@@ -429,7 +429,8 @@ module Expressions {
429429 not this instanceof ObjectCreation and
430430 not this instanceof ArrayCreation and
431431 not this instanceof QualifiedWriteAccess and
432- not this instanceof AccessorWrite and
432+ not this instanceof QualifiedAccessorWrite and
433+ not this instanceof QualifiedAssignments and
433434 not this instanceof NoNodeExpr and
434435 not this instanceof SwitchExpr and
435436 not this instanceof SwitchCaseExpr and
@@ -481,14 +482,8 @@ module Expressions {
481482 }
482483 }
483484
484- private class StatOrDynAccessorCall_ =
485- @dynamic_member_access_expr or @dynamic_element_access_expr or @call_access_expr;
486-
487- /** A normal or a (potential) dynamic call to an accessor. */
488- private class StatOrDynAccessorCall extends Expr , StatOrDynAccessorCall_ { }
489-
490485 /**
491- * An expression that writes via an accessor call , for example `x.Prop = 0`,
486+ * An expression that writes via an accessor, for example `x.Prop = 0`,
492487 * where `Prop` is a property.
493488 *
494489 * Accessor writes need special attention, because we need to model the fact
@@ -498,24 +493,31 @@ module Expressions {
498493 * ```csharp
499494 * x -> 0 -> set_Prop -> x.Prop = 0
500495 * ```
496+ *
497+ * For consistency, control flow is implemented this way for all accessor writes.
498+ * For example, `x.Field = 0`, where `Field` is a field, we want a CFG that looks like
499+ *
500+ * ```csharp
501+ * x -> 0 -> x.Field -> x.Field = 0
502+ * ```
501503 */
502- class AccessorWrite extends PostOrderTree instanceof Expr {
504+ private class QualifiedAccessorWrite extends PostOrderTree instanceof Expr {
503505 AssignableDefinition def ;
504506
505- AccessorWrite ( ) {
507+ QualifiedAccessorWrite ( ) {
506508 def .getExpr ( ) = this and
507- def .getTargetAccess ( ) . ( WriteAccess ) instanceof StatOrDynAccessorCall and
509+ def .getTargetAccess ( ) instanceof QualifiedWriteAccess and
508510 not this instanceof AssignOperationWithExpandedAssignment
509511 }
510512
511513 /**
512514 * Gets the `i`th accessor being called in this write. More than one call
513515 * can happen in tuple assignments.
514516 */
515- StatOrDynAccessorCall getCall ( int i ) {
517+ WriteAccess getAccess ( int i ) {
516518 result =
517519 rank [ i + 1 ] ( AssignableDefinitions:: TupleAssignmentDefinition tdef |
518- tdef .getExpr ( ) = this and tdef . getTargetAccess ( ) instanceof StatOrDynAccessorCall
520+ tdef .getExpr ( ) = this
519521 |
520522 tdef order by tdef .getEvaluationOrder ( )
521523 ) .getTargetAccess ( )
@@ -528,7 +530,13 @@ module Expressions {
528530 final override predicate propagatesAbnormal ( AstNode child ) {
529531 child = getExprChild ( this , _)
530532 or
531- child = this .getCall ( _)
533+ child = this .getAccess ( _)
534+ }
535+
536+ final override predicate last ( AstNode last , Completion c ) {
537+ PostOrderTree .super .last ( last , c )
538+ or
539+ last ( getExprChild ( this , 0 ) , last , c ) and c .( NullnessCompletion ) .isNull ( )
532540 }
533541
534542 final override predicate first ( AstNode first ) { first ( getExprChild ( this , 0 ) , first ) }
@@ -538,31 +546,69 @@ module Expressions {
538546 exists ( int i |
539547 last ( getExprChild ( this , i ) , pred , c ) and
540548 c instanceof NormalCompletion and
549+ ( i != 0 or not c .( NullnessCompletion ) .isNull ( ) ) and
541550 first ( getExprChild ( this , i + 1 ) , succ )
542551 )
543552 or
544553 // Flow from last element of last child to first accessor call
545554 last ( getLastExprChild ( this ) , pred , c ) and
546- succ = this .getCall ( 0 ) and
555+ succ = this .getAccess ( 0 ) and
547556 c instanceof NormalCompletion
548557 or
549558 // Flow from one call to the next
550- exists ( int i | pred = this .getCall ( i ) |
551- succ = this .getCall ( i + 1 ) and
559+ exists ( int i | pred = this .getAccess ( i ) |
560+ succ = this .getAccess ( i + 1 ) and
552561 c .isValidFor ( pred ) and
553562 c instanceof NormalCompletion
554563 )
555564 or
556565 // Post-order: flow from last call to element itself
557- exists ( int last | last = max ( int i | exists ( this .getCall ( i ) ) ) |
558- pred = this .getCall ( last ) and
566+ exists ( int last | last = max ( int i | exists ( this .getAccess ( i ) ) ) |
567+ pred = this .getAccess ( last ) and
559568 succ = this and
560569 c .isValidFor ( pred ) and
561570 c instanceof NormalCompletion
562571 )
563572 }
564573 }
565574
575+ /**
576+ * An assignment where the left hand side is a qualified write access.
577+ *
578+ * This is needed to allow "null" completion to propagate from the qualifier
579+ * to after the assignment.
580+ */
581+ private class QualifiedAssignments extends PostOrderTree instanceof Assignment {
582+ QualifiedWriteAccess left ;
583+ Expr right ;
584+
585+ QualifiedAssignments ( ) {
586+ left = this .getLValue ( ) and
587+ right = this .getRValue ( ) and
588+ not this instanceof AssignOperationWithExpandedAssignment
589+ }
590+
591+ final override predicate propagatesAbnormal ( AstNode child ) { child = [ left , right ] }
592+
593+ final override predicate first ( AstNode first ) { first ( left , first ) }
594+
595+ final override predicate last ( AstNode last , Completion c ) {
596+ PostOrderTree .super .last ( last , c )
597+ or
598+ last ( left , last , c ) and
599+ left .( QualifiableExpr ) .isConditional ( ) and
600+ c .( NullnessCompletion ) .isNull ( )
601+ }
602+
603+ final override predicate succ ( AstNode pred , AstNode succ , Completion c ) {
604+ // Standard left-to-right evaluation
605+ last ( left , pred , c ) and
606+ c instanceof NormalCompletion and
607+ not c .( NullnessCompletion ) .isNull ( ) and
608+ first ( right , succ )
609+ }
610+ }
611+
566612 private class LogicalNotExprTree extends PostOrderTree instanceof LogicalNotExpr {
567613 private Expr operand ;
568614
@@ -704,7 +750,9 @@ module Expressions {
704750 private class ConditionallyQualifiedExpr extends PostOrderTree instanceof QualifiableExpr {
705751 private Expr qualifier ;
706752
707- ConditionallyQualifiedExpr ( ) { this .isConditional ( ) and qualifier = getExprChild ( this , 0 ) }
753+ ConditionallyQualifiedExpr ( ) {
754+ this .isConditional ( ) and qualifier = getExprChild ( this , 0 ) and not this instanceof WriteAccess
755+ }
708756
709757 final override predicate propagatesAbnormal ( AstNode child ) { child = qualifier }
710758
0 commit comments