JS: Generalize access paths to arbitrary root nodes

This commit is contained in:
Asger F
2019-10-25 11:11:42 +01:00
parent 7a7a8b2b09
commit bc35f24f31
4 changed files with 242 additions and 89 deletions

View File

@@ -5,6 +5,24 @@
import javascript
module GlobalAccessPath {
/**
* A source node that can be the root of an access path.
*/
private 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.
*/
@@ -20,6 +38,78 @@ module GlobalAccessPath {
DataFlow::Node getValue() { result = getSsaDefinition().getRhsNode() }
}
/**
* 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(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'
* }
* ```
*/
cached
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(), root)
)
or
node.accessesGlobal(result) and
result != "undefined" and
root.isGlobal()
or
not node.accessesGlobal(_) and
exists(DataFlow::PropRead prop | node = prop |
result = fromReference(prop.getBase(), root) + "." + prop.getPropertyName()
)
or
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(), root) + "." + proj.getASelector()
)
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(), root)
)
or
// Treat 'e && e.f' as having the same name as 'e.f'
exists(LogAndExpr e, Expr lhs, PropAccess rhs | node.asExpr() = e |
lhs = e.getLeftOperand() and
rhs = e.getRightOperand() and
(
exists(Variable v |
lhs = v.getAnAccess() and
rhs.getBase() = v.getAnAccess()
)
or
exists(string name |
lhs.(PropAccess).getQualifiedName() = name and
rhs.getBase().(PropAccess).getQualifiedName() = name
)
) and
result = fromReference(rhs.flow(), root)
)
}
/**
* Gets the global access path referred to by `node`.
*
@@ -40,52 +130,7 @@ module GlobalAccessPath {
*/
cached
string fromReference(DataFlow::Node node) {
result = fromReference(node.getImmediatePredecessor())
or
exists(EffectivelyConstantVariable var |
var.isCaptured() and
node.asExpr() = var.getAnAccess() and
result = fromReference(var.getValue())
)
or
node.accessesGlobal(result) and
result != "undefined"
or
not node.accessesGlobal(_) and
exists(DataFlow::PropRead prop | node = prop |
result = fromReference(prop.getBase()) + "." + prop.getPropertyName()
)
or
exists(Closure::ClosureNamespaceAccess acc | node = acc | result = acc.getClosureNamespace())
or
exists(PropertyProjection proj | node = proj |
proj.isSingletonProjection() and
result = fromReference(proj.getObject()) + "." + proj.getASelector()
)
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())
)
or
// Treat 'e && e.f' as having the same name as 'e.f'
exists(LogAndExpr e, Expr lhs, PropAccess rhs | node.asExpr() = e |
lhs = e.getLeftOperand() and
rhs = e.getRightOperand() and
(
exists(Variable v |
lhs = v.getAnAccess() and
rhs.getBase() = v.getAnAccess()
)
or
exists(string name |
lhs.(PropAccess).getQualifiedName() = name and
rhs.getBase().(PropAccess).getQualifiedName() = name
)
) and
result = fromReference(rhs.flow())
)
result = fromReference(node, DataFlow::globalAccessPathRootPseudoNode())
}
/**
@@ -96,7 +141,9 @@ 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
@@ -104,7 +151,7 @@ module GlobalAccessPath {
*/
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,13 +159,71 @@ 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) {
strictcount(File f | isAssignedInFile(accessPath, f)) = 1
}
/**
* 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` relative to `x`:
* ```
* function f(x) {
* x.foo.bar = class {};
* x.foo = { bar: class() };
* let alias = x;
* alias.foo.bar = class {};
* }
* ```
*/
cached
string fromRhs(DataFlow::Node node, Root root) {
exists(DataFlow::SourceNode base, string baseName, string name |
node = base.getAPropertyWrite(name).getRhs() and
result = baseName + "." + name
|
baseName = fromReference(base, root)
or
baseName = fromRhs(base, 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 the global access path `node` is being assigned to, if any.
*
@@ -138,48 +243,41 @@ module GlobalAccessPath {
*/
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)
)
result = fromRhs(node, DataFlow::globalAccessPathRootPseudoNode())
}
/**
* Gets the access path relative to `root` referenced by or assigned to `node`.
*/
string getAccessPath(DataFlow::Node node, Root root) {
result = fromReference(node, root)
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()
)
not exists(fromReference(node, root)) and
result = fromRhs(node, root)
}
/**
* Gets the global access path referenced by or assigned to `node`.
*/
string getAccessPath(DataFlow::Node node) {
result = fromReference(node)
result = getAccessPath(node, DataFlow::globalAccessPathRootPseudoNode())
}
/**
* 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 |
name = fromRhs(pred, root) and
name = fromReference(succ, root) and
not root.isGlobal()
)
or
not exists(fromReference(node)) and
result = fromRhs(node)
exists(string name |
name = fromRhs(pred) and
name = fromReference(succ) and
isAssignedInUniqueFile(name)
)
}
}

View File

@@ -14,6 +14,9 @@ private class PropertyName extends string {
this = any(DataFlow::PropRef pr).getPropertyName()
or
GlobalAccessPath::isAssignedInUniqueFile(this)
or
this = GlobalAccessPath::fromRhs(_, _) and
this != ""
}
}
@@ -95,6 +98,7 @@ 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
@@ -102,6 +106,7 @@ module StepSummary {
summary = StoreStep(name)
)
or
// Load from global access path
exists(string name |
name = GlobalAccessPath::fromReference(succ) and
GlobalAccessPath::isAssignedInUniqueFile(name) and
@@ -109,6 +114,21 @@ module StepSummary {
summary = LoadStep(name)
)
or
// Store to non-global access path
exists(string name |
name = GlobalAccessPath::fromRhs(pred, succ) and
succ != DataFlow::globalAccessPathRootPseudoNode() and
summary = StoreStep(name)
)
or
// Load from non-global access path
exists(string name |
name = GlobalAccessPath::fromReference(succ, pred) and
pred != DataFlow::globalAccessPathRootPseudoNode() 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
)
GlobalAccessPath::step(function, result)
or
imprecision = 0 and
exists(DataFlow::ClassNode cls |