mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
Merge pull request #10829 from hvitved/ruby/call-graph-perf
Ruby: Call graph performance improvements
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user