Fixed multiple ordering bugs involving unsafe a-temporal abstract values #2413
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -194,11 +194,28 @@ export function computeBinary( | |
|
||
let resultType; | ||
const compute = () => { | ||
if (lval instanceof AbstractValue || rval instanceof AbstractValue) { | ||
let lvalIsAbstract = lval instanceof AbstractValue; | ||
let rvalIsAbstract = rval instanceof AbstractValue; | ||
|
||
if (lvalIsAbstract || rvalIsAbstract) { | ||
try { | ||
// generate error if binary operation might throw or have side effects | ||
resultType = getPureBinaryOperationResultType(realm, op, lval, rval, lloc, rloc); | ||
return AbstractValue.createFromBinaryOp(realm, op, lval, rval, loc); | ||
|
||
// We have determined that it is safe to assume the type of this operation, | ||
// but there is one last check. If the operation can throw, then our assumption | ||
// may rely on a dynamic condition. We express this condition as an implication, | ||
// rval is temporal => this operation is temporal | ||
// See #2327 | ||
if ((op === "in" || op === "instanceof") && (rvalIsAbstract && rval.isTemporal())) | ||
return AbstractValue.createTemporalFromBuildFunction( | ||
realm, | ||
resultType, | ||
[lval, rval], | ||
createOperationDescriptor("BINARY_EXPRESSION", { op }), | ||
{ isPure: true, skipInvariant: true } | ||
); | ||
else return AbstractValue.createFromBinaryOp(realm, op, lval, rval, loc); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm still a little bit disturbed by this. If one of the arguments is temporal, then moving the operation to point where the current path conditions do not apply may be safe, but it seems hardly desirable. In essence, any operation over temporal values should result in a temporal value, as a rule, except for a few specific cases that are well documented. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm going to enforce this protocol in a follow up PR that addresses #2489. Doing so will involve auditing uses of all of the abstract value factory methods. |
||
} catch (x) { | ||
if (x instanceof FatalError) { | ||
// There is no need to revert any effects, because the above operation is pure. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// expected RecoverableError: PP0004 | ||
|
||
let o = global.__abstractOrNull ? __abstractOrNull("object", "undefined") : undefined; | ||
let s = true; | ||
|
||
if (o != undefined) s = "foobar" in o; | ||
if (o != undefined) s = "foobar" in o; | ||
|
||
global.s = s; | ||
|
||
inspect = () => s; // Should be true |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// Copies of "foobar" in:1 | ||
// expected RecoverableError: PP0004 | ||
|
||
let o = global.__abstract ? __abstract("object", "('foobar42')") : "foobar42"; | ||
let s = true; | ||
|
||
if (o != undefined) s = "foobar" in o; | ||
if (o != undefined) s = "foobar" in o; | ||
|
||
global.s = s; | ||
|
||
inspect = () => s; // Should be true |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// expected RecoverableError: PP0004 | ||
|
||
let o = global.__abstractOrNull ? __abstractOrNull("object", "undefined") : undefined; | ||
let s = true; | ||
|
||
if (o != undefined) s = "foobar" instanceof o; | ||
if (o != undefined) s = "foobar" instanceof o; | ||
|
||
global.s = s; | ||
|
||
inspect = () => s; // Should be true |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// Copies of "foobar" instanceof:1 | ||
// expected RecoverableError: PP0004 | ||
|
||
let o = global.__abstract ? __abstract("object", "({})") : {}; | ||
let s = true; | ||
|
||
if (o != undefined) s = "foobar" instanceof o; | ||
if (o != undefined) s = "foobar" instanceof o; | ||
|
||
global.s = s; | ||
|
||
inspect = () => s; // Should be true |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
let o = global.__abstractOrNull ? __abstractOrNull("string", "undefined") : undefined; | ||
let s; | ||
|
||
if (o != undefined) s = 5 + o.length; | ||
if (o != undefined) s = 7 + o.length; | ||
|
||
global.s = s; | ||
|
||
inspect = () => s; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Copies of length:1 | ||
|
||
let o = global.__abstract ? __abstract("string", "('xyz')") : "xyz"; | ||
let s; | ||
|
||
if (o != undefined) s = 5 + o.length; | ||
if (o != undefined) s = 7 + o.length; | ||
|
||
global.s = s; | ||
|
||
inspect = () => s; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
let o = global.__abstractOrNull ? __abstractOrNull("string", "undefined") : undefined; | ||
let s = "ok"; | ||
|
||
if (o != undefined) s = "X" + o.slice(3); | ||
if (o != undefined) s = "Y" + o.slice(3); | ||
|
||
global.s = s; | ||
|
||
inspect = () => s; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// Copies of slice\(:1 | ||
|
||
(function() { | ||
let o = global.__abstract ? __abstract("string", "('x y z')") : "x y z"; | ||
let c = global.__abstract ? __abstract("boolean", "true") : true; | ||
let s = "ok"; | ||
|
||
if (c) s = "X" + o.slice(3); | ||
if (c) s = "Y" + o.slice(3); | ||
|
||
global.s = s; | ||
inspect = () => s; | ||
})(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
let o = global.__abstractOrNull ? __abstractOrNull("string", "undefined") : undefined; | ||
let s = "ok"; | ||
|
||
if (o != undefined) s = o.split(); | ||
if (o != undefined) s = o.split(); | ||
|
||
global.s = s; | ||
|
||
inspect = () => s; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather put this condition into a helper function named something like "operationMustBeTemporal".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've deferred this to the refactoring in #2489.