Skip to content

JS: Make the edges of API-graphs into IPA types #7180

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Dec 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
433 changes: 292 additions & 141 deletions javascript/ql/lib/semmle/javascript/ApiGraphs.qll

Large diffs are not rendered by default.

28 changes: 27 additions & 1 deletion javascript/ql/lib/semmle/javascript/Constants.qll
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
*/

import javascript
private import semmle.javascript.internal.CachedStages

/**
* An expression that evaluates to a constant primitive value.
*/
cached
abstract class ConstantExpr extends Expr { }

/**
Expand All @@ -16,15 +18,19 @@ module SyntacticConstants {
/**
* An expression that evaluates to a constant value according to a bottom-up syntactic analysis.
*/
cached
abstract class SyntacticConstant extends ConstantExpr { }

/**
* A literal primitive expression.
*
* Note that `undefined`, `NaN` and `Infinity` are global variables, and are not covered by this class.
*/
cached
class PrimitiveLiteralConstant extends SyntacticConstant {
cached
PrimitiveLiteralConstant() {
Stages::Ast::ref() and
this instanceof NumberLiteral
or
this instanceof StringLiteral
Expand All @@ -43,19 +49,27 @@ module SyntacticConstants {
/**
* A literal null expression.
*/
class NullConstant extends SyntacticConstant, NullLiteral { }
cached
class NullConstant extends SyntacticConstant, NullLiteral {
cached
NullConstant() { Stages::Ast::ref() and this = this }
}

/**
* A unary operation on a syntactic constant.
*/
cached
class UnaryConstant extends SyntacticConstant, UnaryExpr {
cached
UnaryConstant() { getOperand() instanceof SyntacticConstant }
}

/**
* A binary operation on syntactic constants.
*/
cached
class BinaryConstant extends SyntacticConstant, BinaryExpr {
cached
BinaryConstant() {
getLeftOperand() instanceof SyntacticConstant and
getRightOperand() instanceof SyntacticConstant
Expand All @@ -65,7 +79,9 @@ module SyntacticConstants {
/**
* A conditional expression on syntactic constants.
*/
cached
class ConditionalConstant extends SyntacticConstant, ConditionalExpr {
cached
ConditionalConstant() {
getCondition() instanceof SyntacticConstant and
getConsequent() instanceof SyntacticConstant and
Expand All @@ -76,7 +92,9 @@ module SyntacticConstants {
/**
* A use of the global variable `undefined` or `void e`.
*/
cached
class UndefinedConstant extends SyntacticConstant {
cached
UndefinedConstant() {
this.(GlobalVarAccess).getName() = "undefined" or
this instanceof VoidExpr
Expand All @@ -86,21 +104,27 @@ module SyntacticConstants {
/**
* A use of the global variable `NaN`.
*/
cached
class NaNConstant extends SyntacticConstant {
cached
NaNConstant() { this.(GlobalVarAccess).getName() = "NaN" }
}

/**
* A use of the global variable `Infinity`.
*/
cached
class InfinityConstant extends SyntacticConstant {
cached
InfinityConstant() { this.(GlobalVarAccess).getName() = "Infinity" }
}

/**
* An expression that wraps the syntactic constant it evaluates to.
*/
cached
class WrappedConstant extends SyntacticConstant {
cached
WrappedConstant() { getUnderlyingValue() instanceof SyntacticConstant }
}

Expand All @@ -123,6 +147,8 @@ module SyntacticConstants {
/**
* An expression that evaluates to a constant string.
*/
cached
class ConstantString extends ConstantExpr {
cached
ConstantString() { exists(getStringValue()) }
}
9 changes: 7 additions & 2 deletions javascript/ql/lib/semmle/javascript/Expr.qll
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ class ExprOrType extends @expr_or_type, Documentable {
*
* Also see `getUnderlyingReference` and `stripParens`.
*/
Expr getUnderlyingValue() { result = this }
cached
Expr getUnderlyingValue() { Stages::Ast::ref() and result = this }
}

/**
Expand Down Expand Up @@ -274,7 +275,11 @@ private DataFlow::Node getCatchParameterFromStmt(Stmt stmt) {
*/
class Identifier extends @identifier, ExprOrType {
/** Gets the name of this identifier. */
string getName() { literals(result, _, this) }
cached
string getName() {
Stages::Ast::ref() and
literals(result, _, this)
}

override string getAPrimaryQlClass() { result = "Identifier" }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ module MembershipCandidate {
EnumerationRegExp enumeration;
boolean polarity;

pragma[nomagic]
RegExpEnumerationCandidate() {
exists(DataFlow::MethodCallNode mcn, DataFlow::Node base, string m, DataFlow::Node firstArg |
(
Expand Down
66 changes: 39 additions & 27 deletions javascript/ql/lib/semmle/javascript/dataflow/Configuration.qll
Original file line number Diff line number Diff line change
Expand Up @@ -939,18 +939,21 @@ private predicate basicFlowStepNoBarrier(
* This predicate is field insensitive (it does not distinguish between `x` and `x.p`)
* and hence should only be used for purposes of approximation.
*/
pragma[inline]
pragma[noinline]
private predicate exploratoryFlowStep(
DataFlow::Node pred, DataFlow::Node succ, DataFlow::Configuration cfg
) {
basicFlowStepNoBarrier(pred, succ, _, cfg) or
exploratoryLoadStep(pred, succ, cfg) or
isAdditionalLoadStoreStep(pred, succ, _, _, cfg) or
// the following three disjuncts taken together over-approximate flow through
// higher-order calls
exploratoryCallbackStep(pred, succ) or
succ = pred.(DataFlow::FunctionNode).getAParameter() or
exploratoryBoundInvokeStep(pred, succ)
isRelevantForward(pred, cfg) and
(
basicFlowStepNoBarrier(pred, succ, _, cfg) or
exploratoryLoadStep(pred, succ, cfg) or
isAdditionalLoadStoreStep(pred, succ, _, _, cfg) or
// the following three disjuncts taken together over-approximate flow through
// higher-order calls
exploratoryCallbackStep(pred, succ) or
succ = pred.(DataFlow::FunctionNode).getAParameter() or
exploratoryBoundInvokeStep(pred, succ)
)
}

/**
Expand Down Expand Up @@ -1024,6 +1027,7 @@ private string getAPropertyUsedInLoadStore(DataFlow::Configuration cfg) {
* Holds if there exists a store-step from `pred` to `succ` under configuration `cfg`,
* and somewhere in the program there exists a load-step that could possibly read the stored value.
*/
pragma[noinline]
private predicate exploratoryForwardStoreStep(
DataFlow::Node pred, DataFlow::Node succ, DataFlow::Configuration cfg
) {
Expand Down Expand Up @@ -1075,8 +1079,10 @@ private string getABackwardsRelevantStoreProperty(DataFlow::Configuration cfg) {
private predicate isRelevantForward(DataFlow::Node nd, DataFlow::Configuration cfg) {
isSource(nd, cfg, _) and isLive()
or
exists(DataFlow::Node mid | isRelevantForward(mid, cfg) |
exploratoryFlowStep(mid, nd, cfg) or
exists(DataFlow::Node mid |
exploratoryFlowStep(mid, nd, cfg)
or
isRelevantForward(mid, cfg) and
exploratoryForwardStoreStep(mid, nd, cfg)
)
}
Expand All @@ -1098,11 +1104,10 @@ private predicate isRelevant(DataFlow::Node nd, DataFlow::Configuration cfg) {
private predicate isRelevantBackStep(
DataFlow::Node mid, DataFlow::Node nd, DataFlow::Configuration cfg
) {
exploratoryFlowStep(nd, mid, cfg)
or
isRelevantForward(nd, cfg) and
(
exploratoryFlowStep(nd, mid, cfg) or
exploratoryBackwardStoreStep(nd, mid, cfg)
)
exploratoryBackwardStoreStep(nd, mid, cfg)
}

/**
Expand Down Expand Up @@ -1273,23 +1278,30 @@ private predicate parameterPropRead(
DataFlow::Node arg, string prop, DataFlow::Node succ, DataFlow::Configuration cfg,
PathSummary summary
) {
exists(Function f, DataFlow::Node read, DataFlow::Node invk |
exists(Function f, DataFlow::Node read, DataFlow::Node invk, DataFlow::Node parm |
reachesReturn(f, read, cfg, summary) and
parameterPropReadStep(parm, read, prop, cfg, arg, invk, f, succ)
)
}

// all the non-recursive parts of parameterPropRead outlined into a precomputed predicate
pragma[noinline]
private predicate parameterPropReadStep(
DataFlow::SourceNode parm, DataFlow::Node read, string prop, DataFlow::Configuration cfg,
DataFlow::Node arg, DataFlow::Node invk, Function f, DataFlow::Node succ
) {
(
not f.isAsyncOrGenerator() and invk = succ
or
// load from an immediately awaited function call
f.isAsync() and
invk = getAwaitOperand(succ)
|
exists(DataFlow::SourceNode parm |
callInputStep(f, invk, arg, parm, cfg) and
(
reachesReturn(f, read, cfg, summary) and
read = parm.getAPropertyRead(prop)
or
reachesReturn(f, read, cfg, summary) and
exists(DataFlow::Node use | parm.flowsTo(use) | isAdditionalLoadStep(use, read, prop, cfg))
)
)
) and
callInputStep(f, invk, arg, parm, cfg) and
(
read = parm.getAPropertyRead(prop)
or
exists(DataFlow::Node use | parm.flowsTo(use) | isAdditionalLoadStep(use, read, prop, cfg))
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,12 @@ module TaintTracking {
* of the standard library. Override `Configuration::isSanitizerGuard`
* for analysis-specific taint sanitizer guards.
*/
cached
abstract class AdditionalSanitizerGuardNode extends SanitizerGuardNode {
/**
* Holds if this guard applies to the flow in `cfg`.
*/
cached
abstract predicate appliesTo(Configuration cfg);
}

Expand Down Expand Up @@ -1127,7 +1129,7 @@ module TaintTracking {
idx = astNode.getAnOperand() and
idx.getPropertyNameExpr() = x and
// and the other one is guaranteed to be `undefined`
forex(InferredType tp | tp = undef.getAType() | tp = TTUndefined())
unique(InferredType tp | tp = pragma[only_bind_into](undef.getAType())) = TTUndefined()
)
}

Expand Down
2 changes: 1 addition & 1 deletion javascript/ql/lib/semmle/javascript/frameworks/D3.qll
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ module D3 {
or
result = API::moduleImport("d3-node").getInstance().getMember("d3")
or
result = API::root().getASuccessor(any(D3GlobalEntry i))
result = any(D3GlobalEntry i).getNode()
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ module History {
* Gets a reference to the [`history`](https://npmjs.org/package/history) library.
*/
private API::Node history() {
result = [API::moduleImport("history"), API::root().getASuccessor(any(HistoryGlobalEntry h))]
result = [API::moduleImport("history"), any(HistoryGlobalEntry h).getNode()]
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ private module Immutable {
API::Node immutableImport() {
result = API::moduleImport("immutable")
or
result = API::root().getASuccessor(any(ImmutableGlobalEntry i))
result = any(ImmutableGlobalEntry i).getNode()
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ private module Console {
*/
private API::Node console() {
result = API::moduleImport("console") or
result = API::root().getASuccessor(any(ConsoleGlobalEntry e))
result = any(ConsoleGlobalEntry e).getNode()
}

/**
Expand Down
2 changes: 1 addition & 1 deletion javascript/ql/lib/semmle/javascript/frameworks/Nest.qll
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ module NestJS {
private API::Node validationPipe() {
result = nestjs().getMember("ValidationPipe")
or
result = API::root().getASuccessor(any(ValidationNodeEntry e))
result = any(ValidationNodeEntry e).getNode()
}

/**
Expand Down
4 changes: 1 addition & 3 deletions javascript/ql/lib/semmle/javascript/frameworks/Redux.qll
Original file line number Diff line number Diff line change
Expand Up @@ -1111,9 +1111,7 @@ module Redux {

/** A heuristic call to `connect`, recognized by it taking arguments named `mapStateToProps` and `mapDispatchToProps`. */
private class HeuristicConnectFunction extends ConnectCall {
HeuristicConnectFunction() {
this = API::root().getASuccessor(any(HeuristicConnectEntryPoint e)).getACall()
}
HeuristicConnectFunction() { this = any(HeuristicConnectEntryPoint e).getNode().getACall() }

override API::Node getMapStateToProps() {
result = getAParameter() and
Expand Down
4 changes: 2 additions & 2 deletions javascript/ql/lib/semmle/javascript/frameworks/Vue.qll
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ module Vue {
API::Node vueLibrary() {
result = API::moduleImport("vue")
or
result = API::root().getASuccessor(any(GlobalVueEntryPoint e))
result = any(GlobalVueEntryPoint e).getNode()
}

/**
Expand All @@ -51,7 +51,7 @@ module Vue {
or
result = vueLibrary().getMember("component").getReturn()
or
result = API::root().getASuccessor(any(VueFileImportEntryPoint e))
result = any(VueFileImportEntryPoint e).getNode()
}

/**
Expand Down
Loading