Skip to content

Commit 8333f72

Browse files
authored
Merge pull request #470 from esben-semmle/custom-abstract-values-only
Approved by xiemaisi
2 parents 6c0305c + ee7a6af commit 8333f72

8 files changed

Lines changed: 293 additions & 1 deletion

File tree

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/**
2+
* Provides classes for working with analysis-specific abstract values.
3+
*
4+
* Implement a subclass of `CustomAbstractValueDefinition` when the builtin
5+
* abstract values of `AbstractValues.qll` are not expressive enough.
6+
*
7+
* For performance reasons, all subclasses of `CustomAbstractValueDefinition`
8+
* should be part of the standard library.
9+
*/
10+
11+
import javascript
12+
private import internal.AbstractValuesImpl
13+
private import InferredTypes
14+
15+
/**
16+
* An abstract representation of an analysis-specific value.
17+
*
18+
* Wraps a `CustomAbstractValueDefinition`.
19+
*/
20+
class CustomAbstractValueFromDefinition extends AbstractValue, TCustomAbstractValueFromDefinition {
21+
22+
CustomAbstractValueDefinition def;
23+
24+
CustomAbstractValueFromDefinition() {
25+
this = TCustomAbstractValueFromDefinition(def)
26+
}
27+
28+
override InferredType getType() {
29+
result = def.getType()
30+
}
31+
32+
override boolean getBooleanValue() {
33+
result = def.getBooleanValue()
34+
}
35+
36+
override PrimitiveAbstractValue toPrimitive() {
37+
result = def.toPrimitive()
38+
}
39+
40+
override predicate isCoercibleToNumber() {
41+
def.isCoercibleToNumber()
42+
}
43+
44+
override predicate isIndefinite(DataFlow::Incompleteness cause) {
45+
def.isIndefinite(cause)
46+
}
47+
48+
override DefiniteAbstractValue getAPrototype() {
49+
result = def.getAPrototype()
50+
}
51+
52+
override predicate hasLocationInfo(string f, int startline, int startcolumn, int endline, int endcolumn) {
53+
def.getLocation().hasLocationInfo(f, startline, startcolumn, endline, endcolumn)
54+
}
55+
56+
override string toString() {
57+
result = def.toString()
58+
}
59+
60+
/**
61+
* Gets the definition that induces this value.
62+
*/
63+
CustomAbstractValueDefinition getDefinition() {
64+
result = def
65+
}
66+
67+
/** Holds if this is a value whose properties the type inference tracks. */
68+
predicate shouldTrackProperties() {
69+
def.shouldTrackProperties()
70+
}
71+
72+
}
73+
74+
/**
75+
* A data-flow node that induces an analysis-specific abstract value.
76+
*
77+
* Enables modular extensions of `AbstractValue`.
78+
*
79+
* For performance reasons, all subclasses of this class should be part
80+
* of the standard library.
81+
*/
82+
abstract class CustomAbstractValueDefinition extends Locatable {
83+
84+
/**
85+
* Gets the type of some concrete value represented by the induced
86+
* abstract value.
87+
*/
88+
abstract InferredType getType();
89+
90+
/**
91+
* Gets the Boolean value that some concrete value represented by the
92+
* induced abstract value coerces to.
93+
*/
94+
abstract boolean getBooleanValue();
95+
96+
/**
97+
* Gets an abstract primitive value the induced abstract value coerces
98+
* to.
99+
*
100+
* This abstractly models the `ToPrimitive` coercion described in the
101+
* ECMAScript language specification.
102+
*/
103+
abstract PrimitiveAbstractValue toPrimitive();
104+
105+
/**
106+
* Holds if the induced abstract value is coercible to a number, that
107+
* is, it represents at least one concrete value for which the
108+
* `ToNumber` conversion does not yield `NaN`.
109+
*/
110+
abstract predicate isCoercibleToNumber();
111+
112+
/**
113+
* Holds if the induced abstract value is an indefinite value arising
114+
* from the incompleteness `cause`.
115+
*/
116+
predicate isIndefinite(DataFlow::Incompleteness cause) {
117+
none()
118+
}
119+
120+
/**
121+
* Gets an abstract value that represents a prototype object of the
122+
* induced abstract value.
123+
*/
124+
AbstractValue getAPrototype() {
125+
exists (AbstractProtoProperty proto |
126+
proto.getBase() = getAbstractValue() and
127+
result = proto.getAValue()
128+
)
129+
}
130+
131+
/**
132+
* Gets the induced abstract value.
133+
*/
134+
AbstractValue getAbstractValue() {
135+
result.(CustomAbstractValueFromDefinition).getDefinition() = this
136+
}
137+
138+
/** Holds if this is a value whose properties the type inference tracks. */
139+
abstract predicate shouldTrackProperties();
140+
141+
}
142+
143+
/**
144+
* Flow analysis for custom abstract values.
145+
*/
146+
class CustomAbstractValueFromDefinitionNode extends DataFlow::AnalyzedNode, DataFlow::ValueNode {
147+
148+
CustomAbstractValueFromDefinition val;
149+
150+
CustomAbstractValueFromDefinitionNode() {
151+
val = TCustomAbstractValueFromDefinition(this.getAstNode())
152+
}
153+
154+
override AbstractValue getALocalValue() {
155+
result = val
156+
}
157+
158+
}

