@@ -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,10 @@ module API {
121119 * end
122120 * ```
123121 */
124- DataFlow:: LocalSourceNode asSource ( ) { Impl:: use ( this , result ) }
122+ pragma [ inline]
123+ DataFlow:: LocalSourceNode asSource ( ) {
124+ result = pragma [ only_bind_out ] ( this ) .( Node:: Internal ) .asSourceInternal ( )
125+ }
125126
126127 /**
127128 * Gets a data-flow node where this value leaves the current codebase and flows into an
@@ -167,6 +168,7 @@ module API {
167168 /**
168169 * Gets a call to a method on the receiver represented by this API component.
169170 */
171+ pragma [ inline]
170172 DataFlow:: CallNode getAMethodCall ( string method ) { result = this .getReturn ( method ) .asSource ( ) }
171173
172174 /**
@@ -177,15 +179,20 @@ module API {
177179 * - A submodule of a module
178180 * - An attribute of an object
179181 */
180- bindingset [ m]
181- bindingset [ result ]
182- Node getMember ( string m ) { result = this .getASuccessor ( Label:: member ( m ) ) }
182+ pragma [ inline]
183+ Node getMember ( string m ) {
184+ result = pragma [ only_bind_out ] ( this ) .( Node:: Internal ) .getMemberInternal ( m )
185+ }
183186
184187 /**
185188 * Gets a node representing a member of this API component where the name of the member may
186189 * or may not be known statically.
187190 */
188- Node getAMember ( ) { result = this .getASuccessor ( Label:: member ( _) ) }
191+ cached
192+ Node getAMember ( ) {
193+ Impl:: forceCachingInSameStage ( ) and
194+ result = this .getASuccessor ( Label:: member ( _) )
195+ }
189196
190197 /**
191198 * Gets a node representing an instance of this API component, that is, an object whose
@@ -198,41 +205,54 @@ module API {
198205 * This predicate may have multiple results when there are multiple constructor calls invoking this API component.
199206 * Consider using `getAnInstantiation()` if there is a need to distinguish between individual constructor calls.
200207 */
208+ pragma [ inline]
201209 Node getInstance ( ) { result = this .getASubclass ( ) .getReturn ( "new" ) }
202210
203211 /**
204212 * Gets a node representing a call to `method` on the receiver represented by this node.
205213 */
214+ pragma [ inline]
206215 MethodAccessNode getMethod ( string method ) {
207- result = this . getASubclass ( ) . getASuccessor ( Label :: method ( method ) )
216+ result = pragma [ only_bind_out ] ( this ) . ( Node :: Internal ) . getMethodInternal ( method )
208217 }
209218
210219 /**
211220 * Gets a node representing the result of this call.
212221 */
213- Node getReturn ( ) { result = this .getASuccessor ( Label:: return ( ) ) }
222+ pragma [ inline]
223+ Node getReturn ( ) { result = pragma [ only_bind_out ] ( this ) .( Node:: Internal ) .getReturnInternal ( ) }
214224
215225 /**
216226 * Gets a node representing the result of calling a method on the receiver represented by this node.
217227 */
228+ pragma [ inline]
218229 Node getReturn ( string method ) { result = this .getMethod ( method ) .getReturn ( ) }
219230
220231 /** Gets an API node representing the `n`th positional parameter. */
221- pragma [ nomagic]
222- Node getParameter ( int n ) { result = this .getASuccessor ( Label:: parameter ( n ) ) }
232+ cached
233+ Node getParameter ( int n ) {
234+ Impl:: forceCachingInSameStage ( ) and
235+ result = this .getASuccessor ( Label:: parameter ( n ) )
236+ }
223237
224238 /** Gets an API node representing the given keyword parameter. */
225- pragma [ nomagic ]
239+ cached
226240 Node getKeywordParameter ( string name ) {
241+ Impl:: forceCachingInSameStage ( ) and
227242 result = this .getASuccessor ( Label:: keywordParameter ( name ) )
228243 }
229244
230245 /** Gets an API node representing the block parameter. */
231- Node getBlock ( ) { result = this .getASuccessor ( Label:: blockParameter ( ) ) }
246+ cached
247+ Node getBlock ( ) {
248+ Impl:: forceCachingInSameStage ( ) and
249+ result = this .getASuccessor ( Label:: blockParameter ( ) )
250+ }
232251
233252 /**
234253 * Gets a `new` call to the function represented by this API component.
235254 */
255+ pragma [ inline]
236256 DataFlow:: ExprNode getAnInstantiation ( ) { result = this .getInstance ( ) .asSource ( ) }
237257
238258 /**
@@ -255,12 +275,17 @@ module API {
255275 * ```
256276 * In the example above, `getMember("A").getAnImmediateSubclass()` will return uses of `B` only.
257277 */
258- Node getAnImmediateSubclass ( ) { result = this .getASuccessor ( Label:: subclass ( ) ) }
278+ cached
279+ Node getAnImmediateSubclass ( ) {
280+ Impl:: forceCachingInSameStage ( ) and result = this .getASuccessor ( Label:: subclass ( ) )
281+ }
259282
260283 /**
261284 * Gets a node representing the `content` stored on the base object.
262285 */
286+ cached
263287 Node getContent ( DataFlow:: Content content ) {
288+ Impl:: forceCachingInSameStage ( ) and
264289 result = this .getASuccessor ( Label:: content ( content ) )
265290 }
266291
@@ -274,10 +299,16 @@ module API {
274299 }
275300
276301 /** 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 ) ) }
302+ cached
303+ Node getField ( string name ) {
304+ Impl:: forceCachingInSameStage ( ) and
305+ result = this .getContent ( DataFlowPrivate:: TFieldContent ( name ) )
306+ }
278307
279308 /** Gets a node representing an element of this collection (known or unknown). */
309+ cached
280310 Node getAnElement ( ) {
311+ Impl:: forceCachingInSameStage ( ) and
281312 result = this .getContents ( any ( DataFlow:: ContentSet set | set .isAnyElement ( ) ) )
282313 }
283314
@@ -337,7 +368,7 @@ module API {
337368 /**
338369 * Gets a textual representation of this element.
339370 */
340- abstract string toString ( ) ;
371+ string toString ( ) { none ( ) }
341372
342373 /**
343374 * Gets a path of the given `length` from the root to this node.
@@ -363,6 +394,65 @@ module API {
363394 int getDepth ( ) { result = Impl:: distanceFromRoot ( this ) }
364395 }
365396
397+ /** Companion module to the `Node` class. */
398+ module Node {
399+ /**
400+ * INTERNAL USE ONLY.
401+ *
402+ * An API node, with some internal predicates exposed.
403+ */
404+ class Internal extends Node {
405+ /**
406+ * INTERNAL USE ONLY.
407+ *
408+ * Same as `asSource()` but without join-order hints.
409+ */
410+ cached
411+ DataFlow:: LocalSourceNode asSourceInternal ( ) {
412+ Impl:: forceCachingInSameStage ( ) and
413+ Impl:: use ( this , result )
414+ }
415+
416+ /**
417+ * Same as `getMember` but without join-order hints.
418+ */
419+ cached
420+ Node getMemberInternal ( string m ) {
421+ Impl:: forceCachingInSameStage ( ) and
422+ result = this .getASuccessor ( Label:: member ( m ) )
423+ }
424+
425+ /**
426+ * Same as `getMethod` but without join-order hints.
427+ */
428+ cached
429+ MethodAccessNode getMethodInternal ( string method ) {
430+ Impl:: forceCachingInSameStage ( ) and
431+ result = this .getASubclass ( ) .getASuccessor ( Label:: method ( method ) )
432+ }
433+
434+ /**
435+ * INTERNAL USE ONLY.
436+ *
437+ * Same as `getReturn()` but without join-order hints.
438+ */
439+ cached
440+ Node getReturnInternal ( ) {
441+ Impl:: forceCachingInSameStage ( ) and result = this .getASuccessor ( Label:: return ( ) )
442+ }
443+ }
444+ }
445+
446+ bindingset [ node]
447+ pragma [ inline_late]
448+ private DataFlow:: Node getAValueReachableFromSourceInline ( Node node ) {
449+ exists ( DataFlow:: LocalSourceNode src , DataFlow:: LocalSourceNode dst |
450+ Impl:: use ( node , pragma [ only_bind_into ] ( src ) ) and
451+ pragma [ only_bind_into ] ( dst ) = Impl:: trackUseNode ( src ) and
452+ dst .flowsTo ( result )
453+ )
454+ }
455+
366456 /** The root node of an API graph. */
367457 class Root extends Node , Impl:: MkRoot {
368458 override string toString ( ) { result = "root" }
@@ -443,7 +533,10 @@ module API {
443533 * you should use `.getMember` on the parent module/class. For example, for nodes corresponding to the class `Gem::Version`,
444534 * use `getTopLevelMember("Gem").getMember("Version")`.
445535 */
446- Node getTopLevelMember ( string m ) { result = root ( ) .getMember ( m ) }
536+ cached
537+ Node getTopLevelMember ( string m ) {
538+ Impl:: forceCachingInSameStage ( ) and result = root ( ) .( Node:: Internal ) .getMemberInternal ( m )
539+ }
447540
448541 /**
449542 * Provides the actual implementation of API graphs, cached for performance.
@@ -469,6 +562,36 @@ module API {
469562 */
470563 cached
471564 private module Impl {
565+ cached
566+ predicate forceCachingInSameStage ( ) { any ( ) }
567+
568+ cached
569+ predicate forceCachingBackref ( ) {
570+ 1 = 1
571+ or
572+ exists ( getTopLevelMember ( _) )
573+ or
574+ exists (
575+ any ( Node n )
576+ .( Node:: Internal )
577+ .getMemberInternal ( "foo" )
578+ .getAMember ( )
579+ .( Node:: Internal )
580+ .getMethodInternal ( "foo" )
581+ .( Node:: Internal )
582+ .getReturnInternal ( )
583+ .getParameter ( 0 )
584+ .getKeywordParameter ( "foo" )
585+ .getBlock ( )
586+ .getAnImmediateSubclass ( )
587+ .getContent ( _)
588+ .getField ( _)
589+ .getAnElement ( )
590+ .( Node:: Internal )
591+ .asSourceInternal ( )
592+ )
593+ }
594+
472595 cached
473596 newtype TApiNode =
474597 /** The root of the API graph. */
0 commit comments