Imported Upstream version 1.0.0
[platform/upstream/js.git] / js / src / jsstack.js
1 /*
2  * Check that only JS_REQUIRES_STACK/JS_FORCES_STACK functions, and functions
3  * that have called a JS_FORCES_STACK function, access cx->fp directly or
4  * indirectly.
5  */
6
7 require({ after_gcc_pass: 'cfg' });
8 include('gcc_util.js');
9 include('unstable/adts.js');
10 include('unstable/analysis.js');
11 include('unstable/lazy_types.js');
12 include('unstable/esp.js');
13
14 var Zero_NonZero = {};
15 include('unstable/zero_nonzero.js', Zero_NonZero);
16
17 // Tell MapFactory we don't need multimaps (a speed optimization).
18 MapFactory.use_injective = true;
19
20 /*
21  * There are two regions in the program: RED and GREEN.  Functions and member
22  * variables may be declared RED in the C++ source.  GREEN is the default.
23  *
24  * RED signals danger.  A GREEN part of a function must not call a RED function
25  * or access a RED member.
26  *
27  * The body of a RED function is all red.  The body of a GREEN function is all
28  * GREEN by default, but parts dominated by a call to a TURN_RED function are
29  * red.  This way GREEN functions can safely access RED stuff by calling a
30  * TURN_RED function as preparation.
31  *
32  * The analysis does not attempt to prove anything about the body of a TURN_RED
33  * function.  (Both annotations are trusted; only unannotated code is checked
34  * for errors.)
35  */
36 const RED = 'JS_REQUIRES_STACK';
37 const TURN_RED = 'JS_FORCES_STACK';
38 const IGNORE_ERRORS = 'JS_IGNORE_STACK';
39
40 function attrs(tree) {
41   let a = DECL_P(tree) ? DECL_ATTRIBUTES(tree) : TYPE_ATTRIBUTES(tree);
42   return translate_attributes(a);
43 }
44
45 function hasUserAttribute(tree, attrname) {
46   let attributes = attrs(tree);
47   if (attributes) {
48     for (let i = 0; i < attributes.length; i++) {
49       let attr = attributes[i];
50       if (attr.name == 'user' && attr.value.length == 1 && attr.value[0] == attrname)
51         return true;
52     }
53   }
54   return false;
55 }
56
57 function process_tree_type(d)
58 {
59   let t = dehydra_convert(d);
60   if (t.isFunction)
61     return;
62
63   if (t.typedef !== undefined)
64     if (isRed(TYPE_NAME(d)))
65       warning("Typedef declaration is annotated JS_REQUIRES_STACK: the annotation should be on the type itself", t.loc);
66   
67   if (hasAttribute(t, RED)) {
68     warning("Non-function is annotated JS_REQUIRES_STACK", t.loc);
69     return;
70   }
71   
72   for (let st = t; st !== undefined && st.isPointer; st = st.type) {
73     if (hasAttribute(st, RED)) {
74       warning("Non-function is annotated JS_REQUIRES_STACK", t.loc);
75       return;
76     }
77     
78     if (st.parameters)
79       return;
80   }
81 }
82
83 function process_tree_decl(d)
84 {
85   // For VAR_DECLs, walk the DECL_INITIAL looking for bad assignments
86   if (TREE_CODE(d) != VAR_DECL)
87     return;
88   
89   let i = DECL_INITIAL(d);
90   if (!i)
91     return;
92   
93   assignCheck(i, TREE_TYPE(d), function() { return location_of(d); });
94
95   functionPointerWalk(i, d);
96 }
97
98 /*
99  * x is an expression or decl.  These functions assume that 
100  */
101 function isRed(x) { return hasUserAttribute(x, RED); }
102 function isTurnRed(x) { return hasUserAttribute(x, TURN_RED); }
103
104 function process_tree(fndecl)
105 {
106   if (hasUserAttribute(fndecl, IGNORE_ERRORS))
107     return;
108
109   if (!(isRed(fndecl) || isTurnRed(fndecl))) {
110     // Ordinarily a user of ESP runs the analysis, then generates output based
111     // on the results.  But in our case (a) we need sub-basic-block resolution,
112     // which ESP doesn't keep; (b) it so happens that even though ESP can
113     // iterate over blocks multiple times, in our case that won't cause
114     // spurious output.  (It could cause us to the same error message each time
115     // through--but that's easily avoided.)  Therefore we generate the output
116     // while the ESP analysis is running.
117     let a = new RedGreenCheck(fndecl, 0);
118     if (a.hasRed)
119       a.run();
120   }
121   
122   functionPointerCheck(fndecl);
123 }
124
125 function RedGreenCheck(fndecl, trace) {
126   //print("RedGreenCheck: " + fndecl.toCString());
127   this._fndecl = fndecl;
128
129   // Tell ESP that fndecl is a "property variable".  This makes ESP track it in
130   // a flow-sensitive way.  The variable will be 1 in RED regions and "don't
131   // know" in GREEN regions.  (We are technically lying to ESP about fndecl
132   // being a variable--what we really want is a synthetic variable indicating
133   // RED/GREEN state, but ESP operates on GCC decl nodes.)
134   this._state_var_decl = fndecl;
135   let state_var = new ESP.PropVarSpec(this._state_var_decl, true, undefined);
136
137   // Call base class constructor.
138   let cfg = function_decl_cfg(fndecl);
139   ESP.Analysis.apply(this, [cfg, [state_var], Zero_NonZero.meet, trace]);
140   this.join = Zero_NonZero.join;
141
142   // Preprocess all instructions in the cfg to determine whether this analysis
143   // is necessary and gather some information we'll use later.
144   //
145   // Each isn may include a function call, an assignment, and/or some reads.
146   // Using walk_tree to walk the isns is a little crazy but robust.
147   //
148   this.hasRed = false;
149   let self = this;         // Allow our 'this' to be accessed inside closure
150   for (let bb in cfg_bb_iterator(cfg)) {
151     for (let isn in bb_isn_iterator(bb)) {
152       // Treehydra objects don't support reading never-defined properties
153       // as undefined, so we have to explicitly initialize anything we want
154       // to check for later.
155       isn.redInfo = undefined;
156       walk_tree(isn, function(t, stack) {
157         function getLocation(skiptop) {
158           if (!skiptop) {
159             let loc = location_of(t);
160             if (loc !== undefined)
161               return loc;
162           }
163           
164           for (let i = stack.length - 1; i >= 0; --i) {
165             let loc = location_of(stack[i]);
166             if (loc !== undefined)
167               return loc;
168           }
169           return location_of(fndecl);
170         }
171                   
172         switch (TREE_CODE(t)) {
173           case FIELD_DECL:
174             if (isRed(t)) {
175               let varName = dehydra_convert(t).name;
176               // location_of(t) is the location of the declaration.
177               isn.redInfo = ["cannot access JS_REQUIRES_STACK variable " + varName,
178                              getLocation(true)];
179               self.hasRed = true;
180             }
181             break;
182           case GIMPLE_CALL:
183           {
184             let callee = gimple_call_fndecl(t);
185             if (callee) {
186               if (isRed(callee)) {
187                 let calleeName = dehydra_convert(callee).name;
188                 isn.redInfo = ["cannot call JS_REQUIRES_STACK function " + calleeName,
189                               getLocation(false)];
190                 self.hasRed = true;
191               } else if (isTurnRed(callee)) {
192                 isn.turnRed = true;
193               }
194             }
195             else {
196               let fntype = TREE_CHECK(
197                 TREE_TYPE( // the function type
198                   TREE_TYPE( // the function pointer type
199                     gimple_call_fn(t)
200                   )
201                 ),
202                 FUNCTION_TYPE, METHOD_TYPE);
203               if (isRed(fntype)) {
204                 isn.redInfo = ["cannot call JS_REQUIRES_STACK function pointer",
205                                getLocation(false)];
206                 self.hasRed = true;
207               }
208               else if (isTurnRed(fntype)) {
209                 isn.turnRed = true;
210               }
211             }
212           }
213           break;
214         }
215       });
216     }
217   }
218
219   // Initialize mixin for infeasible-path elimination.
220   this._zeroNonzero = new Zero_NonZero.Zero_NonZero();
221 }
222
223 RedGreenCheck.prototype = new ESP.Analysis;
224
225 RedGreenCheck.prototype.flowStateCond = function(isn, truth, state) {
226   // forward event to mixin
227   this._zeroNonzero.flowStateCond(isn, truth, state);
228 };
229
230 RedGreenCheck.prototype.flowState = function(isn, state) {
231   // forward event to mixin
232   //try { // The try/catch here is a workaround for some baffling bug in zero_nonzero.
233     this._zeroNonzero.flowState(isn, state);
234   //} catch (exc) {
235   //  warning(exc, location_of(isn));
236   //  warning("(Remove the workaround in jsstack.js and recompile to get a JS stack trace.)",
237   //          location_of(isn));
238   //}
239   let stackState = state.get(this._state_var_decl);
240   let green = stackState != 1 && stackState != ESP.NOT_REACHED;
241   let redInfo = isn.redInfo;
242   if (green && redInfo) {
243     warning(redInfo[0], redInfo[1]);
244     isn.redInfo = undefined;  // avoid duplicate messages about this instruction
245   }
246
247   // If we call a TURNS_RED function, it doesn't take effect until after the
248   // whole isn finishes executing (the most conservative rule).
249   if (isn.turnRed)
250     state.assignValue(this._state_var_decl, 1, isn);
251 };
252
253 function followTypedefs(type)
254 {
255   while (type.typedef !== undefined)
256     type = type.typedef;
257   return type;
258 }
259
260 function assignCheck(source, destType, locfunc)
261 {
262   if (TREE_CODE(destType) != POINTER_TYPE)
263     return;
264     
265   let destCode = TREE_CODE(TREE_TYPE(destType));
266   if (destCode != FUNCTION_TYPE && destCode != METHOD_TYPE)
267     return;
268   
269   if (isRed(TREE_TYPE(destType)))
270     return;
271
272   while (TREE_CODE(source) == NOP_EXPR)
273     source = source.operands()[0];
274   
275   // The destination is a green function pointer
276
277   if (TREE_CODE(source) == ADDR_EXPR) {
278     let sourcefn = source.operands()[0];
279     
280     // oddly enough, SpiderMonkey assigns the address of something that's not
281     // a function to a function pointer as part of the API! See JS_TN
282     if (TREE_CODE(sourcefn) != FUNCTION_DECL)
283       return;
284     
285     if (isRed(sourcefn))
286       warning("Assigning non-JS_REQUIRES_STACK function pointer from JS_REQUIRES_STACK function " + dehydra_convert(sourcefn).name, locfunc());
287   } else if (TREE_TYPE(source).tree_code() == POINTER_TYPE) {
288     let sourceType = TREE_TYPE(TREE_TYPE(source));
289     switch (TREE_CODE(sourceType)) {
290       case FUNCTION_TYPE:
291       case METHOD_TYPE:
292         if (isRed(sourceType))
293           warning("Assigning non-JS_REQUIRES_STACK function pointer from JS_REQUIRES_STACK function pointer", locfunc());
294         break;
295     }
296   }
297 }
298
299 /**
300  * A type checker which verifies that a red function pointer is never converted
301  * to a green function pointer.
302  */
303
304 function functionPointerWalk(t, baseloc)
305 {
306   walk_tree(t, function(t, stack) {
307     function getLocation(skiptop) {
308       if (!skiptop) {
309         let loc = location_of(t);
310         if (loc !== undefined)
311           return loc;
312       }
313           
314       for (let i = stack.length - 1; i >= 0; --i) {
315         let loc = location_of(stack[i]);
316         if (loc !== undefined)
317           return loc;
318       }
319       return location_of(baseloc);
320     }
321                   
322     switch (TREE_CODE(t)) {
323       case GIMPLE_ASSIGN: {
324         let [dest, source] = t.operands();
325         assignCheck(source, TREE_TYPE(dest), getLocation);
326         break;
327       }
328       case CONSTRUCTOR: {
329         let ttype = TREE_TYPE(t);
330         switch (TREE_CODE(ttype)) {
331           case RECORD_TYPE:
332           case UNION_TYPE: {
333             for each (let ce in VEC_iterate(CONSTRUCTOR_ELTS(t)))
334               assignCheck(ce.value, TREE_TYPE(ce.index), getLocation);
335             break;
336           }
337           case ARRAY_TYPE: {
338             let eltype = TREE_TYPE(ttype);
339             for each (let ce in VEC_iterate(CONSTRUCTOR_ELTS(t)))
340               assignCheck(ce.value, eltype, getLocation);
341             break;
342           }
343           case LANG_TYPE:
344             // these can be safely ignored
345             break;
346           default:
347             warning("Unexpected type in initializer: " + TREE_CODE(TREE_TYPE(t)), getLocation());
348         }
349         break;
350       }
351       case GIMPLE_CALL: {
352         // Check that the arguments to a function and the declared types
353         // of those arguments are compatible.
354         let ops = t.operands();
355         let funcType = TREE_TYPE( // function type
356           TREE_TYPE(ops[1])); // function pointer type
357         let argTypes = [t for (t in function_type_args(funcType))];
358         for (let i = argTypes.length - 1; i >= 0; --i) {
359           let destType = argTypes[i];
360           let source = ops[i + 3];
361           assignCheck(source, destType, getLocation);
362         }
363         break;
364       }
365     }
366   });
367 }
368   
369 function functionPointerCheck(fndecl)
370 {
371   let cfg = function_decl_cfg(fndecl);
372   for (let bb in cfg_bb_iterator(cfg))
373     for (let isn in bb_isn_iterator(bb))
374       functionPointerWalk(isn, fndecl);
375 }