Revert "Ruby: overhaul API graphs"

This commit is contained in:
Asger F
2023-06-29 15:39:19 +02:00
committed by GitHub
parent f9afea8c48
commit 5d1a437e9c
43 changed files with 1314 additions and 2458 deletions

View File

@@ -1,8 +1,8 @@
classMethodCalls
| test1.rb:58:1:58:8 | ForwardNode(call to m) |
| test1.rb:59:1:59:8 | ForwardNode(call to m) |
| test1.rb:58:1:58:8 | Use getMember("M1").getMember("C1").getMethod("m").getReturn() |
| test1.rb:59:1:59:8 | Use getMember("M2").getMember("C3").getMethod("m").getReturn() |
instanceMethodCalls
| test1.rb:61:1:61:12 | ForwardNode(call to m) |
| test1.rb:62:1:62:12 | ForwardNode(call to m) |
| test1.rb:61:1:61:12 | Use getMember("M1").getMember("C1").getMethod("new").getReturn().getMethod("m").getReturn() |
| test1.rb:62:1:62:12 | Use getMember("M2").getMember("C3").getMethod("new").getReturn().getMethod("m").getReturn() |
flowThroughArray
| test1.rb:73:1:73:10 | call to m |

View File

@@ -1,77 +0,0 @@
import ruby
import codeql.ruby.ast.internal.TreeSitter
import codeql.ruby.dataflow.internal.AccessPathSyntax
import codeql.ruby.frameworks.data.internal.ApiGraphModels
import codeql.ruby.ApiGraphs
import TestUtilities.InlineExpectationsTest
class AccessPathFromExpectation extends AccessPath::Range {
AccessPathFromExpectation() { hasExpectationWithValue(_, this) }
}
API::Node evaluatePath(AccessPath path, int n) {
path instanceof AccessPathFromExpectation and
n = 1 and
exists(AccessPathToken token | token = path.getToken(0) |
token.getName() = "Member" and
result = API::getTopLevelMember(token.getAnArgument())
or
token.getName() = "Method" and
result = API::getTopLevelCall(token.getAnArgument())
or
token.getName() = "EntryPoint" and
result = token.getAnArgument().(API::EntryPoint).getANode()
)
or
result = getSuccessorFromNode(evaluatePath(path, n - 1), path.getToken(n - 1))
or
result = getSuccessorFromInvoke(evaluatePath(path, n - 1), path.getToken(n - 1))
or
// TODO this is a workaround, support parsing of Method['[]'] instead
path.getToken(n - 1).getName() = "MethodBracket" and
result = evaluatePath(path, n - 1).getMethod("[]")
}
API::Node evaluatePath(AccessPath path) { result = evaluatePath(path, path.getNumToken()) }
module ApiUseTest implements TestSig {
string getARelevantTag() { result = ["source", "sink", "call", "reachableFromSource"] }
predicate hasActualResult(Location location, string element, string tag, string value) {
// All results are considered optional
none()
}
predicate hasOptionalResult(Location location, string element, string tag, string value) {
exists(API::Node apiNode, DataFlow::Node dataflowNode |
apiNode = evaluatePath(value) and
(
tag = "source" and dataflowNode = apiNode.asSource()
or
tag = "reachableFromSource" and dataflowNode = apiNode.getAValueReachableFromSource()
or
tag = "sink" and dataflowNode = apiNode.asSink()
or
tag = "call" and dataflowNode = apiNode.asCall()
) and
location = dataflowNode.getLocation() and
element = dataflowNode.toString()
)
}
}
import MakeTest<ApiUseTest>
class CustomEntryPointCall extends API::EntryPoint {
CustomEntryPointCall() { this = "CustomEntryPointCall" }
override DataFlow::CallNode getACall() { result.getMethodName() = "customEntryPointCall" }
}
class CustomEntryPointUse extends API::EntryPoint {
CustomEntryPointUse() { this = "CustomEntryPointUse" }
override DataFlow::LocalSourceNode getASource() {
result.(DataFlow::CallNode).getMethodName() = "customEntryPointUse"
}
}

