• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

raphw / byte-buddy / #801

27 Oct 2025 09:37AM UTC coverage: 84.715% (-0.4%) from 85.118%
#801

push

raphw
Fix imports.

29586 of 34924 relevant lines covered (84.72%)

0.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.94
/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/HashCodeMethod.java
1
/*
2
 * Copyright 2014 - Present Rafael Winterhalter
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package net.bytebuddy.implementation;
17

18
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
19
import net.bytebuddy.build.HashCodeAndEqualsPlugin;
20
import net.bytebuddy.description.field.FieldDescription;
21
import net.bytebuddy.description.method.MethodDescription;
22
import net.bytebuddy.description.type.TypeDefinition;
23
import net.bytebuddy.description.type.TypeDescription;
24
import net.bytebuddy.dynamic.scaffold.InstrumentedType;
25
import net.bytebuddy.implementation.bytecode.Addition;
26
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
27
import net.bytebuddy.implementation.bytecode.Multiplication;
28
import net.bytebuddy.implementation.bytecode.StackManipulation;
29
import net.bytebuddy.implementation.bytecode.StackSize;
30
import net.bytebuddy.implementation.bytecode.constant.ClassConstant;
31
import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
32
import net.bytebuddy.implementation.bytecode.member.FieldAccess;
33
import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
34
import net.bytebuddy.implementation.bytecode.member.MethodReturn;
35
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
36
import net.bytebuddy.matcher.ElementMatcher;
37
import org.objectweb.asm.Label;
38
import org.objectweb.asm.MethodVisitor;
39
import org.objectweb.asm.Opcodes;
40

41
import java.util.ArrayList;
42
import java.util.Arrays;
43
import java.util.List;
44

45
import static net.bytebuddy.matcher.ElementMatchers.isHashCode;
46
import static net.bytebuddy.matcher.ElementMatchers.isStatic;
47
import static net.bytebuddy.matcher.ElementMatchers.named;
48
import static net.bytebuddy.matcher.ElementMatchers.none;
49
import static net.bytebuddy.matcher.ElementMatchers.not;
50
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
51

52
/**
53
 * An implementation of {@link Object#hashCode()} that takes a class's declared fields into consideration. A hash code is computed by transforming
54
 * primitive field types to an {@code int} value and by summing those values up starting from a given offset after multiplying any previous value
55
 * with a multiplier. Reference values are checked against {@code null} values unless specified otherwise.
56
 */
