Backout 30bfb150da06 (bug 449315) due to unit test timeouts.
[wine-gecko.git] / xpcom / analysis / outparams.js
blob312d8b57b7fad17eacd34d8fbe1383b3e7114970
1 require({ version: '1.8' });
2 require({ after_gcc_pass: 'cfg' });
4 include('treehydra.js');
6 include('util.js');
7 include('gcc_util.js');
8 include('gcc_print.js');
9 include('unstable/adts.js');
10 include('unstable/analysis.js');
11 include('unstable/esp.js');
12 let Zero_NonZero = {};
13 include('unstable/zero_nonzero.js', Zero_NonZero);
15 include('mayreturn.js');
17 function safe_location_of(t) {
18 if (t === undefined)
19 return UNKNOWN_LOCATION;
21 return location_of(t);
24 MapFactory.use_injective = true;
26 // Print a trace for each function analyzed
27 let TRACE_FUNCTIONS = 0;
28 // Trace operation of the ESP analysis, use 2 or 3 for more detail
29 let TRACE_ESP = 0;
30 // Trace determination of function call parameter semantics, 2 for detail
31 let TRACE_CALL_SEM = 0;
32 // Print time-taken stats
33 let TRACE_PERF = 0;
34 // Log analysis results in a special format
35 let LOG_RESULTS = false;
37 let WARN_ON_SET_NULL = false;
39 // Filter functions to process per CLI
40 let func_filter;
41 if (this.arg == undefined || this.arg == '') {
42 func_filter = function(fd) true;
43 } else {
44 func_filter = function(fd) function_decl_name(fd) == this.arg;
47 function process_tree(func_decl) {
48 if (!func_filter(func_decl)) return;
50 // Determine outparams and return if function not relevant
51 if (is_constructor(func_decl)) return;
52 let psem = OutparamCheck.prototype.func_param_semantics(func_decl);
53 if (!psem.some(function(x) x.check)) return;
54 let decl = rectify_function_decl(func_decl);
55 if (decl.resultType != 'nsresult' && decl.resultType != 'PRBool' &&
56 decl.resultType != 'void') {
57 warning("Cannot analyze outparam usage for function with return type '" +
58 decl.resultType + "'", location_of(func_decl));
59 return;
62 let params = [ v for (v in flatten_chain(DECL_ARGUMENTS(func_decl))) ];
63 let outparam_list = [];
64 let psem_list = [];
65 for (let i = 0; i < psem.length; ++i) {
66 if (psem[i].check) {
67 outparam_list.push(params[i]);
68 psem_list.push(psem[i]);
71 if (outparam_list.length == 0) return;
73 // At this point we have a function we want to analyze
74 let fstring = rfunc_string(decl);
75 if (TRACE_FUNCTIONS) {
76 print('* function ' + fstring);
77 print(' ' + loc_string(location_of(func_decl)));
79 if (TRACE_PERF) timer_start(fstring);
80 for (let i = 0; i < outparam_list.length; ++i) {
81 let p = outparam_list[i];
82 if (TRACE_FUNCTIONS) {
83 print(" outparam " + expr_display(p) + " " + DECL_UID(p) + ' ' +
84 psem_list[i].label);
88 let cfg = function_decl_cfg(func_decl);
90 let [retvar, retvars] = function() {
91 let trace = 0;
92 let a = new MayReturnAnalysis(cfg, trace);
93 a.run();
94 return [a.retvar, a.vbls];
95 }();
96 if (retvar == undefined && decl.resultType != 'void') throw new Error("assert");
99 let trace = TRACE_ESP;
100 for (let i = 0; i < outparam_list.length; ++i) {
101 let psem = [ psem_list[i] ];
102 let outparam = [ outparam_list[i] ];
103 let a = new OutparamCheck(cfg, psem, outparam, retvar, retvars, trace);
104 // This is annoying, but this field is only used for logging anyway.
105 a.fndecl = func_decl;
106 a.run();
107 a.check(decl.resultType == 'void', func_decl);
111 if (TRACE_PERF) timer_stop(fstring);
114 function is_constructor(function_decl)
116 return function_decl.decl_common.lang_specific.decl_flags.constructor_attr;
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));
137 for (let v in retvar_set.items()) {
138 psvar_list.push(new ESP.PropVarSpec(v, v == this.retvar, ESP.TOP));
140 if (trace) {
141 print("PS vars");
142 for each (let v in this.psvar_list) {
143 print(" " + expr_display(v.vbl));
146 this.zeroNonzero = new Zero_NonZero.Zero_NonZero();
147 ESP.Analysis.call(this, cfg, psvar_list, av.meet, trace);
150 // Abstract values for outparam check
151 function AbstractValue(name, ch) {
152 this.name = name;
153 this.ch = ch;
156 AbstractValue.prototype.equals = function(v) {
157 return this === v;
160 AbstractValue.prototype.toString = function() {
161 return this.name + ' (' + this.ch + ')';
164 AbstractValue.prototype.toShortString = function() {
165 return this.ch;
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
182 let av = {};
183 for each (let [name, ch] in avspec) {
184 av[name] = new AbstractValue(name, ch);
187 av.ZERO = Zero_NonZero.Lattice.ZERO;
188 av.NONZERO = Zero_NonZero.Lattice.NONZERO;
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;
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;
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;
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)
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 ];
249 OutparamCheck.prototype.updateEdgeState = function(e) {
250 e.state.keepOnly(e.dest.keepVars);
253 OutparamCheck.prototype.flowState = function(isn, state) {
254 switch (TREE_CODE(isn)) {
255 case GIMPLE_MODIFY_STMT:
256 this.processAssign(isn, state);
257 break;
258 case CALL_EXPR:
259 this.processCall(undefined, isn, isn, state);
260 break;
261 case SWITCH_EXPR:
262 case COND_EXPR:
263 // This gets handled by flowStateCond instead, has no exec effect
264 break;
265 default:
266 this.zeroNonzero.flowState(isn, state);
270 OutparamCheck.prototype.flowStateCond = function(isn, truth, state) {
271 this.zeroNonzero.flowStateCond(isn, truth, state);
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 = isn.operands()[0];
278 let rhs = isn.operands()[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];
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;
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;
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];
313 return;
316 break;
317 case CALL_EXPR:
318 let fname = call_function_name(rhs);
319 if (fname == 'NS_FAILED') {
320 this.processTest(lhs, rhs, av.NONZERO, isn, state);
321 } else if (fname == 'NS_SUCCEEDED') {
322 this.processTest(lhs, rhs, av.ZERO, isn, state);
323 } else if (fname == '__builtin_expect') {
324 // Same as an assign from arg 0 to lhs
325 state.assign(lhs, call_args(rhs)[0], isn);
326 } else {
327 this.processCall(lhs, rhs, isn, state);
329 return;
331 case INDIRECT_REF:
332 // If rhs is *outparam and pointer-typed, lhs is NULL iff rhs is
333 // WROTE_NULL. Required for testcase onull.cpp.
334 let v = rhs.operands()[0];
335 if (DECL_P(v) && this.outparams.has(v) &&
336 TREE_CODE(TREE_TYPE(v)) == POINTER_TYPE) {
337 state.update(function(ss) {
338 let val = ss.get(v) == av.WROTE_NULL ? av.ZERO : av.NONZERO;
339 ss.assignValue(lhs, val, isn);
340 return [ ss ];
342 return;
346 // Nothing special -- delegate
347 this.zeroNonzero.processAssign(isn, state);
348 return;
351 switch (TREE_CODE(lhs)) {
352 case INDIRECT_REF:
353 // Writing to an outparam. We want to try to figure out if we're
354 // writing NULL.
355 let e = TREE_OPERAND(lhs, 0);
356 if (this.outparams.has(e)) {
357 if (expr_literal_int(rhs) == 0) {
358 state.assignValue(e, av.WROTE_NULL, isn);
359 } else if (DECL_P(rhs)) {
360 state.update(function(ss) {
361 let [s1, s2] = [ss.copy(), ss]; // s1 NULL, s2 non-NULL
362 s1.assignValue(e, av.WROTE_NULL, isn);
363 s1.assignValue(rhs, av.ZERO, isn);
364 s2.assignValue(e, av.WRITTEN, isn);
365 s2.assignValue(rhs, av.NONZERO, isn);
366 return [s1,s2];
368 } else {
369 state.assignValue(e, av.WRITTEN, isn);
371 } else {
372 // unsound -- could be writing to anything through this ptr
374 break;
375 case COMPONENT_REF: // unsound
376 case ARRAY_REF: // unsound
377 case EXC_PTR_EXPR:
378 case FILTER_EXPR:
379 break;
380 default:
381 print(TREE_CODE(lhs));
382 throw new Error("ni");
386 // Handle an assignment x := test(foo) where test is a simple predicate
387 OutparamCheck.prototype.processTest = function(lhs, call, val, blame, state) {
388 let arg = call_arg(call, 0);
389 if (DECL_P(arg)) {
390 this.zeroNonzero.predicate(state, lhs, val, arg, blame);
391 } else {
392 state.assignValue(lhs, ESP.TOP, blame);
396 // The big one: outparam semantics of function calls.
397 OutparamCheck.prototype.processCall = function(dest, expr, blame, state) {
398 let args = call_args(expr);
399 let callable = callable_arg_function_decl(CALL_EXPR_FN(expr));
400 let psem = this.func_param_semantics(callable);
402 if (TRACE_CALL_SEM) {
403 print("param semantics:" + psem);
406 if (args.length != psem.length) {
407 let ct = TREE_TYPE(callable);
408 if (TREE_CODE(ct) == POINTER_TYPE) ct = TREE_TYPE(ct);
409 if (args.length < psem.length || !stdarg_p(ct)) {
410 let name = function_decl_name(callable);
411 // TODO Can __builtin_memcpy write to an outparam? Probably not.
412 if (name != 'operator new' && name != 'operator delete' &&
413 name != 'operator new []' && name != 'operator delete []' &&
414 name.substr(0, 5) != '__cxa' &&
415 name.substr(0, 9) != '__builtin') {
416 throw Error("bad len for '" + name + "': " + args.length + ' args, ' +
417 psem.length + ' params');
422 // Collect variables that are possibly written to on callee success
423 let updates = [];
424 for (let i = 0; i < psem.length; ++i) {
425 let arg = args[i];
426 // The arg could be the address of a return-value variable.
427 // This means it's really the nsresult code for the call,
428 // so we treat it the same as the target of an rv assignment.
429 if (TREE_CODE(arg) == ADDR_EXPR) {
430 let v = arg.operands()[0];
431 if (DECL_P(v) && this.retvar_set.has(v)) {
432 dest = v;
435 // The arg could be a copy of an outparam. We'll unwrap to the
436 // outparam if it is. The following is cheating a bit because
437 // we munge states together, but it should be OK in practice.
438 arg = unwrap_outparam(arg, state);
439 let sem = psem[i];
440 if (sem == ps.CONST) continue;
441 // At this point, we know the call can write thru this param.
442 // Invalidate any vars whose addresses are passed here. This
443 // is distinct from the rv handling above.
444 if (TREE_CODE(arg) == ADDR_EXPR) {
445 let v = arg.operands()[0];
446 if (DECL_P(v)) {
447 state.remove(v);
450 if (!DECL_P(arg) || !this.outparams.has(arg)) continue;
451 // At this point, we may be writing to an outparam
452 updates.push([arg, sem]);
455 if (updates.length) {
456 if (dest != undefined && DECL_P(dest)) {
457 // Update & stored rv. Do updates predicated on success.
458 let [ succ_ret, fail_ret ] = ret_coding(callable);
460 state.update(function(ss) {
461 let [s1, s2] = [ss.copy(), ss]; // s1 success, s2 fail
462 for each (let [vbl, sem] in updates) {
463 s1.assignValue(vbl, sem.val, blame);
464 s1.assignValue(dest, succ_ret, blame);
466 s2.assignValue(dest, fail_ret, blame);
467 return [s1,s2];
469 } else {
470 // Discarded rv. Per spec in the bug, we assume that either success
471 // or failure is possible (if not, callee should return void).
472 // Exceptions: Methods that return void and string mutators are
473 // considered no-fail.
474 state.update(function(ss) {
475 for each (let [vbl, sem] in updates) {
476 if (sem == ps.OUTNOFAIL || sem == ps.OUTNOFAILNOCHECK) {
477 ss.assignValue(vbl, av.WRITTEN, blame);
478 return [ss];
479 } else {
480 let [s1, s2] = [ss.copy(), ss]; // s1 success, s2 fail
481 for each (let [vbl, sem] in updates) {
482 s1.assignValue(vbl, sem.val, blame);
484 return [s1,s2];
489 } else {
490 // no updates, just kill any destination for the rv
491 if (dest != undefined && DECL_P(dest)) {
492 state.remove(dest, blame);
497 /** Return the return value coding of the given function. This is a pair
498 * [ succ, fail ] giving the abstract values of the return value under
499 * success and failure conditions. */
500 function ret_coding(callable) {
501 let type = TREE_TYPE(callable);
502 if (TREE_CODE(type) == POINTER_TYPE) type = TREE_TYPE(type);
504 let rtname = TYPE_NAME(TREE_TYPE(type));
505 if (rtname && IDENTIFIER_POINTER(DECL_NAME(rtname)) == 'PRBool') {
506 return [ av.NONZERO, av.ZERO ];
507 } else {
508 return [ av.ZERO, av.NONZERO ];
512 function unwrap_outparam(arg, state) {
513 if (!DECL_P(arg) || state.factory.outparams.has(arg)) return arg;
515 let outparam;
516 for (let ss in state.substates.getValues()) {
517 let val = ss.get(arg);
518 if (val != undefined && val.hasOwnProperty('outparam')) {
519 outparam = val.outparam;
522 if (outparam) return outparam;
523 return arg;
526 // Check for errors. Must .run() analysis before calling this.
527 OutparamCheck.prototype.check = function(isvoid, fndecl) {
528 let state = this.cfg.x_exit_block_ptr.stateOut;
529 for (let substate in state.substates.getValues()) {
530 this.checkSubstate(isvoid, fndecl, substate);
534 OutparamCheck.prototype.checkSubstate = function(isvoid, fndecl, ss) {
535 if (isvoid) {
536 this.checkSubstateSuccess(ss);
537 } else {
538 let [succ, fail] = ret_coding(fndecl);
539 let rv = ss.get(this.retvar);
540 // We want to check if the abstract value of the rv is entirely
541 // contained in the success or failure condition.
542 if (av.meet(rv, succ) == rv) {
543 this.checkSubstateSuccess(ss);
544 } else if (av.meet(rv, fail) == rv) {
545 this.checkSubstateFailure(ss);
546 } else {
547 // This condition indicates a bug in outparams.js. We'll just
548 // warn so we don't break static analysis builds.
549 warning("Outparams checker cannot determine rv success/failure",
550 location_of(fndecl));
551 this.checkSubstateSuccess(ss);
552 this.checkSubstateFailure(ss);
557 /* @return The return statement in the function
558 * that writes the return value in the given substate.
559 * If the function returns void, then the substate doesn't
560 * matter and we just look for the return. */
561 OutparamCheck.prototype.findReturnStmt = function(ss) {
562 if (this.retvar != undefined)
563 return ss.getBlame(this.retvar);
565 if (this.cfg._cached_return)
566 return this.cfg._cached_return;
568 for (let bb in cfg_bb_iterator(this.cfg)) {
569 for (let isn in bb_isn_iterator(bb)) {
570 if (TREE_CODE(isn) == RETURN_EXPR) {
571 return this.cfg._cached_return = isn;
576 return undefined;
579 OutparamCheck.prototype.checkSubstateSuccess = function(ss) {
580 for (let i = 0; i < this.psem_list.length; ++i) {
581 let [v, psem] = [ this.outparam_list[i], this.psem_list[i] ];
582 if (psem == ps.INOUT) continue;
583 let val = ss.get(v);
584 if (val == av.NOT_WRITTEN) {
585 this.logResult('succ', 'not_written', 'error');
586 this.warn([this.findReturnStmt(ss), "outparam '" + expr_display(v) + "' not written on NS_SUCCEEDED(return value)"],
587 [v, "outparam declared here"]);
588 } else if (val == av.MAYBE_WRITTEN) {
589 this.logResult('succ', 'maybe_written', 'error');
591 let blameStmt = ss.getBlame(v);
592 let callMsg;
593 let callName = "";
594 try {
595 let callExpr = blameStmt.tree_check(GIMPLE_MODIFY_STMT).
596 operands()[1].tree_check(CALL_EXPR);
597 let callDecl = callable_arg_function_decl(CALL_EXPR_FN(callExpr));
599 callMsg = [callDecl, "declared here"];
600 callName = " '" + decl_name(callDecl) + "'";
602 catch (e if e.TreeCheckError) { }
604 this.warn([this.findReturnStmt(ss), "outparam '" + expr_display(v) + "' not written on NS_SUCCEEDED(return value)"],
605 [v, "outparam declared here"],
606 [blameStmt, "possibly written by unannotated function call" + callName],
607 callMsg);
608 } else {
609 this.logResult('succ', '', 'ok');
614 OutparamCheck.prototype.checkSubstateFailure = function(ss) {
615 for (let i = 0; i < this.psem_list.length; ++i) {
616 let [v, ps] = [ this.outparam_list[i], this.psem_list[i] ];
617 let val = ss.get(v);
618 if (val == av.WRITTEN) {
619 this.logResult('fail', 'written', 'error');
620 this.warn([this.findReturnStmt(ss), "outparam '" + expr_display(v) + "' written on NS_FAILED(return value)"],
621 [v, "outparam declared here"],
622 [ss.getBlame(v), "written here"]);
623 } else if (val == av.WROTE_NULL) {
624 this.logResult('fail', 'wrote_null', 'warning');
625 if (WARN_ON_SET_NULL) {
626 this.warn([this.findReturnStmt(ss), "NULL written to outparam '" + expr_display(v) + "' on NS_FAILED(return value)"],
627 [v, "outparam declared here"],
628 [ss.getBlame(v), "written here"]);
630 } else {
631 this.logResult('fail', '', 'ok');
637 * Generate a warning from one or more tuples [treeforloc, message]
639 OutparamCheck.prototype.warn = function(arg0) {
640 let loc = safe_location_of(arg0[0]);
641 let msg = arg0[1];
643 for (let i = 1; i < arguments.length; ++i) {
644 if (arguments[i] === undefined) continue;
645 let [atree, amsg] = arguments[i];
646 msg += "\n" + loc_string(safe_location_of(atree)) + ": " + amsg;
648 warning(msg, loc);
651 OutparamCheck.prototype.logResult = function(rv, msg, kind) {
652 if (LOG_RESULTS) {
653 let s = [ '"' + x + '"' for each (x in [ loc_string(location_of(this.fndecl)), function_decl_name(this.fndecl), rv, msg, kind ]) ].join(', ');
654 print(":LR: (" + s + ")");
658 // Parameter Semantics values -- indicates whether a parameter is
659 // an outparam.
660 // label Used for debugging output
661 // val Abstract value (state) that holds on an argument after
662 // a call
663 // check True if parameters with this semantics should be
664 // checked by this analysis
665 let ps = {
666 OUTNOFAIL: { label: 'out-no-fail', val: av.WRITTEN, check: true },
667 // Special value for receiver of strings methods. Callers should
668 // consider this to be an outparam (i.e., it modifies the string),
669 // but we don't want to check the method itself.
670 OUTNOFAILNOCHECK: { label: 'out-no-fail-no-check' },
671 OUT: { label: 'out', val: av.WRITTEN, check: true },
672 INOUT: { label: 'inout', val: av.WRITTEN, check: true },
673 MAYBE: { label: 'maybe', val: av.MAYBE_WRITTEN}, // maybe out
674 CONST: { label: 'const' } // i.e. not out
677 // Return the param semantics of a FUNCTION_DECL or VAR_DECL representing
678 // a function pointer. The result is a pair [ ann, sems ].
679 OutparamCheck.prototype.func_param_semantics = function(callable) {
680 let ftype = TREE_TYPE(callable);
681 if (TREE_CODE(ftype) == POINTER_TYPE) ftype = TREE_TYPE(ftype);
682 // What failure semantics to use for outparams
683 let rtype = TREE_TYPE(ftype);
684 let nofail = TREE_CODE(rtype) == VOID_TYPE;
685 // Whether to guess outparams by type
686 let guess = type_string(rtype) == 'nsresult';
688 // Set up param lists for analysis
689 let params; // param decls, if available
690 let types; // param types
691 let string_mutator = false;
692 if (TREE_CODE(callable) == FUNCTION_DECL) {
693 params = [ p for (p in function_decl_params(callable)) ];
694 types = [ TREE_TYPE(p) for each (p in params) ];
695 string_mutator = is_string_mutator(callable);
696 } else {
697 types = [ p for (p in function_type_args(ftype))
698 if (TREE_CODE(p) != VOID_TYPE) ];
701 // Analyze params
702 let ans = [];
703 for (let i = 0; i < types.length; ++i) {
704 let sem;
705 if (i == 0 && string_mutator) {
706 // Special case: string mutator receiver is an no-fail outparams
707 // but not checkable
708 sem = ps.OUTNOFAILNOCHECK;
709 } else {
710 if (params) sem = decode_attr(DECL_ATTRIBUTES(params[i]));
711 if (TRACE_CALL_SEM >= 2) print("param " + i + ": annotated " + sem);
712 if (sem == undefined) {
713 sem = decode_attr(TYPE_ATTRIBUTES(types[i]));
714 if (TRACE_CALL_SEM >= 2) print("type " + i + ": annotated " + sem);
715 if (sem == undefined) {
716 if (guess && type_is_outparam(types[i])) {
717 // Params other than last are guessed as MAYBE
718 sem = i < types.length - 1 ? ps.MAYBE : ps.OUT;
719 } else {
720 sem = ps.CONST;
724 if (sem == ps.OUT && nofail) sem = ps.OUTNOFAIL;
726 if (sem == undefined) throw new Error("assert");
727 ans.push(sem);
729 return ans;
732 /* Decode parameter semantics GCC attributes.
733 * @param attrs GCC attributes of a parameter. E.g., TYPE_ATTRIBUTES
734 * or DECL_ATTRIBUTES of an item
735 * @return The parameter semantics value defined by the attributes,
736 * or undefined if no such attributes were present. */
737 function decode_attr(attrs) {
738 // Note: we're not checking for conflicts, we just take the first
739 // one we find.
740 for each (let attr in rectify_attributes(attrs)) {
741 if (attr.name == 'user') {
742 for each (let arg in attr.args) {
743 if (arg == 'NS_outparam') {
744 return ps.OUT;
745 } else if (arg == 'NS_inoutparam') {
746 return ps.INOUT;
747 } else if (arg == 'NS_inparam') {
748 return ps.CONST;
753 return undefined;
756 /* @return true if the given type appears to be an outparam
757 * type based on the type alone (i.e., not considering
758 * attributes. */
759 function type_is_outparam(type) {
760 switch (TREE_CODE(type)) {
761 case POINTER_TYPE:
762 return pointer_type_is_outparam(TREE_TYPE(type));
763 case REFERENCE_TYPE:
764 let rt = TREE_TYPE(type);
765 return !TYPE_READONLY(rt) && is_string_type(rt);
766 default:
767 // Note: This is unsound for UNION_TYPE, because the union could
768 // contain a pointer.
769 return false;
773 /* Helper for type_is_outparam.
774 * @return true if 'pt *' looks like an outparam type. */
775 function pointer_type_is_outparam(pt) {
776 if (TYPE_READONLY(pt)) return false;
778 switch (TREE_CODE(pt)) {
779 case POINTER_TYPE:
780 case ARRAY_TYPE: {
781 // Look for void **, nsIFoo **, char **, PRUnichar **
782 let ppt = TREE_TYPE(pt);
783 let tname = TYPE_NAME(ppt);
784 if (tname == undefined) return false;
785 let name = decl_name_string(tname);
786 return name == 'void' || name == 'char' || name == 'PRUnichar' ||
787 name.substr(0, 3) == 'nsI';
789 case INTEGER_TYPE: {
790 // char * and PRUnichar * are probably strings, otherwise guess
791 // it is an integer outparam.
792 let name = decl_name_string(TYPE_NAME(pt));
793 return name != 'char' && name != 'PRUnichar';
795 case ENUMERAL_TYPE:
796 case REAL_TYPE:
797 case UNION_TYPE:
798 return true;
799 case RECORD_TYPE:
800 // TODO: should we consider field writes?
801 return false;
802 case FUNCTION_TYPE:
803 case VOID_TYPE:
804 return false;
805 default:
806 throw new Error("can't guess if a pointer to this type is an outparam: " +
807 TREE_CODE(pt) + ': ' + type_string(pt));
811 // Map type name to boolean as to whether it is a string.
812 let cached_string_types = MapFactory.create_map(
813 function (x, y) x == y,
814 function (x) x,
815 function (t) t,
816 function (t) t);
818 // Base string types. Others will be found by searching the inheritance
819 // graph.
821 cached_string_types.put('nsAString', true);
822 cached_string_types.put('nsACString', true);
823 cached_string_types.put('nsAString_internal', true);
824 cached_string_types.put('nsACString_internal', true);
826 // Return true if the given type represents a Mozilla string type.
827 // The binfo arg is the binfo to use for further iteration. This is
828 // for internal use only, users of this function should pass only
829 // one arg.
830 function is_string_type(type, binfo) {
831 if (TREE_CODE(type) != RECORD_TYPE) return false;
832 //print(">>>IST " + type_string(type));
833 let name = decl_name_string(TYPE_NAME(type));
834 let ans = cached_string_types.get(name);
835 if (ans != undefined) return ans;
837 ans = false;
838 binfo = binfo != undefined ? binfo : TYPE_BINFO(type);
839 if (binfo != undefined) {
840 for each (let base in VEC_iterate(BINFO_BASE_BINFOS(binfo))) {
841 let parent_ans = is_string_type(BINFO_TYPE(base), base);
842 if (parent_ans) {
843 ans = true;
844 break;
848 cached_string_types.put(name, ans);
849 //print("<<<IST " + type_string(type) + ' ' + ans);
850 return ans;
853 function is_string_ptr_type(type) {
854 return TREE_CODE(type) == POINTER_TYPE && is_string_type(TREE_TYPE(type));
857 // Return true if the given function is a mutator method of a Mozilla
858 // string type.
859 function is_string_mutator(fndecl) {
860 let first_param = function() {
861 for (let p in function_decl_params(fndecl)) {
862 return p;
864 return undefined;
865 }();
867 return first_param != undefined &&
868 decl_name_string(first_param) == 'this' &&
869 is_string_ptr_type(TREE_TYPE(first_param)) &&
870 !TYPE_READONLY(TREE_TYPE(TREE_TYPE(first_param)));