Skip to content

Commit 75422df

Browse files
committed
add library for reasoning about gems and .gemspec files
1 parent 99b9078 commit 75422df

File tree

2 files changed

+93
-0
lines changed

2 files changed

+93
-0
lines changed

ruby/ql/lib/codeql/ruby/frameworks/Core.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ private import codeql.ruby.DataFlow
77
private import codeql.ruby.dataflow.FlowSummary
88
import core.BasicObject::BasicObject
99
import core.Object::Object
10+
import core.Gem::Gem
1011
import core.Kernel::Kernel
1112
import core.Module
1213
import core.Array
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* Provides modeling for the `Gem` module and `.gemspec` files.
3+
*/
4+
5+
private import ruby
6+
private import Ast
7+
private import codeql.ruby.ApiGraphs
8+
9+
/** Provides modeling for the `Gem` module and `.gemspec` files. */
10+
module Gem {
11+
/** A .gemspec file that lists properties of a Ruby gem. */
12+
class GemSpec instanceof File {
13+
API::Node specCall;
14+
15+
GemSpec() {
16+
this.getExtension() = "gemspec" and
17+
specCall = API::root().getMember("Gem").getMember("Specification").getMethod("new") and
18+
specCall.getLocation().getFile() = this
19+
}
20+
21+
/** Gets the name of this .gemspec file. */
22+
string toString() { result = File.super.getBaseName() }
23+
24+
/**
25+
* Gets a value of the `name` property of this .gemspec file.
26+
* These properties are set using the `Gem::Specification.new` method.
27+
*/
28+
private Expr getSpecProperty(string key) {
29+
exists(Expr rhs |
30+
rhs =
31+
specCall
32+
.getBlock()
33+
.getParameter(0)
34+
.getMethod(key + "=")
35+
.getParameter(0)
36+
.asSink()
37+
.asExpr()
38+
.getExpr()
39+
.(Ast::AssignExpr)
40+
.getRightOperand()
41+
|
42+
result = rhs
43+
or
44+
// some properties are arrays, we just unfold them
45+
result = rhs.(ArrayLiteral).getAnElement()
46+
)
47+
}
48+
49+
/** Gets the name of the gem */
50+
string getName() { result = getSpecProperty("name").getConstantValue().getString() }
51+
52+
/** Gets a path that is loaded when the gem is required */
53+
private string getARequirePath() {
54+
result = getSpecProperty(["require_paths", "require_path"]).getConstantValue().getString()
55+
or
56+
not exists(getSpecProperty(["require_paths", "require_path"]).getConstantValue().getString()) and
57+
result = "lib" // the default is "lib"
58+
}
59+
60+
/** Gets a file that is loaded when the gem is required. */
61+
private File getAnRequiredFile() {
62+
result = File.super.getParentContainer().getFolder(getARequirePath()).getAChildContainer*()
63+
}
64+
65+
/** Gets a class/module that is exported by this gem. */
66+
private ModuleBase getAPublicModule() {
67+
result.(Toplevel).getLocation().getFile() = getAnRequiredFile()
68+
or
69+
result = getAPublicModule().getAModule()
70+
or
71+
result = getAPublicModule().getAClass()
72+
or
73+
result = getAPublicModule().getStmt(_).(SingletonClass)
74+
}
75+
76+
/** Gets a parameter from an exported method, which is an input to this gem. */
77+
DataFlow::ParameterNode getAnInputParameter() {
78+
exists(MethodBase method | method = getAPublicModule().getAMethod() |
79+
result.getParameter() = method.getAParameter() and
80+
method.isPublic()
81+
)
82+
}
83+
}
84+
85+
/** Gets a parameter that is an input to a named gem. */
86+
DataFlow::ParameterNode getALibraryInput() {
87+
exists(GemSpec spec |
88+
exists(spec.getName()) and // we only consider `.gemspec` files that have a name
89+
result = spec.getAnInputParameter()
90+
)
91+
}
92+
}

0 commit comments

Comments
 (0)