57
@HashCodeAndEqualsPlugin.Enhance
58
public class HashCodeMethod implements Implementation {
59

60
    /**
61
     * The default offset which should be a prime number.
62
     */
63
    private static final int DEFAULT_OFFSET = 17;
64

65
    /**
66
     * The default multiplier for each value before adding a field's hash code value which should be a prime number.
67
     */
68
    private static final int DEFAULT_MULTIPLIER = 31;
69

70
    /**
71
     * The {@link Object#hashCode()} method.
72
     */
73
    private static final MethodDescription.InDefinedShape HASH_CODE = TypeDescription.ForLoadedType.of(Object.class)
1✔
74
            .getDeclaredMethods()
1✔
75
            .filter(isHashCode())
1✔
76
            .getOnly();
1✔
77

78
    /**
79
     * The {@link Object#getClass()} method.
80
     */
81
    private static final MethodDescription.InDefinedShape GET_CLASS = TypeDescription.ForLoadedType.of(Object.class)
1✔
82
            .getDeclaredMethods()
1✔
83
            .filter(named("getClass").and(takesArguments(0)))
1✔
84
            .getOnly();
1✔
85

86
    /**
87
     * The hash code's offset provider.
88
     */
89
    private final OffsetProvider offsetProvider;
90

91
    /**
92
     * A multiplier for each value before adding a field's hash code value.
93
     */
94
    private final int multiplier;
95

96
    /**
97
     * A matcher to filter fields that should not be used for a hash codes computation.
98
     */
99
    private final ElementMatcher.Junction<? super FieldDescription.InDefinedShape> ignored;
100

101
    /**
102
     * A matcher to determine fields of a reference type that cannot be {@code null}.
103
     */
104
    private final ElementMatcher.Junction<? super FieldDescription.InDefinedShape> nonNullable;
105

106
    /**
107
     * A matcher to determine that a field should be considered by its identity.
108
     */
109
    private final ElementMatcher.Junction<? super FieldDescription.InDefinedShape> identity;
110

111
    /**
112
     * Creates a new hash code method implementation.
113
     *
114
     * @param offsetProvider The hash code's offset provider.
115
     */
116
    protected HashCodeMethod(OffsetProvider offsetProvider) {
117
        this(offsetProvider, DEFAULT_MULTIPLIER, none(), none(), none());
1✔
118
    }
1✔
119

120
    /**
121
     * Creates a new hash code method implementation.
122
     *
123
     * @param offsetProvider The hash code's offset provider.
124
     * @param multiplier     A multiplier for each value before adding a field's hash code value
125
     * @param ignored        A matcher to filter fields that should not be used for a hash codes computation.
126
     * @param nonNullable    A matcher to determine fields of a reference type that cannot be {@code null}.
127
     * @param identity       A matcher to determine that a field should be considered by its identity.
128
     */
129
    private HashCodeMethod(OffsetProvider offsetProvider,
130
                           int multiplier,
131
                           ElementMatcher.Junction<? super FieldDescription.InDefinedShape> ignored,
132
                           ElementMatcher.Junction<? super FieldDescription.InDefinedShape> nonNullable,
133
                           ElementMatcher.Junction<? super FieldDescription.InDefinedShape> identity) {
1✔
134
        this.offsetProvider = offsetProvider;
1✔
135
        this.multiplier = multiplier;
1✔
136
        this.ignored = ignored;
1✔
137
        this.nonNullable = nonNullable;
1✔
138
        this.identity = identity;
1✔
139
    }
1✔
140

141
    /**
142
     * Creates a hash code method implementation that bases the hash code on the instrumented type's super class's hash code value.
143
     *
144
     * @return A hash code method implementation that bases the hash code on the instrumented type's super class's hash code value.
145
     */
146
    public static HashCodeMethod usingSuperClassOffset() {
147
        return new HashCodeMethod(OffsetProvider.ForSuperMethodCall.INSTANCE);
1✔
148
    }
149

150
    /**
151
     * Creates a hash code method implementation that bases the hash code on the instrumented type's class constant's hash code..
152
     *
153
     * @param dynamic {@code true} if the type should be resolved from the instance and not be set as the declaring class.
154
     * @return A hash code method implementation that bases the hash code on the instrumented type's class constant's hash code.
155
     */
156
    public static HashCodeMethod usingTypeHashOffset(boolean dynamic) {
157
        return new HashCodeMethod(dynamic ? OffsetProvider.ForDynamicTypeHash.INSTANCE : OffsetProvider.ForStaticTypeHash.INSTANCE);
1✔
158
    }
159

160
    /**
161
     * Creates a hash code method implementation that bases the hash code on a fixed value.
162
     *
163
     * @return A hash code method implementation that bases the hash code on a fixed value.
164
     */
165
    public static HashCodeMethod usingDefaultOffset() {
166
        return usingOffset(DEFAULT_OFFSET);
1✔
167
    }
168

169
    /**
170
     * Creates a hash code method implementation that bases the hash code on a fixed value.
171
     *
172
     * @param value The fixed value.
173
     * @return A hash code method implementation that bases the hash code on a fixed value.
174
     */
175
    public static HashCodeMethod usingOffset(int value) {
176
        return new HashCodeMethod(new OffsetProvider.ForFixedValue(value));
1✔
177
    }
178

179
    /**
180
     * Returns a new version of this hash code method implementation that ignores the specified fields additionally to any
181
     * previously specified fields.
182
     *
183
     * @param ignored A matcher to specify any fields that should be ignored.
184
     * @return A new version of this hash code method implementation that also ignores any fields matched by the provided matcher.
185
     */
186
    public HashCodeMethod withIgnoredFields(ElementMatcher<? super FieldDescription.InDefinedShape> ignored) {
187
        return new HashCodeMethod(offsetProvider, multiplier, this.ignored.<FieldDescription.InDefinedShape>or(ignored), nonNullable, identity);
1✔
188
    }
189

190
    /**
191
     * Returns a new version of this hash code method implementation that does not apply a {@code null} value check for the specified fields
192
     * if they have a reference type additionally to any previously specified fields.
193
     *
194
     * @param nonNullable A matcher to specify any fields that should not be guarded against {@code null} values.
195
     * @return A new version of this hash code method implementation that also does not apply {@code null} value checks to any fields matched by
196
     * the provided matcher.
197
     */
198
    public HashCodeMethod withNonNullableFields(ElementMatcher<? super FieldDescription.InDefinedShape> nonNullable) {
199
        return new HashCodeMethod(offsetProvider, multiplier, ignored, this.nonNullable.<FieldDescription.InDefinedShape>or(nonNullable), identity);
1✔
200
    }
201

202
    /**
203
     * Returns a new version of this hash code method implementation that considers the matched fields by their identity.
204
     *
205
     * @param identity A matcher to specify any fields that should be considered by their identity.
206
     * @return A new version of this hash code method implementation that also considers the matched fields by their identity.
207
     */
208
    public HashCodeMethod withIdentityFields(ElementMatcher<? super FieldDescription.InDefinedShape> identity) {
209
        return new HashCodeMethod(offsetProvider, multiplier, ignored, nonNullable, this.identity.<FieldDescription.InDefinedShape>or(identity));
1✔
210
    }
211

212
    /**
213
     * Returns a new version of this hash code method implementation that uses the given multiplier onto any given hash code before adding a
214
     * field's hash code.
215
     *
216
     * @param multiplier The multiplier to use for any hash code before adding any field's hash code.
217
     * @return A new version of this hash code method implementation that uses the given multiplier onto any given hash code before adding a
218
     * field's hash code.
219
     */
220
    public HashCodeMethod withMultiplier(int multiplier) {
221
        if (multiplier == 0) {
1✔
222
            throw new IllegalArgumentException("Hash code multiplier must not be zero");
1✔
223
        }
224
        return new HashCodeMethod(offsetProvider, multiplier, ignored, nonNullable, identity);
1✔
225
    }
226

227
    /**
228
     * {@inheritDoc}
229
     */
230
    public InstrumentedType prepare(InstrumentedType instrumentedType) {
231
        return instrumentedType;
1✔
232
    }
233

234
    /**
235
     * {@inheritDoc}
236
     */
237
    public ByteCodeAppender appender(Target implementationTarget) {
238
        if (implementationTarget.getInstrumentedType().isInterface()) {
1✔
239
            throw new IllegalStateException("Cannot implement meaningful hash code method for " + implementationTarget.getInstrumentedType());
1✔
240
        }
241
        return new Appender(offsetProvider.resolve(implementationTarget.getInstrumentedType()),
1✔
242
                multiplier,
243
                implementationTarget.getInstrumentedType().getDeclaredFields().filter(not(isStatic().or(ignored))),
1✔
244
                nonNullable,
245
                identity);
246
    }
247

248
    /**
249
     * An offset provider is responsible for supplying the initial hash code.
250
     */
251
    protected interface OffsetProvider {
252

253
        /**
254
         * Resolves this offset provider for a given instrumented type.
255
         *
256
         * @param instrumentedType The instrumented type.
257
         * @return A stack manipulation that loads the initial hash code onto the operand stack.
258
         */
259
        StackManipulation resolve(TypeDescription instrumentedType);
260

261
        /**
262
         * An offset provider that supplies a fixed value.
263
         */
264
        @HashCodeAndEqualsPlugin.Enhance
265
        class ForFixedValue implements OffsetProvider {
266

267
            /**
268
             * The value to load onto the operand stack.
269
             */
270
            private final int value;
271

272
            /**
273
             * Creates a new offset provider for a fixed value.
274
             *
275
             * @param value The value to load onto the operand stack.
276
             */
277
            protected ForFixedValue(int value) {
1✔
278
                this.value = value;
1✔
279
            }
1✔
280

281
            /**
282
             * {@inheritDoc}
283
             */
284
            public StackManipulation resolve(TypeDescription instrumentedType) {
285
                return IntegerConstant.forValue(value);
1✔
286
            }
287
        }
288

289
        /**
290
         * An offset provider that invokes the super class's {@link Object#hashCode()} implementation.
291
         */
292
        enum ForSuperMethodCall implements OffsetProvider {
1✔
293

294
            /**
295
             * The singleton instance.
296
             */
297
            INSTANCE;
1✔
298

299
            /**
300
             * {@inheritDoc}
301
             */
302
            public StackManipulation resolve(TypeDescription instrumentedType) {
303
                TypeDefinition superClass = instrumentedType.getSuperClass();
1✔
304
                if (superClass == null) {
1✔
305
                    throw new IllegalStateException(instrumentedType + " does not declare a super class");
×
306
                }
307
                return new StackManipulation.Compound(MethodVariableAccess.loadThis(), MethodInvocation.invoke(HASH_CODE).special(superClass.asErasure()));
1✔
308
            }
309
        }
310

311
        /**
312
         * An offset provider that uses the instrumented type's class constant's hash code.
313
         */
314
        enum ForStaticTypeHash implements OffsetProvider {
1✔
315

316
            /**
317
             * The singleton instance.
318
             */
319
            INSTANCE;
1✔
320

321
            /**
322
             * {@inheritDoc}
323
             */
324
            public StackManipulation resolve(TypeDescription instrumentedType) {
325
                return new StackManipulation.Compound(ClassConstant.of(instrumentedType), MethodInvocation.invoke(HASH_CODE).virtual(TypeDescription.ForLoadedType.of(Class.class)));
1✔
326
            }
327
        }
328

329
        /**
330
         * An offset provider that uses the instance's class's hash code.
331
         */
332
        enum ForDynamicTypeHash implements OffsetProvider {
1✔
333

334
            /**
335
             * The singleton instance.
336
             */
337
            INSTANCE;
1✔
338

339
            /**
340
             * {@inheritDoc}
341
             */
342
            public StackManipulation resolve(TypeDescription instrumentedType) {
343
                return new StackManipulation.Compound(MethodVariableAccess.loadThis(),
1✔
344
                        MethodInvocation.invoke(GET_CLASS).virtual(instrumentedType),
1✔
345
                        MethodInvocation.invoke(HASH_CODE).virtual(TypeDescription.ForLoadedType.of(Class.class)));
1✔
346
            }
347
        }
348
    }
349

350
    /**
351
     * A guard against {@code null} values for fields with reference types.
352
     */
353
    protected interface NullValueGuard {
354

355
        /**
356
         * Returns a stack manipulation to apply before computing a hash value.
357
         *
358
         * @return A stack manipulation to apply before computing a hash value.
359
         */
360
        StackManipulation before();
361

362
        /**
363
         * Returns a stack manipulation to apply after computing a hash value.
364
         *
365
         * @return A stack manipulation to apply after computing a hash value.
366
         */
367
        StackManipulation after();
368

369
        /**
370
         * Returns the required padding for the local variable array to apply this guard.
371
         *
372
         * @return The required padding for the local variable array to apply this guard.
373
         */
374
        int getRequiredVariablePadding();
375

376
        /**
377
         * A non-operational null value guard.
378
         */
379
        enum NoOp implements NullValueGuard {
1✔
380

381
            /**
382
             * The singleton instance.
383
             */
384
            INSTANCE;
1✔
385

386
            /**
387
             * {@inheritDoc}
388
             */
389
            public StackManipulation before() {
390
                return StackManipulation.Trivial.INSTANCE;
1✔
391
            }
392

393
            /**
394
             * {@inheritDoc}
395
             */
396
            public StackManipulation after() {
397
                return StackManipulation.Trivial.INSTANCE;
1✔
398
            }
399

400
            /**
401
             * {@inheritDoc}
402
             */
403
            public int getRequiredVariablePadding() {
404
                return StackSize.ZERO.getSize();
1✔
405
            }
406
        }
407

408
        /**
409
         * A null value guard that expects a reference type and that uses a jump if a field value is {@code null}.
410
         */
411
        @HashCodeAndEqualsPlugin.Enhance
412
        class UsingJump implements NullValueGuard {
413

414
            /**
415
             * The instrumented method.
416
             */
417
            private final MethodDescription instrumentedMethod;
418

419
            /**
420
             * A label to indicate the target of a jump.
421
             */
422
            private final Label label;
423

424
            /**
425
             * Creates a new null value guard using a jump instruction for {@code null} values.
426
             *
427
             * @param instrumentedMethod The instrumented method.
428
             */
429
            protected UsingJump(MethodDescription instrumentedMethod) {
1✔
430
                this.instrumentedMethod = instrumentedMethod;
1✔
431
                label = new Label();
1✔
432
            }
1✔
433

434
            /**
435
             * {@inheritDoc}
436
             */
437
            public StackManipulation before() {
438
                return new BeforeInstruction();
1✔
439
            }
440

441
            /**
442
             * {@inheritDoc}
443
             */
444
            public StackManipulation after() {
445
                return new AfterInstruction();
1✔
446
            }
447

448
            /**
449
             * {@inheritDoc}
450
             */
451
            public int getRequiredVariablePadding() {
452
                return 1;
1✔
453
            }
454

455
            /**
456
             * The stack manipulation to apply before the hash value computation.
457
             */
458
            @HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)
459
            protected class BeforeInstruction extends StackManipulation.AbstractBase {
1✔
460

461
                /**
462
                 * {@inheritDoc}
463
                 */
464
                public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
465
                    methodVisitor.visitVarInsn(Opcodes.ASTORE, instrumentedMethod.getStackSize());
1✔
466
                    methodVisitor.visitVarInsn(Opcodes.ALOAD, instrumentedMethod.getStackSize());
1✔
467
                    methodVisitor.visitJumpInsn(Opcodes.IFNULL, label);
1✔
468
                    methodVisitor.visitVarInsn(Opcodes.ALOAD, instrumentedMethod.getStackSize());
1✔
469
                    return Size.ZERO;
1✔
470
                }
471
            }
472

473
            /**
474
             * The stack manipulation to apply after the hash value computation.
475
             */
476
            @HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)
