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 ;
19+
20+ import static org .junit .jupiter .api .Assertions .assertEquals ;
21+ import static org .junit .jupiter .api .Assertions .assertInstanceOf ;
22+ import static org .junit .jupiter .api .Assertions .assertThrows ;
23+ import static org .junit .jupiter .api .Assertions .fail ;
24+
25+ import java .io .InvalidObjectException ;
26+
27+ import org .apache .commons .lang3 .reflect .FieldUtils ;
28+ import org .junit .jupiter .api .Test ;
29+
30+ /**
31+ * Tests that a serialized Range can't store a bad cached hashCode.
32+ */
33+ class RangeReadObjectTest {
34+
35+ private static byte [] intToBytes (final int v ) {
36+ return new byte [] { (byte ) (v >>> 24 ), (byte ) (v >>> 16 ), (byte ) (v >>> 8 ), (byte ) v };
37+ }
38+
39+ private static byte [] replaceLastInt (final byte [] src , final int from , final int to ) {
40+ final byte [] fromB = intToBytes (from );
41+ final byte [] toB = intToBytes (to );
42+ final byte [] out = src .clone ();
43+ for (int i = out .length - 4 ; i >= 0 ; i --) {
44+ if (out [i ] == fromB [0 ] && out [i + 1 ] == fromB [1 ] && out [i + 2 ] == fromB [2 ] && out [i + 3 ] == fromB [3 ]) {
45+ out [i ] = toB [0 ];
46+ out [i + 1 ] = toB [1 ];
47+ out [i + 2 ] = toB [2 ];
48+ out [i + 3 ] = toB [3 ];
49+ return out ;
50+ }
51+ }
52+ fail ("No legitimate int in stream, serialization must keep hashCode in default field set" );
53+ return null ;
54+ }
55+
56+ @ Test
57+ void testBadHashCodeRejected () throws Exception {
58+ final Range <Integer > range = Range .of (1 , 100 );
59+ final byte [] bytes = SerializationUtils .serialize (range );
60+ // Locate the legitimate hashCode int in the serialized stream and overwrite it.
61+ final int hashCode = (Integer ) FieldUtils .readDeclaredField (range , "hashCode" , true );
62+ final byte [] edited = replaceLastInt (bytes , hashCode , 0xDEADBEEF );
63+ final SerializationException ex = assertThrows (SerializationException .class , () -> SerializationUtils .deserialize (edited ),
64+ "Bad hashCode in stream must be rejected with InvalidObjectException" );
65+ assertInstanceOf (InvalidObjectException .class , ex .getCause ());
66+ assertEquals ("java.io.InvalidObjectException: Range hashCode does not match minimum/maximum." , ex .getMessage ());
67+ }
68+
69+ @ Test
70+ void testRoundTripPreservesCorrectHashCode () throws Exception {
71+ final Range <String > range = Range .of ("apple" , "mango" );
72+ final Range <String > roundtrip = SerializationUtils .roundtrip (range );
73+ assertEquals (range .hashCode (), roundtrip .hashCode (), "Round-trip serialization must preserve the correct hashCode" );
74+ assertEquals (range , roundtrip );
75+ }
76+ }
0 commit comments