Merge pull request #2224 from asger-semmle/access-paths-with-source-node-root

Approved by max-schaefer
This commit is contained in:
semmle-qlci
2019-11-07 15:46:14 +00:00
committed by GitHub
10 changed files with 364 additions and 124 deletions

View File

@@ -230,7 +230,7 @@ module Closure {
* Gets the closure namespace path addressed by the given data flow node, if any.
*/
string getClosureNamespaceFromSourceNode(DataFlow::SourceNode node) {
result = GlobalAccessPath::getAccessPath(node) and
node = AccessPath::getAReferenceOrAssignmentTo(result) and
hasClosureNamespacePrefix(result)
}
@@ -238,7 +238,7 @@ module Closure {
* Gets the closure namespace path written to by the given property write, if any.
*/
string getWrittenClosureNamespace(DataFlow::PropWrite node) {
result = GlobalAccessPath::fromRhs(node.getRhs()) and
node.getRhs() = AccessPath::getAnAssignmentTo(result) and
hasClosureNamespacePrefix(result)
}

View File

@@ -4,7 +4,55 @@
import javascript
deprecated
module GlobalAccessPath {
/**
* DEPRECATED. Instead use `AccessPath::getAReferenceTo` with the result and parameter reversed.
*/
pragma[inline]
string fromReference(DataFlow::Node node) {
node = AccessPath::getAReferenceTo(result)
}
/**
* DEPRECATED. Instead use `AccessPath::getAnAssignmentTo` with the result and parameter reversed.
*/
pragma[inline]
string fromRhs(DataFlow::Node node) {
node = AccessPath::getAnAssignmentTo(result)
}
/**
* DEPRECATED. Use `AccessPath::getAReferenceOrAssignmentTo`.
*/
pragma[inline]
string getAccessPath(DataFlow::Node node) {
result = fromReference(node)
or
not exists(fromReference(node)) and
result = fromRhs(node)
}
}
module AccessPath {
/**
* A source node that can be the root of an access path.
*/
class Root extends DataFlow::SourceNode {
Root() {
not this.accessesGlobal(_) and
not this instanceof DataFlow::PropRead and
not this instanceof PropertyProjection and
not this instanceof Closure::ClosureNamespaceAccess and
not this = DataFlow::parameterNode(any(ImmediatelyInvokedFunctionExpr iife).getAParameter())
}
/** Holds if this represents the root of the global access path. */
predicate isGlobal() {
this = DataFlow::globalAccessPathRootPseudoNode()
}
}
/**
* A local variable with exactly one definition, not counting implicit initialization.
*/
@@ -21,52 +69,68 @@ module GlobalAccessPath {
}
/**
* Gets the global access path referred to by `node`.
* Appends a single property name onto the access path `base`, where
* the empty string represents the empty access path.
*/
bindingset[base, prop]
private string join(string base, string prop) {
base = "" and result = prop
or
base != "" and
result = base + "." + prop
}
/**
* Gets the access path relative to `root` referred to by `node`.
*
* This holds for direct references as well as for aliases
* established through local data flow.
*
* Examples:
* ```
* function f() {
* let v = foo.bar; // reference to 'foo.bar'
* v.baz; // reference to 'foo.bar.baz'
* function f(x) {
* let a = x.f.g; // access path relative to 'x' is 'f.g'
* let b = a.h; // access path relative to 'x' is 'f.g.h'
* }
*
* (function(ns) {
* ns.x; // reference to 'NS.x'
* })(NS = NS || {});
* ```
*/
cached
string fromReference(DataFlow::Node node) {
result = fromReference(node.getImmediatePredecessor())
private string fromReference(DataFlow::Node node, Root root) {
root = node and
not root.isGlobal() and
result = ""
or
result = fromReference(node.getImmediatePredecessor(), root)
or
exists(EffectivelyConstantVariable var |
var.isCaptured() and
node.asExpr() = var.getAnAccess() and
result = fromReference(var.getValue())
result = fromReference(var.getValue(), root)
)
or
node.accessesGlobal(result) and
result != "undefined"
result != "undefined" and
root.isGlobal()
or
not node.accessesGlobal(_) and
exists(DataFlow::PropRead prop | node = prop |
result = fromReference(prop.getBase()) + "." + prop.getPropertyName()
result = join(fromReference(prop.getBase(), root), prop.getPropertyName())
)
or
exists(Closure::ClosureNamespaceAccess acc | node = acc | result = acc.getClosureNamespace())
exists(Closure::ClosureNamespaceAccess acc | node = acc |
result = acc.getClosureNamespace() and
root.isGlobal()
)
or
exists(PropertyProjection proj | node = proj |
proj.isSingletonProjection() and
result = fromReference(proj.getObject()) + "." + proj.getASelector()
result = join(fromReference(proj.getObject(), root), proj.getASelector().getStringValue())
)
or
// Treat 'e || {}' as having the same name as 'e'
exists(LogOrExpr e | node.asExpr() = e |
e.getRightOperand().(ObjectExpr).getNumProperty() = 0 and
result = fromReference(e.getLeftOperand().flow())
result = fromReference(e.getLeftOperand().flow(), root)
)
or
// Treat 'e && e.f' as having the same name as 'e.f'
@@ -84,7 +148,7 @@ module GlobalAccessPath {
rhs.getBase().(PropAccess).getQualifiedName() = name
)
) and
result = fromReference(rhs.flow())
result = fromReference(rhs.flow(), root)
)
}
@@ -96,15 +160,17 @@ module GlobalAccessPath {
* foo = foo || {};
* ```
*/
private predicate isSelfAssignment(DataFlow::Node rhs) { fromRhs(rhs) = fromReference(rhs) }
private predicate isSelfAssignment(DataFlow::Node rhs) {
fromRhs(rhs, DataFlow::globalAccessPathRootPseudoNode()) = fromReference(rhs, DataFlow::globalAccessPathRootPseudoNode())
}
/**
* Holds if there is an assignment to `accessPath` in `file`, not counting
* Holds if there is an assignment to the global `accessPath` in `file`, not counting
* self-assignments.
*/
private predicate isAssignedInFile(string accessPath, File file) {
exists(DataFlow::Node rhs |
fromRhs(rhs) = accessPath and
fromRhs(rhs, DataFlow::globalAccessPathRootPseudoNode()) = accessPath and
not isSelfAssignment(rhs) and
// Note: Avoid unneeded materialization of DataFlow::Node.getFile()
rhs.getAstNode().getFile() = file
@@ -112,7 +178,7 @@ module GlobalAccessPath {
}
/**
* Holds if `accessPath` is only assigned to from one file, not counting
* Holds if the global `accessPath` is only assigned to from one file, not counting
* self-assignments.
*/
predicate isAssignedInUniqueFile(string accessPath) {
@@ -120,66 +186,179 @@ module GlobalAccessPath {
}
/**
* Gets the global access path `node` is being assigned to, if any.
* Gets the access path relative to `root`, which `node` is being assigned to, if any.
*
* Only holds for the immediate right-hand side of an assignment or property, not
* for nodes that transitively flow there.
*
* For example, the class nodes below all map to `foo.bar`:
* For example, the class nodes below all map to `foo.bar` relative to `x`:
* ```
* function f(x) {
* x.foo.bar = class {};
* x.foo = { bar: class() };
* let alias = x;
* alias.foo.bar = class {};
* }
* ```
*/
cached
private string fromRhs(DataFlow::Node node, Root root) {
exists(DataFlow::PropWrite write, string baseName |
node = write.getRhs() and
result = join(baseName, write.getPropertyName())
|
baseName = fromReference(write.getBase(), root)
or
baseName = fromRhs(write.getBase(), root)
)
or
exists(GlobalVariable var |
node = var.getAnAssignedExpr().flow() and
result = var.getName() and
root.isGlobal()
)
or
exists(FunctionDeclStmt fun |
node = DataFlow::valueNode(fun) and
result = fun.getId().(GlobalVarDecl).getName() and
root.isGlobal()
)
or
exists(ClassDeclStmt cls |
node = DataFlow::valueNode(cls) and
result = cls.getIdentifier().(GlobalVarDecl).getName() and
root.isGlobal()
)
or
exists(EnumDeclaration decl |
node = DataFlow::valueNode(decl) and
result = decl.getIdentifier().(GlobalVarDecl).getName() and
root.isGlobal()
)
or
exists(NamespaceDeclaration decl |
node = DataFlow::valueNode(decl) and
result = decl.getId().(GlobalVarDecl).getName() and
root.isGlobal()
)
}
/**
* Gets a node that refers to the given access path relative to the given `root` node,
* or `root` itself if the access path is empty.
*
* This works for direct references as well as for aliases established through local data flow.
*
* For example:
* ```
* function f(x) {
* let a = x.f.g; // reference to (x, "f.g")
* let b = a.h; // reference to (x, "f.g.h")
* }
* ```
*/
DataFlow::Node getAReferenceTo(Root root, string path) {
path = fromReference(result, root) and
not root.isGlobal()
}
/**
* Gets a node that refers to the given global access path.
*
* This works for direct references as well as for aliases established through local data flow.
*
* Examples:
* ```
* function f() {
* let v = foo.bar; // reference to 'foo.bar'
* v.baz; // reference to 'foo.bar.baz'
* }
*
* (function(ns) {
* ns.x; // reference to 'NS.x'
* })(NS = NS || {});
* ```
*/
DataFlow::Node getAReferenceTo(string path) {
path = fromReference(result, DataFlow::globalAccessPathRootPseudoNode())
}
/**
* Gets a node that is assigned to the given access path relative to the given `root` node.
*
* Only gets the immediate right-hand side of an assignment or property, not
* nodes that transitively flow there.
*
* For example, the class nodes below are all assignments to `(x, "foo.bar")`.
* ```
* function f(x) {
* x.foo.bar = class {};
* x.foo = { bar: class() };
* let alias = x;
* alias.foo.bar = class {};
* }
* ```
*/
DataFlow::Node getAnAssignmentTo(Root root, string path) {
path = fromRhs(result, root) and
not root.isGlobal()
}
/**
* Gets a node that is assigned to the given global access path.
*
* Only gets the immediate right-hand side of an assignment or property or a global declaration,
* not nodes that transitively flow there.
*
* For example, the class nodes below are all assignmetns to `foo.bar`:
* ```
* foo.bar = class {};
*
* foo = { bar: class {} };
*
* (function(f) {
* f.bar = class {}
* })(foo = foo || {});
* ```
*/
cached
string fromRhs(DataFlow::Node node) {
exists(DataFlow::SourceNode base, string baseName, string name |
node = base.getAPropertyWrite(name).getRhs() and
result = baseName + "." + name
|
baseName = fromReference(base)
or
baseName = fromRhs(base)
)
or
exists(GlobalVariable var |
node = var.getAnAssignedExpr().flow() and
result = var.getName()
)
or
exists(FunctionDeclStmt fun |
node = DataFlow::valueNode(fun) and
result = fun.getId().(GlobalVarDecl).getName()
)
or
exists(ClassDeclStmt cls |
node = DataFlow::valueNode(cls) and
result = cls.getIdentifier().(GlobalVarDecl).getName()
)
or
exists(EnumDeclaration decl |
node = DataFlow::valueNode(decl) and
result = decl.getIdentifier().(GlobalVarDecl).getName()
)
or
exists(NamespaceDeclaration decl |
node = DataFlow::valueNode(decl) and
result = decl.getId().(GlobalVarDecl).getName()
)
DataFlow::Node getAnAssignmentTo(string path) {
path = fromRhs(result, DataFlow::globalAccessPathRootPseudoNode())
}
/**
* Gets the global access path referenced by or assigned to `node`.
* Gets a node that refers to or is assigned to the given global access path.
*
* See `getAReferenceTo` and `getAnAssignmentTo` for more details.
*/
string getAccessPath(DataFlow::Node node) {
result = fromReference(node)
DataFlow::Node getAReferenceOrAssignmentTo(string path) {
result = getAReferenceTo(path)
or
not exists(fromReference(node)) and
result = fromRhs(node)
result = getAnAssignmentTo(path)
}
/**
* Gets a node that refers to or is assigned to the given access path.
*
* See `getAReferenceTo` and `getAnAssignmentTo` for more details.
*/
DataFlow::Node getAReferenceOrAssignmentTo(Root root, string path) {
result = getAReferenceTo(root, path)
or
result = getAnAssignmentTo(root, path)
}
/**
* Holds if there is a step from `pred` to `succ` through an assignment to an access path.
*/
pragma[inline]
predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(string name, Root root |
pred = getAnAssignmentTo(root, name) and
succ = getAReferenceTo(root, name)
)
or
exists(string name |
pred = getAnAssignmentTo(name) and
succ = getAReferenceTo(name) and
isAssignedInUniqueFile(name)
)
}
}

View File

@@ -582,7 +582,7 @@ module JSDoc {
* within this container.
*/
string resolveAlias(string alias) {
result = GlobalAccessPath::getAccessPath(getNodeFromAlias(alias))
getNodeFromAlias(alias) = AccessPath::getAReferenceOrAssignmentTo(result)
}
/**

View File

@@ -188,11 +188,7 @@ module DataFlow {
lvalueFlowStep(result, this) and
not lvalueDefaultFlowStep(_, this)
or
// Use of variable -> definition of variable
exists(SsaVariable var |
this = valueNode(var.getAUse()) and
result = TSsaDefNode(var)
)
immediateFlowStep(result, this)
or
// Refinement of variable -> original definition of variable
exists(SsaRefinementNode refinement |
@@ -1318,6 +1314,44 @@ module DataFlow {
)
}
/**
* Flow steps shared between `getImmediatePredecessor` and `localFlowStep`.
*
* Inlining is forced because the two relations are indexed differently.
*/
pragma[inline]
private predicate immediateFlowStep(Node pred, Node succ) {
exists(SsaVariable v |
pred = TSsaDefNode(v.getDefinition()) and
succ = valueNode(v.getAUse())
)
or
exists(Expr predExpr, Expr succExpr |
pred = valueNode(predExpr) and succ = valueNode(succExpr)
|
predExpr = succExpr.(ParExpr).getExpression()
or
predExpr = succExpr.(SeqExpr).getLastOperand()
or
predExpr = succExpr.(AssignExpr).getRhs()
or
predExpr = succExpr.(TypeAssertion).getExpression()
or
predExpr = succExpr.(NonNullAssertion).getExpression()
or
predExpr = succExpr.(ExpressionWithTypeArguments).getExpression()
)
or
// flow from 'this' parameter into 'this' expressions
exists(ThisExpr thiz |
pred = TThisNode(thiz.getBindingContainer()) and
succ = valueNode(thiz)
)
or
// `f.call(...)` and `f.apply(...)` evaluate to the result of the reflective call they perform
pred = TReflectiveCallNode(succ.asExpr(), _)
}
/**
* Holds if data can flow from `pred` to `succ` in one local step.
*/
@@ -1328,6 +1362,8 @@ module DataFlow {
or
lvalueDefaultFlowStep(pred, succ)
or
immediateFlowStep(pred, succ)
or
// Flow through implicit SSA nodes
exists(SsaImplicitDefinition ssa | succ = TSsaDefNode(ssa) |
// from any explicit definition or implicit init of a captured variable into
@@ -1345,45 +1381,18 @@ module DataFlow {
pred = TSsaDefNode(ssa.(SsaPseudoDefinition).getAnInput().getDefinition())
)
or
// flow out of local variables
exists(SsaVariable v |
pred = TSsaDefNode(v.getDefinition()) and
succ = valueNode(v.getAUse())
)
or
exists(Expr predExpr, Expr succExpr |
pred = valueNode(predExpr) and succ = valueNode(succExpr)
|
predExpr = succExpr.(ParExpr).getExpression()
or
predExpr = succExpr.(SeqExpr).getLastOperand()
or
predExpr = succExpr.(LogicalBinaryExpr).getAnOperand()
or
predExpr = succExpr.(AssignExpr).getRhs()
or
predExpr = succExpr.(ConditionalExpr).getABranch()
or
predExpr = succExpr.(TypeAssertion).getExpression()
or
predExpr = succExpr.(NonNullAssertion).getExpression()
or
predExpr = succExpr.(ExpressionWithTypeArguments).getExpression()
or
exists(Function f |
predExpr = f.getAReturnedExpr() and
localCall(succExpr, f)
)
)
or
// flow from 'this' parameter into 'this' expressions
exists(ThisExpr thiz |
pred = TThisNode(thiz.getBindingContainer()) and
succ = valueNode(thiz)
)
or
// `f.call(...)` and `f.apply(...)` evaluate to the result of the reflective call they perform
pred = TReflectiveCallNode(succ.asExpr(), _)
}
/**

View File

@@ -771,11 +771,7 @@ class ClassNode extends DataFlow::SourceNode {
*/
pragma[noinline]
predicate hasQualifiedName(string name) {
exists(DataFlow::Node rhs |
getAClassReference().flowsTo(rhs) and
name = GlobalAccessPath::fromRhs(rhs) and
GlobalAccessPath::isAssignedInUniqueFile(name)
)
getAClassReference().flowsTo(AccessPath::getAnAssignmentTo(name))
}
}
@@ -883,7 +879,7 @@ module ClassNode {
}
private DataFlow::PropRef getAPrototypeReferenceInFile(string name, File f) {
GlobalAccessPath::getAccessPath(result.getBase()) = name and
result.getBase() = AccessPath::getAReferenceOrAssignmentTo(name) and
result.getPropertyName() = "prototype" and
result.getFile() = f
}
@@ -904,7 +900,7 @@ module ClassNode {
)
or
exists(string name |
name = GlobalAccessPath::fromRhs(this) and
this = AccessPath::getAnAssignmentTo(name) and
exists(getAPrototypeReferenceInFile(name, getFile()))
)
)
@@ -969,7 +965,7 @@ module ClassNode {
)
or
exists(string name |
GlobalAccessPath::fromRhs(this) = name and
this = AccessPath::getAnAssignmentTo(name) and
result = getAPrototypeReferenceInFile(name, getFile())
)
or

View File

@@ -13,7 +13,9 @@ private class PropertyName extends string {
PropertyName() {
this = any(DataFlow::PropRef pr).getPropertyName()
or
GlobalAccessPath::isAssignedInUniqueFile(this)
AccessPath::isAssignedInUniqueFile(this)
or
exists(AccessPath::getAnAssignmentTo(_, this))
}
}
@@ -95,20 +97,35 @@ module StepSummary {
any(AdditionalTypeTrackingStep st).step(pred, succ) and
summary = LevelStep()
or
// Store to global access path
exists(string name |
name = GlobalAccessPath::fromRhs(pred) and
GlobalAccessPath::isAssignedInUniqueFile(name) and
pred = AccessPath::getAnAssignmentTo(name) and
AccessPath::isAssignedInUniqueFile(name) and
succ = DataFlow::globalAccessPathRootPseudoNode() and
summary = StoreStep(name)
)
or
// Load from global access path
exists(string name |
name = GlobalAccessPath::fromReference(succ) and
GlobalAccessPath::isAssignedInUniqueFile(name) and
succ = AccessPath::getAReferenceTo(name) and
AccessPath::isAssignedInUniqueFile(name) and
pred = DataFlow::globalAccessPathRootPseudoNode() and
summary = LoadStep(name)
)
or
// Store to non-global access path
exists(string name |
pred = AccessPath::getAnAssignmentTo(succ, name) and
summary = StoreStep(name)
)
or
// Load from non-global access path
exists(string name |
succ = AccessPath::getAReferenceTo(pred, name) and
summary = LoadStep(name) and
name != ""
)
or
// Summarize calls with flow directly from a parameter to a return.
exists(DataFlow::ParameterNode param, DataFlow::FunctionNode fun |
(

View File

@@ -43,11 +43,7 @@ module CallGraph {
or
imprecision = 0 and
t.start() and
exists(string name |
GlobalAccessPath::isAssignedInUniqueFile(name) and
GlobalAccessPath::fromRhs(function) = name and
GlobalAccessPath::fromReference(result) = name
)
AccessPath::step(function, result)
or
imprecision = 0 and
exists(DataFlow::ClassNode cls |