• 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.33
/byte-buddy-dep/src/main/java/net/bytebuddy/build/CachedReturnPlugin.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.build;
17

18
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
19
import net.bytebuddy.ByteBuddy;
20
import net.bytebuddy.ClassFileVersion;
21
import net.bytebuddy.asm.Advice;
22
import net.bytebuddy.description.annotation.AnnotationDescription;
23
import net.bytebuddy.description.method.MethodDescription;
24
import net.bytebuddy.description.modifier.FieldPersistence;
25
import net.bytebuddy.description.modifier.Ownership;
26
import net.bytebuddy.description.modifier.SyntheticState;
27
import net.bytebuddy.description.modifier.Visibility;
28
import net.bytebuddy.description.type.TypeDefinition;
29
import net.bytebuddy.description.type.TypeDescription;
30
import net.bytebuddy.dynamic.ClassFileLocator;
31
import net.bytebuddy.dynamic.DynamicType;
32
import net.bytebuddy.dynamic.scaffold.TypeValidation;
33
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
34
import net.bytebuddy.implementation.Implementation;
35
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
36
import net.bytebuddy.implementation.bytecode.StackSize;
37
import net.bytebuddy.implementation.bytecode.assign.Assigner;
38
import net.bytebuddy.implementation.bytecode.member.MethodReturn;
39
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
40
import net.bytebuddy.utility.RandomString;
41
import org.objectweb.asm.Label;
42
import org.objectweb.asm.MethodVisitor;
43
import org.objectweb.asm.Opcodes;
44

45
import java.lang.annotation.Documented;
46
import java.lang.annotation.ElementType;
47
import java.lang.annotation.Retention;
48
import java.lang.annotation.RetentionPolicy;
49
import java.lang.annotation.Target;
50

51
import static net.bytebuddy.matcher.ElementMatchers.declaresMethod;
52
import static net.bytebuddy.matcher.ElementMatchers.is;
53
import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
54
import static net.bytebuddy.matcher.ElementMatchers.isBridge;
55
import static net.bytebuddy.matcher.ElementMatchers.named;
56
import static net.bytebuddy.matcher.ElementMatchers.not;
57

58
/**
59
 * A plugin that caches the return value of a method in a synthetic field. The caching mechanism is not thread-safe but can be used in a
60
 * concurrent setup if the cached value is frozen, i.e. only defines {@code final} fields. In this context, it is possible that
61
 * the method is executed multiple times by different threads but at the same time, this approach avoids a {@code volatile} field
62
 * declaration. For methods with a primitive return type, the type's default value is used to indicate that a method was not yet invoked.
63
 * For methods that return a reference type, {@code null} is used as an indicator. If a method returns such a value, this mechanism will
64
 * not work. This plugin does not need to be closed.
65
 */
66
@HashCodeAndEqualsPlugin.Enhance
67
public class CachedReturnPlugin extends Plugin.ForElementMatcher implements Plugin.Factory {
68

69
    /**
70
     * An infix between a field and the random suffix if no field name is chosen.
71
     */
72
    private static final String NAME_INFIX = "_";
73

74
    /**
75
     * A description of the {@link Enhance#value()} method.
76
     */
77
    private static final MethodDescription.InDefinedShape ENHANCE_VALUE = TypeDescription.ForLoadedType.of(Enhance.class)
1✔
78
            .getDeclaredMethods()
1✔
79
            .filter(named("value"))
1✔
80
            .getOnly();
1✔
81

82
    /**
83
     * {@code true} if existing fields should be ignored if the field name was explicitly given.
84
     */
85
    private final boolean ignoreExistingFields;
86

87
    /**
88
     * A random string to use for avoid field name collisions.
89
     */
90
    @HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.IGNORE)
91
    private final RandomString randomString;
92

93
    /**
94
     * Creates a plugin for caching method return values. If a field name exists before applying this plugin, an exception is raised.
95
     */
96
    public CachedReturnPlugin() {
97
        this(false);
1✔
98
    }
1✔
99

100
    /**
101
     * Creates a plugin for caching method return values.
102
     *
103
     * @param ignoreExistingFields {@code true} if existing fields should be ignored if the field name was explicitly given.
104
     */
105
    public CachedReturnPlugin(boolean ignoreExistingFields) {
106
        super(declaresMethod(isAnnotatedWith(Enhance.class)));
1✔
107
        this.ignoreExistingFields = ignoreExistingFields;
1✔
108
        randomString = new RandomString();
1✔
109
    }
1✔
110

111
    /**
112
     * {@inheritDoc}
113
     */
114
    public Plugin make() {
115
        return this;
×
116
    }
