Skip to content

Commit 88baf08

Browse files
authored
Merge pull request #10358 from hvitved/ruby/dataflow/call-ctx
Ruby: Context sensitive instance method resolution
2 parents 11b2a12 + f8d2e0e commit 88baf08

File tree

5 files changed

+284
-97
lines changed

5 files changed

+284
-97
lines changed

ruby/ql/lib/codeql/ruby/ast/Module.qll

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ class Module extends TModule {
1414
/** Gets the super class of this module, if any. */
1515
Module getSuperClass() { result = getSuperClass(this) }
1616

17+
/** Gets an immediate sub class of this module, if any. */
18+
Module getASubClass() { this = getSuperClass(result) }
19+
1720
/** Gets a `prepend`ed module. */
1821
Module getAPrependedModule() { result = getAPrependedModule(this) }
1922

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

Lines changed: 205 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,20 @@ private predicate hasAdjacentTypeCheckedReads(
277277
)
278278
}
279279

280+
/** Holds if `call` may resolve to the returned source-code method. */
281+
private DataFlowCallable viableSourceCallable(DataFlowCall call) {
282+
result = TCfgScope(getTarget(call.asCall())) and
283+
not call.asCall().getExpr() instanceof YieldCall // handled by `lambdaCreation`/`lambdaCall`
284+
}
285+
286+
/** Holds if `call` may resolve to the returned summarized library method. */
287+
private DataFlowCallable viableLibraryCallable(DataFlowCall call) {
288+
exists(LibraryCallable callable |
289+
result = TLibraryCallable(callable) and
290+
call.asCall().getExpr() = callable.getACall()
291+
)
292+
}
293+
280294
cached
281295
private module Cached {
282296
cached
@@ -366,13 +380,9 @@ private module Cached {
366380
/** Gets a viable run-time target for the call `call`. */
367381
cached
368382
DataFlowCallable viableCallable(DataFlowCall call) {
369-
result = TCfgScope(getTarget(call.asCall())) and
370-
not call.asCall().getExpr() instanceof YieldCall // handled by `lambdaCreation`/`lambdaCall`
383+
result = viableSourceCallable(call)
371384
or
372-
exists(LibraryCallable callable |
373-
result = TLibraryCallable(callable) and
374-
call.asCall().getExpr() = callable.getACall()
375-
)
385+
result = viableLibraryCallable(call)
376386
}
377387

378388
cached
@@ -438,90 +448,103 @@ private DataFlow::LocalSourceNode trackModuleAccess(Module m) {
438448
result = trackModuleAccess(m, TypeTracker::end())
439449
}
440450

441-
pragma[nomagic]
442-
private DataFlow::Node trackInstance(Module tp, boolean exact, TypeTracker t) {
443-
t.start() and
444-
(
445-
result.asExpr().getExpr() instanceof NilLiteral and
446-
tp = TResolved("NilClass") and
447-
exact = true
448-
or
449-
result.asExpr().getExpr().(BooleanLiteral).isFalse() and
450-
tp = TResolved("FalseClass") and
451-
exact = true
452-
or
453-
result.asExpr().getExpr().(BooleanLiteral).isTrue() and
454-
tp = TResolved("TrueClass") and
455-
exact = true
456-
or
457-
result.asExpr().getExpr() instanceof IntegerLiteral and
458-
tp = TResolved("Integer") and
459-
exact = true
460-
or
461-
result.asExpr().getExpr() instanceof FloatLiteral and
462-
tp = TResolved("Float") and
463-
exact = true
464-
or
465-
result.asExpr().getExpr() instanceof RationalLiteral and
466-
tp = TResolved("Rational") and
467-
exact = true
468-
or
469-
result.asExpr().getExpr() instanceof ComplexLiteral and
470-
tp = TResolved("Complex") and
471-
exact = true
472-
or
473-
result.asExpr().getExpr() instanceof StringlikeLiteral and
474-
tp = TResolved("String") and
475-
exact = true
476-
or
477-
result.asExpr() instanceof CfgNodes::ExprNodes::ArrayLiteralCfgNode and
478-
tp = TResolved("Array") and
479-
exact = true
480-
or
481-
result.asExpr() instanceof CfgNodes::ExprNodes::HashLiteralCfgNode and
482-
tp = TResolved("Hash") and
483-
exact = true
484-
or
485-
result.asExpr().getExpr() instanceof MethodBase and
486-
tp = TResolved("Symbol") and
487-
exact = true
488-
or
489-
result.asParameter() instanceof BlockParameter and
490-
tp = TResolved("Proc") and
491-
exact = true
451+
/** Holds if `n` is an instance of type `tp`. */
452+
private predicate isInstance(DataFlow::Node n, Module tp, boolean exact) {
453+
n.asExpr().getExpr() instanceof NilLiteral and
454+
tp = TResolved("NilClass") and
455+
exact = true
456+
or
457+
n.asExpr().getExpr().(BooleanLiteral).isFalse() and
458+
tp = TResolved("FalseClass") and
459+
exact = true
460+
or
461+
n.asExpr().getExpr().(BooleanLiteral).isTrue() and
462+
tp = TResolved("TrueClass") and
463+
exact = true
464+
or
465+
n.asExpr().getExpr() instanceof IntegerLiteral and
466+
tp = TResolved("Integer") and
467+
exact = true
468+
or
469+
n.asExpr().getExpr() instanceof FloatLiteral and
470+
tp = TResolved("Float") and
471+
exact = true
472+
or
473+
n.asExpr().getExpr() instanceof RationalLiteral and
474+
tp = TResolved("Rational") and
475+
exact = true
476+
or
477+
n.asExpr().getExpr() instanceof ComplexLiteral and
478+
tp = TResolved("Complex") and
479+
exact = true
480+
or
481+
n.asExpr().getExpr() instanceof StringlikeLiteral and
482+
tp = TResolved("String") and
483+
exact = true
484+
or
485+
n.asExpr() instanceof CfgNodes::ExprNodes::ArrayLiteralCfgNode and
486+
tp = TResolved("Array") and
487+
exact = true
488+
or
489+
n.asExpr() instanceof CfgNodes::ExprNodes::HashLiteralCfgNode and
490+
tp = TResolved("Hash") and
491+
exact = true
492+
or
493+
n.asExpr().getExpr() instanceof MethodBase and
494+
tp = TResolved("Symbol") and
495+
exact = true
496+
or
497+
n.asParameter() instanceof BlockParameter and
498+
tp = TResolved("Proc") and
499+
exact = true
500+
or
501+
n.asExpr().getExpr() instanceof Lambda and
502+
tp = TResolved("Proc") and
503+
exact = true
504+
or
505+
exists(CfgNodes::ExprNodes::CallCfgNode call, DataFlow::LocalSourceNode sourceNode |
506+
flowsToMethodCall(call, sourceNode, "new") and
507+
exact = true and
508+
n.asExpr() = call
509+
|
510+
// `C.new`
511+
sourceNode = trackModuleAccess(tp)
492512
or
493-
result.asExpr().getExpr() instanceof Lambda and
494-
tp = TResolved("Proc") and
495-
exact = true
513+
// `self.new` inside a module
514+
selfInModule(sourceNode.(SsaSelfDefinitionNode).getVariable(), tp)
496515
or
497-
exists(CfgNodes::ExprNodes::CallCfgNode call, DataFlow::LocalSourceNode sourceNode |
498-
flowsToMethodCall(call, sourceNode, "new") and
499-
exact = true and
500-
result.asExpr() = call
501-
|
502-
// `C.new`
503-
sourceNode = trackModuleAccess(tp)
504-
or
505-
// `self.new` inside a module
506-
selfInModule(sourceNode.(SsaSelfDefinitionNode).getVariable(), tp)
516+
// `self.new` inside a singleton method
517+
selfInMethod(sourceNode.(SsaSelfDefinitionNode).getVariable(), any(SingletonMethod sm), tp)
518+
)
519+
or
520+
// `self` reference in method or top-level (but not in module or singleton method,
521+
// where instance methods cannot be called; only singleton methods)
522+
n =
523+
any(SsaSelfDefinitionNode self |
524+
exists(MethodBase m |
525+
selfInMethod(self.getVariable(), m, tp) and
526+
not m instanceof SingletonMethod and
527+
if m.getEnclosingModule() instanceof Toplevel then exact = true else exact = false
528+
)
507529
or
508-
// `self.new` inside a singleton method
509-
selfInMethod(sourceNode.(SsaSelfDefinitionNode).getVariable(), any(SingletonMethod sm), tp)
530+
selfInToplevel(self.getVariable(), tp) and
531+
exact = true
510532
)
511-
or
512-
// `self` reference in method or top-level (but not in module or singleton method,
513-
// where instance methods cannot be called; only singleton methods)
514-
result =
515-
any(SsaSelfDefinitionNode self |
516-
exists(MethodBase m |
517-
selfInMethod(self.getVariable(), m, tp) and
518-
not m instanceof SingletonMethod and
519-
if m.getEnclosingModule() instanceof Toplevel then exact = true else exact = false
520-
)
521-
or
522-
selfInToplevel(self.getVariable(), tp) and
523-
exact = true
524-
)
533+
or
534+
// `in C => c then c.foo`
535+
asModulePattern(n, tp) and
536+
exact = false
537+
or
538+
// `case object when C then object.foo`
539+
hasAdjacentTypeCheckedReads(_, _, n.asExpr(), tp) and
540+
exact = false
541+
}
542+
543+
pragma[nomagic]
544+
private DataFlow::Node trackInstance(Module tp, boolean exact, TypeTracker t) {
545+
t.start() and
546+
(
547+
isInstance(result, tp, exact)
525548
or
526549
exists(Module m |
527550
(if m.isClass() then tp = TResolved("Class") else tp = TResolved("Module")) and
@@ -536,14 +559,6 @@ private DataFlow::Node trackInstance(Module tp, boolean exact, TypeTracker t) {
536559
// needed for e.g. `self.puts`
537560
selfInMethod(result.(SsaSelfDefinitionNode).getVariable(), any(SingletonMethod sm), m)
538561
)
539-
or
540-
// `in C => c then c.foo`
541-
asModulePattern(result, tp) and
542-
exact = false
543-
or
544-
// `case object when C then object.foo`
545-
hasAdjacentTypeCheckedReads(_, _, result.asExpr(), tp) and
546-
exact = false
547562
)
548563
or
549564
exists(TypeTracker t2, StepSummary summary |
@@ -778,19 +793,112 @@ private DataFlow::Node trackSingletonMethodOnInstance(MethodBase method, string
778793
result = trackSingletonMethodOnInstance(method, name, TypeTracker::end())
779794
}
780795

796+
/** Same as `isInstance`, but includes local must-flow through SSA definitions. */
797+
private predicate isInstanceLocalMustFlow(DataFlow::Node n, Module tp, boolean exact) {
798+
isInstance(n, tp, exact)
799+
or
800+
exists(DataFlow::Node mid | isInstanceLocalMustFlow(mid, tp, exact) |
801+
n.asExpr() = mid.(SsaDefinitionNode).getDefinition().getARead()
802+
or
803+
n.(SsaDefinitionNode).getDefinition().(Ssa::WriteDefinition).assigns(mid.asExpr())
804+
)
805+
}
806+
807+
/**
808+
* Holds if `ctx` targets `encl`, which is the enclosing callable of `call`, the receiver
809+
* of `call` is a parameter access, where the corresponding argument of `ctx` is `arg`.
810+
*
811+
* `name` is the name of the method being called by `call`.
812+
*/
813+
pragma[nomagic]
814+
private predicate mayBenefitFromCallContext0(
815+
CfgNodes::ExprNodes::CallCfgNode ctx, ArgumentNode arg, CfgNodes::ExprNodes::CallCfgNode call,
816+
Callable encl, string name
817+
) {
818+
exists(
819+
ParameterNodeImpl p, SsaDefinitionNode ssaNode, ParameterPosition ppos, ArgumentPosition apos
820+
|
821+
// the receiver of `call` references `p`
822+
ssaNode = trackInstance(_, _) and
823+
LocalFlow::localFlowSsaParamInput(p, ssaNode) and
824+
flowsToMethodCall(pragma[only_bind_into](call), pragma[only_bind_into](ssaNode),
825+
pragma[only_bind_into](name)) and
826+
// `p` is a parameter of `encl`,
827+
encl = call.getScope() and
828+
p.isParameterOf(TCfgScope(encl), ppos) and
829+
// `ctx` targets `encl`
830+
getTarget(ctx) = encl and
831+
// `arg` is the argument for `p` in the call `ctx`
832+
arg.sourceArgumentOf(ctx, apos) and
833+
parameterMatch(ppos, apos)
834+
)
835+
}
836+
837+
/**
838+
* Holds if `ctx` targets `encl`, which is the enclosing callable of `call`, and
839+
* the receiver of `call` is a parameter access, where the corresponding argument
840+
* of `ctx` has type `tp`.
841+
*
842+
* `name` is the name of the method being called by `call`, and `exact` is pertaining
843+
* to the type of the argument.
844+
*/
845+
pragma[nomagic]
846+
private predicate mayBenefitFromCallContext1(
847+
CfgNodes::ExprNodes::CallCfgNode ctx, CfgNodes::ExprNodes::CallCfgNode call, Callable encl,
848+
Module tp, boolean exact, string name
849+
) {
850+
exists(ArgumentNode arg |
851+
mayBenefitFromCallContext0(ctx, arg, call, encl, name) and
852+
// `arg` has a relevant instance type
853+
isInstanceLocalMustFlow(arg, pragma[only_bind_out](tp), exact) and
854+
exists(lookupMethod(tp, pragma[only_bind_into](name)))
855+
)
856+
}
857+
781858
/**
782859
* Holds if the set of viable implementations that can be called by `call`
783860
* might be improved by knowing the call context. This is the case if the
784-
* qualifier accesses a parameter of the enclosing callable `c` (including
861+
* receiver accesses a parameter of the enclosing callable `c` (including
785862
* the implicit `self` parameter).
786863
*/
787-
predicate mayBenefitFromCallContext(DataFlowCall call, DataFlowCallable c) { none() }
864+
predicate mayBenefitFromCallContext(DataFlowCall call, DataFlowCallable c) {
865+
mayBenefitFromCallContext1(_, call.asCall(), c.asCallable(), _, _, _)
866+
}
788867

789868
/**
790869
* Gets a viable dispatch target of `call` in the context `ctx`. This is
791870
* restricted to those `call`s for which a context might make a difference.
792871
*/
793-
DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) { none() }
872+
pragma[nomagic]
873+
DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
874+
// `ctx` can provide a potentially better type bound
875+
exists(CfgNodes::ExprNodes::CallCfgNode call0, Callable res |
876+
call0 = call.asCall() and
877+
res = result.asCallable() and
878+
res = getTarget(call0) and // make sure to not include e.g. private methods
879+
exists(Module tp, Module m, boolean exact, string name |
880+
res = lookupMethod(tp, name) and
881+
mayBenefitFromCallContext1(ctx.asCall(), pragma[only_bind_into](call0), _,
882+
pragma[only_bind_into](m), exact, pragma[only_bind_into](name))
883+
|
884+
tp = m
885+
or
886+
exact = false and
887+
tp.getSuperClass+() = m
888+
)
889+
)
890+
or
891+
// `ctx` cannot provide a type bound
892+
exists(ArgumentNode arg |
893+
mayBenefitFromCallContext0(ctx.asCall(), arg, call.asCall(), _, _) and
894+
not isInstanceLocalMustFlow(arg, _, _) and
895+
result = viableSourceCallable(call)
896+
)
897+
or
898+
// library calls should always be able to resolve
899+
mayBenefitFromCallContext0(ctx.asCall(), _, call.asCall(), _, _) and
900+
result = viableLibraryCallable(call)
901+
}
794902

795903
predicate exprNodeReturnedFrom = exprNodeReturnedFromCached/2;
796904

0 commit comments

Comments
 (0)