mirror of
https://github.com/github/codeql.git
synced 2026-04-29 02:35:15 +02:00
add library for reasoning about gems and .gemspec files
This commit is contained in:
@@ -7,6 +7,7 @@ private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
import core.BasicObject::BasicObject
|
||||
import core.Object::Object
|
||||
import core.Gem::Gem
|
||||
import core.Kernel::Kernel
|
||||
import core.Module
|
||||
import core.Array
|
||||
|
||||
92
ruby/ql/lib/codeql/ruby/frameworks/core/Gem.qll
Normal file
92
ruby/ql/lib/codeql/ruby/frameworks/core/Gem.qll
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Provides modeling for the `Gem` module and `.gemspec` files.
|
||||
*/
|
||||
|
||||
private import ruby
|
||||
private import Ast
|
||||
private import codeql.ruby.ApiGraphs
|
||||
|
||||
/** Provides modeling for the `Gem` module and `.gemspec` files. */
|
||||
module Gem {
|
||||
/** A .gemspec file that lists properties of a Ruby gem. */
|
||||
class GemSpec instanceof File {
|
||||
API::Node specCall;
|
||||
|
||||
GemSpec() {
|
||||
this.getExtension() = "gemspec" and
|
||||
specCall = API::root().getMember("Gem").getMember("Specification").getMethod("new") and
|
||||
specCall.getLocation().getFile() = this
|
||||
}
|
||||
|
||||
/** Gets the name of this .gemspec file. */
|
||||
string toString() { result = File.super.getBaseName() }
|
||||
|
||||
/**
|
||||
* Gets a value of the `name` property of this .gemspec file.
|
||||
* These properties are set using the `Gem::Specification.new` method.
|
||||
*/
|
||||
private Expr getSpecProperty(string key) {
|
||||
exists(Expr rhs |
|
||||
rhs =
|
||||
specCall
|
||||
.getBlock()
|
||||
.getParameter(0)
|
||||
.getMethod(key + "=")
|
||||
.getParameter(0)
|
||||
.asSink()
|
||||
.asExpr()
|
||||
.getExpr()
|
||||
.(Ast::AssignExpr)
|
||||
.getRightOperand()
|
||||
|
|
||||
result = rhs
|
||||
or
|
||||
// some properties are arrays, we just unfold them
|
||||
result = rhs.(ArrayLiteral).getAnElement()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the name of the gem */
|
||||
string getName() { result = getSpecProperty("name").getConstantValue().getString() }
|
||||
|
||||
/** Gets a path that is loaded when the gem is required */
|
||||
private string getARequirePath() {
|
||||
result = getSpecProperty(["require_paths", "require_path"]).getConstantValue().getString()
|
||||
or
|
||||
not exists(getSpecProperty(["require_paths", "require_path"]).getConstantValue().getString()) and
|
||||
result = "lib" // the default is "lib"
|
||||
}
|
||||
|
||||
/** Gets a file that is loaded when the gem is required. */
|
||||
private File getAnRequiredFile() {
|
||||
result = File.super.getParentContainer().getFolder(getARequirePath()).getAChildContainer*()
|
||||
}
|
||||
|
||||
/** Gets a class/module that is exported by this gem. */
|
||||
private ModuleBase getAPublicModule() {
|
||||
result.(Toplevel).getLocation().getFile() = getAnRequiredFile()
|
||||
or
|
||||
result = getAPublicModule().getAModule()
|
||||
or
|
||||
result = getAPublicModule().getAClass()
|
||||
or
|
||||
result = getAPublicModule().getStmt(_).(SingletonClass)
|
||||
}
|
||||
|
||||
/** Gets a parameter from an exported method, which is an input to this gem. */
|
||||
DataFlow::ParameterNode getAnInputParameter() {
|
||||
exists(MethodBase method | method = getAPublicModule().getAMethod() |
|
||||
result.getParameter() = method.getAParameter() and
|
||||
method.isPublic()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets a parameter that is an input to a named gem. */
|
||||
DataFlow::ParameterNode getALibraryInput() {
|
||||
exists(GemSpec spec |
|
||||
exists(spec.getName()) and // we only consider `.gemspec` files that have a name
|
||||
result = spec.getAnInputParameter()
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user