477
            protected class AfterInstruction extends StackManipulation.AbstractBase {
1✔
478

479
                /**
480
                 * {@inheritDoc}
481
                 */
482
                public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
483
                    methodVisitor.visitLabel(label);
1✔
484
                    implementationContext.getFrameGeneration().same1(methodVisitor,
1✔
485
                            TypeDescription.ForLoadedType.of(int.class),
1✔
486
                            Arrays.asList(implementationContext.getInstrumentedType(), TypeDescription.ForLoadedType.of(Object.class)));
1✔
487
                    return Size.ZERO;
1✔
488
                }
489
            }
490
        }
491
    }
492

493
    /**
494
     * A value transformer that is responsible for resolving a field value to an {@code int} value.
495
     */
496
    protected enum ValueTransformer implements StackManipulation {
1✔
497

498
        /**
499
         * A transformer for a {@code long} value.
500
         */
501
        LONG {
1✔
502
            /** {@inheritDoc} */
503
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
504
                methodVisitor.visitInsn(Opcodes.DUP2);
1✔
505
                methodVisitor.visitIntInsn(Opcodes.BIPUSH, 32);
1✔
506
                methodVisitor.visitInsn(Opcodes.LUSHR);
1✔
507
                methodVisitor.visitInsn(Opcodes.LXOR);
1✔
508
                methodVisitor.visitInsn(Opcodes.L2I);
1✔
509
                return new Size(-1, 3);
1✔
510
            }
511
        },