View File

@@ -1,39 +1,39 @@
Something.foo.withCallback do |a, b| #$ source=Member[Something].Method[foo].ReturnValue
a.something #$ source=Member[Something].Method[foo].ReturnValue.Method[withCallback].Argument[block].Argument[0].Method[something].ReturnValue
b.somethingElse #$ source=Member[Something].Method[foo].ReturnValue.Method[withCallback].Argument[block].Argument[1].Method[somethingElse].ReturnValue
end #$ source=Member[Something].Method[foo].ReturnValue.Method[withCallback].ReturnValue
Something.foo.withCallback do |a, b| #$ use=getMember("Something").getMethod("foo").getReturn()
a.something #$ use=getMember("Something").getMethod("foo").getReturn().getMethod("withCallback").getBlock().getParameter(0).getMethod("something").getReturn()
b.somethingElse #$ use=getMember("Something").getMethod("foo").getReturn().getMethod("withCallback").getBlock().getParameter(1).getMethod("somethingElse").getReturn()
end #$ use=getMember("Something").getMethod("foo").getReturn().getMethod("withCallback").getReturn()
Something.withNamedArg do |a:, b: nil| #$ source=Member[Something]
a.something #$ source=Member[Something].Method[withNamedArg].Argument[block].Parameter[a:].Method[something].ReturnValue
b.somethingElse #$ source=Member[Something].Method[withNamedArg].Argument[block].Parameter[b:].Method[somethingElse].ReturnValue
end #$ source=Member[Something].Method[withNamedArg].ReturnValue
Something.withNamedArg do |a:, b: nil| #$ use=getMember("Something")
a.something #$ use=getMember("Something").getMethod("withNamedArg").getBlock().getKeywordParameter("a").getMethod("something").getReturn()
b.somethingElse #$ use=getMember("Something").getMethod("withNamedArg").getBlock().getKeywordParameter("b").getMethod("somethingElse").getReturn()
end #$ use=getMember("Something").getMethod("withNamedArg").getReturn()
Something.withLambda ->(a, b) { #$ source=Member[Something]
a.something #$ source=Member[Something].Method[withLambda].Argument[0].Parameter[0].Method[something].ReturnValue
b.something #$ source=Member[Something].Method[withLambda].Argument[0].Parameter[1].Method[something].ReturnValue
} #$ source=Member[Something].Method[withLambda].ReturnValue
Something.withLambda ->(a, b) { #$ use=getMember("Something")
a.something #$ use=getMember("Something").getMethod("withLambda").getParameter(0).getParameter(0).getMethod("something").getReturn()
b.something #$ use=getMember("Something").getMethod("withLambda").getParameter(0).getParameter(1).getMethod("something").getReturn()
} #$ use=getMember("Something").getMethod("withLambda").getReturn()
Something.namedCallback( #$ source=Member[Something]
Something.namedCallback( #$ use=getMember("Something")
onEvent: ->(a, b) {
a.something #$ source=Member[Something].Method[namedCallback].Argument[onEvent:].Parameter[0].Method[something].ReturnValue
b.something #$ source=Member[Something].Method[namedCallback].Argument[onEvent:].Parameter[1].Method[something].ReturnValue
a.something #$ use=getMember("Something").getMethod("namedCallback").getKeywordParameter("onEvent").getParameter(0).getMethod("something").getReturn()
b.something #$ use=getMember("Something").getMethod("namedCallback").getKeywordParameter("onEvent").getParameter(1).getMethod("something").getReturn()
}
) #$ source=Member[Something].Method[namedCallback].ReturnValue
) #$ use=getMember("Something").getMethod("namedCallback").getReturn()
Something.nestedCall1 do |a| #$ source=Member[Something]
a.nestedCall2 do |b:| #$ reachableFromSource=Member[Something].Method[nestedCall1].Argument[block].Parameter[0]
b.something #$ source=Member[Something].Method[nestedCall1].Argument[block].Parameter[0].Method[nestedCall2].Argument[block].Parameter[b:].Method[something].ReturnValue
end #$ source=Member[Something].Method[nestedCall1].Argument[block].Parameter[0].Method[nestedCall2].ReturnValue
end #$ source=Member[Something].Method[nestedCall1].ReturnValue
Something.nestedCall1 do |a| #$ use=getMember("Something")
a.nestedCall2 do |b:| #$ use=getMember("Something").getMethod("nestedCall1").getBlock().getParameter(0)
b.something #$ use=getMember("Something").getMethod("nestedCall1").getBlock().getParameter(0).getMethod("nestedCall2").getBlock().getKeywordParameter("b").getMethod("something").getReturn()
end #$ use=getMember("Something").getMethod("nestedCall1").getBlock().getParameter(0).getMethod("nestedCall2").getReturn()
end #$ use=getMember("Something").getMethod("nestedCall1").getReturn()
def getCallback()
->(x) {
x.something #$ source=Member[Something].Method[indirectCallback].Argument[0].Parameter[0].Method[something].ReturnValue
x.something #$ use=getMember("Something").getMethod("indirectCallback").getParameter(0).getParameter(0).getMethod("something").getReturn()
}
end
Something.indirectCallback(getCallback()) #$ source=Member[Something].Method[indirectCallback].ReturnValue
Something.indirectCallback(getCallback()) #$ use=getMember("Something").getMethod("indirectCallback").getReturn()
Something.withMixed do |a, *args, b| #$ source=Member[Something]
a.something #$ source=Member[Something].Method[withMixed].Argument[block].Parameter[0].Method[something].ReturnValue
Something.withMixed do |a, *args, b| #$ use=getMember("Something")
a.something #$ use=getMember("Something").getMethod("withMixed").getBlock().getParameter(0).getMethod("something").getReturn()
# b.something # not currently handled correctly
end #$ source=Member[Something].Method[withMixed].ReturnValue
end #$ use=getMember("Something").getMethod("withMixed").getReturn()

