mirror of
https://github.com/github/codeql.git
synced 2025-12-17 17:23:36 +01:00
243 lines
8.2 KiB
Plaintext
243 lines
8.2 KiB
Plaintext
/** Provides classes for working with standard library objects. */
|
|
|
|
import javascript
|
|
|
|
/**
|
|
* A call to `Object.defineProperty`.
|
|
*/
|
|
class CallToObjectDefineProperty extends DataFlow::MethodCallNode {
|
|
CallToObjectDefineProperty() {
|
|
exists(GlobalVariable obj |
|
|
obj.getName() = "Object" and
|
|
this.calls(DataFlow::valueNode(obj.getAnAccess()), "defineProperty")
|
|
)
|
|
}
|
|
|
|
/** Gets the data flow node denoting the object on which the property is defined. */
|
|
DataFlow::Node getBaseObject() { result = this.getArgument(0) }
|
|
|
|
/** Gets the name of the property being defined, if it can be determined. */
|
|
string getPropertyName() { result = this.getArgument(1).getStringValue() }
|
|
|
|
/** Gets the data flow node denoting the descriptor of the property being defined. */
|
|
DataFlow::Node getPropertyDescriptor() { result = this.getArgument(2) }
|
|
|
|
/**
|
|
* Holds if there is an assignment to property `name` to the
|
|
* attributes object on this node, and the right hand side of the
|
|
* assignment is `rhs`.
|
|
*/
|
|
predicate hasPropertyAttributeWrite(string name, DataFlow::Node rhs) {
|
|
exists(DataFlow::SourceNode descriptor |
|
|
descriptor.flowsTo(this.getPropertyDescriptor()) and
|
|
descriptor.hasPropertyWrite(name, rhs)
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A direct call to `eval`.
|
|
*/
|
|
class DirectEval extends CallExpr {
|
|
DirectEval() { this.getCallee().(GlobalVarAccess).getName() = "eval" }
|
|
|
|
/** Holds if this call could affect the value of `lv`. */
|
|
predicate mayAffect(LocalVariable lv) { this.getParent+() = lv.getScope().getScopeElement() }
|
|
}
|
|
|
|
/**
|
|
* Models `Array.prototype.map` and friends as partial invocations that pass their second
|
|
* argument as the receiver to the callback.
|
|
*/
|
|
private class ArrayIterationCallbackAsPartialInvoke extends DataFlow::PartialInvokeNode::Range,
|
|
DataFlow::MethodCallNode
|
|
{
|
|
ArrayIterationCallbackAsPartialInvoke() {
|
|
this.getNumArgument() = 2 and
|
|
// Filter out library methods named 'forEach' etc
|
|
not DataFlow::moduleImport(_).flowsTo(this.getReceiver()) and
|
|
this.getMethodName() = ["filter", "forEach", "map", "some", "every"]
|
|
}
|
|
|
|
override DataFlow::Node getBoundReceiver(DataFlow::Node callback) {
|
|
callback = this.getArgument(0) and
|
|
result = this.getArgument(1)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A flow step propagating the exception thrown from a callback to a method whose name coincides
|
|
* a built-in Array iteration method, such as `forEach` or `map`.
|
|
*/
|
|
private class IteratorExceptionStep extends DataFlow::LegacyFlowStep {
|
|
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
|
exists(DataFlow::MethodCallNode call |
|
|
call.getMethodName() = ["forEach", "each", "map", "filter", "some", "every", "fold", "reduce"] and
|
|
pred = call.getAnArgument().(DataFlow::FunctionNode).getExceptionalReturn() and
|
|
succ = call.getExceptionalReturn()
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A call to `String.prototype.replace`.
|
|
*
|
|
* We heuristically include any call to a method called `replace`, provided it either
|
|
* has exactly two arguments, or local data flow suggests that the receiver may be a string.
|
|
*/
|
|
class StringReplaceCall extends DataFlow::MethodCallNode {
|
|
StringReplaceCall() {
|
|
this.getMethodName() = ["replace", "replaceAll"] and
|
|
(this.getNumArgument() = 2 or this.getReceiver().mayHaveStringValue(_))
|
|
}
|
|
|
|
/** Gets the regular expression passed as the first argument to `replace`, if any. */
|
|
DataFlow::RegExpCreationNode getRegExp() { result.flowsTo(this.getArgument(0)) }
|
|
|
|
/** Gets a string that is being replaced by this call. */
|
|
string getAReplacedString() {
|
|
result = this.getRegExp().getRoot().getAMatchedString() or
|
|
this.getArgument(0).mayHaveStringValue(result)
|
|
}
|
|
|
|
/**
|
|
* Gets the second argument of this call to `replace`, which is either a string
|
|
* or a callback.
|
|
*/
|
|
DataFlow::Node getRawReplacement() { result = this.getArgument(1) }
|
|
|
|
/**
|
|
* Gets a function flowing into the second argument of this call to `replace`.
|
|
*/
|
|
DataFlow::FunctionNode getReplacementCallback() { result = this.getCallback(1) }
|
|
|
|
/**
|
|
* Holds if this is a global replacement, that is, the first argument is a regular expression
|
|
* with the `g` flag, or this is a call to `.replaceAll()`.
|
|
*/
|
|
predicate isGlobal() { this.getRegExp().isGlobal() or this.getMethodName() = "replaceAll" }
|
|
|
|
/**
|
|
* Holds if this is a global replacement, that is, the first argument is a regular expression
|
|
* with the `g` flag or unknown flags, or this is a call to `.replaceAll()`.
|
|
*/
|
|
predicate maybeGlobal() { this.getRegExp().maybeGlobal() or this.getMethodName() = "replaceAll" }
|
|
|
|
/**
|
|
* Holds if this call to `replace` replaces `old` with `new`.
|
|
*/
|
|
predicate replaces(string old, string new) {
|
|
exists(string rawNew |
|
|
old = this.getAReplacedString() and
|
|
this.getRawReplacement().mayHaveStringValue(rawNew) and
|
|
new = rawNew.replaceAll("$&", old)
|
|
)
|
|
or
|
|
exists(DataFlow::FunctionNode replacer, DataFlow::PropRead pr, DataFlow::ObjectLiteralNode map |
|
|
replacer = this.getCallback(1) and
|
|
replacer.getParameter(0).flowsToExpr(pr.getPropertyNameExpr()) and
|
|
pr = map.getAPropertyRead() and
|
|
pr.flowsTo(replacer.getAReturn()) and
|
|
map.hasPropertyWrite(old, any(DataFlow::Node repl | repl.getStringValue() = new))
|
|
)
|
|
or
|
|
// str.replace(regex, match => {
|
|
// if (match === 'old') return 'new';
|
|
// if (match === 'foo') return 'bar';
|
|
// ...
|
|
// })
|
|
exists(
|
|
DataFlow::FunctionNode replacer, ConditionGuardNode guard, EqualityTest test,
|
|
DataFlow::Node ret
|
|
|
|
|
replacer = this.getCallback(1) and
|
|
guard.getOutcome() = test.getPolarity() and
|
|
guard.getTest() = test and
|
|
replacer.getParameter(0).flowsToExpr(test.getAnOperand()) and
|
|
test.getAnOperand().getStringValue() = old and
|
|
ret = replacer.getAReturn() and
|
|
guard.dominates(ret.getBasicBlock()) and
|
|
new = ret.getStringValue()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if this call takes a regexp containing a wildcard-like term such as `.`.
|
|
*
|
|
* Also see `RegExp::isWildcardLike`.
|
|
*/
|
|
final predicate hasRegExpContainingWildcard() {
|
|
RegExp::isWildcardLike(this.getRegExp().getRoot().getAChild*())
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A call to `String.prototype.split`.
|
|
*
|
|
* We heuristically include any call to a method called `split`, provided it either
|
|
* has one or two arguments, or local data flow suggests that the receiver may be a string.
|
|
*/
|
|
class StringSplitCall extends DataFlow::MethodCallNode {
|
|
StringSplitCall() {
|
|
this.getMethodName() = "split" and
|
|
(this.getNumArgument() = [1, 2] or this.getReceiver().mayHaveStringValue(_))
|
|
}
|
|
|
|
/**
|
|
* Gets a string that determines where the string is split.
|
|
*/
|
|
string getSeparator() {
|
|
this.getArgument(0).mayHaveStringValue(result)
|
|
or
|
|
result =
|
|
this.getArgument(0)
|
|
.getALocalSource()
|
|
.(DataFlow::RegExpCreationNode)
|
|
.getRoot()
|
|
.getAMatchedString()
|
|
}
|
|
|
|
/**
|
|
* Gets the DataFlow::Node for the base string that is split.
|
|
*/
|
|
DataFlow::Node getBaseString() { result = this.getReceiver() }
|
|
|
|
/**
|
|
* Gets a read of the `i`th element from the split string.
|
|
*/
|
|
bindingset[i]
|
|
DataFlow::Node getASubstringRead(int i) { result = this.getAPropertyRead(i.toString()) }
|
|
}
|
|
|
|
/**
|
|
* A call to `Object.prototype.hasOwnProperty`, `Object.hasOwn`, or a library that implements
|
|
* the same functionality.
|
|
*/
|
|
class HasOwnPropertyCall extends DataFlow::Node instanceof DataFlow::CallNode {
|
|
DataFlow::Node object;
|
|
DataFlow::Node property;
|
|
|
|
HasOwnPropertyCall() {
|
|
// Make sure we handle reflective calls since libraries love to do that.
|
|
super.getCalleeNode().getALocalSource().(DataFlow::PropRead).getPropertyName() =
|
|
"hasOwnProperty" and
|
|
object = super.getReceiver() and
|
|
property = super.getArgument(0)
|
|
or
|
|
this =
|
|
[
|
|
DataFlow::globalVarRef("Object").getAMemberCall("hasOwn"), //
|
|
DataFlow::moduleImport("has").getACall(), //
|
|
LodashUnderscore::member("has").getACall()
|
|
] and
|
|
object = super.getArgument(0) and
|
|
property = super.getArgument(1)
|
|
}
|
|
|
|
/** Gets the object whose property is being checked. */
|
|
DataFlow::Node getObject() { result = object }
|
|
|
|
/** Gets the property being checked. */
|
|
DataFlow::Node getProperty() { result = property }
|
|
}
|