Files
codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll
2024-08-02 15:45:35 -07:00

1571 lines
50 KiB
Plaintext

private import codeql.ruby.AST
private import codeql.ruby.CFG
private import DataFlowPrivate
private import codeql.ruby.typetracking.internal.TypeTrackingImpl
private import codeql.ruby.ast.internal.Module as Module
private import FlowSummaryImpl as FlowSummaryImpl
private import codeql.ruby.dataflow.FlowSummary
private import codeql.ruby.dataflow.SSA
private import codeql.util.Boolean
private import codeql.util.Unit
/**
* A `LocalSourceNode` for a `self` variable. This is the implicit `self`
* parameter, when it exists, otherwise the implicit SSA entry definition.
*/
private class SelfLocalSourceNode extends DataFlow::LocalSourceNode {
private SelfVariable self;
SelfLocalSourceNode() {
self = this.(SelfParameterNodeImpl).getSelfVariable()
or
self = this.(SsaSelfDefinitionNode).getVariable()
}
/** Gets the `self` variable. */
SelfVariable getVariable() { result = self }
}
newtype TReturnKind =
TNormalReturnKind() or
TBreakReturnKind() or
TNewReturnKind()
/**
* Gets a node that can read the value returned from `call` with return kind
* `kind`.
*/
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { call = result.getCall(kind) }
/**
* A return kind. A return kind describes how a value can be returned
* from a callable.
*/
abstract class ReturnKind extends TReturnKind {
/** Gets a textual representation of this position. */
abstract string toString();
}
/**
* A value returned from a callable using a `return` statement or an expression
* body, that is, a "normal" return.
*/
class NormalReturnKind extends ReturnKind, TNormalReturnKind {
override string toString() { result = "return" }
}
/**
* A value returned from a callable using a `break` statement.
*/
class BreakReturnKind extends ReturnKind, TBreakReturnKind {
override string toString() { result = "break" }
}
/**
* A special return kind that is used to represent the value returned
* from user-defined `new` methods as well as the effect on `self` in
* `initialize` methods.
*/
class NewReturnKind extends ReturnKind, TNewReturnKind {
override string toString() { result = "new" }
}
/** A callable defined in library code, identified by a unique string. */
abstract class LibraryCallable extends string {
bindingset[this]
LibraryCallable() { any() }
/** Gets a call to this library callable. */
Call getACall() { none() }
/** Same as `getACall()` except this does not depend on the call graph or API graph. */
Call getACallSimple() { none() }
}
/** A callable defined in library code, which should be taken into account in type tracking. */
abstract class LibraryCallableToIncludeInTypeTracking extends LibraryCallable {
bindingset[this]
LibraryCallableToIncludeInTypeTracking() { exists(this) }
}
/**
* A callable. This includes callables from source code, as well as callables
* defined in library code.
*/
class DataFlowCallable extends TDataFlowCallable {
/**
* Gets the underlying CFG scope, if any.
*
* This is usually a `Callable`, but can also be a `Toplevel` file.
*/
CfgScope asCfgScope() { this = TCfgScope(result) }
/** Gets the underlying library callable, if any. */
LibraryCallable asLibraryCallable() { this = TLibraryCallable(result) }
/** Gets a textual representation of this callable. */
string toString() { result = [this.asCfgScope().toString(), this.asLibraryCallable()] }
/** Gets the location of this callable. */
Location getLocation() {
result = this.asCfgScope().getLocation()
or
this instanceof TLibraryCallable and
result instanceof EmptyLocation
}
/** Gets a best-effort total ordering. */
int totalorder() {
this =
rank[result](DataFlowCallable c, string file, int startline, int startcolumn |
c.getLocation().hasLocationInfo(file, startline, startcolumn, _, _)
|
c order by file, startline, startcolumn
)
}
}
/**
* A call. This includes calls from source code, as well as call(back)s
* inside library callables with a flow summary.
*/
abstract class DataFlowCall extends TDataFlowCall {
/** Gets the enclosing callable. */
abstract DataFlowCallable getEnclosingCallable();
/** Gets the underlying source code call, if any. */
abstract CfgNodes::ExprNodes::CallCfgNode asCall();
/** Gets a textual representation of this call. */
abstract string toString();
/** Gets the location of this call. */
abstract Location getLocation();
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
// #46: Stubs Below
/** Gets an argument to this call as a Node. */
ArgumentNode getAnArgumentNode(){ none() } // TODO: JB1 return an argument as a DataFlow ArgumentNode
/** Gets the target of the call, as a DataFlowCallable. */
DataFlowCallable getARuntimeTarget(){ none() } // TODO
/** Gets a best-effort total ordering. */
int totalorder() {
this =
rank[result](DataFlowCall c, int startline, int startcolumn |
c.hasLocationInfo(_, startline, startcolumn, _, _)
|
c order by startline, startcolumn
)
}
}
/**
* A synthesized call inside a callable with a flow summary.
*
* For example, in
* ```rb
* ints.each do |i|
* puts i
* end
* ```
*
* there is a call to the block argument inside `each`.
*/
class SummaryCall extends DataFlowCall, TSummaryCall {
private FlowSummaryImpl::Public::SummarizedCallable c;
private FlowSummaryImpl::Private::SummaryNode receiver;
SummaryCall() { this = TSummaryCall(c, receiver) }
/** Gets the data flow node that this call targets. */
FlowSummaryImpl::Private::SummaryNode getReceiver() { result = receiver }
override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = c }
override CfgNodes::ExprNodes::CallCfgNode asCall() { none() }
override string toString() { result = "[summary] call to " + receiver + " in " + c }
override EmptyLocation getLocation() { any() }
}
class NormalCall extends DataFlowCall, TNormalCall {
private CfgNodes::ExprNodes::CallCfgNode c;
NormalCall() { this = TNormalCall(c) }
override CfgNodes::ExprNodes::CallCfgNode asCall() { result = c }
override DataFlowCallable getEnclosingCallable() { result = TCfgScope(c.getScope()) }
override string toString() { result = c.toString() }
override Location getLocation() { result = c.getLocation() }
}
/**
* Provides modeling of flow through the `render` method of view components.
*
* ```rb
* # view.rb
* class View < ViewComponent::Base
* def initialize(x)
* @x = x
* end
*
* def foo
* sink(@x)
* end
* end
* ```
*
* ```erb
* # view.html.erb
* <%= foo() %> # 1
* ```
*
* ```rb
* # app.rb
* class App
* def run
* view = View.new(taint) # 2
* render(view) # 3
* end
* end
* ```
*
* The `render` call (3) is modeled using a flow summary. The summary specifies
* that the first argument (`view`) will have a special method invoked on it (we
* call the method `__invoke__toplevel__erb__`), which targets the top-level of the
* matching ERB file (`view.html.erb`). The `view` argument will flow into the receiver
* of the synthesized method call, from there into the implicit `self` parameter of
* the ERB file, and from there to the implicit `self` receiver of the call to `foo` (1).
*
* Since it is not actually possible to specify such flow summaries, we instead
* specify a call-back summary, and adjust the generated call to target the special
* `__invoke__toplevel__erb__` method.
*
* In order to resolve the target of the adjusted method call, we need to take
* the `render` summary into account when constructing the call graph. That is, we
* need to track the `View` instance (2) into the receiver of the adjusted method
* call, in order to figure out that the call target is in fact `view.html.erb`.
*/
private module ViewComponentRenderModeling {
private import codeql.ruby.frameworks.ViewComponent
private class RenderMethod extends SummarizedCallable, LibraryCallableToIncludeInTypeTracking {
RenderMethod() { this = "render view component" }
override MethodCall getACallSimple() { result.getMethodName() = "render" }
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
input = "Argument[0]" and
// use a call-back summary, and adjust it to a method call below
output = "Argument[0].Parameter[self]" and
preservesValue = true
}
}
private string invokeToplevelName() { result = "__invoke__toplevel__erb__" }
/** Holds if `call` should be adjusted to be a method call to `name` on `receiver`. */
predicate adjustedMethodCall(DataFlowCall call, FlowSummaryNode receiver, string name) {
exists(RenderMethod render |
call = TSummaryCall(render, receiver.getSummaryNode()) and
name = invokeToplevelName()
)
}
/** Holds if `self` belongs to the top-level of an ERB file with matching view class `view`. */
pragma[nomagic]
predicate selfInErbToplevel(SelfVariable self, ViewComponent::ComponentClass view) {
self.getDeclaringScope().(Toplevel).getFile() = view.getTemplate()
}
Toplevel lookupMethod(ViewComponent::ComponentClass m, string name) {
result.getFile() = m.getTemplate() and
name = invokeToplevelName()
}
}
/** 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(DataFlowCall call, DataFlow::Node receiver, string method) {
call.asCall() =
any(RelevantCall rc |
method = rc.getExpr().(MethodCall).getMethodName() and
receiver.asExpr() = rc.getReceiver()
)
or
ViewComponentRenderModeling::adjustedMethodCall(call, receiver, method)
}
pragma[nomagic]
private predicate flowsToMethodCallReceiver(
DataFlowCall call, DataFlow::LocalSourceNode sourceNode, string method
) {
exists(DataFlow::Node receiver |
methodCall(call, receiver, method) and
sourceNode.flowsTo(receiver)
)
}
pragma[nomagic]
private predicate moduleFlowsToMethodCallReceiver(DataFlowCall call, Module m, string method) {
flowsToMethodCallReceiver(call, trackModuleAccess(m), method)
}
private Block blockCall(RelevantCall call) {
lambdaSourceCall(call, _, trackBlock(result).(DataFlow::LocalSourceNode).getALocalUse())
}
pragma[nomagic]
private predicate superCall(RelevantCall call, Module cls, string method) {
call.getExpr() instanceof SuperCall and
cls = call.getExpr().getEnclosingModule().getModule() and
method = call.getExpr().getEnclosingMethod().getName()
}
/** Holds if `self` belongs to module `m`. */
pragma[nomagic]
private predicate selfInModule(SelfVariable self, Module m) {
exists(Scope scope |
scope = self.getDeclaringScope() and
m = scope.(ModuleBase).getModule() and
not scope instanceof Toplevel
)
}
/** Holds if `self` belongs to method `method` inside module `m`. */
pragma[nomagic]
private predicate selfInMethod(SelfVariable self, MethodBase method, Module m) {
exists(ModuleBase encl |
method = self.getDeclaringScope() and
encl = method.getEnclosingModule() and
if encl instanceof SingletonClass
then m = encl.getEnclosingModule().getModule()
else m = encl.getModule()
)
}
/** Holds if `self` belongs to the top-level. */
pragma[nomagic]
private predicate selfInToplevel(SelfVariable self, Module m) {
ViewComponentRenderModeling::selfInErbToplevel(self, m)
or
not ViewComponentRenderModeling::selfInErbToplevel(self, _) and
self.getDeclaringScope() instanceof Toplevel and
m = Module::TResolved("Object")
}
/**
* Holds if SSA definition `def` belongs to a variable introduced via pattern
* matching on type `m`. For example, in
*
* ```rb
* case object
* in C => c then c.foo
* end
* ```
*
* the SSA definition for `c` is introduced by matching on `C`.
*/
private predicate asModulePattern(SsaDefinitionExtNode def, Module m) {
exists(AsPattern ap |
m = Module::resolveConstantReadAccess(ap.getPattern()) and
def.getDefinitionExt().(Ssa::WriteDefinition).getWriteAccess().getAstNode() =
ap.getVariableAccess()
)
}
/**
* Holds if `read1` and `read2` are adjacent reads of SSA definition `def`,
* and `read2` is checked to have type `m`. For example, in
*
* ```rb
* case object
* when C then object.foo
* end
* ```
*
* the two reads of `object` are adjacent, and the second is checked to have type `C`.
*/
private predicate hasAdjacentTypeCheckedReads(
Ssa::Definition def, CfgNodes::ExprCfgNode read1, CfgNodes::ExprCfgNode read2, Module m
) {
exists(
CfgNodes::ExprCfgNode pattern, ConditionBlock cb, CfgNodes::ExprNodes::CaseExprCfgNode case
|
m = Module::resolveConstantReadAccess(pattern.getExpr()) and
cb.getLastNode() = pattern and
cb.controls(read2.getBasicBlock(),
any(SuccessorTypes::MatchingSuccessor match | match.getValue() = true)) and
def.hasAdjacentReads(read1, read2) and
case.getValue() = read1
|
pattern = case.getBranch(_).(CfgNodes::ExprNodes::WhenClauseCfgNode).getPattern(_)
or
pattern = case.getBranch(_).(CfgNodes::ExprNodes::InClauseCfgNode).getPattern()
)
}
/** Holds if `new` is a user-defined `self.new` method. */
predicate isUserDefinedNew(SingletonMethod new) {
exists(Expr object | singletonMethod(new, "new", object) |
selfInModule(object.(SelfVariableReadAccess).getVariable(), _)
or
exists(Module::resolveConstantReadAccess(object))
)
}
private DataFlowCallable viableSourceCallableNonInit(DataFlowCall call) {
result.asCfgScope() = getTargetInstance(call, _)
or
result.asCfgScope() = getTargetSingleton(call, _)
or
exists(Module cls, string method |
superCall(call.asCall(), cls, method) and
result.asCfgScope() = lookupMethod(cls.getAnImmediateAncestor(), method)
)
}
private Callable viableSourceCallableInit(RelevantCall call) { result = getInitializeTarget(call) }
/** Holds if `call` may resolve to the returned source-code method. */
private DataFlowCallable viableSourceCallable(DataFlowCall call) {
result = viableSourceCallableNonInit(call) or
result.asCfgScope() = viableSourceCallableInit(call.asCall()) or
result = any(AdditionalCallTarget t).viableTarget(call.asCall())
}
/**
* A unit class for adding additional call steps.
*
* Extend this class to add additional call steps to the data flow graph.
*/
class AdditionalCallTarget extends Unit {
/**
* Gets a viable target for `call`.
*/
abstract DataFlowCallable viableTarget(CfgNodes::ExprNodes::CallCfgNode call);
}
/** Holds if `call` may resolve to the returned summarized library method. */
DataFlowCallable viableLibraryCallable(DataFlowCall call) {
exists(LibraryCallable callable |
result = TLibraryCallable(callable) and
call.asCall().getExpr() = [callable.getACall(), callable.getACallSimple()]
)
}
/** Holds if there is a call like `receiver.extend(M)`. */
pragma[nomagic]
private predicate extendCall(DataFlow::ExprNode receiver, Module m) {
exists(DataFlow::CallNode extendCall |
extendCall.getMethodName() = "extend" and
exists(DataFlow::LocalSourceNode sourceNode | sourceNode.flowsTo(extendCall.getArgument(_)) |
selfInModule(sourceNode.(SelfLocalSourceNode).getVariable(), m) or
m = Module::resolveConstantReadAccess(sourceNode.asExpr().getExpr())
) and
receiver = extendCall.getReceiver()
)
}
/** Holds if there is a call like `M.extend(N)` */
pragma[nomagic]
private predicate extendCallModule(Module m, Module n) {
exists(DataFlow::LocalSourceNode receiver, DataFlow::ExprNode e |
receiver.flowsTo(e) and extendCall(e, n)
|
selfInModule(receiver.(SelfLocalSourceNode).getVariable(), m) or
m = Module::resolveConstantReadAccess(receiver.asExpr().getExpr())
)
}
private CfgScope lookupMethod(Module m, string name) {
result = Module::lookupMethod(m, name)
or
result = ViewComponentRenderModeling::lookupMethod(m, name)
}
/**
* Gets a method available in module `m`, or in one of `m`'s transitive
* sub classes when `exact = false`.
*/
pragma[nomagic]
private CfgScope lookupMethod(Module m, string name, boolean exact) {
result = lookupMethod(m, name) and
exact in [false, true]
or
result = Module::lookupMethodInSubClasses(m, name) and
exact = false
}
cached
private module Cached {
cached
newtype TDataFlowCallable =
TCfgScope(CfgScope scope) or
TLibraryCallable(LibraryCallable callable)
cached
newtype TDataFlowCall =
TNormalCall(CfgNodes::ExprNodes::CallCfgNode c) or
TSummaryCall(
FlowSummaryImpl::Public::SummarizedCallable c, FlowSummaryImpl::Private::SummaryNode receiver
) {
FlowSummaryImpl::Private::summaryCallbackRange(c, receiver)
}
/**
* Gets the relevant `initialize` method for the `new` call, if any.
*/
cached
Method getInitializeTarget(RelevantCall new) {
exists(Module m, boolean exact |
isStandardNewCall(new, m, exact) and
result = lookupMethod(m, "initialize", exact) and
// In the case where `exact = false`, we need to check that there is
// no user-defined `new` method in between `m` and the enclosing module
// of the `initialize` method (`isStandardNewCall` already checks that
// there is no user-defined `new` method in `m` or any of `m`'s ancestors)
not hasUserDefinedNew(result.getEnclosingModule().getModule())
)
}
cached
CfgScope getTarget(DataFlowCall call) {
result = viableSourceCallableNonInit(call).asCfgScope()
or
result = blockCall(call.asCall())
}
/** Gets a viable run-time target for the call `call`. */
cached
DataFlowCallable viableCallable(DataFlowCall call) {
result = viableSourceCallable(call)
or
result = viableLibraryCallable(call)
}
cached
newtype TArgumentPosition =
TSelfArgumentPosition() or
TLambdaSelfArgumentPosition() or
TBlockArgumentPosition() or
TPositionalArgumentPosition(int pos) {
exists(Call c | exists(c.getArgument(pos)))
or
FlowSummaryImpl::ParsePositions::isParsedParameterPosition(_, pos)
} or
TKeywordArgumentPosition(string name) {
name = any(KeywordParameter kp).getName()
or
exists(any(Call c).getKeywordArgument(name))
or
FlowSummaryImpl::ParsePositions::isParsedKeywordParameterPosition(_, name)
} or
THashSplatArgumentPosition() or
TSynthHashSplatArgumentPosition() or
TSplatArgumentPosition(int pos) { exists(Call c | c.getArgument(pos) instanceof SplatExpr) } or
TSynthSplatArgumentPosition() or
TAnyArgumentPosition() or
TAnyKeywordArgumentPosition()
cached
newtype TParameterPosition =
TSelfParameterPosition() or
TLambdaSelfParameterPosition() or
TBlockParameterPosition() or
TPositionalParameterPosition(int pos) {
pos = any(Parameter p).getPosition()
or
FlowSummaryImpl::ParsePositions::isParsedArgumentPosition(_, pos)
} or
TPositionalParameterLowerBoundPosition(int pos) {
FlowSummaryImpl::ParsePositions::isParsedArgumentLowerBoundPosition(_, pos)
} or
TKeywordParameterPosition(string name) {
name = any(KeywordParameter kp).getName()
or
exists(any(Call c).getKeywordArgument(name))
or
FlowSummaryImpl::ParsePositions::isParsedKeywordArgumentPosition(_, name)
} or
THashSplatParameterPosition() or
TSynthHashSplatParameterPosition() or
TSplatParameterPosition(int pos) {
pos = 0
or
exists(Parameter p | p.getPosition() = pos and p instanceof SplatParameter)
} or
TSynthSplatParameterPosition() or
TAnyParameterPosition() or
TAnyKeywordParameterPosition()
}
import Cached
private module TrackModuleInput implements CallGraphConstruction::Simple::InputSig {
class State = Module;
predicate start(DataFlow::Node start, Module m) {
m = Module::resolveConstantReadAccess(start.asExpr().getExpr())
}
// We exclude steps into `self` parameters, and instead rely on the type of the
// enclosing module
predicate filter(DataFlow::Node n) { n instanceof SelfParameterNodeImpl }
}
predicate trackModuleAccess = CallGraphConstruction::Simple::Make<TrackModuleInput>::track/1;
pragma[nomagic]
private predicate hasUserDefinedNew(Module m) {
exists(DataFlow::MethodNode method |
// not `getAnAncestor` because singleton methods cannot be included
singletonMethodOnModule(method.asCallableAstNode(), "new", m.getSuperClass*()) and
not method.getSelfParameter().getAMethodCall("allocate").flowsTo(method.getAReturnNode())
)
}
/**
* Holds if `new` is a call to `new`, targeting a class of type `m` (or a
* sub class, when `exact = false`), where there is no user-defined
* `self.new` on `m`.
*/
pragma[nomagic]
private predicate isStandardNewCall(RelevantCall new, Module m, boolean exact) {
exists(DataFlow::LocalSourceNode sourceNode |
flowsToMethodCallReceiver(TNormalCall(new), sourceNode, "new") and
// `m` should not have a user-defined `self.new` method
not hasUserDefinedNew(m)
|
// `C.new`
sourceNode = trackModuleAccess(m) and
exact = true
or
// `self.new` inside a module
selfInModule(sourceNode.(SelfLocalSourceNode).getVariable(), m) and
exact = true
or
// `self.new` inside a singleton method
exists(MethodBase caller |
selfInMethod(sourceNode.(SelfLocalSourceNode).getVariable(), caller, m) and
singletonMethod(caller, _, _) and
exact = false
)
)
}
private predicate localFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
localFlowStepTypeTracker(nodeFrom, nodeTo) and
summary.toString() = "level"
}
private module TrackInstanceInput implements CallGraphConstruction::InputSig {
pragma[nomagic]
private predicate isInstanceNoCall(DataFlow::Node n, Module tp, boolean exact) {
n.asExpr().getExpr() instanceof NilLiteral and
tp = Module::TResolved("NilClass") and
exact = true
or
n.asExpr().getExpr().(BooleanLiteral).isFalse() and
tp = Module::TResolved("FalseClass") and
exact = true
or
n.asExpr().getExpr().(BooleanLiteral).isTrue() and
tp = Module::TResolved("TrueClass") and
exact = true
or
n.asExpr().getExpr() instanceof IntegerLiteral and
tp = Module::TResolved("Integer") and
exact = true
or
n.asExpr().getExpr() instanceof FloatLiteral and
tp = Module::TResolved("Float") and
exact = true
or
n.asExpr().getExpr() instanceof RationalLiteral and
tp = Module::TResolved("Rational") and
exact = true
or
n.asExpr().getExpr() instanceof ComplexLiteral and
tp = Module::TResolved("Complex") and
exact = true
or
n.asExpr().getExpr() instanceof StringlikeLiteral and
tp = Module::TResolved("String") and
exact = true
or
n.asExpr() instanceof CfgNodes::ExprNodes::ArrayLiteralCfgNode and
tp = Module::TResolved("Array") and
exact = true
or
n.asExpr() instanceof CfgNodes::ExprNodes::HashLiteralCfgNode and
tp = Module::TResolved("Hash") and
exact = true
or
n.asExpr().getExpr() instanceof MethodBase and
tp = Module::TResolved("Symbol") and
exact = true
or
n.asParameter() instanceof BlockParameter and
tp = Module::TResolved("Proc") and
exact = true
or
n.asExpr().getExpr() instanceof Lambda and
tp = Module::TResolved("Proc") and
exact = true
or
// `self` reference in method or top-level (but not in module or singleton method,
// where instance methods cannot be called; only singleton methods)
n =
any(SelfLocalSourceNode self |
exists(MethodBase m |
selfInMethod(self.getVariable(), m, tp) and
not m instanceof SingletonMethod and
if m.getEnclosingModule() instanceof Toplevel then exact = true else exact = false
)
or
selfInToplevel(self.getVariable(), tp) and
exact = true
)
or
// `in C => c then c.foo`
asModulePattern(n, tp) and
exact = false
or
// `case object when C then object.foo`
hasAdjacentTypeCheckedReads(_, _, n.asExpr(), tp) and
exact = false
}
pragma[nomagic]
private predicate isInstanceCall(DataFlow::Node n, Module tp, boolean exact) {
isStandardNewCall(n.asExpr(), tp, exact)
}
/** Holds if `n` is an instance of type `tp`. */
pragma[inline]
private predicate isInstance(DataFlow::Node n, Module tp, boolean exact) {
isInstanceNoCall(n, tp, exact)
or
isInstanceCall(n, tp, exact)
}
pragma[nomagic]
private predicate hasAdjacentTypeCheckedReads(DataFlow::Node node) {
hasAdjacentTypeCheckedReads(_, _, node.asExpr(), _)
}
newtype State = additional MkState(Module m, Boolean exact)
predicate start(DataFlow::Node start, State state) {
exists(Module tp, boolean exact | state = MkState(tp, exact) |
isInstance(start, tp, exact)
or
exists(Module m |
(if m.isClass() then tp = Module::TResolved("Class") else tp = Module::TResolved("Module")) and
exact = true
|
// needed for e.g. `C.new`
m = Module::resolveConstantReadAccess(start.asExpr().getExpr())
or
// needed for e.g. `self.include`
selfInModule(start.(SelfLocalSourceNode).getVariable(), m)
or
// needed for e.g. `self.puts`
selfInMethod(start.(SelfLocalSourceNode).getVariable(), any(SingletonMethod sm), m)
)
)
}
pragma[nomagic]
predicate stepNoCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
smallStepNoCall(nodeFrom, nodeTo, summary)
or
// We exclude steps into type checked variables. For those, we instead rely on the
// type being checked against
localFlowStep(nodeFrom, nodeTo, summary) and
not hasAdjacentTypeCheckedReads(nodeTo) and
not asModulePattern(nodeTo, _)
}
predicate stepCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
smallStepCall(nodeFrom, nodeTo, summary)
}
class StateProj = Unit;
Unit stateProj(State state) { exists(state) and exists(result) }
// We exclude steps into `self` parameters, and instead rely on the type of the
// enclosing module
predicate filter(DataFlow::Node n, Unit u) {
n instanceof SelfParameterNodeImpl and
exists(u)
}
}
pragma[nomagic]
private DataFlow::Node trackInstance(Module tp, boolean exact) {
result =
CallGraphConstruction::Make<TrackInstanceInput>::track(TrackInstanceInput::MkState(tp, exact))
}
pragma[nomagic]
private CfgScope lookupInstanceMethodCall(DataFlowCall 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()
}
pragma[nomagic]
private CfgScope getTargetInstance(DataFlowCall call, string method) {
exists(boolean exact |
result = lookupInstanceMethodCall(call, method, exact) and
(
if result.(Method).isPrivate()
then
call.asCall().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.asCall().getFile()) or
not isToplevelMethodInFile(result, _)
)
else any()
) and
if result.(Method).isProtected()
then
result = lookupMethod(call.asCall().getExpr().getEnclosingModule().getModule(), method, exact)
else any()
)
}
private module TrackBlockInput implements CallGraphConstruction::Simple::InputSig {
class State = Block;
predicate start(DataFlow::Node start, Block block) { start.asExpr().getExpr() = block }
// We exclude steps into `self` parameters, and instead rely on the type of the
// enclosing module
predicate filter(DataFlow::Node n) { n instanceof SelfParameterNodeImpl }
}
private predicate trackBlock = CallGraphConstruction::Simple::Make<TrackBlockInput>::track/1;
/** Holds if `m` is a singleton method named `name`, defined on `object. */
private predicate singletonMethod(MethodBase m, string name, Expr object) {
name = m.getName() and
(
object = m.(SingletonMethod).getObject()
or
m = any(SingletonClass cls | object = cls.getValue()).getAMethod().(Method)
)
}
pragma[nomagic]
private predicate flowsToSingletonMethodObject(
DataFlow::LocalSourceNode nodeFrom, MethodBase m, string name
) {
exists(DataFlow::Node nodeTo |
nodeFrom.flowsTo(nodeTo) and
singletonMethod(m, name, nodeTo.asExpr().getExpr())
)
}
/**
* Holds if `method` is a singleton method named `name`, defined on module
* `m`:
*
* ```rb
* class C
* def self.m1; end # included
*
* class << self
* def m2; end # included
* end
* end
*
* def C.m3; end # included
*
* c_alias = C
* def c_alias.m4; end # included
*
* c = C.new
* def c.m5; end # not included
*
* class << c
* def m6; end # not included
* end
*
* module M
* def instance; end # included in `N` via `extend` call below
* end
* N.extend(M)
* N.instance
* ```
*/
pragma[nomagic]
private predicate singletonMethodOnModule(MethodBase method, string name, Module m) {
exists(Expr object |
singletonMethod(method, name, object) and
selfInModule(object.(SelfVariableReadAccess).getVariable(), m)
)
or
exists(DataFlow::LocalSourceNode sourceNode |
m = Module::resolveConstantReadAccess(sourceNode.asExpr().getExpr()) and
flowsToSingletonMethodObject(sourceNode, method, name)
)
or
exists(Module other |
extendCallModule(m, other) and
method = lookupMethod(other, name)
)
}
pragma[nomagic]
private MethodBase lookupSingletonMethodDirect(Module m, string name) {
singletonMethodOnModule(result, name, m)
or
exists(DataFlow::LocalSourceNode sourceNode |
sourceNode = trackModuleAccess(m) and
not m = Module::resolveConstantReadAccess(sourceNode.asExpr().getExpr()) and
flowsToSingletonMethodObject(sourceNode, result, name)
)
}
/**
* Holds if `method` is a singleton method named `name`, defined on module
* `m`, or any transitive base class of `m`.
*/
pragma[nomagic]
private MethodBase lookupSingletonMethod(Module m, string name) {
result = lookupSingletonMethodDirect(m, name)
or
// cannot use `lookupSingletonMethodDirect` because it would introduce
// negative recursion
not singletonMethodOnModule(_, name, m) and
result = lookupSingletonMethod(m.getSuperClass(), name) // not `getAnImmediateAncestor` because singleton methods cannot be included
}
pragma[nomagic]
private MethodBase lookupSingletonMethodInSubClasses(Module m, string name) {
// Singleton methods declared in a block in the top-level may spuriously end up being seen as singleton
// methods on Object, if the block is actually evaluated in the context of another class.
// The 'self' inside such a singleton method could then be any class, leading to self-calls
// being resolved to arbitrary singleton methods.
// To remedy this, we do not allow following super-classes all the way to Object.
not m = Module::TResolved("Object") and
exists(Module sub |
sub.getSuperClass() = m // not `getAnImmediateAncestor` because singleton methods cannot be included
|
result = lookupSingletonMethodDirect(sub, name) or
result = lookupSingletonMethodInSubClasses(sub, name)
)
}
pragma[nomagic]
private MethodBase lookupSingletonMethod(Module m, string name, boolean exact) {
result = lookupSingletonMethod(m, name) and
exact in [false, true]
or
result = lookupSingletonMethodInSubClasses(m, name) and
exact = false
}
/**
* Holds if `method` is a singleton method named `name`, defined on expression
* `object`, where `object` is not likely to resolve to a module:
*
* ```rb
* class C
* def self.m1; end # not included
*
* class << self
* def m2; end # not included
* end
* end
*
* def C.m3; end # not included
*
* c_alias = C
* def c_alias.m4; end # included (due to negative recursion limitation)
*
* c = C.new
* def c.m5; end # included
*
* class << c
* def m6; end # included
* end
*
* module M
* def instance; end # included in `c` via `extend` call below
* end
* c.extend(M)
* ```
*/
pragma[nomagic]
predicate singletonMethodOnInstance(MethodBase method, string name, Expr object) {
singletonMethod(method, name, object) and
not selfInModule(object.(SelfVariableReadAccess).getVariable(), _) and
// cannot use `trackModuleAccess` because of negative recursion
not exists(Module::resolveConstantReadAccess(object))
or
exists(DataFlow::ExprNode receiver, Module other |
extendCall(receiver, other) and
object = receiver.getExprNode().getExpr() and
method = lookupMethod(other, name)
)
}
private module TrackSingletonMethodOnInstanceInput implements CallGraphConstruction::InputSig {
/**
* Holds if there is reverse flow from `nodeFrom` to `nodeTo` via a parameter.
*
* This is only used for tracking singleton methods, where we want to be able
* to handle cases like
*
* ```rb
* def add_singleton x
* def x.foo; end
* end
*
* y = add_singleton C.new
* y.foo
* ```
*
* and
*
* ```rb
* class C
* def add_singleton_to_self
* def self.foo; end
* end
* end
*
* y = C.new
* y.add_singleton_to_self
* y.foo
* ```
*/
pragma[nomagic]
private predicate paramReturnFlow(
DataFlow::Node nodeFrom, DataFlow::PostUpdateNode nodeTo, StepSummary summary
) {
exists(
RelevantCall call, DataFlow::Node arg, DataFlow::ParameterNode p,
CfgNodes::ExprCfgNode nodeFromPreExpr
|
sourceCallStep(call, arg, p) and
nodeTo.getPreUpdateNode() = arg and
summary.toString() = "return" and
(
nodeFromPreExpr = nodeFrom.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr()
or
nodeFromPreExpr = nodeFrom.asExpr() and
singletonMethodOnInstance(_, _, nodeFromPreExpr.getExpr())
)
|
nodeFromPreExpr =
LocalFlow::getParameterDefNode(p.getParameter()).getDefinitionExt().getARead()
or
nodeFromPreExpr = p.(SelfParameterNodeImpl).getSelfDefinition().getARead()
)
}
class State = MethodBase;
predicate start(DataFlow::Node start, MethodBase method) {
singletonMethodOnInstance(method, _, start.asExpr().getExpr())
}
predicate stepNoCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
smallStepNoCall(nodeFrom, nodeTo, summary)
or
localFlowStep(nodeFrom, nodeTo, summary)
}
predicate stepCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
smallStepCall(nodeFrom, nodeTo, summary)
or
paramReturnFlow(nodeFrom, nodeTo, summary)
}
class StateProj extends string {
StateProj() { singletonMethodOnInstance(_, this, _) }
}
StateProj stateProj(MethodBase method) { singletonMethodOnInstance(method, result, _) }
// Stop flow at redefinitions.
//
// Example:
// ```rb
// def x.foo; end
// def x.foo; end
// x.foo # <- we want to resolve this call to the second definition only
// ```
predicate filter(DataFlow::Node n, StateProj name) {
singletonMethodOnInstance(_, name, n.asExpr().getExpr())
}
}
pragma[nomagic]
private DataFlow::Node trackSingletonMethodOnInstance(MethodBase method, string name) {
result = CallGraphConstruction::Make<TrackSingletonMethodOnInstanceInput>::track(method) and
singletonMethodOnInstance(method, name, _)
}
/** Holds if a `self` access may be the receiver of `call` directly inside module `m`. */
pragma[nomagic]
private predicate selfInModuleFlowsToMethodCallReceiver(DataFlowCall call, Module m, string method) {
exists(SelfLocalSourceNode self |
flowsToMethodCallReceiver(call, self, method) and
selfInModule(self.getVariable(), m)
)
}
/**
* Holds if a `self` access may be the receiver of `call` inside some singleton method, where
* that method belongs to `m` or one of `m`'s transitive super classes.
*/
pragma[nomagic]
private predicate selfInSingletonMethodFlowsToMethodCallReceiver(
DataFlowCall call, Module m, string method
) {
exists(SelfLocalSourceNode self, MethodBase caller |
flowsToMethodCallReceiver(call, self, method) and
selfInMethod(self.getVariable(), caller, m) and
singletonMethod(caller, _, _)
)
}
pragma[nomagic]
private CfgScope getTargetSingleton(DataFlowCall call, string method) {
// 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, boolean exact | result = lookupSingletonMethod(m, method, exact) |
// ```rb
// def C.singleton; end # <- result
// C.singleton # <- call
// ```
moduleFlowsToMethodCallReceiver(call, m, method) and
exact = true
or
// ```rb
// class C
// def self.singleton; end # <- result
// self.singleton # <- call
// end
// ```
selfInModuleFlowsToMethodCallReceiver(call, m, method) and
exact = true
or
// ```rb
// class C
// def self.singleton; end # <- result
// def self.other
// self.singleton # <- call
// end
// end
// ```
selfInSingletonMethodFlowsToMethodCallReceiver(call, m, method) and
exact = false
)
}
/**
* Holds if the parameter at position `pos` inside `encl` must flow to the receiver
* of `call`, which targets a method named `name`.
*/
pragma[nomagic]
private predicate paramMustFlowToReceiver(
ParameterPosition pos, DataFlowCall call, DataFlowCallable encl, string name
) {
exists(ParameterNodeImpl p |
// `p` is a parameter of `encl`,
p.isParameterOf(encl, pos) and
// the receiver of `call` references `p`
exists(DataFlow::Node receiver |
methodCall(pragma[only_bind_into](call), pragma[only_bind_into](receiver), name) and
LocalFlow::localMustFlowStep*(p, receiver)
)
)
}
pragma[nomagic]
private predicate mayBenefitFromCallContext(
DataFlowCall call, ParameterPosition pos, DataFlowCall ctx
) {
paramMustFlowToReceiver(pos, call, viableCallable(ctx), _)
}
/**
* Holds if the set of viable implementations that can be called by `call`
* might be improved by knowing the call context.
*/
predicate mayBenefitFromCallContext(DataFlowCall call) { mayBenefitFromCallContext(call, _, _) }
/**
* Holds if `ctx` targets the enclosing callable of `call`, the receiver of `call` is a
* parameter access (at position `ppos`), where the corresponding argument of `ctx`
* is `arg`.
*
* `name` is the name of the method being called by `call` and `source` is a
* `LocalSourceNode` that flows to `arg`.
*/
pragma[nomagic]
private predicate argMustFlowToReceiver(
RelevantCall ctx, DataFlow::LocalSourceNode source, DataFlow::Node arg, ParameterPosition ppos,
DataFlowCall call, string name
) {
exists(ArgumentPosition apos, DataFlowCallable encl |
paramMustFlowToReceiver(ppos, call, encl, name) and
parameterMatch(ppos, apos) and
source.flowsTo(arg)
|
encl = viableSourceCallableNonInit(TNormalCall(ctx)) and
arg.(ArgumentNode).sourceArgumentOf(ctx, apos)
or
encl.asCfgScope() = viableSourceCallableInit(ctx) and
if apos.isSelf()
then
// when we are targeting an initializer, the type of `self` inside the
// initializer will be the type of the `new` call itself, not the receiver
// of the `new` call
arg.asExpr() = ctx
else arg.(ArgumentNode).sourceArgumentOf(ctx, apos)
or
ctx.getAstNode() = encl.asLibraryCallable().getACallSimple() and
arg.(ArgumentNode).sourceArgumentOf(ctx, apos)
)
}
pragma[nomagic]
private CfgScope viableImplInCallContextInitialize(RelevantCall call, RelevantCall ctx) {
exists(Module m, DataFlow::LocalSourceNode source |
argMustFlowToReceiver(ctx, pragma[only_bind_into](source), _, _, TNormalCall(call), "new") and
source = trackModuleAccess(m) and
result = getInitializeTarget(call) and
result = lookupMethod(m, "initialize")
)
}
pragma[nomagic]
private CfgScope viableImplInCallContextInstance(DataFlowCall call, RelevantCall ctx) {
exists(Module m, DataFlow::LocalSourceNode source, string name, boolean exact |
argMustFlowToReceiver(ctx, pragma[only_bind_into](source), _, _, pragma[only_bind_into](call),
pragma[only_bind_into](name)) and
source = trackInstance(m, exact) and
result = getTargetInstance(call, pragma[only_bind_into](name)) and
result = lookupMethod(m, pragma[only_bind_into](name), exact)
)
}
pragma[nomagic]
private CfgScope viableImplInCallContextSingleton(DataFlowCall call, RelevantCall ctx) {
exists(
Module m, DataFlow::LocalSourceNode source, DataFlow::Node arg, string name, boolean exact
|
argMustFlowToReceiver(ctx, pragma[only_bind_into](source), arg, _, pragma[only_bind_into](call),
pragma[only_bind_into](name)) and
result = getTargetSingleton(call, pragma[only_bind_into](name)) and
result = lookupSingletonMethod(m, pragma[only_bind_into](name), exact)
|
source = trackModuleAccess(m) and
exact = true
or
exists(SelfVariable self | arg.asExpr().getExpr() = self.getAnAccess() |
selfInModule(self, m) and
exact = true
or
exists(MethodBase caller |
selfInMethod(self, caller, m) and
singletonMethod(caller, _, _) and
exact = false
)
)
)
}
/**
* Gets a viable dispatch target of `call` in the context `ctx`. This is
* restricted to those `call`s for which a context might make a difference.
*/
pragma[nomagic]
DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
// `ctx` can provide a potentially better type bound
exists(CfgScope res | res = result.asCfgScope() |
res = viableImplInCallContextInitialize(call.asCall(), ctx.asCall())
or
res = viableImplInCallContextInstance(call, ctx.asCall())
or
res = viableImplInCallContextSingleton(call, ctx.asCall())
)
or
exists(ParameterPosition pos | mayBenefitFromCallContext(call, pos, ctx) |
// `ctx` cannot provide a type bound, and the receiver of the call is `self`;
// in this case, still apply an open-world assumption
pos.isSelf() and
result = viableSourceCallable(call) and
not exists(RelevantCall ctx0 | ctx0 = ctx.asCall() |
exists(viableImplInCallContextInitialize(call.asCall(), ctx0)) or
exists(viableImplInCallContextInstance(call, ctx0)) or
exists(viableImplInCallContextSingleton(call, ctx0))
)
or
// library calls should always be able to resolve
result = viableLibraryCallable(call)
)
}
predicate exprNodeReturnedFrom = exprNodeReturnedFromCached/2;
/** A parameter position. */
class ParameterPosition extends TParameterPosition {
/** Holds if this position represents a `self` parameter. */
predicate isSelf() { this = TSelfParameterPosition() }
/** Holds if this position represents a reference to a lambda itself. Only used for tracking flow through captured variables. */
predicate isLambdaSelf() { this = TLambdaSelfParameterPosition() }
/** Holds if this position represents a block parameter. */
predicate isBlock() { this = TBlockParameterPosition() }
/** Holds if this position represents a positional parameter at position `pos`. */
predicate isPositional(int pos) { this = TPositionalParameterPosition(pos) }
/** Holds if this position represents any positional parameter starting from position `pos`. */
predicate isPositionalLowerBound(int pos) { this = TPositionalParameterLowerBoundPosition(pos) }
/** Holds if this position represents a keyword parameter named `name`. */
predicate isKeyword(string name) { this = TKeywordParameterPosition(name) }
/** Holds if this position represents a hash-splat parameter. */
predicate isHashSplat() { this = THashSplatParameterPosition() }
/** Holds if this position represents a synthetic hash-splat parameter. */
predicate isSynthHashSplat() { this = TSynthHashSplatParameterPosition() }
/** Holds if this position represents a splat parameter at position `n`. */
predicate isSplat(int n) { this = TSplatParameterPosition(n) }
/** Holds if this position represents a synthetic splat parameter. */
predicate isSynthSplat() { this = TSynthSplatParameterPosition() }
/**
* Holds if this position represents any parameter, except `self` parameters. This
* includes both positional, named, and block parameters.
*/
predicate isAny() { this = TAnyParameterPosition() }
/** Holds if this position represents any positional parameter. */
predicate isAnyNamed() { this = TAnyKeywordParameterPosition() }
/** Gets a textual representation of this position. */
string toString() {
this.isSelf() and result = "self"
or
this.isLambdaSelf() and result = "lambda self"
or
this.isBlock() and result = "block"
or
exists(int pos | this.isPositional(pos) and result = "position " + pos)
or
exists(int pos | this.isPositionalLowerBound(pos) and result = "position " + pos + "..")
or
exists(string name | this.isKeyword(name) and result = "keyword " + name)
or
this.isHashSplat() and result = "**"
or
this.isSynthHashSplat() and result = "synthetic **"
or
this.isAny() and result = "any"
or
this.isAnyNamed() and result = "any-named"
or
exists(int pos | this.isSplat(pos) and result = "* (position " + pos + ")")
or
this.isSynthSplat() and result = "synthetic *"
}
}
/** An argument position. */
class ArgumentPosition extends TArgumentPosition {
/** Holds if this position represents a `self` argument. */
predicate isSelf() { this = TSelfArgumentPosition() }
/** Holds if this position represents a lambda `self` argument. Only used for tracking flow through captured variables. */
predicate isLambdaSelf() { this = TLambdaSelfArgumentPosition() }
/** Holds if this position represents a block argument. */
predicate isBlock() { this = TBlockArgumentPosition() }
/** Holds if this position represents a positional argument at position `pos`. */
predicate isPositional(int pos) { this = TPositionalArgumentPosition(pos) }
/** Holds if this position represents a keyword argument named `name`. */
predicate isKeyword(string name) { this = TKeywordArgumentPosition(name) }
/**
* Holds if this position represents any argument, except `self` arguments. This
* includes both positional, named, and block arguments.
*/
predicate isAny() { this = TAnyArgumentPosition() }
/** Holds if this position represents any positional parameter. */
predicate isAnyNamed() { this = TAnyKeywordArgumentPosition() }
/** Holds if this position represents a hash-splat argument. */
predicate isHashSplat() { this = THashSplatArgumentPosition() }
/** Holds if this position represents a synthetic hash-splat argument. */
predicate isSynthHashSplat() { this = TSynthHashSplatArgumentPosition() }
/** Holds if this position represents a splat argument at position `n`. */
predicate isSplat(int n) { this = TSplatArgumentPosition(n) }
/** Holds if this position represents a synthetic splat argument. */
predicate isSynthSplat() { this = TSynthSplatArgumentPosition() }
/** Gets a textual representation of this position. */
string toString() {
this.isSelf() and result = "self"
or
this.isLambdaSelf() and result = "lambda self"
or
this.isBlock() and result = "block"
or
exists(int pos | this.isPositional(pos) and result = "position " + pos)
or
exists(string name | this.isKeyword(name) and result = "keyword " + name)
or
this.isAny() and result = "any"
or
this.isAnyNamed() and result = "any-named"
or
this.isHashSplat() and result = "**"
or
this.isSynthHashSplat() and result = "synthetic **"
or
exists(int pos | this.isSplat(pos) and result = "* (position " + pos + ")")
or
this.isSynthSplat() and result = "synthetic *"
}
}
pragma[nomagic]
private predicate parameterPositionIsNotSelf(ParameterPosition ppos) {
not ppos.isSelf() and
not ppos.isLambdaSelf()
}
pragma[nomagic]
private predicate argumentPositionIsNotSelf(ArgumentPosition apos) {
not apos.isSelf() and
not apos.isLambdaSelf()
}
/** Holds if arguments at position `apos` match parameters at position `ppos`. */
pragma[nomagic]
predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
ppos.isSelf() and apos.isSelf()
or
ppos.isLambdaSelf() and apos.isLambdaSelf()
or
ppos.isBlock() and apos.isBlock()
or
exists(int pos | ppos.isPositional(pos) and apos.isPositional(pos))
or
exists(int pos1, int pos2 |
ppos.isPositionalLowerBound(pos1) and apos.isPositional(pos2) and pos2 >= pos1
)
or
exists(string name | ppos.isKeyword(name) and apos.isKeyword(name))
or
(ppos.isHashSplat() or ppos.isSynthHashSplat()) and
(apos.isHashSplat() or apos.isSynthHashSplat())
or
exists(int pos |
(
ppos.isSplat(pos)
or
ppos.isSynthSplat() and pos = 0
) and
(
apos.isSplat(pos)
or
apos.isSynthSplat() and pos = 0
)
)
or
ppos.isAny() and argumentPositionIsNotSelf(apos)
or
apos.isAny() and parameterPositionIsNotSelf(ppos)
or
ppos.isAnyNamed() and apos.isKeyword(_)
or
apos.isAnyNamed() and ppos.isKeyword(_)
}