View File

@@ -1,31 +0,0 @@
def chained_access1
Something.foo [[[
'sink' # $ sink=Member[Something].Method[foo].Argument[0].Element[0].Element[0].Element[0]
]]]
end
def chained_access2
array = []
array[0] = [[
'sink' # $ sink=Member[Something].Method[foo].Argument[0].Element[0].Element[0].Element[0]
]]
Something.foo array
end
def chained_access3
array = [[]]
array[0][0] = [
'sink' # $ sink=Member[Something].Method[foo].Argument[0].Element[0].Element[0].Element[0]
]
Something.foo array
end
def chained_access4
Something.foo {
:one => {
:two => {
:three => 'sink' # $ sink=Member[Something].Method[foo].Argument[0].Element[:one].Element[:two].Element[:three]
}
}
}
end

View File

@@ -1,64 +0,0 @@
class BaseClass
def inheritedInstanceMethod
yield "taint" # $ sink=Member[Something].Method[foo].Argument[block].ReturnValue.Method[inheritedInstanceMethod].Parameter[block].Argument[0]
end
def self.inheritedSingletonMethod
yield "taint" # $ sink=Member[Something].Method[bar].Argument[block].ReturnValue.Method[inheritedSingletonMethod].Parameter[block].Argument[0]
end
end
class ClassWithCallbacks < BaseClass
def instanceMethod
yield "taint" # $ sink=Member[Something].Method[foo].Argument[block].ReturnValue.Method[instanceMethod].Parameter[block].Argument[0]
end
def self.singletonMethod
yield "bar" # $ sink=Member[Something].Method[bar].Argument[block].ReturnValue.Method[singletonMethod].Parameter[block].Argument[0]
end
def escapeSelf
Something.baz { self }
end
def self.escapeSingletonSelf
Something.baz { self }
end
def self.foo x
x # $ reachableFromSource=Member[BaseClass].Method[foo].Parameter[0]
x # $ reachableFromSource=Member[ClassWithCallbacks].Method[foo].Parameter[0]
x # $ reachableFromSource=Member[Subclass].Method[foo].Parameter[0]
end
def bar x
x # $ reachableFromSource=Member[BaseClass].Instance.Method[bar].Parameter[0]
x # $ reachableFromSource=Member[ClassWithCallbacks].Instance.Method[bar].Parameter[0]
x # $ reachableFromSource=Member[Subclass].Instance.Method[bar].Parameter[0]
end
end
class Subclass < ClassWithCallbacks
def instanceMethodInSubclass
yield "bar" # $ sink=Member[Something].Method[baz].Argument[block].ReturnValue.Method[instanceMethodInSubclass].Parameter[block].Argument[0]
end
def self.singletonMethodInSubclass
yield "bar" # $ sink=Member[Something].Method[baz].Argument[block].ReturnValue.Method[singletonMethodInSubclass].Parameter[block].Argument[0]
end
end
Something.foo { ClassWithCallbacks.new }
Something.bar { ClassWithCallbacks }
class ClassWithCallMethod
def call x
x # $ reachableFromSource=Method[topLevelMethod].Argument[0].Parameter[0]
"bar" # $ sink=Method[topLevelMethod].Argument[0].ReturnValue
end
end
topLevelMethod ClassWithCallMethod.new
blah = topLevelMethod
blah # $ reachableFromSource=Method[topLevelMethod].ReturnValue

