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
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');
14 var Zero_NonZero = {};
15 include('unstable/zero_nonzero.js', Zero_NonZero);
17 // Tell MapFactory we don't need multimaps (a speed optimization).
18 MapFactory.use_injective = true;
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.
24 * RED signals danger. A GREEN part of a function must not call a RED function
25 * or access a RED member.
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.
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
36 const RED = 'JS_REQUIRES_STACK';
37 const TURN_RED = 'JS_FORCES_STACK';
38 const IGNORE_ERRORS = 'JS_IGNORE_STACK';
40 function attrs(tree) {
41 let a = DECL_P(tree) ? DECL_ATTRIBUTES(tree) : TYPE_ATTRIBUTES(tree);
42 return translate_attributes(a);
45 function hasUserAttribute(tree, attrname) {
46 let attributes = attrs(tree);
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)
57 function process_tree_type(d)
59 let t = dehydra_convert(d);
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);
67 if (hasAttribute(t, RED)) {
68 warning("Non-function is annotated JS_REQUIRES_STACK", t.loc);
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);
83 function process_tree_decl(d)
85 // For VAR_DECLs, walk the DECL_INITIAL looking for bad assignments
86 if (TREE_CODE(d) != VAR_DECL)
89 let i = DECL_INITIAL(d);
93 assignCheck(i, TREE_TYPE(d), function() { return location_of(d); });
95 functionPointerWalk(i, d);
99 * x is an expression or decl. These functions assume that
101 function isRed(x) { return hasUserAttribute(x, RED); }
102 function isTurnRed(x) { return hasUserAttribute(x, TURN_RED); }
104 function process_tree(fndecl)
106 if (hasUserAttribute(fndecl, IGNORE_ERRORS))
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);
122 functionPointerCheck(fndecl);
125 function RedGreenCheck(fndecl, trace) {
126 //print("RedGreenCheck: " + fndecl.toCString());
127 this._fndecl = fndecl;
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);
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;
142 // Preprocess all instructions in the cfg to determine whether this analysis
143 // is necessary and gather some information we'll use later.
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.
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) {
159 let loc = location_of(t);
160 if (loc !== undefined)
164 for (let i = stack.length - 1; i >= 0; --i) {
165 let loc = location_of(stack[i]);
166 if (loc !== undefined)
169 return location_of(fndecl);
172 switch (TREE_CODE(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,
184 let callee = gimple_call_fndecl(t);
187 let calleeName = dehydra_convert(callee).name;
188 isn.redInfo = ["cannot call JS_REQUIRES_STACK function " + calleeName,
191 } else if (isTurnRed(callee)) {
196 let fntype = TREE_CHECK(
197 TREE_TYPE( // the function type
198 TREE_TYPE( // the function pointer type
202 FUNCTION_TYPE, METHOD_TYPE);
204 isn.redInfo = ["cannot call JS_REQUIRES_STACK function pointer",
208 else if (isTurnRed(fntype)) {
219 // Initialize mixin for infeasible-path elimination.
220 this._zeroNonzero = new Zero_NonZero.Zero_NonZero();
223 RedGreenCheck.prototype = new ESP.Analysis;
225 RedGreenCheck.prototype.flowStateCond = function(isn, truth, state) {
226 // forward event to mixin
227 this._zeroNonzero.flowStateCond(isn, truth, state);
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);
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));
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
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).
250 state.assignValue(this._state_var_decl, 1, isn);
253 function followTypedefs(type)
255 while (type.typedef !== undefined)
260 function assignCheck(source, destType, locfunc)
262 if (TREE_CODE(destType) != POINTER_TYPE)
265 let destCode = TREE_CODE(TREE_TYPE(destType));
266 if (destCode != FUNCTION_TYPE && destCode != METHOD_TYPE)
269 if (isRed(TREE_TYPE(destType)))
272 while (TREE_CODE(source) == NOP_EXPR)
273 source = source.operands()[0];
275 // The destination is a green function pointer
277 if (TREE_CODE(source) == ADDR_EXPR) {
278 let sourcefn = source.operands()[0];
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)
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)) {
292 if (isRed(sourceType))
293 warning("Assigning non-JS_REQUIRES_STACK function pointer from JS_REQUIRES_STACK function pointer", locfunc());
300 * A type checker which verifies that a red function pointer is never converted
301 * to a green function pointer.
304 function functionPointerWalk(t, baseloc)
306 walk_tree(t, function(t, stack) {
307 function getLocation(skiptop) {
309 let loc = location_of(t);
310 if (loc !== undefined)
314 for (let i = stack.length - 1; i >= 0; --i) {
315 let loc = location_of(stack[i]);
316 if (loc !== undefined)
319 return location_of(baseloc);
322 switch (TREE_CODE(t)) {
323 case GIMPLE_ASSIGN: {
324 let [dest, source] = t.operands();
325 assignCheck(source, TREE_TYPE(dest), getLocation);
329 let ttype = TREE_TYPE(t);
330 switch (TREE_CODE(ttype)) {
333 for each (let ce in VEC_iterate(CONSTRUCTOR_ELTS(t)))
334 assignCheck(ce.value, TREE_TYPE(ce.index), getLocation);
338 let eltype = TREE_TYPE(ttype);
339 for each (let ce in VEC_iterate(CONSTRUCTOR_ELTS(t)))
340 assignCheck(ce.value, eltype, getLocation);
344 // these can be safely ignored
347 warning("Unexpected type in initializer: " + TREE_CODE(TREE_TYPE(t)), getLocation());
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);
369 function functionPointerCheck(fndecl)
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);