зеркало из https://github.com/mozilla/pjs.git
Bug 433939: updating outparams check for latest Treehydra, r=bsmedberg
This commit is contained in:
Родитель
6fc3274f41
Коммит
9704ef1a8b
|
@ -10,6 +10,8 @@ include('unstable/adts.js');
|
|||
include('unstable/analysis.js');
|
||||
include('unstable/esp.js');
|
||||
include('unstable/liveness.js');
|
||||
let Zero_NonZero = {};
|
||||
include('unstable/zero_nonzero.js', Zero_NonZero);
|
||||
|
||||
include('mayreturn.js');
|
||||
|
||||
|
@ -148,7 +150,8 @@ function OutparamCheck(cfg, psem_list, outparam_list, retvar, retvar_set, finall
|
|||
print(" " + expr_display(v));
|
||||
}
|
||||
}
|
||||
ESP.Analysis.call(this, cfg, this.psvar_list, av.BOTTOM, av.meet, trace);
|
||||
this.zeroNonzero = new Zero_NonZero.Zero_NonZero();
|
||||
ESP.Analysis.call(this, cfg, this.psvar_list, av.meet, trace);
|
||||
}
|
||||
|
||||
// Abstract values for outparam check
|
||||
|
@ -165,10 +168,11 @@ AbstractValue.prototype.toString = function() {
|
|||
return this.name + ' (' + this.ch + ')';
|
||||
}
|
||||
|
||||
let avspec = [
|
||||
// General-purpose abstract values
|
||||
[ 'BOTTOM', '.' ], // any value, also used for dead vars
|
||||
AbstractValue.prototype.toShortString = function() {
|
||||
return this.ch;
|
||||
}
|
||||
|
||||
let avspec = [
|
||||
// Abstract values for outparam contents write status
|
||||
[ 'NULL', 'x' ], // is a null pointer
|
||||
[ 'NOT_WRITTEN', '-' ], // not written
|
||||
|
@ -180,10 +184,6 @@ let avspec = [
|
|||
// in last position), so if there is an error with it not being written,
|
||||
// we can give a hint about the possible outparam in the warning.
|
||||
[ 'MAYBE_WRITTEN', '?' ], // written if possible outparam is one
|
||||
|
||||
// Abstract values for rvs
|
||||
[ 'ZERO', '0' ], // zero value
|
||||
[ 'NONZERO', '1' ] // nonzero value
|
||||
];
|
||||
|
||||
let av = {};
|
||||
|
@ -191,11 +191,13 @@ for each (let [name, ch] in avspec) {
|
|||
av[name] = new AbstractValue(name, ch);
|
||||
}
|
||||
|
||||
av.ZERO = Zero_NonZero.Lattice.ZERO;
|
||||
av.NONZERO = Zero_NonZero.Lattice.NONZERO;
|
||||
|
||||
/*
|
||||
av.ZERO.negation = av.NONZERO;
|
||||
av.NONZERO.negation = av.ZERO;
|
||||
|
||||
let cachedAVs = {};
|
||||
|
||||
// Abstract values for int constants. We use these to figure out feasible
|
||||
// paths in the presence of GCC finally_tmp-controlled switches.
|
||||
function makeIntAV(v) {
|
||||
|
@ -207,13 +209,16 @@ function makeIntAV(v) {
|
|||
ans.int_val = v;
|
||||
return ans;
|
||||
}
|
||||
*/
|
||||
|
||||
let cachedAVs = {};
|
||||
|
||||
// Abstract values for pointers that contain a copy of an outparam
|
||||
// pointer. We use these to figure out writes to a casted copy of
|
||||
// an outparam passed to another method.
|
||||
function makeOutparamAV(v) {
|
||||
let key = 'outparam_' + DECL_UID(v);
|
||||
if (cachedAVs.hasOwnProperty(key)) return cachedAVs[key];
|
||||
if (key in cachedAVs) return cachedAVs[key];
|
||||
|
||||
let ans = cachedAVs[key] =
|
||||
new AbstractValue('OUTPARAM:' + expr_display(v), 'P');
|
||||
|
@ -230,31 +235,14 @@ av.intVal = function(v) {
|
|||
|
||||
/** Meet function for our abstract values. */
|
||||
av.meet = function(v1, v2) {
|
||||
// Important for following cases -- as output, undefined means top here.
|
||||
if (v1 == undefined) v1 = av.BOTTOM;
|
||||
if (v2 == undefined) v2 = av.BOTTOM;
|
||||
|
||||
// These cases apply for any lattice.
|
||||
if (v1 == av.BOTTOM) return v2;
|
||||
if (v2 == av.BOTTOM) return v1;
|
||||
if (v1 == v2) return v1;
|
||||
|
||||
// At this point we know v1 != v2.
|
||||
switch (v1) {
|
||||
case av.ZERO:
|
||||
return av.intVal(v2) == 0 ? v2 : undefined;
|
||||
case av.NONZERO:
|
||||
// Return a nonempty meet only if the other value is an integer
|
||||
// and is nonzero.
|
||||
let iv2 = av.intVal(v2);
|
||||
return iv2 != undefined && iv2 != 0 ? v2 : undefined;
|
||||
default:
|
||||
let iv = av.intVal(v1);
|
||||
if (iv == 0) return v2 == av.ZERO ? v1 : undefined;
|
||||
if (iv != undefined) return v2 == av.NONZERO ? v1 : undefined;
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
let values = [v1,v2]
|
||||
if (values.indexOf(av.LOCKED) != -1
|
||||
|| values.indexOf(av.UNLOCKED) != -1)
|
||||
return ESP.NOT_REACHED;
|
||||
|
||||
return Zero_NonZero.meet(v1, v2)
|
||||
};
|
||||
|
||||
// Outparam check analysis
|
||||
OutparamCheck.prototype = new ESP.Analysis;
|
||||
|
@ -262,11 +250,17 @@ OutparamCheck.prototype = new ESP.Analysis;
|
|||
OutparamCheck.prototype.startValues = function() {
|
||||
let ans = create_decl_map();
|
||||
for each (let p in this.psvar_list) {
|
||||
ans.put(p, this.outparams.has(p) ? av.NOT_WRITTEN : av.BOTTOM);
|
||||
ans.put(p, this.outparams.has(p) ? av.NOT_WRITTEN : ESP.TOP);
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
OutparamCheck.prototype.split = function(vbl, v) {
|
||||
// Can't happen for current version of ESP, but could change
|
||||
if (v != ESP.TOP) throw new Error("not implemented");
|
||||
return [ av.ZERO, av.NONZERO ];
|
||||
}
|
||||
|
||||
OutparamCheck.prototype.updateEdgeState = function(e) {
|
||||
e.state.keepOnly(e.dest.keepVars);
|
||||
}
|
||||
|
@ -283,93 +277,17 @@ OutparamCheck.prototype.flowState = function(isn, state) {
|
|||
case COND_EXPR:
|
||||
// This gets handled by flowStateCond instead, has no exec effect
|
||||
break;
|
||||
case RETURN_EXPR:
|
||||
let op = isn.operands()[0];
|
||||
if (op) this.processAssign(isn.operands()[0], state);
|
||||
break;
|
||||
case LABEL_EXPR:
|
||||
case RESX_EXPR:
|
||||
case ASM_EXPR:
|
||||
// NOPs for us
|
||||
break;
|
||||
default:
|
||||
print(TREE_CODE(isn));
|
||||
throw new Error("ni");
|
||||
this.zeroNonzero.flowState(isn, state);
|
||||
}
|
||||
}
|
||||
|
||||
OutparamCheck.prototype.flowStateCond = function(isn, truth, state) {
|
||||
switch (TREE_CODE(isn)) {
|
||||
case COND_EXPR:
|
||||
this.flowStateIf(isn, truth, state);
|
||||
break;
|
||||
case SWITCH_EXPR:
|
||||
this.flowStateSwitch(isn, truth, state);
|
||||
break;
|
||||
default:
|
||||
throw new Error("ni " + TREE_CODE(isn));
|
||||
}
|
||||
this.zeroNonzero.flowStateCond(isn, truth, state);
|
||||
}
|
||||
|
||||
OutparamCheck.prototype.flowStateIf = function(isn, truth, state) {
|
||||
let exp = TREE_OPERAND(isn, 0);
|
||||
|
||||
if (DECL_P(exp)) {
|
||||
this.filter(state, exp, av.NONZERO, truth, isn);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (TREE_CODE(exp)) {
|
||||
case EQ_EXPR:
|
||||
case NE_EXPR:
|
||||
// Handle 'x op <int lit>' pattern only
|
||||
let op1 = TREE_OPERAND(exp, 0);
|
||||
let op2 = TREE_OPERAND(exp, 1);
|
||||
if (expr_literal_int(op1) != undefined) {
|
||||
[op1,op2] = [op2,op1];
|
||||
}
|
||||
if (!DECL_P(op1)) break;
|
||||
if (expr_literal_int(op2) != 0) break;
|
||||
let val = TREE_CODE(exp) == EQ_EXPR ? av.ZERO : av.NONZERO;
|
||||
|
||||
this.filter(state, op1, val, truth, isn);
|
||||
break;
|
||||
default:
|
||||
// Don't care about anything else.
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
OutparamCheck.prototype.flowStateSwitch = function(isn, truth, state) {
|
||||
let exp = TREE_OPERAND(isn, 0);
|
||||
|
||||
if (DECL_P(exp)) {
|
||||
if (truth != null) {
|
||||
this.filter(state, exp, makeIntAV(truth), true, isn);
|
||||
}
|
||||
return;
|
||||
}
|
||||
throw new Error("ni");
|
||||
}
|
||||
|
||||
// Apply a filter to the state. We need to special case it for this analysis
|
||||
// because outparams only care about being a NULL pointer.
|
||||
OutparamCheck.prototype.filter = function(state, vbl, val, truth, blame) {
|
||||
if (truth != true && truth != false) throw new Error("ni " + truth);
|
||||
if (this.outparams.has(vbl)) {
|
||||
// We do need to check for true and false here because other values
|
||||
// could get passed in from switches.
|
||||
if (truth == true && val == av.ZERO || truth == false && val == av.NONZERO) {
|
||||
state.assignValue(vbl, av.NULL, blame);
|
||||
}
|
||||
} else {
|
||||
if (truth == false) {
|
||||
val = val.negation;
|
||||
}
|
||||
state.filter(vbl, val, blame);
|
||||
}
|
||||
};
|
||||
|
||||
// For any outparams-specific semantics, we handle it here and then
|
||||
// return. Otherwise we delegate to the zero-nonzero analysis.
|
||||
OutparamCheck.prototype.processAssign = function(isn, state) {
|
||||
let lhs = isn.operands()[0];
|
||||
let rhs = isn.operands()[1];
|
||||
|
@ -380,40 +298,20 @@ OutparamCheck.prototype.processAssign = function(isn, state) {
|
|||
rhs = rhs.operands()[0];
|
||||
}
|
||||
|
||||
if (DECL_P(rhs)) {
|
||||
if (this.outparams.has(rhs)) {
|
||||
if (DECL_P(rhs) && this.outparams.has(rhs)) {
|
||||
// Copying an outparam pointer. We have to remember this so that
|
||||
// if it is assigned thru later, we pick up the write.
|
||||
state.assignValue(lhs, makeOutparamAV(rhs), isn);
|
||||
} else {
|
||||
state.assign(lhs, rhs, isn);
|
||||
}
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
switch (TREE_CODE(rhs)) {
|
||||
case INTEGER_CST:
|
||||
if (this.outparams.has(lhs)) {
|
||||
warning("assigning to outparam pointer");
|
||||
} else {
|
||||
// Need to know the exact int value for finally_tmp.
|
||||
if (is_finally_tmp(lhs)) {
|
||||
let v = TREE_INT_CST_LOW(rhs);
|
||||
state.assignValue(lhs, makeIntAV(v), isn);
|
||||
} else {
|
||||
let value = expr_literal_int(rhs) == 0 ? av.ZERO : av.NONZERO;
|
||||
state.assignValue(lhs, value, isn);
|
||||
}
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case NE_EXPR: {
|
||||
// We only care about gcc-generated x != 0 for conversions and such.
|
||||
let [op1, op2] = rhs.operands();
|
||||
if (DECL_P(op1) && expr_literal_int(op2) == 0) {
|
||||
state.assign(lhs, op1, isn);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EQ_EXPR: {
|
||||
// We only care about testing outparams for NULL (and then not writing)
|
||||
let [op1, op2] = rhs.operands();
|
||||
|
@ -425,6 +323,7 @@ OutparamCheck.prototype.processAssign = function(isn, state) {
|
|||
s2.assignValue(lhs, av.ZERO, isn);
|
||||
return [s1, s2];
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -440,40 +339,18 @@ OutparamCheck.prototype.processAssign = function(isn, state) {
|
|||
} else {
|
||||
this.processCall(lhs, rhs, isn, state);
|
||||
}
|
||||
break;
|
||||
// Stuff we don't analyze -- just kill the LHS info
|
||||
case ADDR_EXPR:
|
||||
case POINTER_PLUS_EXPR:
|
||||
case ARRAY_REF:
|
||||
case COMPONENT_REF:
|
||||
case INDIRECT_REF:
|
||||
case FILTER_EXPR:
|
||||
case EXC_PTR_EXPR:
|
||||
case CONSTRUCTOR:
|
||||
|
||||
case REAL_CST:
|
||||
case STRING_CST:
|
||||
|
||||
case CONVERT_EXPR:
|
||||
case TRUTH_NOT_EXPR:
|
||||
case TRUTH_XOR_EXPR:
|
||||
case BIT_FIELD_REF:
|
||||
state.remove(lhs);
|
||||
break;
|
||||
default:
|
||||
if (UNARY_CLASS_P(rhs) || BINARY_CLASS_P(rhs) || COMPARISON_CLASS_P(rhs)) {
|
||||
state.remove(lhs);
|
||||
break;
|
||||
}
|
||||
print(TREE_CODE(rhs));
|
||||
throw new Error("ni");
|
||||
return;
|
||||
}
|
||||
|
||||
// Nothing special -- delegate
|
||||
this.zeroNonzero.processAssign(isn, state);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (TREE_CODE(lhs)) {
|
||||
case INDIRECT_REF:
|
||||
// Writing to an outparam. We want to try to figure out if we're writing NULL.
|
||||
// Writing to an outparam. We want to try to figure out if we're
|
||||
// writing NULL.
|
||||
let e = TREE_OPERAND(lhs, 0);
|
||||
if (this.outparams.has(e)) {
|
||||
if (expr_literal_int(rhs) == 0) {
|
||||
|
@ -509,9 +386,9 @@ OutparamCheck.prototype.processAssign = function(isn, state) {
|
|||
OutparamCheck.prototype.processTest = function(lhs, call, val, blame, state) {
|
||||
let arg = call_arg(call, 0);
|
||||
if (DECL_P(arg)) {
|
||||
state.predicate(lhs, val, arg, blame);
|
||||
this.zeroNonzero.predicate(state, lhs, val, arg, blame);
|
||||
} else {
|
||||
state.assignValue(lhs, av.BOTTOM, blame);
|
||||
state.assignValue(lhs, ESP.TOP, blame);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -650,14 +527,13 @@ OutparamCheck.prototype.checkSubstate = function(isvoid, fndecl, ss) {
|
|||
} else {
|
||||
let [succ, fail] = ret_coding(fndecl);
|
||||
let rv = ss.get(this.retvar);
|
||||
switch (rv) {
|
||||
case succ:
|
||||
// We want to check if the abstract value of the rv is entirely
|
||||
// contained in the success or failure condition.
|
||||
if (av.meet(rv, succ) == rv) {
|
||||
this.checkSubstateSuccess(ss);
|
||||
break;
|
||||
case fail:
|
||||
} else if (av.meet(rv, fail) == rv) {
|
||||
this.checkSubstateFailure(ss);
|
||||
break;
|
||||
default:
|
||||
} else {
|
||||
// This condition indicates a bug in outparams.js. We'll just
|
||||
// warn so we don't break static analysis builds.
|
||||
warning("Outparams checker cannot determine rv success/failure",
|
||||
|
|
Загрузка…
Ссылка в новой задаче