|
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/. */ |
|
4 |
|
5 require({ version: '1.8' }); |
|
6 require({ after_gcc_pass: 'cfg' }); |
|
7 |
|
8 include('treehydra.js'); |
|
9 |
|
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); |
|
18 |
|
19 include('xpcom/analysis/mayreturn.js'); |
|
20 |
|
21 function safe_location_of(t) { |
|
22 if (t === undefined) |
|
23 return UNKNOWN_LOCATION; |
|
24 |
|
25 return location_of(t); |
|
26 } |
|
27 |
|
28 MapFactory.use_injective = true; |
|
29 |
|
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; |
|
40 |
|
41 const WARN_ON_SET_NULL = false; |
|
42 const WARN_ON_SET_FAILURE = false; |
|
43 |
|
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 } |
|
51 |
|
52 function process_tree(func_decl) { |
|
53 if (!func_filter(func_decl)) return; |
|
54 |
|
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 } |
|
66 |
|
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; |
|
77 |
|
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 } |
|
92 |
|
93 let cfg = function_decl_cfg(func_decl); |
|
94 |
|
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"); |
|
102 |
|
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 } |
|
115 |
|
116 if (TRACE_PERF) timer_stop(fstring); |
|
117 } |
|
118 |
|
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; |
|
126 |
|
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; |
|
131 |
|
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 } |
|
149 |
|
150 // Abstract values for outparam check |
|
151 function AbstractValue(name, ch) { |
|
152 this.name = name; |
|
153 this.ch = ch; |
|
154 } |
|
155 |
|
156 AbstractValue.prototype.equals = function(v) { |
|
157 return this === v; |
|
158 } |
|
159 |
|
160 AbstractValue.prototype.toString = function() { |
|
161 return this.name + ' (' + this.ch + ')'; |
|
162 } |
|
163 |
|
164 AbstractValue.prototype.toShortString = function() { |
|
165 return this.ch; |
|
166 } |
|
167 |
|
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 ]; |
|
181 |
|
182 let av = {}; |
|
183 for each (let [name, ch] in avspec) { |
|
184 av[name] = new AbstractValue(name, ch); |
|
185 } |
|
186 |
|
187 av.ZERO = Zero_NonZero.Lattice.ZERO; |
|
188 av.NONZERO = Zero_NonZero.Lattice.NONZERO; |
|
189 |
|
190 /* |
|
191 av.ZERO.negation = av.NONZERO; |
|
192 av.NONZERO.negation = av.ZERO; |
|
193 |
|
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]; |
|
199 |
|
200 let s = "" + v; |
|
201 let ans = cachedAVs[key] = new AbstractValue(s, s); |
|
202 ans.int_val = v; |
|
203 return ans; |
|
204 } |
|
205 */ |
|
206 |
|
207 let cachedAVs = {}; |
|
208 |
|
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]; |
|
215 |
|
216 let ans = cachedAVs[key] = |
|
217 new AbstractValue('OUTPARAM:' + expr_display(v), 'P'); |
|
218 ans.outparam = v; |
|
219 return ans; |
|
220 } |
|
221 |
|
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 } |
|
228 |
|
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; |
|
236 |
|
237 return Zero_NonZero.meet(v1, v2) |
|
238 }; |
|
239 |
|
240 // Outparam check analysis |
|
241 OutparamCheck.prototype = new ESP.Analysis; |
|
242 |
|
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 } |
|
248 |
|
249 OutparamCheck.prototype.updateEdgeState = function(e) { |
|
250 e.state.keepOnly(e.dest.keepVars); |
|
251 } |
|
252 |
|
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 } |
|
269 |
|
270 OutparamCheck.prototype.flowStateCond = function(isn, truth, state) { |
|
271 this.zeroNonzero.flowStateCond(isn, truth, state); |
|
272 } |
|
273 |
|
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); |
|
279 |
|
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 } |
|
285 |
|
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 } |
|
292 |
|
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; |
|
321 |
|
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 } |
|
336 |
|
337 // Nothing special -- delegate |
|
338 this.zeroNonzero.processAssign(isn, state); |
|
339 return; |
|
340 } |
|
341 |
|
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 } |
|
376 |
|
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 }; |
|
386 |
|
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); |
|
391 |
|
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); |
|
395 |
|
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 } |
|
408 |
|
409 if (TRACE_CALL_SEM) { |
|
410 print("param semantics:" + psem); |
|
411 } |
|
412 |
|
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 } |
|
427 |
|
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 } |
|
460 |
|
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); |
|
465 |
|
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 }; |
|
502 |
|
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); |
|
509 |
|
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 } |
|
517 |
|
518 function unwrap_outparam(arg, state) { |
|
519 if (!DECL_P(arg) || state.factory.outparams.has(arg)) return arg; |
|
520 |
|
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 } |
|
531 |
|
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 } |
|
539 |
|
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 } |
|
562 |
|
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); |
|
570 |
|
571 if (this.cfg._cached_return) |
|
572 return this.cfg._cached_return; |
|
573 |
|
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 } |
|
581 |
|
582 return undefined; |
|
583 } |
|
584 |
|
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'); |
|
596 |
|
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)); |
|
603 |
|
604 callMsg = [callDecl, "declared here"]; |
|
605 callName = " '" + decl_name(callDecl) + "'"; |
|
606 } |
|
607 catch (e if e.TreeCheckError) { } |
|
608 |
|
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 } |
|
618 |
|
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 } |
|
642 |
|
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]; |
|
649 |
|
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 } |
|
657 |
|
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 } |
|
664 |
|
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 }; |
|
683 |
|
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'; |
|
694 |
|
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 } |
|
707 |
|
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 } |
|
738 |
|
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 } |
|
762 |
|
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 } |
|
779 |
|
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; |
|
784 |
|
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 } |
|
818 |
|
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); |
|
825 |
|
826 // Base string types. Others will be found by searching the inheritance |
|
827 // graph. |
|
828 |
|
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); |
|
833 |
|
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; |
|
844 |
|
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 } |
|
860 |
|
861 function is_string_ptr_type(type) { |
|
862 return TREE_CODE(type) == POINTER_TYPE && is_string_type(TREE_TYPE(type)); |
|
863 } |
|
864 |
|
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 }(); |
|
874 |
|
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 } |
|
880 |