512

513
        /**
514
         * A transformer for a {@code float} value.
515
         */
516
        FLOAT {
1✔
517
            /** {@inheritDoc} */
518
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
519
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Float", "floatToIntBits", "(F)I", false);
1✔
520
                return Size.ZERO;
1✔
521
            }
522
        },
523

524
        /**
525
         * A transformer for a {@code double} value.
526
         */
527
        DOUBLE {
1✔
528
            /** {@inheritDoc} */
529
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
530
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Double", "doubleToLongBits", "(D)J", false);
1✔
531
                methodVisitor.visitInsn(Opcodes.DUP2);
1✔
532
                methodVisitor.visitIntInsn(Opcodes.BIPUSH, 32);
1✔
533
                methodVisitor.visitInsn(Opcodes.LUSHR);
1✔
534
                methodVisitor.visitInsn(Opcodes.LXOR);
1✔
535
                methodVisitor.visitInsn(Opcodes.L2I);
1✔
536
                return new Size(-1, 3);
1✔
537
            }
538
        },
539

540
        /**
541
         * A transformer for a {@code boolean[]} value.
542
         */
543
        BOOLEAN_ARRAY {
1✔
544
            /** {@inheritDoc} */
545
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
546
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "hashCode", "([Z)I", false);
1✔
547
                return Size.ZERO;
