xpcom/analysis/outparams.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial