Skip to content

Commit cd111fe

Browse files
authored
Merge pull request github#3721 from asger-semmle/js/non-linear-pattern-msg
JS: Improve alert message in js/non-linear-pattern
2 parents ab327b9 + 23d2896 commit cd111fe

File tree

9 files changed

+110
-5
lines changed

9 files changed

+110
-5
lines changed

change-notes/1.25/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
| Hard-coded credentials (`js/hardcoded-credentials`) | More results | This query now recognizes hard-coded credentials sent via HTTP authorization headers. |
4747
| Incomplete URL scheme check (`js/incomplete-url-scheme-check`) | More results | This query now recognizes additional url scheme checks. |
4848
| Misspelled variable name (`js/misspelled-variable-name`) | Message changed | The message for this query now correctly identifies the misspelled variable in additional cases. |
49+
| Non-linear pattern (`js/non-linear-pattern`) | Fewer duplicates and message changed | This query now generates fewer duplicate alerts and has a clearer explanation in case of type annotations used in a pattern. |
4950
| Prototype pollution in utility function (`js/prototype-pollution-utility`) | More results | This query now recognizes additional utility functions as vulnerable to prototype polution. |
5051
| Uncontrolled command line (`js/command-line-injection`) | More results | This query now recognizes additional command execution calls. |
5152
| Uncontrolled data used in path expression (`js/path-injection`) | More results | This query now recognizes additional file system calls. |

javascript/ql/src/LanguageFeatures/NonLinearPattern.qhelp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ If the same pattern variable is bound multiple times in the same object or array
88
binding overwrites all of the earlier ones. This is most likely unintended and should be avoided.
99
</p>
1010

11+
<p>
12+
In TypeScript, a common mistake is to try to write type annotations inside a pattern. This is not
13+
possible, and the type annotation should come after the pattern.
14+
</p>
15+
1116
</overview>
1217
<recommendation>
1318

@@ -34,6 +39,21 @@ From context, it appears that the second binding should have been for variable <
3439

3540
<sample src="examples/NonLinearPatternGood.js" />
3641

42+
<p>
43+
This can sometimes happen in TypeScript, due to the apparant similarity between property patterns
44+
and type annotations. In the following example, the function uses a pattern parameter with properties <code>x</code>
45+
and <code>y</code>. These appear to have type <code>number</code>, but are in fact untyped properties both stored in a variable named <code>number</code>.
46+
</p>
47+
48+
<sample src="examples/NonLinearPatternTS.ts" />
49+
50+
<p>
51+
It is not possible to specify type annotations inside a pattern. The correct way is to specify the type
52+
after the parameter:
53+
</p>
54+
55+
<sample src="examples/NonLinearPatternTSGood.ts" />
56+
3757
</example>
3858
<references>
3959
<li>Mozilla Developer Network: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment">Destructuring assignment</a>.</li>

javascript/ql/src/LanguageFeatures/NonLinearPattern.ql

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,55 @@
1414

1515
import javascript
1616

