mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
2951 lines
71 KiB
Plaintext
2951 lines
71 KiB
Plaintext
/**
|
|
* Provides classes for working with expressions.
|
|
*/
|
|
|
|
import javascript
|
|
private import semmle.javascript.internal.CachedStages
|
|
private import semmle.javascript.internal.TypeResolution
|
|
|
|
/**
|
|
* A program element that is either an expression or a type annotation.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* x + 1
|
|
* string[]
|
|
* ```
|
|
*/
|
|
class ExprOrType extends @expr_or_type, Documentable {
|
|
/** Gets the statement in which this expression or type appears. */
|
|
Stmt getEnclosingStmt() { enclosing_stmt(this, result) }
|
|
|
|
/** Gets the function in which this expression or type appears, if any. */
|
|
Function getEnclosingFunction() { result = this.getContainer() }
|
|
|
|
/**
|
|
* Gets the JSDoc comment associated with this expression or type or its parent statement, if any.
|
|
*/
|
|
override JSDoc getDocumentation() {
|
|
result = this.getOwnDocumentation()
|
|
or
|
|
// if there is no JSDoc for the expression itself, check the enclosing property or statement
|
|
not exists(this.getOwnDocumentation()) and
|
|
(
|
|
exists(Property prop | prop = this.getParent() | result = prop.getDocumentation())
|
|
or
|
|
exists(MethodDeclaration decl | decl = this.getParent() | result = decl.getDocumentation())
|
|
or
|
|
exists(VariableDeclarator decl | decl = this.getParent() | result = decl.getDocumentation())
|
|
or
|
|
exists(DeclStmt stmt | this = stmt.getDecl(0) | result = stmt.getDocumentation())
|
|
or
|
|
exists(DotExpr dot | this = dot.getProperty() | result = dot.getDocumentation())
|
|
or
|
|
exists(AssignExpr e | this = e.getRhs() | result = e.getDocumentation())
|
|
or
|
|
exists(ParExpr p | this = p.getExpression() | result = p.getDocumentation())
|
|
)
|
|
}
|
|
|
|
/** Gets a JSDoc comment that is immediately before this expression or type (ignoring parentheses). */
|
|
private JSDoc getOwnDocumentation() {
|
|
exists(Token tk | tk = result.getComment().getNextToken() |
|
|
tk = this.getFirstToken()
|
|
or
|
|
exists(Expr p | p.getUnderlyingValue() = this | tk = p.getFirstToken())
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets this expression or type, with any surrounding parentheses removed.
|
|
*
|
|
* Also see `getUnderlyingValue` and `getUnderlyingReference`.
|
|
*/
|
|
ExprOrType stripParens() { result = this }
|
|
|
|
/**
|
|
* Gets the innermost reference that this expression evaluates to, if any.
|
|
*
|
|
* Examples:
|
|
*
|
|
* - a variable or property access: the access itself.
|
|
* - a parenthesized expression `(e)`: the underlying reference of `e`.
|
|
* - a TypeScript type assertion `e as T`: the underlying reference of `e`.
|
|
*
|
|
* Also see `getUnderlyingValue` and `stripParens`.
|
|
*/
|
|
Expr getUnderlyingReference() { none() }
|
|
|
|
/**
|
|
* Gets the innermost expression that this expression evaluates to.
|
|
*
|
|
* Examples:
|
|
*
|
|
* - a parenthesised expression `(e)`: the underlying value of `e`.
|
|
* - a sequence expression `e1, e2`: the underlying value of `e2`.
|
|
* - an assignment expression `v = e`: the underlying value of `e`.
|
|
* - a TypeScript type assertion `e as T`: the underlying value of `e`.
|
|
* - any other expression: the expression itself.
|
|
*
|
|
* Also see `getUnderlyingReference` and `stripParens`.
|
|
*/
|
|
cached
|
|
Expr getUnderlyingValue() { Stages::Ast::ref() and result = this }
|
|
}
|
|
|
|
/**
|
|
* An expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* Math.sqrt(x*x + y*y)
|
|
* ```
|
|
*/
|
|
class Expr extends @expr, ExprOrStmt, ExprOrType, AST::ValueNode {
|
|
/** Gets this expression, with any surrounding parentheses removed. */
|
|
override Expr stripParens() { result = this }
|
|
|
|
/** Gets the constant integer value this expression evaluates to, if any. */
|
|
int getIntValue() { none() }
|
|
|
|
/** Gets the constant string value this expression evaluates to, if any. */
|
|
cached
|
|
string getStringValue() { Stages::Ast::ref() and result = getStringValue(this) }
|
|
|
|
/** Holds if this expression is impure, that is, its evaluation could have side effects. */
|
|
predicate isImpure() { any() }
|
|
|
|
/**
|
|
* Holds if this expression is pure, that is, its evaluation is guaranteed
|
|
* to be side-effect free.
|
|
*/
|
|
predicate isPure() { not this.isImpure() }
|
|
|
|
/**
|
|
* Gets the kind of this expression, which is an integer value representing the expression's
|
|
* node type.
|
|
*
|
|
* _Note_: The mapping from node types to integers is considered an implementation detail
|
|
* and may change between versions of the extractor.
|
|
*/
|
|
int getKind() { exprs(this, result, _, _, _) }
|
|
|
|
override string toString() { exprs(this, _, _, _, result) }
|
|
|
|
/**
|
|
* Gets the expression that is the parent of this expression in the AST, if any.
|
|
*
|
|
* Note that for property names and property values the associated object expression or pattern
|
|
* is returned, skipping the property node itself (which is not an expression).
|
|
*/
|
|
Expr getParentExpr() {
|
|
this = result.getAChildExpr()
|
|
or
|
|
exists(Property prop |
|
|
result = prop.getParent() and
|
|
this = prop.getAChildExpr()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if this expression accesses the global variable `g`, either directly
|
|
* or through the `window` object.
|
|
*/
|
|
predicate accessesGlobal(string g) { this.flow().accessesGlobal(g) }
|
|
|
|
/**
|
|
* Holds if this expression may evaluate to `s`.
|
|
*/
|
|
predicate mayHaveStringValue(string s) { this.flow().mayHaveStringValue(s) }
|
|
|
|
/**
|
|
* Holds if this expression may evaluate to `b`.
|
|
*/
|
|
predicate mayHaveBooleanValue(boolean b) { this.flow().mayHaveBooleanValue(b) }
|
|
|
|
/**
|
|
* Holds if this expression may refer to the initial value of parameter `p`.
|
|
*/
|
|
predicate mayReferToParameter(Parameter p) { DataFlow::parameterNode(p).flowsToExpr(this) }
|
|
|
|
/**
|
|
* Gets the static type of this expression, as determined by the TypeScript type system.
|
|
*
|
|
* Has no result if the expression is in a JavaScript file or in a TypeScript
|
|
* file that was extracted without type information.
|
|
*/
|
|
Type getType() { ast_node_type(this, result) }
|
|
|
|
/**
|
|
* Holds if the syntactic context that the expression appears in relies on the expression
|
|
* being non-null/non-undefined.
|
|
*
|
|
* A context relies on the subexpression being non-null/non-undefined if either...
|
|
*
|
|
* * Using null or undefined would cause a runtime error
|
|
* * Using null or undefined would cause no error due to type conversion, but the
|
|
* behavior in the broader context is sufficiently non-obvious to warrant explicitly
|
|
* converting to ensure that readers understand the intent
|
|
*/
|
|
predicate inNullSensitiveContext() {
|
|
exists(ExprOrStmt ctx |
|
|
// bases cases
|
|
this = ctx.(PropAccess).getBase()
|
|
or
|
|
this = ctx.(IndexExpr).getPropertyNameExpr()
|
|
or
|
|
this = ctx.(InvokeExpr).getCallee()
|
|
or
|
|
this = ctx.(BinaryExpr).getAnOperand() and
|
|
not ctx instanceof LogicalBinaryExpr and // x LOGOP y is fine because of implicit conversion
|
|
not ctx instanceof EqualityTest and // x EQOP y is fine because of implicit conversion and lack thereof
|
|
not ctx.(BitOrExpr).getAnOperand().(NumberLiteral).getIntValue() = 0 and // x | 0 is fine because it's used to convert to numbers
|
|
not ctx.(RShiftExpr).getRightOperand().(NumberLiteral).getIntValue() = 0 and // x >> 0 is fine because it's used to convert to numbers
|
|
not ctx.(URShiftExpr).getRightOperand().(NumberLiteral).getIntValue() = 0 // x >>> 0 is fine because it's used to convert to numbers
|
|
or
|
|
this = ctx.(UnaryExpr).getOperand() and
|
|
not ctx instanceof LogNotExpr and // !x is fine because of implicit conversion
|
|
not ctx instanceof PlusExpr and // +x is fine because of implicit conversion
|
|
not ctx instanceof VoidExpr // void x is fine because it explicitly is for capturing void things
|
|
or
|
|
this = ctx.(UpdateExpr).getOperand()
|
|
or
|
|
this = ctx.(CompoundAssignExpr).getLhs()
|
|
or
|
|
this = ctx.(CompoundAssignExpr).getRhs()
|
|
or
|
|
this = ctx.(AssignExpr).getRhs() and
|
|
ctx.(AssignExpr).getLhs() instanceof DestructuringPattern
|
|
or
|
|
this = ctx.(SpreadElement).getOperand()
|
|
or
|
|
this = ctx.(ForOfStmt).getIterationDomain()
|
|
or
|
|
// recursive cases
|
|
this = ctx.(ParExpr).getExpression() and
|
|
ctx.(ParExpr).inNullSensitiveContext()
|
|
or
|
|
this = ctx.(SeqExpr).getLastOperand() and
|
|
ctx.(SeqExpr).inNullSensitiveContext()
|
|
or
|
|
this = ctx.(LogicalBinaryExpr).getRightOperand() and
|
|
ctx.(LogicalBinaryExpr).inNullSensitiveContext()
|
|
or
|
|
this = ctx.(ConditionalExpr).getABranch() and
|
|
ctx.(ConditionalExpr).inNullSensitiveContext()
|
|
)
|
|
}
|
|
|
|
pragma[inline]
|
|
private Stmt getRawEnclosingStmt(Expr e) {
|
|
// For performance reasons, we need the enclosing statement without overrides
|
|
enclosing_stmt(e, result)
|
|
}
|
|
|
|
/**
|
|
* Gets the data-flow node where exceptions thrown by this expression will
|
|
* propagate if this expression causes an exception to be thrown.
|
|
*/
|
|
pragma[inline]
|
|
DataFlow::Node getExceptionTarget() {
|
|
result = getCatchParameterFromStmt(this.getRawEnclosingStmt(this))
|
|
or
|
|
not exists(getCatchParameterFromStmt(this.getRawEnclosingStmt(this))) and
|
|
result =
|
|
any(DataFlow::FunctionNode f | f.getFunction() = this.getContainer()).getExceptionalReturn()
|
|
}
|
|
}
|
|
|
|
cached
|
|
private DataFlow::Node getCatchParameterFromStmt(Stmt stmt) {
|
|
Stages::DataFlowStage::ref() and
|
|
result =
|
|
DataFlow::parameterNode(stmt.getEnclosingTryCatchStmt().getACatchClause().getAParameter())
|
|
}
|
|
|
|
/**
|
|
* An identifier.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* x
|
|
* ```
|
|
*/
|
|
class Identifier extends @identifier, ExprOrType {
|
|
/** Gets the name of this identifier. */
|
|
cached
|
|
string getName() {
|
|
Stages::Ast::ref() and
|
|
literals(result, _, this)
|
|
}
|
|
|
|
override string getAPrimaryQlClass() { result = "Identifier" }
|
|
}
|
|
|
|
/**
|
|
* A statement or property label, that is, an identifier that
|
|
* does not refer to a variable.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* outer: // `outer` is a statement label
|
|
* for(i=0; i<a.length; ++i) { // `length` is a property label
|
|
* // ...
|
|
* }
|
|
* ```
|
|
*/
|
|
class Label extends @label, Identifier, Expr {
|
|
override predicate isImpure() { none() }
|
|
|
|
override string getAPrimaryQlClass() { result = "Label" }
|
|
}
|
|
|
|
/**
|
|
* A literal.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* null // null literal
|
|
* true // Boolean literal
|
|
* 2 // number literal
|
|
* 3n // BigInt literal
|
|
* "hello" // string literal
|
|
* /jsx?/ // regular-expression literal
|
|
* ```
|
|
*/
|
|
class Literal extends @literal, Expr {
|
|
/** Gets the value of this literal, as a string. */
|
|
string getValue() { literals(result, _, this) }
|
|
|
|
/**
|
|
* Gets the raw source text of this literal, including quotes for
|
|
* string literals.
|
|
*/
|
|
string getRawValue() { literals(_, result, this) }
|
|
|
|
override predicate isImpure() { none() }
|
|
|
|
override string getAPrimaryQlClass() { result = "Literal" }
|
|
}
|
|
|
|
/**
|
|
* A parenthesized expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* (function() { console.log("Hello, world!"); }())
|
|
* ```
|
|
*/
|
|
class ParExpr extends @par_expr, Expr {
|
|
/** Gets the expression within parentheses. */
|
|
Expr getExpression() { result = this.getChildExpr(0) }
|
|
|
|
override Expr stripParens() { result = this.getExpression().stripParens() }
|
|
|
|
override int getIntValue() { result = this.getExpression().getIntValue() }
|
|
|
|
override predicate isImpure() { this.getExpression().isImpure() }
|
|
|
|
override Expr getUnderlyingValue() { result = this.getExpression().getUnderlyingValue() }
|
|
|
|
override Expr getUnderlyingReference() { result = this.getExpression().getUnderlyingReference() }
|
|
|
|
override string getAPrimaryQlClass() { result = "ParExpr" }
|
|
}
|
|
|
|
/**
|
|
* A `null` literal.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* null
|
|
* ```
|
|
*/
|
|
class NullLiteral extends @null_literal, Literal { }
|
|
|
|
/**
|
|
* A Boolean literal, that is, either `true` or `false`.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* true
|
|
* false
|
|
* ```
|
|
*/
|
|
class BooleanLiteral extends @boolean_literal, Literal {
|
|
/** Gets the value of this literal. */
|
|
boolean getBoolValue() { if this.getRawValue() = "true" then result = true else result = false }
|
|
}
|
|
|
|
/**
|
|
* A numeric literal.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* 0b101
|
|
* 0o31
|
|
* 25
|
|
* 0xffff
|
|
* 6.626e-34
|
|
* ```
|
|
*/
|
|
class NumberLiteral extends @number_literal, Literal {
|
|
/** Gets the integer value of this literal. */
|
|
override int getIntValue() { result = this.getValue().toInt() }
|
|
|
|
/** Gets the floating point value of this literal. */
|
|
float getFloatValue() { result = this.getValue().toFloat() }
|
|
}
|
|
|
|
/**
|
|
* A BigInt literal.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* 9007199254740991n
|
|
* ```
|
|
*/
|
|
class BigIntLiteral extends @bigint_literal, Literal {
|
|
/**
|
|
* Gets the integer value of this literal if it can be represented
|
|
* as a QL integer value.
|
|
*/
|
|
override int getIntValue() { result = this.getValue().toInt() }
|
|
|
|
/**
|
|
* Gets the floating point value of this literal if it can be represented
|
|
* as a QL floating point value.
|
|
*/
|
|
float getFloatValue() { result = this.getValue().toFloat() }
|
|
}
|
|
|
|
/**
|
|
* A string literal, either single-quoted or double-quoted.
|
|
*
|
|
* Note that template literals are represented by a different class, `TemplateLiteral`.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* "Hello, world!\n"
|
|
* 'Hello, "world"!'
|
|
* ```
|
|
*/
|
|
class StringLiteral extends @string_literal, Literal {
|
|
/**
|
|
* Gets the value of this string literal parsed as a regular expression, if possible.
|
|
*
|
|
* All string literals have an associated regular expression tree, provided they can
|
|
* be parsed without syntax errors.
|
|
*/
|
|
RegExpTerm asRegExp() { this = result.getParent() }
|
|
}
|
|
|
|
/**
|
|
* A regular expression literal.
|
|
*
|
|
* Note that this class does not cover regular expressions constructed by calling the built-in
|
|
* `RegExp` function.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* /(?i)ab*c(d|e)$/
|
|
* ```
|
|
*/
|
|
class RegExpLiteral extends @regexp_literal, Literal, RegExpParent {
|
|
/** Gets the root term of this regular expression literal. */
|
|
RegExpTerm getRoot() { this = result.getParent() }
|
|
|
|
/** Gets the flags of this regular expression. */
|
|
string getFlags() { result = this.getValue().regexpCapture(".*/(\\w*)$", 1) }
|
|
|
|
/** Holds if this regular expression has an `m` flag. */
|
|
predicate isMultiline() { RegExp::isMultiline(this.getFlags()) }
|
|
|
|
/** Holds if this regular expression has a `g` flag. */
|
|
predicate isGlobal() { RegExp::isGlobal(this.getFlags()) }
|
|
|
|
/** Holds if this regular expression has an `i` flag. */
|
|
predicate isIgnoreCase() { RegExp::isIgnoreCase(this.getFlags()) }
|
|
|
|
/** Holds if this regular expression has an `s` flag. */
|
|
predicate isDotAll() { RegExp::isDotAll(this.getFlags()) }
|
|
|
|
/** Holds if this regular expression has an `v` flag. */
|
|
predicate isUnicodeSets() { RegExp::isUnicodeSets(this.getFlags()) }
|
|
|
|
override string getAPrimaryQlClass() { result = "RegExpLiteral" }
|
|
}
|
|
|
|
/**
|
|
* A `this` expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* this
|
|
* ```
|
|
*/
|
|
class ThisExpr extends @this_expr, Expr {
|
|
override predicate isImpure() { none() }
|
|
|
|
/**
|
|
* Gets the function whose `this` binding this expression refers to,
|
|
* which is the nearest enclosing non-arrow function.
|
|
*/
|
|
Function getBinder() { result = this.getEnclosingFunction().getThisBinder() }
|
|
|
|
/**
|
|
* Gets the function or top-level whose `this` binding this expression refers to,
|
|
* which is the nearest enclosing non-arrow function or top-level.
|
|
*/
|
|
StmtContainer getBindingContainer() {
|
|
result = this.getContainer().(Function).getThisBindingContainer()
|
|
or
|
|
result = this.getContainer().(TopLevel)
|
|
}
|
|
|
|
override string getAPrimaryQlClass() { result = "ThisExpr" }
|
|
}
|
|
|
|
/**
|
|
* An array literal.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* [ 1, , [ 3, 4 ] ]
|
|
* ```
|
|
*/
|
|
class ArrayExpr extends @array_expr, Expr {
|
|
/** Gets the `i`th element of this array literal. */
|
|
Expr getElement(int i) { result = this.getChildExpr(i) }
|
|
|
|
/** Gets an element of this array literal. */
|
|
Expr getAnElement() { result = this.getAChildExpr() }
|
|
|
|
/** Gets the number of elements in this array literal. */
|
|
int getSize() { array_size(this, result) }
|
|
|
|
/**
|
|
* Holds if this array literal includes a trailing comma after the
|
|
* last element.
|
|
*/
|
|
predicate hasTrailingComma() { this.getLastToken().getPreviousToken().getValue() = "," }
|
|
|
|
/** Holds if the `i`th element of this array literal is omitted. */
|
|
predicate elementIsOmitted(int i) {
|
|
i in [0 .. this.getSize() - 1] and
|
|
not exists(this.getElement(i))
|
|
}
|
|
|
|
/** Holds if this array literal has an omitted element. */
|
|
predicate hasOmittedElement() { this.elementIsOmitted(_) }
|
|
|
|
override predicate isImpure() { this.getAnElement().isImpure() }
|
|
|
|
override string getAPrimaryQlClass() { result = "ArrayExpr" }
|
|
}
|
|
|
|
/**
|
|
* An object literal, containing zero or more property definitions.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* var p = { // object literal containing five property definitions
|
|
* x: 1,
|
|
* y: 1,
|
|
* diag: function() { return this.x - this.y; },
|
|
* get area() { return this.x * this.y; },
|
|
* set area(a) { this.x = Math.sqrt(a); this.y = Math.sqrt(a); }
|
|
* };
|
|
* ```
|
|
*/
|
|
class ObjectExpr extends @obj_expr, Expr {
|
|
/** Gets the `i`th property in this object literal. */
|
|
Property getProperty(int i) { properties(result, this, i, _, _) }
|
|
|
|
/** Gets a property in this object literal. */
|
|
Property getAProperty() { result = this.getProperty(_) }
|
|
|
|
/** Gets the number of properties in this object literal. */
|
|
int getNumProperty() { result = count(this.getAProperty()) }
|
|
|
|
/** Gets the property with the given name, if any. */
|
|
Property getPropertyByName(string name) {
|
|
result = this.getAProperty() and
|
|
result.getName() = name
|
|
}
|
|
|
|
/**
|
|
* Holds if this object literal includes a trailing comma after the
|
|
* last property.
|
|
*/
|
|
predicate hasTrailingComma() { this.getLastToken().getPreviousToken().getValue() = "," }
|
|
|
|
override predicate isImpure() { this.getAProperty().isImpure() }
|
|
|
|
override string getAPrimaryQlClass() { result = "ObjectExpr" }
|
|
}
|
|
|
|
/**
|
|
* A property definition in an object literal, which may be either
|
|
* a value property, a property getter, or a property setter.
|
|
*
|
|
* Note that property definitions are not expressions.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* var p = {
|
|
* x: 1, // value property
|
|
* y: 1, // value property
|
|
* diag: function() { return this.x - this.y; }, // value property
|
|
* get area() { return this.x * this.y; }, // property getter
|
|
* set area(a) { this.x = Math.sqrt(a); this.y = Math.sqrt(a); } // property setter
|
|
* }
|
|
* ```
|
|
*/
|
|
class Property extends @property, Documentable {
|
|
Property() {
|
|
// filter out property patterns and JSX attributes
|
|
exists(ObjectExpr obj | properties(this, obj, _, _, _))
|
|
}
|
|
|
|
/**
|
|
* Gets the expression specifying the name of this property.
|
|
*
|
|
* For normal properties, this is either an identifier, a string literal, or a
|
|
* numeric literal; for computed properties it can be an arbitrary expression;
|
|
* for spread properties, it is not defined.
|
|
*/
|
|
Expr getNameExpr() { result = this.getChildExpr(0) }
|
|
|
|
/** Gets the expression specifying the initial value of this property. */
|
|
Expr getInit() { result = this.getChildExpr(1) }
|
|
|
|
/** Gets the name of this property. */
|
|
string getName() {
|
|
not this.isComputed() and result = this.getNameExpr().(Identifier).getName()
|
|
or
|
|
result = this.getNameExpr().(Literal).getValue()
|
|
}
|
|
|
|
/** Holds if the name of this property is computed. */
|
|
predicate isComputed() { is_computed(this) }
|
|
|
|
/** Holds if this property is defined using method syntax. */
|
|
predicate isMethod() { is_method(this) }
|
|
|
|
/** Holds if this property is defined using shorthand syntax. */
|
|
predicate isShorthand() { this.getNameExpr().getLocation() = this.getInit().getLocation() }
|
|
|
|
/** Gets the object literal this property belongs to. */
|
|
ObjectExpr getObjectExpr() { properties(this, result, _, _, _) }
|
|
|
|
/** Gets the (0-based) index at which this property appears in its enclosing literal. */
|
|
int getIndex() { this = this.getObjectExpr().getProperty(result) }
|
|
|
|
/**
|
|
* Holds if this property is impure, that is, the evaluation of its name or
|
|
* its initializer expression could have side effects.
|
|
*/
|
|
predicate isImpure() {
|
|
this.isComputed() and this.getNameExpr().isImpure()
|
|
or
|
|
this.getInit().isImpure()
|
|
}
|
|
|
|
override string toString() { properties(this, _, _, _, result) }
|
|
|
|
override ControlFlowNode getFirstControlFlowNode() {
|
|
result = this.getNameExpr().getFirstControlFlowNode()
|
|
or
|
|
not exists(this.getNameExpr()) and result = this.getInit().getFirstControlFlowNode()
|
|
}
|
|
|
|
/**
|
|
* Gets the kind of this property, which is an opaque integer
|
|
* value indicating whether this property is a value property,
|
|
* a property getter, or a property setter.
|
|
*/
|
|
int getKind() { properties(this, _, _, result, _) }
|
|
|
|
/**
|
|
* Gets the `i`th decorator applied to this property.
|
|
*
|
|
* For example, the property `@A @B x: 42` has
|
|
* `@A` as its 0th decorator, and `@B` as its first decorator.
|
|
*/
|
|
Decorator getDecorator(int i) { result = this.getChildExpr(-(i + 1)) }
|
|
|
|
/**
|
|
* Gets a decorator applied to this property.
|
|
*
|
|
* For example, the property `@A @B x: 42` has
|
|
* decorators `@A` and `@B`.
|
|
*/
|
|
Decorator getADecorator() { result = this.getDecorator(_) }
|
|
|
|
override string getAPrimaryQlClass() { result = "Property" }
|
|
}
|
|
|
|
/**
|
|
* A value property definition in an object literal.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* var p = {
|
|
* x: 1, // value property
|
|
* y: 1, // value property
|
|
* diag: function() { return this.x - this.y; }, // value property
|
|
* }
|
|
* ```
|
|
*/
|
|
class ValueProperty extends Property, @value_property { }
|
|
|
|
/**
|
|
* A property getter or setter in an object literal.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* var p = {
|
|
* x: 1,
|
|
* y: 1,
|
|
* diag: function() { return this.x - this.y; },
|
|
* get area() { return this.x * this.y; }, // property getter
|
|
* set area(a) { this.x = Math.sqrt(a); this.y = Math.sqrt(a); } // property setter
|
|
* }
|
|
* ```
|
|
*/
|
|
class PropertyAccessor extends Property, @property_accessor {
|
|
override FunctionExpr getInit() { result = Property.super.getInit() }
|
|
}
|
|
|
|
/**
|
|
* A property getter in an object literal.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* var p = {
|
|
* x: 1,
|
|
* y: 1,
|
|
* diag: function() { return this.x - this.y; },
|
|
* get area() { return this.x * this.y; }, // property getter
|
|
* set area(a) { this.x = Math.sqrt(a); this.y = Math.sqrt(a); }
|
|
* }
|
|
* ```
|
|
*/
|
|
class PropertyGetter extends PropertyAccessor, @property_getter { }
|
|
|
|
/**
|
|
* A property setter in an object literal.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* var p = {
|
|
* x: 1,
|
|
* y: 1,
|
|
* diag: function() { return this.x - this.y; },
|
|
* get area() { return this.x * this.y; },
|
|
* set area(a) { this.x = Math.sqrt(a); this.y = Math.sqrt(a); } // property setter
|
|
* }
|
|
* ```
|
|
*/
|
|
class PropertySetter extends PropertyAccessor, @property_setter { }
|
|
|
|
/**
|
|
* A spread property in an object literal.
|
|
*
|
|
* The initializer of a spread property is always
|
|
* a `SpreadElement`.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* var options = {
|
|
* ...defaultOptions, // spread property
|
|
* password: pwd
|
|
* }
|
|
* ```
|
|
*/
|
|
class SpreadProperty extends Property {
|
|
SpreadProperty() { not exists(this.getNameExpr()) }
|
|
}
|
|
|
|
/**
|
|
* A (non-arrow) function expression.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* var greet =
|
|
* function g() { // function expression with name `g`
|
|
* console.log("Hi!");
|
|
* };
|
|
*
|
|
* class C {
|
|
* m() { // methods are (anonymous) function expressions
|
|
* return 1;
|
|
* }
|
|
* }
|
|
* ```
|
|
*/
|
|
class FunctionExpr extends @function_expr, Expr, Function {
|
|
/** Holds if this function expression is a property setter. */
|
|
predicate isSetter() { exists(PropertySetter s | s.getInit() = this) }
|
|
|
|
/** Holds if this function expression is a property getter. */
|
|
predicate isGetter() { exists(PropertyGetter g | g.getInit() = this) }
|
|
|
|
/** Holds if this function expression is a property accessor. */
|
|
predicate isAccessor() { exists(PropertyAccessor acc | acc.getInit() = this) }
|
|
|
|
/** Gets the statement in which this function expression appears. */
|
|
override Stmt getEnclosingStmt() { result = Expr.super.getEnclosingStmt() }
|
|
|
|
override StmtContainer getEnclosingContainer() {
|
|
Stages::Ast::ref() and result = Expr.super.getContainer()
|
|
}
|
|
|
|
override predicate isImpure() { none() }
|
|
|
|
override string getAPrimaryQlClass() { result = "FunctionExpr" }
|
|
}
|
|
|
|
/**
|
|
* An arrow function expression.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* var greet =
|
|
* () => console.log("Hi!"); // arrow function expression
|
|
*/
|
|
class ArrowFunctionExpr extends @arrow_function_expr, Expr, Function {
|
|
/** Gets the statement in which this expression appears. */
|
|
override Stmt getEnclosingStmt() { result = Expr.super.getEnclosingStmt() }
|
|
|
|
override StmtContainer getEnclosingContainer() { result = Expr.super.getContainer() }
|
|
|
|
override predicate isImpure() { none() }
|
|
|
|
override Function getThisBinder() {
|
|
result = this.getEnclosingContainer().(Function).getThisBinder()
|
|
}
|
|
|
|
override string getAPrimaryQlClass() { result = "ArrowFunctionExpr" }
|
|
}
|
|
|
|
/**
|
|
* A sequence expression (also known as comma expression).
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* x++, y++
|
|
* ```
|
|
*/
|
|
class SeqExpr extends @seq_expr, Expr {
|
|
/** Gets the `i`th expression in this sequence. */
|
|
Expr getOperand(int i) { result = this.getChildExpr(i) }
|
|
|
|
/** Gets an expression in this sequence. */
|
|
Expr getAnOperand() { result = this.getOperand(_) }
|
|
|
|
/** Gets the number of expressions in this sequence. */
|
|
int getNumOperands() { result = count(this.getOperand(_)) }
|
|
|
|
/** Gets the last expression in this sequence. */
|
|
Expr getLastOperand() { result = this.getOperand(this.getNumOperands() - 1) }
|
|
|
|
override predicate isImpure() { this.getAnOperand().isImpure() }
|
|
|
|
override Expr getUnderlyingValue() { result = this.getLastOperand().getUnderlyingValue() }
|
|
|
|
override string getAPrimaryQlClass() { result = "SeqExpr" }
|
|
}
|
|
|
|
/**
|
|
* A conditional expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* x == 0 ? 0 : 1/x
|
|
* ```
|
|
*/
|
|
class ConditionalExpr extends @conditional_expr, Expr {
|
|
/** Gets the condition expression of this conditional. */
|
|
Expr getCondition() { result = this.getChildExpr(0) }
|
|
|
|
/** Gets the 'then' expression of this conditional. */
|
|
Expr getConsequent() { result = this.getChildExpr(1) }
|
|
|
|
/** Gets the 'else' expression of this conditional. */
|
|
Expr getAlternate() { result = this.getChildExpr(2) }
|
|
|
|
/** Gets either the 'then' or the 'else' expression of this conditional. */
|
|
Expr getABranch() { result = this.getConsequent() or result = this.getAlternate() }
|
|
|
|
override predicate isImpure() {
|
|
this.getCondition().isImpure() or
|
|
this.getABranch().isImpure()
|
|
}
|
|
|
|
override string getAPrimaryQlClass() { result = "ConditionalExpr" }
|
|
}
|
|
|
|
/**
|
|
* An invocation expression, that is, either a function call or
|
|
* a `new` expression.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* f(1)
|
|
* x.f(1, ...args)
|
|
* f.call(x, 1)
|
|
* new Array(16)
|
|
* ```
|
|
*/
|
|
class InvokeExpr extends @invokeexpr, Expr {
|
|
/** Gets the expression specifying the function to be called. */
|
|
Expr getCallee() { result = this.getChildExpr(-1) }
|
|
|
|
/** Gets the name of the function or method being invoked, if it can be determined. */
|
|
string getCalleeName() {
|
|
exists(Expr callee | callee = this.getCallee().getUnderlyingValue() |
|
|
result = callee.(Identifier).getName() or
|
|
result = callee.(PropAccess).getPropertyName()
|
|
)
|
|
}
|
|
|
|
/** Gets the `i`th argument of this invocation. */
|
|
Expr getArgument(int i) { i >= 0 and result = this.getChildExpr(i) }
|
|
|
|
/** Gets an argument of this invocation. */
|
|
Expr getAnArgument() { result = this.getArgument(_) }
|
|
|
|
/** Gets the last argument of this invocation, if any. */
|
|
Expr getLastArgument() { result = this.getArgument(this.getNumArgument() - 1) }
|
|
|
|
/** Gets the number of arguments of this invocation. */
|
|
int getNumArgument() { result = count(this.getAnArgument()) }
|
|
|
|
/** Gets the `i`th type argument of this invocation. */
|
|
TypeExpr getTypeArgument(int i) { i >= 0 and result = this.getChildTypeExpr(-i - 2) }
|
|
|
|
/** Gets a type argument of this invocation. */
|
|
TypeExpr getATypeArgument() { result = this.getTypeArgument(_) }
|
|
|
|
/** Gets the number of type arguments of this invocation. */
|
|
int getNumTypeArgument() { result = count(this.getATypeArgument()) }
|
|
|
|
override ControlFlowNode getFirstControlFlowNode() {
|
|
result = this.getCallee().getFirstControlFlowNode()
|
|
}
|
|
|
|
/** Holds if the argument list of this function has a trailing comma. */
|
|
predicate hasTrailingComma() {
|
|
// check whether the last token of this invocation is a closing
|
|
// parenthesis, which itself is preceded by a comma
|
|
exists(PunctuatorToken rparen | rparen.getValue() = ")" |
|
|
rparen = this.getLastToken() and
|
|
rparen.getPreviousToken().getValue() = ","
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if the `i`th argument of this invocation is a spread element.
|
|
*/
|
|
predicate isSpreadArgument(int i) { this.getArgument(i).stripParens() instanceof SpreadElement }
|
|
|
|
/**
|
|
* Holds if the `i`th argument of this invocation is an object literal whose property
|
|
* `name` is set to `value`.
|
|
*
|
|
* This predicate is an approximation, computed using only local data flow.
|
|
*/
|
|
predicate hasOptionArgument(int i, string name, Expr value) {
|
|
value = this.flow().(DataFlow::InvokeNode).getOptionArgument(i, name).asExpr()
|
|
}
|
|
|
|
/**
|
|
* Gets the call signature of the invoked function, as determined by the TypeScript
|
|
* type system, with overloading resolved and type parameters substituted.
|
|
*
|
|
* This predicate is only populated for files extracted with full TypeScript extraction.
|
|
*/
|
|
CallSignatureType getResolvedSignature() { invoke_expr_signature(this, result) }
|
|
|
|
/**
|
|
* Gets the index of the targeted call signature among the overload signatures
|
|
* on the invoked function.
|
|
*
|
|
* This predicate is only populated for files extracted with full TypeScript extraction.
|
|
*/
|
|
int getResolvedOverloadIndex() { invoke_expr_overload_index(this, result) }
|
|
|
|
/**
|
|
* Gets the canonical name of the static call target, as determined by the TypeScript type system.
|
|
*
|
|
* This predicate is only populated for files extracted with full TypeScript extraction.
|
|
*/
|
|
CanonicalFunctionName getResolvedCalleeName() { ast_node_symbol(this, result) }
|
|
|
|
/**
|
|
* Gets the statically resolved target function, as determined by the TypeScript type system, if any.
|
|
*
|
|
* This predicate is only populated for files extracted with full TypeScript extraction.
|
|
*
|
|
* Note that the resolved function may be overridden in a subclass and thus is not
|
|
* necessarily the actual target of this invocation at runtime.
|
|
*/
|
|
Function getResolvedCallee() {
|
|
TypeResolution::callTarget(this, result)
|
|
or
|
|
result = this.getResolvedCalleeName().getImplementation()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A `new` expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* new Array(16)
|
|
* ```
|
|
*/
|
|
class NewExpr extends @new_expr, InvokeExpr {
|
|
override string getAPrimaryQlClass() { result = "NewExpr" }
|
|
}
|
|
|
|
/**
|
|
* A function call expression.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* f()
|
|
* require('express')()
|
|
* x.f()
|
|
* ```
|
|
*/
|
|
class CallExpr extends @call_expr, InvokeExpr {
|
|
/**
|
|
* Gets the expression specifying the receiver on which the function
|
|
* is invoked, if any.
|
|
*/
|
|
Expr getReceiver() { result = this.getCallee().(PropAccess).getBase() }
|
|
|
|
override string getAPrimaryQlClass() { result = "CallExpr" }
|
|
}
|
|
|
|
/**
|
|
* A method call expression.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* Object.create(null)
|
|
* [1, 2, 3].forEach(alert);
|
|
* ```
|
|
*/
|
|
class MethodCallExpr extends CallExpr {
|
|
MethodCallExpr() { this.getCallee().stripParens() instanceof PropAccess }
|
|
|
|
/**
|
|
* Gets the property access referencing the method to be invoked.
|
|
*/
|
|
private PropAccess getMethodRef() { result = this.getCallee().stripParens() }
|
|
|
|
/**
|
|
* Gets the receiver expression of this method call.
|
|
*/
|
|
override Expr getReceiver() { result = this.getMethodRef().getBase() }
|
|
|
|
/**
|
|
* Gets the name of the invoked method, if it can be determined.
|
|
*/
|
|
string getMethodName() { result = this.getMethodRef().getPropertyName() }
|
|
|
|
/** Holds if this invocation calls method `m` on expression `base`. */
|
|
predicate calls(Expr base, string m) { this.getMethodRef().accesses(base, m) }
|
|
|
|
override string getAPrimaryQlClass() { result = "MethodCallExpr" }
|
|
}
|
|
|
|
/**
|
|
* A property access, that is, either a dot expression of the form
|
|
* `e.f` or an index expression of the form `e[p]`.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* Math.PI
|
|
* arguments[i]
|
|
* ```
|
|
*/
|
|
class PropAccess extends @propaccess, Expr {
|
|
/** Gets the base expression on which the property is accessed. */
|
|
Expr getBase() { result = this.getChildExpr(0) }
|
|
|
|
/**
|
|
* Gets the expression specifying the name of the property being
|
|
* read or written. For dot expressions, this is an identifier; for
|
|
* index expressions it can be an arbitrary expression.
|
|
*/
|
|
Expr getPropertyNameExpr() { result = this.getChildExpr(1) }
|
|
|
|
/** Gets the name of the accessed property, if it can be determined. */
|
|
string getPropertyName() { none() }
|
|
|
|
/** Gets the qualified name of the accessed property, if it can be determined. */
|
|
string getQualifiedName() {
|
|
exists(string basename |
|
|
basename = this.getBase().(Identifier).getName() or
|
|
basename = this.getBase().(PropAccess).getQualifiedName()
|
|
|
|
|
result = basename + "." + this.getPropertyName()
|
|
)
|
|
}
|
|
|
|
/** Holds if this property name accesses property `p` on expression `base`. */
|
|
predicate accesses(Expr base, string p) {
|
|
base = this.getBase() and
|
|
p = this.getPropertyName()
|
|
}
|
|
|
|
override ControlFlowNode getFirstControlFlowNode() {
|
|
result = this.getBase().getFirstControlFlowNode()
|
|
}
|
|
|
|
override Expr getUnderlyingReference() { result = this }
|
|
}
|
|
|
|
/**
|
|
* A dot expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* Math.PI
|
|
* ```
|
|
*/
|
|
class DotExpr extends @dot_expr, PropAccess {
|
|
override string getPropertyName() { result = this.getProperty().getName() }
|
|
|
|
/** Gets the identifier specifying the name of the accessed property. */
|
|
Identifier getProperty() { result = this.getChildExpr(1) }
|
|
|
|
override predicate isImpure() { this.getBase().isImpure() }
|
|
|
|
override string getAPrimaryQlClass() { result = "DotExpr" }
|
|
}
|
|
|
|
/**
|
|
* An index expression (also known as computed property access).
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* arguments[i]
|
|
* ```
|
|
*/
|
|
class IndexExpr extends @index_expr, PropAccess {
|
|
/** Gets the expression specifying the name of the accessed property. */
|
|
Expr getIndex() { result = this.getChildExpr(1) }
|
|
|
|
override string getPropertyName() { result = this.getIndex().(Literal).getValue() }
|
|
|
|
override predicate isImpure() {
|
|
this.getBase().isImpure() or
|
|
this.getIndex().isImpure()
|
|
}
|
|
|
|
override string getAPrimaryQlClass() { result = "IndexExpr" }
|
|
}
|
|
|
|
/**
|
|
* An expression with a unary operator.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* -x
|
|
* !!done
|
|
* ```
|
|
*/
|
|
class UnaryExpr extends @unaryexpr, Expr {
|
|
/** Gets the operand of this unary operator. */
|
|
Expr getOperand() { result = this.getChildExpr(0) }
|
|
|
|
/** Gets the operator of this expression. */
|
|
string getOperator() { none() }
|
|
|
|
override predicate isImpure() { this.getOperand().isImpure() }
|
|
|
|
override ControlFlowNode getFirstControlFlowNode() {
|
|
result = this.getOperand().getFirstControlFlowNode()
|
|
}
|
|
|
|
override string getAPrimaryQlClass() { result = "UnaryExpr" }
|
|
}
|
|
|
|
/**
|
|
* An arithmetic negation expression (also known as unary minus).
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* -x
|
|
* ```
|
|
*/
|
|
class NegExpr extends @neg_expr, UnaryExpr {
|
|
override string getOperator() { result = "-" }
|
|
|
|
override int getIntValue() { result = -this.getOperand().getIntValue() }
|
|
}
|
|
|
|
/**
|
|
* A unary plus expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* +x
|
|
* ```
|
|
*/
|
|
class PlusExpr extends @plus_expr, UnaryExpr {
|
|
override string getOperator() { result = "+" }
|
|
}
|
|
|
|
/**
|
|
* A logical negation expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* !done
|
|
* ```
|
|
*/
|
|
class LogNotExpr extends @log_not_expr, UnaryExpr {
|
|
override string getOperator() { result = "!" }
|
|
}
|
|
|
|
/**
|
|
* A bitwise negation expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* ~bitmask
|
|
* ```
|
|
*/
|
|
class BitNotExpr extends @bit_not_expr, UnaryExpr {
|
|
override string getOperator() { result = "~" }
|
|
}
|
|
|
|
/**
|
|
* A `typeof` expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* typeof A.prototype
|
|
* ```
|
|
*/
|
|
class TypeofExpr extends @typeof_expr, UnaryExpr {
|
|
override string getOperator() { result = "typeof" }
|
|
}
|
|
|
|
/**
|
|
* A `void` expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* void(0)
|
|
* ```
|
|
*/
|
|
class VoidExpr extends @void_expr, UnaryExpr {
|
|
override string getOperator() { result = "void" }
|
|
}
|
|
|
|
/**
|
|
* A `delete` expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* delete elt[_expando]
|
|
* ```
|
|
*/
|
|
class DeleteExpr extends @delete_expr, UnaryExpr {
|
|
override string getOperator() { result = "delete" }
|
|
|
|
override predicate isImpure() { any() }
|
|
}
|
|
|
|
/**
|
|
* A spread element.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* [].concat(
|
|
* ...lists // a spread element
|
|
* )
|
|
* ```
|
|
*/
|
|
class SpreadElement extends @spread_element, UnaryExpr {
|
|
override string getOperator() { result = "..." }
|
|
|
|
override string getAPrimaryQlClass() { result = "SpreadElement" }
|
|
}
|
|
|
|
/**
|
|
* An expression with a binary operator.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* x + 1
|
|
* a instanceof Array
|
|
* ```
|
|
*/
|
|
class BinaryExpr extends @binaryexpr, Expr {
|
|
/** Gets the left operand of this binary operator. */
|
|
Expr getLeftOperand() { result = this.getChildExpr(0) }
|
|
|
|
/** Gets the right operand of this binary operator. */
|
|
Expr getRightOperand() { result = this.getChildExpr(1) }
|
|
|
|
/** Gets an operand of this binary operator. */
|
|
Expr getAnOperand() { result = this.getAChildExpr() }
|
|
|
|
/** Holds if `e` and `f` (in either order) are the two operands of this expression. */
|
|
predicate hasOperands(Expr e, Expr f) {
|
|
e = this.getAnOperand() and
|
|
f = this.getAnOperand() and
|
|
e != f
|
|
}
|
|
|
|
/** Gets the operator of this expression. */
|
|
string getOperator() { none() }
|
|
|
|
override predicate isImpure() { this.getAnOperand().isImpure() }
|
|
|
|
override ControlFlowNode getFirstControlFlowNode() {
|
|
result = this.getLeftOperand().getFirstControlFlowNode()
|
|
}
|
|
|
|
/**
|
|
* Gets the number of whitespace characters around the operator of this expression.
|
|
*
|
|
* This predicate is only defined if both operands are on the same line, and if the
|
|
* amount of whitespace before and after the operator are the same.
|
|
*/
|
|
int getWhitespaceAroundOperator() {
|
|
exists(Token lastLeft, Token operator, Token firstRight, int l, int c1, int c2, int c3, int c4 |
|
|
lastLeft = this.getLeftOperand().getLastToken() and
|
|
operator = lastLeft.getNextToken() and
|
|
firstRight = operator.getNextToken() and
|
|
lastLeft.getLocation().hasLocationInfo(_, _, _, l, c1) and
|
|
operator.getLocation().hasLocationInfo(_, l, c2, l, c3) and
|
|
firstRight.getLocation().hasLocationInfo(_, l, c4, _, _) and
|
|
result = c2 - c1 - 1 and
|
|
result = c4 - c3 - 1
|
|
)
|
|
}
|
|
|
|
override string getAPrimaryQlClass() { result = "BinaryExpr" }
|
|
}
|
|
|
|
/**
|
|
* A comparison expression, that is, either an equality test
|
|
* (`==`, `!=`, `===`, `!==`) or a relational expression
|
|
* (`<`, `<=`, `>=`, `>`).
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* x !== y
|
|
* y < 0
|
|
* ```
|
|
*/
|
|
class Comparison extends @comparison, BinaryExpr { }
|
|
|
|
/**
|
|
* An equality test using `==`, `!=`, `===` or `!==`.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* "" == arg
|
|
* x != null
|
|
* recv === undefined
|
|
* res !== res
|
|
* ```
|
|
*/
|
|
class EqualityTest extends @equality_test, Comparison {
|
|
/** Gets the polarity of this test: `true` for equalities, `false` for inequalities. */
|
|
boolean getPolarity() {
|
|
(this instanceof EqExpr or this instanceof StrictEqExpr) and
|
|
result = true
|
|
or
|
|
(this instanceof NEqExpr or this instanceof StrictNEqExpr) and
|
|
result = false
|
|
}
|
|
|
|
/**
|
|
* Holds if the equality operator is strict (`===` or `!==`).
|
|
*/
|
|
predicate isStrict() { this instanceof StrictEqExpr or this instanceof StrictNEqExpr }
|
|
}
|
|
|
|
/**
|
|
* An equality test using `==`.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* "" == arg
|
|
* ```
|
|
*/
|
|
class EqExpr extends @eq_expr, EqualityTest {
|
|
override string getOperator() { result = "==" }
|
|
}
|
|
|
|
/**
|
|
* An inequality test using `!=`.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* x != null
|
|
* ```
|
|
*/
|
|
class NEqExpr extends @neq_expr, EqualityTest {
|
|
override string getOperator() { result = "!=" }
|
|
}
|
|
|
|
/**
|
|
* A strict equality test using `===`.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* recv === undefined
|
|
* ```
|
|
*/
|
|
class StrictEqExpr extends @eqq_expr, EqualityTest {
|
|
override string getOperator() { result = "===" }
|
|
}
|
|
|
|
/**
|
|
* A strict inequality test using `!==`.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* res !== res
|
|
* ```
|
|
*/
|
|
class StrictNEqExpr extends @neqq_expr, EqualityTest {
|
|
override string getOperator() { result = "!==" }
|
|
}
|
|
|
|
/**
|
|
* A less-than expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* i < 10
|
|
* ```
|
|
*/
|
|
class LTExpr extends @lt_expr, Comparison {
|
|
override string getOperator() { result = "<" }
|
|
}
|
|
|
|
/**
|
|
* A less-than-or-equal expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* x+1 <= a.length
|
|
* ```
|
|
*/
|
|
class LEExpr extends @le_expr, Comparison {
|
|
override string getOperator() { result = "<=" }
|
|
}
|
|
|
|
/**
|
|
* A greater-than expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* a[j] > a[k]
|
|
* ```
|
|
*/
|
|
class GTExpr extends @gt_expr, Comparison {
|
|
override string getOperator() { result = ">" }
|
|
}
|
|
|
|
/**
|
|
* A greater-than-or-equal expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* x >= 0
|
|
* ```
|
|
*/
|
|
class GEExpr extends @ge_expr, Comparison {
|
|
override string getOperator() { result = ">=" }
|
|
}
|
|
|
|
/**
|
|
* A left-shift expression using `<<`.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* 2 << i
|
|
* ```
|
|
*/
|
|
class LShiftExpr extends @lshift_expr, BinaryExpr {
|
|
override string getOperator() { result = "<<" }
|
|
}
|
|
|
|
/**
|
|
* A right-shift expression using `>>`.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* r >> 8
|
|
* ```
|
|
*/
|
|
class RShiftExpr extends @rshift_expr, BinaryExpr {
|
|
override string getOperator() { result = ">>" }
|
|
}
|
|
|
|
/**
|
|
* An unsigned right-shift expression using `>>>`.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* u >>> v
|
|
* ```
|
|
*/
|
|
class URShiftExpr extends @urshift_expr, BinaryExpr {
|
|
override string getOperator() { result = ">>>" }
|
|
}
|
|
|
|
/**
|
|
* An addition or string-concatenation expression.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* a + b
|
|
* msg + "\n"
|
|
* ```
|
|
*/
|
|
class AddExpr extends @add_expr, BinaryExpr {
|
|
override string getOperator() { result = "+" }
|
|
|
|
/**
|
|
* Gets the value of this string concatenation parsed as a regular expression, if possible.
|
|
*
|
|
* All string literals have an associated regular expression tree, provided they can
|
|
* be parsed without syntax errors.
|
|
*/
|
|
RegExpTerm asRegExp() { this = result.getParent() }
|
|
}
|
|
|
|
/**
|
|
* Gets the string value for the expression `e`.
|
|
* This string-value is either a constant-string, or the result from a simple string-concatenation.
|
|
*/
|
|
private string getStringValue(Expr e) {
|
|
result = getConstantString(e)
|
|
or
|
|
result = getConcatenatedString(e)
|
|
}
|
|
|
|
/**
|
|
* Gets the constant string value for the expression `e`.
|
|
*/
|
|
private string getConstantString(Expr e) {
|
|
result = getConstantString(e.getUnderlyingValue())
|
|
or
|
|
result = e.(StringLiteral).getValue()
|
|
or
|
|
exists(TemplateLiteral lit | lit = e |
|
|
// fold singletons
|
|
lit.getNumChildExpr() = 0 and
|
|
result = ""
|
|
or
|
|
e.getNumChildExpr() = 1 and
|
|
result = getConstantString(lit.getElement(0))
|
|
)
|
|
or
|
|
result = e.(TemplateElement).getValue()
|
|
}
|
|
|
|
/**
|
|
* Holds if `add` is a string-concatenation where all the transitive leafs have a constant string value.
|
|
*/
|
|
private predicate hasAllConstantLeafs(AddExpr add) {
|
|
forex(Expr leaf | leaf = getAnAddOperand*(add) and not exists(getAnAddOperand(leaf)) |
|
|
exists(getConstantString(leaf))
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets the concatenated string for a string-concatenation `add`.
|
|
* Only has a result if `add` is not itself an operand in another string-concatenation with all constant leafs.
|
|
*/
|
|
private string getConcatenatedString(Expr add) {
|
|
result = getConcatenatedString(add.getUnderlyingValue())
|
|
or
|
|
result =
|
|
strictconcat(Expr leaf |
|
|
leaf = getAnAddOperand*(add.(SmallConcatRoot))
|
|
|
|
|
getConstantString(leaf)
|
|
order by
|
|
leaf.getLocation().getStartLine(), leaf.getLocation().getStartColumn()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* An expr that is the root of a string concatenation of constant parts,
|
|
* and the length of the resulting concatenation is less than 1 million chars.
|
|
*/
|
|
private class SmallConcatRoot extends Expr {
|
|
SmallConcatRoot() {
|
|
not this = getAnAddOperand(any(AddExpr parent | hasAllConstantLeafs(parent))) and
|
|
hasAllConstantLeafs(this) and
|
|
sum(Expr leaf | leaf = getAnAddOperand*(this) | getConstantString(leaf).length()) < 1000 * 1000
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets an operand from `add`.
|
|
* Is specialized to `AddExpr` such that `getAnAddOperand*(add)` can be used to get a leaf from a string-concatenation transitively.
|
|
*/
|
|
private Expr getAnAddOperand(AddExpr add) { result = add.getAnOperand().getUnderlyingValue() }
|
|
|
|
/**
|
|
* A subtraction expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* w - len
|
|
* ```
|
|
*/
|
|
class SubExpr extends @sub_expr, BinaryExpr {
|
|
override string getOperator() { result = "-" }
|
|
}
|
|
|
|
/**
|
|
* A multiplication expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* x * y
|
|
* ```
|
|
*/
|
|
class MulExpr extends @mul_expr, BinaryExpr {
|
|
override string getOperator() { result = "*" }
|
|
}
|
|
|
|
/**
|
|
* A division expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* gg / ac
|
|
* ```
|
|
*/
|
|
class DivExpr extends @div_expr, BinaryExpr {
|
|
override string getOperator() { result = "/" }
|
|
}
|
|
|
|
/**
|
|
* A modulo expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* n % 2
|
|
* ```
|
|
*/
|
|
class ModExpr extends @mod_expr, BinaryExpr {
|
|
override string getOperator() { result = "%" }
|
|
}
|
|
|
|
/**
|
|
* An exponentiation expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* p ** 10
|
|
* ```
|
|
*/
|
|
class ExpExpr extends @exp_expr, BinaryExpr {
|
|
override string getOperator() { result = "**" }
|
|
}
|
|
|
|
/**
|
|
* A bitwise 'or' expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* O_RDWR | O_APPEND
|
|
* ```
|
|
*/
|
|
class BitOrExpr extends @bitor_expr, BinaryExpr {
|
|
override string getOperator() { result = "|" }
|
|
}
|
|
|
|
/**
|
|
* An exclusive 'or' expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* x ^ 1
|
|
* ```
|
|
*/
|
|
class XOrExpr extends @xor_expr, BinaryExpr {
|
|
override string getOperator() { result = "^" }
|
|
}
|
|
|
|
/**
|
|
* A bitwise 'and' expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* flags & O_APPEND
|
|
* ```
|
|
*/
|
|
class BitAndExpr extends @bitand_expr, BinaryExpr {
|
|
override string getOperator() { result = "&" }
|
|
}
|
|
|
|
/**
|
|
* An `in` expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* "leftpad" in String.prototype
|
|
* ```
|
|
*/
|
|
class InExpr extends @in_expr, BinaryExpr {
|
|
override string getOperator() { result = "in" }
|
|
}
|
|
|
|
/**
|
|
* An `instanceof` expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* b instanceof Buffer
|
|
* ```
|
|
*/
|
|
class InstanceofExpr extends @instanceof_expr, BinaryExpr {
|
|
override string getOperator() { result = "instanceof" }
|
|
}
|
|
|
|
/**
|
|
* A logical 'and' expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* x != null && x.f
|
|
* ```
|
|
*/
|
|
class LogAndExpr extends @logand_expr, BinaryExpr {
|
|
override string getOperator() { result = "&&" }
|
|
|
|
override ControlFlowNode getFirstControlFlowNode() { result = this }
|
|
}
|
|
|
|
/**
|
|
* A logical 'or' expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* x == null || x.f
|
|
* ```
|
|
*/
|
|
class LogOrExpr extends @logor_expr, BinaryExpr {
|
|
override string getOperator() { result = "||" }
|
|
|
|
override ControlFlowNode getFirstControlFlowNode() { result = this }
|
|
}
|
|
|
|
/**
|
|
* A nullish coalescing '??' expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* x ?? f
|
|
* ```
|
|
*/
|
|
class NullishCoalescingExpr extends @nullishcoalescing_expr, BinaryExpr {
|
|
override string getOperator() { result = "??" }
|
|
|
|
override ControlFlowNode getFirstControlFlowNode() { result = this }
|
|
}
|
|
|
|
/**
|
|
* A short-circuiting logical binary expression, that is, a logical 'or' expression,
|
|
* a logical 'and' expression, or a nullish-coalescing expression.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* x && x.f
|
|
* !x || x.f
|
|
* x ?? f
|
|
* ```
|
|
*/
|
|
class LogicalBinaryExpr extends BinaryExpr {
|
|
LogicalBinaryExpr() {
|
|
this instanceof LogAndExpr or
|
|
this instanceof LogOrExpr or
|
|
this instanceof NullishCoalescingExpr
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A bitwise binary expression, that is, either a bitwise
|
|
* 'and', a bitwise 'or', or an exclusive 'or' expression.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* qw & 0xffff
|
|
* O_RDWR | O_APPEND
|
|
* x ^ 1
|
|
* ```
|
|
*/
|
|
class BitwiseBinaryExpr extends BinaryExpr {
|
|
BitwiseBinaryExpr() {
|
|
this instanceof BitAndExpr or
|
|
this instanceof BitOrExpr or
|
|
this instanceof XOrExpr
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A shift expression.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* 2 << i
|
|
* r >> 8
|
|
* u >>> v
|
|
* ```
|
|
*/
|
|
class ShiftExpr extends BinaryExpr {
|
|
ShiftExpr() {
|
|
this instanceof LShiftExpr or
|
|
this instanceof RShiftExpr or
|
|
this instanceof URShiftExpr
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An assignment expression, either compound or simple.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* x = y
|
|
* sum += element
|
|
* ```
|
|
*/
|
|
class Assignment extends @assignment, Expr {
|
|
/** Gets the left hand side of this assignment. */
|
|
Expr getLhs() { result = this.getChildExpr(0) }
|
|
|
|
/** Gets the right hand side of this assignment. */
|
|
Expr getRhs() { result = this.getChildExpr(1) }
|
|
|
|
/** Gets the variable or property this assignment writes to, if any. */
|
|
Expr getTarget() { result = this.getLhs().stripParens() }
|
|
|
|
override ControlFlowNode getFirstControlFlowNode() {
|
|
result = this.getLhs().getFirstControlFlowNode()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A simple assignment expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* x = y
|
|
* ```
|
|
*/
|
|
class AssignExpr extends @assign_expr, Assignment {
|
|
override Expr getUnderlyingValue() { result = this.getRhs().getUnderlyingValue() }
|
|
|
|
override string getAPrimaryQlClass() { result = "AssignExpr" }
|
|
}
|
|
|
|
private class TCompoundAssignExpr =
|
|
@assign_add_expr or @assign_sub_expr or @assign_mul_expr or @assign_div_expr or
|
|
@assign_mod_expr or @assign_exp_expr or @assign_lshift_expr or @assign_rshift_expr or
|
|
@assign_urshift_expr or @assign_or_expr or @assign_xor_expr or @assign_and_expr or
|
|
@assignlogandexpr or @assignlogorexpr or @assignnullishcoalescingexpr;
|
|
|
|
/**
|
|
* A compound assign expression.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* sum += element
|
|
* x /= 2
|
|
* ```
|
|
*/
|
|
class CompoundAssignExpr extends TCompoundAssignExpr, Assignment {
|
|
override string getAPrimaryQlClass() { result = "CompoundAssignExpr" }
|
|
|
|
/**
|
|
* Holds if this compound assignment always returns a number value.
|
|
*/
|
|
predicate isNumeric() {
|
|
not (
|
|
this instanceof AssignAddExpr or
|
|
this instanceof AssignLogOrExpr or
|
|
this instanceof AssignLogAndExpr or
|
|
this instanceof AssignNullishCoalescingExpr
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A compound add-assign expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* sum += element
|
|
* ```
|
|
*/
|
|
class AssignAddExpr extends @assign_add_expr, CompoundAssignExpr { }
|
|
|
|
/**
|
|
* A compound subtract-assign expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* i -= 2
|
|
* ```
|
|
*/
|
|
class AssignSubExpr extends @assign_sub_expr, CompoundAssignExpr { }
|
|
|
|
/**
|
|
* A compound multiply-assign expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* x *= y
|
|
* ```
|
|
*/
|
|
class AssignMulExpr extends @assign_mul_expr, CompoundAssignExpr { }
|
|
|
|
/**
|
|
* A compound divide-assign expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* n /= 10
|
|
* ```
|
|
*/
|
|
class AssignDivExpr extends @assign_div_expr, CompoundAssignExpr { }
|
|
|
|
/**
|
|
* A compound modulo-assign expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* m %= 3
|
|
* ```
|
|
*/
|
|
class AssignModExpr extends @assign_mod_expr, CompoundAssignExpr { }
|
|
|
|
/**
|
|
* A compound exponentiate-assign expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* scale **= 10
|
|
* ```
|
|
*/
|
|
class AssignExpExpr extends @assign_exp_expr, CompoundAssignExpr { }
|
|
|
|
/**
|
|
* A compound left-shift-assign expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* exp <<= 2
|
|
* ```
|
|
*/
|
|
class AssignLShiftExpr extends @assign_lshift_expr, CompoundAssignExpr { }
|
|
|
|
/**
|
|
* A compound right-shift-assign expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* qw >>= 8
|
|
* ```
|
|
*/
|
|
class AssignRShiftExpr extends @assign_rshift_expr, CompoundAssignExpr { }
|
|
|
|
/**
|
|
* A compound unsigned-right-shift-assign expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* bits >>>= 16
|
|
* ```
|
|
*/
|
|
class AssignURShiftExpr extends @assign_urshift_expr, CompoundAssignExpr { }
|
|
|
|
/**
|
|
* A compound bitwise-'or'-assign expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* flags |= O_CREAT
|
|
* ```
|
|
*/
|
|
class AssignOrExpr extends @assign_or_expr, CompoundAssignExpr { }
|
|
|
|
/**
|
|
* A compound exclusive-'or'-assign expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* bits ^= mask
|
|
* ```
|
|
*/
|
|
class AssignXOrExpr extends @assign_xor_expr, CompoundAssignExpr { }
|
|
|
|
/**
|
|
* A compound bitwise-'and'-assign expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* data &= 0xffff
|
|
* ```
|
|
*/
|
|
class AssignAndExpr extends @assign_and_expr, CompoundAssignExpr { }
|
|
|
|
/**
|
|
* A logical-'or'-assign expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* x ||= y
|
|
* ```
|
|
*/
|
|
class AssignLogOrExpr extends @assignlogorexpr, CompoundAssignExpr { }
|
|
|
|
/**
|
|
* A logical-'and'-assign expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* x &&= y
|
|
* ```
|
|
*/
|
|
class AssignLogAndExpr extends @assignlogandexpr, CompoundAssignExpr { }
|
|
|
|
/**
|
|
* A 'nullish-coalescing'-assign expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* x ??= y
|
|
* ```
|
|
*/
|
|
class AssignNullishCoalescingExpr extends @assignnullishcoalescingexpr, CompoundAssignExpr { }
|
|
|
|
/**
|
|
* An update expression, that is, an increment or decrement expression.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* ++i
|
|
* --i
|
|
* i++
|
|
* i--
|
|
* ```
|
|
*/
|
|
class UpdateExpr extends @updateexpr, Expr {
|
|
/** Gets the operand of this update. */
|
|
Expr getOperand() { result = this.getChildExpr(0) }
|
|
|
|
/** Holds if this is a prefix increment or prefix decrement expression. */
|
|
predicate isPrefix() { none() }
|
|
|
|
/** Gets the operator of this update expression. */
|
|
string getOperator() { none() }
|
|
|
|
override ControlFlowNode getFirstControlFlowNode() {
|
|
result = this.getOperand().getFirstControlFlowNode()
|
|
}
|
|
|
|
override string getAPrimaryQlClass() { result = "UpdateExpr" }
|
|
}
|
|
|
|
/**
|
|
* A prefix increment expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* ++i
|
|
* ```
|
|
*/
|
|
class PreIncExpr extends @preinc_expr, UpdateExpr {
|
|
override predicate isPrefix() { any() }
|
|
|
|
override string getOperator() { result = "++" }
|
|
}
|
|
|
|
/**
|
|
* A postfix increment expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* i++
|
|
* ```
|
|
*/
|
|
class PostIncExpr extends @postinc_expr, UpdateExpr {
|
|
override string getOperator() { result = "++" }
|
|
}
|
|
|
|
/**
|
|
* A prefix decrement expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* --i
|
|
* ```
|
|
*/
|
|
class PreDecExpr extends @predec_expr, UpdateExpr {
|
|
override predicate isPrefix() { any() }
|
|
|
|
override string getOperator() { result = "--" }
|
|
}
|
|
|
|
/**
|
|
* A postfix decrement expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* i--
|
|
* ```
|
|
*/
|
|
class PostDecExpr extends @postdec_expr, UpdateExpr {
|
|
override string getOperator() { result = "--" }
|
|
}
|
|
|
|
/**
|
|
* A `yield` expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* yield next
|
|
* ```
|
|
*/
|
|
class YieldExpr extends @yield_expr, Expr {
|
|
/** Gets the operand of this `yield` expression. */
|
|
Expr getOperand() { result = this.getChildExpr(0) }
|
|
|
|
/** Holds if this is a `yield*` expression. */
|
|
predicate isDelegating() { is_delegating(this) }
|
|
|
|
override predicate isImpure() { any() }
|
|
|
|
override ControlFlowNode getFirstControlFlowNode() {
|
|
result = this.getOperand().getFirstControlFlowNode()
|
|
or
|
|
not exists(this.getOperand()) and result = this
|
|
}
|
|
|
|
override string getAPrimaryQlClass() { result = "YieldExpr" }
|
|
}
|
|
|
|
/**
|
|
* A comprehension expression, that is, either an array comprehension
|
|
* expression or a generator expression.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* [for (x of xs) x*x]
|
|
* (for (x of xs) x*x)
|
|
* ```
|
|
*/
|
|
class ComprehensionExpr extends @comprehension_expr, Expr {
|
|
/** Gets the `n`th comprehension block in this comprehension. */
|
|
ComprehensionBlock getBlock(int n) {
|
|
exists(int idx |
|
|
result = this.getChildExpr(idx) and
|
|
idx > 0 and
|
|
n = idx - 1
|
|
)
|
|
}
|
|
|
|
/** Gets a comprehension block in this comprehension. */
|
|
ComprehensionBlock getABlock() { result = this.getBlock(_) }
|
|
|
|
/** Gets the number of comprehension blocks in this comprehension. */
|
|
int getNumBlock() { result = count(this.getABlock()) }
|
|
|
|
/** Gets the `n`th filter expression in this comprehension. */
|
|
Expr getFilter(int n) {
|
|
exists(int idx |
|
|
result = this.getChildExpr(idx) and
|
|
idx < 0 and
|
|
n = -idx - 1
|
|
)
|
|
}
|
|
|
|
/** Gets a filter expression in this comprehension. */
|
|
Expr getAFilter() { result = this.getFilter(_) }
|
|
|
|
/** Gets the number of filter expressions in this comprehension. */
|
|
int getNumFilter() { result = count(this.getAFilter()) }
|
|
|
|
/** Gets the body expression of this comprehension. */
|
|
Expr getBody() { result = this.getChildExpr(0) }
|
|
|
|
override predicate isImpure() {
|
|
this.getABlock().isImpure() or
|
|
this.getAFilter().isImpure() or
|
|
this.getBody().isImpure()
|
|
}
|
|
|
|
/** Holds if this is a legacy postfix comprehension expression. */
|
|
predicate isPostfix() {
|
|
exists(Token tk | tk = this.getFirstToken().getNextToken() | not tk.getValue() = ["if", "for"])
|
|
}
|
|
|
|
override string getAPrimaryQlClass() { result = "ComprehensionExpr" }
|
|
}
|
|
|
|
/**
|
|
* An array comprehension expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* [for (x of xs) x*x]
|
|
* ```
|
|
*/
|
|
class ArrayComprehensionExpr extends @array_comprehension_expr, ComprehensionExpr { }
|
|
|
|
/**
|
|
* A generator expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* (for (x of xs) x*x)
|
|
* ```
|
|
*/
|
|
class GeneratorExpr extends @generator_expr, ComprehensionExpr { }
|
|
|
|
/**
|
|
* A comprehension block in a comprehension expression.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* [
|
|
* for (x of [1, 2 3]) // comprehension block
|
|
* x*x
|
|
* ]
|
|
*
|
|
* [
|
|
* for (x in o) // comprehension block
|
|
* "_" + x
|
|
* ]
|
|
* ```
|
|
*/
|
|
class ComprehensionBlock extends @comprehension_block, Expr {
|
|
/** Gets the iterating variable or pattern of this comprehension block. */
|
|
BindingPattern getIterator() { result = this.getChildExpr(0) }
|
|
|
|
/** Gets the domain over which this comprehension block iterates. */
|
|
Expr getDomain() { result = this.getChildExpr(1) }
|
|
|
|
override predicate isImpure() {
|
|
this.getIterator().isImpure() or
|
|
this.getDomain().isImpure()
|
|
}
|
|
|
|
override string getAPrimaryQlClass() { result = "ComprehensionBlock" }
|
|
}
|
|
|
|
/**
|
|
* A `for`-`in` comprehension block in a comprehension expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* [
|
|
* for (x in o) // comprehension block
|
|
* "_" + x
|
|
* ]
|
|
* ```
|
|
*/
|
|
class ForInComprehensionBlock extends @for_in_comprehension_block, ComprehensionBlock { }
|
|
|
|
/**
|
|
* A `for`-`of` comprehension block in a comprehension expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* [
|
|
* for (x of [1, 2 3]) // comprehension block
|
|
* x*x
|
|
* ]
|
|
* ```
|
|
*/
|
|
class ForOfComprehensionBlock extends @for_of_comprehension_block, ComprehensionBlock { }
|
|
|
|
/**
|
|
* A binary arithmetic expression using `+`, `-`, `/`, `%` or `**`.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* x + y
|
|
* i - 1
|
|
* dist / scale
|
|
* k % 2
|
|
* p ** 10
|
|
* ```
|
|
*/
|
|
class ArithmeticExpr extends BinaryExpr {
|
|
ArithmeticExpr() {
|
|
this instanceof AddExpr or
|
|
this instanceof SubExpr or
|
|
this instanceof MulExpr or
|
|
this instanceof DivExpr or
|
|
this instanceof ModExpr or
|
|
this instanceof ExpExpr
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A logical expression using `&&`, `||`, or `!`.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* x && x.f
|
|
* x == null || x.f
|
|
* !x
|
|
* ```
|
|
*/
|
|
class LogicalExpr extends Expr {
|
|
LogicalExpr() {
|
|
this instanceof LogicalBinaryExpr or
|
|
this instanceof LogNotExpr
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A bitwise expression using `&`, `|`, `^`, `~`, `<<`, `>>`, or `>>>`.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* qw & 0xffff
|
|
* O_RDWR | O_APPEND
|
|
* x ^ 1
|
|
* ~bitmask
|
|
* 2 << i
|
|
* r >> 8
|
|
* u >>> v
|
|
* ```
|
|
*/
|
|
class BitwiseExpr extends Expr {
|
|
BitwiseExpr() {
|
|
this instanceof BitwiseBinaryExpr or
|
|
this instanceof BitNotExpr or
|
|
this instanceof ShiftExpr
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A strict equality test using `!==` or `===`.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* recv === undefined
|
|
* res !== res
|
|
* ```
|
|
*/
|
|
class StrictEqualityTest extends EqualityTest {
|
|
StrictEqualityTest() {
|
|
this instanceof StrictEqExpr or
|
|
this instanceof StrictNEqExpr
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A non-strict equality test using `!=` or `==`.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* "" == arg
|
|
* x != null
|
|
* ```
|
|
*/
|
|
class NonStrictEqualityTest extends EqualityTest {
|
|
NonStrictEqualityTest() {
|
|
this instanceof EqExpr or
|
|
this instanceof NEqExpr
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A relational comparison using `<`, `<=`, `>=`, or `>`.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* i < 10
|
|
* x+1 <= a.length
|
|
* x >= 0
|
|
* a[j] > a[k]
|
|
* ```
|
|
*/
|
|
class RelationalComparison extends Comparison {
|
|
RelationalComparison() {
|
|
this instanceof LTExpr or
|
|
this instanceof LEExpr or
|
|
this instanceof GEExpr or
|
|
this instanceof GTExpr
|
|
}
|
|
|
|
/**
|
|
* Gets the lesser operand of this comparison, that is, the left operand for
|
|
* a `<` or `<=` comparison, and the right operand for `>=` or `>`.
|
|
*/
|
|
Expr getLesserOperand() {
|
|
(this instanceof LTExpr or this instanceof LEExpr) and
|
|
result = this.getLeftOperand()
|
|
or
|
|
(this instanceof GTExpr or this instanceof GEExpr) and
|
|
result = this.getRightOperand()
|
|
}
|
|
|
|
/**
|
|
* Gets the greater operand of this comparison, that is, the right operand for
|
|
* a `<` or `<=` comparison, and the left operand for `>=` or `>`.
|
|
*/
|
|
Expr getGreaterOperand() { result = this.getAnOperand() and result != this.getLesserOperand() }
|
|
|
|
/**
|
|
* Holds if this is a comparison with `<=` or `>=`.
|
|
*/
|
|
predicate isInclusive() {
|
|
this instanceof LEExpr or
|
|
this instanceof GEExpr
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A (pre or post) increment expression.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* ++i
|
|
* i++
|
|
* ```
|
|
*/
|
|
class IncExpr extends UpdateExpr {
|
|
IncExpr() { this instanceof PreIncExpr or this instanceof PostIncExpr }
|
|
}
|
|
|
|
/**
|
|
* A (pre or post) decrement expression.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* --i
|
|
* i--
|
|
* ```
|
|
*/
|
|
class DecExpr extends UpdateExpr {
|
|
DecExpr() { this instanceof PreDecExpr or this instanceof PostDecExpr }
|
|
}
|
|
|
|
/**
|
|
* An old-style `let` expression of the form `let(vardecls) expr`.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* let (x = f()) x*x
|
|
* ```
|
|
*/
|
|
class LegacyLetExpr extends Expr, @legacy_letexpr {
|
|
/** Gets the `i`th declarator in this `let` expression. */
|
|
VariableDeclarator getDecl(int i) { result = this.getChildExpr(i) and i >= 0 }
|
|
|
|
/** Gets a declarator in this declaration expression. */
|
|
VariableDeclarator getADecl() { result = this.getDecl(_) }
|
|
|
|
/** Gets the expression this `let` expression scopes over. */
|
|
Expr getBody() { result = this.getChildExpr(-1) }
|
|
|
|
override string getAPrimaryQlClass() { result = "LegacyLetExpr" }
|
|
}
|
|
|
|
/**
|
|
* An immediately invoked function expression (IIFE).
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* (function() { return this; })()
|
|
* ```
|
|
*/
|
|
class ImmediatelyInvokedFunctionExpr extends Function {
|
|
/** The invocation expression of this IIFE. */
|
|
InvokeExpr invk;
|
|
/**
|
|
* The kind of invocation by which this IIFE is invoked: `"call"`
|
|
* for a direct function call, `"call"` or `"apply"` for a reflective
|
|
* invocation through `call` or `apply`, respectively.
|
|
*/
|
|
string kind;
|
|
|
|
ImmediatelyInvokedFunctionExpr() {
|
|
// direct call
|
|
this = invk.getCallee().getUnderlyingValue() and kind = "direct"
|
|
or
|
|
// reflective call
|
|
exists(MethodCallExpr mce | mce = invk |
|
|
this = mce.getReceiver().getUnderlyingValue() and
|
|
kind = mce.getMethodName() and
|
|
(kind = "call" or kind = "apply")
|
|
)
|
|
}
|
|
|
|
/** Gets the invocation of this IIFE. */
|
|
InvokeExpr getInvocation() { result = invk }
|
|
|
|
/**
|
|
* Gets a string describing the way this IIFE is invoked
|
|
* (one of `"direct"`, `"call"` or `"apply"`).
|
|
*/
|
|
string getInvocationKind() { result = kind }
|
|
|
|
/**
|
|
* Gets the `i`th argument of this IIFE.
|
|
*/
|
|
Expr getArgument(int i) { result = invk.getArgument(i) }
|
|
|
|
/**
|
|
* Holds if the `i`th argument of this IIFE is a spread element.
|
|
*/
|
|
predicate isSpreadArgument(int i) { invk.isSpreadArgument(i) }
|
|
|
|
/**
|
|
* Gets the offset of argument positions relative to parameter
|
|
* positions: for direct IIFEs the offset is zero, for IIFEs
|
|
* using `Function.prototype.call` the offset is one, and for
|
|
* IIFEs using `Function.prototype.apply` the offset is not defined.
|
|
*/
|
|
int getArgumentOffset() {
|
|
kind = "direct" and result = 0
|
|
or
|
|
kind = "call" and result = 1
|
|
}
|
|
|
|
/**
|
|
* Holds if `p` is a parameter of this IIFE and `arg` is
|
|
* the corresponding argument.
|
|
*
|
|
* Note that rest parameters do not have corresponding arguments;
|
|
* conversely, arguments after a spread element do not have a corresponding
|
|
* parameter.
|
|
*/
|
|
predicate argumentPassing(Parameter p, Expr arg) {
|
|
exists(int parmIdx, int argIdx |
|
|
p = this.getParameter(parmIdx) and
|
|
not p.isRestParameter() and
|
|
argIdx = parmIdx + this.getArgumentOffset() and
|
|
arg = this.getArgument(argIdx) and
|
|
not this.isSpreadArgument([0 .. argIdx])
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An `await` expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* await p()
|
|
* ```
|
|
*/
|
|
class AwaitExpr extends @await_expr, Expr {
|
|
/** Gets the operand of this `await` expression. */
|
|
Expr getOperand() { result = this.getChildExpr(0) }
|
|
|
|
override predicate isImpure() { any() }
|
|
|
|
override ControlFlowNode getFirstControlFlowNode() {
|
|
result = this.getOperand().getFirstControlFlowNode()
|
|
}
|
|
|
|
override string getAPrimaryQlClass() { result = "AwaitExpr" }
|
|
}
|
|
|
|
/**
|
|
* A `function.sent` expression.
|
|
*
|
|
* Inside a generator function, `function.sent` evaluates to the value passed
|
|
* to the generator by the `next` method that most recently resumed execution
|
|
* of the generator.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* function.sent
|
|
* ```
|
|
*/
|
|
class FunctionSentExpr extends @function_sent_expr, Expr {
|
|
override predicate isImpure() { none() }
|
|
|
|
override string getAPrimaryQlClass() { result = "FunctionSentExpr" }
|
|
}
|
|
|
|
/**
|
|
* A decorator applied to a class, property or member definition.
|
|
*
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* @A @testable(true) class C { // `@A` and `@testable(true)` are decorators
|
|
* @Test test1() { // `@Test` is a decorator
|
|
* }
|
|
* }
|
|
* ```
|
|
*/
|
|
class Decorator extends @decorator, Expr {
|
|
/**
|
|
* Gets the element this decorator is applied to.
|
|
*
|
|
* For example, in the class declaration `@A class C { }`,
|
|
* the element decorator `@A` is applied to is `C`.
|
|
*/
|
|
Decoratable getElement() { this = result.getADecorator() }
|
|
|
|
/**
|
|
* Gets the expression of this decorator.
|
|
*
|
|
* For example, the decorator `@A` has expression `A`,
|
|
* and `@testable(true)` has expression `testable(true)`.
|
|
*/
|
|
Expr getExpression() { result = this.getChildExpr(0) }
|
|
|
|
override ControlFlowNode getFirstControlFlowNode() {
|
|
result = this.getExpression().getFirstControlFlowNode()
|
|
}
|
|
|
|
override string getAPrimaryQlClass() { result = "Decorator" }
|
|
}
|
|
|
|
/**
|
|
* A program element to which decorators can be applied,
|
|
* that is, a class, a property or a member definition.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* @A @testable(true) class C { // class `C` is decoratable
|
|
* @Test test1() { // method `test1` is decoratable
|
|
* }
|
|
* }
|
|
* ```
|
|
*/
|
|
class Decoratable extends AstNode {
|
|
Decoratable() {
|
|
this instanceof ClassDefinition or
|
|
this instanceof Property or
|
|
this instanceof MemberDefinition or
|
|
this instanceof EnumDeclaration or
|
|
this instanceof Parameter
|
|
}
|
|
|
|
/**
|
|
* Gets the `i`th decorator applied to this element.
|
|
*/
|
|
Decorator getDecorator(int i) {
|
|
result = this.(ClassDefinition).getDecorator(i) or
|
|
result = this.(Property).getDecorator(i) or
|
|
result = this.(MemberDefinition).getDecorator(i) or
|
|
result = this.(EnumDeclaration).getDecorator(i) or
|
|
result = this.(Parameter).getDecorator(i)
|
|
}
|
|
|
|
/**
|
|
* Gets a decorator applied to this element, if any.
|
|
*/
|
|
Decorator getADecorator() { result = this.getDecorator(_) }
|
|
}
|
|
|
|
/**
|
|
* A function-bind expression.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* b::f
|
|
* ::b.f
|
|
* ```
|
|
*/
|
|
class FunctionBindExpr extends @bind_expr, Expr {
|
|
/**
|
|
* Gets the object of this function bind expression; undefined for
|
|
* expressions of the form `::b.f`.
|
|
*/
|
|
Expr getObject() { result = this.getChildExpr(0) }
|
|
|
|
/** Gets the callee of this function bind expression. */
|
|
Expr getCallee() { result = this.getChildExpr(1) }
|
|
|
|
override ControlFlowNode getFirstControlFlowNode() {
|
|
result = this.getObject().getFirstControlFlowNode()
|
|
or
|
|
not exists(this.getObject()) and result = this.getCallee().getFirstControlFlowNode()
|
|
}
|
|
|
|
override string getAPrimaryQlClass() { result = "FunctionBindExpr" }
|
|
}
|
|
|
|
/**
|
|
* A dynamic import expression.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```
|
|
* import("fs")
|
|
* import("foo", { with: { type: "json" }})
|
|
* ```
|
|
*/
|
|
class DynamicImportExpr extends @dynamic_import, Expr, Import {
|
|
/** Gets the expression specifying the path of the imported module. */
|
|
Expr getSource() { result = this.getChildExpr(0) }
|
|
|
|
override ControlFlowNode getFirstControlFlowNode() {
|
|
result = this.getSource().getFirstControlFlowNode()
|
|
}
|
|
|
|
override PathExpr getImportedPath() { result = this.getSource() }
|
|
|
|
/**
|
|
* Gets the second "argument" to the import expression, that is, the `Y` in `import(X, Y)`.
|
|
*
|
|
* For example, gets the `{ with: { type: "json" }}` expression in the following:
|
|
* ```js
|
|
* import('foo', { with: { type: "json" }})
|
|
* ```
|
|
*/
|
|
Expr getImportOptions() { result = this.getChildExpr(1) }
|
|
|
|
/**
|
|
* DEPRECATED: use `getImportOptions` instead.
|
|
* Gets the second "argument" to the import expression, that is, the `Y` in `import(X, Y)`.
|
|
*
|
|
* For example, gets the `{ with: { type: "json" }}` expression in the following:
|
|
* ```js
|
|
* import('foo', { with: { type: "json" }})
|
|
* ```
|
|
*/
|
|
deprecated Expr getImportAttributes() { result = this.getImportOptions() }
|
|
|
|
override Module getEnclosingModule() { result = this.getTopLevel() }
|
|
|
|
override DataFlow::Node getImportedModuleNode() { result = DataFlow::valueNode(this) }
|
|
|
|
override string getAPrimaryQlClass() { result = "DynamicImportExpr" }
|
|
}
|
|
|
|
/** A literal path expression appearing in a dynamic import. */
|
|
private class LiteralDynamicImportPath extends PathExpr, ConstantString {
|
|
LiteralDynamicImportPath() {
|
|
exists(DynamicImportExpr di | this.getParentExpr*() = di.getSource())
|
|
}
|
|
|
|
override string getValue() { result = this.getStringValue() }
|
|
}
|
|
|
|
/**
|
|
* A call or member access that evaluates to `undefined` if its base operand evaluates to
|
|
* `undefined` or `null`.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* x ?? f
|
|
* ```
|
|
*/
|
|
class OptionalUse extends Expr, @optionalchainable {
|
|
OptionalUse() { isOptionalChaining(this) }
|
|
|
|
override string getAPrimaryQlClass() { result = "OptionalUse" }
|
|
}
|
|
|
|
private class ChainElem extends Expr, @optionalchainable {
|
|
/**
|
|
* Gets the base operand of this chainable element.
|
|
*/
|
|
ChainElem getChainBase() {
|
|
result = this.(CallExpr).getCallee() or
|
|
result = this.(PropAccess).getBase()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* INTERNAL: This class should not be used by queries.
|
|
*
|
|
* The root in a chain of calls or property accesses, where at least one call or property access is optional.
|
|
*/
|
|
class OptionalChainRoot extends ChainElem {
|
|
OptionalUse optionalUse;
|
|
|
|
OptionalChainRoot() {
|
|
this.getChainBase*() = optionalUse and
|
|
not exists(ChainElem other | this = other.getChainBase())
|
|
}
|
|
|
|
/**
|
|
* Gets an optional call or property access in the chain of this root.
|
|
*/
|
|
OptionalUse getAnOptionalUse() { result = optionalUse }
|
|
}
|
|
|
|
/**
|
|
* An `import.meta` expression.
|
|
*
|
|
* Example:
|
|
* ```js
|
|
* let url = import.meta.url;
|
|
* ```
|
|
*/
|
|
class ImportMetaExpr extends @import_meta_expr, Expr {
|
|
override predicate isImpure() { none() }
|
|
|
|
override string getAPrimaryQlClass() { result = "ImportMetaExpr" }
|
|
}
|
|
|
|
/**
|
|
* A placeholder for some code generated by a templating engine,
|
|
* speculatively parsed as an expression.
|
|
*
|
|
* For example, the right-hand side of the following assignments will each be parsed
|
|
* as `GeneratedNodeExpr` nodes:
|
|
* ```js
|
|
* let data1 = {{ user_data1 }};
|
|
* let data2 = {{{ user_data2 }}};
|
|
* ```
|
|
*
|
|
* Note that templating placeholders occurring inside strings literals are not parsed,
|
|
* and are simply seen as being part of the string literal.
|
|
* For example, following snippet does not contain any `GeneratedCodeExpr` nodes:
|
|
* ```js
|
|
* let data1 = "{{ user_data }}";
|
|
* ```
|
|
*/
|
|
class GeneratedCodeExpr extends @generated_code_expr, Expr {
|
|
/** Gets the placeholder tag that was parsed as an expression. */
|
|
Templating::TemplatePlaceholderTag getPlaceholderTag() { this = result.getEnclosingExpr() }
|
|
|
|
override string getAPrimaryQlClass() { result = "GeneratedCodeExpr" }
|
|
}
|