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