117

118
    /**
119
     * {@inheritDoc}
120
     */
121
    @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Annotation presence is required by matcher.")
122
    public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassFileLocator classFileLocator) {
123
        for (MethodDescription.InDefinedShape methodDescription : typeDescription.getDeclaredMethods()
1✔
124
                .filter(not(isBridge()).<MethodDescription>and(isAnnotatedWith(Enhance.class)))) {
1✔
125
            if (methodDescription.isAbstract()) {
1✔
126
                throw new IllegalStateException("Cannot cache the value of an abstract method: " + methodDescription);
1✔
127
            } else if (!methodDescription.getParameters().isEmpty()) {
1✔
128
                throw new IllegalStateException("Cannot cache the value of a method with parameters: " + methodDescription);
1✔
129
            } else if (methodDescription.getReturnType().represents(void.class)) {
1✔
130
                throw new IllegalStateException("Cannot cache void result for " + methodDescription);
1✔
131
            }
132
            String name = methodDescription.getDeclaredAnnotations().ofType(Enhance.class)
1✔
133
                    .getValue(ENHANCE_VALUE)
1✔
134
                    .resolve(String.class);
1✔
135
            if (name.length() == 0) {
1✔
136
                name = methodDescription.getName() + NAME_INFIX + randomString.nextString();
1✔
137
            } else if (ignoreExistingFields && !typeDescription.getDeclaredFields().filter(named(name)).isEmpty()) {
1✔
138
                return builder;
1✔
139
            }
140
            builder = builder
1✔
141
                    .defineField(name, methodDescription.getReturnType().asErasure(), methodDescription.isStatic()
1✔
142
                            ? Ownership.STATIC
143
                            : Ownership.MEMBER, methodDescription.isStatic()
1✔
144
                            ? FieldPersistence.PLAIN
145
                            : FieldPersistence.TRANSIENT, Visibility.PRIVATE, SyntheticState.SYNTHETIC)
146
                    .visit(AdviceResolver
1✔
147
                            .of(methodDescription.getReturnType())
1✔
148
                            .toAdvice(name).on(is(methodDescription)));
1✔
149
        }
1✔
150
        return builder;
1✔
151
    }
152

153
    /**
154
     * {@inheritDoc}
155
     */
156
    public void close() {
157
        /* do nothing */
158
    }
×
159

160
    /**
161
     * Indicates methods that should be cached, i.e. where the return value is stored in a synthetic field. For this to be
162
     * possible, the returned value should not be altered and the instance must be thread-safe if the value might be used from
163
     * multiple threads.
164
     */
165
    @Documented
166
    @Target(ElementType.METHOD)
167
    @Retention(RetentionPolicy.RUNTIME)
168
    public @interface Enhance {
169

170
        /**
171
         * The fields name or an empty string if the name should be generated randomly.
172
         *
173
         * @return The fields name or an empty string if the name should be generated randomly.
174
         */
175
        String value() default "";
176
    }
177

178
    /**
179
     * Indicates the field that stores the cached value.
180
     */
181
    @Target(ElementType.PARAMETER)
182
    @Retention(RetentionPolicy.RUNTIME)
183
    protected @interface CacheField {
184
        /* empty */
185
    }
186

187
    /**
188
     * A resolver for {@link Advice} that caches a method's return type.
189
     */
