1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.lang3.builder;
19
20 import static org.junit.jupiter.api.Assertions.assertNotEquals;
21
22 import org.junit.jupiter.api.Test;
23
24 /**
25 * Tests cycles in {@link HashCodeBuilder}.
26 * <p>
27 * {@link HashCodeBuilder#append(Object)} calls object.hashCode() directly without the ThreadLocal cycle-guard registry that reflectionHashCode() uses.
28 * </p>
29 *
30 * <p>
31 * When two objects reference each other and each implements hashCode() via {@code new HashCodeBuilder().append(peer)}, calling hashCode() recurses infinitely:
32 * a.hashCode() â append(b) â b.hashCode() â append(a) â ...
33 * </p>
34 *
35 * <p>
36 * Pre-patch: StackOverflowError is thrown. Post-patch: cycle detected; completes without error.
37 * </p>
38 */
39 class HashCodeBuilderCycleTest {
40
41 static class CyclicNode {
42
43 final String label;
44 CyclicNode peer;
45
46 CyclicNode(final String label) {
47 this.label = label;
48 }
49
50 @Override
51 public boolean equals(final Object o) {
52 return o instanceof CyclicNode && label.equals(((CyclicNode) o).label);
53 }
54
55 @Override
56 public int hashCode() {
57 return new HashCodeBuilder(17, 37).append(label).append(peer).toHashCode();
58 }
59 }
60
61 @Test
62 void acyclicChainProducesValue() {
63 final CyclicNode a = new CyclicNode("a");
64 final CyclicNode b = new CyclicNode("b");
65 a.peer = b; // b.peer is null, no cycle
66 assertNotEquals(0, a.hashCode());
67 }
68
69 @Test
70 void cyclicPeerDoesNotOverflowStack() {
71 final CyclicNode a = new CyclicNode("a");
72 final CyclicNode b = new CyclicNode("b");
73 a.peer = b;
74 b.peer = a;
75 assertNotEquals(0, a.hashCode());
76 }
77
78 @Test
79 void selfReferentialDoesNotOverflowStack() {
80 final CyclicNode self = new CyclicNode("self");
81 self.peer = self;
82 assertNotEquals(0, self.hashCode());
83 }
84 }