Skip to content

Commit 5441352

Browse files
author
Max Schaefer
authored
Merge pull request #1113 from esben-semmle/js/useless-property-assign-setter
JS: improve use of attributes from ~Object.defineProperty~
2 parents cb86687 + bfc1c6e commit 5441352

File tree

9 files changed

+90
-15
lines changed

9 files changed

+90
-15
lines changed

change-notes/1.21/analysis-javascript.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515

1616
## Changes to existing queries
1717

18-
| **Query** | **Expected impact** | **Change** |
19-
|--------------------------------------------|------------------------------|------------------------------------------------------------------------------|
18+
| **Query** | **Expected impact** | **Change** |
19+
|--------------------------------|------------------------------|---------------------------------------------------------------------------|
20+
| Expression has no effect | Fewer false-positive results | This rule now treats uses of `Object.defineProperty` more conservatively. |
21+
| Useless assignment to property | Fewer false-positive results | This rule now ignore reads of additional getters. |
2022

2123
## Changes to QL libraries

javascript/ql/src/Declarations/DeadStoreOfProperty.ql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,11 @@ where
157157
or
158158
// exclude result from accessor declarations
159159
assign1.getWriteNode() instanceof AccessorMethodDeclaration
160+
) and
161+
// exclude results from non-value definitions from `Object.defineProperty`
162+
(
163+
assign1 instanceof CallToObjectDefineProperty implies
164+
assign1.(CallToObjectDefineProperty).getAPropertyAttribute().getPropertyName() = "value"
160165
)
161166
select assign1.getWriteNode(),
162167
"This write to property '" + name + "' is useless, since $@ always overrides it.",

