mirror of
https://github.com/github/codeql.git
synced 2026-04-25 16:55:19 +02:00
Merge pull request #15048 from hmac/hmac-model-editor-ruby-modules
Ruby: Model editor improvements
This commit is contained in:
@@ -6,6 +6,8 @@
|
||||
private import ruby
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import Util as Util
|
||||
private import codeql.ruby.ast.Module
|
||||
private import codeql.ruby.ast.internal.Module
|
||||
|
||||
/**
|
||||
* Contains predicates for generating `typeModel`s that contain typing
|
||||
@@ -42,5 +44,13 @@ module Types {
|
||||
valueHasTypeName(node.getAValueReachingSink(), type1) and
|
||||
Util::pathToNode(node, type2, path, true)
|
||||
)
|
||||
or
|
||||
// class Type2 < Type1
|
||||
// class Type2; include Type1
|
||||
exists(Module m1, Module m2 |
|
||||
m2.getAnImmediateAncestor() = m1 and not m2.isBuiltin() and not m1.isBuiltin()
|
||||
|
|
||||
m1.getQualifiedName() = type1 and m2.getQualifiedName() = type2 and path = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
*/
|
||||
|
||||
import ruby
|
||||
import codeql.ruby.AST
|
||||
import ModelEditor
|
||||
|
||||
from PublicEndpointFromSource endpoint
|
||||
select endpoint, endpoint.getNamespace(), endpoint.getTypeName(), endpoint.getName(),
|
||||
endpoint.getParameterTypes(), endpoint.getSupportedStatus(), endpoint.getFile().getBaseName(),
|
||||
from Endpoint endpoint
|
||||
select endpoint, endpoint.getNamespace(), endpoint.getType(), endpoint.getName(),
|
||||
endpoint.getParameters(), endpoint.getSupportedStatus(), endpoint.getFileName(),
|
||||
endpoint.getSupportedType()
|
||||
|
||||
@@ -25,56 +25,75 @@ private predicate gemFileStep(Gem::GemSpec gem, Folder folder, int n) {
|
||||
}
|
||||
|
||||
/**
|
||||
* A callable method or accessor from either the Ruby Standard Library, a 3rd party library, or from the source.
|
||||
* Gets the namespace of an endpoint in `file`.
|
||||
*/
|
||||
class Endpoint extends DataFlow::MethodNode {
|
||||
Endpoint() { this.isPublic() and not isUninteresting(this) }
|
||||
string getNamespace(File file) {
|
||||
exists(Folder folder | folder = file.getParentContainer() |
|
||||
// The nearest gemspec to this endpoint, if one exists
|
||||
result = min(Gem::GemSpec g, int n | gemFileStep(g, folder, n) | g order by n).getName()
|
||||
or
|
||||
not gemFileStep(_, folder, _) and
|
||||
result = ""
|
||||
)
|
||||
}
|
||||
|
||||
File getFile() { result = this.getLocation().getFile() }
|
||||
abstract class Endpoint instanceof DataFlow::Node {
|
||||
string getNamespace() { result = getNamespace(super.getLocation().getFile()) }
|
||||
|
||||
string getName() { result = this.getMethodName() }
|
||||
string getFileName() { result = super.getLocation().getFile().getBaseName() }
|
||||
|
||||
/**
|
||||
* Gets the namespace of this endpoint.
|
||||
*/
|
||||
bindingset[this]
|
||||
string getNamespace() {
|
||||
exists(Folder folder | folder = this.getFile().getParentContainer() |
|
||||
// The nearest gemspec to this endpoint, if one exists
|
||||
result = min(Gem::GemSpec g, int n | gemFileStep(g, folder, n) | g order by n).getName()
|
||||
or
|
||||
not gemFileStep(_, folder, _) and
|
||||
result = ""
|
||||
)
|
||||
string toString() { result = super.toString() }
|
||||
|
||||
Location getLocation() { result = super.getLocation() }
|
||||
|
||||
abstract string getType();
|
||||
|
||||
abstract string getName();
|
||||
|
||||
abstract string getParameters();
|
||||
|
||||
abstract boolean getSupportedStatus();
|
||||
|
||||
abstract string getSupportedType();
|
||||
}
|
||||
|
||||
/**
|
||||
* A callable method or accessor from source code.
|
||||
*/
|
||||
class MethodEndpoint extends Endpoint instanceof DataFlow::MethodNode {
|
||||
MethodEndpoint() {
|
||||
this.isPublic() and
|
||||
not isUninteresting(this)
|
||||
}
|
||||
|
||||
DataFlow::MethodNode getNode() { result = this }
|
||||
|
||||
override string getName() { result = super.getMethodName() }
|
||||
|
||||
/**
|
||||
* Gets the unbound type name of this endpoint.
|
||||
*/
|
||||
bindingset[this]
|
||||
string getTypeName() {
|
||||
override string getType() {
|
||||
result =
|
||||
any(DataFlow::ModuleNode m | m.getOwnInstanceMethod(this.getMethodName()) = this)
|
||||
.getQualifiedName() or
|
||||
any(DataFlow::ModuleNode m | m.getOwnInstanceMethod(this.getName()) = this).getQualifiedName() or
|
||||
result =
|
||||
any(DataFlow::ModuleNode m | m.getOwnSingletonMethod(this.getMethodName()) = this)
|
||||
any(DataFlow::ModuleNode m | m.getOwnSingletonMethod(this.getName()) = this)
|
||||
.getQualifiedName() + "!"
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parameter types of this endpoint.
|
||||
*/
|
||||
bindingset[this]
|
||||
string getParameterTypes() {
|
||||
// For now, return the names of postional and keyword parameters. We don't always have type information, so we can't return type names.
|
||||
override string getParameters() {
|
||||
// For now, return the names of positional and keyword parameters. We don't always have type information, so we can't return type names.
|
||||
// We don't yet handle splat params or block params.
|
||||
result =
|
||||
"(" +
|
||||
concat(string key, string value |
|
||||
value = any(int i | i.toString() = key | this.asCallable().getParameter(i)).getName()
|
||||
value = any(int i | i.toString() = key | super.asCallable().getParameter(i)).getName()
|
||||
or
|
||||
exists(DataFlow::ParameterNode param |
|
||||
param = this.asCallable().getKeywordParameter(key)
|
||||
param = super.asCallable().getKeywordParameter(key)
|
||||
|
|
||||
value = key + ":"
|
||||
)
|
||||
@@ -89,11 +108,11 @@ class Endpoint extends DataFlow::MethodNode {
|
||||
|
||||
/** Holds if this API is a known source. */
|
||||
pragma[nomagic]
|
||||
abstract predicate isSource();
|
||||
predicate isSource() { this.getNode() instanceof SourceCallable }
|
||||
|
||||
/** Holds if this API is a known sink. */
|
||||
pragma[nomagic]
|
||||
abstract predicate isSink();
|
||||
predicate isSink() { this.getNode() instanceof SinkCallable }
|
||||
|
||||
/** Holds if this API is a known neutral. */
|
||||
pragma[nomagic]
|
||||
@@ -107,9 +126,11 @@ class Endpoint extends DataFlow::MethodNode {
|
||||
this.hasSummary() or this.isSource() or this.isSink() or this.isNeutral()
|
||||
}
|
||||
|
||||
boolean getSupportedStatus() { if this.isSupported() then result = true else result = false }
|
||||
override boolean getSupportedStatus() {
|
||||
if this.isSupported() then result = true else result = false
|
||||
}
|
||||
|
||||
string getSupportedType() {
|
||||
override string getSupportedType() {
|
||||
this.isSink() and result = "sink"
|
||||
or
|
||||
this.isSource() and result = "source"
|
||||
@@ -131,7 +152,7 @@ string methodClassification(Call method) {
|
||||
|
||||
class TestFile extends File {
|
||||
TestFile() {
|
||||
this.getRelativePath().regexpMatch(".*(test|spec).+") and
|
||||
this.getRelativePath().regexpMatch(".*(test|spec|examples).+") and
|
||||
not this.getAbsolutePath().matches("%/ql/test/%") // allows our test cases to work
|
||||
}
|
||||
}
|
||||
@@ -163,10 +184,32 @@ class SourceCallable extends DataFlow::CallableNode {
|
||||
}
|
||||
|
||||
/**
|
||||
* A class of effectively public callables from source code.
|
||||
* A module defined in source code
|
||||
*/
|
||||
class PublicEndpointFromSource extends Endpoint {
|
||||
override predicate isSource() { this instanceof SourceCallable }
|
||||
class ModuleEndpoint extends Endpoint {
|
||||
private DataFlow::ModuleNode moduleNode;
|
||||
|
||||
override predicate isSink() { this instanceof SinkCallable }
|
||||
ModuleEndpoint() {
|
||||
this =
|
||||
min(DataFlow::Node n, Location loc |
|
||||
n.asExpr().getExpr() = moduleNode.getADeclaration() and
|
||||
loc = n.getLocation()
|
||||
|
|
||||
n order by loc.getFile().getAbsolutePath(), loc.getStartLine(), loc.getStartColumn()
|
||||
) and
|
||||
not moduleNode.(Module).isBuiltin() and
|
||||
not moduleNode.getLocation().getFile() instanceof TestFile
|
||||
}
|
||||
|
||||
DataFlow::ModuleNode getNode() { result = moduleNode }
|
||||
|
||||
override string getType() { result = this.getNode().getQualifiedName() }
|
||||
|
||||
override string getName() { result = "" }
|
||||
|
||||
override string getParameters() { result = "" }
|
||||
|
||||
override boolean getSupportedStatus() { result = false }
|
||||
|
||||
override string getSupportedType() { result = "" }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user