Skip to content

Commit e40edb1

Browse files
owen-mcCopilot
andcommitted
JS: Add shared SSA migration regression tests
Add all migration regressions before changing the implementation. Mark main's known missing and spurious results inline, and record its current consistency-query output. Use the existing internal node names and compare public/native definition coordinates so these tests compile before the migration. The integration commit switches the representation-specific checks to native shared definitions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent feb95ba commit e40edb1

21 files changed

Lines changed: 2080 additions & 0 deletions

‎javascript/ql/test/library-tests/BarrierGuardPhiInputs/test.expected‎

Whitespace-only changes.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import javascript
2+
import utils.test.InlineExpectationsTest
3+
4+
module TestConfig implements DataFlow::ConfigSig {
5+
predicate isSource(DataFlow::Node source) {
6+
source.(DataFlow::InvokeNode).getCalleeName() = "SOURCE"
7+
}
8+
9+
predicate isSink(DataFlow::Node sink) {
10+
exists(DataFlow::InvokeNode call |
11+
call.getCalleeName() = "SINK" and
12+
sink = call.getArgument(0)
13+
)
14+
}
15+
16+
predicate isBarrier(DataFlow::Node node) {
17+
node = DataFlow::MakeBarrierGuard<SimpleBarrierGuard>::getABarrierNode()
18+
}
19+
}
20+
21+
module TestFlow = DataFlow::Global<TestConfig>;
22+
23+
class SimpleBarrierGuard extends DataFlow::InvokeNode {
24+
SimpleBarrierGuard() { this.getCalleeName() = "BARRIER" }
25+
26+
predicate blocksExpr(boolean outcome, Expr expr) {
27+
outcome = true and
28+
expr = this.getArgument(0).asExpr()
29+
}
30+
}
31+
32+
module InlineTest implements TestSig {
33+
string getARelevantTag() { result = "flow" }
34+
35+
predicate hasActualResult(Location location, string element, string tag, string value) {
36+
tag = "flow" and
37+
element = "" and
38+
exists(DataFlow::InvokeNode source, DataFlow::Node sink |
39+
TestFlow::flow(source, sink) and
40+
source.getCalleeName() = "SOURCE" and
41+
value = source.getArgument(0).getStringValue() and
42+
location = sink.getLocation()
43+
)
44+
}
45+
}
46+
47+
import MakeTest<InlineTest>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
function overwriteOnFailure() {
2+
let x = SOURCE("overwrite");
3+
if (!BARRIER(x)) {
4+
x = "safe";
5+
}
6+
SINK(x); // $ SPURIOUS: flow=overwrite
7+
}
8+
9+
function explicitElse() {
10+
let x = SOURCE("else");
11+
if (BARRIER(x)) {
12+
use(x);
13+
} else {
14+
x = "safe";
15+
}
16+
SINK(x); // $ SPURIOUS: flow=else
17+
}
18+
19+
function preserveUnguardedInput() {
20+
let x = SOURCE("guarded");
21+
if (!BARRIER(x)) {
22+
x = SOURCE("fallback");
23+
}
24+
SINK(x); // $ flow=fallback SPURIOUS: flow=guarded
25+
}
26+
27+
function returnOnFailure() {
28+
let x = SOURCE("return");
29+
if (!BARRIER(x)) {
30+
return;
31+
}
32+
SINK(x);
33+
}
34+
35+
SINK(SOURCE("control")); // $ flow=control
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
WARNING: type 'BarrierGuardNode' has been deprecated and may be removed in future (captured.ql:32,40-66)
2+
WARNING: type 'BarrierGuardNode' has been deprecated and may be removed in future (captured.ql:43,37-63)
3+
WARNING: type 'Configuration' has been deprecated and may be removed in future (captured.ql:36,36-59)
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import "dummy";
2+
3+
let capturedWrite = 0; // $ capturedWrite=capturedWrite ssaDefinition=capturedWrite noSsaAstNode=capturedWrite
4+
5+
function readCaptured() {
6+
return capturedWrite; // $ noCapturedWrite=capturedWrite
7+
}
8+
9+
function unreachableCapturedWrites() {
10+
return;
11+
capturedWrite = 2; // $ noCapturedWrite=capturedWrite
12+
}
13+
14+
function writeCaptured() {
15+
capturedWrite = 1; // $ capturedWrite=capturedWrite ssaDefinition=capturedWrite
16+
++capturedWrite; // $ capturedWrite=capturedWrite ssaDefinition=capturedWrite
17+
capturedWrite += 1; // $ capturedWrite=capturedWrite ssaDefinition=capturedWrite
18+
}
19+
20+
function uncapturedWrites() {
21+
let uncapturedWrite = 0; // $ noCapturedWrite=uncapturedWrite
22+
return uncapturedWrite++; // $ noCapturedWrite=uncapturedWrite
23+
}
24+
25+
globalWrite = 1; // $ noCapturedWrite=globalWrite
26+
use(globalWrite); // $ noCapturedWrite=globalWrite
27+
28+
const { capturedPattern } = input; // $ capturedWrite=capturedPattern ssaDefinition=capturedPattern
29+
function readCapturedPattern() {
30+
return capturedPattern; // $ noCapturedWrite=capturedPattern
31+
}
32+
33+
function destructuredDeclaration() {
34+
const { declaredObject, safeObject } = { // $ ssaDefinition=declaredObject ssaDefinition=safeObject
35+
declaredObject: SOURCE("object-declaration"),
36+
safeObject: "safe"
37+
};
38+
SINK(declaredObject); // $ localCapturedFlow=declaredObject capturedFlow=object-declaration legacyCapturedFlow=object-declaration
39+
SINK(safeObject); // $ localCapturedFlow=safeObject
40+
return () => {
41+
SINK(declaredObject); // $ capturedFlow=object-declaration legacyCapturedFlow=object-declaration
42+
SINK(safeObject);
43+
};
44+
}
45+
destructuredDeclaration()();
46+
47+
function destructuredAssignment() {
48+
let assignedObject;
49+
({ assignedObject } = { assignedObject: SOURCE("object-assignment") }); // $ ssaDefinition=assignedObject
50+
SINK(assignedObject); // $ localCapturedFlow=assignedObject capturedFlow=object-assignment legacyCapturedFlow=object-assignment
51+
return () => SINK(assignedObject); // $ capturedFlow=object-assignment legacyCapturedFlow=object-assignment
52+
}
53+
destructuredAssignment()();
54+
55+
function destructuredParameter({ parameterObject }) { // $ ssaDefinition=parameterObject
56+
SINK(parameterObject); // $ localCapturedFlow=parameterObject capturedFlow=object-parameter legacyCapturedFlow=object-parameter
57+
return () => SINK(parameterObject); // $ capturedFlow=object-parameter legacyCapturedFlow=object-parameter
58+
}
59+
destructuredParameter({ parameterObject: SOURCE("object-parameter") })();
60+
61+
function destructuredLoop() {
62+
for (const { loopObject } of [{ loopObject: SOURCE("object-loop") }]) { // $ ssaDefinition=loopObject
63+
SINK(loopObject); // $ localCapturedFlow=loopObject capturedFlow=object-loop legacyCapturedFlow=object-loop
64+
(() => SINK(loopObject))(); // $ capturedFlow=object-loop legacyCapturedFlow=object-loop
65+
}
66+
}
67+
destructuredLoop();
68+
69+
function destructuredArray([arrayElement]) { // $ ssaDefinition=arrayElement
70+
SINK(arrayElement); // $ localCapturedFlow=arrayElement capturedFlow=array-element legacyCapturedFlow=array-element
71+
return () => SINK(arrayElement); // $ capturedFlow=array-element legacyCapturedFlow=array-element
72+
}
73+
destructuredArray([SOURCE("array-element")])();
74+
75+
const { topLevelObject } = { topLevelObject: SOURCE("top-level-object") }; // $ ssaDefinition=topLevelObject
76+
function readTopLevelObject() {
77+
SINK(topLevelObject); // $ capturedFlow=top-level-object legacyCapturedFlow=top-level-object
78+
}
79+
readTopLevelObject();
80+
81+
let topLevelPhi;
82+
if (unknownCondition()) {
83+
topLevelPhi = SOURCE("top-level-phi"); // $ ssaDefinition=topLevelPhi
84+
} else {
85+
topLevelPhi = "safe"; // $ ssaDefinition=topLevelPhi
86+
}
87+
SINK(topLevelPhi); // $ capturedFlow=top-level-phi legacyCapturedFlow=top-level-phi
88+
function readTopLevelPhi() {
89+
SINK(topLevelPhi); // $ capturedFlow=top-level-phi legacyCapturedFlow=top-level-phi
90+
}
91+
readTopLevelPhi();
92+
93+
function legacyGuardedPhi(condition) {
94+
let value = SOURCE("legacy-guarded-phi");
95+
if (condition && BARRIER(value)) {
96+
} else {
97+
value = "safe";
98+
}
99+
SINK(value); // $ SPURIOUS: capturedFlow=legacy-guarded-phi
100+
}
101+
102+
function legacyGuardedWithReads(condition) {
103+
let value = SOURCE("legacy-with-guard");
104+
use(value);
105+
with ({}) {
106+
if (condition) {
107+
if (!BARRIER(value)) return;
108+
use(value);
109+
} else {
110+
value = "safe";
111+
}
112+
}
113+
SINK(value); // $ SPURIOUS: capturedFlow=legacy-with-guard
114+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import javascript
2+
import utils.test.InlineExpectationsTest
3+
4+
module CapturedConfig implements DataFlow::ConfigSig {
5+
predicate isSource(DataFlow::Node source) {
6+
source.(DataFlow::InvokeNode).getCalleeName() = "SOURCE"
7+
}
8+
9+
predicate isSink(DataFlow::Node sink) {
10+
exists(DataFlow::InvokeNode call |
11+
call.getCalleeName() = "SINK" and
12+
sink = call.getArgument(0)
13+
)
14+
}
15+
16+
predicate isBarrier(DataFlow::Node node) {
17+
node = DataFlow::MakeBarrierGuard<SimpleBarrierGuard>::getABarrierNode()
18+
}
19+
}
20+
21+
module CapturedFlow = DataFlow::Global<CapturedConfig>;
22+
23+
class SimpleBarrierGuard extends DataFlow::InvokeNode {
24+
SimpleBarrierGuard() { this.getCalleeName() = "BARRIER" }
25+
26+
predicate blocksExpr(boolean outcome, Expr expr) {
27+
outcome = true and
28+
expr = this.getArgument(0).asExpr()
29+
}
30+
}
31+
32+
class LegacySimpleBarrierGuard extends DataFlow::BarrierGuardNode, SimpleBarrierGuard {
33+
override predicate blocks(boolean outcome, Expr expr) { this.blocksExpr(outcome, expr) }
34+
}
35+
36+
class LegacyCapturedConfig extends DataFlow::Configuration {
37+
LegacyCapturedConfig() { this = "LegacyCapturedConfig" }
38+
39+
override predicate isSource(DataFlow::Node source) { CapturedConfig::isSource(source) }
40+
41+
override predicate isSink(DataFlow::Node sink) { CapturedConfig::isSink(sink) }
42+
43+
override predicate isBarrierGuard(DataFlow::BarrierGuardNode guard) {
44+
guard instanceof LegacySimpleBarrierGuard
45+
}
46+
}
47+
48+
module InlineTest implements TestSig {
49+
string getARelevantTag() {
50+
result =
51+
[
52+
"capturedWrite", "noCapturedWrite", "ssaDefinition", "noSsaAstNode", "localCapturedFlow",
53+
"capturedFlow", "legacyCapturedFlow"
54+
]
55+
}
56+
57+
predicate hasActualResult(Location location, string element, string tag, string value) {
58+
element = "" and
59+
location.getFile().getBaseName() = "captured.js" and
60+
(
61+
exists(VarRef reference |
62+
value = reference.getVariable().getName() and
63+
value = ["capturedWrite", "uncapturedWrite", "globalWrite", "capturedPattern"] and
64+
location = reference.getLocation() and
65+
(
66+
tag = "capturedWrite" and
67+
exists(DataFlow::SsaDefinitionNode node |
68+
node.getSsaVariable().getDefinition().(SsaExplicitDefinition).getLhs() = reference and
69+
reference.getVariable().isCaptured()
70+
)
71+
or
72+
tag = "noCapturedWrite" and
73+
not exists(DataFlow::SsaDefinitionNode node |
74+
node.getSsaVariable().getDefinition().(SsaExplicitDefinition).getLhs() = reference and
75+
reference.getVariable().isCaptured()
76+
)
77+
)
78+
)
79+
or
80+
tag = "ssaDefinition" and
81+
exists(SsaExplicitDefinition definition, VarRef write, DataFlow::SsaDefinitionNode node |
82+
write = definition.getLhs() and
83+
write.getVariable().isCaptured() and
84+
node = DataFlow::lvalueNode(write) and
85+
node = DataFlow::ssaDefinitionNode(definition) and
86+
node.getSsaVariable() = definition.getVariable() and
87+
location = write.getLocation() and
88+
value = write.getVariable().getName()
89+
)
90+
or
91+
tag = "noSsaAstNode" and
92+
exists(VarDecl write |
93+
write.getVariable().getName() = "capturedWrite" and
94+
not exists(DataFlow::lvalueNode(write).getAstNode()) and
95+
location = write.getLocation() and
96+
value = write.getVariable().getName()
97+
)
98+
or
99+
tag = "localCapturedFlow" and
100+
exists(DataFlow::InvokeNode sink, VarUse read, SsaExplicitDefinition definition |
101+
sink.getCalleeName() = "SINK" and
102+
sink.getArgument(0).asExpr() = read and
103+
read.getSsaVariable().getDefinition() = definition and
104+
definition.getSourceVariable().isCaptured() and
105+
definition.getContainer() = sink.getContainer() and
106+
DataFlow::lvalueNode(definition.getLhs()).getASuccessor+() = sink.getArgument(0) and
107+
location = sink.getLocation() and
108+
value = read.getVariable().getName()
109+
)
110+
or
111+
exists(DataFlow::InvokeNode source, DataFlow::InvokeNode sink |
112+
source.getFile().getBaseName() = "captured.js" and
113+
source.getCalleeName() = "SOURCE" and
114+
sink.getCalleeName() = "SINK" and
115+
value = source.getArgument(0).getStringValue() and
116+
location = sink.getLocation() and
117+
(
118+
tag = "capturedFlow" and CapturedFlow::flow(source, sink.getArgument(0))
119+
or
120+
tag = "legacyCapturedFlow" and
121+
any(LegacyCapturedConfig config).hasFlow(source, sink.getArgument(0))
122+
)
123+
)
124+
)
125+
}
126+
}
127+
128+
import MakeTest<InlineTest>

0 commit comments

Comments
 (0)