xpcom/analysis/outparams.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial