Collection Element Replaced Without Being Read
ID |
java.no_unconditional_element_replace |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
Java |
Tags |
collection, dead-code, reliability |
Description
Reports two consecutive statements in the same block that store to the same map key or array
index with no intervening read of it: map.put(k, ..) twice with the same receiver and the
same literal-or-symbol key, or arr[i] = .. twice with the same array symbol and index.
letters.put("a", "Apple");
letters.put("a", "Boy"); // flagged: "Apple" is discarded, never read
towns[i] = "London";
towns[i] = "Chicago"; // flagged: "London" is discarded, never read
Rationale
The earlier value is overwritten before anyone ever reads it. That is redundant at best — the first store did nothing — and far more often a copy-paste slip that overwrote the wrong key or index, or forgot a condition that should have separated the two stores.
A read of the same key or index between the two stores makes the pair legitimate, including a read embedded in the second store’s own value expression:
arr[i] = arr[i] + 1; // reads the previous value first
arr[i] = x; // not flagged: the value above was actually used
Only literal and simple-symbol keys are compared, and only within one flat statement list — a store inside a branch or a loop between the two candidates is not chased into, and a store to a different collection, or under a different key, is left alone.
The receiver must be declared as a java.util.Map (or one of its implementations) in the same
method or as a field of the enclosing type. A put that adds rather than replaces is not a
defect, and plenty of types spell one that way:
ArrayListMultimap<Field, ValueChecker> checkers = ArrayListMultimap.create();
checkers.put(BOOKTITLE, new BooktitleContainsYearChecker());
checkers.put(BOOKTITLE, new AbbreviationChecker()); // not flagged: Multimap.put appends
Any statement that merely names the collection between the two stores also clears it, a store statement included — passing the map on is how the code under test gets to read it:
objectVar.put("age", 28);
variables.put("objectVar", objectVar); // objectVar is handed on here
deployAndStart(variables);
objectVar.put("age", 29); // not flagged: the first value was read in between
Remediation
Delete the redundant first store, or add whatever condition or read was missing between the two.
letters.put("a", "Apple");
// ... some logic reads letters.get("a") here ...
letters.put("a", "Boy");