Merge pull request #10829 from hvitved/ruby/call-graph-perf

Ruby: Call graph performance improvements
This commit is contained in:
Tom Hvitved
2022-10-14 15:24:27 +02:00
committed by GitHub
2 changed files with 178 additions and 131 deletions

View File

@@ -138,9 +138,26 @@ private module Cached {
cached
string resolveConstantWrite(ConstantWriteAccess c) { result = resolveConstantWriteAccess(c) }
/**
* Gets a method named `name` that is available in module `m`. This includes methods
* that are included/prepended into `m` and methods available on base classes of `m`.
*/
cached
Method lookupMethod(Module m, string name) { TMethod(result) = lookupMethodOrConst(m, name) }
/**
* Gets a method named `name` that is available in a sub class of module `m`. This
* includes methods that are included/prepended into any of the sub classes of `m`,
* but not methods inherited from base classes.
*/
cached
Method lookupMethodInSubClasses(Module m, string name) {
exists(Module sub | sub.getSuperClass() = m |
TMethod(result) = lookupMethodOrConst0(sub, name) or
result = lookupMethodInSubClasses(sub, name)
)
}
cached
Expr lookupConst(Module m, string name) {
TExpr(result) = lookupMethodOrConst(m, name)

View File

@@ -151,17 +151,24 @@ private class NormalCall extends DataFlowCall, TNormalCall {
override Location getLocation() { result = c.getLocation() }
}
/** A call for which we want to compute call targets. */
private class RelevantCall extends CfgNodes::ExprNodes::CallCfgNode {
pragma[nomagic]
RelevantCall() {
// Temporarily disable operation resolution (due to bad performance)
not this.getExpr() instanceof Operation
}
}
pragma[nomagic]
private predicate methodCall(
CfgNodes::ExprNodes::CallCfgNode call, DataFlow::Node receiver, string method
) {
private predicate methodCall(RelevantCall call, DataFlow::Node receiver, string method) {
method = call.getExpr().(MethodCall).getMethodName() and
receiver.asExpr() = call.getReceiver()
}
pragma[nomagic]
private predicate flowsToMethodCall(
CfgNodes::ExprNodes::CallCfgNode call, DataFlow::LocalSourceNode sourceNode, string method
private predicate flowsToMethodCallReceiver(
RelevantCall call, DataFlow::LocalSourceNode sourceNode, string method
) {
exists(DataFlow::Node receiver |
methodCall(call, receiver, method) and
@@ -169,7 +176,12 @@ private predicate flowsToMethodCall(
)
}
private Block yieldCall(CfgNodes::ExprNodes::CallCfgNode call) {
pragma[nomagic]
private predicate moduleFlowsToMethodCallReceiver(RelevantCall call, Module m, string method) {
flowsToMethodCallReceiver(call, trackModuleAccess(m), method)
}
private Block yieldCall(RelevantCall call) {
call.getExpr() instanceof YieldCall and
exists(BlockParameterNode node |
node = trackBlock(result) and
@@ -178,7 +190,7 @@ private Block yieldCall(CfgNodes::ExprNodes::CallCfgNode call) {
}
pragma[nomagic]
private predicate superCall(CfgNodes::ExprNodes::CallCfgNode call, Module superClass, string method) {
private predicate superCall(RelevantCall call, Module superClass, string method) {
call.getExpr() instanceof SuperCall and
exists(Module tp |
tp = call.getExpr().getEnclosingModule().getModule() and
@@ -187,20 +199,6 @@ private predicate superCall(CfgNodes::ExprNodes::CallCfgNode call, Module superC
)
}
pragma[nomagic]
private predicate instanceMethodCall(CfgNodes::ExprNodes::CallCfgNode call, Module tp, string method) {
exists(DataFlow::Node receiver, Module m, boolean exact |
methodCall(call, receiver, method) and
receiver = trackInstance(m, exact)
|
tp = m
or
// When we don't know the exact type, it could be any sub class
exact = false and
tp.getSuperClass+() = m
)
}
/** Holds if `self` belongs to module `m`. */
pragma[nomagic]
private predicate selfInModule(SelfVariable self, Module m) {
@@ -318,6 +316,19 @@ private predicate extendCallModule(Module m, Module n) {
)
}
/**
* Gets a method available in module `m`, or in one of `m`'s transitive
* sub classes when `exact = false`.
*/
pragma[nomagic]
private Method lookupMethod(Module m, string name, boolean exact) {
result = lookupMethod(m, name) and
exact in [false, true]
or
result = lookupMethodInSubClasses(m, name) and
exact = false
}
cached
private module Cached {
cached
@@ -332,100 +343,129 @@ private module Cached {
FlowSummaryImpl::Private::summaryCallbackRange(c, receiver)
}
cached
CfgScope getTarget(CfgNodes::ExprNodes::CallCfgNode call) {
// Temporarily disable operation resolution (due to bad performance)
not call.getExpr() instanceof Operation and
(
exists(string method |
exists(Module tp |
instanceMethodCall(call, tp, method) and
result = lookupMethod(tp, method) and
(
if result.(Method).isPrivate()
then
call.getReceiver().getExpr() instanceof SelfVariableAccess and
// For now, we restrict the scope of top-level declarations to their file.
// This may remove some plausible targets, but also removes a lot of
// implausible targets
if result.getEnclosingModule() instanceof Toplevel
then result.getFile() = call.getFile()
else any()
else any()
) and
if result.(Method).isProtected()
then result = lookupMethod(call.getExpr().getEnclosingModule().getModule(), method)
else any()
)
or
// singleton method defined on an instance, e.g.
// ```rb
// c = C.new
// def c.singleton; end # <- result
// c.singleton # <- call
// ```
// or an `extend`ed instance, e.g.
// ```rb
// c = C.new
// module M
// def instance; end # <- result
// end
// c.extend M
// c.instance # <- call
// ```
exists(DataFlow::Node receiver |
methodCall(call, receiver, method) and
receiver = trackSingletonMethodOnInstance(result, method)
)
or
// singleton method defined on a module
// or an `extend`ed module, e.g.
// ```rb
// module M
// def instance; end # <- result
// end
// M.extend(M)
// M.instance # <- call
// ```
exists(DataFlow::Node sourceNode, Module m |
flowsToMethodCall(call, sourceNode, method) and
result = lookupSingletonMethod(m, method)
|
// ```rb
// def C.singleton; end # <- result
// C.singleton # <- call
// ```
sourceNode = trackModuleAccess(m)
or
// ```rb
// class C
// def self.singleton; end # <- result
// self.singleton # <- call
// end
// ```
selfInModule(sourceNode.(SsaSelfDefinitionNode).getVariable(), m)
or
// ```rb
// class C
// def self.singleton; end # <- result
// def self.other
// self.singleton # <- call
// end
// end
// ```
selfInMethod(sourceNode.(SsaSelfDefinitionNode).getVariable(), _, m.getSuperClass*())
)
)
or
exists(Module superClass, string method |
superCall(call, superClass, method) and
result = lookupMethod(superClass, method)
)
or
result = yieldCall(call)
pragma[nomagic]
private Method lookupInstanceMethodCall(RelevantCall call, string method, boolean exact) {
exists(Module tp, DataFlow::Node receiver |
methodCall(call, pragma[only_bind_into](receiver), pragma[only_bind_into](method)) and
receiver = trackInstance(tp, exact) and
result = lookupMethod(tp, pragma[only_bind_into](method), exact)
)
}
pragma[nomagic]
private predicate isToplevelMethodInFile(Method m, File f) {
m.getEnclosingModule() instanceof Toplevel and
f = m.getFile()
}
/** Holds if a `self` access may be the receiver of `call` directly inside module `m`. */
pragma[nomagic]
private predicate selfInModuleFlowsToMethodCallReceiver(RelevantCall call, Module m, string method) {
exists(SsaSelfDefinitionNode self |
flowsToMethodCallReceiver(call, self, method) and
selfInModule(self.getVariable(), m)
)
}
/**
* Holds if a `self` access may be the receiver of `call` inside some method, where
* that method belongs to `m` or one of `m`'s transitive super classes.
*/
pragma[nomagic]
private predicate selfInMethodFlowsToMethodCallReceiver(RelevantCall call, Module m, string method) {
exists(SsaSelfDefinitionNode self |
flowsToMethodCallReceiver(call, self, method) and
selfInMethod(self.getVariable(), _, m.getSuperClass*())
)
}
cached
CfgScope getTarget(RelevantCall call) {
exists(string method |
exists(boolean exact |
result = lookupInstanceMethodCall(call, method, exact) and
(
if result.(Method).isPrivate()
then
call.getReceiver().getExpr() instanceof SelfVariableAccess and
// For now, we restrict the scope of top-level declarations to their file.
// This may remove some plausible targets, but also removes a lot of
// implausible targets
(
isToplevelMethodInFile(result, call.getFile()) or
not isToplevelMethodInFile(result, _)
)
else any()
) and
if result.(Method).isProtected()
then result = lookupMethod(call.getExpr().getEnclosingModule().getModule(), method, exact)
else any()
)
or
// singleton method defined on an instance, e.g.
// ```rb
// c = C.new
// def c.singleton; end # <- result
// c.singleton # <- call
// ```
// or an `extend`ed instance, e.g.
// ```rb
// c = C.new
// module M
// def instance; end # <- result
// end
// c.extend M
// c.instance # <- call
// ```
exists(DataFlow::Node receiver |
methodCall(call, receiver, method) and
receiver = trackSingletonMethodOnInstance(result, method)
)
or
// singleton method defined on a module
// or an `extend`ed module, e.g.
// ```rb
// module M
// def instance; end # <- result
// end
// M.extend(M)
// M.instance # <- call
// ```
exists(Module m | result = lookupSingletonMethod(m, method) |
// ```rb
// def C.singleton; end # <- result
// C.singleton # <- call
// ```
moduleFlowsToMethodCallReceiver(call, m, method)
or
// ```rb
// class C
// def self.singleton; end # <- result
// self.singleton # <- call
// end
// ```
selfInModuleFlowsToMethodCallReceiver(call, m, method)
or
// ```rb
// class C
// def self.singleton; end # <- result
// def self.other
// self.singleton # <- call
// end
// end
// ```
selfInMethodFlowsToMethodCallReceiver(call, m, method)
)
)
or
exists(Module superClass, string method |
superCall(call, superClass, method) and
result = lookupMethod(superClass, method)
)
or
result = yieldCall(call)
}
/** Gets a viable run-time target for the call `call`. */
cached
DataFlowCallable viableCallable(DataFlowCall call) {
@@ -551,8 +591,8 @@ private predicate isInstance(DataFlow::Node n, Module tp, boolean exact) {
tp = TResolved("Proc") and
exact = true
or
exists(CfgNodes::ExprNodes::CallCfgNode call, DataFlow::LocalSourceNode sourceNode |
flowsToMethodCall(call, sourceNode, "new") and
exists(RelevantCall call, DataFlow::LocalSourceNode sourceNode |
flowsToMethodCallReceiver(call, sourceNode, "new") and
exact = true and
n.asExpr() = call
|
@@ -834,10 +874,7 @@ pragma[nomagic]
private predicate paramReturnFlow(
DataFlow::Node nodeFrom, DataFlow::PostUpdateNode nodeTo, StepSummary summary
) {
exists(
CfgNodes::ExprNodes::CallCfgNode call, DataFlow::Node arg, DataFlow::ParameterNode p,
Expr nodeFromPreExpr
|
exists(RelevantCall call, DataFlow::Node arg, DataFlow::ParameterNode p, Expr nodeFromPreExpr |
TypeTrackerSpecific::callStep(call, arg, p) and
nodeTo.getPreUpdateNode() = arg and
summary.toString() = "return" and
@@ -911,8 +948,7 @@ private predicate isInstanceLocalMustFlow(DataFlow::Node n, Module tp, boolean e
*/
pragma[nomagic]
private predicate mayBenefitFromCallContext0(
CfgNodes::ExprNodes::CallCfgNode ctx, ArgumentNode arg, CfgNodes::ExprNodes::CallCfgNode call,
Callable encl, string name
RelevantCall ctx, ArgumentNode arg, RelevantCall call, Callable encl, string name
) {
exists(
ParameterNodeImpl p, SsaDefinitionNode ssaNode, ParameterPosition ppos, ArgumentPosition apos
@@ -920,7 +956,7 @@ private predicate mayBenefitFromCallContext0(
// the receiver of `call` references `p`
ssaNode = trackInstance(_, _) and
LocalFlow::localFlowSsaParamInput(p, ssaNode) and
flowsToMethodCall(pragma[only_bind_into](call), pragma[only_bind_into](ssaNode),
flowsToMethodCallReceiver(pragma[only_bind_into](call), pragma[only_bind_into](ssaNode),
pragma[only_bind_into](name)) and
// `p` is a parameter of `encl`,
encl = call.getScope() and
@@ -943,8 +979,7 @@ private predicate mayBenefitFromCallContext0(
*/
pragma[nomagic]
private predicate mayBenefitFromCallContext1(
CfgNodes::ExprNodes::CallCfgNode ctx, CfgNodes::ExprNodes::CallCfgNode call, Callable encl,
Module tp, boolean exact, string name
RelevantCall ctx, RelevantCall call, Callable encl, Module tp, boolean exact, string name
) {
exists(ArgumentNode arg |
mayBenefitFromCallContext0(ctx, pragma[only_bind_into](arg), call, encl,
@@ -972,19 +1007,14 @@ predicate mayBenefitFromCallContext(DataFlowCall call, DataFlowCallable c) {
pragma[nomagic]
DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
// `ctx` can provide a potentially better type bound
exists(CfgNodes::ExprNodes::CallCfgNode call0, Callable res |
exists(RelevantCall call0, Callable res |
call0 = call.asCall() and
res = result.asCallable() and
res = getTarget(call0) and // make sure to not include e.g. private methods
exists(Module tp, Module m, boolean exact, string name |
res = lookupMethod(tp, name) and
exists(Module m, boolean exact, string name |
res = lookupMethod(m, name, exact) and
mayBenefitFromCallContext1(ctx.asCall(), pragma[only_bind_into](call0), _,
pragma[only_bind_into](m), exact, pragma[only_bind_into](name))
|
tp = m
or
exact = false and
tp.getSuperClass+() = m
)
)
or