javascript/ql/src/semmle/javascript/dataflow/internal/AbstractPropertiesImpl.qll

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,5 +77,6 @@ predicate shouldAlwaysTrackProperties(AbstractValue baseVal) {
7777
predicate shouldTrackProperties(AbstractValue baseVal) {
7878
shouldAlwaysTrackProperties(baseVal) or
7979
baseVal instanceof AbstractObjectLiteral or
80-
baseVal instanceof AbstractInstance
80+
baseVal instanceof AbstractInstance or
81+
baseVal.(CustomAbstractValueFromDefinition).shouldTrackProperties()
8182
}

javascript/ql/src/semmle/javascript/dataflow/internal/AbstractValuesImpl.qll

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import semmle.javascript.dataflow.AbstractValues
88
private import semmle.javascript.dataflow.InferredTypes
9+
import semmle.javascript.dataflow.CustomAbstractValueDefinitions
910

1011
/** An abstract value inferred by the flow analysis. */
1112
cached newtype TAbstractValue =
@@ -101,6 +102,9 @@ cached newtype TAbstractValue =
101102
or
102103
/** A custom abstract value induced by `tag`. */
103104
TCustomAbstractValue(CustomAbstractValueTag tag)
105+
or
106+
/** A custom abstract value induced by `def`. */
107+
TCustomAbstractValueFromDefinition(CustomAbstractValueDefinition def)
104108

105109
/**
106110
* Gets a definite abstract value with the given type.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
| tst.js:4:16:4:50 | { custo ... false } | tst.js:4:16:4:50 | { custo ... false } | false | false |
2+
| tst.js:5:16:5:49 | { custo ... true } | tst.js:5:16:5:49 | { custo ... true } | false | true |
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import javascript
2+
import semmle.javascript.dataflow.InferredTypes
3+
import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
4+
import semmle.javascript.dataflow.internal.AbstractPropertiesImpl as AbstractPropertiesImpl
5+
import semmle.javascript.dataflow.CustomAbstractValueDefinitions
6+
7+
class MyCustomAbstractValueDefinition extends CustomAbstractValueDefinition {
8+
9+
DataFlow::ValueNode node;
10+
11+
MyCustomAbstractValueDefinition() {
12+
DataFlow::valueNode(this) = node and
13+
node instanceof DataFlow::ObjectLiteralNode and
14+
exists (DataFlow::PropWrite pwn |
15+
pwn.writes(node, "custom", any(BooleanLiteral l | l.getValue() = "true").flow())
16+
)
17+
}
18+
19+
override boolean getBooleanValue() {
20+
result = true
21+
}
22+
23+
override predicate isCoercibleToNumber() {
24+
none()
25+
}
26+
27+
override PrimitiveAbstractValue toPrimitive() {
28+
result = TAbstractOtherString()
29+
}
30+
31+
override InferredType getType() { result = TTObject() }
32+
33+
override predicate shouldTrackProperties() {
34+
exists (DataFlow::PropWrite pwn |
35+
pwn.writes(node, "trackProps", any(BooleanLiteral l | l.getValue() = "true").flow())
36+
)
37+
}
38+
39+
}
40+
41+
boolean flowProps(AbstractValue val) {
42+
if FlowSteps::shouldTrackProperties(val) then
43+
result = true
44+
else
45+
result = false
46+
}
47+
48+
boolean typeProps(AbstractValue val) {
49+
if AbstractPropertiesImpl::shouldTrackProperties(val) then
50+
result = true
51+
else
52+
result = false
53+
}
54+
55+
from MyCustomAbstractValueDefinition def, AbstractValue val
56+
where def.getAbstractValue() = val
57+
select def, val, flowProps(val), typeProps(val)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
| tst.js:4:16:4:50 | { custo ... false } | tst.js:4:16:4:50 | { custo ... false } |
2+
| tst.js:5:16:5:49 | { custo ... true } | tst.js:5:16:5:49 | { custo ... true } |
3+
| tst.js:15:9:15:21 | result = obj3 | tst.js:4:16:4:50 | { custo ... false } |
4+
| tst.js:15:18:15:21 | obj3 | tst.js:4:16:4:50 | { custo ... false } |
5+
| tst.js:18:9:18:21 | result = obj4 | tst.js:5:16:5:49 | { custo ... true } |
6+
| tst.js:18:18:18:21 | obj4 | tst.js:5:16:5:49 | { custo ... true } |
7+
| tst.js:21:5:21:10 | result | tst.js:4:16:4:50 | { custo ... false } |
8+
| tst.js:21:5:21:10 | result | tst.js:5:16:5:49 | { custo ... true } |
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import javascript
2+
import semmle.javascript.dataflow.InferredTypes
3+
import semmle.javascript.dataflow.CustomAbstractValueDefinitions
4+
5+
class MyCustomAbstractValueDefinition extends CustomAbstractValueDefinition {
6+
7+
DataFlow::ValueNode node;
8+
9+
MyCustomAbstractValueDefinition() {
10+
DataFlow::valueNode(this) = node and
11+
node instanceof DataFlow::ObjectLiteralNode and
12+
exists (DataFlow::PropWrite pwn |
13+
pwn.writes(node, "custom", any(BooleanLiteral l | l.getValue() = "true").flow())
14+
)
15+
}
16+
17+
override boolean getBooleanValue() {
18+
result = true
19+
}
20+
21+
override predicate isCoercibleToNumber() {
22+
none()
23+
}
24+
25+
override PrimitiveAbstractValue toPrimitive() {
26+
result = TAbstractOtherString()
27+
}
28+
29+
override InferredType getType() { result = TTObject() }
30+
31+
override predicate shouldTrackProperties() {
32+
none()
33+
}
34+
35+
}
36+
37+
from AnalyzedValueNode n, MyCustomAbstractValueDefinition def, CustomAbstractValueFromDefinition val
38+
where def.getAbstractValue() = val and
39+
n.getAValue() = val
40+
select n, val
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
(function() {
2+
var obj1 = { custom: false, trackProps: false };
3+
var obj2 = { custom: false, trackProps: true };
4+
var obj3 = { custom: true, trackProps: false };
5+
var obj4 = { custom: true, trackProps: true };
6+
7+
var result;
8+
if (x1) {
9+
result = obj1;
10+
}
11+
if (x2) {
12+
result = obj2;
13+
}
14+
if (x3) {
15+
result = obj3;
16+
}
17+
if (x4) {
18+
result = obj4;
19+
}
20+
21+
result;
22+
})();

0 commit comments

Comments
 (0)