View File

@@ -1,10 +0,0 @@
module SelfDotClass
module Mixin
def foo
self.class.bar # $ call=Member[Foo].Method[bar]
end
end
class Subclass < Foo
include Mixin
end
end

View File

@@ -1,34 +1,34 @@
MyModule #$ source=Member[MyModule]
print MyModule.foo #$ source=Member[MyModule].Method[foo].ReturnValue
Kernel.print(e) #$ source=Member[Kernel].Method[print].ReturnValue sink=Member[Kernel].Method[print].Argument[0]
Object::Kernel #$ source=Member[Kernel]
Object::Kernel.print(e) #$ source=Member[Kernel].Method[print].ReturnValue
MyModule #$ use=getMember("MyModule")
print MyModule.foo #$ use=getMember("MyModule").getMethod("foo").getReturn()
Kernel.print(e) #$ use=getMember("Kernel").getMethod("print").getReturn() def=getMember("Kernel").getMethod("print").getParameter(0)
Object::Kernel #$ use=getMember("Kernel")
Object::Kernel.print(e) #$ use=getMember("Kernel").getMethod("print").getReturn()
begin
print MyModule.bar #$ source=Member[MyModule].Method[bar].ReturnValue
raise AttributeError #$ source=Member[AttributeError]
rescue AttributeError => e #$ source=Member[AttributeError]
Kernel.print(e) #$ source=Member[Kernel].Method[print].ReturnValue
print MyModule.bar #$ use=getMember("MyModule").getMethod("bar").getReturn()
raise AttributeError #$ use=getMember("AttributeError")
rescue AttributeError => e #$ use=getMember("AttributeError")
Kernel.print(e) #$ use=getMember("Kernel").getMethod("print").getReturn()
end
Unknown.new.run #$ source=Member[Unknown].Method[new].ReturnValue.Method[run].ReturnValue
Foo::Bar::Baz #$ source=Member[Foo].Member[Bar].Member[Baz]
Unknown.new.run #$ use=getMember("Unknown").getMethod("new").getReturn().getMethod("run").getReturn()
Foo::Bar::Baz #$ use=getMember("Foo").getMember("Bar").getMember("Baz")
Const = [1, 2, 3] #$ source=Member[Array].MethodBracket.ReturnValue
Const.each do |c| #$ source=Member[Const]
puts c #$ reachableFromSource=Member[Const].Method[each].Argument[block].Parameter[0] reachableFromSource=Member[Const].Element[any]
end #$ source=Member[Const].Method[each].ReturnValue sink=Member[Const].Method[each].Argument[block]
Const = [1, 2, 3] #$ use=getMember("Array").getMethod("[]").getReturn()
Const.each do |c| #$ use=getMember("Const")
puts c #$ use=getMember("Const").getMethod("each").getBlock().getParameter(0) use=getMember("Const").getContent(element)
end #$ use=getMember("Const").getMethod("each").getReturn() def=getMember("Const").getMethod("each").getBlock()
foo = Foo #$ source=Member[Foo]
foo::Bar::Baz #$ source=Member[Foo].Member[Bar].Member[Baz]
foo = Foo #$ use=getMember("Foo")
foo::Bar::Baz #$ use=getMember("Foo").getMember("Bar").getMember("Baz")
FooAlias = Foo #$ source=Member[Foo]
FooAlias::Bar::Baz #$ source=Member[Foo].Member[Bar].Member[Baz] source=Member[FooAlias].Member[Bar].Member[Baz]
FooAlias = Foo #$ use=getMember("Foo")
FooAlias::Bar::Baz #$ use=getMember("Foo").getMember("Bar").getMember("Baz")
module Outer
module Inner
end
end
Outer::Inner.foo #$ source=Member[Outer].Member[Inner].Method[foo].ReturnValue
Outer::Inner.foo #$ use=getMember("Outer").getMember("Inner").getMethod("foo").getReturn()
module M1
class C1
@@ -40,36 +40,36 @@ module M1
end
end
class C2 < M1::C1 #$ source=Member[M1].Member[C1]
class C2 < M1::C1 #$ use=getMember("M1").getMember("C1")
end
module M2
class C3 < M1::C1 #$ source=Member[M1].Member[C1]
class C3 < M1::C1 #$ use=getMember("M1").getMember("C1")
end
class C4 < C2 #$ source=Member[C2]
class C4 < C2 #$ use=getMember("C2")
end
end
C2 #$ source=Member[C2] reachableFromSource=Member[M1].Member[C1]
M2::C3 #$ source=Member[M2].Member[C3] reachableFromSource=Member[M1].Member[C1]
M2::C4 #$ source=Member[M2].Member[C4] reachableFromSource=Member[C2] reachableFromSource=Member[M1].Member[C1]
C2 #$ use=getMember("C2") use=getMember("M1").getMember("C1").getASubclass()
M2::C3 #$ use=getMember("M2").getMember("C3") use=getMember("M1").getMember("C1").getASubclass()
M2::C4 #$ use=getMember("M2").getMember("C4") use=getMember("C2").getASubclass() use=getMember("M1").getMember("C1").getASubclass().getASubclass()
M1::C1.m #$ source=Member[M1].Member[C1].Method[m].ReturnValue
M2::C3.m #$ source=Member[M2].Member[C3].Method[m].ReturnValue source=Member[M1].Member[C1].Method[m].ReturnValue
M1::C1.m #$ use=getMember("M1").getMember("C1").getMethod("m").getReturn()
M2::C3.m #$ use=getMember("M2").getMember("C3").getMethod("m").getReturn() use=getMember("M1").getMember("C1").getASubclass().getMethod("m").getReturn()
M1::C1.new.m #$ source=Member[M1].Member[C1].Method[new].ReturnValue.Method[m].ReturnValue
M2::C3.new.m #$ source=Member[M2].Member[C3].Method[new].ReturnValue.Method[m].ReturnValue
M1::C1.new.m #$ use=getMember("M1").getMember("C1").getMethod("new").getReturn().getMethod("m").getReturn()
M2::C3.new.m #$ use=getMember("M2").getMember("C3").getMethod("new").getReturn().getMethod("m").getReturn()
Foo.foo(a,b:c) #$ source=Member[Foo].Method[foo].ReturnValue sink=Member[Foo].Method[foo].Argument[0] sink=Member[Foo].Method[foo].Argument[b:]
Foo.foo(a,b:c) #$ use=getMember("Foo").getMethod("foo").getReturn() def=getMember("Foo").getMethod("foo").getParameter(0) def=getMember("Foo").getMethod("foo").getKeywordParameter("b")
def userDefinedFunction(x, y)
x.noApiGraph(y)
x.customEntryPointCall(y) #$ call=EntryPoint[CustomEntryPointCall] source=EntryPoint[CustomEntryPointCall].ReturnValue sink=EntryPoint[CustomEntryPointCall].Parameter[0]
x.customEntryPointUse(y) #$ source=EntryPoint[CustomEntryPointUse]
x.customEntryPointCall(y) #$ call=entryPoint("CustomEntryPointCall") use=entryPoint("CustomEntryPointCall").getReturn() rhs=entryPoint("CustomEntryPointCall").getParameter(0)
x.customEntryPointUse(y) #$ use=entryPoint("CustomEntryPointUse")
end
array = [A::B::C] #$ source=Member[Array].MethodBracket.ReturnValue
array[0].m #$ source=Member[A].Member[B].Member[C].Method[m].ReturnValue source=Member[Array].MethodBracket.ReturnValue.Element[0].Method[m].ReturnValue
array = [A::B::C] #$ use=getMember("Array").getMethod("[]").getReturn()
array[0].m #$ use=getMember("A").getMember("B").getMember("C").getMethod("m").getReturn()
A::B::C[0] #$ source=Member[A].Member[B].Member[C].Element[0]
A::B::C[0] #$ use=getMember("A").getMember("B").getMember("C").getContent(element_0)