1✔
548
            }
549
        },
550

551
        /**
552
         * A transformer for a {@code byte[]} value.
553
         */
554
        BYTE_ARRAY {
1✔
555
            /** {@inheritDoc} */
556
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
557
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "hashCode", "([B)I", false);
1✔
558
                return Size.ZERO;
1✔
559
            }
560
        },
561

562
        /**
563
         * A transformer for a {@code short[]} value.
564
         */
565
        SHORT_ARRAY {
1✔
566
            /** {@inheritDoc} */
567
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
568
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "hashCode", "([S)I", false);
1✔
569
                return Size.ZERO;
1✔
570
            }
571
        },
572

573
        /**
574
         * A transformer for a {@code char[]} value.
575
         */
576
        CHARACTER_ARRAY {
1✔
577
            /** {@inheritDoc} */
578
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
579
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "hashCode", "([C)I", false);
1✔
580
                return Size.ZERO;
1✔
581
            }
582
        },
583

584
        /**
585
         * A transformer for an {@code int[]} value.
586
         */
587
        INTEGER_ARRAY {
1✔
588
            /** {@inheritDoc} */
589
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
590
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "hashCode", "([I)I", false);
1✔
591
                return Size.ZERO;
1✔
592
            }
593
        },
594

595
        /**
596
         * A transformer for a {@code long[]} value.
597
         */
