add library for reasoning about gems and .gemspec files

This commit is contained in:
erik-krogh
2022-10-10 15:02:18 +02:00
parent 99b90789e5
commit 75422dfa72
2 changed files with 93 additions and 0 deletions

View File

@@ -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

View 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()
)
}
}