| |
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 |