598
        LONG_ARRAY {
1✔
599
            /** {@inheritDoc} */
600
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
601
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "hashCode", "([J)I", false);
1✔
602
                return Size.ZERO;
1✔
603
            }
604
        },
605

606
        /**
607
         * A transformer for a {@code float[]} value.
608
         */
609
        FLOAT_ARRAY {
1✔
610
            /** {@inheritDoc} */
611
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
612
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "hashCode", "([F)I", false);
1✔
613
                return Size.ZERO;
1✔
614
            }
615
        },
616

617
        /**
618
         * A transformer for a {@code double[]} value.
619
         */
620
        DOUBLE_ARRAY {
1✔
621
            /** {@inheritDoc} */
622
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
623
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "hashCode", "([D)I", false);
1✔
624
                return Size.ZERO;
1✔
625
            }
626
        },
627

628
        /**
629
         * A transformer for a reference array value.
630
         */
631
        REFERENCE_ARRAY {
1✔
632
            /** {@inheritDoc} */
633
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
634
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "hashCode", "([Ljava/lang/Object;)I", false);
1✔
635
                return Size.ZERO;
1✔
636
            }
637
        },
638

639
        /**
640
         * A transformer for a nested reference array value.
641
         */
642
        NESTED_ARRAY {
1✔
643
            /** {@inheritDoc} */
644
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
645
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "deepHashCode", "([Ljava/lang/Object;)I", false);
1✔
646
                return Size.ZERO;
