Bug 575870 - Enable the firefox button on xp themed, classic, and aero basic. r=dao...
[mozilla-central.git] / xpcom / analysis / outparams.js
blobf6803ddb76031945da2125fa25ade1a7575a26fb
1 require({ version: '1.8' });
2 require({ after_gcc_pass: 'cfg' });
4 include('treehydra.js');
6 include('util.js');
7 include('gcc_util.js');
8 include('gcc_print.js');
9 include('unstable/adts.js');
10 include('unstable/analysis.js');
11 include('unstable/esp.js');
12 let Zero_NonZero = {};
13 include('unstable/zero_nonzero.js', Zero_NonZero);
15 include('xpcom/analysis/mayreturn.js');
17 function safe_location_of(t) {
18   if (t === undefined)
19     return UNKNOWN_LOCATION;
20   
21   return location_of(t);
24 MapFactory.use_injective = true;
26 // Print a trace for each function analyzed
27 let TRACE_FUNCTIONS = 0;
28 // Trace operation of the ESP analysis, use 2 or 3 for more detail
29 let TRACE_ESP = 0;
30 // Trace determination of function call parameter semantics, 2 for detail
31 let TRACE_CALL_SEM = 0;
32 // Print time-taken stats 
33 let TRACE_PERF = 0;
34 // Log analysis results in a special format
35 let LOG_RESULTS = false;
37 const WARN_ON_SET_NULL = false;
38 const WARN_ON_SET_FAILURE = false;
40 // Filter functions to process per CLI
41 let func_filter;
42 if (this.arg == undefined || this.arg == '') {
43   func_filter = function(fd) true;
44 } else {
45   func_filter = function(fd) function_decl_name(fd) == this.arg;
48 function process_tree(func_decl) {
49   if (!func_filter(func_decl)) return;
51   // Determine outparams and return if function not relevant
52   if (DECL_CONSTRUCTOR_P(func_decl)) return;
53   let psem = OutparamCheck.prototype.func_param_semantics(func_decl);
54   if (!psem.some(function(x) x.check)) return;
55   let decl = rectify_function_decl(func_decl);
56   if (decl.resultType != 'nsresult' && decl.resultType != 'PRBool' &&
57       decl.resultType != 'void') {
58     warning("Cannot analyze outparam usage for function with return type '" +
59             decl.resultType + "'", location_of(func_decl));
60     return;
61   }
63   let params = [ v for (v in flatten_chain(DECL_ARGUMENTS(func_decl))) ];
64   let outparam_list = [];
65   let psem_list = [];
66   for (let i = 0; i < psem.length; ++i) {
67     if (psem[i].check) {
68       outparam_list.push(params[i]);
69       psem_list.push(psem[i]);
70     }
71   }
72   if (outparam_list.length == 0) return;
74   // At this point we have a function we want to analyze
75   let fstring = rfunc_string(decl);
76   if (TRACE_FUNCTIONS) {
77     print('* function ' + fstring);
78     print('    ' + loc_string(location_of(func_decl)));
79   }
80   if (TRACE_PERF) timer_start(fstring);
81   for (let i = 0; i < outparam_list.length; ++i) {
82     let p = outparam_list[i];
83     if (TRACE_FUNCTIONS) {
84       print("  outparam " + expr_display(p) + " " + DECL_UID(p) + ' ' + 
85             psem_list[i].label);
86     }
87   }
89   let cfg = function_decl_cfg(func_decl);
91   let [retvar, retvars] = function() {
92     let trace = 0;
93     let a = new MayReturnAnalysis(cfg, trace);
94     a.run();
95     return [a.retvar, a.vbls];
96   }();
97   if (retvar == undefined && decl.resultType != 'void') throw new Error("assert");
99   {
100     let trace = TRACE_ESP;
101     for (let i = 0; i < outparam_list.length; ++i) {
102       let psem = [ psem_list[i] ];
103       let outparam = [ outparam_list[i] ];
104       let a = new OutparamCheck(cfg, psem, outparam, retvar, retvars, trace);
105       // This is annoying, but this field is only used for logging anyway.
106       a.fndecl = func_decl;
107       a.run();
108       a.check(decl.resultType == 'void', func_decl);
109     }
110   }
111   
112   if (TRACE_PERF) timer_stop(fstring);
115 // Outparam check analysis
116 function OutparamCheck(cfg, psem_list, outparam_list, retvar, retvar_set, 
117                        trace) {
118   // We need to save the retvars so we can detect assignments through
119   // their addresses passed as arguments.
120   this.retvar_set = retvar_set;
121   this.retvar = retvar;
123   // We need both an ordered set and a lookup structure
124   this.outparam_list = outparam_list
125   this.outparams = create_decl_set(outparam_list);
126   this.psem_list = psem_list;
128   // Set up property state vars for ESP
129   let psvar_list = [];
130   for each (let v in outparam_list) {
131     psvar_list.push(new ESP.PropVarSpec(v, true, av.NOT_WRITTEN));
132   }
133   for (let v in retvar_set.items()) {
134     psvar_list.push(new ESP.PropVarSpec(v, v == this.retvar, ESP.TOP));
135   }
136   if (trace) {
137     print("PS vars");
138     for each (let v in this.psvar_list) {
139       print("    " + expr_display(v.vbl));
140     }
141   }
142   this.zeroNonzero = new Zero_NonZero.Zero_NonZero();
143   ESP.Analysis.call(this, cfg, psvar_list, av.meet, trace);
146 // Abstract values for outparam check
147 function AbstractValue(name, ch) {
148   this.name = name;
149   this.ch = ch;
152 AbstractValue.prototype.equals = function(v) {
153   return this === v;
156 AbstractValue.prototype.toString = function() {
157   return this.name + ' (' + this.ch + ')';
160 AbstractValue.prototype.toShortString = function() {
161   return this.ch;
164 let avspec = [
165   // Abstract values for outparam contents write status
166   [ 'NULL',          'x' ],   // is a null pointer
167   [ 'NOT_WRITTEN',   '-' ],   // not written
168   [ 'WROTE_NULL',    '/' ],   // had NULL written to
169   [ 'WRITTEN',       '+' ],   // had anything written to
170   // MAYBE_WRITTEN is special. "Officially", it means the same thing as
171   // NOT_WRITTEN. What it really means is that an outparam was passed
172   // to another function as a possible outparam (outparam type, but not
173   // in last position), so if there is an error with it not being written,
174   // we can give a hint about the possible outparam in the warning.
175   [ 'MAYBE_WRITTEN', '?' ],   // written if possible outparam is one
178 let av = {};
179 for each (let [name, ch] in avspec) {
180   av[name] = new AbstractValue(name, ch);
183 av.ZERO = Zero_NonZero.Lattice.ZERO;
184 av.NONZERO = Zero_NonZero.Lattice.NONZERO;
187 av.ZERO.negation = av.NONZERO;
188 av.NONZERO.negation = av.ZERO;
190 // Abstract values for int constants. We use these to figure out feasible
191 // paths in the presence of GCC finally_tmp-controlled switches.
192 function makeIntAV(v) {
193   let key = 'int_' + v;
194   if (cachedAVs.hasOwnProperty(key)) return cachedAVs[key];
196   let s = "" + v;
197   let ans = cachedAVs[key] = new AbstractValue(s, s);
198   ans.int_val = v;
199   return ans;
203 let cachedAVs = {};
205 // Abstract values for pointers that contain a copy of an outparam 
206 // pointer. We use these to figure out writes to a casted copy of 
207 // an outparam passed to another method.
208 function makeOutparamAV(v) {
209   let key = 'outparam_' + DECL_UID(v);
210   if (key in cachedAVs) return cachedAVs[key];
212   let ans = cachedAVs[key] = 
213     new AbstractValue('OUTPARAM:' + expr_display(v), 'P');
214   ans.outparam = v;
215   return ans;
218 /** Return the integer value if this is an integer av, otherwise undefined. */
219 av.intVal = function(v) {
220   if (v.hasOwnProperty('int_val'))
221     return v.int_val;
222   return undefined;
225 /** Meet function for our abstract values. */
226 av.meet = function(v1, v2) {
227   // At this point we know v1 != v2.
228   let values = [v1,v2]
229   if (values.indexOf(av.LOCKED) != -1
230       || values.indexOf(av.UNLOCKED) != -1)
231     return ESP.NOT_REACHED;
233   return Zero_NonZero.meet(v1, v2)
236 // Outparam check analysis
237 OutparamCheck.prototype = new ESP.Analysis;
239 OutparamCheck.prototype.split = function(vbl, v) {
240   // Can't happen for current version of ESP, but could change
241   if (v != ESP.TOP) throw new Error("not implemented");
242   return [ av.ZERO, av.NONZERO ];
245 OutparamCheck.prototype.updateEdgeState = function(e) {
246   e.state.keepOnly(e.dest.keepVars);
249 OutparamCheck.prototype.flowState = function(isn, state) {
250   switch (TREE_CODE(isn)) {
251   case GIMPLE_ASSIGN:
252     this.processAssign(isn, state);
253     break;
254   case GIMPLE_CALL:
255     this.processCall(isn, isn, state);
256     break;
257   case GIMPLE_SWITCH:
258   case GIMPLE_COND:
259     // This gets handled by flowStateCond instead, has no exec effect
260     break;
261   default:
262     this.zeroNonzero.flowState(isn, state);
263   }
266 OutparamCheck.prototype.flowStateCond = function(isn, truth, state) {
267   this.zeroNonzero.flowStateCond(isn, truth, state);
270 // For any outparams-specific semantics, we handle it here and then
271 // return. Otherwise we delegate to the zero-nonzero analysis.
272 OutparamCheck.prototype.processAssign = function(isn, state) {
273   let lhs = gimple_op(isn, 0);
274   let rhs = gimple_op(isn, 1);
276   if (DECL_P(lhs)) {
277     // Unwrap NOP_EXPR, which is semantically a copy.
278     if (TREE_CODE(rhs) == NOP_EXPR) {
279       rhs = rhs.operands()[0];
280     }
282     if (DECL_P(rhs) && this.outparams.has(rhs)) {
283         // Copying an outparam pointer. We have to remember this so that
284         // if it is assigned thru later, we pick up the write.
285         state.assignValue(lhs, makeOutparamAV(rhs), isn);
286         return;
287     }
289     // Cases of this switch that handle something should return from
290     // the function. Anything that does not return is picked up afteward.
291     switch (TREE_CODE(rhs)) {
292     case INTEGER_CST:
293       if (this.outparams.has(lhs)) {
294         warning("assigning to outparam pointer");
295         return;
296       }
297       break;
298     case EQ_EXPR: {
299       // We only care about testing outparams for NULL (and then not writing)
300       let [op1, op2] = rhs.operands();
301       if (DECL_P(op1) && this.outparams.has(op1) && expr_literal_int(op2) == 0) {
302         state.update(function(ss) {
303           let [s1, s2] = [ss, ss.copy()]; // s1 true, s2 false
304           s1.assignValue(lhs, av.NONZERO, isn);
305           s1.assignValue(op1, av.NULL, isn);
306           s2.assignValue(lhs, av.ZERO, isn);
307           return [s1, s2];
308         });
309         return;
310       }
311     }
312       break;
313     case CALL_EXPR:
314       /* Embedded CALL_EXPRs are a 4.3 issue */
315       this.processCall(rhs, isn, state, lhs);
316       return;
318     case INDIRECT_REF:
319       // If rhs is *outparam and pointer-typed, lhs is NULL iff rhs is
320       // WROTE_NULL. Required for testcase onull.cpp.
321       let v = rhs.operands()[0];
322       if (DECL_P(v) && this.outparams.has(v) && 
323           TREE_CODE(TREE_TYPE(v)) == POINTER_TYPE) {
324         state.update(function(ss) {
325           let val = ss.get(v) == av.WROTE_NULL ? av.ZERO : av.NONZERO;
326           ss.assignValue(lhs, val, isn);
327           return [ ss ];
328         });
329         return;
330       }
331     }
333     // Nothing special -- delegate
334     this.zeroNonzero.processAssign(isn, state);
335     return;
336   }
338   switch (TREE_CODE(lhs)) {
339   case INDIRECT_REF:
340     // Writing to an outparam. We want to try to figure out if we're 
341     // writing NULL.
342     let e = TREE_OPERAND(lhs, 0);
343     if (this.outparams.has(e)) {
344       if (expr_literal_int(rhs) == 0) {
345         state.assignValue(e, av.WROTE_NULL, isn);
346       } else if (DECL_P(rhs)) {
347         state.update(function(ss) {
348           let [s1, s2] = [ss.copy(), ss]; // s1 NULL, s2 non-NULL
349           s1.assignValue(e, av.WROTE_NULL, isn);
350           s1.assignValue(rhs, av.ZERO, isn);
351           s2.assignValue(e, av.WRITTEN, isn);
352           s2.assignValue(rhs, av.NONZERO, isn);
353           return [s1,s2];
354         });
355       } else {
356         state.assignValue(e, av.WRITTEN, isn);
357       }
358     } else {
359       // unsound -- could be writing to anything through this ptr
360     }
361     break;
362   case COMPONENT_REF: // unsound
363   case ARRAY_REF: // unsound
364   case EXC_PTR_EXPR:
365   case FILTER_EXPR:
366     break;
367   default:
368     print(TREE_CODE(lhs));
369     throw new Error("ni");
370   }
373 // Handle an assignment x := test(foo) where test is a simple predicate
374 OutparamCheck.prototype.processTest = function(lhs, call, val, blame, state) {
375   let arg = gimple_call_arg(call, 0);
376   if (DECL_P(arg)) {
377     this.zeroNonzero.predicate(state, lhs, val, arg, blame);
378   } else {
379     state.assignValue(lhs, ESP.TOP, blame);
380   }
383 // The big one: outparam semantics of function calls.
384 OutparamCheck.prototype.processCall = function(call, blame, state, dest) {
385   if (!dest)
386     dest = gimple_call_lhs(call);
388   let args = gimple_call_args(call);
389   let callable = callable_arg_function_decl(gimple_call_fn(call));
390   let psem = this.func_param_semantics(callable);
392   let name = function_decl_name(callable);
393   if (name == 'NS_FAILED') {
394     this.processTest(dest, call, av.NONZERO, call, state);
395     return;
396   } else if (name == 'NS_SUCCEEDED') {
397     this.processTest(dest, call, av.ZERO, call, state);
398     return;
399   } else if (name == '__builtin_expect') {
400     // Same as an assign from arg 0 to lhs
401     state.assign(dest, args[0], call);
402     return;
403   }
405   if (TRACE_CALL_SEM) {
406     print("param semantics:" + psem);
407   }
409   if (args.length != psem.length) {
410     let ct = TREE_TYPE(callable);
411     if (TREE_CODE(ct) == POINTER_TYPE) ct = TREE_TYPE(ct);
412     if (args.length < psem.length || !stdarg_p(ct)) {
413       // TODO Can __builtin_memcpy write to an outparam? Probably not.
414       if (name != 'operator new' && name != 'operator delete' &&
415           name != 'operator new []' && name != 'operator delete []' &&
416           name.substr(0, 5) != '__cxa' &&
417           name.substr(0, 9) != '__builtin') {
418         throw Error("bad len for '" + name + "': " + args.length + ' args, ' + 
419                     psem.length + ' params');
420       }
421     }
422   }
424   // Collect variables that are possibly written to on callee success
425   let updates = [];
426   for (let i = 0; i < psem.length; ++i) {
427     let arg = args[i];
428     // The arg could be the address of a return-value variable.
429     // This means it's really the nsresult code for the call,
430     // so we treat it the same as the target of an rv assignment.
431     if (TREE_CODE(arg) == ADDR_EXPR) {
432       let v = arg.operands()[0];
433       if (DECL_P(v) && this.retvar_set.has(v)) {
434         dest = v;
435       }
436     }
437     // The arg could be a copy of an outparam. We'll unwrap to the
438     // outparam if it is. The following is cheating a bit because
439     // we munge states together, but it should be OK in practice.
440     arg = unwrap_outparam(arg, state);
441     let sem = psem[i];
442     if (sem == ps.CONST) continue;
443     // At this point, we know the call can write thru this param.
444     // Invalidate any vars whose addresses are passed here. This
445     // is distinct from the rv handling above.
446     if (TREE_CODE(arg) == ADDR_EXPR) {
447       let v = arg.operands()[0];
448       if (DECL_P(v)) {
449         state.remove(v);
450       }
451     }
452     if (!DECL_P(arg) || !this.outparams.has(arg)) continue;
453     // At this point, we may be writing to an outparam 
454     updates.push([arg, sem]);
455   }
456   
457   if (updates.length) {
458     if (dest != undefined && DECL_P(dest)) {
459       // Update & stored rv. Do updates predicated on success.
460       let [ succ_ret, fail_ret ] = ret_coding(callable);
462       state.update(function(ss) {
463         let [s1, s2] = [ss.copy(), ss]; // s1 success, s2 fail
464         for each (let [vbl, sem] in updates) {
465           s1.assignValue(vbl, sem.val, blame);
466           s1.assignValue(dest, succ_ret, blame);
467         }
468         s2.assignValue(dest, fail_ret, blame);
469         return [s1,s2];
470       });
471     } else {
472       // Discarded rv. Per spec in the bug, we assume that either success
473       // or failure is possible (if not, callee should return void).
474       // Exceptions: Methods that return void and string mutators are
475       //     considered no-fail.
476       state.update(function(ss) {
477         for each (let [vbl, sem] in updates) {
478           if (sem == ps.OUTNOFAIL || sem == ps.OUTNOFAILNOCHECK) {
479             ss.assignValue(vbl, av.WRITTEN, blame);
480             return [ss];
481           } else {
482             let [s1, s2] = [ss.copy(), ss]; // s1 success, s2 fail
483             for each (let [vbl, sem] in updates) {
484               s1.assignValue(vbl, sem.val, blame);
485             }
486             return [s1,s2];
487           }
488         }
489       });
490     }
491   } else {
492     // no updates, just kill any destination for the rv
493     if (dest != undefined && DECL_P(dest)) {
494       state.remove(dest, blame);
495     }
496   }
499 /** Return the return value coding of the given function. This is a pair
500  *  [ succ, fail ] giving the abstract values of the return value under
501  *  success and failure conditions. */
502 function ret_coding(callable) {
503   let type = TREE_TYPE(callable);
504   if (TREE_CODE(type) == POINTER_TYPE) type = TREE_TYPE(type);
506   let rtname = TYPE_NAME(TREE_TYPE(type));
507   if (rtname && IDENTIFIER_POINTER(DECL_NAME(rtname)) == 'PRBool') {
508     return [ av.NONZERO, av.ZERO ];
509   } else {
510     return [ av.ZERO, av.NONZERO ];
511   }
514 function unwrap_outparam(arg, state) {
515   if (!DECL_P(arg) || state.factory.outparams.has(arg)) return arg;
517   let outparam;
518   for (let ss in state.substates.getValues()) {
519     let val = ss.get(arg);
520     if (val != undefined && val.hasOwnProperty('outparam')) {
521       outparam = val.outparam;
522     }
523   }
524   if (outparam) return outparam;
525   return arg;
528 // Check for errors. Must .run() analysis before calling this.
529 OutparamCheck.prototype.check = function(isvoid, fndecl) {
530   let state = this.cfg.x_exit_block_ptr.stateOut;
531   for (let substate in state.substates.getValues()) {
532     this.checkSubstate(isvoid, fndecl, substate);
533   }
536 OutparamCheck.prototype.checkSubstate = function(isvoid, fndecl, ss) {
537   if (isvoid) {
538     this.checkSubstateSuccess(ss);
539   } else {
540     let [succ, fail] = ret_coding(fndecl);
541     let rv = ss.get(this.retvar);
542     // We want to check if the abstract value of the rv is entirely
543     // contained in the success or failure condition.
544     if (av.meet(rv, succ) == rv) {
545       this.checkSubstateSuccess(ss);
546     } else if (av.meet(rv, fail) == rv) {
547       this.checkSubstateFailure(ss);
548     } else {
549       // This condition indicates a bug in outparams.js. We'll just
550       // warn so we don't break static analysis builds.
551       warning("Outparams checker cannot determine rv success/failure",
552               location_of(fndecl));
553       this.checkSubstateSuccess(ss);
554       this.checkSubstateFailure(ss);
555     }
556   }
559 /* @return     The return statement in the function
560  *             that writes the return value in the given substate.
561  *             If the function returns void, then the substate doesn't
562  *             matter and we just look for the return. */
563 OutparamCheck.prototype.findReturnStmt = function(ss) {
564   if (this.retvar != undefined)
565     return ss.getBlame(this.retvar);
567   if (this.cfg._cached_return)
568     return this.cfg._cached_return;
569   
570   for (let bb in cfg_bb_iterator(this.cfg)) {
571     for (let isn in bb_isn_iterator(bb)) {
572       if (isn.tree_code() == GIMPLE_RETURN) {
573         return this.cfg._cached_return = isn;
574       }
575     }
576   }
578   return undefined;
581 OutparamCheck.prototype.checkSubstateSuccess = function(ss) {
582   for (let i = 0; i < this.psem_list.length; ++i) {
583     let [v, psem] = [ this.outparam_list[i], this.psem_list[i] ];
584     if (psem == ps.INOUT) continue;
585     let val = ss.get(v);
586     if (val == av.NOT_WRITTEN) {
587       this.logResult('succ', 'not_written', 'error');
588       this.warn([this.findReturnStmt(ss), "outparam '" + expr_display(v) + "' not written on NS_SUCCEEDED(return value)"],
589                 [v, "outparam declared here"]);
590     } else if (val == av.MAYBE_WRITTEN) {
591       this.logResult('succ', 'maybe_written', 'error');
593       let blameStmt = ss.getBlame(v);
594       let callMsg;
595       let callName = "";
596       try {
597         let call = TREE_CHECK(blameStmt, GIMPLE_CALL, GIMPLE_MODIFY_STMT);
598         let callDecl = callable_arg_function_decl(gimple_call_fn(call));
599         
600         callMsg = [callDecl, "declared here"];
601         callName = " '" + decl_name(callDecl) + "'";
602       }
603       catch (e if e.TreeCheckError) { }
604       
605       this.warn([this.findReturnStmt(ss), "outparam '" + expr_display(v) + "' not written on NS_SUCCEEDED(return value)"],
606                 [v, "outparam declared here"],
607                 [blameStmt, "possibly written by unannotated function call" + callName],
608                 callMsg);
609     } else {
610       this.logResult('succ', '', 'ok');
611     }
612   }    
615 OutparamCheck.prototype.checkSubstateFailure = function(ss) {
616   for (let i = 0; i < this.psem_list.length; ++i) {
617     let [v, ps] = [ this.outparam_list[i], this.psem_list[i] ];
618     let val = ss.get(v);
619     if (val == av.WRITTEN) {
620       this.logResult('fail', 'written', 'error');
621       if (WARN_ON_SET_FAILURE) {
622         this.warn([this.findReturnStmt(ss), "outparam '" + expr_display(v) + "' written on NS_FAILED(return value)"],
623                   [v, "outparam declared here"],
624                   [ss.getBlame(v), "written here"]);
625       }
626     } else if (val == av.WROTE_NULL) {
627       this.logResult('fail', 'wrote_null', 'warning');
628       if (WARN_ON_SET_NULL) {
629         this.warn([this.findReturnStmt(ss), "NULL written to outparam '" + expr_display(v) + "' on NS_FAILED(return value)"],
630                   [v, "outparam declared here"],
631                   [ss.getBlame(v), "written here"]);
632       }
633     } else {
634       this.logResult('fail', '', 'ok');
635     }
636   }    
640  * Generate a warning from one or more tuples [treeforloc, message]
641  */
642 OutparamCheck.prototype.warn = function(arg0) {
643   let loc = safe_location_of(arg0[0]);
644   let msg = arg0[1];
646   for (let i = 1; i < arguments.length; ++i) {
647     if (arguments[i] === undefined) continue;
648     let [atree, amsg] = arguments[i];
649     msg += "\n" + loc_string(safe_location_of(atree)) + ":   " + amsg;
650   }
651   warning(msg, loc);
654 OutparamCheck.prototype.logResult = function(rv, msg, kind) {
655   if (LOG_RESULTS) {
656     let s = [ '"' + x + '"' for each (x in [ loc_string(location_of(this.fndecl)), function_decl_name(this.fndecl), rv, msg, kind ]) ].join(', ');
657     print(":LR: (" + s + ")");
658   }
661 // Parameter Semantics values -- indicates whether a parameter is
662 // an outparam.
663 //    label    Used for debugging output
664 //    val      Abstract value (state) that holds on an argument after
665 //             a call
666 //    check    True if parameters with this semantics should be
667 //             checked by this analysis
668 let ps = {
669   OUTNOFAIL: { label: 'out-no-fail', val: av.WRITTEN,  check: true },
670   // Special value for receiver of strings methods. Callers should
671   // consider this to be an outparam (i.e., it modifies the string),
672   // but we don't want to check the method itself.
673   OUTNOFAILNOCHECK: { label: 'out-no-fail-no-check' },
674   OUT:       { label: 'out',         val: av.WRITTEN,  check: true },
675   INOUT:     { label: 'inout',       val: av.WRITTEN,  check: true },
676   MAYBE:     { label: 'maybe',       val: av.MAYBE_WRITTEN},  // maybe out
677   CONST:     { label: 'const' }   // i.e. not out
680 // Return the param semantics of a FUNCTION_DECL or VAR_DECL representing
681 // a function pointer. The result is a pair [ ann, sems ].
682 OutparamCheck.prototype.func_param_semantics = function(callable) {
683   let ftype = TREE_TYPE(callable);
684   if (TREE_CODE(ftype) == POINTER_TYPE) ftype = TREE_TYPE(ftype);
685   // What failure semantics to use for outparams
686   let rtype = TREE_TYPE(ftype);
687   let nofail = TREE_CODE(rtype) == VOID_TYPE;
688   // Whether to guess outparams by type
689   let guess = type_string(rtype) == 'nsresult';
691   // Set up param lists for analysis
692   let params;     // param decls, if available
693   let types;      // param types
694   let string_mutator = false;
695   if (TREE_CODE(callable) == FUNCTION_DECL) {
696     params = [ p for (p in function_decl_params(callable)) ];
697     types = [ TREE_TYPE(p) for each (p in params) ];
698     string_mutator = is_string_mutator(callable);
699   } else {
700     types = [ p for (p in function_type_args(ftype)) 
701                 if (TREE_CODE(p) != VOID_TYPE) ];
702   }
704   // Analyze params
705   let ans = [];
706   for (let i = 0; i < types.length; ++i) {
707     let sem;
708     if (i == 0 && string_mutator) {
709       // Special case: string mutator receiver is an no-fail outparams
710       //               but not checkable
711       sem = ps.OUTNOFAILNOCHECK;
712     } else {
713       if (params) sem = decode_attr(DECL_ATTRIBUTES(params[i]));
714       if (TRACE_CALL_SEM >= 2) print("param " + i + ": annotated " + sem);
715       if (sem == undefined) {
716         sem = decode_attr(TYPE_ATTRIBUTES(types[i]));
717         if (TRACE_CALL_SEM >= 2) print("type " + i + ": annotated " + sem);
718         if (sem == undefined) {
719           if (guess && type_is_outparam(types[i])) {
720             // Params other than last are guessed as MAYBE
721             sem = i < types.length - 1 ? ps.MAYBE : ps.OUT;
722           } else {
723             sem = ps.CONST;
724           }
725         }
726       }
727       if (sem == ps.OUT && nofail) sem = ps.OUTNOFAIL;
728     }
729     if (sem == undefined) throw new Error("assert");
730     ans.push(sem);
731   }
732   return ans;
735 /* Decode parameter semantics GCC attributes.
736  * @param attrs    GCC attributes of a parameter. E.g., TYPE_ATTRIBUTES
737  *                 or DECL_ATTRIBUTES of an item
738  * @return         The parameter semantics value defined by the attributes,
739  *                 or undefined if no such attributes were present. */
740 function decode_attr(attrs) {
741   // Note: we're not checking for conflicts, we just take the first
742   // one we find.
743   for each (let attr in rectify_attributes(attrs)) {
744     if (attr.name == 'user') {
745       for each (let arg in attr.args) {
746         if (arg == 'NS_outparam') {
747           return ps.OUT;
748         } else if (arg == 'NS_inoutparam') {
749           return ps.INOUT;
750         } else if (arg == 'NS_inparam') {
751           return ps.CONST;
752         }
753       }
754     }
755   }
756   return undefined;
759 /* @return       true if the given type appears to be an outparam
760  *               type based on the type alone (i.e., not considering
761  *               attributes. */
762 function type_is_outparam(type) {
763   switch (TREE_CODE(type)) {
764   case POINTER_TYPE:
765     return pointer_type_is_outparam(TREE_TYPE(type));
766   case REFERENCE_TYPE:
767     let rt = TREE_TYPE(type);
768     return !TYPE_READONLY(rt) && is_string_type(rt);
769   default:
770     // Note: This is unsound for UNION_TYPE, because the union could
771     //       contain a pointer.
772     return false;
773   }
776 /* Helper for type_is_outparam.
777  * @return      true if 'pt *' looks like an outparam type. */
778 function pointer_type_is_outparam(pt) {
779   if (TYPE_READONLY(pt)) return false;
781   switch (TREE_CODE(pt)) {
782   case POINTER_TYPE:
783   case ARRAY_TYPE: {
784     // Look for void **, nsIFoo **, char **, PRUnichar **
785     let ppt = TREE_TYPE(pt);
786     let tname = TYPE_NAME(ppt);
787     if (tname == undefined) return false;
788     let name = decl_name_string(tname);
789     return name == 'void' || name == 'char' || name == 'PRUnichar' ||
790       name.substr(0, 3) == 'nsI';
791   }
792   case INTEGER_TYPE: {
793     // char * and PRUnichar * are probably strings, otherwise guess
794     // it is an integer outparam.
795     let name = decl_name_string(TYPE_NAME(pt));
796     return name != 'char' && name != 'PRUnichar';
797   }
798   case ENUMERAL_TYPE:
799   case REAL_TYPE:
800   case UNION_TYPE:
801   case BOOLEAN_TYPE:
802     return true;
803   case RECORD_TYPE:
804     // TODO: should we consider field writes?
805     return false;
806   case FUNCTION_TYPE:
807   case VOID_TYPE:
808     return false;
809   default:
810     throw new Error("can't guess if a pointer to this type is an outparam: " +
811                     TREE_CODE(pt) + ': ' + type_string(pt));
812   }
815 // Map type name to boolean as to whether it is a string.
816 let cached_string_types = MapFactory.create_map(
817   function (x, y) x == y,
818   function (x) x,
819   function (t) t,
820   function (t) t);
822 // Base string types. Others will be found by searching the inheritance
823 // graph.
825 cached_string_types.put('nsAString', true);
826 cached_string_types.put('nsACString', true);
827 cached_string_types.put('nsAString_internal', true);
828 cached_string_types.put('nsACString_internal', true);
830 // Return true if the given type represents a Mozilla string type.
831 // The binfo arg is the binfo to use for further iteration. This is
832 // for internal use only, users of this function should pass only
833 // one arg.
834 function is_string_type(type, binfo) {
835   if (TREE_CODE(type) != RECORD_TYPE) return false;
836   //print(">>>IST " + type_string(type));
837   let name = decl_name_string(TYPE_NAME(type));
838   let ans = cached_string_types.get(name);
839   if (ans != undefined) return ans;
841   ans = false;
842   binfo = binfo != undefined ? binfo : TYPE_BINFO(type);
843   if (binfo != undefined) {
844     for each (let base in VEC_iterate(BINFO_BASE_BINFOS(binfo))) {
845       let parent_ans = is_string_type(BINFO_TYPE(base), base);
846       if (parent_ans) {
847         ans = true;
848         break;
849       }
850     }
851   }
852   cached_string_types.put(name, ans);
853   //print("<<<IST " + type_string(type) + ' ' + ans);
854   return ans;
857 function is_string_ptr_type(type) {
858   return TREE_CODE(type) == POINTER_TYPE && is_string_type(TREE_TYPE(type));
861 // Return true if the given function is a mutator method of a Mozilla
862 // string type.
863 function is_string_mutator(fndecl) {
864   let first_param = function() {
865     for (let p in function_decl_params(fndecl)) {
866       return p;
867     }
868     return undefined;
869   }();
871   return first_param != undefined && 
872     decl_name_string(first_param) == 'this' &&
873     is_string_ptr_type(TREE_TYPE(first_param)) &&
874     !TYPE_READONLY(TREE_TYPE(TREE_TYPE(first_param)));