190
    protected enum AdviceResolver {
1✔
191

192
        /**
193
         * A resolver for a {@code boolean} type.
194
         */
195
        BOOLEAN(boolean.class, Opcodes.ILOAD, Opcodes.ISTORE, Opcodes.NOP, Opcodes.IFNE),
1✔
196

197
        /**
198
         * A resolver for a {@code byte} type.
199
         */
200
        BYTE(byte.class, Opcodes.ILOAD, Opcodes.ISTORE, Opcodes.NOP, Opcodes.IFNE),
1✔
201

202
        /**
203
         * A resolver for a {@code short} type.
204
         */
205
        SHORT(short.class, Opcodes.ILOAD, Opcodes.ISTORE, Opcodes.NOP, Opcodes.IFNE),
1✔
206

207
        /**
208
         * A resolver for a {@code char} type.
209
         */
210
        CHARACTER(char.class, Opcodes.ILOAD, Opcodes.ISTORE, Opcodes.NOP, Opcodes.IFNE),
1✔
211

212
        /**
213
         * A resolver for a {@code int} type.
214
         */
215
        INTEGER(int.class, Opcodes.ILOAD, Opcodes.ISTORE, Opcodes.NOP, Opcodes.IFNE),
1✔
216

217
        /**
218
         * A resolver for a {@code long} type.
219
         */
220
        LONG(long.class, Opcodes.LLOAD, Opcodes.LSTORE, Opcodes.L2I, Opcodes.IFNE),
1✔
221

222
        /**
223
         * A resolver for a {@code float} type.
224
         */
225
        FLOAT(float.class, Opcodes.FLOAD, Opcodes.FSTORE, Opcodes.F2I, Opcodes.IFNE),
1✔
226

227
        /**
228
         * A resolver for a {@code double} type.
229
         */
230
        DOUBLE(double.class, Opcodes.DLOAD, Opcodes.DSTORE, Opcodes.D2I, Opcodes.IFNE),
1✔
231

232
        /**
233
         * A resolver for a reference type.
234
         */
235
        REFERENCE(Object.class, Opcodes.ALOAD, Opcodes.ASTORE, Opcodes.NOP, Opcodes.IFNONNULL);
1✔
236

237
        /**
238
         * The created dynamic type to use for advice.
239
         */
240
        private final DynamicType dynamicType;
241

242
        /**
243
         * Creates an advice resolver.
244
         *
245
         * @param type    The type of the return type.
246
         * @param load    The byte code that loads a value onto the stack from the local variable array.
247
         * @param store   The byte code that stores a value to the local variable array.
248
         * @param convert An instruction to convert the cached value to a value that is applied on the branch instruction.
249
         * @param branch  A jump instruction that checks if the cached value is already set.
250
         */
251
        AdviceResolver(Class<?> type, int load, int store, int convert, int branch) {
1✔
252
            dynamicType = new ByteBuddy(ClassFileVersion.JAVA_V6)
1✔
253
                    .with(TypeValidation.DISABLED)
1✔
254
                    .subclass(Object.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
1✔
255
                    .name(CachedReturnPlugin.class.getName() + "$Advice$" + this)
1✔
256
                    .defineMethod("enter", type, Ownership.STATIC)
1✔
257
                    .withParameter(type)
1✔
258
                    .annotateParameter(AnnotationDescription.Builder.ofType(CachedReturnPlugin.CacheField.class).build())
1✔
259
                    .intercept(new Implementation.Simple(
1✔
260
                            MethodVariableAccess.of(TypeDescription.ForLoadedType.of(type)).loadFrom(0),
1✔
261
                            MethodReturn.of(TypeDescription.ForLoadedType.of(type))
1✔
262
                    ))
263
                    .annotateMethod(AnnotationDescription.Builder.ofType(Advice.OnMethodEnter.class)
1✔
264
                            .define("skipOn", Advice.OnNonDefaultValue.class)
1✔
265
                            .build())
1✔
266
                    .defineMethod("exit", void.class, Ownership.STATIC)
1✔
267
                    .withParameter(type)
1✔
268
                    .annotateParameter(AnnotationDescription.Builder.ofType(Advice.Return.class)
1✔
269
                            .define("readOnly", false)
1✔
270
                            .define("typing", Assigner.Typing.DYNAMIC)
1✔
271
                            .build())
1✔
272
                    .withParameter(type)
1✔
273
                    .annotateParameter(AnnotationDescription.Builder.ofType(CachedReturnPlugin.CacheField.class).build())
1✔
274
                    .intercept(new Implementation.Simple(new ExitAdviceByteCodeAppender(load, store, convert, branch, StackSize.of(type).getSize())))
1✔
275
                    .annotateMethod(AnnotationDescription.Builder.ofType(Advice.OnMethodExit.class).build())
1✔
276
                    .make();
1✔
277
        }
1✔
278

279
        /**
280
         * Creates an advice resolver for a given type definition.
281
         *
282
         * @param typeDefinition The type definition for which advice is to be created.
283
         * @return An appropriate advice resolver.
284
         */
285
        protected static AdviceResolver of(TypeDefinition typeDefinition) {
286
            if (typeDefinition.represents(boolean.class)) {
1✔
287
                return BOOLEAN;
1✔
288
            } else if (typeDefinition.represents(byte.class)) {
1✔
289
                return BYTE;
1✔
290
            } else if (typeDefinition.represents(short.class)) {
1✔
291
                return SHORT;
1✔
292
            } else if (typeDefinition.represents(char.class)) {
1✔
293
                return CHARACTER;
1✔
294
            } else if (typeDefinition.represents(int.class)) {
1✔
295
                return INTEGER;
1✔
296
            } else if (typeDefinition.represents(long.class)) {
1✔
297
                return LONG;
1✔
298
            } else if (typeDefinition.represents(float.class)) {
1✔
299
                return FLOAT;
1✔
300
            } else if (typeDefinition.represents(double.class)) {
1✔
301
                return DOUBLE;
1✔
302
            } else if (typeDefinition.isPrimitive()) {
1✔
303
                throw new IllegalArgumentException("Unexpected advice type: " + typeDefinition);
1✔
304
            } else {
305
                return REFERENCE;
1✔
306
            }
307
        }
308

309
        /**
310
         * Resolve advice for a given field name.
311
         *
312
         * @param name The name of the field to resolve the advice for.
313
         * @return An appropriate advice.
314
         */
315
        protected Advice toAdvice(String name) {
316
            return Advice.withCustomMapping()
1✔
317
                    .bind(CacheField.class, new CacheFieldOffsetMapping(name))
1✔
318
                    .to(dynamicType.getTypeDescription(), dynamicType);
1✔
319
        }
320

321
        /**
322
         * A byte code appender for the exit advice.
323
         */
324
        @HashCodeAndEqualsPlugin.Enhance
325
        protected static class ExitAdviceByteCodeAppender implements ByteCodeAppender {
326

327
            /**
328
             * The byte code that loads a value onto the stack from the local variable array.
329
             */
330
            private final int load;
331

332
            /**
333
             * The byte code that stores a value to the local variable array.
334
             */
335
            private final int store;
336

337
            /**
338
             * An instruction to convert the cached value to a value that is applied on the branch instruction.
339
             */
340
            private final int convert;
341

342
            /**
343
             * A jump instruction that checks if the cached value is already set.
344
             */
345
            private final int branch;
346

347
            /**
348
             * The size of the created type on the operand stack.
349
             */
350
            private final int size;
351

352
            /**
353
             * Creates a byte code appender for exit advice on a cached return plugin.
354
             *
355
             * @param load    The byte code that loads a value onto the stack from the local variable array.
356
             * @param store   The byte code that stores a value to the local variable array.
357
             * @param convert An instruction to convert the cached value to a value that is applied on the branch instruction.
358
             * @param branch  A jump instruction that checks if the cached value is already set.
359
             * @param size    The size of the created type on the operand stack.
360
             */
361
            protected ExitAdviceByteCodeAppender(int load, int store, int convert, int branch, int size) {
1✔
362
                this.load = load;
1✔
363
                this.store = store;
1✔
364
                this.convert = convert;
1✔
365
                this.branch = branch;
1✔
366
                this.size = size;
1✔
367
            }
1✔
368

369
            /**
370
             * {@inheritDoc}
371
             */
372
            public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodDescription instrumentedMethod) {
373
                Label complete = new Label(), uncached = new Label();
1✔
374
                methodVisitor.visitVarInsn(load, 0);
1✔
375
                if (convert != Opcodes.NOP) {
1✔
376
                    methodVisitor.visitInsn(convert);
1✔
377
                }
378
                methodVisitor.visitJumpInsn(branch, uncached);
1✔
379
                methodVisitor.visitVarInsn(load, size);
1✔
380
                methodVisitor.visitVarInsn(store, 0);
1✔
381
                methodVisitor.visitJumpInsn(Opcodes.GOTO, complete);
1✔
382
                methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
1✔
383
                methodVisitor.visitLabel(uncached);
1✔
384
                methodVisitor.visitVarInsn(load, 0);
1✔
385
                methodVisitor.visitVarInsn(store, size);
1✔
386
                methodVisitor.visitLabel(complete);
1✔
387
                methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
1✔
388
                methodVisitor.visitInsn(Opcodes.RETURN);
1✔
389
                return new Size(size * 2, instrumentedMethod.getStackSize());
1✔
390
            }
391
        }
392
    }
393

394
    /**
395
     * An offset mapping for the cached field.
396
     */
397
    @HashCodeAndEqualsPlugin.Enhance
398
    protected static class CacheFieldOffsetMapping implements Advice.OffsetMapping {
399

400
        /**
401
         * The field's name.
402
         */
403
        private final String name;
404

405
        /**
406
         * Creates an offset mapping for the cached field.
407
         *
408
         * @param name The field's name.
409
         */
410
        protected CacheFieldOffsetMapping(String name) {
1✔
411
            this.name = name;
1✔
412
        }
1✔
413

414
        /**
415
         * {@inheritDoc}
416
         */
417
        public Target resolve(TypeDescription instrumentedType,
418
                              MethodDescription instrumentedMethod,
419
                              Assigner assigner,
420
                              Advice.ArgumentHandler argumentHandler,
421
                              Sort sort) {
422
            return new Target.ForField.ReadWrite(instrumentedType.getDeclaredFields().filter(named(name)).getOnly());
1✔
423
        }
424
    }
425
}
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