View Javadoc
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  }