17-
from BindingPattern p, string n, VarDecl v, VarDecl w
17+
class RootDestructuringPattern extends BindingPattern {
18+
RootDestructuringPattern() {
19+
this instanceof DestructuringPattern and
20+
not this = any(PropertyPattern p).getValuePattern() and
21+
not this = any(ArrayPattern p).getAnElement()
22+
}
23+
24+
/** Holds if this pattern has multiple bindings for `name`. */
25+
predicate hasConflictingBindings(string name) {
26+
exists(VarRef v, VarRef w |
27+
v = getABindingVarRef() and
28+
w = getABindingVarRef() and
29+
name = v.getName() and
30+
name = w.getName() and
31+
v != w
32+
)
33+
}
34+
35+
/** Gets the first occurrence of the conflicting binding `name`. */
36+
VarDecl getFirstClobberedVarDecl(string name) {
37+
hasConflictingBindings(name) and
38+
result =
39+
min(VarDecl decl |
40+
decl = getABindingVarRef() and decl.getName() = name
41+
|
42+
decl order by decl.getLocation().getStartLine(), decl.getLocation().getStartColumn()
43+
)
44+
}
45+
46+
/** Holds if variables in this pattern may resemble type annotations. */
47+
predicate resemblesTypeAnnotation() {
48+
hasConflictingBindings(_) and // Restrict size of predicate.
49+
this instanceof Parameter and
50+
this instanceof ObjectPattern and
51+
not exists(getTypeAnnotation()) and
52+
getFile().getFileType().isTypeScript()
53+
}
54+
}
55+
56+
from RootDestructuringPattern p, string n, VarDecl v, VarDecl w, string message
1857
where
19-
v = p.getABindingVarRef() and
58+
v = p.getFirstClobberedVarDecl(n) and
2059
w = p.getABindingVarRef() and
21-
v.getName() = n and
2260
w.getName() = n and
2361
v != w and
24-
v.getLocation().startsBefore(w.getLocation())
25-
select w, "Repeated binding of pattern variable '" + n + "' previously bound $@.", v, "here"
62+
if p.resemblesTypeAnnotation()
63+
then
64+
message =
65+
"The pattern variable '" + n +
66+
"' appears to be a type, but is a variable previously bound $@."
67+
else message = "Repeated binding of pattern variable '" + n + "' previously bound $@."
68+
select w, message, v, "here"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
function distance({x: number, y: number}) {
2+
return Math.sqrt(x*x + y*y);
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
function distance({x, y}: {x: number, y: number}) {
2+
return Math.sqrt(x*x + y*y);
3+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
| NonLinearPatternTS.ts:1:34:1:39 | number | The pattern variable 'number' appears to be a type, but is a variable previously bound $@. | NonLinearPatternTS.ts:1:23:1:28 | number | here |
12
| ts-test.ts:3:13:3:13 | x | Repeated binding of pattern variable 'x' previously bound $@. | ts-test.ts:3:10:3:10 | x | here |
23
| ts-test.ts:8:16:8:16 | x | Repeated binding of pattern variable 'x' previously bound $@. | ts-test.ts:8:10:8:10 | x | here |
34
| ts-test.ts:11:10:11:10 | x | Repeated binding of pattern variable 'x' previously bound $@. | ts-test.ts:11:7:11:7 | x | here |
5+
| ts-test.ts:21:8:21:13 | string | The pattern variable 'string' appears to be a type, but is a variable previously bound $@. | ts-test.ts:20:8:20:13 | string | here |
6+
| ts-test.ts:32:16:32:16 | x | Repeated binding of pattern variable 'x' previously bound $@. | ts-test.ts:30:12:30:12 | x | here |
7+
| ts-test.ts:34:20:34:20 | x | Repeated binding of pattern variable 'x' previously bound $@. | ts-test.ts:30:12:30:12 | x | here |
8+
| ts-test.ts:40:27:40:32 | string | Repeated binding of pattern variable 'string' previously bound $@. | ts-test.ts:40:16:40:21 | string | here |
49
| tst.js:3:13:3:13 | x | Repeated binding of pattern variable 'x' previously bound $@. | tst.js:3:10:3:10 | x | here |
510
| tst.js:8:16:8:16 | x | Repeated binding of pattern variable 'x' previously bound $@. | tst.js:8:10:8:10 | x | here |
611
| tst.js:11:10:11:10 | x | Repeated binding of pattern variable 'x' previously bound $@. | tst.js:11:7:11:7 | x | here |
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
function distance({x: number, y: number}) {
2+
return Math.sqrt(x*x + y*y);
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
function distance({x, y}: {x: number, y: number}) {
2+
return Math.sqrt(x*x + y*y);
3+
}

javascript/ql/test/query-tests/LanguageFeatures/NonLinearPattern/ts-test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,27 @@ var { x: x, x: y } = o;
1515

1616
// OK
1717
var { p = x, q = x } = o;
18+
19+
function f({
20+
x: string,
21+
y: string // NOT OK
22+
}) {
23+
}
24+
25+
function g({x, y}: {x: string, y: string}) { // OK
26+
}
27+
28+
function blah(arg) {
29+
var {
30+
x: x,
31+
y: {
32+
x: x, // NOT OK
33+
y: {
34+
x: x // NOT OK
35+
}
36+
}
37+
} = arg;
38+
}
39+
40+
function h({x: string, y: string}: any) { // NOT OK
41+
}

0 commit comments

Comments
 (0)