44
55package net .sourceforge .pmd .lang .java .rule .internal ;
66
7+ import static java .util .Collections .emptySet ;
78import static net .sourceforge .pmd .util .CollectionUtil .asSingle ;
89
910import java .util .ArrayDeque ;
@@ -177,12 +178,21 @@ private static DataflowResult process(ASTCompilationUnit node) {
177178 */
178179 public static final class ReachingDefinitionSet {
179180
181+ static final ReachingDefinitionSet UNKNOWN = new ReachingDefinitionSet ();
182+ static final ReachingDefinitionSet EMPTY_KNOWN = new ReachingDefinitionSet (emptySet ());
183+
180184 private Set <AssignmentEntry > reaching ;
181185 private boolean isNotFullyKnown ;
182186 private boolean containsInitialFieldValue ;
183187
188+
189+ static {
190+ assert !EMPTY_KNOWN .isNotFullyKnown ();
191+ assert UNKNOWN .isNotFullyKnown ();
192+ }
193+
184194 private ReachingDefinitionSet () {
185- this .reaching = Collections . emptySet ();
195+ this .reaching = emptySet ();
186196 this .containsInitialFieldValue = false ;
187197 this .isNotFullyKnown = true ;
188198 }
@@ -228,6 +238,10 @@ void absorb(ReachingDefinitionSet reaching) {
228238 public static ReachingDefinitionSet unknown () {
229239 return new ReachingDefinitionSet ();
230240 }
241+
242+ public static ReachingDefinitionSet blank () {
243+ return new ReachingDefinitionSet (emptySet ());
244+ }
231245 }
232246
233247 /**
@@ -256,7 +270,7 @@ public Set<AssignmentEntry> getUnusedAssignments() {
256270 * May be useful to check for reassignment.
257271 */
258272 public @ NonNull Set <AssignmentEntry > getKillers (AssignmentEntry assignment ) {
259- return killRecord .getOrDefault (assignment , Collections . emptySet ());
273+ return killRecord .getOrDefault (assignment , emptySet ());
260274 }
261275
262276 // These methods are only valid to be called if the dataflow pass has run.
@@ -278,14 +292,25 @@ public Set<AssignmentEntry> getUnusedAssignments() {
278292 return expr .getUserMap ().computeIfAbsent (REACHING_DEFS , () -> reachingFallback (expr ));
279293 }
280294
281- // Fallback, to compute reaching definitions for some fields
295+ // Fallback, to compute reaching definitions for some nodes
282296 // that are not tracked by the tree exploration. Final fields
283297 // indeed have a fully known set of reaching definitions.
284- // TODO maybe they should actually be tracked?
285298 private @ NonNull ReachingDefinitionSet reachingFallback (ASTNamedReferenceExpr expr ) {
286299 JVariableSymbol sym = expr .getReferencedSym ();
287- if (sym == null || ! sym .isField () || !sym .isFinal ()) {
300+ if (sym == null || sym .isField () && !sym .isFinal ()) {
288301 return ReachingDefinitionSet .unknown ();
302+ } else if (!sym .isField ()) {
303+ ASTVariableId node = sym .tryGetNode ();
304+ assert node != null
305+ : "Not a field, and symbol is known, so should be a local which has a node" ;
306+ if (node .isLocalVariable ()) {
307+ assert node .getInitializer () == null : "Should be a blank local variable" ;
308+ return ReachingDefinitionSet .blank ();
309+ } else {
310+ // Formal parameter or other kind of def which has
311+ // an implicit initializer.
312+ return ReachingDefinitionSet .unknown ();
313+ }
289314 }
290315
291316 ASTVariableId node = sym .tryGetNode ();
@@ -965,23 +990,25 @@ public SpanInfo visit(ASTAssignmentExpression node, SpanInfo data) {
965990
966991 }
967992
968- private SpanInfo processAssignment (ASTExpression lhs , // LHS or unary operand
993+ private SpanInfo processAssignment (ASTExpression lhs0 , // LHS or unary operand
969994 ASTExpression rhs , // RHS or unary
970995 boolean useBeforeAssigning ,
971996 SpanInfo result ) {
972997
973- if (lhs instanceof ASTNamedReferenceExpr ) {
974- JVariableSymbol lhsVar = ((ASTNamedReferenceExpr ) lhs ).getReferencedSym ();
998+ if (lhs0 instanceof ASTNamedReferenceExpr ) {
999+ ASTNamedReferenceExpr lhs = (ASTNamedReferenceExpr ) lhs0 ;
1000+ JVariableSymbol lhsVar = lhs .getReferencedSym ();
9751001 if (lhsVar != null
9761002 && (lhsVar instanceof JLocalVariableSymbol
9771003 || isRelevantField (lhs ))) {
9781004
9791005 if (useBeforeAssigning ) {
9801006 // compound assignment, to use BEFORE assigning
981- result .use (lhsVar , ( ASTNamedReferenceExpr ) lhs );
1007+ result .use (lhsVar , lhs );
9821008 }
9831009
984- result .assign (lhsVar , rhs );
1010+ VarLocalInfo oldVar = result .assign (lhsVar , rhs );
1011+ SpanInfo .updateReachingDefs (lhs , lhsVar , oldVar );
9851012 }
9861013 }
9871014 return result ;
@@ -1321,11 +1348,12 @@ void declareBlank(ASTVariableId id) {
13211348 assign (id .getSymbol (), id );
13221349 }
13231350
1324- void assign (JVariableSymbol var , JavaNode rhs ) {
1325- assign (var , rhs , SpecialAssignmentKind .NOT_SPECIAL );
1351+ VarLocalInfo assign (JVariableSymbol var , JavaNode rhs ) {
1352+ return assign (var , rhs , SpecialAssignmentKind .NOT_SPECIAL );
13261353 }
13271354
1328- @ Nullable AssignmentEntry assign (JVariableSymbol var , JavaNode rhs , SpecialAssignmentKind kind ) {
1355+ @ Nullable
1356+ VarLocalInfo assign (JVariableSymbol var , JavaNode rhs , SpecialAssignmentKind kind ) {
13291357 ASTVariableId node = var .tryGetNode ();
13301358 if (node == null ) {
13311359 return null ; // we don't care about non-local declarations
@@ -1355,7 +1383,7 @@ void assign(JVariableSymbol var, JavaNode rhs) {
13551383 }
13561384 }
13571385 global .allAssignments .add (entry );
1358- return entry ;
1386+ return previous ;
13591387 }
13601388
13611389 void declareSpecialFieldValues (JClassSymbol sym , boolean onlyStatic ) {
@@ -1399,20 +1427,25 @@ void use(@Nullable JVariableSymbol var, @Nullable ASTNamedReferenceExpr reaching
13991427 if (info != null ) {
14001428 global .usedAssignments .addAll (info .reachingDefs );
14011429 if (reachingDefSink != null ) {
1402- ReachingDefinitionSet reaching = new ReachingDefinitionSet (new LinkedHashSet <>(info .reachingDefs ));
1403- // need to merge into previous to account for cyclic control flow
1404- reachingDefSink .getUserMap ().compute (REACHING_DEFS , current -> {
1405- if (current != null ) {
1406- current .absorb (reaching );
1407- return current ;
1408- } else {
1409- return reaching ;
1410- }
1411- });
1430+ updateReachingDefs (reachingDefSink , var , info );
14121431 }
14131432 }
14141433 }
14151434
1435+ private static void updateReachingDefs (@ NonNull ASTNamedReferenceExpr reachingDefSink , JVariableSymbol var , VarLocalInfo info ) {
1436+ ReachingDefinitionSet reaching ;
1437+ if (info == null || var .isField () && var .isFinal ()) {
1438+ return ;
1439+ } else {
1440+ reaching = new ReachingDefinitionSet (new LinkedHashSet <>(info .reachingDefs ));
1441+ }
1442+ // need to merge into previous to account for cyclic control flow
1443+ reachingDefSink .getUserMap ().merge (REACHING_DEFS , reaching , (current , newer ) -> {
1444+ current .absorb (newer );
1445+ return current ;
1446+ });
1447+ }
1448+
14161449 void deleteVar (JVariableSymbol var ) {
14171450 symtable .remove (var );
14181451 }
0 commit comments