diff --git a/ql/src/codeql_ruby/ast/internal/Module.qll b/ql/src/codeql_ruby/ast/internal/Module.qll index 3e21e43c879..9c4d7742706 100644 --- a/ql/src/codeql_ruby/ast/internal/Module.qll +++ b/ql/src/codeql_ruby/ast/internal/Module.qll @@ -110,7 +110,7 @@ private predicate isDefinedConstant(string qualifiedModuleName) { * definition is a Namespace then it is returned, if it's a constant assignment then * the right-hand side of the assignment is resolved. */ -private TResolved resolveScopeExpr(ConstantReadAccess r) { +TResolved resolveScopeExpr(ConstantReadAccess r) { exists(string qname | qname = min(string qn, int p | diff --git a/ql/src/codeql_ruby/dataflow/internal/DataFlowDispatch.qll b/ql/src/codeql_ruby/dataflow/internal/DataFlowDispatch.qll index 916ea836e63..7c3e81d66d9 100644 --- a/ql/src/codeql_ruby/dataflow/internal/DataFlowDispatch.qll +++ b/ql/src/codeql_ruby/dataflow/internal/DataFlowDispatch.qll @@ -1,6 +1,9 @@ private import ruby private import codeql_ruby.CFG private import DataFlowPrivate +private import codeql_ruby.typetracking.TypeTracker +private import codeql_ruby.dataflow.internal.DataFlowPublic as DataFlow +private import codeql_ruby.ast.internal.Module newtype TReturnKind = TNormalReturnKind() or @@ -42,12 +45,66 @@ class DataFlowCall extends CfgNodes::ExprNodes::CallCfgNode { DataFlowCallable getEnclosingCallable() { result = this.getScope() } DataFlowCallable getTarget() { - // TODO: this is a placeholder that finds a method with the same name, iff it's uniquely named. - result = - unique(DataFlowCallable c | c.(Method).getName() = this.getNode().(MethodCall).getMethodName()) + exists(MethodCall mcall, Module tp | + mcall = this.getExpr() and + result = lookupMethod(tp, mcall.getMethodName()) + | + exists(DataFlow::Node nodeTo | + nodeTo.asExpr() = this.getReceiver() and + trackInstance(tp).flowsTo(nodeTo) + ) + ) } } +private DataFlow::LocalSourceNode trackInstance(Module tp, TypeTracker t) { + t.start() and + ( + exists(CfgNodes::ExprNodes::CallCfgNode call, DataFlow::Node nodeTo | + call.getExpr().(MethodCall).getMethodName() = "new" and + nodeTo.asExpr() = call.getReceiver() and + trackModule(tp).flowsTo(nodeTo) and + result.asExpr() = call + ) + or + // `self` in method + exists(Self self, Method enclosing | + self = result.asExpr().getExpr() and + enclosing = self.getEnclosingMethod() and + tp = enclosing.getEnclosingModule().getModule() and + not self.getEnclosingModule().getEnclosingMethod() = enclosing + ) + ) + or + exists(TypeTracker t2 | result = trackInstance(tp, t2).track(t2, t)) +} + +private DataFlow::LocalSourceNode trackInstance(Module tp) { + result = trackInstance(tp, TypeTracker::end()) +} + +private DataFlow::LocalSourceNode trackModule(Module tp, TypeTracker t) { + t.start() and + ( + // ConstantReadAccess to Module + resolveScopeExpr(result.asExpr().getExpr()) = tp + or + // `self` reference to Module + exists(Self self, ModuleBase enclosing | + self = result.asExpr().getExpr() and + enclosing = self.getEnclosingModule() and + tp = enclosing.getModule() and + not self.getEnclosingMethod().getEnclosingModule() = enclosing + ) + ) + or + exists(TypeTracker t2 | result = trackModule(tp, t2).track(t2, t)) +} + +private DataFlow::LocalSourceNode trackModule(Module tp) { + result = trackModule(tp, TypeTracker::end()) +} + /** Gets a viable run-time target for the call `call`. */ DataFlowCallable viableCallable(DataFlowCall call) { none() }