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