@@ -142,6 +142,96 @@ class AssignableWrite extends AssignableAccess {
142142 AssignableWrite ( ) { exists ( AssignableDefinition def | def .getTargetAccess ( ) = this ) }
143143}
144144
145+ /**
146+ * A `ref` argument in a call.
147+ *
148+ * All predicates in this class deliberately do not use the `Call` class, or any
149+ * subclass thereof, as that results in too conservative negative recursion
150+ * compilation errors.
151+ */
152+ private class RefArg extends AssignableAccess {
153+ private Expr call ;
154+
155+ private int position ;
156+
157+ RefArg ( ) {
158+ this .isRefArgument ( ) and
159+ this = call .getChildExpr ( position ) and
160+ (
161+ call instanceof @method_invocation_expr
162+ or
163+ call instanceof @delegate_invocation_expr
164+ or
165+ call instanceof @local_function_invocation_expr
166+ or
167+ call instanceof @object_creation_expr
168+ )
169+ }
170+
171+ pragma [ noinline]
172+ Parameter getAParameter ( string name ) {
173+ exists ( Callable callable | result = callable .getAParameter ( ) |
174+ expr_call ( call , callable ) and
175+ result .getName ( ) = name
176+ )
177+ }
178+
179+ /** Gets the parameter that this argument corresponds to. */
180+ private Parameter getParameter ( ) {
181+ exists ( string name | result = this .getAParameter ( name ) |
182+ // Appears in the positional part of the call
183+ result .getPosition ( ) = position and
184+ not exists ( this .getExplicitArgumentName ( ) )
185+ or
186+ // Appears in the named part of the call
187+ name = this .getExplicitArgumentName ( )
188+ )
189+ }
190+
191+ private Callable getSourceDeclarationTarget ( Parameter p ) {
192+ p = this .getParameter ( ) .getSourceDeclaration ( ) and
193+ result .getAParameter ( ) = p
194+ }
195+
196+ /**
197+ * Holds if the assignment to this `ref` argument via parameter `p` is
198+ * analyzable. That is, the target callable is non-overridable and from
199+ * source.
200+ */
201+ predicate isAnalyzable ( Parameter p ) {
202+ exists ( Callable callable | callable = this .getSourceDeclarationTarget ( p ) |
203+ not callable .( Virtualizable ) .isOverridableOrImplementable ( ) and
204+ callable .hasBody ( )
205+ )
206+ }
207+
208+ /** Gets an assignment to analyzable parameter `p`. */
209+ AssignableDefinition getAnAnalyzableRefDef ( Parameter p ) {
210+ this .isAnalyzable ( p ) and
211+ result .getTarget ( ) = p and
212+ not result = TImplicitParameterDefinition ( _)
213+ }
214+
215+ /**
216+ * Holds if this `ref` assignment is *not* analyzable. Equivalent with
217+ * `not this.isAnalyzable(_)`, but avoids negative recursion.
218+ */
219+ private predicate isNonAnalyzable ( ) {
220+ call instanceof @delegate_invocation_expr
221+ or
222+ exists ( Callable callable | callable = this .getSourceDeclarationTarget ( _) |
223+ callable .( Virtualizable ) .isOverridableOrImplementable ( ) or
224+ not callable .hasBody ( )
225+ )
226+ }
227+
228+ /** Holds if this `ref` access is a potential assignment. */
229+ predicate isPotentialAssignment ( ) {
230+ this .isNonAnalyzable ( ) or
231+ exists ( this .getAnAnalyzableRefDef ( _) )
232+ }
233+ }
234+
145235/** INTERNAL: Do not use. */
146236module AssignableInternal {
147237 private predicate tupleAssignmentDefinition ( AssignExpr ae , Expr leaf ) {
@@ -169,123 +259,6 @@ module AssignableInternal {
169259 )
170260 }
171261
172- /**
173- * A `ref` argument in a call.
174- *
175- * All predicates in this class deliberately do not use the `Call` class, or any
176- * subclass thereof, as that results in too conservative negative recursion
177- * compilation errors.
178- */
179- private class RefArg extends AssignableAccess {
180- private Expr call ;
181-
182- private int position ;
183-
184- RefArg ( ) {
185- this .isRefArgument ( ) and
186- this = call .getChildExpr ( position ) and
187- (
188- call instanceof @method_invocation_expr
189- or
190- call instanceof @delegate_invocation_expr
191- or
192- call instanceof @local_function_invocation_expr
193- or
194- call instanceof @object_creation_expr
195- )
196- }
197-
198- pragma [ noinline]
199- Parameter getAParameter ( string name ) {
200- exists ( Callable callable | result = callable .getAParameter ( ) |
201- expr_call ( call , callable ) and
202- result .getName ( ) = name
203- )
204- }
205-
206- /** Gets the parameter that this argument corresponds to. */
207- private Parameter getParameter ( ) {
208- exists ( string name | result = this .getAParameter ( name ) |
209- // Appears in the positional part of the call
210- result .getPosition ( ) = position and
211- not exists ( this .getExplicitArgumentName ( ) )
212- or
213- // Appears in the named part of the call
214- name = this .getExplicitArgumentName ( )
215- )
216- }
217-
218- private Callable getSourceDeclarationTarget ( Parameter p ) {
219- p = this .getParameter ( ) .getSourceDeclaration ( ) and
220- result .getAParameter ( ) = p
221- }
222-
223- /**
224- * Holds if the assignment to this `ref` argument via parameter `p` is
225- * analyzable. That is, the target callable is non-overridable and from
226- * source.
227- */
228- predicate isAnalyzable ( Parameter p ) {
229- exists ( Callable callable | callable = this .getSourceDeclarationTarget ( p ) |
230- not callable .( Virtualizable ) .isOverridableOrImplementable ( ) and
231- callable .hasBody ( )
232- )
233- }
234-
235- /** Gets an assignment to analyzable parameter `p`. */
236- AssignableDefinition getAnAnalyzableRefDef ( Parameter p ) {
237- this .isAnalyzable ( p ) and
238- result .getTarget ( ) = p and
239- not result = TImplicitParameterDefinition ( _)
240- }
241-
242- /**
243- * Holds if this `ref` assignment is *not* analyzable. Equivalent with
244- * `not this.isAnalyzable(_)`, but avoids negative recursion.
245- */
246- private predicate isNonAnalyzable ( ) {
247- call instanceof @delegate_invocation_expr
248- or
249- exists ( Callable callable | callable = this .getSourceDeclarationTarget ( _) |
250- callable .( Virtualizable ) .isOverridableOrImplementable ( ) or
251- not callable .hasBody ( )
252- )
253- }
254-
255- /** Holds if this `ref` access is a potential assignment. */
256- predicate isPotentialAssignment ( ) {
257- this .isNonAnalyzable ( ) or
258- exists ( this .getAnAnalyzableRefDef ( _) )
259- }
260- }
261-
262- /** Holds if a node in basic block `bb` assigns to `ref` parameter `p` via definition `def`. */
263- private predicate basicBlockRefParamDef (
264- ControlFlow:: BasicBlock bb , Parameter p , AssignableDefinition def
265- ) {
266- def = any ( RefArg arg ) .getAnAnalyzableRefDef ( p ) and
267- bb .getANode ( ) = def .getAControlFlowNode ( )
268- }
269-
270- /**
271- * Holds if `p` is an analyzable `ref` parameter and there is a path from the
272- * entry point of `p`'s callable to basic block `bb` without passing through
273- * any assignments to `p`.
274- */
275- private predicate parameterReachesWithoutDef ( Parameter p , ControlFlow:: BasicBlock bb ) {
276- forall ( AssignableDefinition def | basicBlockRefParamDef ( bb , p , def ) |
277- isUncertainRefCall ( def .getTargetAccess ( ) )
278- ) and
279- (
280- any ( RefArg arg ) .isAnalyzable ( p ) and
281- p .getCallable ( ) .getEntryPoint ( ) = bb .getFirstNode ( )
282- or
283- exists ( ControlFlow:: BasicBlock mid | parameterReachesWithoutDef ( p , mid ) |
284- bb = mid .getASuccessor ( )
285- )
286- )
287- }
288-
289262 // Not defined by dispatch in order to avoid too conservative negative recursion error
290263 Expr getExpr ( AssignableDefinition def ) {
291264 def = TAssignmentDefinition ( result )
@@ -355,18 +328,6 @@ module AssignableInternal {
355328 )
356329 }
357330
358- /**
359- * Holds if the `ref` assignment to `aa` via call `c` is uncertain.
360- */
361- cached
362- predicate isUncertainRefCall ( RefArg arg ) {
363- arg .isPotentialAssignment ( ) and
364- exists ( ControlFlow:: BasicBlock bb , Parameter p | arg .isAnalyzable ( p ) |
365- parameterReachesWithoutDef ( p , bb ) and
366- bb .getLastNode ( ) = p .getCallable ( ) .getExitPoint ( )
367- )
368- }
369-
370331 // Not defined by dispatch in order to avoid too conservative negative recursion error
371332 cached
372333 Assignable getTarget ( AssignableDefinition def ) {
@@ -594,6 +555,47 @@ module AssignableDefinitions {
594555 override string toString ( ) { result = ae .toString ( ) }
595556 }
596557
558+ /** Holds if a node in basic block `bb` assigns to `ref` parameter `p` via definition `def`. */
559+ private predicate basicBlockRefParamDef (
560+ ControlFlow:: BasicBlock bb , Parameter p , AssignableDefinition def
561+ ) {
562+ def = any ( RefArg arg ) .getAnAnalyzableRefDef ( p ) and
563+ bb .getANode ( ) = def .getAControlFlowNode ( )
564+ }
565+
566+ /**
567+ * Holds if `p` is an analyzable `ref` parameter and there is a path from the
568+ * entry point of `p`'s callable to basic block `bb` without passing through
569+ * any assignments to `p`.
570+ */
571+ private predicate parameterReachesWithoutDef ( Parameter p , ControlFlow:: BasicBlock bb ) {
572+ forall ( AssignableDefinition def | basicBlockRefParamDef ( bb , p , def ) |
573+ isUncertainRefCall ( def .getTargetAccess ( ) )
574+ ) and
575+ (
576+ any ( RefArg arg ) .isAnalyzable ( p ) and
577+ p .getCallable ( ) .getEntryPoint ( ) = bb .getFirstNode ( )
578+ or
579+ exists ( ControlFlow:: BasicBlock mid | parameterReachesWithoutDef ( p , mid ) |
580+ bb = mid .getASuccessor ( )
581+ )
582+ )
583+ }
584+
585+ /**
586+ * Holds if the `ref` assignment to `aa` via call `c` is uncertain.
587+ */
588+ // Not in the cached module `Cached`, as that would introduce a dependency
589+ // on the CFG construction, and effectively collapse too many stages into one
590+ cached
591+ predicate isUncertainRefCall ( RefArg arg ) {
592+ arg .isPotentialAssignment ( ) and
593+ exists ( ControlFlow:: BasicBlock bb , Parameter p | arg .isAnalyzable ( p ) |
594+ parameterReachesWithoutDef ( p , bb ) and
595+ bb .getLastNode ( ) = p .getCallable ( ) .getExitPoint ( )
596+ )
597+ }
598+
597599 /**
598600 * A definition via an `out`/`ref` argument in a call, for example
599601 * `M(out x, ref y)`.
0 commit comments