1✔
647
            }
648
        },
649

650
        /**
651
         * A transformer for computing the identity hash code for a reference.
652
         */
653
        REFERENCE_IDENTITY {
1✔
654
            /** {@inheritDoc} */
655
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
656
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "identityHashCode", "(Ljava/lang/Object;)I", false);
1✔
657
                return Size.ZERO;
1✔
658
            }
659
        };
660

661
        /**
662
         * Resolves a type definition to a hash code.
663
         *
664
         * @param typeDefinition The type definition to resolve.
665
         * @return The stack manipulation to apply.
666
         */
667
        @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Assuming component type for array type.")
668
        public static StackManipulation of(TypeDefinition typeDefinition) {
669
            if (typeDefinition.represents(boolean.class)
1✔
670
                    || typeDefinition.represents(byte.class)
1✔
671
                    || typeDefinition.represents(short.class)
1✔
672
                    || typeDefinition.represents(char.class)
1✔
673
                    || typeDefinition.represents(int.class)) {
1✔
674
                return Trivial.INSTANCE;
1✔
675
            } else if (typeDefinition.represents(long.class)) {
1✔
676
                return LONG;
1✔
677
            } else if (typeDefinition.represents(float.class)) {
1✔
678
                return FLOAT;
1✔
679
            } else if (typeDefinition.represents(double.class)) {
1✔
680
                return DOUBLE;
1✔
681
            } else if (typeDefinition.represents(boolean[].class)) {
1✔
682
                return BOOLEAN_ARRAY;
1✔
683
            } else if (typeDefinition.represents(byte[].class)) {
1✔
684
                return BYTE_ARRAY;
1✔
685
            } else if (typeDefinition.represents(short[].class)) {
1✔
686
                return SHORT_ARRAY;
1✔
687
            } else if (typeDefinition.represents(char[].class)) {
1✔
688
                return CHARACTER_ARRAY;
1✔
689
            } else if (typeDefinition.represents(int[].class)) {
1✔
690
                return INTEGER_ARRAY;
1✔
691
            } else if (typeDefinition.represents(long[].class)) {
1✔
692
                return LONG_ARRAY;
1✔
693
            } else if (typeDefinition.represents(float[].class)) {
1✔
694
                return FLOAT_ARRAY;
1✔
695
            } else if (typeDefinition.represents(double[].class)) {
1✔
696
                return DOUBLE_ARRAY;
1✔
697
            } else if (typeDefinition.isArray()) {
1✔
698
                return typeDefinition.getComponentType().isArray()
1✔
699
                        ? NESTED_ARRAY
700
                        : REFERENCE_ARRAY;
701
            } else {
702
                return MethodInvocation.invoke(HASH_CODE).virtual(typeDefinition.asErasure());
1✔
703
            }
704
        }
705

706
        /**
707
         * {@inheritDoc}
708
         */
709
        public boolean isValid() {
710
            return true;
×
711
        }
712
    }
713

714
    /**
715
     * A byte code appender to implement a hash code method.
716
     */
717
    @HashCodeAndEqualsPlugin.Enhance
