michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: require({ version: '1.8' }); michael@0: require({ after_gcc_pass: 'cfg' }); michael@0: michael@0: include('treehydra.js'); michael@0: michael@0: include('util.js'); michael@0: include('gcc_util.js'); michael@0: include('gcc_print.js'); michael@0: include('unstable/adts.js'); michael@0: include('unstable/analysis.js'); michael@0: include('unstable/esp.js'); michael@0: let Zero_NonZero = {}; michael@0: include('unstable/zero_nonzero.js', Zero_NonZero); michael@0: michael@0: include('xpcom/analysis/mayreturn.js'); michael@0: michael@0: function safe_location_of(t) { michael@0: if (t === undefined) michael@0: return UNKNOWN_LOCATION; michael@0: michael@0: return location_of(t); michael@0: } michael@0: michael@0: MapFactory.use_injective = true; michael@0: michael@0: // Print a trace for each function analyzed michael@0: let TRACE_FUNCTIONS = 0; michael@0: // Trace operation of the ESP analysis, use 2 or 3 for more detail michael@0: let TRACE_ESP = 0; michael@0: // Trace determination of function call parameter semantics, 2 for detail michael@0: let TRACE_CALL_SEM = 0; michael@0: // Print time-taken stats michael@0: let TRACE_PERF = 0; michael@0: // Log analysis results in a special format michael@0: let LOG_RESULTS = false; michael@0: michael@0: const WARN_ON_SET_NULL = false; michael@0: const WARN_ON_SET_FAILURE = false; michael@0: michael@0: // Filter functions to process per CLI michael@0: let func_filter; michael@0: if (this.arg == undefined || this.arg == '') { michael@0: func_filter = function(fd) true; michael@0: } else { michael@0: func_filter = function(fd) function_decl_name(fd) == this.arg; michael@0: } michael@0: michael@0: function process_tree(func_decl) { michael@0: if (!func_filter(func_decl)) return; michael@0: michael@0: // Determine outparams and return if function not relevant michael@0: if (DECL_CONSTRUCTOR_P(func_decl)) return; michael@0: let psem = OutparamCheck.prototype.func_param_semantics(func_decl); michael@0: if (!psem.some(function(x) x.check)) return; michael@0: let decl = rectify_function_decl(func_decl); michael@0: if (decl.resultType != 'nsresult' && decl.resultType != 'PRBool' && michael@0: decl.resultType != 'void') { michael@0: warning("Cannot analyze outparam usage for function with return type '" + michael@0: decl.resultType + "'", location_of(func_decl)); michael@0: return; michael@0: } michael@0: michael@0: let params = [ v for (v in flatten_chain(DECL_ARGUMENTS(func_decl))) ]; michael@0: let outparam_list = []; michael@0: let psem_list = []; michael@0: for (let i = 0; i < psem.length; ++i) { michael@0: if (psem[i].check) { michael@0: outparam_list.push(params[i]); michael@0: psem_list.push(psem[i]); michael@0: } michael@0: } michael@0: if (outparam_list.length == 0) return; michael@0: michael@0: // At this point we have a function we want to analyze michael@0: let fstring = rfunc_string(decl); michael@0: if (TRACE_FUNCTIONS) { michael@0: print('* function ' + fstring); michael@0: print(' ' + loc_string(location_of(func_decl))); michael@0: } michael@0: if (TRACE_PERF) timer_start(fstring); michael@0: for (let i = 0; i < outparam_list.length; ++i) { michael@0: let p = outparam_list[i]; michael@0: if (TRACE_FUNCTIONS) { michael@0: print(" outparam " + expr_display(p) + " " + DECL_UID(p) + ' ' + michael@0: psem_list[i].label); michael@0: } michael@0: } michael@0: michael@0: let cfg = function_decl_cfg(func_decl); michael@0: michael@0: let [retvar, retvars] = function() { michael@0: let trace = 0; michael@0: let a = new MayReturnAnalysis(cfg, trace); michael@0: a.run(); michael@0: return [a.retvar, a.vbls]; michael@0: }(); michael@0: if (retvar == undefined && decl.resultType != 'void') throw new Error("assert"); michael@0: michael@0: { michael@0: let trace = TRACE_ESP; michael@0: for (let i = 0; i < outparam_list.length; ++i) { michael@0: let psem = [ psem_list[i] ]; michael@0: let outparam = [ outparam_list[i] ]; michael@0: let a = new OutparamCheck(cfg, psem, outparam, retvar, retvars, trace); michael@0: // This is annoying, but this field is only used for logging anyway. michael@0: a.fndecl = func_decl; michael@0: a.run(); michael@0: a.check(decl.resultType == 'void', func_decl); michael@0: } michael@0: } michael@0: michael@0: if (TRACE_PERF) timer_stop(fstring); michael@0: } michael@0: michael@0: // Outparam check analysis michael@0: function OutparamCheck(cfg, psem_list, outparam_list, retvar, retvar_set, michael@0: trace) { michael@0: // We need to save the retvars so we can detect assignments through michael@0: // their addresses passed as arguments. michael@0: this.retvar_set = retvar_set; michael@0: this.retvar = retvar; michael@0: michael@0: // We need both an ordered set and a lookup structure michael@0: this.outparam_list = outparam_list michael@0: this.outparams = create_decl_set(outparam_list); michael@0: this.psem_list = psem_list; michael@0: michael@0: // Set up property state vars for ESP michael@0: let psvar_list = []; michael@0: for each (let v in outparam_list) { michael@0: psvar_list.push(new ESP.PropVarSpec(v, true, av.NOT_WRITTEN)); michael@0: } michael@0: for (let v in retvar_set.items()) { michael@0: psvar_list.push(new ESP.PropVarSpec(v, v == this.retvar, ESP.TOP)); michael@0: } michael@0: if (trace) { michael@0: print("PS vars"); michael@0: for each (let v in this.psvar_list) { michael@0: print(" " + expr_display(v.vbl)); michael@0: } michael@0: } michael@0: this.zeroNonzero = new Zero_NonZero.Zero_NonZero(); michael@0: ESP.Analysis.call(this, cfg, psvar_list, av.meet, trace); michael@0: } michael@0: michael@0: // Abstract values for outparam check michael@0: function AbstractValue(name, ch) { michael@0: this.name = name; michael@0: this.ch = ch; michael@0: } michael@0: michael@0: AbstractValue.prototype.equals = function(v) { michael@0: return this === v; michael@0: } michael@0: michael@0: AbstractValue.prototype.toString = function() { michael@0: return this.name + ' (' + this.ch + ')'; michael@0: } michael@0: michael@0: AbstractValue.prototype.toShortString = function() { michael@0: return this.ch; michael@0: } michael@0: michael@0: let avspec = [ michael@0: // Abstract values for outparam contents write status michael@0: [ 'NULL', 'x' ], // is a null pointer michael@0: [ 'NOT_WRITTEN', '-' ], // not written michael@0: [ 'WROTE_NULL', '/' ], // had NULL written to michael@0: [ 'WRITTEN', '+' ], // had anything written to michael@0: // MAYBE_WRITTEN is special. "Officially", it means the same thing as michael@0: // NOT_WRITTEN. What it really means is that an outparam was passed michael@0: // to another function as a possible outparam (outparam type, but not michael@0: // in last position), so if there is an error with it not being written, michael@0: // we can give a hint about the possible outparam in the warning. michael@0: [ 'MAYBE_WRITTEN', '?' ], // written if possible outparam is one michael@0: ]; michael@0: michael@0: let av = {}; michael@0: for each (let [name, ch] in avspec) { michael@0: av[name] = new AbstractValue(name, ch); michael@0: } michael@0: michael@0: av.ZERO = Zero_NonZero.Lattice.ZERO; michael@0: av.NONZERO = Zero_NonZero.Lattice.NONZERO; michael@0: michael@0: /* michael@0: av.ZERO.negation = av.NONZERO; michael@0: av.NONZERO.negation = av.ZERO; michael@0: michael@0: // Abstract values for int constants. We use these to figure out feasible michael@0: // paths in the presence of GCC finally_tmp-controlled switches. michael@0: function makeIntAV(v) { michael@0: let key = 'int_' + v; michael@0: if (cachedAVs.hasOwnProperty(key)) return cachedAVs[key]; michael@0: michael@0: let s = "" + v; michael@0: let ans = cachedAVs[key] = new AbstractValue(s, s); michael@0: ans.int_val = v; michael@0: return ans; michael@0: } michael@0: */ michael@0: michael@0: let cachedAVs = {}; michael@0: michael@0: // Abstract values for pointers that contain a copy of an outparam michael@0: // pointer. We use these to figure out writes to a casted copy of michael@0: // an outparam passed to another method. michael@0: function makeOutparamAV(v) { michael@0: let key = 'outparam_' + DECL_UID(v); michael@0: if (key in cachedAVs) return cachedAVs[key]; michael@0: michael@0: let ans = cachedAVs[key] = michael@0: new AbstractValue('OUTPARAM:' + expr_display(v), 'P'); michael@0: ans.outparam = v; michael@0: return ans; michael@0: } michael@0: michael@0: /** Return the integer value if this is an integer av, otherwise undefined. */ michael@0: av.intVal = function(v) { michael@0: if (v.hasOwnProperty('int_val')) michael@0: return v.int_val; michael@0: return undefined; michael@0: } michael@0: michael@0: /** Meet function for our abstract values. */ michael@0: av.meet = function(v1, v2) { michael@0: // At this point we know v1 != v2. michael@0: let values = [v1,v2] michael@0: if (values.indexOf(av.LOCKED) != -1 michael@0: || values.indexOf(av.UNLOCKED) != -1) michael@0: return ESP.NOT_REACHED; michael@0: michael@0: return Zero_NonZero.meet(v1, v2) michael@0: }; michael@0: michael@0: // Outparam check analysis michael@0: OutparamCheck.prototype = new ESP.Analysis; michael@0: michael@0: OutparamCheck.prototype.split = function(vbl, v) { michael@0: // Can't happen for current version of ESP, but could change michael@0: if (v != ESP.TOP) throw new Error("not implemented"); michael@0: return [ av.ZERO, av.NONZERO ]; michael@0: } michael@0: michael@0: OutparamCheck.prototype.updateEdgeState = function(e) { michael@0: e.state.keepOnly(e.dest.keepVars); michael@0: } michael@0: michael@0: OutparamCheck.prototype.flowState = function(isn, state) { michael@0: switch (TREE_CODE(isn)) { michael@0: case GIMPLE_ASSIGN: michael@0: this.processAssign(isn, state); michael@0: break; michael@0: case GIMPLE_CALL: michael@0: this.processCall(isn, isn, state); michael@0: break; michael@0: case GIMPLE_SWITCH: michael@0: case GIMPLE_COND: michael@0: // This gets handled by flowStateCond instead, has no exec effect michael@0: break; michael@0: default: michael@0: this.zeroNonzero.flowState(isn, state); michael@0: } michael@0: } michael@0: michael@0: OutparamCheck.prototype.flowStateCond = function(isn, truth, state) { michael@0: this.zeroNonzero.flowStateCond(isn, truth, state); michael@0: } michael@0: michael@0: // For any outparams-specific semantics, we handle it here and then michael@0: // return. Otherwise we delegate to the zero-nonzero analysis. michael@0: OutparamCheck.prototype.processAssign = function(isn, state) { michael@0: let lhs = gimple_op(isn, 0); michael@0: let rhs = gimple_op(isn, 1); michael@0: michael@0: if (DECL_P(lhs)) { michael@0: // Unwrap NOP_EXPR, which is semantically a copy. michael@0: if (TREE_CODE(rhs) == NOP_EXPR) { michael@0: rhs = rhs.operands()[0]; michael@0: } michael@0: michael@0: if (DECL_P(rhs) && this.outparams.has(rhs)) { michael@0: // Copying an outparam pointer. We have to remember this so that michael@0: // if it is assigned thru later, we pick up the write. michael@0: state.assignValue(lhs, makeOutparamAV(rhs), isn); michael@0: return; michael@0: } michael@0: michael@0: // Cases of this switch that handle something should return from michael@0: // the function. Anything that does not return is picked up afteward. michael@0: switch (TREE_CODE(rhs)) { michael@0: case INTEGER_CST: michael@0: if (this.outparams.has(lhs)) { michael@0: warning("assigning to outparam pointer"); michael@0: return; michael@0: } michael@0: break; michael@0: case EQ_EXPR: { michael@0: // We only care about testing outparams for NULL (and then not writing) michael@0: let [op1, op2] = rhs.operands(); michael@0: if (DECL_P(op1) && this.outparams.has(op1) && expr_literal_int(op2) == 0) { michael@0: state.update(function(ss) { michael@0: let [s1, s2] = [ss, ss.copy()]; // s1 true, s2 false michael@0: s1.assignValue(lhs, av.NONZERO, isn); michael@0: s1.assignValue(op1, av.NULL, isn); michael@0: s2.assignValue(lhs, av.ZERO, isn); michael@0: return [s1, s2]; michael@0: }); michael@0: return; michael@0: } michael@0: } michael@0: break; michael@0: case CALL_EXPR: michael@0: /* Embedded CALL_EXPRs are a 4.3 issue */ michael@0: this.processCall(rhs, isn, state, lhs); michael@0: return; michael@0: michael@0: case INDIRECT_REF: michael@0: // If rhs is *outparam and pointer-typed, lhs is NULL iff rhs is michael@0: // WROTE_NULL. Required for testcase onull.cpp. michael@0: let v = rhs.operands()[0]; michael@0: if (DECL_P(v) && this.outparams.has(v) && michael@0: TREE_CODE(TREE_TYPE(v)) == POINTER_TYPE) { michael@0: state.update(function(ss) { michael@0: let val = ss.get(v) == av.WROTE_NULL ? av.ZERO : av.NONZERO; michael@0: ss.assignValue(lhs, val, isn); michael@0: return [ ss ]; michael@0: }); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // Nothing special -- delegate michael@0: this.zeroNonzero.processAssign(isn, state); michael@0: return; michael@0: } michael@0: michael@0: switch (TREE_CODE(lhs)) { michael@0: case INDIRECT_REF: michael@0: // Writing to an outparam. We want to try to figure out if we're michael@0: // writing NULL. michael@0: let e = TREE_OPERAND(lhs, 0); michael@0: if (this.outparams.has(e)) { michael@0: if (expr_literal_int(rhs) == 0) { michael@0: state.assignValue(e, av.WROTE_NULL, isn); michael@0: } else if (DECL_P(rhs)) { michael@0: state.update(function(ss) { michael@0: let [s1, s2] = [ss.copy(), ss]; // s1 NULL, s2 non-NULL michael@0: s1.assignValue(e, av.WROTE_NULL, isn); michael@0: s1.assignValue(rhs, av.ZERO, isn); michael@0: s2.assignValue(e, av.WRITTEN, isn); michael@0: s2.assignValue(rhs, av.NONZERO, isn); michael@0: return [s1,s2]; michael@0: }); michael@0: } else { michael@0: state.assignValue(e, av.WRITTEN, isn); michael@0: } michael@0: } else { michael@0: // unsound -- could be writing to anything through this ptr michael@0: } michael@0: break; michael@0: case COMPONENT_REF: // unsound michael@0: case ARRAY_REF: // unsound michael@0: case EXC_PTR_EXPR: michael@0: case FILTER_EXPR: michael@0: break; michael@0: default: michael@0: print(TREE_CODE(lhs)); michael@0: throw new Error("ni"); michael@0: } michael@0: } michael@0: michael@0: // Handle an assignment x := test(foo) where test is a simple predicate michael@0: OutparamCheck.prototype.processTest = function(lhs, call, val, blame, state) { michael@0: let arg = gimple_call_arg(call, 0); michael@0: if (DECL_P(arg)) { michael@0: this.zeroNonzero.predicate(state, lhs, val, arg, blame); michael@0: } else { michael@0: state.assignValue(lhs, ESP.TOP, blame); michael@0: } michael@0: }; michael@0: michael@0: // The big one: outparam semantics of function calls. michael@0: OutparamCheck.prototype.processCall = function(call, blame, state, dest) { michael@0: if (!dest) michael@0: dest = gimple_call_lhs(call); michael@0: michael@0: let args = gimple_call_args(call); michael@0: let callable = callable_arg_function_decl(gimple_call_fn(call)); michael@0: let psem = this.func_param_semantics(callable); michael@0: michael@0: let name = function_decl_name(callable); michael@0: if (name == 'NS_FAILED') { michael@0: this.processTest(dest, call, av.NONZERO, call, state); michael@0: return; michael@0: } else if (name == 'NS_SUCCEEDED') { michael@0: this.processTest(dest, call, av.ZERO, call, state); michael@0: return; michael@0: } else if (name == '__builtin_expect') { michael@0: // Same as an assign from arg 0 to lhs michael@0: state.assign(dest, args[0], call); michael@0: return; michael@0: } michael@0: michael@0: if (TRACE_CALL_SEM) { michael@0: print("param semantics:" + psem); michael@0: } michael@0: michael@0: if (args.length != psem.length) { michael@0: let ct = TREE_TYPE(callable); michael@0: if (TREE_CODE(ct) == POINTER_TYPE) ct = TREE_TYPE(ct); michael@0: if (args.length < psem.length || !stdarg_p(ct)) { michael@0: // TODO Can __builtin_memcpy write to an outparam? Probably not. michael@0: if (name != 'operator new' && name != 'operator delete' && michael@0: name != 'operator new []' && name != 'operator delete []' && michael@0: name.substr(0, 5) != '__cxa' && michael@0: name.substr(0, 9) != '__builtin') { michael@0: throw Error("bad len for '" + name + "': " + args.length + ' args, ' + michael@0: psem.length + ' params'); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Collect variables that are possibly written to on callee success michael@0: let updates = []; michael@0: for (let i = 0; i < psem.length; ++i) { michael@0: let arg = args[i]; michael@0: // The arg could be the address of a return-value variable. michael@0: // This means it's really the nsresult code for the call, michael@0: // so we treat it the same as the target of an rv assignment. michael@0: if (TREE_CODE(arg) == ADDR_EXPR) { michael@0: let v = arg.operands()[0]; michael@0: if (DECL_P(v) && this.retvar_set.has(v)) { michael@0: dest = v; michael@0: } michael@0: } michael@0: // The arg could be a copy of an outparam. We'll unwrap to the michael@0: // outparam if it is. The following is cheating a bit because michael@0: // we munge states together, but it should be OK in practice. michael@0: arg = unwrap_outparam(arg, state); michael@0: let sem = psem[i]; michael@0: if (sem == ps.CONST) continue; michael@0: // At this point, we know the call can write thru this param. michael@0: // Invalidate any vars whose addresses are passed here. This michael@0: // is distinct from the rv handling above. michael@0: if (TREE_CODE(arg) == ADDR_EXPR) { michael@0: let v = arg.operands()[0]; michael@0: if (DECL_P(v)) { michael@0: state.remove(v); michael@0: } michael@0: } michael@0: if (!DECL_P(arg) || !this.outparams.has(arg)) continue; michael@0: // At this point, we may be writing to an outparam michael@0: updates.push([arg, sem]); michael@0: } michael@0: michael@0: if (updates.length) { michael@0: if (dest != undefined && DECL_P(dest)) { michael@0: // Update & stored rv. Do updates predicated on success. michael@0: let [ succ_ret, fail_ret ] = ret_coding(callable); michael@0: michael@0: state.update(function(ss) { michael@0: let [s1, s2] = [ss.copy(), ss]; // s1 success, s2 fail michael@0: for each (let [vbl, sem] in updates) { michael@0: s1.assignValue(vbl, sem.val, blame); michael@0: s1.assignValue(dest, succ_ret, blame); michael@0: } michael@0: s2.assignValue(dest, fail_ret, blame); michael@0: return [s1,s2]; michael@0: }); michael@0: } else { michael@0: // Discarded rv. Per spec in the bug, we assume that either success michael@0: // or failure is possible (if not, callee should return void). michael@0: // Exceptions: Methods that return void and string mutators are michael@0: // considered no-fail. michael@0: state.update(function(ss) { michael@0: for each (let [vbl, sem] in updates) { michael@0: if (sem == ps.OUTNOFAIL || sem == ps.OUTNOFAILNOCHECK) { michael@0: ss.assignValue(vbl, av.WRITTEN, blame); michael@0: return [ss]; michael@0: } else { michael@0: let [s1, s2] = [ss.copy(), ss]; // s1 success, s2 fail michael@0: for each (let [vbl, sem] in updates) { michael@0: s1.assignValue(vbl, sem.val, blame); michael@0: } michael@0: return [s1,s2]; michael@0: } michael@0: } michael@0: }); michael@0: } michael@0: } else { michael@0: // no updates, just kill any destination for the rv michael@0: if (dest != undefined && DECL_P(dest)) { michael@0: state.remove(dest, blame); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: /** Return the return value coding of the given function. This is a pair michael@0: * [ succ, fail ] giving the abstract values of the return value under michael@0: * success and failure conditions. */ michael@0: function ret_coding(callable) { michael@0: let type = TREE_TYPE(callable); michael@0: if (TREE_CODE(type) == POINTER_TYPE) type = TREE_TYPE(type); michael@0: michael@0: let rtname = TYPE_NAME(TREE_TYPE(type)); michael@0: if (rtname && IDENTIFIER_POINTER(DECL_NAME(rtname)) == 'PRBool') { michael@0: return [ av.NONZERO, av.ZERO ]; michael@0: } else { michael@0: return [ av.ZERO, av.NONZERO ]; michael@0: } michael@0: } michael@0: michael@0: function unwrap_outparam(arg, state) { michael@0: if (!DECL_P(arg) || state.factory.outparams.has(arg)) return arg; michael@0: michael@0: let outparam; michael@0: for (let ss in state.substates.getValues()) { michael@0: let val = ss.get(arg); michael@0: if (val != undefined && val.hasOwnProperty('outparam')) { michael@0: outparam = val.outparam; michael@0: } michael@0: } michael@0: if (outparam) return outparam; michael@0: return arg; michael@0: } michael@0: michael@0: // Check for errors. Must .run() analysis before calling this. michael@0: OutparamCheck.prototype.check = function(isvoid, fndecl) { michael@0: let state = this.cfg.x_exit_block_ptr.stateOut; michael@0: for (let substate in state.substates.getValues()) { michael@0: this.checkSubstate(isvoid, fndecl, substate); michael@0: } michael@0: } michael@0: michael@0: OutparamCheck.prototype.checkSubstate = function(isvoid, fndecl, ss) { michael@0: if (isvoid) { michael@0: this.checkSubstateSuccess(ss); michael@0: } else { michael@0: let [succ, fail] = ret_coding(fndecl); michael@0: let rv = ss.get(this.retvar); michael@0: // We want to check if the abstract value of the rv is entirely michael@0: // contained in the success or failure condition. michael@0: if (av.meet(rv, succ) == rv) { michael@0: this.checkSubstateSuccess(ss); michael@0: } else if (av.meet(rv, fail) == rv) { michael@0: this.checkSubstateFailure(ss); michael@0: } else { michael@0: // This condition indicates a bug in outparams.js. We'll just michael@0: // warn so we don't break static analysis builds. michael@0: warning("Outparams checker cannot determine rv success/failure", michael@0: location_of(fndecl)); michael@0: this.checkSubstateSuccess(ss); michael@0: this.checkSubstateFailure(ss); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* @return The return statement in the function michael@0: * that writes the return value in the given substate. michael@0: * If the function returns void, then the substate doesn't michael@0: * matter and we just look for the return. */ michael@0: OutparamCheck.prototype.findReturnStmt = function(ss) { michael@0: if (this.retvar != undefined) michael@0: return ss.getBlame(this.retvar); michael@0: michael@0: if (this.cfg._cached_return) michael@0: return this.cfg._cached_return; michael@0: michael@0: for (let bb in cfg_bb_iterator(this.cfg)) { michael@0: for (let isn in bb_isn_iterator(bb)) { michael@0: if (isn.tree_code() == GIMPLE_RETURN) { michael@0: return this.cfg._cached_return = isn; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return undefined; michael@0: } michael@0: michael@0: OutparamCheck.prototype.checkSubstateSuccess = function(ss) { michael@0: for (let i = 0; i < this.psem_list.length; ++i) { michael@0: let [v, psem] = [ this.outparam_list[i], this.psem_list[i] ]; michael@0: if (psem == ps.INOUT) continue; michael@0: let val = ss.get(v); michael@0: if (val == av.NOT_WRITTEN) { michael@0: this.logResult('succ', 'not_written', 'error'); michael@0: this.warn([this.findReturnStmt(ss), "outparam '" + expr_display(v) + "' not written on NS_SUCCEEDED(return value)"], michael@0: [v, "outparam declared here"]); michael@0: } else if (val == av.MAYBE_WRITTEN) { michael@0: this.logResult('succ', 'maybe_written', 'error'); michael@0: michael@0: let blameStmt = ss.getBlame(v); michael@0: let callMsg; michael@0: let callName = ""; michael@0: try { michael@0: let call = TREE_CHECK(blameStmt, GIMPLE_CALL, GIMPLE_MODIFY_STMT); michael@0: let callDecl = callable_arg_function_decl(gimple_call_fn(call)); michael@0: michael@0: callMsg = [callDecl, "declared here"]; michael@0: callName = " '" + decl_name(callDecl) + "'"; michael@0: } michael@0: catch (e if e.TreeCheckError) { } michael@0: michael@0: this.warn([this.findReturnStmt(ss), "outparam '" + expr_display(v) + "' not written on NS_SUCCEEDED(return value)"], michael@0: [v, "outparam declared here"], michael@0: [blameStmt, "possibly written by unannotated function call" + callName], michael@0: callMsg); michael@0: } else { michael@0: this.logResult('succ', '', 'ok'); michael@0: } michael@0: } michael@0: } michael@0: michael@0: OutparamCheck.prototype.checkSubstateFailure = function(ss) { michael@0: for (let i = 0; i < this.psem_list.length; ++i) { michael@0: let [v, ps] = [ this.outparam_list[i], this.psem_list[i] ]; michael@0: let val = ss.get(v); michael@0: if (val == av.WRITTEN) { michael@0: this.logResult('fail', 'written', 'error'); michael@0: if (WARN_ON_SET_FAILURE) { michael@0: this.warn([this.findReturnStmt(ss), "outparam '" + expr_display(v) + "' written on NS_FAILED(return value)"], michael@0: [v, "outparam declared here"], michael@0: [ss.getBlame(v), "written here"]); michael@0: } michael@0: } else if (val == av.WROTE_NULL) { michael@0: this.logResult('fail', 'wrote_null', 'warning'); michael@0: if (WARN_ON_SET_NULL) { michael@0: this.warn([this.findReturnStmt(ss), "NULL written to outparam '" + expr_display(v) + "' on NS_FAILED(return value)"], michael@0: [v, "outparam declared here"], michael@0: [ss.getBlame(v), "written here"]); michael@0: } michael@0: } else { michael@0: this.logResult('fail', '', 'ok'); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Generate a warning from one or more tuples [treeforloc, message] michael@0: */ michael@0: OutparamCheck.prototype.warn = function(arg0) { michael@0: let loc = safe_location_of(arg0[0]); michael@0: let msg = arg0[1]; michael@0: michael@0: for (let i = 1; i < arguments.length; ++i) { michael@0: if (arguments[i] === undefined) continue; michael@0: let [atree, amsg] = arguments[i]; michael@0: msg += "\n" + loc_string(safe_location_of(atree)) + ": " + amsg; michael@0: } michael@0: warning(msg, loc); michael@0: } michael@0: michael@0: OutparamCheck.prototype.logResult = function(rv, msg, kind) { michael@0: if (LOG_RESULTS) { michael@0: let s = [ '"' + x + '"' for each (x in [ loc_string(location_of(this.fndecl)), function_decl_name(this.fndecl), rv, msg, kind ]) ].join(', '); michael@0: print(":LR: (" + s + ")"); michael@0: } michael@0: } michael@0: michael@0: // Parameter Semantics values -- indicates whether a parameter is michael@0: // an outparam. michael@0: // label Used for debugging output michael@0: // val Abstract value (state) that holds on an argument after michael@0: // a call michael@0: // check True if parameters with this semantics should be michael@0: // checked by this analysis michael@0: let ps = { michael@0: OUTNOFAIL: { label: 'out-no-fail', val: av.WRITTEN, check: true }, michael@0: // Special value for receiver of strings methods. Callers should michael@0: // consider this to be an outparam (i.e., it modifies the string), michael@0: // but we don't want to check the method itself. michael@0: OUTNOFAILNOCHECK: { label: 'out-no-fail-no-check' }, michael@0: OUT: { label: 'out', val: av.WRITTEN, check: true }, michael@0: INOUT: { label: 'inout', val: av.WRITTEN, check: true }, michael@0: MAYBE: { label: 'maybe', val: av.MAYBE_WRITTEN}, // maybe out michael@0: CONST: { label: 'const' } // i.e. not out michael@0: }; michael@0: michael@0: // Return the param semantics of a FUNCTION_DECL or VAR_DECL representing michael@0: // a function pointer. The result is a pair [ ann, sems ]. michael@0: OutparamCheck.prototype.func_param_semantics = function(callable) { michael@0: let ftype = TREE_TYPE(callable); michael@0: if (TREE_CODE(ftype) == POINTER_TYPE) ftype = TREE_TYPE(ftype); michael@0: // What failure semantics to use for outparams michael@0: let rtype = TREE_TYPE(ftype); michael@0: let nofail = TREE_CODE(rtype) == VOID_TYPE; michael@0: // Whether to guess outparams by type michael@0: let guess = type_string(rtype) == 'nsresult'; michael@0: michael@0: // Set up param lists for analysis michael@0: let params; // param decls, if available michael@0: let types; // param types michael@0: let string_mutator = false; michael@0: if (TREE_CODE(callable) == FUNCTION_DECL) { michael@0: params = [ p for (p in function_decl_params(callable)) ]; michael@0: types = [ TREE_TYPE(p) for each (p in params) ]; michael@0: string_mutator = is_string_mutator(callable); michael@0: } else { michael@0: types = [ p for (p in function_type_args(ftype)) michael@0: if (TREE_CODE(p) != VOID_TYPE) ]; michael@0: } michael@0: michael@0: // Analyze params michael@0: let ans = []; michael@0: for (let i = 0; i < types.length; ++i) { michael@0: let sem; michael@0: if (i == 0 && string_mutator) { michael@0: // Special case: string mutator receiver is an no-fail outparams michael@0: // but not checkable michael@0: sem = ps.OUTNOFAILNOCHECK; michael@0: } else { michael@0: if (params) sem = decode_attr(DECL_ATTRIBUTES(params[i])); michael@0: if (TRACE_CALL_SEM >= 2) print("param " + i + ": annotated " + sem); michael@0: if (sem == undefined) { michael@0: sem = decode_attr(TYPE_ATTRIBUTES(types[i])); michael@0: if (TRACE_CALL_SEM >= 2) print("type " + i + ": annotated " + sem); michael@0: if (sem == undefined) { michael@0: if (guess && type_is_outparam(types[i])) { michael@0: // Params other than last are guessed as MAYBE michael@0: sem = i < types.length - 1 ? ps.MAYBE : ps.OUT; michael@0: } else { michael@0: sem = ps.CONST; michael@0: } michael@0: } michael@0: } michael@0: if (sem == ps.OUT && nofail) sem = ps.OUTNOFAIL; michael@0: } michael@0: if (sem == undefined) throw new Error("assert"); michael@0: ans.push(sem); michael@0: } michael@0: return ans; michael@0: } michael@0: michael@0: /* Decode parameter semantics GCC attributes. michael@0: * @param attrs GCC attributes of a parameter. E.g., TYPE_ATTRIBUTES michael@0: * or DECL_ATTRIBUTES of an item michael@0: * @return The parameter semantics value defined by the attributes, michael@0: * or undefined if no such attributes were present. */ michael@0: function decode_attr(attrs) { michael@0: // Note: we're not checking for conflicts, we just take the first michael@0: // one we find. michael@0: for each (let attr in rectify_attributes(attrs)) { michael@0: if (attr.name == 'user') { michael@0: for each (let arg in attr.args) { michael@0: if (arg == 'NS_outparam') { michael@0: return ps.OUT; michael@0: } else if (arg == 'NS_inoutparam') { michael@0: return ps.INOUT; michael@0: } else if (arg == 'NS_inparam') { michael@0: return ps.CONST; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: return undefined; michael@0: } michael@0: michael@0: /* @return true if the given type appears to be an outparam michael@0: * type based on the type alone (i.e., not considering michael@0: * attributes. */ michael@0: function type_is_outparam(type) { michael@0: switch (TREE_CODE(type)) { michael@0: case POINTER_TYPE: michael@0: return pointer_type_is_outparam(TREE_TYPE(type)); michael@0: case REFERENCE_TYPE: michael@0: let rt = TREE_TYPE(type); michael@0: return !TYPE_READONLY(rt) && is_string_type(rt); michael@0: default: michael@0: // Note: This is unsound for UNION_TYPE, because the union could michael@0: // contain a pointer. michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: /* Helper for type_is_outparam. michael@0: * @return true if 'pt *' looks like an outparam type. */ michael@0: function pointer_type_is_outparam(pt) { michael@0: if (TYPE_READONLY(pt)) return false; michael@0: michael@0: switch (TREE_CODE(pt)) { michael@0: case POINTER_TYPE: michael@0: case ARRAY_TYPE: { michael@0: // Look for void **, nsIFoo **, char **, PRUnichar ** michael@0: let ppt = TREE_TYPE(pt); michael@0: let tname = TYPE_NAME(ppt); michael@0: if (tname == undefined) return false; michael@0: let name = decl_name_string(tname); michael@0: return name == 'void' || name == 'char' || name == 'PRUnichar' || michael@0: name.substr(0, 3) == 'nsI'; michael@0: } michael@0: case INTEGER_TYPE: { michael@0: // char * and PRUnichar * are probably strings, otherwise guess michael@0: // it is an integer outparam. michael@0: let name = decl_name_string(TYPE_NAME(pt)); michael@0: return name != 'char' && name != 'PRUnichar'; michael@0: } michael@0: case ENUMERAL_TYPE: michael@0: case REAL_TYPE: michael@0: case UNION_TYPE: michael@0: case BOOLEAN_TYPE: michael@0: return true; michael@0: case RECORD_TYPE: michael@0: // TODO: should we consider field writes? michael@0: return false; michael@0: case FUNCTION_TYPE: michael@0: case VOID_TYPE: michael@0: return false; michael@0: default: michael@0: throw new Error("can't guess if a pointer to this type is an outparam: " + michael@0: TREE_CODE(pt) + ': ' + type_string(pt)); michael@0: } michael@0: } michael@0: michael@0: // Map type name to boolean as to whether it is a string. michael@0: let cached_string_types = MapFactory.create_map( michael@0: function (x, y) x == y, michael@0: function (x) x, michael@0: function (t) t, michael@0: function (t) t); michael@0: michael@0: // Base string types. Others will be found by searching the inheritance michael@0: // graph. michael@0: michael@0: cached_string_types.put('nsAString', true); michael@0: cached_string_types.put('nsACString', true); michael@0: cached_string_types.put('nsAString_internal', true); michael@0: cached_string_types.put('nsACString_internal', true); michael@0: michael@0: // Return true if the given type represents a Mozilla string type. michael@0: // The binfo arg is the binfo to use for further iteration. This is michael@0: // for internal use only, users of this function should pass only michael@0: // one arg. michael@0: function is_string_type(type, binfo) { michael@0: if (TREE_CODE(type) != RECORD_TYPE) return false; michael@0: //print(">>>IST " + type_string(type)); michael@0: let name = decl_name_string(TYPE_NAME(type)); michael@0: let ans = cached_string_types.get(name); michael@0: if (ans != undefined) return ans; michael@0: michael@0: ans = false; michael@0: binfo = binfo != undefined ? binfo : TYPE_BINFO(type); michael@0: if (binfo != undefined) { michael@0: for each (let base in VEC_iterate(BINFO_BASE_BINFOS(binfo))) { michael@0: let parent_ans = is_string_type(BINFO_TYPE(base), base); michael@0: if (parent_ans) { michael@0: ans = true; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: cached_string_types.put(name, ans); michael@0: //print("<<