Skip to content

Commit 89c83de

Browse files
committed
Ruby: Call-context sensitivity for singleton method calls
1 parent 6feff7e commit 89c83de

File tree

2 files changed

+109
-81
lines changed

2 files changed

+109
-81
lines changed

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

Lines changed: 87 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,75 @@ 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, _, _)
1071+
)
1072+
)
1073+
) and
1074+
exists(lookupSingletonMethod(tp, pragma[only_bind_into](name), exact))
1075+
}
1076+
10371077
/**
10381078
* Holds if the set of viable implementations that can be called by `call`
10391079
* might be improved by knowing the call context. This is the case if the
10401080
* receiver accesses a parameter of the enclosing callable `c` (including
10411081
* the implicit `self` parameter).
10421082
*/
10431083
predicate mayBenefitFromCallContext(DataFlowCall call, DataFlowCallable c) {
1044-
mayBenefitFromCallContext1(_, call.asCall(), c.asCallable(), _, _, _)
1084+
mayBenefitFromCallContextInstance(_, call.asCall(), _, c.asCallable(), _, _, _)
1085+
or
1086+
mayBenefitFromCallContextSingleton(_, call.asCall(), _, c.asCallable(), _, _, _)
10451087
}
10461088

10471089
/**
@@ -1050,28 +1092,38 @@ predicate mayBenefitFromCallContext(DataFlowCall call, DataFlowCallable c) {
10501092
*/
10511093
pragma[nomagic]
10521094
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))
1095+
mayBenefitFromCallContext(call, _) and
1096+
(
1097+
// `ctx` can provide a potentially better type bound
1098+
exists(RelevantCall call0, Callable res |
1099+
call0 = call.asCall() and
1100+
res = result.asCallable() and
1101+
res = getTarget(call0) and // make sure to not include e.g. private methods
1102+
exists(Module m, boolean exact, string name |
1103+
mayBenefitFromCallContextInstance(ctx.asCall(), pragma[only_bind_into](call0), _, _,
1104+
pragma[only_bind_into](m), exact, pragma[only_bind_into](name)) and
1105+
res = lookupMethod(m, name, exact)
1106+
or
1107+
mayBenefitFromCallContextSingleton(ctx.asCall(), pragma[only_bind_into](call0), _, _,
1108+
pragma[only_bind_into](m), exact, pragma[only_bind_into](name)) and
1109+
res = lookupSingletonMethod(m, name, exact)
1110+
)
10621111
)
1112+
or
1113+
// `ctx` cannot provide a type bound
1114+
exists(RelevantCall call0, RelevantCall ctx0, ArgumentNode arg, string name |
1115+
call0 = call.asCall() and
1116+
ctx0 = ctx.asCall() and
1117+
argFlowsToReceiver(ctx0, arg, call0, _, name) and
1118+
not mayBenefitFromCallContextInstance(ctx0, call0, arg, _, _, _, name) and
1119+
not mayBenefitFromCallContextSingleton(ctx0, call0, arg, _, _, _, name) and
1120+
result = viableSourceCallable(call)
1121+
)
1122+
or
1123+
// library calls should always be able to resolve
1124+
argFlowsToReceiver(ctx.asCall(), _, call.asCall(), _, _) and
1125+
result = viableLibraryCallable(call)
10631126
)
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)
10751127
}
10761128

10771129
predicate exprNodeReturnedFrom = exprNodeReturnedFromCached/2;

0 commit comments

Comments
 (0)