Merge remote-tracking branch 'upstream/master' into UselessCat

This commit is contained in:
Erik Krogh Kristensen
2020-02-28 09:56:23 +01:00
468 changed files with 12761 additions and 4082 deletions

View File

@@ -24,8 +24,9 @@ where
exists(string n | p = f.getDependencyParameter(n) |
p.getName() != n and
exists(f.getDependencyParameter(p.getName())) and
msg = "This parameter is named '" + p.getName() + "', " +
"but actually refers to dependency '" + n + "'."
msg =
"This parameter is named '" + p.getName() + "', " + "but actually refers to dependency '" +
n + "'."
)
)
select p, msg

View File

@@ -15,7 +15,8 @@ import javascript
from AngularJS::ServiceReference compile, SimpleParameter elem, CallExpr c
where
compile.getName() = "$compile" and
elem = any(AngularJS::CustomDirective d)
elem =
any(AngularJS::CustomDirective d)
.getALinkFunction()
.(AngularJS::LinkFunction)
.getElementParameter() and

View File

@@ -130,7 +130,8 @@ where
kind = getServiceKind(request, name) and
exists(request.getAServiceDefinition(name)) and // ignore unknown/undefined services
not isCompatibleRequestedService(request, kind) and
compatibleWithString = concat(string compatibleKind |
compatibleWithString =
concat(string compatibleKind |
isCompatibleRequestedService(request, compatibleKind) and
not isWildcardKind(compatibleKind)
|

View File

@@ -34,8 +34,9 @@ predicate isMissingParameter(AngularJS::InjectableFunction f, string msg, ASTNod
then dependenciesAreString = "dependency is"
else dependenciesAreString = "dependencies are"
) and
msg = "This function has " + paramCount + " " + parametersString + ", but " + injectionCount +
" " + dependenciesAreString + " injected into it."
msg =
"This function has " + paramCount + " " + parametersString + ", but " + injectionCount + " "
+ dependenciesAreString + " injected into it."
)
)
}

View File