View File

@@ -0,0 +1,88 @@
import codeql.ruby.AST
import codeql.ruby.DataFlow
import TestUtilities.InlineExpectationsTest
import codeql.ruby.ApiGraphs
class CustomEntryPointCall extends API::EntryPoint {
CustomEntryPointCall() { this = "CustomEntryPointCall" }
override DataFlow::CallNode getACall() { result.getMethodName() = "customEntryPointCall" }
}
class CustomEntryPointUse extends API::EntryPoint {
CustomEntryPointUse() { this = "CustomEntryPointUse" }
override DataFlow::LocalSourceNode getASource() {
result.(DataFlow::CallNode).getMethodName() = "customEntryPointUse"
}
}
module ApiUseTest implements TestSig {
string getARelevantTag() { result = ["use", "def", "call"] }
private predicate relevantNode(API::Node a, DataFlow::Node n, Location l, string tag) {
l = n.getLocation() and
(
tag = "use" and
n = a.getAValueReachableFromSource()
or
tag = "def" and
n = a.asSink()
or
tag = "call" and
n = a.(API::MethodAccessNode).getCallNode()
)
}
predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "use" and // def tags are always optional
exists(DataFlow::Node n | relevantNode(_, n, location, tag) |
// Only report the longest path on this line:
value =
max(API::Node a2, Location l2, DataFlow::Node n2 |
relevantNode(a2, n2, l2, tag) and
l2.getFile() = location.getFile() and
l2.getEndLine() = location.getEndLine()
|
a2.getPath()
order by
size(n2.asExpr().getExpr()), a2.getPath().length() desc, a2.getPath() desc
) and
element = n.toString()
)
}
// We also permit optional annotations for any other path on the line.
// This is used to test subclass paths, which typically have a shorter canonical path.
predicate hasOptionalResult(Location location, string element, string tag, string value) {
exists(API::Node a, DataFlow::Node n | relevantNode(a, n, location, tag) |
element = n.toString() and
value = getAPath(a, _)
)
}
}
import MakeTest<ApiUseTest>
private int size(AstNode n) { not n instanceof StmtSequence and result = count(n.getAChild*()) }
/**
* Gets a path of the given `length` from the root to the given node.
* This is a copy of `API::getAPath()` without the restriction on path length,
* which would otherwise rule out paths involving `getASubclass()`.
*/
string getAPath(API::Node node, int length) {
node instanceof API::Root and
length = 0 and
result = ""
or
exists(API::Node pred, API::Label::ApiLabel lbl, string predpath |
pred.getASuccessor(lbl) = node and
predpath = getAPath(pred, length - 1) and
exists(string dot | if length = 1 then dot = "" else dot = "." |
result = predpath + dot + lbl and
// avoid producing strings longer than 1MB
result.length() < 1000 * 1000
)
)
}