Skip to content

Commit c1e9eec

Browse files
committed
JS: Modernize jQuery attribute defs
1 parent a224186 commit c1e9eec

File tree

6 files changed

+57
-32
lines changed

6 files changed

+57
-32
lines changed

javascript/ql/src/semmle/javascript/frameworks/jQuery.qll

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ private class JQueryDomElementDefinition extends DOM::ElementDefinition, @callex
125125
/**
126126
* An attribute defined using jQuery APIs.
127127
*/
128-
abstract private class JQueryAttributeDefinition extends DOM::AttributeDefinition { }
128+
abstract private class JQueryAttributeDefinition extends DOM::AttributeDefinition {}
129129

130130
/**
131131
* An attribute definition supplied when constructing a DOM element using `$(...)`.
@@ -149,18 +149,20 @@ private class JQueryAttributeDefinitionInElement extends JQueryAttributeDefiniti
149149
override DOM::ElementDefinition getElement() { result = elt }
150150
}
151151

152+
/** Gets the `attr` or `prop` string. */
153+
private string attrOrProp() {
154+
result = "attr" or result = "prop"
155+
}
156+
152157
/**
153158
* An attribute definition using `elt.attr(name, value)` or `elt.prop(name, value)`
154159
* where `elt` is a wrapped set.
155160
*/
156161
private class JQueryAttr2Call extends JQueryAttributeDefinition, @callexpr {
157-
JQueryDomElementDefinition elt;
158-
159162
JQueryAttr2Call() {
160-
exists(MethodCallExpr mce | this = mce |
161-
mce.getReceiver().(DOM::Element).getDefinition() = elt and
162-
(mce.getMethodName() = "attr" or mce.getMethodName() = "prop") and
163-
mce.getNumArgument() = 2
163+
exists(DataFlow::MethodCallNode call | this = call.asExpr() |
164+
call = JQuery::objectRef().getAMethodCall(attrOrProp()) and
165+
call.getNumArgument() = 2
164166
)
165167
}
166168

@@ -170,57 +172,63 @@ private class JQueryAttr2Call extends JQueryAttributeDefinition, @callexpr {
170172
result = DataFlow::valueNode(this.(CallExpr).getArgument(1))
171173
}
172174

173-
override DOM::ElementDefinition getElement() { result = elt }
175+
override DOM::ElementDefinition getElement() {
176+
exists(DataFlow::MethodCallNode call | this = call.asExpr() |
177+
result = call.getReceiver().getALocalSource().asExpr().(DOM::Element).getDefinition()
178+
)
179+
}
174180
}
175181

176182
/**
177183
* Holds if `mce` is a call to `elt.attr(attributes)` or `elt.prop(attributes)`.
178184
*/
179-
private predicate bulkAttributeInit(
180-
MethodCallExpr mce, JQueryDomElementDefinition elt, DataFlow::SourceNode attributes
181-
) {
182-
mce.getReceiver().(DOM::Element).getDefinition() = elt and
183-
(mce.getMethodName() = "attr" or mce.getMethodName() = "prop") and
185+
private predicate bulkAttributeInit(DataFlow::MethodCallNode mce, DataFlow::SourceNode attributes) {
186+
mce = JQuery::objectRef().getAMethodCall(attrOrProp()) and
184187
mce.getNumArgument() = 1 and
185-
attributes.flowsToExpr(mce.getArgument(0))
188+
attributes.flowsTo(mce.getArgument(0))
186189
}
187190

188191
/**
189-
* An attribute definition using `elt.attr(attributes)` or `elt.prop(attributes)`
190-
* where `elt` is a wrapped set and `attributes` is an object of attribute-value pairs
191-
* to set.
192+
* A property stored on an object flowing to `elt.attr(attributes)` or `elt.prop(attributes)`
193+
* where `elt` is a wrapped set.
194+
*
195+
* To avoid spurious combinations of `getName()` and `getValueNode()`,
196+
* this class is tied to an individual property write, as opposed to the call itself.
192197
*/
193-
private class JQueryAttrCall extends JQueryAttributeDefinition, @callexpr {
194-
JQueryDomElementDefinition elt;
198+
private class JQueryBulkAttributeProp extends JQueryAttributeDefinition {
195199
DataFlow::PropWrite pwn;
196200

197-
JQueryAttrCall() {
201+
JQueryBulkAttributeProp() {
198202
exists(DataFlow::SourceNode attributes |
199-
bulkAttributeInit(this, elt, attributes) and
200-
attributes.flowsTo(pwn.getBase())
203+
bulkAttributeInit(_, attributes) and
204+
pwn = attributes.getAPropertyWrite() and
205+
this = pwn.getAstNode()
201206
)
202207
}
203208

204209
override string getName() { result = pwn.getPropertyName() }
205210

206211
override DataFlow::Node getValueNode() { result = pwn.getRhs() }
207212

208-
override DOM::ElementDefinition getElement() { result = elt }
213+
override DOM::ElementDefinition getElement() {
214+
exists(DataFlow::MethodCallNode mce |
215+
bulkAttributeInit(mce, pwn.getBase().getALocalSource()) and
216+
result = mce.getReceiver().asExpr().(DOM::Element).getDefinition()
217+
)
218+
}
209219
}
210220

211221
/**
212222
* An attribute definition using `jQuery.attr(elt, name, value)` or `jQuery.prop(elt, name, value)`
213223
* where `elt` is a wrapped set or a plain DOM element.
214224
*/
215225
private class JQueryAttr3Call extends JQueryAttributeDefinition, @callexpr {
216-
DOM::ElementDefinition elt;
226+
MethodCallExpr mce;
217227

218228
JQueryAttr3Call() {
219-
exists(MethodCallExpr mce | this = mce |
220-
mce = jquery().getAMemberCall(any(string m | m = "attr" or m = "prop")).asExpr() and
221-
mce.getArgument(0).(DOM::Element).getDefinition() = elt and
222-
mce.getNumArgument() = 3
223-
)
229+
this = mce and
230+
mce = jquery().getAMemberCall(attrOrProp()).asExpr() and
231+
mce.getNumArgument() = 3
224232
}
225233

226234
override string getName() { result = this.(CallExpr).getArgument(1).getStringValue() }
@@ -229,7 +237,9 @@ private class JQueryAttr3Call extends JQueryAttributeDefinition, @callexpr {
229237
result = DataFlow::valueNode(this.(CallExpr).getArgument(2))
230238
}
231239

232-
override DOM::ElementDefinition getElement() { result = elt }
240+
override DOM::ElementDefinition getElement() {
241+
result = mce.getArgument(0).(DOM::Element).getDefinition()
242+
}
233243
}
234244

235245
/**
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
| tracking.js:15:5:15:26 | e.attr( ... 'foo') | class | tracking.js:15:21:15:25 | 'foo' |
2+
| tracking.js:17:7:17:18 | color: 'red' | color | tracking.js:17:14:17:18 | 'red' |
3+
| tracking.js:18:7:18:18 | size: '12pt' | size | tracking.js:18:13:18:18 | '12pt' |
4+
| tst.js:3:1:3:44 | $("<a/> ... e.com") | href | tst.js:3:24:3:43 | "https://semmle.com" |
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import javascript
2+
3+
from DOM::AttributeDefinition def
4+
select def, def.getName(), def.getValueNode()

javascript/ql/test/library-tests/frameworks/jQuery/JQueryMethodCall.expected

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
WARNING: Type JQueryMethodCall has been deprecated and may be removed in future (JQueryMethodCall.ql:3,6-22)
22
| tracking.js:7:5:7:24 | this.elm.html('foo') | html |
33
| tracking.js:14:5:14:17 | e.html('foo') | html |
4-
| tracking.js:18:7:18:15 | $('#foo') | $ |
4+
| tracking.js:15:5:15:26 | e.attr( ... 'foo') | attr |
5+
| tracking.js:16:5:19:6 | e.attr( ... \\n }) | attr |
6+
| tracking.js:23:7:23:15 | $('#foo') | $ |
57
| ts_global_types.ts:3:5:3:17 | e.html('foo') | html |
68
| ts_global_types.ts:7:1:7:9 | $('#foo') | $ |
79
| ts_import.ts:5:5:5:17 | e.html('foo') | html |

javascript/ql/test/library-tests/frameworks/jQuery/interpretsArgumentAsHtml.expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
WARNING: Type JQueryMethodCall has been deprecated and may be removed in future (interpretsArgumentAsHtml.ql:3,6-22)
22
| tracking.js:7:5:7:24 | this.elm.html('foo') | tracking.js:7:19:7:23 | 'foo' |
33
| tracking.js:14:5:14:17 | e.html('foo') | tracking.js:14:12:14:16 | 'foo' |
4-
| tracking.js:18:7:18:15 | $('#foo') | tracking.js:18:9:18:14 | '#foo' |
4+
| tracking.js:23:7:23:15 | $('#foo') | tracking.js:23:9:23:14 | '#foo' |
55
| ts_global_types.ts:3:5:3:17 | e.html('foo') | ts_global_types.ts:3:12:3:16 | 'foo' |
66
| ts_global_types.ts:7:1:7:9 | $('#foo') | ts_global_types.ts:7:3:7:8 | '#foo' |
77
| ts_import.ts:5:5:5:17 | e.html('foo') | ts_import.ts:5:12:5:16 | 'foo' |

javascript/ql/test/library-tests/frameworks/jQuery/tracking.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ class C {
1212
*/
1313
doSomethingWithTypes(e) {
1414
e.html('foo');
15+
e.attr('class', 'foo');
16+
e.attr({
17+
color: 'red',
18+
size: '12pt',
19+
});
1520
}
1621
}
1722

0 commit comments

Comments
 (0)