@@ -31,7 +31,8 @@ VarRef refInContainer(Variable var, RefKind kind, StmtContainer sc) {
* declaration of `var` (if `kind` is `Decl()`) in `sc`.
*/
VarRef firstRefInContainer(Variable var, RefKind kind, StmtContainer sc) {
result = min(refInContainer(var, kind, sc) as ref
result =
min(refInContainer(var, kind, sc) as ref
order by
ref.getLocation().getStartLine(), ref.getLocation().getStartColumn()
)
@@ -52,7 +53,8 @@ VarRef refInTopLevel(Variable var, RefKind kind, TopLevel tl) {
* declaration of `var` (if `kind` is `Decl()`) in `tl`.
*/
VarRef firstRefInTopLevel(Variable var, RefKind kind, TopLevel tl) {
result = min(refInTopLevel(var, kind, tl) as ref
result =
min(refInTopLevel(var, kind, tl) as ref
order by
ref.getLocation().getStartLine(), ref.getLocation().getStartColumn()
)

View File

@@ -57,7 +57,8 @@ GlobalVarAccess getAccessIn(GlobalVariable v, Function f) {
* Gets the (lexically) first access to variable `v` in function `f`.
*/
GlobalVarAccess getFirstAccessIn(GlobalVariable v, Function f) {
result = min(getAccessIn(v, f) as gva
result =
min(getAccessIn(v, f) as gva
order by
gva.getLocation().getStartLine(), gva.getLocation().getStartColumn()
)

View File

@@ -1,7 +1,7 @@
/**
* @name Suspicious method name declaration
* @description A method declaration with a name that is a special keyword in another
* context is suspicious.
* @description A method declaration with a name that is a special keyword in another
* context is suspicious.
* @kind problem
* @problem.severity warning
* @id js/suspicious-method-name-declaration
@@ -19,7 +19,7 @@ import javascript
predicate isSuspiciousMethodName(string name, ClassOrInterface container) {
name = "function"
or
// "constructor" is only suspicious outside a class.
// "constructor" is only suspicious outside a class.
name = "constructor" and not container instanceof ClassDefinition
or
// "new" is only suspicious inside a class.
@@ -31,7 +31,6 @@ where
container.getLocation().getFile().getFileType().isTypeScript() and
container.getMember(name) = member and
isSuspiciousMethodName(name, container) and
// Cases to ignore.
not (
// Assume that a "new" method is intentional if the class has an explicit constructor.
@@ -39,31 +38,34 @@ where
container instanceof ClassDefinition and
exists(ConstructorDeclaration constructor |
container.getMember("constructor") = constructor and
not constructor.isSynthetic()
)
not constructor.isSynthetic()
)
or
// Explicitly declared static methods are fine.
container instanceof ClassDefinition and
member.isStatic()
or
// Only looking for declared methods. Methods with a body are OK.
// Only looking for declared methods. Methods with a body are OK.
exists(member.getBody().getBody())
or
// The developer was not confused about "function" when there are other methods in the interface.
name = "function" and
name = "function" and
exists(MethodDeclaration other | other = container.getAMethod() |
other.getName() != "function" and
not other.(ConstructorDeclaration).isSynthetic()
)
)
and
) and
(
name = "constructor" and msg = "The member name 'constructor' does not declare a constructor in interfaces, but it does in classes."
name = "constructor" and
msg =
"The member name 'constructor' does not declare a constructor in interfaces, but it does in classes."
or
name = "new" and msg = "The member name 'new' does not declare a constructor, but 'constructor' does in class declarations."
name = "new" and
msg =
"The member name 'new' does not declare a constructor, but 'constructor' does in class declarations."
or
name = "function" and msg = "The member name 'function' does not declare a function, it declares a method named 'function'."
name = "function" and
msg =
"The member name 'function' does not declare a function, it declares a method named 'function'."
)
select member, msg

View File

@@ -44,43 +44,39 @@ string getKind(MemberDeclaration m) {
* A call-signature that originates from a MethodSignature in the AST.
*/
private class MethodCallSig extends CallSignatureType {
string name;
MethodCallSig() {
exists(MethodSignature sig |
this = sig.getBody().getCallSignature() and
name = sig.getName()
)
}
/**
* Gets the name of any member that has this signature.
*/
string getName() {
result = name
}
string name;
MethodCallSig() {
exists(MethodSignature sig |
this = sig.getBody().getCallSignature() and
name = sig.getName()
)
}
/**
* Gets the name of any member that has this signature.
*/
string getName() { result = name }
}
/**
* Holds if the two call signatures could be overloads of each other and have the same parameter types.
*/
predicate matchingCallSignature(MethodCallSig method, MethodCallSig other) {
method.getName() = other.getName() and
method.getName() = other.getName() and
method.getNumOptionalParameter() = other.getNumOptionalParameter() and
method.getNumParameter() = other.getNumParameter() and
method.getNumRequiredParameter() = other.getNumRequiredParameter() and
// purposely not looking at number of type arguments.
method.getKind() = other.getKind() and
forall(int i | i in [0 .. -1 + method.getNumParameter()] |
// purposely not looking at number of type arguments.
method.getKind() = other.getKind() and
forall(int i | i in [0 .. -1 + method.getNumParameter()] |
method.getParameter(i) = other.getParameter(i) // This is sometimes imprecise, so it is still a good idea to compare type annotations.
) and
// shared type parameters are equal.
forall(int i | i in [0 .. -1 + min(int num | num = method.getNumTypeParameter() or num = other.getNumTypeParameter())] |
) and
// shared type parameters are equal.
forall(int i |
i in [0 .. -1 +
min(int num | num = method.getNumTypeParameter() or num = other.getNumTypeParameter())]
|
method.getTypeParameterBound(i) = other.getTypeParameterBound(i)
)
}
@@ -89,7 +85,7 @@ predicate matchingCallSignature(MethodCallSig method, MethodCallSig other) {
* Gets which overload index the MethodSignature has among the overloads of the same name.
*/
int getOverloadIndex(MethodSignature sig) {
sig.getDeclaringType().getMethodOverload(sig.getName(), result) = sig
sig.getDeclaringType().getMethodOverload(sig.getName(), result) = sig
}
/**
@@ -100,18 +96,14 @@ predicate signaturesMatch(MethodSignature method, MethodSignature other) {
method.getDeclaringType() = other.getDeclaringType() and
// same static modifier.
getKind(method) = getKind(other) and
// same name.
method.getName() = other.getName() and
// same number of parameters.
method.getBody().getNumParameter() = other.getBody().getNumParameter() and
// The types are compared in matchingCallSignature. This is sanity-check that the textual representation of the type-annotations are somewhat similar.
forall(int i | i in [0 .. -1 + method.getBody().getNumParameter()] |
getParameterTypeAnnotation(method, i) = getParameterTypeAnnotation(other, i)
getParameterTypeAnnotation(method, i) = getParameterTypeAnnotation(other, i)
) and
matchingCallSignature(method.getBody().getCallSignature(), other.getBody().getCallSignature())
}
@@ -119,22 +111,19 @@ from ClassOrInterface decl, string name, MethodSignature previous, MethodSignatu
where
previous = decl.getMethod(name) and
unreachable = getOtherMatchingSignatures(previous) and
// If the method is part of inheritance between classes/interfaces, then there can sometimes be reasons for having this pattern.
not exists(decl.getASuperTypeDeclaration().getMethod(name)) and
not exists(ClassOrInterface sub |
not exists(decl.getASuperTypeDeclaration().getMethod(name)) and
not exists(ClassOrInterface sub |
decl = sub.getASuperTypeDeclaration() and
exists(sub.getMethod(name))
) and
// If a later method overload has more type parameters, then that overload can be selected by explicitly declaring the type arguments at the callsite.
// This comparison removes those cases.
unreachable.getBody().getNumTypeParameter() <= previous.getBody().getNumTypeParameter() and
// We always select the first of the overloaded methods.
not exists(MethodSignature later | later = getOtherMatchingSignatures(previous) |
getOverloadIndex(later) < getOverloadIndex(previous)
)
select unreachable,
"This overload of " + name + "() is unreachable, the $@ overload will always be selected.", previous, "previous"
"This overload of " + name + "() is unreachable, the $@ overload will always be selected.",
previous, "previous"

View File

@@ -74,7 +74,8 @@ class CandidateVarAccess extends VarAccess {
* We use this to avoid duplicate alerts about the same underlying cyclic import.
*/
VarAccess getFirstCandidateAccess(ImportDeclaration decl) {
result = min(decl.getASpecifier().getLocal().getVariable().getAnAccess().(CandidateVarAccess) as p
result =
min(decl.getASpecifier().getLocal().getVariable().getAnAccess().(CandidateVarAccess) as p
order by
p.getFirstToken().getIndex()
)
@@ -133,14 +134,15 @@ string pathToModule(Module source, Module destination, int steps) {
steps > 1 and
exists(Module next |
// Only extend the path to one of the potential successors, as we only need one example.
next = min(Module mod |
next =
min(Module mod |
isImportedAtRuntime(source, mod) and
numberOfStepsToModule(mod, destination, steps - 1)
|
mod order by mod.getName()
) and
result = repr(getARuntimeImport(source, next)) + " => " +
pathToModule(next, destination, steps - 1)
result =
repr(getARuntimeImport(source, next)) + " => " + pathToModule(next, destination, steps - 1)
)
)
}

View File

@@ -16,7 +16,6 @@ import javascript
import ExprHasNoEffect
import semmle.javascript.RestrictedLocations
from Expr e
where hasNoEffect(e)
select e.(FirstLineOf), "This expression has no effect."

View File

@@ -200,10 +200,12 @@ where
rightExprDescription = getDescription(right.asExpr(), "an expression") and
leftTypeCount = strictcount(left.getAType()) and
rightTypeCount = strictcount(right.getAType()) and
leftTypeDescription = getTypeDescription("is of type " + leftTypes,
"cannot be of type " + rightTypes, leftTypeCount, rightTypeCount) and
rightTypeDescription = getTypeDescription("of type " + rightTypes,
", which cannot be of type " + leftTypes, rightTypeCount, leftTypeCount)
leftTypeDescription =
getTypeDescription("is of type " + leftTypes, "cannot be of type " + rightTypes, leftTypeCount,
rightTypeCount) and
rightTypeDescription =
getTypeDescription("of type " + rightTypes, ", which cannot be of type " + leftTypes,
rightTypeCount, leftTypeCount)
select left,
leftExprDescription + " " + leftTypeDescription + ", but it is compared to $@ " +
rightTypeDescription + ".", right, rightExprDescription

View File

@@ -43,7 +43,10 @@ predicate isBadPromiseContext(Expr expr) {
or
expr = any(LogicalBinaryExpr e).getLeftOperand()
or
expr = any(UnaryExpr e).getOperand()
exists(UnaryExpr e |
expr = e.getOperand() and
not e instanceof VoidExpr
)
or
expr = any(UpdateExpr e).getOperand()
or

View File

@@ -23,48 +23,40 @@ Expr leftChild(Expr e) {
}
predicate isInConcat(Expr e) {
exists(ParExpr par | isInConcat(par) and par.getExpression() = e)
or
exists(AddExpr a | a.getAnOperand() = e)
exists(ParExpr par | isInConcat(par) and par.getExpression() = e)
or
exists(AddExpr a | a.getAnOperand() = e)
}
class ConcatenationLiteral extends Expr {
ConcatenationLiteral() {
(
this instanceof TemplateLiteral
this instanceof TemplateLiteral
or
this instanceof Literal
)
and isInConcat(this)
) and
isInConcat(this)
}
}
Expr getConcatChild(Expr e) {
result = rightChild(e) or
result = leftChild(e)
result = rightChild(e) or
result = leftChild(e)
}
Expr getConcatParent(Expr e) {
e = getConcatChild(result)
}
Expr getConcatParent(Expr e) { e = getConcatChild(result) }
predicate isWordLike(ConcatenationLiteral lit) {
lit.getStringValue().regexpMatch("(?i).*[a-z]{3,}.*")
lit.getStringValue().regexpMatch("(?i).*[a-z]{3,}.*")
}
class ConcatRoot extends AddExpr {
ConcatRoot() {
not isInConcat(this)
}
ConcatRoot() { not isInConcat(this) }
}
ConcatRoot getAddRoot(AddExpr e) {
result = getConcatParent*(e)
}
ConcatRoot getAddRoot(AddExpr e) { result = getConcatParent*(e) }
predicate hasWordLikeFragment(AddExpr e) {
isWordLike(getConcatChild*(getAddRoot(e)))
}
predicate hasWordLikeFragment(AddExpr e) { isWordLike(getConcatChild*(getAddRoot(e))) }
from AddExpr e, ConcatenationLiteral l, ConcatenationLiteral r, string word
where
@@ -79,7 +71,6 @@ where
word = l.getStringValue().regexpCapture(".* (([-A-Za-z/'\\.:,]*[a-zA-Z]|[0-9]+)[\\.:,!?']*)", 1) and
r.getStringValue().regexpMatch("[a-zA-Z].*") and
not word.regexpMatch(".*[,\\.:].*[a-zA-Z].*[^a-zA-Z]") and
// There must be a constant-string in the concatenation that looks like a word.
hasWordLikeFragment(e)
select l, "This string appears to be missing a space after '" + word + "'."

View File

@@ -91,7 +91,8 @@ private string replaceATypoAndLowerCase(Identifier wrong) {
idPart(wrong, wrongPart, offset)
|
normalized_typos(wrongPart, rightPart, _, _, _, _) and
rightName = wrong.getName().substring(0, offset) + rightPart +
rightName =
wrong.getName().substring(0, offset) + rightPart +
wrong.getName().suffix(offset + wrongPart.length()) and
result = rightName.toLowerCase()
)

View File

@@ -36,7 +36,8 @@ private predicate isBoundInMethod(MethodDeclaration method) {
mod = "react-autobind"
|
thiz.flowsTo(DataFlow::moduleImport(mod).getACall().getArgument(0))
) or
)
or
// heuristic reflective binders
exists(DataFlow::CallNode binder, string calleeName |
(
@@ -92,8 +93,8 @@ private DOM::AttributeDefinition getAnEventHandlerAttribute() {
from MethodDeclaration callback, DOM::AttributeDefinition attribute, ThisExpr unbound
where
attribute = getAnEventHandlerAttribute() and
attribute.getValueNode().analyze().getAValue().(AbstractFunction).getFunction() = callback
.getBody() and
attribute.getValueNode().analyze().getAValue().(AbstractFunction).getFunction() =
callback.getBody() and
unbound.getBinder() = callback.getBody() and
not isBoundInMethod(callback)
select attribute,

View File

@@ -59,7 +59,8 @@ where
not isCallToFunction(cs) and
// conservatively only flag call sites where _all_ callees are illegal
forex(DataFlow::InvokeNode cs2, Function otherCallee |
cs2.getInvokeExpr() = cs.getInvokeExpr() and otherCallee = cs2.getACallee() |
cs2.getInvokeExpr() = cs.getInvokeExpr() and otherCallee = cs2.getACallee()
|
illegalInvocation(cs, otherCallee, _, _)
) and
// require that all callees are known

View File

@@ -35,7 +35,8 @@ predicate guardsAgainstMissingNew(Function f) {
* `true` if `cs` is a `new` expression, and to `false` otherwise.
*/
Function getALikelyCallee(DataFlow::InvokeNode cs, boolean isNew) {
result = min(Function callee, int imprecision |
result =
min(Function callee, int imprecision |
callee = cs.getACallee(imprecision)
|
callee order by imprecision
@@ -84,7 +85,8 @@ predicate whitelistedCall(DataFlow::CallNode call) {
* and start column.
*/
DataFlow::InvokeNode getFirstInvocation(Function f, boolean isNew) {
result = min(DataFlow::InvokeNode invk, string path, int line, int col |
result =
min(DataFlow::InvokeNode invk, string path, int line, int col |
f = getALikelyCallee(invk, isNew) and invk.hasLocationInfo(path, line, col, _, _)
|
invk order by path, line, col

View File

@@ -14,7 +14,8 @@ import javascript
from File f, float n
where
n = avg(Function fun, int toAvg |
n =
avg(Function fun, int toAvg |
fun.getTopLevel().getFile() = f and toAvg = fun.getCyclomaticComplexity()
|
toAvg

View File

@@ -0,0 +1,108 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<include src="ReDoSIntroduction.qhelp" />
<example>
<p>
Consider this use of a regular expression, which removes
all leading and trailing whitespace in a string:
</p>
<sample language="javascript">
text.replace(/^\s+|\s+$/g, ''); // BAD
</sample>
<p>
The sub-expression <code>"\s+$"</code> will match the
whitespace characters in <code>text</code> from left to right, but it
can start matching anywhere within a whitespace sequence. This is
problematic for strings that do <strong>not</strong> end with a whitespace
character. Such a string will force the regular expression engine to
process each whitespace sequence once per whitespace character in the
sequence.
</p>
<p>
This ultimately means that the time cost of trimming a
string is quadratic in the length of the string. So a string like
<code>"a b"</code> will take milliseconds to process, but a similar
string with a million spaces instead of just one will take several
minutes.
</p>
<p>
Avoid this problem by rewriting the regular expression to
not contain the ambiguity about when to start matching whitespace
sequences. For instance, by using a negative look-behind
(<code>/^\s+|(?&lt;!\s)\s+$/g</code>), or just by using the built-in trim
method (<code>text.trim()</code>).
</p>
<p>
Note that the sub-expression <code>"^\s+"</code> is
<strong>not</strong> problematic as the <code>^</code> anchor restricts
when that sub-expression can start matching, and as the regular
expression engine matches from left to right.
</p>
</example>
<example>
<p>
As a similar, but slightly subtler problem, consider the
regular expression that matches lines with numbers, possibly written
using scientific notation:
</p>
<sample language="javascript">
^0\.\d+E?\d+$ // BAD
</sample>
<p>
The problem with this regular expression is in the
sub-expression <code>\d+E?\d+</code> because the second
<code>\d+</code> can start matching digits anywhere after the first
match of the first <code>\d+</code> if there is no <code>E</code> in
the input string.
</p>
<p>
This is problematic for strings that do <strong>not</strong>
end with a digit. Such a string will force the regular expression
engine to process each digit sequence once per digit in the sequence,
again leading to a quadratic time complexity.
</p>
<p>
To make the processing faster, the regular expression
should be rewritten such that the two <code>\d+</code> sub-expressions
do not have overlapping matches: <code>^0\.\d+(E\d+)?$</code>.
</p>
</example>
<include src="ReDoSReferences.qhelp"/>
</qhelp>

View File

@@ -0,0 +1,22 @@
/**
* @name Polynomial regular expression used on uncontrolled data
* @description A regular expression that can require polynomial time
* to match user-provided values may be
* vulnerable to denial-of-service attacks.
* @kind path-problem
* @problem.severity warning
* @precision high
* @id js/polynomial-redos
* @tags security
* external/cwe/cwe-730
* external/cwe/cwe-400
*/
import javascript
import semmle.javascript.security.performance.PolynomialReDoS::PolynomialReDoS
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "This expensive $@ use depends on $@.",
sink.getNode().(Sink).getRegExp(), "regular expression", source.getNode(), "a user-provided value"

View File

@@ -1,70 +1,34 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Some regular expressions take a very long time to match certain input strings to the point where
the time it takes to match a string of length <i>n</i> is proportional to <i>2<sup>n</sup></i>.
Such regular expressions can negatively affect performance, or even allow a malicious user to
perform a Denial of Service ("DoS") attack by crafting an expensive input string for the regular
expression to match.
</p>
<p>
The regular expression engines provided by many popular JavaScript platforms use backtracking
non-deterministic finite automata to implement regular expression matching. While this approach
is space-efficient and allows supporting advanced features like capture groups, it is not
time-efficient in general. The worst-case time complexity of such an automaton can be exponential,
meaning that for strings of a certain shape, increasing the input length by ten characters may
make the automaton about 1000 times slower.
</p>
<p>
Typically, a regular expression is affected by this problem if it contains a repetition of the
form <code>r*</code> or <code>r+</code> where the sub-expression <code>r</code> is ambiguous in
the sense that it can match some string in multiple ways. More information about the precise
circumstances can be found in the references.
</p>
</overview>
<include src="ReDoSIntroduction.qhelp" />
<recommendation>
<p>
Modify the regular expression to remove the ambiguity.
</p>
</recommendation>
<example>
<p>
Consider this regular expression:
</p>
<sample language="javascript">
/^_(__|.)+_$/
</sample>
<p>
Its sub-expression <code>"(__|.)+?"</code> can match the string <code>"__"</code> either by the
first alternative <code>"__"</code> to the left of the <code>"|"</code> operator, or by two
repetitions of the second alternative <code>"."</code> to the right. Thus, a string consisting
of an odd number of underscores followed by some other character will cause the regular
expression engine to run for an exponential amount of time before rejecting the input.
</p>
<p>
This problem can be avoided by rewriting the regular expression to remove the ambiguity between
the two branches of the alternative inside the repetition:
</p>
<sample language="javascript">
/^_(__|[^_])+_$/
</sample>
</example>
<example>
<p>
Consider this regular expression:
</p>
<sample language="javascript">
/^_(__|.)+_$/
</sample>
<p>
Its sub-expression <code>"(__|.)+?"</code> can match the string <code>"__"</code> either by the
first alternative <code>"__"</code> to the left of the <code>"|"</code> operator, or by two
repetitions of the second alternative <code>"."</code> to the right. Thus, a string consisting
of an odd number of underscores followed by some other character will cause the regular
expression engine to run for an exponential amount of time before rejecting the input.
</p>
<p>
This problem can be avoided by rewriting the regular expression to remove the ambiguity between
the two branches of the alternative inside the repetition:
</p>
<sample language="javascript">
/^_(__|[^_])+_$/
</sample>
</example>
<include src="ReDoSReferences.qhelp"/>
<references>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS">Regular expression Denial of Service - ReDoS</a>.
</li>
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/ReDoS">ReDoS</a>.</li>
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Time_complexity">Time complexity</a>.</li>
<li>James Kirrage, Asiri Rathnayake, Hayo Thielecke:
<a href="http://www.cs.bham.ac.uk/~hxt/research/reg-exp-sec.pdf">Static Analysis for Regular Expression Denial-of-Service Attack</a>.
</li>
</references>
</qhelp>

View File

@@ -292,9 +292,7 @@ class EdgeLabel extends TInputSymbol {
* Gets the state before matching `t`.
*/
pragma[inline]
State before(RegExpTerm t) {
result = Match(t, 0)
}
State before(RegExpTerm t) { result = Match(t, 0) }
/**
* Gets a state the NFA may be in after matching `t`.
@@ -337,9 +335,7 @@ predicate delta(State q1, EdgeLabel lbl, State q2) {
)
)
or
exists(RegExpDot dot |
q1 = before(dot) and q2 = after(dot)
|
exists(RegExpDot dot | q1 = before(dot) and q2 = after(dot) |
if dot.getLiteral().isDotAll() then lbl = Any() else lbl = Dot()
)
or
@@ -545,7 +541,8 @@ string intersect(InputSymbol c, InputSymbol d) {
* Gets a character matched by character class `cc`.
*/
string choose(RegExpCharacterClass cc) {
result = min(string c |
result =
min(string c |
exists(RegExpTerm child | child = cc.getAChild() |
c = child.(RegExpConstant).getValue() or
child.(RegExpCharacterRange).isRange(c, _)

View File

@@ -0,0 +1,55 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Some regular expressions take a long time to match certain
input strings to the point where the time it takes to match a string
of length <i>n</i> is proportional to <i>n<sup>k</sup></i> or even
<i>2<sup>n</sup></i>. Such regular expressions can negatively affect
performance, or even allow a malicious user to perform a Denial of
Service ("DoS") attack by crafting an expensive input string for the
regular expression to match.
</p>
<p>
The regular expression engines provided by many popular
JavaScript platforms use backtracking non-deterministic finite
automata to implement regular expression matching. While this approach
is space-efficient and allows supporting advanced features like
capture groups, it is not time-efficient in general. The worst-case
time complexity of such an automaton can be polynomial or even
exponential, meaning that for strings of a certain shape, increasing
the input length by ten characters may make the automaton about 1000
times slower.
</p>
<p>
Typically, a regular expression is affected by this
problem if it contains a repetition of the form <code>r*</code> or
<code>r+</code> where the sub-expression <code>r</code> is ambiguous
in the sense that it can match some string in multiple ways. More
information about the precise circumstances can be found in the
references.
</p>
</overview>
<recommendation>
<p>
Modify the regular expression to remove the ambiguity, or
ensure that the strings matched with the regular expression are short
enough that the time-complexity does not matter.
</p>
</recommendation>
</qhelp>

View File

@@ -0,0 +1,16 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<references>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS">Regular expression Denial of Service - ReDoS</a>.
</li>
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/ReDoS">ReDoS</a>.</li>
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Time_complexity">Time complexity</a>.</li>
<li>James Kirrage, Asiri Rathnayake, Hayo Thielecke:
<a href="http://www.cs.bham.ac.uk/~hxt/research/reg-exp-sec.pdf">Static Analysis for Regular Expression Denial-of-Service Attack</a>.
</li>
</references>
</qhelp>

View File

@@ -14,6 +14,7 @@
import javascript
from RegExpCharEscape rece
where rece.toString() = "\\b"
and rece.isPartOfRegExpLiteral()
where
rece.toString() = "\\b" and
rece.isPartOfRegExpLiteral()
select rece, "Backspace escape in regular expression."

View File

@@ -18,7 +18,8 @@ import javascript
* Indexing is 1-based.
*/
predicate constantInCharacterClass(RegExpCharacterClass recc, int i, RegExpConstant cc, string val) {
cc = rank[i](RegExpConstant cc2, int j |
cc =
rank[i](RegExpConstant cc2, int j |
cc2 = recc.getChild(j) and cc2.isCharacter() and cc2.getValue() = val
|
cc2 order by j

View File

@@ -58,7 +58,8 @@ predicate regExpMatchesString(RegExpTerm t, string s) {
or
// sequences match the concatenation of their elements
exists(RegExpSequence seq | seq = t |
s = concat(int i, RegExpTerm child |
s =
concat(int i, RegExpTerm child |
child = seq.getChild(i)
|
any(string subs | regExpMatchesString(child, subs)) order by i

View File

@@ -73,13 +73,9 @@ abstract class RegExpQuery extends DataFlow::CallNode {
class RegExpTestCall extends DataFlow::MethodCallNode, RegExpQuery {
DataFlow::RegExpCreationNode regexp;
RegExpTestCall() {
this = regexp.getAReference().getAMethodCall("test")
}
RegExpTestCall() { this = regexp.getAReference().getAMethodCall("test") }
override RegExpTerm getRegExp() {
result = regexp.getRoot()
}
override RegExpTerm getRegExp() { result = regexp.getRoot() }
}
/**
@@ -93,9 +89,7 @@ class RegExpSearchCall extends DataFlow::MethodCallNode, RegExpQuery {
regexp.getAReference().flowsTo(getArgument(0))
}
override RegExpTerm getRegExp() {
result = regexp.getRoot()
}
override RegExpTerm getRegExp() { result = regexp.getRoot() }
}
/**
@@ -116,10 +110,12 @@ where
(
call instanceof RegExpTestCall and
not isPossiblyAnchoredOnBothEnds(term) and
message = "This regular expression always matches when used in a test $@, as it can match an empty substring."
message =
"This regular expression always matches when used in a test $@, as it can match an empty substring."
or
call instanceof RegExpSearchCall and
not term.getAChild*() instanceof RegExpDollar and
message = "This regular expression always the matches at index 0 when used $@, as it matches the empty substring."
message =
"This regular expression always the matches at index 0 when used $@, as it matches the empty substring."
)
select term, message, call, "here"

View File

@@ -2,6 +2,7 @@
* Provides predicates for reasoning about regular expressions
* that match URLs and hostname patterns.
*/
import javascript
/**
@@ -39,9 +40,7 @@ predicate isDotLike(RegExpTerm term) {
predicate matchesBeginningOfString(RegExpTerm term) {
term.isRootTerm()
or
exists(RegExpTerm parent |
matchesBeginningOfString(parent)
|
exists(RegExpTerm parent | matchesBeginningOfString(parent) |
term = parent.(RegExpSequence).getChild(0)
or
parent.(RegExpSequence).getChild(0) instanceof RegExpCaret and
@@ -60,7 +59,11 @@ predicate matchesBeginningOfString(RegExpTerm term) {
* `i` is bound to the index of the last child in the top-level domain part.
*/
predicate hasTopLevelDomainEnding(RegExpSequence seq, int i) {
seq.getChild(i).(RegExpConstant).getValue().regexpMatch("(?i)" + RegExpPatterns::commonTLD() + "(:\\d+)?([/?#].*)?") and
seq
.getChild(i)
.(RegExpConstant)
.getValue()
.regexpMatch("(?i)" + RegExpPatterns::commonTLD() + "(:\\d+)?([/?#].*)?") and
isDotLike(seq.getChild(i - 1)) and
not (i = 1 and matchesBeginningOfString(seq))
}
@@ -69,9 +72,7 @@ predicate hasTopLevelDomainEnding(RegExpSequence seq, int i) {
* Holds if the given regular expression term contains top-level domain preceded by a dot,
* such as `.com`.
*/
predicate hasTopLevelDomainEnding(RegExpSequence seq) {
hasTopLevelDomainEnding(seq, _)
}
predicate hasTopLevelDomainEnding(RegExpSequence seq) { hasTopLevelDomainEnding(seq, _) }
/**
* Holds if `term` will always match a hostname, that is, all disjunctions contain

View File

@@ -61,9 +61,12 @@ predicate isIncompleteHostNameRegExpPattern(RegExpTerm regexp, RegExpSequence se
unescapedDot = seq.getChild([0 .. i - 1]).getAChild*() and
unescapedDot != seq.getChild(i - 1) and // Should not be the '.' immediately before the TLD
not hasConsecutiveDots(unescapedDot.getParent()) and
hostname = seq.getChild(i - 2).getRawValue() + seq.getChild(i - 1).getRawValue() + seq.getChild(i).getRawValue()
hostname =
seq.getChild(i - 2).getRawValue() + seq.getChild(i - 1).getRawValue() +
seq.getChild(i).getRawValue()
|
if unescapedDot.getParent() instanceof RegExpQuantifier then (
if unescapedDot.getParent() instanceof RegExpQuantifier
then
// `.*\.example.com` can match `evil.com/?x=.example.com`
//
// This problem only occurs when the pattern is applied against a full URL, not just a hostname/origin.
@@ -73,16 +76,20 @@ predicate isIncompleteHostNameRegExpPattern(RegExpTerm regexp, RegExpSequence se
seq.getChild(0) instanceof RegExpCaret and
not seq.getAChild() instanceof RegExpDollar and
seq.getChild([i .. i + 1]).(RegExpConstant).getValue().regexpMatch(".*[/?#].*") and
msg = "has an unrestricted wildcard '" +
unescapedDot.getParent().(RegExpQuantifier).getRawValue() +
"' which may cause '" + hostname + "' to be matched anywhere in the URL, outside the hostname."
) else (
msg = "has an unescaped '.' before '" + hostname + "', so it might match more hosts than expected."
)
msg =
"has an unrestricted wildcard '" + unescapedDot.getParent().(RegExpQuantifier).getRawValue()
+ "' which may cause '" + hostname +
"' to be matched anywhere in the URL, outside the hostname."
else
msg =
"has an unescaped '.' before '" + hostname +
"', so it might match more hosts than expected."
)
}
from RegExpPatternSource re, RegExpTerm regexp, RegExpSequence hostSequence, string msg, string kind, DataFlow::Node aux
from
RegExpPatternSource re, RegExpTerm regexp, RegExpSequence hostSequence, string msg, string kind,
DataFlow::Node aux
where
regexp = re.getRegExpTerm() and
isIncompleteHostNameRegExpPattern(regexp, hostSequence, msg) and
@@ -96,5 +103,4 @@ where
)
) and
not CharacterEscapes::hasALikelyRegExpPatternMistake(re)
select hostSequence,
"This " + kind + " " + msg, aux, "here"
select hostSequence, "This " + kind + " " + msg, aux, "here"

View File

@@ -20,9 +20,7 @@ class DangerousScheme extends string {
}
/** Gets a data-flow node that checks `nd` against the given `scheme`. */
DataFlow::Node schemeCheck(
DataFlow::Node nd, DangerousScheme scheme
) {
DataFlow::Node schemeCheck(DataFlow::Node nd, DangerousScheme scheme) {
// check of the form `nd.startsWith(scheme)`
exists(StringOps::StartsWith sw | sw = result |
sw.getBaseString() = nd and

View File

@@ -46,9 +46,7 @@ predicate isInteriorAnchor(RegExpAnchor term) {
* Holds if `term` contains an anchor that is not the first or last node
* in its tree, such as `(foo|bar$|baz)`.
*/
predicate containsInteriorAnchor(RegExpTerm term) {
isInteriorAnchor(term.getAChild*())
}
predicate containsInteriorAnchor(RegExpTerm term) { isInteriorAnchor(term.getAChild*()) }
/**
* Holds if `term` starts with a word boundary or lookbehind assertion,
@@ -78,9 +76,7 @@ predicate containsTrailingPseudoAnchor(RegExpSequence term) {
* Holds if `term` is an empty sequence, usually arising from
* literals with a trailing alternative such as `foo|`.
*/
predicate isEmpty(RegExpSequence term) {
term.getNumChild() = 0
}
predicate isEmpty(RegExpSequence term) { term.getNumChild() = 0 }
/**
* Holds if `term` contains a letter constant.
@@ -131,14 +127,14 @@ predicate hasMisleadingAnchorPrecedence(RegExpPatternSource src, string msg) {
(
anchoredTerm = root.getChild(0) and
anchoredTerm.getChild(0) instanceof RegExpCaret and
not containsLeadingPseudoAnchor(root.getChild([ 1 .. root.getNumChild() - 1 ])) and
containsLetters(root.getChild([ 1 .. root.getNumChild() - 1 ])) and
not containsLeadingPseudoAnchor(root.getChild([1 .. root.getNumChild() - 1])) and
containsLetters(root.getChild([1 .. root.getNumChild() - 1])) and
direction = "beginning"
or
anchoredTerm = root.getLastChild() and
anchoredTerm.getLastChild() instanceof RegExpDollar and
not containsTrailingPseudoAnchor(root.getChild([ 0 .. root.getNumChild() - 2 ])) and
containsLetters(root.getChild([ 0 .. root.getNumChild() - 2 ])) and
not containsTrailingPseudoAnchor(root.getChild([0 .. root.getNumChild() - 2])) and
containsLetters(root.getChild([0 .. root.getNumChild() - 2])) and
direction = "end"
) and
// is not used for replace
@@ -146,8 +142,10 @@ predicate hasMisleadingAnchorPrecedence(RegExpPatternSource src, string msg) {
replace.getMethodName() = "replace" and
src.getARegExpObject().flowsTo(replace.getArgument(0))
) and
msg = "Misleading operator precedence. The subexpression '" + anchoredTerm.getRawValue() +
"' is anchored at the " + direction + ", but the other parts of this regular expression are not"
msg =
"Misleading operator precedence. The subexpression '" + anchoredTerm.getRawValue() +
"' is anchored at the " + direction +
", but the other parts of this regular expression are not"
)
}
@@ -181,7 +179,8 @@ predicate isSemiAnchoredHostnameRegExp(RegExpPatternSource src, string msg) {
hasTopLevelDomainEnding(tld, i) and
isFinalRegExpTerm(tld.getChild(i)) and // nothing is matched after the TLD
tld.getChild(0) instanceof RegExpCaret and
msg = "This hostname pattern may match any domain name, as it is missing a '$' or '/' at the end."
msg =
"This hostname pattern may match any domain name, as it is missing a '$' or '/' at the end."
)
}
@@ -214,7 +213,8 @@ predicate isUnanchoredHostnameRegExp(RegExpPatternSource src, string msg) {
name = "match" and exists(mcn.getAPropertyRead())
)
) and
msg = "When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it."
msg =
"When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it."
)
}

View File

@@ -105,13 +105,9 @@ class RegExpPatternMistake extends TRegExpPatternMistake {
*/
class IdentityEscapeInStringMistake extends RegExpPatternMistake, TIdentityEscapeInStringMistake {
RegExpPatternSource src;
string char;
string mistake;
int index;
ASTNode rawStringNode;
IdentityEscapeInStringMistake() {
@@ -130,19 +126,19 @@ class IdentityEscapeInStringMistake extends RegExpPatternMistake, TIdentityEscap
}
/**
* A backspace as '\b' in a regular expression string, indicating
* programmer intent to use the word-boundary assertion '\b'.
*/
* A backspace as '\b' in a regular expression string, indicating
* programmer intent to use the word-boundary assertion '\b'.
*/
class BackspaceInStringMistake extends RegExpPatternMistake, TBackspaceInStringMistake {
RegExpPatternSource src;
int index;
ASTNode rawStringNode;
BackspaceInStringMistake() { this = TBackspaceInStringMistake(src, rawStringNode, index) }
override string getMessage() { result = "'\\b' is a backspace, and not a word-boundary assertion" }
override string getMessage() {
result = "'\\b' is a backspace, and not a word-boundary assertion"
}
override int getIndex() { result = index }

View File

@@ -16,11 +16,16 @@ import javascript
import semmle.javascript.security.dataflow.CommandInjection::CommandInjection
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node highlight, Source sourceNode
from
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node highlight,
Source sourceNode
where
cfg.hasFlowPath(source, sink) and
if cfg.isSinkWithHighlight(sink.getNode(), _)
then cfg.isSinkWithHighlight(sink.getNode(), highlight)
else highlight = sink.getNode() and
(
if cfg.isSinkWithHighlight(sink.getNode(), _)
then cfg.isSinkWithHighlight(sink.getNode(), highlight)
else highlight = sink.getNode()
) and
sourceNode = source.getNode()
select highlight, source, sink, "This command depends on $@.", sourceNode, sourceNode.getSourceType()
select highlight, source, sink, "This command depends on $@.", sourceNode,
sourceNode.getSourceType()

View File

@@ -15,10 +15,8 @@ import javascript
import semmle.javascript.security.dataflow.ExceptionXss::ExceptionXss
import DataFlow::PathGraph
from
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where
cfg.hasFlowPath(source, sink)
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink,
sink.getNode().(Xss::Shared::Sink).getVulnerabilityKind() + " vulnerability due to $@.", source.getNode(),
"user-provided value"
sink.getNode().(Xss::Shared::Sink).getVulnerabilityKind() + " vulnerability due to $@.",
source.getNode(), "user-provided value"

View File

@@ -44,23 +44,17 @@ predicate escapingScheme(string metachar, string regex) {
* A call to `String.prototype.replace` that replaces all instances of a pattern.
*/
class Replacement extends StringReplaceCall {
Replacement() {
isGlobal()
}
Replacement() { isGlobal() }
/**
* Gets the input of this replacement.
*/
DataFlow::Node getInput() {
result = this.getReceiver()
}
DataFlow::Node getInput() { result = this.getReceiver() }
/**
* Gets the output of this replacement.
*/
DataFlow::SourceNode getOutput() {
result = this
}
DataFlow::SourceNode getOutput() { result = this }
/**
* Holds if this replacement escapes `char` using `metachar`.
@@ -93,9 +87,7 @@ class Replacement extends StringReplaceCall {
/**
* Gets the previous replacement in this chain of replacements.
*/
Replacement getPreviousReplacement() {
result.getOutput() = getASimplePredecessor*(getInput())
}
Replacement getPreviousReplacement() { result.getOutput() = getASimplePredecessor*(getInput()) }
/**
* Gets an earlier replacement in this chain of replacements that

View File

@@ -44,9 +44,7 @@ predicate isSimpleCharacterClass(RegExpCharacterClass t) {
}
/** Holds if `t` is an alternation of simple terms. */
predicate isSimpleAlt(RegExpAlt t) {
forall(RegExpTerm ch | ch = t.getAChild() | isSimple(ch))
}
predicate isSimpleAlt(RegExpAlt t) { forall(RegExpTerm ch | ch = t.getAChild() | isSimple(ch)) }
/**
* Holds if `mce` is of the form `x.replace(re, new)`, where `re` is a global

View File

@@ -13,26 +13,20 @@
import javascript
/** Gets a property name of `req` which refers to data usually derived from cookie data. */
string cookieProperty() {
result = "session" or result = "cookies" or result = "user"
}
string cookieProperty() { result = "session" or result = "cookies" or result = "user" }
/** Gets a data flow node that flows to the base of an access to `cookies`, `session`, or `user`. */
private DataFlow::SourceNode nodeLeadingToCookieAccess(DataFlow::TypeBackTracker t) {
t.start() and
exists(DataFlow::PropRead value |
value = result.getAPropertyRead(cookieProperty()).getAPropertyRead() and
// Ignore accesses to values that are part of a CSRF or captcha check
not value.getPropertyName().regexpMatch("(?i).*(csrf|xsrf|captcha).*") and
// Ignore calls like `req.session.save()`
not value = any(DataFlow::InvokeNode call).getCalleeNode()
)
or
exists(DataFlow::TypeBackTracker t2 |
result = nodeLeadingToCookieAccess(t2).backtrack(t2, t)
)
exists(DataFlow::TypeBackTracker t2 | result = nodeLeadingToCookieAccess(t2).backtrack(t2, t))
}
/** Gets a data flow node that flows to the base of an access to `cookies`, `session`, or `user`. */
@@ -52,9 +46,7 @@ private DataFlow::SourceNode getARouteUsingCookies(DataFlow::TypeTracker t) {
t.start() and
isRouteHandlerUsingCookies(result)
or
exists(DataFlow::TypeTracker t2 |
result = getARouteUsingCookies(t2).track(t2, t)
)
exists(DataFlow::TypeTracker t2 | result = getARouteUsingCookies(t2).track(t2, t))
}
/** Gets a data flow node referring to a route handler that uses cookies. */
@@ -113,7 +105,6 @@ from
where
router = setup.getRouter() and
handler = setup.getARouteHandlerExpr() and
// Require that the handler uses cookies and has cookie middleware.
//
// In practice, handlers that use cookies always have the cookie middleware or
@@ -122,10 +113,8 @@ where
// don't trust it to detect the presence of CSRF middleware either.
getARouteUsingCookies().flowsToExpr(handler) and
hasCookieMiddleware(handler, cookie) and
// Only flag the cookie parser registered first.
not hasCookieMiddleware(cookie, _) and
not hasCsrfMiddleware(handler) and
// Only warn for the last handler in a chain.
handler.isLastHandler() and

View File

@@ -1,7 +1,7 @@
/**
* @name Prototype pollution in utility function
* @description Recursively copying properties between objects may cause
accidental modification of a built-in prototype object.
* accidental modification of a built-in prototype object.
* @kind path-problem
* @problem.severity warning
* @precision high
@@ -30,9 +30,7 @@ predicate arePropertiesEnumerated(DataFlow::SourceNode node) {
predicate isEnumeratedPropName(Node node) {
node instanceof EnumeratedPropName
or
exists(Node pred |
isEnumeratedPropName(pred)
|
exists(Node pred | isEnumeratedPropName(pred) |
node = pred.getASuccessor()
or
argumentPassingStep(_, pred, _, node)
@@ -54,7 +52,6 @@ predicate isPotentiallyObjectPrototype(SourceNode node) {
exists(Node base, Node key |
dynamicPropReadStep(base, key, node) and
isEnumeratedPropName(key) and
// Ignore cases where the properties of `base` are enumerated, to avoid FPs
// where the key came from that enumeration (and thus will not return Object.prototype).
// For example, `src[key]` in `for (let key in src) { ... src[key] ... }` will generally
@@ -62,9 +59,7 @@ predicate isPotentiallyObjectPrototype(SourceNode node) {
not arePropertiesEnumerated(base.getALocalSource())
)
or
exists(Node use |
isPotentiallyObjectPrototype(use.getALocalSource())
|
exists(Node use | isPotentiallyObjectPrototype(use.getALocalSource()) |
argumentPassingStep(_, use, _, node)
)
}
@@ -87,7 +82,6 @@ predicate dynamicPropWrite(DataFlow::Node base, DataFlow::Node prop, DataFlow::N
rhs = write.getRhs().flow() and
not exists(prop.getStringValue()) and
not arePropertiesEnumerated(base.getALocalSource()) and
// Prune writes that are unlikely to modify Object.prototype.
// This is mainly for performance, but may block certain results due to
// not tracking out of function returns and into callbacks.
@@ -188,12 +182,7 @@ class PropNameTracking extends DataFlow::Configuration {
override predicate isBarrier(DataFlow::Node node) {
super.isBarrier(node)
or
exists(ConditionGuardNode guard, SsaRefinementNode refinement |
node = DataFlow::ssaDefinitionNode(refinement) and
refinement.getGuard() = guard and
guard.getTest() instanceof VarAccess and
guard.getOutcome() = false
)
node instanceof DataFlow::VarAccessBarrier
}
override predicate isBarrierGuard(DataFlow::BarrierGuardNode node) {
@@ -204,7 +193,8 @@ class PropNameTracking extends DataFlow::Configuration {
node instanceof InstanceOfGuard or
node instanceof TypeofGuard or
node instanceof BlacklistInclusionGuard or
node instanceof WhitelistInclusionGuard
node instanceof WhitelistInclusionGuard or
node instanceof IsPlainObjectGuard
}
}
@@ -379,6 +369,25 @@ class WhitelistInclusionGuard extends DataFlow::LabeledBarrierGuardNode {
}
}
/**
* A check of form `isPlainObject(e)` or similar, which sanitizes the `constructor`
* payload in the true case, since it rejects objects with a non-standard `constructor`
* property.
*/
class IsPlainObjectGuard extends DataFlow::LabeledBarrierGuardNode, DataFlow::CallNode {
IsPlainObjectGuard() {
exists(string name | name = "is-plain-object" or name = "is-extendable" |
this = moduleImport(name).getACall()
)
}
override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel lbl) {
e = getArgument(0).asExpr() and
outcome = true and
lbl = "constructor"
}
}
/**
* Gets a meaningful name for `node` if possible.
*/
@@ -420,9 +429,7 @@ private DataFlow::SourceNode getANodeLeadingToBase(DataFlow::TypeBackTracker t,
isPrototypePollutingAssignment(base, _, _, _) and
result = base.getALocalSource()
or
exists(DataFlow::TypeBackTracker t2 |
result = getANodeLeadingToBase(t2, base).backtrack(t2, t)
)
exists(DataFlow::TypeBackTracker t2 | result = getANodeLeadingToBase(t2, base).backtrack(t2, t))
}
/**

View File

@@ -14,8 +14,9 @@ import PortalExitSource
import PortalEntrySink
from
TaintTracking::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink, Portal p1,
Portal p2, DataFlow::FlowLabel lbl1, DataFlow::FlowLabel lbl2, DataFlow::MidPathNode last
TaintTracking::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink,
Portal p1, Portal p2, DataFlow::FlowLabel lbl1, DataFlow::FlowLabel lbl2,
DataFlow::MidPathNode last
where
cfg = source.getConfiguration() and
last = source.getASuccessor*() and

View File

@@ -11,7 +11,8 @@ import Configurations
import PortalExitSource
import SinkFromAnnotation
from DataFlow::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink,
from
DataFlow::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink,
Portal p, DataFlow::MidPathNode last
where
cfg = source.getConfiguration() and

View File

@@ -11,7 +11,8 @@ import Configurations
import PortalEntrySink
import SourceFromAnnotation
from DataFlow::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink,
from
DataFlow::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink,
Portal p, DataFlow::MidPathNode last
where
cfg = source.getConfiguration() and

View File

@@ -127,7 +127,8 @@ class AdditionalStepSpec extends ExternalData {
exists(string config |
if getField(4) = "" then config = "any configuration" else config = getConfiguration()
|
result = "edge from " + getStartPortal() + " to " + getEndPortal() + ", transforming " +
result =
"edge from " + getStartPortal() + " to " + getEndPortal() + ", transforming " +
getStartFlowLabel() + " into " + getEndFlowLabel() + " for " + config
)
}

View File

@@ -13,7 +13,7 @@ import javascript
import Expressions.ExprHasNoEffect
DataFlow::SourceNode callsArray(DataFlow::TypeBackTracker t, DataFlow::MethodCallNode call) {
isIgnoredPureArrayCall(call) and
isIgnoredPureArrayCall(call) and
t.start() and
result = call.getReceiver().getALocalSource()
or
@@ -39,7 +39,7 @@ predicate isIgnoredPureArrayCall(DataFlow::MethodCallNode call) {
}
from DataFlow::MethodCallNode call
where
where
callsArray(call) instanceof DataFlow::ArrayCreationNode and
not call.getReceiver().asExpr().(ArrayExpr).getSize() = 0
select call, "Result from call to " + call.getMethodName() + " ignored."

View File

@@ -32,7 +32,8 @@ class ValueReturn extends ReturnStmt {
/** Gets the lexically first explicit return statement in function `f`. */
ValueReturn getFirstExplicitReturn(Function f) {
result = min(ValueReturn ret |
result =
min(ValueReturn ret |
ret.getContainer() = f
|
ret order by ret.getLocation().getStartLine(), ret.getLocation().getStartColumn()

View File

@@ -21,40 +21,41 @@ predicate returnsVoid(Function f) {
}
predicate isStub(Function f) {
f.getBody().(BlockStmt).getNumChild() = 0
or
f instanceof ExternalDecl
f.getBody().(BlockStmt).getNumChild() = 0
or
f instanceof ExternalDecl
}
/**
* Holds if `e` is in a syntactic context where it likely is fine that the value of `e` comes from a call to a returnless function.
*/
predicate benignContext(Expr e) {
inVoidContext(e) or
inVoidContext(e)
or
// A return statement is often used to just end the function.
e = any(Function f).getBody()
or
e = any(ReturnStmt r).getExpr()
or
exists(ConditionalExpr cond | cond.getABranch() = e and benignContext(cond))
or
exists(LogicalBinaryExpr bin | bin.getAnOperand() = e and benignContext(bin))
or
exists(ConditionalExpr cond | cond.getABranch() = e and benignContext(cond))
or
exists(LogicalBinaryExpr bin | bin.getAnOperand() = e and benignContext(bin))
or
exists(Expr parent | parent.getUnderlyingValue() = e and benignContext(parent))
or
or
any(VoidExpr voidExpr).getOperand() = e
or
// weeds out calls inside HTML-attributes.
e.getParent().(ExprStmt).getParent() instanceof CodeInAttribute or
e.getParent().(ExprStmt).getParent() instanceof CodeInAttribute
or
// and JSX-attributes.
e = any(JSXAttribute attr).getValue() or
exists(AwaitExpr await | await.getOperand() = e and benignContext(await))
e = any(JSXAttribute attr).getValue()
or
exists(AwaitExpr await | await.getOperand() = e and benignContext(await))
or
// Avoid double reporting with js/trivial-conditional
isExplicitConditional(_, e)
or
or
// Avoid double reporting with js/comparison-between-incompatible-types
any(Comparison binOp).getAnOperand() = e
or
@@ -62,12 +63,14 @@ predicate benignContext(Expr e) {
any(PropAccess ac).getBase() = e
or
// Avoid double-reporting with js/unused-local-variable
exists(VariableDeclarator v | v.getInit() = e and v.getBindingPattern().getVariable() instanceof UnusedLocal)
exists(VariableDeclarator v |
v.getInit() = e and v.getBindingPattern().getVariable() instanceof UnusedLocal
)
or
// Avoid double reporting with js/call-to-non-callable
any(InvokeExpr invoke).getCallee() = e
or
// arguments to Promise.resolve (and promise library variants) are benign.
// arguments to Promise.resolve (and promise library variants) are benign.
e = any(PromiseCreationCall promise).getValue().asExpr()
}
@@ -86,15 +89,13 @@ predicate alwaysThrows(Function f) {
/**
* Holds if the last statement in the function is flagged by the js/useless-expression query.
*/
predicate lastStatementHasNoEffect(Function f) {
hasNoEffect(f.getExit().getAPredecessor())
}
predicate lastStatementHasNoEffect(Function f) { hasNoEffect(f.getExit().getAPredecessor()) }
/**
* Holds if `func` is a callee of `call`, and all possible callees of `call` never return a value.
*/
predicate callToVoidFunction(DataFlow::CallNode call, Function func) {
not call.isIncomplete() and
not call.isIncomplete() and
func = call.getACallee() and
forall(Function f | f = call.getACallee() |
returnsVoid(f) and not isStub(f) and not alwaysThrows(f)
@@ -122,22 +123,20 @@ predicate hasNonVoidCallbackMethod(string name) {
DataFlow::SourceNode array(DataFlow::TypeTracker t) {
t.start() and result instanceof DataFlow::ArrayCreationNode
or
exists (DataFlow::TypeTracker t2 |
result = array(t2).track(t2, t)
)
exists(DataFlow::TypeTracker t2 | result = array(t2).track(t2, t))
}
DataFlow::SourceNode array() { result = array(DataFlow::TypeTracker::end()) }
/**
* Holds if `call` is an Array or Lodash method accepting a callback `func`,
* where the `call` expects a callback that returns an expression,
* but `func` does not return a value.
* where the `call` expects a callback that returns an expression,
* but `func` does not return a value.
*/
predicate voidArrayCallback(DataFlow::CallNode call, Function func) {
hasNonVoidCallbackMethod(call.getCalleeName()) and
exists(int index |
index = min(int i | exists(call.getCallback(i))) and
exists(int index |
index = min(int i | exists(call.getCallback(i))) and
func = call.getCallback(index).getFunction()
) and
returnsVoid(func) and
@@ -151,26 +150,23 @@ predicate voidArrayCallback(DataFlow::CallNode call, Function func) {
}
predicate hasNonVoidReturnType(Function f) {
exists(TypeAnnotation type | type = f.getReturnTypeAnnotation() |
not type.isVoid()
)
exists(TypeAnnotation type | type = f.getReturnTypeAnnotation() | not type.isVoid())
}
/**
* Provides classes for working with various Deferred implementations.
* It is a heuristic. The heuristic assume that a class is a promise defintion
* Provides classes for working with various Deferred implementations.
* It is a heuristic. The heuristic assume that a class is a promise defintion
* if the class is called "Deferred" and the method `resolve` is called on an instance.
*
* Removes some false positives in the js/use-of-returnless-function query.
*
* Removes some false positives in the js/use-of-returnless-function query.
*/
module Deferred {
/**
* An instance of a `Deferred` class.
* An instance of a `Deferred` class.
* For example the result from `new Deferred()` or `new $.Deferred()`.
*/
class DeferredInstance extends DataFlow::NewNode {
// Describes both `new Deferred()`, `new $.Deferred` and other variants.
// Describes both `new Deferred()`, `new $.Deferred` and other variants.
DeferredInstance() { this.getCalleeName() = "Deferred" }
private DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
@@ -179,7 +175,7 @@ module Deferred {
or
exists(DataFlow::TypeTracker t2 | result = ref(t2).track(t2, t))
}
DataFlow::SourceNode ref() { result = ref(DataFlow::TypeTracker::end()) }
}
@@ -188,7 +184,7 @@ module Deferred {
*/
private class DeferredPromiseDefinition extends PromiseDefinition, DeferredInstance {
DeferredPromiseDefinition() {
// hardening of the "Deferred" heuristic: a method call to `resolve`.
// hardening of the "Deferred" heuristic: a method call to `resolve`.
exists(ref().getAMethodCall("resolve"))
}
@@ -210,12 +206,14 @@ module Deferred {
from DataFlow::CallNode call, Function func, string name, string msg
where
(
callToVoidFunction(call, func) and
callToVoidFunction(call, func) and
msg = "the $@ does not return anything, yet the return value is used." and
name = func.describe()
or
voidArrayCallback(call, func) and
msg = "the $@ does not return anything, yet the return value from the call to " + call.getCalleeName() + " is used." and
voidArrayCallback(call, func) and
msg =
"the $@ does not return anything, yet the return value from the call to " +
call.getCalleeName() + " is used." and
name = "callback function"
) and
not benignContext(call.getEnclosingExpr()) and
@@ -224,5 +222,4 @@ where
not oneshotClosure(call) and
not hasNonVoidReturnType(func) and
not call.getEnclosingExpr() instanceof SuperCall
select
call, msg, func, name
select call, msg, func, name

View File

@@ -1,6 +1,6 @@
/**
* Provides predicates for working with useless conditionals.
*/
/**
* Provides predicates for working with useless conditionals.
*/
import javascript
@@ -18,4 +18,4 @@ predicate isExplicitConditional(ASTNode cond, Expr e) {
or
isExplicitConditional(_, cond) and
e = cond.(Expr).getUnderlyingValue().(LogicalBinaryExpr).getAnOperand()
}
}

View File

@@ -0,0 +1 @@
This directory contains [experimental](../../../../docs/experimental.md) CodeQL queries and libraries.

View File

@@ -266,7 +266,8 @@ predicate similarLines(File f, int line) {
}
private predicate similarLinesPerEquivalenceClass(int equivClass, int lines, File f) {
lines = strictsum(SimilarBlock b, int toSum |
lines =
strictsum(SimilarBlock b, int toSum |
(b.sourceFile() = f and b.getEquivalenceClass() = equivClass) and
toSum = b.sourceLines()
|
@@ -278,7 +279,8 @@ pragma[noopt]
private predicate similarLinesCovered(File f, int coveredLines, File otherFile) {
exists(int numLines | numLines = f.getNumberOfLines() |
exists(int coveredApprox |
coveredApprox = strictsum(int num |
coveredApprox =
strictsum(int num |
exists(int equivClass |
similarLinesPerEquivalenceClass(equivClass, num, f) and
similarLinesPerEquivalenceClass(equivClass, num, otherFile) and
@@ -301,7 +303,8 @@ predicate duplicateLines(File f, int line) {
}
private predicate duplicateLinesPerEquivalenceClass(int equivClass, int lines, File f) {
lines = strictsum(DuplicateBlock b, int toSum |
lines =
strictsum(DuplicateBlock b, int toSum |
(b.sourceFile() = f and b.getEquivalenceClass() = equivClass) and
toSum = b.sourceLines()
|
@@ -313,7 +316,8 @@ pragma[noopt]
private predicate duplicateLinesCovered(File f, int coveredLines, File otherFile) {
exists(int numLines | numLines = f.getNumberOfLines() |
exists(int coveredApprox |
coveredApprox = strictsum(int num |
coveredApprox =
strictsum(int num |
exists(int equivClass |
duplicateLinesPerEquivalenceClass(equivClass, num, f) and
duplicateLinesPerEquivalenceClass(equivClass, num, otherFile) and

View File

@@ -48,7 +48,8 @@ class DefectResult extends int {
/** Gets the URL corresponding to the location of this query result. */
string getURL() {
result = "file://" + getFile().getAbsolutePath() + ":" + getStartLine() + ":" + getStartColumn()
+ ":" + getEndLine() + ":" + getEndColumn()
result =
"file://" + getFile().getAbsolutePath() + ":" + getStartLine() + ":" + getStartColumn() + ":" +
getEndLine() + ":" + getEndColumn()
}
}

View File

@@ -66,7 +66,8 @@ class MetricResult extends int {
/** Gets the URL corresponding to the location of this query result. */
string getURL() {
result = "file://" + getFile().getAbsolutePath() + ":" + getStartLine() + ":" + getStartColumn()
+ ":" + getEndLine() + ":" + getEndColumn()
result =
"file://" + getFile().getAbsolutePath() + ":" + getStartLine() + ":" + getStartColumn() + ":" +
getEndLine() + ":" + getEndColumn()
}
}

View File

@@ -86,9 +86,7 @@ SourceNode nodeLeadingToInvocation() {
* Holds if there is a call edge `invoke -> f` between a relevant invocation
* and a relevant function.
*/
predicate relevantCall(RelevantInvoke invoke, RelevantFunction f) {
FlowSteps::calls(invoke, f)
}
predicate relevantCall(RelevantInvoke invoke, RelevantFunction f) { FlowSteps::calls(invoke, f) }
/**
* A call site that can be resolved to a function in the same project.
@@ -105,9 +103,7 @@ class ResolvableCall extends RelevantInvoke {
* A call site that could not be resolved.
*/
class UnresolvableCall extends RelevantInvoke {
UnresolvableCall() {
not this instanceof ResolvableCall
}
UnresolvableCall() { not this instanceof ResolvableCall }
}
/**

View File

@@ -11,8 +11,6 @@
import javascript
import CallGraphQuality
Import unresolvableImport() {
not exists(result.getImportedModule())
}
Import unresolvableImport() { not exists(result.getImportedModule()) }
select projectRoot(), count(unresolvableImport())

View File

@@ -12,7 +12,8 @@ import javascript
import semmle.javascript.meta.ExtractionMetrics::ExtractionMetrics
from File f, string cause
where not extraction_data(f, _, _, _) and cause = "No extraction_data for this file"
or
not extraction_time(f, _,_, _) and cause = "No extraction_time for this file"
select f, cause
where
not extraction_data(f, _, _, _) and cause = "No extraction_data for this file"
or
not extraction_time(f, _, _, _) and cause = "No extraction_time for this file"
select f, cause

View File

@@ -9,4 +9,5 @@
import semmle.javascript.meta.ExtractionMetrics::ExtractionMetrics
from PhaseName phaseName
select phaseName, Aggregated::getCpuTime(phaseName) as CPU_NANO, Aggregated::getWallclockTime(phaseName) as WALLCLOCK_NANO
select phaseName, Aggregated::getCpuTime(phaseName) as CPU_NANO,
Aggregated::getWallclockTime(phaseName) as WALLCLOCK_NANO

View File

@@ -125,7 +125,7 @@ class ASTNode extends @ast_node, Locatable {
* Holds if this is part of an ambient declaration or type annotation in a TypeScript file.
*
* A declaration is ambient if it occurs under a `declare` modifier or is
* an interface declaration, type alias, or type annotation.
* an interface declaration, type alias, type annotation, or type-only import/export declaration.
*
* The TypeScript compiler emits no code for ambient declarations, but they
* can affect name resolution and type checking at compile-time.

View File

@@ -235,7 +235,8 @@ class BasicBlock extends @cfg_node, Locatable {
*/
private int nextDefOrUseAfter(PurelyLocalVariable v, int i, VarDef d) {
defAt(i, v, d) and
result = min(int j |
result =
min(int j |
(defAt(j, v, _) or useAt(j, v, _) or j = length()) and
j > i
)

View File

@@ -57,7 +57,8 @@ module CharacterEscapes {
hasRawStringAndQuote(n, delim, rawStringNode, raw) and
if rawStringNode instanceof RegExpLiteral
then
additionalEscapeChars = Sets::regexpMetaChars() + Sets::regexpAssertionChars() + Sets::regexpCharClassChars() +
additionalEscapeChars =
Sets::regexpMetaChars() + Sets::regexpAssertionChars() + Sets::regexpCharClassChars() +
Sets::regexpBackreferenceChars()
else additionalEscapeChars = "b"
|

View File

@@ -123,13 +123,14 @@ class ClassOrInterface extends @classorinterface, TypeParameterized {
* Anonymous classes and interfaces do not have a canonical name.
*/
TypeName getTypeName() { result.getADefinition() = this }
/**
* Gets the ClassOrInterface corresponding to either a super type or an implemented interface.
*/
ClassOrInterface getASuperTypeDeclaration() {
this.getSuperClass().(VarAccess).getVariable().getADeclaration() = result.getIdentifier() or
this.getASuperInterface().(LocalTypeAccess).getLocalTypeName().getADeclaration() = result.getIdentifier()
this.getASuperInterface().(LocalTypeAccess).getLocalTypeName().getADeclaration() =
result.getIdentifier()
}
}
@@ -334,11 +335,8 @@ class ClassExpr extends @classexpr, ClassDefinition, Expr {
else
if exists(getClassInitializedMember())
then
result = min(ClassInitializedMember m |
m = getClassInitializedMember()
|
m order by m.getIndex()
)
result =
min(ClassInitializedMember m | m = getClassInitializedMember() | m order by m.getIndex())
else result = this
}
@@ -688,7 +686,8 @@ class MethodDeclaration extends MemberDeclaration {
*/
int getOverloadIndex() {
exists(ClassOrInterface type, string name |
this = rank[result + 1](MethodDeclaration method, int i |
this =
rank[result + 1](MethodDeclaration method, int i |
methodDeclaredInType(type, name, i, method)
|
method order by i
@@ -696,7 +695,8 @@ class MethodDeclaration extends MemberDeclaration {
)
or
exists(ClassDefinition type |
this = rank[result + 1](ConstructorDeclaration ctor, int i |
this =
rank[result + 1](ConstructorDeclaration ctor, int i |
ctor = type.getMemberByIndex(i)
|
ctor order by i
@@ -1156,7 +1156,8 @@ class FunctionCallSignature extends @function_call_signature, CallSignature {
/** Gets the index of this function call signature among the function call signatures in the enclosing type. */
int getOverloadIndex() {
exists(ClassOrInterface type | type = getDeclaringType() |
this = rank[result + 1](FunctionCallSignature sig, int i |
this =
rank[result + 1](FunctionCallSignature sig, int i |
sig = type.getMemberByIndex(i)
|
sig order by i
@@ -1186,7 +1187,8 @@ class ConstructorCallSignature extends @constructor_call_signature, CallSignatur
/** Gets the index of this constructor call signature among the constructor call signatures in the enclosing type. */
int getOverloadIndex() {
exists(ClassOrInterface type | type = getDeclaringType() |
this = rank[result + 1](ConstructorCallSignature sig, int i |
this =
rank[result + 1](ConstructorCallSignature sig, int i |
sig = type.getMemberByIndex(i)
|
sig order by i

View File

@@ -267,6 +267,9 @@ module Closure {
result = this
}
override DataFlow::Node getBoundReceiver() { result = getArgument(1) }
override DataFlow::Node getBoundReceiver(DataFlow::Node callback) {
callback = getArgument(0) and
result = getArgument(1)
}
}
}

View File

@@ -284,7 +284,8 @@ private SsaDefinition getAPseudoDefinitionInput(SsaDefinition nd) {
*/
private int nextDefAfter(BasicBlock bb, Variable v, int i, VarDef d) {
bb.defAt(i, v, d) and
result = min(int jj |
result =
min(int jj |
(bb.defAt(jj, v, _) or jj = bb.length()) and
jj > i
)

View File

@@ -76,6 +76,14 @@ class ImportDeclaration extends Stmt, Import, @importdeclaration {
// `import { createServer } from 'http'`
result = DataFlow::destructuredModuleImportNode(this)
}
/** Holds if this is declared with the `type` keyword, so it only imports types. */
predicate isTypeOnly() { hasTypeKeyword(this) }
override predicate isAmbient() {
Stmt.super.isAmbient() or
isTypeOnly()
}
}
/** A literal path expression appearing in an `import` declaration. */
@@ -256,6 +264,14 @@ abstract class ExportDeclaration extends Stmt, @exportdeclaration {
* to module `a` or possibly to some other module from which `a` re-exports.
*/
abstract DataFlow::Node getSourceNode(string name);
/** Holds if is declared with the `type` keyword, so only types are exported. */
predicate isTypeOnly() { hasTypeKeyword(this) }
override predicate isAmbient() {
Stmt.super.isAmbient() or
isTypeOnly()
}
}
/**
@@ -413,6 +429,18 @@ class ExportNamedDeclaration extends ExportDeclaration, @exportnameddeclaration
}
}
/**
* An export declaration with the `type` modifier.
*/
private class TypeOnlyExportDeclaration extends ExportNamedDeclaration {
TypeOnlyExportDeclaration() { isTypeOnly() }
override predicate exportsAs(LexicalName v, string name) {
super.exportsAs(v, name) and
not v instanceof Variable
}
}
/**
* An export specifier in an export declaration.
*
@@ -582,15 +610,10 @@ abstract class ReExportDeclaration extends ExportDeclaration {
*
* Gets the module from which this declaration re-exports.
*/
deprecated
ES2015Module getImportedModule() {
result = getReExportedModule()
}
deprecated ES2015Module getImportedModule() { result = getReExportedModule() }
/** Gets the module from which this declaration re-exports, if it is an ES2015 module. */
ES2015Module getReExportedES2015Module() {
result = getReExportedModule()
}
ES2015Module getReExportedES2015Module() { result = getReExportedModule() }
/** Gets the module from which this declaration re-exports. */
Module getReExportedModule() {
@@ -603,7 +626,8 @@ abstract class ReExportDeclaration extends ExportDeclaration {
* Gets a module in a `node_modules/@types/` folder that matches the imported module name.
*/
private Module resolveFromTypeRoot() {
result.getFile() = min(TypeRootFolder typeRoot |
result.getFile() =
min(TypeRootFolder typeRoot |
|
typeRoot.getModuleFile(getImportedPath().getStringValue())
order by

View File

@@ -43,9 +43,8 @@ abstract class EmailSender extends DataFlow::SourceNode {
*/
private class NodemailerEmailSender extends EmailSender, DataFlow::MethodCallNode {
NodemailerEmailSender() {
this = DataFlow::moduleMember("nodemailer", "createTransport")
.getACall()
.getAMethodCall("sendMail")
this =
DataFlow::moduleMember("nodemailer", "createTransport").getACall().getAMethodCall("sendMail")
}
override DataFlow::Node getPlainTextBody() { result = getOptionArgument(0, "text") }

View File

@@ -253,12 +253,15 @@ class Expr extends @expr, ExprOrStmt, ExprOrType, AST::ValueNode {
DataFlow::Node getExceptionTarget() {
if exists(this.getEnclosingStmt().getEnclosingTryCatchStmt())
then
result = DataFlow::parameterNode(this
result =
DataFlow::parameterNode(this
.getEnclosingStmt()
.getEnclosingTryCatchStmt()
.getACatchClause()
.getAParameter())
else result = any(DataFlow::FunctionNode f | f.getFunction() = this.getContainer()).getExceptionalReturn()
else
result =
any(DataFlow::FunctionNode f | f.getFunction() = this.getContainer()).getExceptionalReturn()
}
}

View File

@@ -183,11 +183,8 @@ class Folder extends Container, @folder {
* HTML files will not be found by this method.
*/
File getJavaScriptFile(string stem) {
result = min(int p, string ext |
p = getFileExtensionPriority(ext)
|
getFile(stem, ext) order by p
)
result =
min(int p, string ext | p = getFileExtensionPriority(ext) | getFile(stem, ext) order by p)
}
/** Gets a subfolder contained in this folder. */

View File

@@ -206,7 +206,8 @@ class Function extends @function, Parameterized, TypeParameterized, StmtContaine
/** Gets the cyclomatic complexity of this function. */
int getCyclomaticComplexity() {
result = 2 +
result =
2 +
sum(Expr nd |
nd.getContainer() = this and nd.isBranch()
|
@@ -420,9 +421,7 @@ class Function extends @function, Parameterized, TypeParameterized, StmtContaine
/**
* Gets the call signature of this function, as determined by the TypeScript compiler, if any.
*/
CallSignatureType getCallSignature() {
declared_function_signature(this, result)
}
CallSignatureType getCallSignature() { declared_function_signature(this, result) }
}
/**

View File

@@ -35,8 +35,10 @@ class CodeGeneratorMarkerComment extends GeneratedCodeMarkerComment {
*/
private predicate codeGeneratorMarkerComment(Comment c, string tool) {
exists(string toolPattern |
toolPattern = "js_of_ocaml|CoffeeScript|LiveScript|dart2js|ANTLR|PEG\\.js|Opal|JSX|jison(?:-lex)?|(?:Microsoft \\(R\\) AutoRest Code Generator)|purs" and
tool = c
toolPattern =
"js_of_ocaml|CoffeeScript|LiveScript|dart2js|ANTLR|PEG\\.js|Opal|JSX|jison(?:-lex)?|(?:Microsoft \\(R\\) AutoRest Code Generator)|purs" and
tool =
c
.getText()
.regexpCapture("(?s)[\\s*]*(?:parser |Code )?[gG]eneratedy? (?:from .*)?by (" +
toolPattern + ")\\b.*", 1)

View File

@@ -5,23 +5,18 @@
import javascript
private import semmle.javascript.dataflow.InferredTypes
deprecated
module GlobalAccessPath {
deprecated module GlobalAccessPath {
/**
* DEPRECATED. Instead use `AccessPath::getAReferenceTo` with the result and parameter reversed.
*/
pragma[inline]
string fromReference(DataFlow::Node node) {
node = AccessPath::getAReferenceTo(result)
}
string fromReference(DataFlow::Node node) { node = AccessPath::getAReferenceTo(result) }
/**
* DEPRECATED. Instead use `AccessPath::getAnAssignmentTo` with the result and parameter reversed.
*/
pragma[inline]
string fromRhs(DataFlow::Node node) {
node = AccessPath::getAnAssignmentTo(result)
}
string fromRhs(DataFlow::Node node) { node = AccessPath::getAnAssignmentTo(result) }
/**
* DEPRECATED. Use `AccessPath::getAReferenceOrAssignmentTo`.
@@ -67,9 +62,7 @@ module AccessPath {
}
/** Holds if this represents the root of the global access path. */
predicate isGlobal() {
this = DataFlow::globalAccessPathRootPseudoNode()
}
predicate isGlobal() { this = DataFlow::globalAccessPathRootPseudoNode() }
}
/**
@@ -212,7 +205,8 @@ module AccessPath {
* ```
*/
private predicate isSelfAssignment(DataFlow::Node rhs) {
fromRhs(rhs, DataFlow::globalAccessPathRootPseudoNode()) = fromReference(rhs, DataFlow::globalAccessPathRootPseudoNode())
fromRhs(rhs, DataFlow::globalAccessPathRootPseudoNode()) =
fromReference(rhs, DataFlow::globalAccessPathRootPseudoNode())
}
/**
@@ -418,8 +412,8 @@ module AccessPath {
*/
pragma[inline]
DataFlow::SourceNode getAnAliasedSourceNode(DataFlow::Node node) {
exists(DataFlow::SourceNode root, string accessPath |
node = AccessPath::getAReferenceTo(root, accessPath) and
exists(DataFlow::SourceNode root, string accessPath |
node = AccessPath::getAReferenceTo(root, accessPath) and
result = AccessPath::getAReferenceTo(root, accessPath)
)
or

View File

@@ -49,9 +49,8 @@ private class DefaultHtmlSanitizerCall extends HtmlSanitizerCall {
callee = LodashUnderscore::member("escape")
or
exists(string name | name = "encode" or name = "encodeNonUTF" |
callee = DataFlow::moduleMember("html-entities", _)
.getAnInstantiation()
.getAPropertyRead(name) or
callee =
DataFlow::moduleMember("html-entities", _).getAnInstantiation().getAPropertyRead(name) or
callee = DataFlow::moduleMember("html-entities", _).getAPropertyRead(name)
)
or

View File

@@ -1,6 +1,7 @@
/**
* Contains classes for recognizing array and string inclusion tests.
*/
private import javascript
/**

View File

@@ -142,5 +142,6 @@ class Locatable extends @locatable {
*/
private class FileLocatable extends File, Locatable {
override Location getLocation() { result = File.super.getLocation() }
override string toString() { result = File.super.toString() }
}

View File

@@ -140,7 +140,8 @@ abstract class Import extends ASTNode {
* Gets a module in a `node_modules/@types/` folder that matches the imported module name.
*/
private Module resolveFromTypeRoot() {
result.getFile() = min(TypeRootFolder typeRoot |
result.getFile() =
min(TypeRootFolder typeRoot |
|
typeRoot.getModuleFile(getImportedPath().getValue())
order by

View File

@@ -243,7 +243,8 @@ class Require extends CallExpr, Import {
private File load(int priority) {
exists(int r | getEnclosingModule().searchRoot(getImportedPath(), _, r) |
result = loadAsFile(this, r, priority - prioritiesPerCandidate() * r) or
result = loadAsDirectory(this, r,
result =
loadAsDirectory(this, r,
priority - (prioritiesPerCandidate() * r + numberOfExtensions() + 1))
)
}

View File

@@ -92,7 +92,7 @@ File resolveMainModule(PackageJSON pkg, int priority) {
not exists(main.resolve()) and
not exists(main.getExtension()) and
exists(int n | n = main.getNumComponent() |
result = tryExtensions(main.resolveUpTo(n-1), main.getComponent(n-1), priority)
result = tryExtensions(main.resolveUpTo(n - 1), main.getComponent(n - 1), priority)
)
)
else result = tryExtensions(pkg.getFile().getParentContainer(), "index", priority)

View File

@@ -128,17 +128,13 @@ private module PromiseFlow {
/**
* Gets the pseudo-field used to describe resolved values in a promise.
*/
string resolveField() {
result = "$PromiseResolveField$"
}
string resolveField() { result = "$PromiseResolveField$" }
/**
* Gets the pseudo-field used to describe rejected values in a promise.
*/
string rejectField() {
result = "$PromiseRejectField$"
}
string rejectField() { result = "$PromiseRejectField$" }
/**
* A flow step describing a promise definition.
*
@@ -146,9 +142,8 @@ private module PromiseFlow {
*/
class PromiseDefitionStep extends DataFlow::AdditionalFlowStep {
PromiseDefinition promise;
PromiseDefitionStep() {
this = promise
}
PromiseDefitionStep() { this = promise }
override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
prop = resolveField() and
@@ -170,15 +165,14 @@ private module PromiseFlow {
succ = this
}
}
/**
* A flow step describing the a Promise.resolve (and similar) call.
*/
class CreationStep extends DataFlow::AdditionalFlowStep {
PromiseCreationCall promise;
CreationStep() {
this = promise
}
CreationStep() { this = promise }
override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
prop = resolveField() and
@@ -194,7 +188,6 @@ private module PromiseFlow {
}
}
/**
* A load step loading the pseudo-field describing that the promise is rejected.
* The rejected value is thrown as a exception.
@@ -202,6 +195,7 @@ private module PromiseFlow {
class AwaitStep extends DataFlow::AdditionalFlowStep {
DataFlow::Node operand;
AwaitExpr await;
AwaitStep() {
this.getEnclosingExpr() = await and
operand.getEnclosingExpr() = await.getOperand()
@@ -222,9 +216,7 @@ private module PromiseFlow {
* A flow step describing the data-flow related to the `.then` method of a promise.
*/
class ThenStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
ThenStep() {
this.getMethodName() = "then"
}
ThenStep() { this.getMethodName() = "then" }
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
prop = resolveField() and
@@ -235,7 +227,7 @@ private module PromiseFlow {
pred = getReceiver() and
succ = getCallback(1).getParameter(0)
}
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
not exists(this.getArgument(1)) and
prop = rejectField() and
@@ -244,17 +236,17 @@ private module PromiseFlow {
or
// read the value of a resolved/rejected promise that is returned
(prop = rejectField() or prop = resolveField()) and
pred = getCallback([0..1]).getAReturn() and
pred = getCallback([0 .. 1]).getAReturn() and
succ = this
}
override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
prop = resolveField() and
pred = getCallback([0..1]).getAReturn() and
pred = getCallback([0 .. 1]).getAReturn() and
succ = this
or
prop = rejectField() and
pred = getCallback([0..1]).getExceptionalReturn() and
pred = getCallback([0 .. 1]).getExceptionalReturn() and
succ = this
}
}
@@ -263,9 +255,7 @@ private module PromiseFlow {
* A flow step describing the data-flow related to the `.catch` method of a promise.
*/
class CatchStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
CatchStep() {
this.getMethodName() = "catch"
}
CatchStep() { this.getMethodName() = "catch" }
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
prop = rejectField() and
@@ -299,9 +289,7 @@ private module PromiseFlow {
* A flow step describing the data-flow related to the `.finally` method of a promise.
*/
class FinallyStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
FinallyStep() {
this.getMethodName() = "finally"
}
FinallyStep() { this.getMethodName() = "finally" }
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
(prop = resolveField() or prop = rejectField()) and
@@ -332,15 +320,13 @@ predicate promiseTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
// from `x` to `Promise.resolve(x)`
pred = succ.(PromiseCreationCall).getValue()
or
exists(DataFlow::MethodCallNode thn |
thn.getMethodName() = "then"
|
exists(DataFlow::MethodCallNode thn | thn.getMethodName() = "then" |
// from `p` to `x` in `p.then(x => ...)`
pred = thn.getReceiver() and
succ = thn.getCallback(0).getParameter(0)
or
// from `v` to `p.then(x => return v)`
pred = thn.getCallback([0..1]).getAReturn() and
pred = thn.getCallback([0 .. 1]).getAReturn() and
succ = thn
)
or
@@ -406,22 +392,19 @@ module Bluebird {
override DataFlow::Node getValue() { result = getArgument(0) }
}
/**
* An aggregated promise produced either by `Promise.all`, `Promise.race` or `Promise.map`.
* An aggregated promise produced either by `Promise.all`, `Promise.race` or `Promise.map`.
*/
class AggregateBluebirdPromiseDefinition extends PromiseCreationCall {
AggregateBluebirdPromiseDefinition() {
exists(string m | m = "all" or m = "race" or m = "map" |
this = bluebird().getAMemberCall(m)
)
exists(string m | m = "all" or m = "race" or m = "map" | this = bluebird().getAMemberCall(m))
}
override DataFlow::Node getValue() {
result = getArgument(0).getALocalSource().(DataFlow::ArrayCreationNode).getAnElement()
}
}
}
/**

View File

@@ -75,25 +75,22 @@ class RegExpTerm extends Locatable, @regexpterm {
/** Gets the regular expression term that is matched (textually) before this one, if any. */
RegExpTerm getPredecessor() {
exists(RegExpSequence seq, int i |
seq.getChild(i) = this and
seq.getChild(i - 1) = result
exists(RegExpTerm parent | parent = getParent() |
result = parent.(RegExpSequence).previousElement(this)
or
not exists(parent.(RegExpSequence).previousElement(this)) and
not parent instanceof RegExpSubPattern and
result = parent.getPredecessor()
)
or
result = getParent().(RegExpTerm).getPredecessor()
}
/** Gets the regular expression term that is matched (textually) after this one, if any. */
RegExpTerm getSuccessor() {
exists(RegExpSequence seq, int i |
seq.getChild(i) = this and
seq.getChild(i + 1) = result
)
or
exists(RegExpTerm parent |
parent = getParent() and
not parent instanceof RegExpSubPattern
|
exists(RegExpTerm parent | parent = getParent() |
result = parent.(RegExpSequence).nextElement(this)
or
not exists(parent.(RegExpSequence).nextElement(this)) and
not parent instanceof RegExpSubPattern and
result = parent.getSuccessor()
)
}
@@ -299,9 +296,7 @@ class RegExpSequence extends RegExpTerm, @regexp_seq {
forall(RegExpTerm child | child = getAChild() | child.isNullable())
}
override string getConstantValue() {
result = getConstantValue(0)
}
override string getConstantValue() { result = getConstantValue(0) }
/**
* Gets the single string matched by the `i`th child and all following children of
@@ -311,7 +306,18 @@ class RegExpSequence extends RegExpTerm, @regexp_seq {
i = getNumChild() and
result = ""
or
result = getChild(i).getConstantValue() + getConstantValue(i+1)
result = getChild(i).getConstantValue() + getConstantValue(i + 1)
}
/** Gets the element preceding `element` in this sequence. */
RegExpTerm previousElement(RegExpTerm element) { element = nextElement(result) }
/** Gets the element following `element` in this sequence. */
RegExpTerm nextElement(RegExpTerm element) {
exists(int i |
element = this.getChild(i) and
result = this.getChild(i + 1)
)
}
}
@@ -506,17 +512,25 @@ class RegExpOpt extends RegExpQuantifier, @regexp_opt {
/**
* A range-quantified term
*
* Example:
* Examples:
*
* ```
* \w{2,4}
* \w{2,}
* \w{2}
* ```
*/
class RegExpRange extends RegExpQuantifier, @regexp_range {
/** Gets the lower bound of the range, if any. */
/** Gets the lower bound of the range. */
int getLowerBound() { rangeQuantifierLowerBound(this, result) }
/** Gets the upper bound of the range, if any. */
/**
* Gets the upper bound of the range, if any.
*
* If there is no upper bound, any number of repetitions is allowed.
* For a term of the form `r{lo}`, both the lower and the upper bound
* are `lo`.
*/
int getUpperBound() { rangeQuantifierUpperBound(this, result) }
override predicate isNullable() {
@@ -816,7 +830,7 @@ class RegExpParseError extends Error, @regexp_parse_error {
}
/**
* Holds if `func` is a method defined on `String.prototype` with name `name`.
* Holds if `func` is a method defined on `String.prototype` with name `name`.
*/
private predicate isNativeStringMethod(Function func, string name) {
exists(ExternalInstanceMemberDecl decl |
@@ -838,9 +852,7 @@ predicate isInterpretedAsRegExp(DataFlow::Node source) {
exists(DataFlow::MethodCallNode mce, string methodName |
mce.getReceiver().analyze().getAType() = TTString() and
mce.getMethodName() = methodName and
not exists(Function func |
func = mce.getACallee()
|
not exists(Function func | func = mce.getACallee() |
not isNativeStringMethod(func, methodName)
)
|

View File

@@ -25,9 +25,8 @@ class FirstLineOf extends Locatable {
if xl = startline
then endcolumn = xc
else
endcolumn = max(int c |
any(Location l).hasLocationInfo(filepath, startline, _, startline, c)
)
endcolumn =
max(int c | any(Location l).hasLocationInfo(filepath, startline, _, startline, c))
)
}
}

View File

@@ -647,9 +647,7 @@ class SsaPhiNode extends SsaPseudoDefinition, TPhi {
result = getDefReachingEndOf(bb, getSourceVariable())
}
override SsaVariable getAnInput() {
result = getInputFromBlock(_)
}
override SsaVariable getAnInput() { result = getInputFromBlock(_) }
override predicate definesAt(ReachableBasicBlock bb, int i, SsaSourceVariable v) {
bb = getBasicBlock() and v = getSourceVariable() and i = -1
@@ -676,9 +674,7 @@ class SsaPhiNode extends SsaPseudoDefinition, TPhi {
* gets that variable.
*/
SsaVariable getRephinedVariable() {
forex(SsaVariable input | input = getAnInput() |
result = getRefinedVariable(input)
)
forex(SsaVariable input | input = getAnInput() | result = getRefinedVariable(input))
}
}

View File

@@ -46,33 +46,27 @@ class DirectEval extends CallExpr {
}
/**
* Flow analysis for `this` expressions inside a function that is called with
* `Array.prototype.map` or a similar Array function that binds `this`.
*
* However, since the function could be invoked in another way, we additionally
* still infer the ordinary abstract value.
* Models `Array.prototype.map` and friends as partial invocations that pass their second
* argument as the receiver to the callback.
*/
private class AnalyzedThisInArrayIterationFunction extends AnalyzedNode, DataFlow::ThisNode {
AnalyzedNode thisSource;
AnalyzedThisInArrayIterationFunction() {
exists(DataFlow::MethodCallNode bindingCall, string name |
private class ArrayIterationCallbackAsPartialInvoke extends DataFlow::PartialInvokeNode::Range,
DataFlow::MethodCallNode {
ArrayIterationCallbackAsPartialInvoke() {
getNumArgument() = 2 and
// Filter out library methods named 'forEach' etc
not DataFlow::moduleImport(_).flowsTo(getReceiver()) and
exists(string name | name = getMethodName() |
name = "filter" or
name = "forEach" or
name = "map" or
name = "some" or
name = "every"
|
name = bindingCall.getMethodName() and
2 = bindingCall.getNumArgument() and
getBinder() = bindingCall.getCallback(0) and
thisSource = bindingCall.getArgument(1)
)
}
override AbstractValue getALocalValue() {
result = thisSource.getALocalValue() or
result = AnalyzedNode.super.getALocalValue()
override DataFlow::Node getBoundReceiver(DataFlow::Node callback) {
callback = getArgument(0) and
result = getArgument(1)
}
}

View File

@@ -55,15 +55,15 @@ class Stmt extends @stmt, ExprOrStmt, Documentable {
}
override predicate isAmbient() { hasDeclareKeyword(this) or getParent().isAmbient() }
/**
* Gets the `try` statement with a catch block containing this statement without
* crossing function boundaries or other `try ` statements with catch blocks.
*/
* Gets the `try` statement with a catch block containing this statement without
* crossing function boundaries or other `try ` statements with catch blocks.
*/
TryStmt getEnclosingTryCatchStmt() {
getParentStmt+() = result.getBody() and
exists(result.getACatchClause()) and
not exists(TryStmt mid | exists(mid.getACatchClause()) |
not exists(TryStmt mid | exists(mid.getACatchClause()) |
getParentStmt+() = mid.getBody() and mid.getParentStmt+() = result.getBody()
)
}

View File

@@ -165,10 +165,16 @@ module StringOps {
StartsWith_Substring() {
astNode.hasOperands(call.asExpr(), substring.asExpr()) and
(call.getMethodName() = "substring" or call.getMethodName() = "substr" or call.getMethodName() = "slice") and
(
call.getMethodName() = "substring" or
call.getMethodName() = "substr" or
call.getMethodName() = "slice"
) and
call.getNumArgument() = 2 and
(
AccessPath::getAnAliasedSourceNode(substring).getAPropertyRead("length").flowsTo(call.getArgument(1))
AccessPath::getAnAliasedSourceNode(substring)
.getAPropertyRead("length")
.flowsTo(call.getArgument(1))
or
substring.getStringValue().length() = call.getArgument(1).asExpr().getIntValue()
)
@@ -502,7 +508,8 @@ module StringOps {
result = getStringValue()
or
not exists(getStringValue()) and
result = strictconcat(StringLiteralLike leaf |
result =
strictconcat(StringLiteralLike leaf |
leaf = getALeaf().asExpr()
|
leaf.getStringValue() order by leaf.getFirstToken().getIndex()

View File

@@ -939,9 +939,7 @@ class PredicateTypeExpr extends @predicatetypeexpr, TypeExpr {
/**
* Holds if this is a type of form `asserts E is T` or `asserts E`.
*/
predicate hasAssertsKeyword() {
hasAssertsKeyword(this)
}
predicate hasAssertsKeyword() { hasAssertsKeyword(this) }
}
/**
@@ -954,9 +952,7 @@ class PredicateTypeExpr extends @predicatetypeexpr, TypeExpr {
* ```
*/
class IsTypeExpr extends PredicateTypeExpr {
IsTypeExpr() {
exists(getPredicateType())
}
IsTypeExpr() { exists(getPredicateType()) }
}
/**
@@ -2312,18 +2308,14 @@ class EnumLiteralType extends TypeReference {
* A type that refers to a type alias.
*/
class TypeAliasReference extends TypeReference {
TypeAliasReference() {
type_alias(this, _)
}
TypeAliasReference() { type_alias(this, _) }
/**
* Gets the type behind the type alias.
*
* For example, for `type B<T> = T[][]`, this maps the type `B<number>` to `number[][]`.
*/
Type getAliasedType() {
type_alias(this, result)
}
Type getAliasedType() { type_alias(this, result) }
}
/**
@@ -2635,9 +2627,7 @@ class CallSignatureType extends @signature_type {
*
* For example, for the signature `(...y: string[])`, this gets the type `string[]`.
*/
PlainArrayType getRestParameterArrayType() {
signature_rest_parameter(this, result)
}
PlainArrayType getRestParameterArrayType() { signature_rest_parameter(this, result) }
}
/**

View File

@@ -189,10 +189,8 @@ class Variable extends @variable, LexicalName {
*/
predicate isCaptured() {
this instanceof GlobalVariable or
getAnAccess().getContainer().getFunctionBoundary() != this
.(LocalVariable)
.getDeclaringContainer()
.getFunctionBoundary()
getAnAccess().getContainer().getFunctionBoundary() !=
this.(LocalVariable).getDeclaringContainer().getFunctionBoundary()
}
/** Holds if there is a declaration of this variable in `tl`. */
@@ -680,7 +678,7 @@ class Parameterized extends @parameterized, Documentable {
}
/**
* A parameter declaration.
* A parameter declaration in a function or catch clause.
*
* Examples:
*
@@ -688,6 +686,9 @@ class Parameterized extends @parameterized, Documentable {
* function f(x, { y: z }, ...rest) { // `x`, `{ y: z }` and `rest` are parameter declarations
* var [ a, b ] = rest;
* var c;
* try {
* x.m();
* } catch(e) {} // `e` is a parameter declaration
* }
* ```
*/
@@ -761,9 +762,7 @@ class Parameter extends BindingPattern {
* function f(x?: number) {}
* ```
*/
predicate isDeclaredOptional() {
isOptionalParameterDeclaration(this)
}
predicate isDeclaredOptional() { isOptionalParameterDeclaration(this) }
}
/**

View File

@@ -18,9 +18,7 @@ import javascript
private class BackwardExploringConfiguration extends DataFlow::Configuration {
DataFlow::Configuration cfg;
BackwardExploringConfiguration() {
this = cfg
}
BackwardExploringConfiguration() { this = cfg }
override predicate isSource(DataFlow::Node node) { any() }

View File

@@ -71,6 +71,7 @@
private import javascript
private import internal.FlowSteps
private import internal.AccessPaths
private import internal.CallGraphs
/**
* A data flow tracking configuration for finding inter-procedural paths from
@@ -620,10 +621,11 @@ private predicate exploratoryFlowStep(
isAdditionalStoreStep(pred, succ, _, cfg) or
isAdditionalLoadStep(pred, succ, _, cfg) or
isAdditionalLoadStoreStep(pred, succ, _, cfg) or
// the following two disjuncts taken together over-approximate flow through
// the following three disjuncts taken together over-approximate flow through
// higher-order calls
callback(pred, succ) or
succ = pred.(DataFlow::FunctionNode).getAParameter()
succ = pred.(DataFlow::FunctionNode).getAParameter() or
exploratoryBoundInvokeStep(pred, succ)
}
/**
@@ -751,7 +753,7 @@ private predicate flowThroughCall(
) {
exists(Function f, DataFlow::ValueNode ret |
ret.asExpr() = f.getAReturnedExpr() and
calls(output, f) and // Do not consider partial calls
(calls(output, f) or callsBound(output, f, _)) and // Do not consider partial calls
reachableFromInput(f, output, input, ret, cfg, summary) and
not isBarrierEdge(cfg, ret, output) and
not isLabeledBarrierEdge(cfg, ret, output, summary.getEndLabel()) and
@@ -761,7 +763,7 @@ private predicate flowThroughCall(
exists(Function f, DataFlow::Node invk, DataFlow::Node ret |
DataFlow::exceptionalFunctionReturnNode(ret, f) and
DataFlow::exceptionalInvocationReturnNode(output, invk.asExpr()) and
calls(invk, f) and
(calls(invk, f) or callsBound(invk, f, _)) and
reachableFromInput(f, invk, input, ret, cfg, summary) and
not isBarrierEdge(cfg, ret, output) and
not isLabeledBarrierEdge(cfg, ret, output, summary.getEndLabel()) and
@@ -1032,6 +1034,15 @@ private predicate flowIntoHigherOrderCall(
succ = cb.getParameter(i) and
summary = oldSummary.append(PathSummary::call())
)
or
exists(
DataFlow::SourceNode cb, DataFlow::FunctionNode f, int i, int boundArgs, PathSummary oldSummary
|
higherOrderCall(pred, cb, i, cfg, oldSummary) and
cb = CallGraph::getABoundFunctionReference(f, boundArgs, false) and
succ = f.getParameter(boundArgs + i) and
summary = oldSummary.append(PathSummary::call())
)
}
/**
@@ -1311,6 +1322,9 @@ class MidPathNode extends PathNode, MkMidNode {
or
// Skip the exceptional return on functions, as this highlights the entire function.
nd = any(DataFlow::FunctionNode f).getExceptionalReturn()
or
// Skip the synthetic 'this' node, as a ThisExpr will be the next node anyway
nd = DataFlow::thisNode(_)
}
}
@@ -1480,3 +1494,18 @@ private class AdditionalBarrierGuardCall extends AdditionalBarrierGuardNode, Dat
override predicate appliesTo(Configuration cfg) { f.appliesTo(cfg) }
}
/**
* A guard node for a variable in a negative condition, such as `x` in `if(!x)`.
* Can be added to a `isBarrier` in a data-flow configuration to block flow through such checks.
*/
class VarAccessBarrier extends DataFlow::Node {
VarAccessBarrier() {
exists(ConditionGuardNode guard, SsaRefinementNode refinement |
this = DataFlow::ssaDefinitionNode(refinement) and
refinement.getGuard() = guard and
guard.getTest() instanceof VarAccess and
guard.getOutcome() = false
)
}
}

View File

@@ -87,10 +87,10 @@ module DataFlow {
Expr asExpr() { this = TValueNode(result) }
/**
* Gets the expression enclosing this data flow node.
* In most cases the result is the same as `asExpr()`, however this method
* additionally the `InvokeExpr` corresponding to reflective calls, and the `Parameter`
* for a `DataFlow::ParameterNode`.
* Gets the expression enclosing this data flow node.
* In most cases the result is the same as `asExpr()`, however this method
* additionally the `InvokeExpr` corresponding to reflective calls, and the `Parameter`
* for a `DataFlow::ParameterNode`.
*/
Expr getEnclosingExpr() {
result = asExpr() or
@@ -147,7 +147,7 @@ module DataFlow {
final FunctionNode getABoundFunctionValue(int boundArgs) {
result = getAFunctionValue() and boundArgs = 0
or
CallGraph::getABoundFunctionReference(result, boundArgs).flowsTo(this)
CallGraph::getABoundFunctionReference(result, boundArgs, _).flowsTo(this)
}
/**
@@ -536,10 +536,10 @@ module DataFlow {
*/
pragma[noinline]
predicate accesses(Node base, string p) { getBase() = base and getPropertyName() = p }
/**
* Holds if this data flow node reads or writes a private field in a class.
*/
*/
predicate isPrivateField() {
getPropertyName().charAt(0) = "#" and getPropertyNameExpr() instanceof Label
}
@@ -860,7 +860,7 @@ module DataFlow {
override Expr getPropertyNameExpr() { none() }
override string getPropertyName() {
exists (int i |
exists(int i |
elt = pattern.getElement(i) and
result = i.toString()
)
@@ -872,7 +872,6 @@ module DataFlow {
*/
private class ImportSpecifierAsPropRead extends PropRead, ValueNode {
override ImportSpecifier astNode;
ImportDeclaration imprt;
ImportSpecifierAsPropRead() {
@@ -894,9 +893,7 @@ module DataFlow {
private class ForOfLvalueAsPropRead extends PropRead {
ForOfStmt stmt;
ForOfLvalueAsPropRead() {
this = lvalueNode(stmt.getLValue())
}
ForOfLvalueAsPropRead() { this = lvalueNode(stmt.getLValue()) }
override Node getBase() { result = stmt.getIterationDomain().flow() }
@@ -1010,7 +1007,7 @@ module DataFlow {
* Gets a pseudo-node representing the root of a global access path.
*/
DataFlow::Node globalAccessPathRootPseudoNode() { result instanceof TGlobalAccessPathRoot }
/**
* Gets a data flow node representing the underlying call performed by the given
* call to `Function.prototype.call` or `Function.prototype.apply`.

View File

@@ -16,9 +16,7 @@ import javascript
private class ForwardExploringConfiguration extends DataFlow::Configuration {
DataFlow::Configuration cfg;
ForwardExploringConfiguration() {
this = cfg
}
ForwardExploringConfiguration() { this = cfg }
override predicate isSink(DataFlow::Node node) { any() }

View File

@@ -134,6 +134,7 @@ class NonPrimitiveType extends InferredType {
* Gets a pretty-printed list of all type tags in alphabetical order.
*/
string ppAllTypeTags() {
result = "boolean, class, date, function, null, number, object, regular expression," +
result =
"boolean, class, date, function, null, number, object, regular expression," +
"string or undefined"
}

View File

@@ -9,7 +9,7 @@ private import semmle.javascript.dependencies.Dependencies
private import internal.CallGraphs
/**
* A data flow node corresponding to an expression.
* A data flow node corresponding to an expression.
*
* Examples:
* ```js
@@ -147,7 +147,8 @@ class InvokeNode extends DataFlow::SourceNode {
*/
ParameterNode getABoundCallbackParameter(int callback, int param) {
exists(int boundArgs |
result = getArgument(callback).getABoundFunctionValue(boundArgs).getParameter(param + boundArgs)
result =
getArgument(callback).getABoundFunctionValue(boundArgs).getParameter(param + boundArgs)
)
}
@@ -548,9 +549,7 @@ class RegExpLiteralNode extends DataFlow::ValueNode, DataFlow::SourceNode {
RegExpTerm getRoot() { result = astNode.getRoot() }
/** Gets the flags of this regular expression literal. */
string getFlags() {
result = astNode.getFlags()
}
string getFlags() { result = astNode.getFlags() }
}
/**
@@ -581,10 +580,9 @@ class ArrayConstructorInvokeNode extends DataFlow::InvokeNode {
/** Gets the initial size of the created array, if it can be determined. */
int getSize() {
if getNumArgument() = 1 then
result = getArgument(0).getIntValue()
else
result = count(getAnElement())
if getNumArgument() = 1
then result = getArgument(0).getIntValue()
else result = count(getAnElement())
}
}
@@ -596,7 +594,7 @@ class ArrayConstructorInvokeNode extends DataFlow::InvokeNode {
* Examples:
* ```js
* ['apple', 'orange'];
* Array('apple', 'orange')
* Array('apple', 'orange')
* new Array('apple', 'orange')
* Array(16)
* new Array(16)
@@ -1036,6 +1034,9 @@ module ClassNode {
kind = MemberKind::of(method) and
result = method.getBody().flow()
)
or
kind = MemberKind::method() and
result = getConstructor().getReceiver().getAPropertySource(name)
}
override FunctionNode getAnInstanceMember(MemberKind kind) {
@@ -1045,6 +1046,9 @@ module ClassNode {
kind = MemberKind::of(method) and
result = method.getBody().flow()
)
or
kind = MemberKind::method() and
result = getConstructor().getReceiver().getAPropertySource()
}
override FunctionNode getStaticMethod(string name) {
@@ -1063,6 +1067,8 @@ module ClassNode {
method.isStatic() and
result = method.getBody().flow()
)
or
result = getAPropertySource()
}
override DataFlow::Node getASuperClassNode() { result = astNode.getSuperClass().flow() }
@@ -1199,6 +1205,13 @@ class PartialInvokeNode extends DataFlow::Node {
PartialInvokeNode() { this = range }
/** Gets a node holding a callback invoked by this partial invocation node. */
DataFlow::Node getACallbackNode() {
isPartialArgument(result, _, _)
or
exists(getBoundReceiver(result))
}
/**
* Holds if `argument` is passed as argument `index` to the function in `callback`.
*/
@@ -1216,7 +1229,14 @@ class PartialInvokeNode extends DataFlow::Node {
/**
* Gets the node holding the receiver to be passed to the bound function, if specified.
*/
DataFlow::Node getBoundReceiver() { result = range.getBoundReceiver() }
DataFlow::Node getBoundReceiver() { result = range.getBoundReceiver(_) }
/**
* Gets the node holding the receiver to be passed to the bound function, if specified.
*/
DataFlow::Node getBoundReceiver(DataFlow::Node callback) {
result = range.getBoundReceiver(callback)
}
}
module PartialInvokeNode {
@@ -1227,7 +1247,9 @@ module PartialInvokeNode {
/**
* Holds if `argument` is passed as argument `index` to the function in `callback`.
*/
predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) { none() }
predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) {
none()
}
/**
* Gets a node referring to a bound version of `callback` with `boundArgs` arguments bound.
@@ -1235,18 +1257,24 @@ module PartialInvokeNode {
DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) { none() }
/**
* DEPRECATED. Use the one-argument version of `getBoundReceiver` instead.
*
* Gets the node holding the receiver to be passed to the bound function, if specified.
*/
DataFlow::Node getBoundReceiver() { none() }
deprecated DataFlow::Node getBoundReceiver() { none() }
/**
* Gets the node holding the receiver to be passed to `callback`.
*/
DataFlow::Node getBoundReceiver(DataFlow::Node callback) { none() }
}
/**
* A partial call through the built-in `Function.prototype.bind`.
*/
* A partial call through the built-in `Function.prototype.bind`.
*/
private class BindPartialCall extends PartialInvokeNode::Range, DataFlow::MethodCallNode {
BindPartialCall() {
getMethodName() = "bind" and
// Avoid overlap with angular.bind and goog.bind
not this = AngularJS::angular().getAMethodCall() and
not getReceiver().accessesGlobal("goog")
@@ -1264,14 +1292,15 @@ module PartialInvokeNode {
result = this
}
override DataFlow::Node getBoundReceiver() {
override DataFlow::Node getBoundReceiver(DataFlow::Node callback) {
callback = getReceiver() and
result = getArgument(0)
}
}
/**
* A partial call through `_.partial`.
*/
* A partial call through `_.partial`.
*/
private class LodashPartialCall extends PartialInvokeNode::Range, DataFlow::CallNode {
LodashPartialCall() { this = LodashUnderscore::member("partial").getACall() }
@@ -1294,9 +1323,7 @@ module PartialInvokeNode {
private class RamdaPartialCall extends PartialInvokeNode::Range, DataFlow::CallNode {
RamdaPartialCall() { this = DataFlow::moduleMember("ramda", "partial").getACall() }
private DataFlow::ArrayCreationNode getArgumentsArray() {
result.flowsTo(getArgument(1))
}
private DataFlow::ArrayCreationNode getArgumentsArray() { result.flowsTo(getArgument(1)) }
override predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) {
callback = getArgument(0) and
@@ -1309,6 +1336,22 @@ module PartialInvokeNode {
result = this
}
}
/**
* A call to `for-in` or `for-own`, passing the context parameter to the target function.
*/
class ForOwnInPartialCall extends PartialInvokeNode::Range, DataFlow::CallNode {
ForOwnInPartialCall() {
exists(string name | name = "for-in" or name = "for-own" |
this = moduleImport(name).getACall()
)
}
override DataFlow::Node getBoundReceiver(DataFlow::Node callback) {
callback = getArgument(1) and
result = getArgument(2)
}
}
}
/**
@@ -1331,17 +1374,13 @@ deprecated class AdditionalPartialInvokeNode = PartialInvokeNode::Range;
* ```
*/
class RegExpConstructorInvokeNode extends DataFlow::InvokeNode {
RegExpConstructorInvokeNode() {
this = DataFlow::globalVarRef("RegExp").getAnInvocation()
}
RegExpConstructorInvokeNode() { this = DataFlow::globalVarRef("RegExp").getAnInvocation() }
/**
* Gets the AST of the regular expression created here, provided that the
* first argument is a string literal.
*/
RegExpTerm getRoot() {
result = getArgument(0).asExpr().(StringLiteral).asRegExp()
}
RegExpTerm getRoot() { result = getArgument(0).asExpr().(StringLiteral).asRegExp() }
/**
* Gets the flags provided in the second argument, or an empty string if no
@@ -1417,13 +1456,9 @@ class RegExpCreationNode extends DataFlow::SourceNode {
t.start() and
result = this
or
exists(DataFlow::TypeTracker t2 |
result = getAReference(t2).track(t2, t)
)
exists(DataFlow::TypeTracker t2 | result = getAReference(t2).track(t2, t))
}
/** Gets a data flow node referring to this regular expression. */
DataFlow::SourceNode getAReference() {
result = getAReference(DataFlow::TypeTracker::end())
}
DataFlow::SourceNode getAReference() { result = getAReference(DataFlow::TypeTracker::end()) }
}

View File

@@ -89,7 +89,8 @@ module TaintTracking {
final override predicate isBarrier(DataFlow::Node node) {
super.isBarrier(node) or
isSanitizer(node)
isSanitizer(node) or
node instanceof DataFlow::VarAccessBarrier
}
final override predicate isBarrierEdge(DataFlow::Node source, DataFlow::Node sink) {
@@ -340,7 +341,7 @@ module TaintTracking {
pred = call.getAnArgument() and
succ = call
or
// `e = arr1.concat(arr2, arr3)`: if any of the `arr` is tainted, then so is `e`.
// `e = arr1.concat(arr2, arr3)`: if any of the `arr` is tainted, then so is `e`.
call.(DataFlow::MethodCallNode).calls(pred, "concat") and
succ = call
or
@@ -573,7 +574,6 @@ module TaintTracking {
succ = this
}
}
/**
* A taint propagating data flow edge arising from calling `String.prototype.match()`.
@@ -582,7 +582,7 @@ module TaintTracking {
StringMatchTaintStep() {
this.getMethodName() = "match" and
this.getNumArgument() = 1 and
this.getArgument(0) .analyze().getAType() = TTRegExp()
this.getArgument(0).analyze().getAType() = TTRegExp()
}
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
@@ -695,9 +695,7 @@ module TaintTracking {
* A taint step through the Node.JS function `util.inspect(..)`.
*/
class UtilInspectTaintStep extends AdditionalTaintStep, DataFlow::InvokeNode {
UtilInspectTaintStep() {
this = DataFlow::moduleImport("util").getAMemberCall("inspect")
}
UtilInspectTaintStep() { this = DataFlow::moduleImport("util").getAMemberCall("inspect") }
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
succ = this and
@@ -718,14 +716,16 @@ module TaintTracking {
mce = astNode and mce.calls(base, m) and firstArg = mce.getArgument(0)
|
// /re/.test(u) or /re/.exec(u)
RegExp::isGenericRegExpSanitizer(RegExp::getRegExpObjectFromNode(base.flow()), sanitizedOutcome) and
RegExp::isGenericRegExpSanitizer(RegExp::getRegExpObjectFromNode(base.flow()),
sanitizedOutcome) and
(m = "test" or m = "exec") and
firstArg = expr
or
// u.match(/re/) or u.match("re")
base = expr and
m = "match" and
RegExp::isGenericRegExpSanitizer(RegExp::getRegExpFromNode(firstArg.flow()), sanitizedOutcome)
RegExp::isGenericRegExpSanitizer(RegExp::getRegExpFromNode(firstArg.flow()),
sanitizedOutcome)
)
or
// m = /re/.exec(u) and similar

View File

@@ -137,10 +137,11 @@ module StepSummary {
summary = LoadStep(prop)
)
) and
if param = fun.getAParameter() then (
if param = fun.getAParameter()
then
// Step from argument to call site.
argumentPassing(succ, pred, fun.getFunction(), param)
) else (
else (
// Step from captured parameter to local call sites
pred = param and
succ = fun.getAnInvocation()

View File

@@ -128,8 +128,9 @@ class AccessPath extends TAccessPath {
exists(AccessPath base, PropertyName name, string rest |
rest = "." + any(string s | name = StaticPropertyName(s))
or
rest = "[" +
any(SsaVariable var | name = DynamicPropertyName(var)).getSourceVariable().getName() + "]"
rest =
"[" + any(SsaVariable var | name = DynamicPropertyName(var)).getSourceVariable().getName() +
"]"
|
result = base.toString() + rest and
this = MkAccessStep(base, name)

View File

@@ -1,6 +1,7 @@
/**
* Internal predicates for computing the call graph.
*/
private import javascript
cached
@@ -24,21 +25,22 @@ module CallGraph {
* from underlying class tracking if the function came from a class or instance.
*/
pragma[nomagic]
private
DataFlow::SourceNode getAFunctionReference(DataFlow::FunctionNode function, int imprecision, DataFlow::TypeTracker t) {
private DataFlow::SourceNode getAFunctionReference(
DataFlow::FunctionNode function, int imprecision, DataFlow::TypeTracker t
) {
t.start() and
exists(Function fun |
fun = function.getFunction() and
fun = getAFunctionValue(result)
|
if isIndefiniteGlobal(result) then
if isIndefiniteGlobal(result)
then
fun.getFile() = result.getFile() and imprecision = 0
or
fun.inExternsFile() and imprecision = 1
or
imprecision = 2
else
imprecision = 0
else imprecision = 0
)
or
imprecision = 0 and
@@ -73,8 +75,9 @@ module CallGraph {
* with `function` as the underlying function.
*/
pragma[nomagic]
private
DataFlow::SourceNode getABoundFunctionReference(DataFlow::FunctionNode function, int boundArgs, DataFlow::TypeTracker t) {
private DataFlow::SourceNode getABoundFunctionReferenceAux(
DataFlow::FunctionNode function, int boundArgs, DataFlow::TypeTracker t
) {
exists(DataFlow::PartialInvokeNode partial, DataFlow::Node callback |
result = partial.getBoundFunction(callback, boundArgs) and
getAFunctionReference(function, 0, t.continue()).flowsTo(callback)
@@ -87,10 +90,12 @@ module CallGraph {
}
pragma[noinline]
private
DataFlow::SourceNode getABoundFunctionReferenceAux(DataFlow::FunctionNode function, int boundArgs, DataFlow::TypeTracker t, DataFlow::StepSummary summary) {
private DataFlow::SourceNode getABoundFunctionReferenceAux(
DataFlow::FunctionNode function, int boundArgs, DataFlow::TypeTracker t,
DataFlow::StepSummary summary
) {
exists(DataFlow::SourceNode prev |
prev = getABoundFunctionReference(function, boundArgs, t) and
prev = getABoundFunctionReferenceAux(function, boundArgs, t) and
DataFlow::StepSummary::step(prev, result, summary)
)
}
@@ -100,8 +105,14 @@ module CallGraph {
* with `function` as the underlying function.
*/
cached
DataFlow::SourceNode getABoundFunctionReference(DataFlow::FunctionNode function, int boundArgs) {
result = getABoundFunctionReference(function, boundArgs, DataFlow::TypeTracker::end())
DataFlow::SourceNode getABoundFunctionReference(
DataFlow::FunctionNode function, int boundArgs, boolean contextDependent
) {
exists(DataFlow::TypeTracker t |
result = getABoundFunctionReferenceAux(function, boundArgs, t) and
t.end() and
contextDependent = t.hasCall()
)
}
/**
@@ -112,8 +123,9 @@ module CallGraph {
* This predicate may be overridden to customize the class hierarchy analysis.
*/
pragma[nomagic]
private
DataFlow::PropRead getAnInstanceMemberAccess(DataFlow::ClassNode cls, string name, DataFlow::TypeTracker t) {
private DataFlow::PropRead getAnInstanceMemberAccess(
DataFlow::ClassNode cls, string name, DataFlow::TypeTracker t
) {
result = cls.getAnInstanceReference(t.continue()).getAPropertyRead(name)
or
exists(DataFlow::ClassNode subclass |

View File

@@ -6,6 +6,7 @@
import javascript
import semmle.javascript.dataflow.Configuration
import semmle.javascript.dataflow.internal.CallGraphs
/**
* Holds if flow should be tracked through properties of `obj`.
@@ -91,6 +92,32 @@ private module CachedSteps {
cached
predicate calls(DataFlow::InvokeNode invk, Function f) { f = invk.getACallee(0) }
/**
* Holds if `invk` may invoke a bound version of `f` with `boundArgs` already bound.
*
* The receiver is assumed to be bound as well, and should not propagate into `f`.
*
* Does not hold for context-dependent call sites, such as callback invocations.
*/
cached
predicate callsBound(DataFlow::InvokeNode invk, Function f, int boundArgs) {
CallGraph::getABoundFunctionReference(f.flow(), boundArgs, false).flowsTo(invk.getCalleeNode())
}
/**
* Holds if `pred` may flow to `succ` through an invocation of a bound function.
*
* Should only be used for graph pruning, as the edge may lead to spurious flow.
*/
cached
predicate exploratoryBoundInvokeStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::InvokeNode invk, DataFlow::FunctionNode f, int i, int boundArgs |
CallGraph::getABoundFunctionReference(f, boundArgs, _).flowsTo(invk.getCalleeNode()) and
pred = invk.getArgument(i) and
succ = f.getParameter(i + boundArgs)
)
}
/**
* Holds if `invk` may invoke `f` indirectly through the given `callback` argument.
*
@@ -99,7 +126,7 @@ private module CachedSteps {
private predicate partiallyCalls(
DataFlow::PartialInvokeNode invk, DataFlow::AnalyzedNode callback, Function f
) {
invk.isPartialArgument(callback, _, _) and
callback = invk.getACallbackNode() and
exists(AbstractFunction callee | callee = callback.getAValue() |
if callback.getAValue().isIndefinite("global")
then f = callee.getFunction() and f.getFile() = invk.getFile()
@@ -135,6 +162,20 @@ private module CachedSteps {
not p.isRestParameter() and
parm = DataFlow::parameterNode(p)
)
or
exists(DataFlow::Node callback |
arg = invk.(DataFlow::PartialInvokeNode).getBoundReceiver(callback) and
partiallyCalls(invk, callback, f) and
parm = DataFlow::thisNode(f)
)
or
exists(int boundArgs, int i, Parameter p |
callsBound(invk, f, boundArgs) and
f.getParameter(boundArgs + i) = p and
not p.isRestParameter() and
arg = invk.getArgument(i) and
parm = DataFlow::parameterNode(p)
)
}
/**
@@ -152,7 +193,7 @@ private module CachedSteps {
*/
cached
predicate returnStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(Function f | calls(succ, f) |
exists(Function f | calls(succ, f) or callsBound(succ, f, _) |
returnExpr(f, pred, _)
or
succ instanceof DataFlow::NewNode and
@@ -161,8 +202,11 @@ private module CachedSteps {
or
exists(InvokeExpr invoke, Function fun |
DataFlow::exceptionalFunctionReturnNode(pred, fun) and
DataFlow::exceptionalInvocationReturnNode(succ, invoke) and
DataFlow::exceptionalInvocationReturnNode(succ, invoke)
|
calls(invoke.flow(), fun)
or
callsBound(invoke.flow(), fun, _)
)
}
@@ -396,8 +440,8 @@ class PathSummary extends TPathSummary {
exists(Boolean hasReturn2, Boolean hasCall2, FlowLabel end2 |
that = MkPathSummary(hasReturn2, hasCall2, end, end2)
|
result = MkPathSummary(hasReturn.booleanOr(hasReturn2), hasCall.booleanOr(hasCall2), start,
end2) and
result =
MkPathSummary(hasReturn.booleanOr(hasReturn2), hasCall.booleanOr(hasCall2), start, end2) and
// avoid constructing invalid paths
not (hasCall = true and hasReturn2 = true)
)
@@ -412,8 +456,8 @@ class PathSummary extends TPathSummary {
exists(Boolean hasReturn2, Boolean hasCall2 |
that = MkPathSummary(hasReturn2, hasCall2, FlowLabel::data(), FlowLabel::data())
|
result = MkPathSummary(hasReturn.booleanOr(hasReturn2), hasCall.booleanOr(hasCall2), start,
end) and
result =
MkPathSummary(hasReturn.booleanOr(hasReturn2), hasCall.booleanOr(hasCall2), start, end) and
// avoid constructing invalid paths
not (hasCall = true and hasReturn2 = true)
)
@@ -430,8 +474,9 @@ class PathSummary extends TPathSummary {
(if hasReturn = true then withReturn = "with" else withReturn = "without") and
(if hasCall = true then withCall = "with" else withCall = "without")
|
result = "path " + withReturn + " return steps and " + withCall + " call steps " +
"transforming " + start + " into " + end
result =
"path " + withReturn + " return steps and " + withCall + " call steps " + "transforming " +
start + " into " + end
)
}
}
@@ -458,4 +503,3 @@ module PathSummary {
*/
PathSummary return() { exists(FlowLabel lbl | result = MkPathSummary(true, false, lbl, lbl)) }
}

View File

@@ -405,10 +405,16 @@ private class AnalyzedClosureGlobalAccessPath extends AnalyzedNode, AnalyzedProp
*/
private class AnalyzedExportNamespaceSpecifier extends AnalyzedPropertyWrite, DataFlow::ValueNode {
override ExportNamespaceSpecifier astNode;
ReExportDeclaration decl;
AnalyzedExportNamespaceSpecifier() {
decl = astNode.getExportDeclaration() and
not decl.isTypeOnly()
}
override predicate writesValue(AbstractValue baseVal, string propName, AbstractValue value) {
baseVal = TAbstractExportsObject(getTopLevel()) and
propName = astNode.getExportedName() and
value = TAbstractExportsObject(astNode.getExportDeclaration().(ReExportDeclaration).getReExportedModule())
value = TAbstractExportsObject(decl.getReExportedModule())
}
}

View File

@@ -274,3 +274,24 @@ private class TypeInferredMethodWithAnalyzedReturnFlow extends CallWithNonLocalA
override AnalyzedFunction getACallee() { result = fun }
}
/**
* Propagates receivers into locally defined callbacks of partial invocations.
*/
private class AnalyzedThisInPartialInvokeCallback extends AnalyzedNode, DataFlow::ThisNode {
DataFlow::PartialInvokeNode call;
DataFlow::Node receiver;
AnalyzedThisInPartialInvokeCallback() {
exists(DataFlow::Node callbackArg |
receiver = call.getBoundReceiver(callbackArg) and
getBinder().flowsTo(callbackArg)
)
}
override AbstractValue getALocalValue() {
result = receiver.analyze().getALocalValue()
or
result = AnalyzedNode.super.getALocalValue()
}
}

View File

@@ -145,8 +145,9 @@ abstract class FrameworkLibraryWithGenericURL extends FrameworkLibraryWithURLReg
override string getAURLRegex() {
exists(string id | id = getId() or id = getAnAlias() |
result = ".*(?:^|/)" + id + "-(" + semverRegex() + ")" + variantRegex() + "\\.js" or
result = ".*/(?:\\w+@)?(" + semverRegex() + ")/(?:(?:dist|js|" + id + ")/)?" + id +
variantRegex() + "\\.js"
result =
".*/(?:\\w+@)?(" + semverRegex() + ")/(?:(?:dist|js|" + id + ")/)?" + id + variantRegex() +
"\\.js"
)
}
}
@@ -158,7 +159,8 @@ abstract class FrameworkLibraryWithGenericURL extends FrameworkLibraryWithURLReg
* We ignore these when identifying frameworks.
*/
private string variantRegex() {
result = "([.-](slim|min|debug|dbg|umd|dev|all|testing|polyfills|" +
result =
"([.-](slim|min|debug|dbg|umd|dev|all|testing|polyfills|" +
"core|compat|more|modern|sandbox|rtl|with-addons|legacy))*"
}
@@ -235,8 +237,8 @@ private predicate jQueryMarkerComment(Comment c, TopLevel tl, string version) {
tl = c.getTopLevel() and
exists(string txt | txt = c.getText() |
// more recent versions use this format
version = txt
.regexpCapture("(?s).*jQuery (?:JavaScript Library )?v(" + versionRegex() + ").*", 1)
version =
txt.regexpCapture("(?s).*jQuery (?:JavaScript Library )?v(" + versionRegex() + ").*", 1)
or
// earlier versions used this format
version = txt.regexpCapture("(?s).*jQuery (" + versionRegex() + ") - New Wave Javascript.*", 1)
@@ -502,7 +504,8 @@ private class Lodash extends FrameworkLibraryWithGenericURL, FrameworkLibraryWit
Lodash() { this = "lodash" }
override string getAMarkerCommentRegex() {
result = "(?s).* (?:lod|Lo-D)ash (<VERSION>)" + "(?: \\(Custom Build\\))? " +
result =
"(?s).* (?:lod|Lo-D)ash (<VERSION>)" + "(?: \\(Custom Build\\))? " +
"<https?://lodash.com/>.*"
}
@@ -842,7 +845,8 @@ private class ApplicationInsightsInstance extends FrameworkLibraryInstance {
string version;
ApplicationInsightsInstance() {
version = this
version =
this
.(TopLevel)
.getFile()
.getAbsolutePath()

View File

@@ -832,7 +832,8 @@ private newtype TAngularScope =
} or
MkIsolateScope(CustomDirective dir) { dir.hasIsolateScope() } or
MkElementScope(DOM::ElementDefinition elem) {
any(DirectiveInstance d | not d.(CustomDirective).hasIsolateScope()).getAMatchingElement() = elem
any(DirectiveInstance d | not d.(CustomDirective).hasIsolateScope()).getAMatchingElement() =
elem
}
/**
@@ -1097,5 +1098,8 @@ private class BindCall extends DataFlow::PartialInvokeNode::Range, DataFlow::Cal
result = this
}
override DataFlow::Node getBoundReceiver() { result = getArgument(0) }
override DataFlow::Node getBoundReceiver(DataFlow::Node callback) {
callback = getArgument(1) and
result = getArgument(0)
}
}

View File

@@ -152,7 +152,8 @@ private class TemplateFieldNgSourceProvider extends NgSourceProvider {
TemplateFieldNgSourceProvider() {
this = directive.getMember("template").asExpr() and
source = this
source =
this
.(ConstantString)
.getStringValue()
.regexpFind(getInterpolatedExpressionPattern(), _, offset)
@@ -211,11 +212,8 @@ abstract class NgToken extends TNgToken {
*/
private int getIndex() {
exists(NgSource src, int start | this.at(src, start) |
start = rank[result + 1](NgToken someToken, int someStart |
someToken.at(src, someStart)
|
someStart
)
start =
rank[result + 1](NgToken someToken, int someStart | someToken.at(src, someStart) | someStart)
)
}
@@ -278,7 +276,8 @@ private module Lexer {
NgOpTokenType() { this = "NgOpTokenType" }
override string getPattern() {
result = concat(string op |
result =
concat(string op |
op = "===" or
op = "!==" or
op = "==" or
@@ -383,7 +382,8 @@ abstract class NgAstNode extends TNode {
*/
language[monotonicAggregates]
string ppChildren() {
result = concat(NgAstNode child, int idx |
result =
concat(NgAstNode child, int idx |
child = getChild(idx) and
not child instanceof Empty
|

View File

@@ -553,10 +553,10 @@ module ClientRequest {
/**
* Gets a reference to an instance of `chrome-remote-interface`.
*
* An instantiation of `chrome-remote-interface` either accepts a callback or returns a promise.
*
* The `isPromise` parameter reflects whether the reference is a promise containing
* an instance of `chrome-remote-interface`, or an instance of `chrome-remote-interface`.
* An instantiation of `chrome-remote-interface` either accepts a callback or returns a promise.
*
* The `isPromise` parameter reflects whether the reference is a promise containing
* an instance of `chrome-remote-interface`, or an instance of `chrome-remote-interface`.
*/
private DataFlow::SourceNode chromeRemoteInterface(DataFlow::TypeTracker t, boolean isPromise) {
t.start() and

Some files were not shown because too many files have changed in this diff Show More