Skip to content

Commit 69cb138

Browse files
committed
Ruby: Tweak caching/inlining or API graph predicates
1 parent 7e23bf3 commit 69cb138

File tree

2 files changed

+129
-20
lines changed

2 files changed

+129
-20
lines changed

ruby/ql/lib/codeql/ruby/ApiGraphs.qll

Lines changed: 125 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,7 @@ module API {
9999
*/
100100
pragma[inline]
101101
DataFlow::Node getAValueReachableFromSource() {
102-
exists(DataFlow::LocalSourceNode src | Impl::use(this, src) |
103-
Impl::trackUseNode(src).flowsTo(result)
104-
)
102+
result = getAValueReachableFromSourceInline(this)
105103
}
106104

107105
/**
@@ -121,7 +119,19 @@ module API {
121119
* end
122120
* ```
123121
*/
124-
DataFlow::LocalSourceNode asSource() { Impl::use(this, result) }
122+
pragma[inline]
123+
DataFlow::LocalSourceNode asSource() { result = pragma[only_bind_out](this).asSourceInternal() }
124+
125+
/**
126+
* INTERNAL USE ONLY.
127+
*
128+
* Same as `asSource()` but without join-order hints.
129+
*/
130+
cached
131+
DataFlow::LocalSourceNode asSourceInternal() {
132+
Impl::forceCachingInSameStage() and
133+
Impl::use(this, result)
134+
}
125135

126136
/**
127137
* Gets a data-flow node where this value leaves the current codebase and flows into an
@@ -167,6 +177,7 @@ module API {
167177
/**
168178
* Gets a call to a method on the receiver represented by this API component.
169179
*/
180+
pragma[inline]
170181
DataFlow::CallNode getAMethodCall(string method) { result = this.getReturn(method).asSource() }
171182

172183
/**
@@ -177,15 +188,29 @@ module API {
177188
* - A submodule of a module
178189
* - An attribute of an object
179190
*/
180-
bindingset[m]
181-
bindingset[result]
182-
Node getMember(string m) { result = this.getASuccessor(Label::member(m)) }
191+
pragma[inline]
192+
Node getMember(string m) { result = pragma[only_bind_out](this).getMemberInternal(m) }
193+
194+
/**
195+
* INTERNAL USE ONLY.
196+
*
197+
* Same as `getMember` but without join-order hints.
198+
*/
199+
cached
200+
Node getMemberInternal(string m) {
201+
Impl::forceCachingInSameStage() and
202+
result = this.getASuccessor(Label::member(m))
203+
}
183204

184205
/**
185206
* Gets a node representing a member of this API component where the name of the member may
186207
* or may not be known statically.
187208
*/
188-
Node getAMember() { result = this.getASuccessor(Label::member(_)) }
209+
cached
210+
Node getAMember() {
211+
Impl::forceCachingInSameStage() and
212+
result = this.getASuccessor(Label::member(_))
213+
}
189214

190215
/**
191216
* Gets a node representing an instance of this API component, that is, an object whose
@@ -198,41 +223,75 @@ module API {
198223
* This predicate may have multiple results when there are multiple constructor calls invoking this API component.
199224
* Consider using `getAnInstantiation()` if there is a need to distinguish between individual constructor calls.
200225
*/
226+
pragma[inline]
201227
Node getInstance() { result = this.getASubclass().getReturn("new") }
202228

203229
/**
204230
* Gets a node representing a call to `method` on the receiver represented by this node.
205231
*/
232+
pragma[inline]
206233
MethodAccessNode getMethod(string method) {
234+
result = pragma[only_bind_out](this).getMethodInternal(method)
235+
}
236+
237+
/**
238+
* INTERNAL USE ONLY.
239+
*
240+
* Same as `getMethod` but without join-order hints.
241+
*/
242+
cached
243+
MethodAccessNode getMethodInternal(string method) {
244+
Impl::forceCachingInSameStage() and
207245
result = this.getASubclass().getASuccessor(Label::method(method))
208246
}
209247

210248
/**
211249
* Gets a node representing the result of this call.
212250
*/
213-
Node getReturn() { result = this.getASuccessor(Label::return()) }
251+
pragma[inline]
252+
Node getReturn() { result = pragma[only_bind_out](this).getReturnInternal() }
253+
254+
/**
255+
* INTERNAL USE ONLY.
256+
*
257+
* Same as `getReturn()` but without join-order hints.
258+
*/
259+
cached
260+
Node getReturnInternal() {
261+
Impl::forceCachingInSameStage() and result = this.getASuccessor(Label::return())
262+
}
214263

215264
/**
216265
* Gets a node representing the result of calling a method on the receiver represented by this node.
217266
*/
267+
pragma[inline]
218268
Node getReturn(string method) { result = this.getMethod(method).getReturn() }
219269

220270
/** Gets an API node representing the `n`th positional parameter. */
221-
pragma[nomagic]
222-
Node getParameter(int n) { result = this.getASuccessor(Label::parameter(n)) }
271+
cached
272+
Node getParameter(int n) {
273+
Impl::forceCachingInSameStage() and
274+
result = this.getASuccessor(Label::parameter(n))
275+
}
223276

224277
/** Gets an API node representing the given keyword parameter. */
225-
pragma[nomagic]
278+
cached
226279
Node getKeywordParameter(string name) {
280+
Impl::forceCachingInSameStage() and
227281
result = this.getASuccessor(Label::keywordParameter(name))
228282
}
229283

230284
/** Gets an API node representing the block parameter. */
231-
Node getBlock() { result = this.getASuccessor(Label::blockParameter()) }
285+
cached
286+
Node getBlock() {
287+
Impl::forceCachingInSameStage() and
288+
result = this.getASuccessor(Label::blockParameter())
289+
}
232290

233291
/**
234292
* Gets a `new` call to the function represented by this API component.
235293
*/
294+
pragma[inline]
236295
DataFlow::ExprNode getAnInstantiation() { result = this.getInstance().asSource() }
237296

238297
/**
@@ -255,12 +314,17 @@ module API {
255314
* ```
256315
* In the example above, `getMember("A").getAnImmediateSubclass()` will return uses of `B` only.
257316
*/
258-
Node getAnImmediateSubclass() { result = this.getASuccessor(Label::subclass()) }
317+
cached
318+
Node getAnImmediateSubclass() {
319+
Impl::forceCachingInSameStage() and result = this.getASuccessor(Label::subclass())
320+
}
259321

260322
/**
261323
* Gets a node representing the `content` stored on the base object.
262324
*/
325+
cached
263326
Node getContent(DataFlow::Content content) {
327+
Impl::forceCachingInSameStage() and
264328
result = this.getASuccessor(Label::content(content))
265329
}
266330

@@ -274,10 +338,16 @@ module API {
274338
}
275339

276340
/** Gets a node representing the instance field of the given `name`, which must include the `@` character. */
277-
Node getField(string name) { result = this.getContent(DataFlowPrivate::TFieldContent(name)) }
341+
cached
342+
Node getField(string name) {
343+
Impl::forceCachingInSameStage() and
344+
result = this.getContent(DataFlowPrivate::TFieldContent(name))
345+
}
278346

279347
/** Gets a node representing an element of this collection (known or unknown). */
348+
cached
280349
Node getAnElement() {
350+
Impl::forceCachingInSameStage() and
281351
result = this.getContents(any(DataFlow::ContentSet set | set.isAnyElement()))
282352
}
283353

@@ -363,6 +433,16 @@ module API {
363433
int getDepth() { result = Impl::distanceFromRoot(this) }
364434
}
365435

436+
bindingset[node]
437+
pragma[inline_late]
438+
private DataFlow::Node getAValueReachableFromSourceInline(Node node) {
439+
exists(DataFlow::LocalSourceNode src, DataFlow::LocalSourceNode dst |
440+
Impl::use(node, pragma[only_bind_into](src)) and
441+
pragma[only_bind_into](dst) = Impl::trackUseNode(src) and
442+
dst.flowsTo(result)
443+
)
444+
}
445+
366446
/** The root node of an API graph. */
367447
class Root extends Node, Impl::MkRoot {
368448
override string toString() { result = "root" }
@@ -443,7 +523,10 @@ module API {
443523
* you should use `.getMember` on the parent module/class. For example, for nodes corresponding to the class `Gem::Version`,
444524
* use `getTopLevelMember("Gem").getMember("Version")`.
445525
*/
446-
Node getTopLevelMember(string m) { result = root().getMember(m) }
526+
cached
527+
Node getTopLevelMember(string m) {
528+
Impl::forceCachingInSameStage() and result = root().getMemberInternal(m)
529+
}
447530

448531
/**
449532
* Provides the actual implementation of API graphs, cached for performance.
@@ -469,6 +552,32 @@ module API {
469552
*/
470553
cached
471554
private module Impl {
555+
cached
556+
predicate forceCachingInSameStage() { any() }
557+
558+
cached
559+
predicate forceCachingBackref() {
560+
1 = 1
561+
or
562+
exists(getTopLevelMember(_))
563+
or
564+
exists(
565+
any(Node n)
566+
.getMemberInternal("foo")
567+
.getAMember()
568+
.getMethodInternal("foo")
569+
.getReturnInternal()
570+
.getParameter(0)
571+
.getKeywordParameter("foo")
572+
.getBlock()
573+
.getAnImmediateSubclass()
574+
.getContent(_)
575+
.getField(_)
576+
.getAnElement()
577+
.asSourceInternal()
578+
)
579+
}
580+
472581
cached
473582
newtype TApiNode =
474583
/** The root of the API graph. */

ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModelsSpecific.qll

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,16 +113,16 @@ API::Node getExtraNodeFromType(string type) {
113113
|
114114
suffix = "!" and
115115
(
116-
result.asSource() = constRef
116+
result.asSourceInternal() = constRef
117117
or
118-
result.asSource() = constRef.getADescendentModule().getAnOwnModuleSelf()
118+
result.asSourceInternal() = constRef.getADescendentModule().getAnOwnModuleSelf()
119119
)
120120
or
121121
suffix = "" and
122122
(
123-
result.asSource() = constRef.getAMethodCall("new")
123+
result.asSourceInternal() = constRef.getAMethodCall("new")
124124
or
125-
result.asSource() = constRef.getADescendentModule().getAnInstanceSelf()
125+
result.asSourceInternal() = constRef.getADescendentModule().getAnInstanceSelf()
126126
)
127127
)
128128
or

0 commit comments

Comments
 (0)