1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/xpcom/analysis/outparams.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,880 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +require({ version: '1.8' }); 1.9 +require({ after_gcc_pass: 'cfg' }); 1.10 + 1.11 +include('treehydra.js'); 1.12 + 1.13 +include('util.js'); 1.14 +include('gcc_util.js'); 1.15 +include('gcc_print.js'); 1.16 +include('unstable/adts.js'); 1.17 +include('unstable/analysis.js'); 1.18 +include('unstable/esp.js'); 1.19 +let Zero_NonZero = {}; 1.20 +include('unstable/zero_nonzero.js', Zero_NonZero); 1.21 + 1.22 +include('xpcom/analysis/mayreturn.js'); 1.23 + 1.24 +function safe_location_of(t) { 1.25 + if (t === undefined) 1.26 + return UNKNOWN_LOCATION; 1.27 + 1.28 + return location_of(t); 1.29 +} 1.30 + 1.31 +MapFactory.use_injective = true; 1.32 + 1.33 +// Print a trace for each function analyzed 1.34 +let TRACE_FUNCTIONS = 0; 1.35 +// Trace operation of the ESP analysis, use 2 or 3 for more detail 1.36 +let TRACE_ESP = 0; 1.37 +// Trace determination of function call parameter semantics, 2 for detail 1.38 +let TRACE_CALL_SEM = 0; 1.39 +// Print time-taken stats 1.40 +let TRACE_PERF = 0; 1.41 +// Log analysis results in a special format 1.42 +let LOG_RESULTS = false; 1.43 + 1.44 +const WARN_ON_SET_NULL = false; 1.45 +const WARN_ON_SET_FAILURE = false; 1.46 + 1.47 +// Filter functions to process per CLI 1.48 +let func_filter; 1.49 +if (this.arg == undefined || this.arg == '') { 1.50 + func_filter = function(fd) true; 1.51 +} else { 1.52 + func_filter = function(fd) function_decl_name(fd) == this.arg; 1.53 +} 1.54 + 1.55 +function process_tree(func_decl) { 1.56 + if (!func_filter(func_decl)) return; 1.57 + 1.58 + // Determine outparams and return if function not relevant 1.59 + if (DECL_CONSTRUCTOR_P(func_decl)) return; 1.60 + let psem = OutparamCheck.prototype.func_param_semantics(func_decl); 1.61 + if (!psem.some(function(x) x.check)) return; 1.62 + let decl = rectify_function_decl(func_decl); 1.63 + if (decl.resultType != 'nsresult' && decl.resultType != 'PRBool' && 1.64 + decl.resultType != 'void') { 1.65 + warning("Cannot analyze outparam usage for function with return type '" + 1.66 + decl.resultType + "'", location_of(func_decl)); 1.67 + return; 1.68 + } 1.69 + 1.70 + let params = [ v for (v in flatten_chain(DECL_ARGUMENTS(func_decl))) ]; 1.71 + let outparam_list = []; 1.72 + let psem_list = []; 1.73 + for (let i = 0; i < psem.length; ++i) { 1.74 + if (psem[i].check) { 1.75 + outparam_list.push(params[i]); 1.76 + psem_list.push(psem[i]); 1.77 + } 1.78 + } 1.79 + if (outparam_list.length == 0) return; 1.80 + 1.81 + // At this point we have a function we want to analyze 1.82 + let fstring = rfunc_string(decl); 1.83 + if (TRACE_FUNCTIONS) { 1.84 + print('* function ' + fstring); 1.85 + print(' ' + loc_string(location_of(func_decl))); 1.86 + } 1.87 + if (TRACE_PERF) timer_start(fstring); 1.88 + for (let i = 0; i < outparam_list.length; ++i) { 1.89 + let p = outparam_list[i]; 1.90 + if (TRACE_FUNCTIONS) { 1.91 + print(" outparam " + expr_display(p) + " " + DECL_UID(p) + ' ' + 1.92 + psem_list[i].label); 1.93 + } 1.94 + } 1.95 + 1.96 + let cfg = function_decl_cfg(func_decl); 1.97 + 1.98 + let [retvar, retvars] = function() { 1.99 + let trace = 0; 1.100 + let a = new MayReturnAnalysis(cfg, trace); 1.101 + a.run(); 1.102 + return [a.retvar, a.vbls]; 1.103 + }(); 1.104 + if (retvar == undefined && decl.resultType != 'void') throw new Error("assert"); 1.105 + 1.106 + { 1.107 + let trace = TRACE_ESP; 1.108 + for (let i = 0; i < outparam_list.length; ++i) { 1.109 + let psem = [ psem_list[i] ]; 1.110 + let outparam = [ outparam_list[i] ]; 1.111 + let a = new OutparamCheck(cfg, psem, outparam, retvar, retvars, trace); 1.112 + // This is annoying, but this field is only used for logging anyway. 1.113 + a.fndecl = func_decl; 1.114 + a.run(); 1.115 + a.check(decl.resultType == 'void', func_decl); 1.116 + } 1.117 + } 1.118 + 1.119 + if (TRACE_PERF) timer_stop(fstring); 1.120 +} 1.121 + 1.122 +// Outparam check analysis 1.123 +function OutparamCheck(cfg, psem_list, outparam_list, retvar, retvar_set, 1.124 + trace) { 1.125 + // We need to save the retvars so we can detect assignments through 1.126 + // their addresses passed as arguments. 1.127 + this.retvar_set = retvar_set; 1.128 + this.retvar = retvar; 1.129 + 1.130 + // We need both an ordered set and a lookup structure 1.131 + this.outparam_list = outparam_list 1.132 + this.outparams = create_decl_set(outparam_list); 1.133 + this.psem_list = psem_list; 1.134 + 1.135 + // Set up property state vars for ESP 1.136 + let psvar_list = []; 1.137 + for each (let v in outparam_list) { 1.138 + psvar_list.push(new ESP.PropVarSpec(v, true, av.NOT_WRITTEN)); 1.139 + } 1.140 + for (let v in retvar_set.items()) { 1.141 + psvar_list.push(new ESP.PropVarSpec(v, v == this.retvar, ESP.TOP)); 1.142 + } 1.143 + if (trace) { 1.144 + print("PS vars"); 1.145 + for each (let v in this.psvar_list) { 1.146 + print(" " + expr_display(v.vbl)); 1.147 + } 1.148 + } 1.149 + this.zeroNonzero = new Zero_NonZero.Zero_NonZero(); 1.150 + ESP.Analysis.call(this, cfg, psvar_list, av.meet, trace); 1.151 +} 1.152 + 1.153 +// Abstract values for outparam check 1.154 +function AbstractValue(name, ch) { 1.155 + this.name = name; 1.156 + this.ch = ch; 1.157 +} 1.158 + 1.159 +AbstractValue.prototype.equals = function(v) { 1.160 + return this === v; 1.161 +} 1.162 + 1.163 +AbstractValue.prototype.toString = function() { 1.164 + return this.name + ' (' + this.ch + ')'; 1.165 +} 1.166 + 1.167 +AbstractValue.prototype.toShortString = function() { 1.168 + return this.ch; 1.169 +} 1.170 + 1.171 +let avspec = [ 1.172 + // Abstract values for outparam contents write status 1.173 + [ 'NULL', 'x' ], // is a null pointer 1.174 + [ 'NOT_WRITTEN', '-' ], // not written 1.175 + [ 'WROTE_NULL', '/' ], // had NULL written to 1.176 + [ 'WRITTEN', '+' ], // had anything written to 1.177 + // MAYBE_WRITTEN is special. "Officially", it means the same thing as 1.178 + // NOT_WRITTEN. What it really means is that an outparam was passed 1.179 + // to another function as a possible outparam (outparam type, but not 1.180 + // in last position), so if there is an error with it not being written, 1.181 + // we can give a hint about the possible outparam in the warning. 1.182 + [ 'MAYBE_WRITTEN', '?' ], // written if possible outparam is one 1.183 +]; 1.184 + 1.185 +let av = {}; 1.186 +for each (let [name, ch] in avspec) { 1.187 + av[name] = new AbstractValue(name, ch); 1.188 +} 1.189 + 1.190 +av.ZERO = Zero_NonZero.Lattice.ZERO; 1.191 +av.NONZERO = Zero_NonZero.Lattice.NONZERO; 1.192 + 1.193 +/* 1.194 +av.ZERO.negation = av.NONZERO; 1.195 +av.NONZERO.negation = av.ZERO; 1.196 + 1.197 +// Abstract values for int constants. We use these to figure out feasible 1.198 +// paths in the presence of GCC finally_tmp-controlled switches. 1.199 +function makeIntAV(v) { 1.200 + let key = 'int_' + v; 1.201 + if (cachedAVs.hasOwnProperty(key)) return cachedAVs[key]; 1.202 + 1.203 + let s = "" + v; 1.204 + let ans = cachedAVs[key] = new AbstractValue(s, s); 1.205 + ans.int_val = v; 1.206 + return ans; 1.207 +} 1.208 +*/ 1.209 + 1.210 +let cachedAVs = {}; 1.211 + 1.212 +// Abstract values for pointers that contain a copy of an outparam 1.213 +// pointer. We use these to figure out writes to a casted copy of 1.214 +// an outparam passed to another method. 1.215 +function makeOutparamAV(v) { 1.216 + let key = 'outparam_' + DECL_UID(v); 1.217 + if (key in cachedAVs) return cachedAVs[key]; 1.218 + 1.219 + let ans = cachedAVs[key] = 1.220 + new AbstractValue('OUTPARAM:' + expr_display(v), 'P'); 1.221 + ans.outparam = v; 1.222 + return ans; 1.223 +} 1.224 + 1.225 +/** Return the integer value if this is an integer av, otherwise undefined. */ 1.226 +av.intVal = function(v) { 1.227 + if (v.hasOwnProperty('int_val')) 1.228 + return v.int_val; 1.229 + return undefined; 1.230 +} 1.231 + 1.232 +/** Meet function for our abstract values. */ 1.233 +av.meet = function(v1, v2) { 1.234 + // At this point we know v1 != v2. 1.235 + let values = [v1,v2] 1.236 + if (values.indexOf(av.LOCKED) != -1 1.237 + || values.indexOf(av.UNLOCKED) != -1) 1.238 + return ESP.NOT_REACHED; 1.239 + 1.240 + return Zero_NonZero.meet(v1, v2) 1.241 +}; 1.242 + 1.243 +// Outparam check analysis 1.244 +OutparamCheck.prototype = new ESP.Analysis; 1.245 + 1.246 +OutparamCheck.prototype.split = function(vbl, v) { 1.247 + // Can't happen for current version of ESP, but could change 1.248 + if (v != ESP.TOP) throw new Error("not implemented"); 1.249 + return [ av.ZERO, av.NONZERO ]; 1.250 +} 1.251 + 1.252 +OutparamCheck.prototype.updateEdgeState = function(e) { 1.253 + e.state.keepOnly(e.dest.keepVars); 1.254 +} 1.255 + 1.256 +OutparamCheck.prototype.flowState = function(isn, state) { 1.257 + switch (TREE_CODE(isn)) { 1.258 + case GIMPLE_ASSIGN: 1.259 + this.processAssign(isn, state); 1.260 + break; 1.261 + case GIMPLE_CALL: 1.262 + this.processCall(isn, isn, state); 1.263 + break; 1.264 + case GIMPLE_SWITCH: 1.265 + case GIMPLE_COND: 1.266 + // This gets handled by flowStateCond instead, has no exec effect 1.267 + break; 1.268 + default: 1.269 + this.zeroNonzero.flowState(isn, state); 1.270 + } 1.271 +} 1.272 + 1.273 +OutparamCheck.prototype.flowStateCond = function(isn, truth, state) { 1.274 + this.zeroNonzero.flowStateCond(isn, truth, state); 1.275 +} 1.276 + 1.277 +// For any outparams-specific semantics, we handle it here and then 1.278 +// return. Otherwise we delegate to the zero-nonzero analysis. 1.279 +OutparamCheck.prototype.processAssign = function(isn, state) { 1.280 + let lhs = gimple_op(isn, 0); 1.281 + let rhs = gimple_op(isn, 1); 1.282 + 1.283 + if (DECL_P(lhs)) { 1.284 + // Unwrap NOP_EXPR, which is semantically a copy. 1.285 + if (TREE_CODE(rhs) == NOP_EXPR) { 1.286 + rhs = rhs.operands()[0]; 1.287 + } 1.288 + 1.289 + if (DECL_P(rhs) && this.outparams.has(rhs)) { 1.290 + // Copying an outparam pointer. We have to remember this so that 1.291 + // if it is assigned thru later, we pick up the write. 1.292 + state.assignValue(lhs, makeOutparamAV(rhs), isn); 1.293 + return; 1.294 + } 1.295 + 1.296 + // Cases of this switch that handle something should return from 1.297 + // the function. Anything that does not return is picked up afteward. 1.298 + switch (TREE_CODE(rhs)) { 1.299 + case INTEGER_CST: 1.300 + if (this.outparams.has(lhs)) { 1.301 + warning("assigning to outparam pointer"); 1.302 + return; 1.303 + } 1.304 + break; 1.305 + case EQ_EXPR: { 1.306 + // We only care about testing outparams for NULL (and then not writing) 1.307 + let [op1, op2] = rhs.operands(); 1.308 + if (DECL_P(op1) && this.outparams.has(op1) && expr_literal_int(op2) == 0) { 1.309 + state.update(function(ss) { 1.310 + let [s1, s2] = [ss, ss.copy()]; // s1 true, s2 false 1.311 + s1.assignValue(lhs, av.NONZERO, isn); 1.312 + s1.assignValue(op1, av.NULL, isn); 1.313 + s2.assignValue(lhs, av.ZERO, isn); 1.314 + return [s1, s2]; 1.315 + }); 1.316 + return; 1.317 + } 1.318 + } 1.319 + break; 1.320 + case CALL_EXPR: 1.321 + /* Embedded CALL_EXPRs are a 4.3 issue */ 1.322 + this.processCall(rhs, isn, state, lhs); 1.323 + return; 1.324 + 1.325 + case INDIRECT_REF: 1.326 + // If rhs is *outparam and pointer-typed, lhs is NULL iff rhs is 1.327 + // WROTE_NULL. Required for testcase onull.cpp. 1.328 + let v = rhs.operands()[0]; 1.329 + if (DECL_P(v) && this.outparams.has(v) && 1.330 + TREE_CODE(TREE_TYPE(v)) == POINTER_TYPE) { 1.331 + state.update(function(ss) { 1.332 + let val = ss.get(v) == av.WROTE_NULL ? av.ZERO : av.NONZERO; 1.333 + ss.assignValue(lhs, val, isn); 1.334 + return [ ss ]; 1.335 + }); 1.336 + return; 1.337 + } 1.338 + } 1.339 + 1.340 + // Nothing special -- delegate 1.341 + this.zeroNonzero.processAssign(isn, state); 1.342 + return; 1.343 + } 1.344 + 1.345 + switch (TREE_CODE(lhs)) { 1.346 + case INDIRECT_REF: 1.347 + // Writing to an outparam. We want to try to figure out if we're 1.348 + // writing NULL. 1.349 + let e = TREE_OPERAND(lhs, 0); 1.350 + if (this.outparams.has(e)) { 1.351 + if (expr_literal_int(rhs) == 0) { 1.352 + state.assignValue(e, av.WROTE_NULL, isn); 1.353 + } else if (DECL_P(rhs)) { 1.354 + state.update(function(ss) { 1.355 + let [s1, s2] = [ss.copy(), ss]; // s1 NULL, s2 non-NULL 1.356 + s1.assignValue(e, av.WROTE_NULL, isn); 1.357 + s1.assignValue(rhs, av.ZERO, isn); 1.358 + s2.assignValue(e, av.WRITTEN, isn); 1.359 + s2.assignValue(rhs, av.NONZERO, isn); 1.360 + return [s1,s2]; 1.361 + }); 1.362 + } else { 1.363 + state.assignValue(e, av.WRITTEN, isn); 1.364 + } 1.365 + } else { 1.366 + // unsound -- could be writing to anything through this ptr 1.367 + } 1.368 + break; 1.369 + case COMPONENT_REF: // unsound 1.370 + case ARRAY_REF: // unsound 1.371 + case EXC_PTR_EXPR: 1.372 + case FILTER_EXPR: 1.373 + break; 1.374 + default: 1.375 + print(TREE_CODE(lhs)); 1.376 + throw new Error("ni"); 1.377 + } 1.378 +} 1.379 + 1.380 +// Handle an assignment x := test(foo) where test is a simple predicate 1.381 +OutparamCheck.prototype.processTest = function(lhs, call, val, blame, state) { 1.382 + let arg = gimple_call_arg(call, 0); 1.383 + if (DECL_P(arg)) { 1.384 + this.zeroNonzero.predicate(state, lhs, val, arg, blame); 1.385 + } else { 1.386 + state.assignValue(lhs, ESP.TOP, blame); 1.387 + } 1.388 +}; 1.389 + 1.390 +// The big one: outparam semantics of function calls. 1.391 +OutparamCheck.prototype.processCall = function(call, blame, state, dest) { 1.392 + if (!dest) 1.393 + dest = gimple_call_lhs(call); 1.394 + 1.395 + let args = gimple_call_args(call); 1.396 + let callable = callable_arg_function_decl(gimple_call_fn(call)); 1.397 + let psem = this.func_param_semantics(callable); 1.398 + 1.399 + let name = function_decl_name(callable); 1.400 + if (name == 'NS_FAILED') { 1.401 + this.processTest(dest, call, av.NONZERO, call, state); 1.402 + return; 1.403 + } else if (name == 'NS_SUCCEEDED') { 1.404 + this.processTest(dest, call, av.ZERO, call, state); 1.405 + return; 1.406 + } else if (name == '__builtin_expect') { 1.407 + // Same as an assign from arg 0 to lhs 1.408 + state.assign(dest, args[0], call); 1.409 + return; 1.410 + } 1.411 + 1.412 + if (TRACE_CALL_SEM) { 1.413 + print("param semantics:" + psem); 1.414 + } 1.415 + 1.416 + if (args.length != psem.length) { 1.417 + let ct = TREE_TYPE(callable); 1.418 + if (TREE_CODE(ct) == POINTER_TYPE) ct = TREE_TYPE(ct); 1.419 + if (args.length < psem.length || !stdarg_p(ct)) { 1.420 + // TODO Can __builtin_memcpy write to an outparam? Probably not. 1.421 + if (name != 'operator new' && name != 'operator delete' && 1.422 + name != 'operator new []' && name != 'operator delete []' && 1.423 + name.substr(0, 5) != '__cxa' && 1.424 + name.substr(0, 9) != '__builtin') { 1.425 + throw Error("bad len for '" + name + "': " + args.length + ' args, ' + 1.426 + psem.length + ' params'); 1.427 + } 1.428 + } 1.429 + } 1.430 + 1.431 + // Collect variables that are possibly written to on callee success 1.432 + let updates = []; 1.433 + for (let i = 0; i < psem.length; ++i) { 1.434 + let arg = args[i]; 1.435 + // The arg could be the address of a return-value variable. 1.436 + // This means it's really the nsresult code for the call, 1.437 + // so we treat it the same as the target of an rv assignment. 1.438 + if (TREE_CODE(arg) == ADDR_EXPR) { 1.439 + let v = arg.operands()[0]; 1.440 + if (DECL_P(v) && this.retvar_set.has(v)) { 1.441 + dest = v; 1.442 + } 1.443 + } 1.444 + // The arg could be a copy of an outparam. We'll unwrap to the 1.445 + // outparam if it is. The following is cheating a bit because 1.446 + // we munge states together, but it should be OK in practice. 1.447 + arg = unwrap_outparam(arg, state); 1.448 + let sem = psem[i]; 1.449 + if (sem == ps.CONST) continue; 1.450 + // At this point, we know the call can write thru this param. 1.451 + // Invalidate any vars whose addresses are passed here. This 1.452 + // is distinct from the rv handling above. 1.453 + if (TREE_CODE(arg) == ADDR_EXPR) { 1.454 + let v = arg.operands()[0]; 1.455 + if (DECL_P(v)) { 1.456 + state.remove(v); 1.457 + } 1.458 + } 1.459 + if (!DECL_P(arg) || !this.outparams.has(arg)) continue; 1.460 + // At this point, we may be writing to an outparam 1.461 + updates.push([arg, sem]); 1.462 + } 1.463 + 1.464 + if (updates.length) { 1.465 + if (dest != undefined && DECL_P(dest)) { 1.466 + // Update & stored rv. Do updates predicated on success. 1.467 + let [ succ_ret, fail_ret ] = ret_coding(callable); 1.468 + 1.469 + state.update(function(ss) { 1.470 + let [s1, s2] = [ss.copy(), ss]; // s1 success, s2 fail 1.471 + for each (let [vbl, sem] in updates) { 1.472 + s1.assignValue(vbl, sem.val, blame); 1.473 + s1.assignValue(dest, succ_ret, blame); 1.474 + } 1.475 + s2.assignValue(dest, fail_ret, blame); 1.476 + return [s1,s2]; 1.477 + }); 1.478 + } else { 1.479 + // Discarded rv. Per spec in the bug, we assume that either success 1.480 + // or failure is possible (if not, callee should return void). 1.481 + // Exceptions: Methods that return void and string mutators are 1.482 + // considered no-fail. 1.483 + state.update(function(ss) { 1.484 + for each (let [vbl, sem] in updates) { 1.485 + if (sem == ps.OUTNOFAIL || sem == ps.OUTNOFAILNOCHECK) { 1.486 + ss.assignValue(vbl, av.WRITTEN, blame); 1.487 + return [ss]; 1.488 + } else { 1.489 + let [s1, s2] = [ss.copy(), ss]; // s1 success, s2 fail 1.490 + for each (let [vbl, sem] in updates) { 1.491 + s1.assignValue(vbl, sem.val, blame); 1.492 + } 1.493 + return [s1,s2]; 1.494 + } 1.495 + } 1.496 + }); 1.497 + } 1.498 + } else { 1.499 + // no updates, just kill any destination for the rv 1.500 + if (dest != undefined && DECL_P(dest)) { 1.501 + state.remove(dest, blame); 1.502 + } 1.503 + } 1.504 +}; 1.505 + 1.506 +/** Return the return value coding of the given function. This is a pair 1.507 + * [ succ, fail ] giving the abstract values of the return value under 1.508 + * success and failure conditions. */ 1.509 +function ret_coding(callable) { 1.510 + let type = TREE_TYPE(callable); 1.511 + if (TREE_CODE(type) == POINTER_TYPE) type = TREE_TYPE(type); 1.512 + 1.513 + let rtname = TYPE_NAME(TREE_TYPE(type)); 1.514 + if (rtname && IDENTIFIER_POINTER(DECL_NAME(rtname)) == 'PRBool') { 1.515 + return [ av.NONZERO, av.ZERO ]; 1.516 + } else { 1.517 + return [ av.ZERO, av.NONZERO ]; 1.518 + } 1.519 +} 1.520 + 1.521 +function unwrap_outparam(arg, state) { 1.522 + if (!DECL_P(arg) || state.factory.outparams.has(arg)) return arg; 1.523 + 1.524 + let outparam; 1.525 + for (let ss in state.substates.getValues()) { 1.526 + let val = ss.get(arg); 1.527 + if (val != undefined && val.hasOwnProperty('outparam')) { 1.528 + outparam = val.outparam; 1.529 + } 1.530 + } 1.531 + if (outparam) return outparam; 1.532 + return arg; 1.533 +} 1.534 + 1.535 +// Check for errors. Must .run() analysis before calling this. 1.536 +OutparamCheck.prototype.check = function(isvoid, fndecl) { 1.537 + let state = this.cfg.x_exit_block_ptr.stateOut; 1.538 + for (let substate in state.substates.getValues()) { 1.539 + this.checkSubstate(isvoid, fndecl, substate); 1.540 + } 1.541 +} 1.542 + 1.543 +OutparamCheck.prototype.checkSubstate = function(isvoid, fndecl, ss) { 1.544 + if (isvoid) { 1.545 + this.checkSubstateSuccess(ss); 1.546 + } else { 1.547 + let [succ, fail] = ret_coding(fndecl); 1.548 + let rv = ss.get(this.retvar); 1.549 + // We want to check if the abstract value of the rv is entirely 1.550 + // contained in the success or failure condition. 1.551 + if (av.meet(rv, succ) == rv) { 1.552 + this.checkSubstateSuccess(ss); 1.553 + } else if (av.meet(rv, fail) == rv) { 1.554 + this.checkSubstateFailure(ss); 1.555 + } else { 1.556 + // This condition indicates a bug in outparams.js. We'll just 1.557 + // warn so we don't break static analysis builds. 1.558 + warning("Outparams checker cannot determine rv success/failure", 1.559 + location_of(fndecl)); 1.560 + this.checkSubstateSuccess(ss); 1.561 + this.checkSubstateFailure(ss); 1.562 + } 1.563 + } 1.564 +} 1.565 + 1.566 +/* @return The return statement in the function 1.567 + * that writes the return value in the given substate. 1.568 + * If the function returns void, then the substate doesn't 1.569 + * matter and we just look for the return. */ 1.570 +OutparamCheck.prototype.findReturnStmt = function(ss) { 1.571 + if (this.retvar != undefined) 1.572 + return ss.getBlame(this.retvar); 1.573 + 1.574 + if (this.cfg._cached_return) 1.575 + return this.cfg._cached_return; 1.576 + 1.577 + for (let bb in cfg_bb_iterator(this.cfg)) { 1.578 + for (let isn in bb_isn_iterator(bb)) { 1.579 + if (isn.tree_code() == GIMPLE_RETURN) { 1.580 + return this.cfg._cached_return = isn; 1.581 + } 1.582 + } 1.583 + } 1.584 + 1.585 + return undefined; 1.586 +} 1.587 + 1.588 +OutparamCheck.prototype.checkSubstateSuccess = function(ss) { 1.589 + for (let i = 0; i < this.psem_list.length; ++i) { 1.590 + let [v, psem] = [ this.outparam_list[i], this.psem_list[i] ]; 1.591 + if (psem == ps.INOUT) continue; 1.592 + let val = ss.get(v); 1.593 + if (val == av.NOT_WRITTEN) { 1.594 + this.logResult('succ', 'not_written', 'error'); 1.595 + this.warn([this.findReturnStmt(ss), "outparam '" + expr_display(v) + "' not written on NS_SUCCEEDED(return value)"], 1.596 + [v, "outparam declared here"]); 1.597 + } else if (val == av.MAYBE_WRITTEN) { 1.598 + this.logResult('succ', 'maybe_written', 'error'); 1.599 + 1.600 + let blameStmt = ss.getBlame(v); 1.601 + let callMsg; 1.602 + let callName = ""; 1.603 + try { 1.604 + let call = TREE_CHECK(blameStmt, GIMPLE_CALL, GIMPLE_MODIFY_STMT); 1.605 + let callDecl = callable_arg_function_decl(gimple_call_fn(call)); 1.606 + 1.607 + callMsg = [callDecl, "declared here"]; 1.608 + callName = " '" + decl_name(callDecl) + "'"; 1.609 + } 1.610 + catch (e if e.TreeCheckError) { } 1.611 + 1.612 + this.warn([this.findReturnStmt(ss), "outparam '" + expr_display(v) + "' not written on NS_SUCCEEDED(return value)"], 1.613 + [v, "outparam declared here"], 1.614 + [blameStmt, "possibly written by unannotated function call" + callName], 1.615 + callMsg); 1.616 + } else { 1.617 + this.logResult('succ', '', 'ok'); 1.618 + } 1.619 + } 1.620 +} 1.621 + 1.622 +OutparamCheck.prototype.checkSubstateFailure = function(ss) { 1.623 + for (let i = 0; i < this.psem_list.length; ++i) { 1.624 + let [v, ps] = [ this.outparam_list[i], this.psem_list[i] ]; 1.625 + let val = ss.get(v); 1.626 + if (val == av.WRITTEN) { 1.627 + this.logResult('fail', 'written', 'error'); 1.628 + if (WARN_ON_SET_FAILURE) { 1.629 + this.warn([this.findReturnStmt(ss), "outparam '" + expr_display(v) + "' written on NS_FAILED(return value)"], 1.630 + [v, "outparam declared here"], 1.631 + [ss.getBlame(v), "written here"]); 1.632 + } 1.633 + } else if (val == av.WROTE_NULL) { 1.634 + this.logResult('fail', 'wrote_null', 'warning'); 1.635 + if (WARN_ON_SET_NULL) { 1.636 + this.warn([this.findReturnStmt(ss), "NULL written to outparam '" + expr_display(v) + "' on NS_FAILED(return value)"], 1.637 + [v, "outparam declared here"], 1.638 + [ss.getBlame(v), "written here"]); 1.639 + } 1.640 + } else { 1.641 + this.logResult('fail', '', 'ok'); 1.642 + } 1.643 + } 1.644 +} 1.645 + 1.646 +/** 1.647 + * Generate a warning from one or more tuples [treeforloc, message] 1.648 + */ 1.649 +OutparamCheck.prototype.warn = function(arg0) { 1.650 + let loc = safe_location_of(arg0[0]); 1.651 + let msg = arg0[1]; 1.652 + 1.653 + for (let i = 1; i < arguments.length; ++i) { 1.654 + if (arguments[i] === undefined) continue; 1.655 + let [atree, amsg] = arguments[i]; 1.656 + msg += "\n" + loc_string(safe_location_of(atree)) + ": " + amsg; 1.657 + } 1.658 + warning(msg, loc); 1.659 +} 1.660 + 1.661 +OutparamCheck.prototype.logResult = function(rv, msg, kind) { 1.662 + if (LOG_RESULTS) { 1.663 + let s = [ '"' + x + '"' for each (x in [ loc_string(location_of(this.fndecl)), function_decl_name(this.fndecl), rv, msg, kind ]) ].join(', '); 1.664 + print(":LR: (" + s + ")"); 1.665 + } 1.666 +} 1.667 + 1.668 +// Parameter Semantics values -- indicates whether a parameter is 1.669 +// an outparam. 1.670 +// label Used for debugging output 1.671 +// val Abstract value (state) that holds on an argument after 1.672 +// a call 1.673 +// check True if parameters with this semantics should be 1.674 +// checked by this analysis 1.675 +let ps = { 1.676 + OUTNOFAIL: { label: 'out-no-fail', val: av.WRITTEN, check: true }, 1.677 + // Special value for receiver of strings methods. Callers should 1.678 + // consider this to be an outparam (i.e., it modifies the string), 1.679 + // but we don't want to check the method itself. 1.680 + OUTNOFAILNOCHECK: { label: 'out-no-fail-no-check' }, 1.681 + OUT: { label: 'out', val: av.WRITTEN, check: true }, 1.682 + INOUT: { label: 'inout', val: av.WRITTEN, check: true }, 1.683 + MAYBE: { label: 'maybe', val: av.MAYBE_WRITTEN}, // maybe out 1.684 + CONST: { label: 'const' } // i.e. not out 1.685 +}; 1.686 + 1.687 +// Return the param semantics of a FUNCTION_DECL or VAR_DECL representing 1.688 +// a function pointer. The result is a pair [ ann, sems ]. 1.689 +OutparamCheck.prototype.func_param_semantics = function(callable) { 1.690 + let ftype = TREE_TYPE(callable); 1.691 + if (TREE_CODE(ftype) == POINTER_TYPE) ftype = TREE_TYPE(ftype); 1.692 + // What failure semantics to use for outparams 1.693 + let rtype = TREE_TYPE(ftype); 1.694 + let nofail = TREE_CODE(rtype) == VOID_TYPE; 1.695 + // Whether to guess outparams by type 1.696 + let guess = type_string(rtype) == 'nsresult'; 1.697 + 1.698 + // Set up param lists for analysis 1.699 + let params; // param decls, if available 1.700 + let types; // param types 1.701 + let string_mutator = false; 1.702 + if (TREE_CODE(callable) == FUNCTION_DECL) { 1.703 + params = [ p for (p in function_decl_params(callable)) ]; 1.704 + types = [ TREE_TYPE(p) for each (p in params) ]; 1.705 + string_mutator = is_string_mutator(callable); 1.706 + } else { 1.707 + types = [ p for (p in function_type_args(ftype)) 1.708 + if (TREE_CODE(p) != VOID_TYPE) ]; 1.709 + } 1.710 + 1.711 + // Analyze params 1.712 + let ans = []; 1.713 + for (let i = 0; i < types.length; ++i) { 1.714 + let sem; 1.715 + if (i == 0 && string_mutator) { 1.716 + // Special case: string mutator receiver is an no-fail outparams 1.717 + // but not checkable 1.718 + sem = ps.OUTNOFAILNOCHECK; 1.719 + } else { 1.720 + if (params) sem = decode_attr(DECL_ATTRIBUTES(params[i])); 1.721 + if (TRACE_CALL_SEM >= 2) print("param " + i + ": annotated " + sem); 1.722 + if (sem == undefined) { 1.723 + sem = decode_attr(TYPE_ATTRIBUTES(types[i])); 1.724 + if (TRACE_CALL_SEM >= 2) print("type " + i + ": annotated " + sem); 1.725 + if (sem == undefined) { 1.726 + if (guess && type_is_outparam(types[i])) { 1.727 + // Params other than last are guessed as MAYBE 1.728 + sem = i < types.length - 1 ? ps.MAYBE : ps.OUT; 1.729 + } else { 1.730 + sem = ps.CONST; 1.731 + } 1.732 + } 1.733 + } 1.734 + if (sem == ps.OUT && nofail) sem = ps.OUTNOFAIL; 1.735 + } 1.736 + if (sem == undefined) throw new Error("assert"); 1.737 + ans.push(sem); 1.738 + } 1.739 + return ans; 1.740 +} 1.741 + 1.742 +/* Decode parameter semantics GCC attributes. 1.743 + * @param attrs GCC attributes of a parameter. E.g., TYPE_ATTRIBUTES 1.744 + * or DECL_ATTRIBUTES of an item 1.745 + * @return The parameter semantics value defined by the attributes, 1.746 + * or undefined if no such attributes were present. */ 1.747 +function decode_attr(attrs) { 1.748 + // Note: we're not checking for conflicts, we just take the first 1.749 + // one we find. 1.750 + for each (let attr in rectify_attributes(attrs)) { 1.751 + if (attr.name == 'user') { 1.752 + for each (let arg in attr.args) { 1.753 + if (arg == 'NS_outparam') { 1.754 + return ps.OUT; 1.755 + } else if (arg == 'NS_inoutparam') { 1.756 + return ps.INOUT; 1.757 + } else if (arg == 'NS_inparam') { 1.758 + return ps.CONST; 1.759 + } 1.760 + } 1.761 + } 1.762 + } 1.763 + return undefined; 1.764 +} 1.765 + 1.766 +/* @return true if the given type appears to be an outparam 1.767 + * type based on the type alone (i.e., not considering 1.768 + * attributes. */ 1.769 +function type_is_outparam(type) { 1.770 + switch (TREE_CODE(type)) { 1.771 + case POINTER_TYPE: 1.772 + return pointer_type_is_outparam(TREE_TYPE(type)); 1.773 + case REFERENCE_TYPE: 1.774 + let rt = TREE_TYPE(type); 1.775 + return !TYPE_READONLY(rt) && is_string_type(rt); 1.776 + default: 1.777 + // Note: This is unsound for UNION_TYPE, because the union could 1.778 + // contain a pointer. 1.779 + return false; 1.780 + } 1.781 +} 1.782 + 1.783 +/* Helper for type_is_outparam. 1.784 + * @return true if 'pt *' looks like an outparam type. */ 1.785 +function pointer_type_is_outparam(pt) { 1.786 + if (TYPE_READONLY(pt)) return false; 1.787 + 1.788 + switch (TREE_CODE(pt)) { 1.789 + case POINTER_TYPE: 1.790 + case ARRAY_TYPE: { 1.791 + // Look for void **, nsIFoo **, char **, PRUnichar ** 1.792 + let ppt = TREE_TYPE(pt); 1.793 + let tname = TYPE_NAME(ppt); 1.794 + if (tname == undefined) return false; 1.795 + let name = decl_name_string(tname); 1.796 + return name == 'void' || name == 'char' || name == 'PRUnichar' || 1.797 + name.substr(0, 3) == 'nsI'; 1.798 + } 1.799 + case INTEGER_TYPE: { 1.800 + // char * and PRUnichar * are probably strings, otherwise guess 1.801 + // it is an integer outparam. 1.802 + let name = decl_name_string(TYPE_NAME(pt)); 1.803 + return name != 'char' && name != 'PRUnichar'; 1.804 + } 1.805 + case ENUMERAL_TYPE: 1.806 + case REAL_TYPE: 1.807 + case UNION_TYPE: 1.808 + case BOOLEAN_TYPE: 1.809 + return true; 1.810 + case RECORD_TYPE: 1.811 + // TODO: should we consider field writes? 1.812 + return false; 1.813 + case FUNCTION_TYPE: 1.814 + case VOID_TYPE: 1.815 + return false; 1.816 + default: 1.817 + throw new Error("can't guess if a pointer to this type is an outparam: " + 1.818 + TREE_CODE(pt) + ': ' + type_string(pt)); 1.819 + } 1.820 +} 1.821 + 1.822 +// Map type name to boolean as to whether it is a string. 1.823 +let cached_string_types = MapFactory.create_map( 1.824 + function (x, y) x == y, 1.825 + function (x) x, 1.826 + function (t) t, 1.827 + function (t) t); 1.828 + 1.829 +// Base string types. Others will be found by searching the inheritance 1.830 +// graph. 1.831 + 1.832 +cached_string_types.put('nsAString', true); 1.833 +cached_string_types.put('nsACString', true); 1.834 +cached_string_types.put('nsAString_internal', true); 1.835 +cached_string_types.put('nsACString_internal', true); 1.836 + 1.837 +// Return true if the given type represents a Mozilla string type. 1.838 +// The binfo arg is the binfo to use for further iteration. This is 1.839 +// for internal use only, users of this function should pass only 1.840 +// one arg. 1.841 +function is_string_type(type, binfo) { 1.842 + if (TREE_CODE(type) != RECORD_TYPE) return false; 1.843 + //print(">>>IST " + type_string(type)); 1.844 + let name = decl_name_string(TYPE_NAME(type)); 1.845 + let ans = cached_string_types.get(name); 1.846 + if (ans != undefined) return ans; 1.847 + 1.848 + ans = false; 1.849 + binfo = binfo != undefined ? binfo : TYPE_BINFO(type); 1.850 + if (binfo != undefined) { 1.851 + for each (let base in VEC_iterate(BINFO_BASE_BINFOS(binfo))) { 1.852 + let parent_ans = is_string_type(BINFO_TYPE(base), base); 1.853 + if (parent_ans) { 1.854 + ans = true; 1.855 + break; 1.856 + } 1.857 + } 1.858 + } 1.859 + cached_string_types.put(name, ans); 1.860 + //print("<<<IST " + type_string(type) + ' ' + ans); 1.861 + return ans; 1.862 +} 1.863 + 1.864 +function is_string_ptr_type(type) { 1.865 + return TREE_CODE(type) == POINTER_TYPE && is_string_type(TREE_TYPE(type)); 1.866 +} 1.867 + 1.868 +// Return true if the given function is a mutator method of a Mozilla 1.869 +// string type. 1.870 +function is_string_mutator(fndecl) { 1.871 + let first_param = function() { 1.872 + for (let p in function_decl_params(fndecl)) { 1.873 + return p; 1.874 + } 1.875 + return undefined; 1.876 + }(); 1.877 + 1.878 + return first_param != undefined && 1.879 + decl_name_string(first_param) == 'this' && 1.880 + is_string_ptr_type(TREE_TYPE(first_param)) && 1.881 + !TYPE_READONLY(TREE_TYPE(TREE_TYPE(first_param))); 1.882 +} 1.883 +