javascript/ql/src/Expressions/ExprHasNoEffect.ql

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,20 @@ predicate isDeclaration(Expr e) {
3535
* Holds if there exists a getter for a property called `name` anywhere in the program.
3636
*/
3737
predicate isGetterProperty(string name) {
38-
// there is a call of the form `Object.defineProperty(..., name, { get: ..., ... })`
39-
// or `Object.defineProperty(..., name, <something that's not an object literal>)`
38+
// there is a call of the form `Object.defineProperty(..., name, descriptor)` ...
4039
exists(CallToObjectDefineProperty defProp |
41-
name = defProp.getPropertyName() and
42-
exists(Expr descriptor | descriptor = defProp.getPropertyDescriptor().asExpr() |
43-
exists(descriptor.(ObjectExpr).getPropertyByName("get")) or
44-
not descriptor instanceof ObjectExpr
40+
name = defProp.getPropertyName() |
41+
// ... where `descriptor` defines a getter
42+
defProp.getAPropertyAttribute().getPropertyName() = "get" or
43+
// ... where `descriptor` may define a getter
44+
exists (DataFlow::SourceNode descriptor |
45+
descriptor.flowsTo(defProp.getPropertyDescriptor()) |
46+
descriptor.isIncomplete(_) or
47+
// minimal escape analysis for the descriptor
48+
exists (DataFlow::InvokeNode invk |
49+
not invk = defProp and
50+
descriptor.flowsTo(invk.getAnArgument())
51+
)
4552
)
4653
)
4754
or

javascript/ql/src/semmle/javascript/StandardLibrary.qll

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ class CallToObjectDefineProperty extends DataFlow::MethodCallNode {
2121

2222
/** Gets the data flow node denoting the descriptor of the property being defined. */
2323
DataFlow::Node getPropertyDescriptor() { result = getArgument(2) }
24+
25+
/** Gets a data flow node defining a descriptor attribute of the property being defined. */
26+
DataFlow::PropWrite getAPropertyAttribute() {
27+
exists (DataFlow::SourceNode descriptor |
28+
descriptor.flowsTo(getPropertyDescriptor()) and
29+
result = descriptor.getAPropertyWrite()
30+
)
31+
}
32+
2433
}
2534

2635
/**

javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -496,10 +496,7 @@ module DataFlow {
496496
override string getPropertyName() { result = odp.getPropertyName() }
497497

498498
override Node getRhs() {
499-
exists(ObjectLiteralNode propdesc |
500-
propdesc.flowsTo(odp.getPropertyDescriptor()) and
501-
propdesc.hasPropertyWrite("value", result)
502-
)
499+
odp.getAPropertyAttribute().writes(_, "value", result)
503500
}
504501

505502
override ControlFlowNode getWriteNode() { result = odp.getAstNode() }

javascript/ql/test/query-tests/Declarations/DeadStoreOfProperty/DeadStoreOfProperty.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@
1212
| tst.js:76:5:76:34 | o.pure1 ... te = 42 | This write to property 'pure16_simpleAliasWrite' is useless, since $@ always overrides it. | tst.js:77:5:77:36 | o16.pur ... te = 42 | another property write |
1313
| tst.js:95:5:95:17 | o.pure18 = 42 | This write to property 'pure18' is useless, since $@ always overrides it. | tst.js:96:5:96:17 | o.pure18 = 42 | another property write |
1414
| tst.js:96:5:96:17 | o.pure18 = 42 | This write to property 'pure18' is useless, since $@ always overrides it. | tst.js:97:5:97:17 | o.pure18 = 42 | another property write |
15+
| tst.js:114:2:114:14 | o.setter = 42 | This write to property 'setter' is useless, since $@ always overrides it. | tst.js:115:2:115:14 | o.setter = 87 | another property write |
16+
| tst.js:118:2:118:104 | Object. ... lue()}) | This write to property 'prop' is useless, since $@ always overrides it. | tst.js:119:2:119:12 | o.prop = 42 | another property write |

javascript/ql/test/query-tests/Declarations/DeadStoreOfProperty/tst.js

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,47 @@
8282
}
8383

8484
// DOM
85-
o.clientTop = 42;
85+
o.clientTop = 42; // OK
8686
o.clientTop = 42;
8787

88-
o.defaulted1 = null;
88+
o.defaulted1 = null; // OK
8989
o.defaulted1 = 42;
9090

91-
o.defaulted2 = -1;
91+
o.defaulted2 = -1; // OK
9292
o.defaulted2 = 42;
9393

9494
var o = {};
9595
o.pure18 = 42; // NOT OK
9696
o.pure18 = 42; // NOT OK
9797
o.pure18 = 42;
98+
99+
var o = {};
100+
Object.defineProperty(o, "setter", { // OK
101+
set: function (value) { }
102+
});
103+
o.setter = "";
104+
105+
var o = { set setter(value) { } }; // OK
106+
o.setter = "";
107+
108+
var o = {
109+
set accessor(value) { }, // OK
110+
get accessor() { }
111+
};
112+
113+
var o = { set setter(value) { } };
114+
o.setter = 42; // probably OK, but still flagged - it seems fishy
115+
o.setter = 87;
116+
117+
var o = {};
118+
Object.defineProperty(o, "prop", {writable:!0,configurable:!0,enumerable:!1, value: getInitialValue()}) // NOT OK
119+
o.prop = 42;
120+
121+
var o = {};
122+
Object.defineProperty(o, "prop", {writable:!0,configurable:!0,enumerable:!1, value: undefined}) // OK, default value
123+
o.prop = 42;
124+
125+
var o = {};
126+
Object.defineProperty(o, "prop", {writable:!0,configurable:!0,enumerable:!1}) // OK
127+
o.prop = 42;
98128
});

javascript/ql/test/query-tests/Expressions/ExprHasNoEffect/ExprHasNoEffect.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@
88
| tst.js:49:3:49:26 | new Err ... ou so") | This expression has no effect. |
99
| tst.js:50:3:50:49 | new Syn ... o me?") | This expression has no effect. |
1010
| tst.js:51:3:51:36 | new Err ... age(e)) | This expression has no effect. |
11+
| tst.js:62:2:62:20 | o.trivialNonGetter1 | This expression has no effect. |
1112
| uselessfn.js:1:1:1:15 | (functi ... .");\\n}) | This expression has no effect. |

javascript/ql/test/query-tests/Expressions/ExprHasNoEffect/tst.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,25 @@ try {
5151
new Error(computeSnarkyMessage(e)); // NOT OK
5252
new UnknownError(); // OK
5353
}
54+
55+
function g() {
56+
var o = {};
57+
58+
Object.defineProperty(o, "trivialGetter1", { get: function(){} });
59+
o.trivialGetter1; // OK
60+
61+
Object.defineProperty(o, "trivialNonGetter1", "foo");
62+
o.trivialNonGetter1; // NOT OK
63+
64+
var getterDef1 = { get: function(){} };
65+
Object.defineProperty(o, "nonTrivialGetter1", getterDef1);
66+
o.nonTrivialGetter1; // OK
67+
68+
var getterDef2 = { };
69+
unknownPrepareGetter(getterDef2);
70+
Object.defineProperty(o, "nonTrivialNonGetter1", getterDef2);
71+
o.nonTrivialNonGetter1; // OK
72+
73+
Object.defineProperty(o, "nonTrivialGetter2", unknownGetterDef());
74+
o.nonTrivialGetter2; // OK
75+
};

0 commit comments

Comments
 (0)