-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Expand file tree
/
Copy pathTemplateSyntaxInStringLiteral.ql
More file actions
131 lines (118 loc) · 4.17 KB
/
TemplateSyntaxInStringLiteral.ql
File metadata and controls
131 lines (118 loc) · 4.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/**
* @name Template syntax in string literal
* @description A string literal appears to use template syntax but is not quoted with backticks.
* @kind problem
* @problem.severity warning
* @id js/template-syntax-in-string-literal
* @precision high
* @tags quality
* reliability
* correctness
* language-features
*/
import javascript
/** A toplevel that contains at least one template literal. */
class CandidateTopLevel extends TopLevel {
CandidateTopLevel() { exists(TemplateLiteral template | template.getTopLevel() = this) }
}
/** A string literal in a toplevel that contains at least one template literal. */
class CandidateStringLiteral extends StringLiteral {
string v;
CandidateStringLiteral() {
this.getTopLevel() instanceof CandidateTopLevel and
v = this.getStringValue()
}
/**
* Extracts a `${...}` part from this string literal using an inexact regular expression.
*
* Breakdown of the sequence matched by the expression:
* - any prefix and then `${`
* - any amount of whitespace and simple unary operators
* - name of the variable
* - optionally: a character terminating the identifier token, followed by anything
* - `}`, followed by anything
*/
string getAReferencedVariable() {
result = v.regexpCapture(".*\\$\\{[\\s+\\-!]*([\\w\\p{L}$]+)([^\\w\\p{L}$].*)?\\}.*", 1)
}
/**
* Gets an ancestor node of this string literal in the AST that can be reached without
* stepping over scope elements.
*/
AstNode getIntermediate() {
result = this
or
exists(AstNode mid | mid = this.getIntermediate() |
not mid instanceof ScopeElement and
result = mid.getParent()
)
}
/**
* Holds if this string literal is in the given `scope`.
*/
predicate isInScope(Scope scope) {
scope instanceof GlobalScope or
this.getIntermediate().(ScopeElement).getScope() = scope.getAnInnerScope*()
}
}
/**
* Holds if there exists an object that has a property for each template variable in `lit` and
* they occur as arguments to the same call.
*
* This recognises a typical pattern in which template arguments are passed along with a string,
* for example:
*
* ```
* output.emit('<a href="${url}">${name}$</a>',
* { url: url, name: name } );
* ```
*/
predicate hasObjectProvidingTemplateVariables(CandidateStringLiteral lit) {
exists(DataFlow::CallNode call, DataFlow::ObjectLiteralNode obj |
call.getAnArgument() = [lit.flow(), StringConcatenation::getRoot(lit.flow())] and
obj.flowsTo(call.getAnArgument()) and
forex(string name | name = lit.getAReferencedVariable() | exists(obj.getAPropertyWrite(name)))
)
}
/**
* Gets a declaration of variable `v` in `tl`, where `v` has the given `name` and
* belongs to `scope`.
*/
VarDecl getDeclIn(Variable v, Scope scope, string name, CandidateTopLevel tl) {
v.getName() = name and
v.getADeclaration() = result and
v.getScope() = scope and
result.getTopLevel() = tl
}
/**
* Tracks data flow from a string literal that may flow to a replace operation.
*/
DataFlow::SourceNode trackStringWithTemplateSyntax(
CandidateStringLiteral lit, DataFlow::TypeTracker t
) {
t.start() and result = lit.flow() and exists(lit.getAReferencedVariable())
or
exists(DataFlow::TypeTracker t2 | result = trackStringWithTemplateSyntax(lit, t2).track(t2, t))
}
/**
* Gets a string literal that flows to a replace operation.
*/
DataFlow::SourceNode trackStringWithTemplateSyntax(CandidateStringLiteral lit) {
result = trackStringWithTemplateSyntax(lit, DataFlow::TypeTracker::end())
}
/**
* Holds if the string literal flows to a replace method call.
*/
predicate hasReplaceMethodCall(CandidateStringLiteral lit) {
trackStringWithTemplateSyntax(lit).getAMethodCall() instanceof StringReplaceCall
}
from CandidateStringLiteral lit, Variable v, Scope s, string name, VarDecl decl
where
decl = getDeclIn(v, s, name, lit.getTopLevel()) and
lit.getAReferencedVariable() = name and
lit.isInScope(s) and
not hasObjectProvidingTemplateVariables(lit) and
not lit.getStringValue() = "${" + name + "}" and
not hasReplaceMethodCall(lit)
select lit, "This string is not a template literal, but appears to reference the variable $@.",
decl, v.getName()