718
    protected static class Appender implements ByteCodeAppender {
719

720
        /**
721
         * Loads the initial hash code onto the operand stack.
722
         */
723
        private final StackManipulation initialValue;
724

725
        /**
726
         * A multiplier for each value before adding a field's hash code value.
727
         */
728
        private final int multiplier;
729

730
        /**
731
         * A list of fields to include in the hash code computation.
732
         */
733
        private final List<FieldDescription.InDefinedShape> fieldDescriptions;
734

735
        /**
736
         * A matcher to determine fields of a reference type that cannot be {@code null}.
737
         */
738
        private final ElementMatcher<? super FieldDescription.InDefinedShape> nonNullable;
739

740
        /**
741
         * A matcher to determine that a field should be considered by its identity.
742
         */
743
        private final ElementMatcher<? super FieldDescription.InDefinedShape> identity;
744

745
        /**
746
         * Creates a new appender for implementing a hash code method.
747
         *
748
         * @param initialValue      Loads the initial hash code onto the operand stack.
749
         * @param multiplier        A multiplier for each value before adding a field's hash code value.
750
         * @param fieldDescriptions A list of fields to include in the hash code computation.
751
         * @param nonNullable       A matcher to determine fields of a reference type that cannot be {@code null}.
752
         * @param identity          A matcher to determine that a field should be considered by its identity.
753
         */
754
        protected Appender(StackManipulation initialValue,
755
                           int multiplier,
756
                           List<FieldDescription.InDefinedShape> fieldDescriptions,
757
                           ElementMatcher<? super FieldDescription.InDefinedShape> nonNullable,
758
                           ElementMatcher<? super FieldDescription.InDefinedShape> identity) {
1✔
759
            this.initialValue = initialValue;
1✔
760
            this.multiplier = multiplier;
1✔
761
            this.fieldDescriptions = fieldDescriptions;
1✔
762
            this.nonNullable = nonNullable;
1✔
763
            this.identity = identity;
1✔
764
        }
1✔
765

766
        /**
767
         * {@inheritDoc}
768
         */
769
        public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
770
            if (instrumentedMethod.isStatic()) {
1✔
771
                throw new IllegalStateException("Hash code method must not be static: " + instrumentedMethod);
1✔
772
            } else if (!instrumentedMethod.getReturnType().represents(int.class)) {
1✔
773
                throw new IllegalStateException("Hash code method does not return primitive integer: " + instrumentedMethod);
1✔
774
            }
775
            List<StackManipulation> stackManipulations = new ArrayList<StackManipulation>(2 + fieldDescriptions.size() * 8);
1✔
776
            stackManipulations.add(initialValue);
1✔
777
            int padding = 0;
1✔
778
            for (FieldDescription.InDefinedShape fieldDescription : fieldDescriptions) {
1✔
779
                stackManipulations.add(IntegerConstant.forValue(multiplier));
1✔
780
                stackManipulations.add(Multiplication.INTEGER);
1✔
781
                stackManipulations.add(MethodVariableAccess.loadThis());
1✔
782
                stackManipulations.add(FieldAccess.forField(fieldDescription).read());
1✔
783
                if (!fieldDescription.getType().isPrimitive() && identity.matches(fieldDescription)) {
1✔
784
                    stackManipulations.add(ValueTransformer.REFERENCE_IDENTITY);
1✔
785
                    stackManipulations.add(Addition.INTEGER);
1✔
786
                } else {
787
                    NullValueGuard nullValueGuard = fieldDescription.getType().isPrimitive() || fieldDescription.getType().isArray() || nonNullable.matches(fieldDescription)
1✔
788
                            ? NullValueGuard.NoOp.INSTANCE
789
                            : new NullValueGuard.UsingJump(instrumentedMethod);
790
                    stackManipulations.add(nullValueGuard.before());
1✔
791
                    stackManipulations.add(ValueTransformer.of(fieldDescription.getType()));
1✔
792
                    stackManipulations.add(Addition.INTEGER);
1✔
793
                    stackManipulations.add(nullValueGuard.after());
1✔
794
                    padding = Math.max(padding, nullValueGuard.getRequiredVariablePadding());
1✔
795
                }
796
            }
1✔
797
            stackManipulations.add(MethodReturn.INTEGER);
1✔
798
            return new Size(new StackManipulation.Compound(stackManipulations).apply(methodVisitor, implementationContext).getMaximalSize(), instrumentedMethod.getStackSize() + padding);
1✔
799
        }
800
    }
801
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc