Skip to content

Commit e8f9429

Browse files
authored
Merge pull request #10917 from hvitved/ruby/singleton-call-sensitivity
Ruby: Call-context sensitivity for singleton method calls
2 parents 84c754e + 4422327 commit e8f9429

File tree

3 files changed

+329
-64
lines changed

3 files changed

+329
-64
lines changed

ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll

Lines changed: 88 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -615,9 +615,9 @@ private predicate isInstance(DataFlow::Node n, Module tp, boolean exact) {
615615
exact = true
616616
or
617617
// `self.new` inside a singleton method
618-
exists(MethodBase target |
619-
selfInMethod(sourceNode.(SsaSelfDefinitionNode).getVariable(), target, tp) and
620-
singletonMethod(target, _, _) and
618+
exists(MethodBase caller |
619+
selfInMethod(sourceNode.(SsaSelfDefinitionNode).getVariable(), caller, tp) and
620+
singletonMethod(caller, _, _) and
621621
exact = false
622622
)
623623
)
@@ -991,14 +991,13 @@ private predicate isInstanceLocalMustFlow(DataFlow::Node n, Module tp, boolean e
991991
* `name` is the name of the method being called by `call`.
992992
*/
993993
pragma[nomagic]
994-
private predicate mayBenefitFromCallContext0(
994+
private predicate argFlowsToReceiver(
995995
RelevantCall ctx, ArgumentNode arg, RelevantCall call, Callable encl, string name
996996
) {
997997
exists(
998998
ParameterNodeImpl p, SsaDefinitionNode ssaNode, ParameterPosition ppos, ArgumentPosition apos
999999
|
10001000
// the receiver of `call` references `p`
1001-
ssaNode = trackInstance(_, _) and
10021001
LocalFlow::localFlowSsaParamInput(p, ssaNode) and
10031002
flowsToMethodCallReceiver(pragma[only_bind_into](call), pragma[only_bind_into](ssaNode),
10041003
pragma[only_bind_into](name)) and
@@ -1016,32 +1015,76 @@ private predicate mayBenefitFromCallContext0(
10161015
/**
10171016
* Holds if `ctx` targets `encl`, which is the enclosing callable of `call`, and
10181017
* the receiver of `call` is a parameter access, where the corresponding argument
1019-
* of `ctx` has type `tp`.
1018+
* `arg` of `ctx` has type `tp`.
10201019
*
10211020
* `name` is the name of the method being called by `call`, and `exact` is pertaining
10221021
* to the type of the argument.
10231022
*/
10241023
pragma[nomagic]
1025-
private predicate mayBenefitFromCallContext1(
1026-
RelevantCall ctx, RelevantCall call, Callable encl, Module tp, boolean exact, string name
1024+
private predicate mayBenefitFromCallContextInstance(
1025+
RelevantCall ctx, RelevantCall call, ArgumentNode arg, Callable encl, Module tp, boolean exact,
1026+
string name
10271027
) {
1028-
exists(ArgumentNode arg |
1029-
mayBenefitFromCallContext0(ctx, pragma[only_bind_into](arg), call, encl,
1030-
pragma[only_bind_into](name)) and
1031-
// `arg` has a relevant instance type
1032-
isInstanceLocalMustFlow(arg, tp, exact) and
1033-
exists(lookupMethod(tp, pragma[only_bind_into](name)))
1028+
argFlowsToReceiver(ctx, pragma[only_bind_into](arg), call, encl, pragma[only_bind_into](name)) and
1029+
// `arg` has a relevant instance type
1030+
isInstanceLocalMustFlow(arg, tp, exact) and
1031+
exists(lookupMethod(tp, pragma[only_bind_into](name)))
1032+
}
1033+
1034+
/** Same as `resolveConstantReadAccess`, but includes local must-flow through SSA definitions. */
1035+
private predicate resolveConstantReadAccessMustFlow(DataFlow::Node n, Module tp) {
1036+
tp = resolveConstantReadAccess(n.asExpr().getExpr())
1037+
or
1038+
exists(DataFlow::Node mid | resolveConstantReadAccessMustFlow(mid, tp) |
1039+
n.asExpr() = mid.(SsaDefinitionNode).getDefinition().getARead()
1040+
or
1041+
n.(SsaDefinitionNode).getDefinition().(Ssa::WriteDefinition).assigns(mid.asExpr())
10341042
)
10351043
}
10361044

1045+
/**
1046+
* Holds if `ctx` targets `encl`, which is the enclosing callable of `call`, and
1047+
* the receiver of `call` is a parameter access, where the corresponding argument
1048+
* `arg` of `ctx` is a module access targeting a module of type `tp`.
1049+
*
1050+
* `name` is the name of the method being called by `call`, and `exact` is pertaining
1051+
* to the type of the argument.
1052+
*/
1053+
pragma[nomagic]
1054+
private predicate mayBenefitFromCallContextSingleton(
1055+
RelevantCall ctx, RelevantCall call, ArgumentNode arg, Callable encl, Module tp, boolean exact,
1056+
string name
1057+
) {
1058+
argFlowsToReceiver(ctx, pragma[only_bind_into](arg), call, encl, pragma[only_bind_into](name)) and
1059+
// `arg` has a relevant module type
1060+
(
1061+
resolveConstantReadAccessMustFlow(arg, tp) and
1062+
exact = true
1063+
or
1064+
exists(SelfVariable self | arg.asExpr().getExpr() = self.getAnAccess() |
1065+
selfInModule(self, tp) and
1066+
exact = true
1067+
or
1068+
exists(MethodBase caller |
1069+
selfInMethod(self, caller, tp) and
1070+
singletonMethod(caller, _, _) and
1071+
exact = false
1072+
)
1073+
)
1074+
) and
1075+
exists(lookupSingletonMethod(tp, pragma[only_bind_into](name), exact))
1076+
}
1077+
10371078
/**
10381079
* Holds if the set of viable implementations that can be called by `call`
10391080
* might be improved by knowing the call context. This is the case if the
10401081
* receiver accesses a parameter of the enclosing callable `c` (including
10411082
* the implicit `self` parameter).
10421083
*/
10431084
predicate mayBenefitFromCallContext(DataFlowCall call, DataFlowCallable c) {
1044-
mayBenefitFromCallContext1(_, call.asCall(), c.asCallable(), _, _, _)
1085+
mayBenefitFromCallContextInstance(_, call.asCall(), _, c.asCallable(), _, _, _)
1086+
or
1087+
mayBenefitFromCallContextSingleton(_, call.asCall(), _, c.asCallable(), _, _, _)
10451088
}
10461089

10471090
/**
@@ -1050,28 +1093,38 @@ predicate mayBenefitFromCallContext(DataFlowCall call, DataFlowCallable c) {
10501093
*/
10511094
pragma[nomagic]
10521095
DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
1053-
// `ctx` can provide a potentially better type bound
1054-
exists(RelevantCall call0, Callable res |
1055-
call0 = call.asCall() and
1056-
res = result.asCallable() and
1057-
res = getTarget(call0) and // make sure to not include e.g. private methods
1058-
exists(Module m, boolean exact, string name |
1059-
res = lookupMethod(m, name, exact) and
1060-
mayBenefitFromCallContext1(ctx.asCall(), pragma[only_bind_into](call0), _,
1061-
pragma[only_bind_into](m), exact, pragma[only_bind_into](name))
1096+
mayBenefitFromCallContext(call, _) and
1097+
(
1098+
// `ctx` can provide a potentially better type bound
1099+
exists(RelevantCall call0, Callable res |
1100+
call0 = call.asCall() and
1101+
res = result.asCallable() and
1102+
res = getTarget(call0) and // make sure to not include e.g. private methods
1103+
exists(Module m, boolean exact, string name |
1104+
mayBenefitFromCallContextInstance(ctx.asCall(), pragma[only_bind_into](call0), _, _,
1105+
pragma[only_bind_into](m), exact, pragma[only_bind_into](name)) and
1106+
res = lookupMethod(m, name, exact)
1107+
or
1108+
mayBenefitFromCallContextSingleton(ctx.asCall(), pragma[only_bind_into](call0), _, _,
1109+
pragma[only_bind_into](m), exact, pragma[only_bind_into](name)) and
1110+
res = lookupSingletonMethod(m, name, exact)
1111+
)
10621112
)
1113+
or
1114+
// `ctx` cannot provide a type bound
1115+
exists(RelevantCall call0, RelevantCall ctx0, ArgumentNode arg, string name |
1116+
call0 = call.asCall() and
1117+
ctx0 = ctx.asCall() and
1118+
argFlowsToReceiver(ctx0, arg, call0, _, name) and
1119+
not mayBenefitFromCallContextInstance(ctx0, call0, arg, _, _, _, name) and
1120+
not mayBenefitFromCallContextSingleton(ctx0, call0, arg, _, _, _, name) and
1121+
result = viableSourceCallable(call)
1122+
)
1123+
or
1124+
// library calls should always be able to resolve
1125+
argFlowsToReceiver(ctx.asCall(), _, call.asCall(), _, _) and
1126+
result = viableLibraryCallable(call)
10631127
)
1064-
or
1065-
// `ctx` cannot provide a type bound
1066-
exists(ArgumentNode arg |
1067-
mayBenefitFromCallContext0(ctx.asCall(), arg, call.asCall(), _, _) and
1068-
not isInstanceLocalMustFlow(arg, _, _) and
1069-
result = viableSourceCallable(call)
1070-
)
1071-
or
1072-
// library calls should always be able to resolve
1073-
mayBenefitFromCallContext0(ctx.asCall(), _, call.asCall(), _, _) and
1074-
result = viableLibraryCallable(call)
10751128
}
10761129

10771130
predicate exprNodeReturnedFrom = exprNodeReturnedFromCached/2;

0 commit comments

Comments
 (0)