Skip to content

Commit 461ca62

Browse files
committed
Avoid wrong results when Object.hashCode()happen to collide
Use IdentityHashMap instead of HashMap when key is TestElement See apache#693
1 parent bed24df commit 461ca62

File tree

10 files changed

+53
-34
lines changed

10 files changed

+53
-34
lines changed

src/components/src/main/java/org/apache/jmeter/timers/ConstantThroughputTimer.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@
2020
import java.beans.BeanInfo;
2121
import java.beans.IntrospectionException;
2222
import java.beans.Introspector;
23+
import java.util.Collections;
24+
import java.util.IdentityHashMap;
25+
import java.util.Map;
2326
import java.util.ResourceBundle;
24-
import java.util.concurrent.ConcurrentHashMap;
25-
import java.util.concurrent.ConcurrentMap;
2627

2728
import org.apache.jmeter.gui.GUIMenuSortOrder;
2829
import org.apache.jmeter.gui.TestElementMetadata;
@@ -103,8 +104,9 @@ public String toString() {
103104
private static final ThroughputInfo allThreadsInfo = new ThroughputInfo();
104105

105106
//For holding the ThroughputInfo objects for all ThreadGroups. Keyed by AbstractThreadGroup objects
106-
private static final ConcurrentMap<AbstractThreadGroup, ThroughputInfo> threadGroupsInfoMap =
107-
new ConcurrentHashMap<>();
107+
//TestElements can't be used as keys in HashMap.
108+
private static final Map<AbstractThreadGroup, ThroughputInfo> threadGroupsInfoMap =
109+
Collections.synchronizedMap(new IdentityHashMap<>());
108110

109111

110112
/**

src/components/src/main/java/org/apache/jmeter/timers/poissonarrivals/PreciseThroughputTimer.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717

1818
package org.apache.jmeter.timers.poissonarrivals;
1919

20-
import java.util.concurrent.ConcurrentHashMap;
21-
import java.util.concurrent.ConcurrentMap;
20+
import java.util.Collections;
21+
import java.util.IdentityHashMap;
22+
import java.util.Map;
2223
import java.util.concurrent.TimeUnit;
2324

2425
import org.apache.jmeter.gui.GUIMenuSortOrder;
@@ -44,7 +45,10 @@ public class PreciseThroughputTimer extends AbstractTestElement implements Clone
4445
private static final Logger log = LoggerFactory.getLogger(PreciseThroughputTimer.class);
4546

4647
private static final long serialVersionUID = 4;
47-
private static final ConcurrentMap<AbstractThreadGroup, EventProducer> groupEvents = new ConcurrentHashMap<>();
48+
49+
// TestElements can't be used as keys in a HashMap, so we use IdentityHashMap
50+
private static final Map<AbstractThreadGroup, EventProducer> groupEvents =
51+
Collections.synchronizedMap(new IdentityHashMap<>());
4852

4953
/**
5054
* Desired throughput configured as {@code throughput/throughputPeriod} per second.

src/core/src/main/java/org/apache/jmeter/control/GenericController.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,9 @@
2121
import java.util.ArrayDeque;
2222
import java.util.ArrayList;
2323
import java.util.Deque;
24+
import java.util.IdentityHashMap;
2425
import java.util.Iterator;
2526
import java.util.List;
26-
import java.util.concurrent.ConcurrentHashMap;
27-
import java.util.concurrent.ConcurrentMap;
2827

2928
import org.apache.jmeter.engine.event.LoopIterationEvent;
3029
import org.apache.jmeter.engine.event.LoopIterationListener;
@@ -58,7 +57,7 @@ public class GenericController extends AbstractTestElement implements Controller
5857
private transient Deque<LoopIterationListener> iterationListeners = new ArrayDeque<>();
5958

6059
// Only create the map if it is required
61-
private transient ConcurrentMap<TestElement, Object> children = new ConcurrentHashMap<>();
60+
private transient IdentityHashMap<TestElement, Object> children = new IdentityHashMap<>();
6261

6362
private static final Object DUMMY = new Object();
6463

@@ -353,10 +352,13 @@ public void addTestElement(TestElement child) {
353352
* {@inheritDoc}
354353
*/
355354
@Override
355+
@SuppressWarnings("SynchronizeOnNonFinalField")
356356
public final boolean addTestElementOnce(TestElement child){
357-
if (children.putIfAbsent(child, DUMMY) == null) {
358-
addTestElement(child);
359-
return true;
357+
synchronized (children) {
358+
if (children.putIfAbsent(child, DUMMY) == null) {
359+
addTestElement(child);
360+
return true;
361+
}
360362
}
361363
return false;
362364
}
@@ -414,7 +416,7 @@ protected void resetIterCount() {
414416

415417
protected Object readResolve(){
416418
iterationListeners = new ArrayDeque<>();
417-
children = new ConcurrentHashMap<>();
419+
children = new IdentityHashMap<>();
418420
subControllersAndSamplers = new ArrayList<>();
419421

420422
return this;

src/core/src/main/java/org/apache/jmeter/gui/GuiPackage.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.ArrayList;
2525
import java.util.Collections;
2626
import java.util.HashMap;
27+
import java.util.IdentityHashMap;
2728
import java.util.List;
2829
import java.util.Map;
2930
import java.util.concurrent.atomic.AtomicInteger;
@@ -102,7 +103,7 @@ public final class GuiPackage implements LocaleChangeListener, HistoryListener {
102103
* to their corresponding GUI components.
103104
* <p>This enables to associate {@link UnsharedComponent} UIs with their {@link TestElement}.</p>
104105
*/
105-
private Map<TestElement, JMeterGUIComponent> nodesToGui = new HashMap<>();
106+
private IdentityHashMap<TestElement, JMeterGUIComponent> nodesToGui = new IdentityHashMap<>();
106107

107108
/**
108109
* Map from Class to JMeterGUIComponent, mapping the Class of a GUI

src/core/src/main/java/org/apache/jmeter/testbeans/gui/TestBeanGUI.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737

3838
import javax.swing.JPopupMenu;
3939

40-
import org.apache.commons.collections4.map.LRUMap;
4140
import org.apache.jmeter.assertions.Assertion;
4241
import org.apache.jmeter.assertions.gui.AbstractAssertionGui;
4342
import org.apache.jmeter.config.ConfigElement;
@@ -69,6 +68,9 @@
6968
import org.slf4j.Logger;
7069
import org.slf4j.LoggerFactory;
7170

71+
import com.github.benmanes.caffeine.cache.Cache;
72+
import com.github.benmanes.caffeine.cache.Caffeine;
73+
7274
/**
7375
* JMeter GUI element editing for TestBean elements.
7476
* <p>
@@ -109,7 +111,11 @@ public class TestBeanGUI extends AbstractJMeterGuiComponent implements JMeterGUI
109111
* needs to be limited, though, to avoid memory issues when editing very
110112
* large test plans.
111113
*/
112-
private final Map<TestElement, Customizer> customizers = new LRUMap<>(20);
114+
private final Cache<TestElement, Customizer> customizers =
115+
Caffeine.newBuilder()
116+
.weakKeys() // So test elements are compared using identity == rather than .equals
117+
.maximumSize(20)
118+
.build();
113119

114120
/** Index of the customizer in the JPanel's child component list: */
115121
private int customizerIndexInPanel;
@@ -325,7 +331,7 @@ private void setValues(TestElement element) {
325331
if (initialized){
326332
remove(customizerIndexInPanel);
327333
}
328-
Customizer c = customizers.computeIfAbsent(element, e -> {
334+
Customizer c = customizers.get(element, e -> {
329335
Customizer result = createCustomizer();
330336
result.setObject(propertyMap);
331337
return result;

src/core/src/main/java/org/apache/jmeter/threads/AbstractThreadGroup.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@
1919

2020
import java.io.Serializable;
2121
import java.time.Duration;
22-
import java.util.concurrent.ConcurrentHashMap;
23-
import java.util.concurrent.ConcurrentMap;
22+
import java.util.IdentityHashMap;
2423
import java.util.concurrent.atomic.AtomicInteger;
2524

2625
import org.apache.jmeter.control.Controller;
@@ -50,7 +49,7 @@ public abstract class AbstractThreadGroup extends AbstractTestElement
5049
private static final long serialVersionUID = 240L;
5150

5251
// Only create the map if it is required
53-
private final transient ConcurrentMap<TestElement, Object> children = new ConcurrentHashMap<>();
52+
private final transient IdentityHashMap<TestElement, Object> children = new IdentityHashMap<>();
5453

5554
private static final Object DUMMY = new Object();
5655

@@ -135,9 +134,11 @@ public void addTestElement(TestElement child) {
135134
*/
136135
@Override
137136
public final boolean addTestElementOnce(TestElement child){
138-
if (children.putIfAbsent(child, DUMMY) == null) {
139-
addTestElement(child);
140-
return true;
137+
synchronized (children) {
138+
if (children.putIfAbsent(child, DUMMY) == null) {
139+
addTestElement(child);
140+
return true;
141+
}
141142
}
142143
return false;
143144
}

src/core/src/main/java/org/apache/jmeter/threads/TestCompiler.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,11 @@
1818
package org.apache.jmeter.threads;
1919

2020
import java.util.ArrayList;
21-
import java.util.HashMap;
2221
import java.util.HashSet;
22+
import java.util.IdentityHashMap;
2323
import java.util.LinkedList;
2424
import java.util.List;
2525
import java.util.ListIterator;
26-
import java.util.Map;
2726
import java.util.Set;
2827

2928
import org.apache.jmeter.assertions.Assertion;
@@ -69,10 +68,10 @@ public class TestCompiler implements HashTreeTraverser {
6968
// TODO: replace with ArrayDequeue
7069
private final LinkedList<TestElement> stack = new LinkedList<>();
7170

72-
private final Map<Sampler, SamplePackage> samplerConfigMap = new HashMap<>();
71+
private final IdentityHashMap<Sampler, SamplePackage> samplerConfigMap = new IdentityHashMap<>();
7372

74-
private final Map<TransactionController, SamplePackage> transactionControllerConfigMap =
75-
new HashMap<>();
73+
private final IdentityHashMap<TransactionController, SamplePackage> transactionControllerConfigMap =
74+
new IdentityHashMap<>();
7675

7776
private final HashTree testTree;
7877

src/core/src/main/java/org/apache/jmeter/util/JSR223TestElement.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,10 @@ public abstract class JSR223TestElement extends ScriptingTestElement
5858
/**
5959
* Cache of compiled scripts
6060
*/
61+
@SuppressWarnings("RedundantTypeArguments")
6162
private static final Map<String, CompiledScript> compiledScriptsCache =
62-
Collections.synchronizedMap(
63+
// Type arguments are needed to workaround https://bugs.openjdk.org/browse/JDK-8288590
64+
Collections.<String, CompiledScript>synchronizedMap(
6365
new LRUMap<>(JMeterUtils.getPropDefault("jsr223.compiled_scripts_cache_size", 100)));
6466

6567
/** If not empty then script in ScriptText will be compiled and cached */

src/jorphan/src/main/java/org/apache/jorphan/collections/HashTree.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.Collection;
2626
import java.util.HashMap;
2727
import java.util.HashSet;
28+
import java.util.IdentityHashMap;
2829
import java.util.Map;
2930
import java.util.Set;
3031

@@ -78,7 +79,7 @@ protected HashTree(Map<Object, HashTree> _map) {
7879
* name of the new top-level node
7980
*/
8081
public HashTree(Object key) {
81-
this(new HashMap<Object, HashTree>(), key);
82+
this(new IdentityHashMap<>(), key);
8283
}
8384

8485
/**
@@ -96,7 +97,7 @@ private HashTree(Map<Object, HashTree> _map, Object key) {
9697
if(_map != null) {
9798
data = _map;
9899
} else {
99-
data = new HashMap<>();
100+
data = new IdentityHashMap<>();
100101
}
101102
if(key != null) {
102103
data.put(key, new HashTree());
@@ -213,7 +214,7 @@ public void add(HashTree newTree) {
213214
* a collection of objects to be added to the created HashTree.
214215
*/
215216
public HashTree(Collection<?> keys) {
216-
data = new HashMap<>();
217+
data = new IdentityHashMap<>();
217218
for (Object o : keys) {
218219
data.put(o, new HashTree());
219220
}
@@ -227,7 +228,7 @@ public HashTree(Collection<?> keys) {
227228
* array with names for the new top-level nodes
228229
*/
229230
public HashTree(Object[] keys) {
230-
data = new HashMap<>();
231+
data = new IdentityHashMap<>();
231232
for (Object key : keys) {
232233
data.put(key, new HashTree());
233234
}

xdocs/changes.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ Summary
153153
<h3>General</h3>
154154
<ul>
155155
<li><bug>66157</bug><pr>719</pr>Correct theme for darklaf on rsyntaxtextarea</li>
156+
<li><pr>693</pr>Avoid wrong results when <code>Object.hashCode()</code> happen to collide. Use <code>IdentityHashMap</code> instead of <code>HashMap</code> when key is <code>TestElement</code></li>
156157
</ul>
157158

158159
<!-- =================== Thanks =================== -->

0 commit comments

Comments
 (0)