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

raphw / byte-buddy / #723

18 Jan 2025 10:07PM UTC coverage: 85.371% (-0.002%) from 85.373%
#723

push

raphw
Fix use of ASM class reader and writer.

1 of 5 new or added lines in 2 files covered. (20.0%)

28974 of 33939 relevant lines covered (85.37%)

0.85 hits per line

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

53.76
/byte-buddy-dep/src/main/java/net/bytebuddy/utility/AsmClassWriter.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.utility;
17

18
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
19
import net.bytebuddy.ClassFileVersion;
20
import net.bytebuddy.build.AccessControllerPlugin;
21
import net.bytebuddy.build.HashCodeAndEqualsPlugin;
22
import net.bytebuddy.description.type.TypeDescription;
23
import net.bytebuddy.pool.TypePool;
24
import net.bytebuddy.utility.dispatcher.JavaDispatcher;
25
import net.bytebuddy.utility.nullability.AlwaysNull;
26
import net.bytebuddy.utility.nullability.MaybeNull;
27
import net.bytebuddy.utility.privilege.GetSystemPropertyAction;
28
import org.objectweb.asm.ClassReader;
29
import org.objectweb.asm.ClassVisitor;
30
import org.objectweb.asm.ClassWriter;
31

32
import java.lang.reflect.Method;
33
import java.security.PrivilegedAction;
34

35
/**
36
 * A facade for creating a {@link ClassVisitor} that writes a class file.
37
 */
38
public interface AsmClassWriter {
39

40
    /**
41
     * Returns the {@link ClassVisitor} to use for writing the class file.
42
     *
43
     * @return An appropriate class visitor.
44
     */
45
    ClassVisitor getVisitor();
46

47
    /**
48
     * Returns the binary representation of the created class file.
49
     *
50
     * @return The binary representation of the created class file.
51
     */
52
    byte[] getBinaryRepresentation();
53

54
    /**
55
     * A factory for creating an {@link AsmClassWriter}.
56
     */
57
    interface Factory {
58

59
        /**
60
         * Creates a new class writer for the given flags.
61
         *
62
         * @param flags The flags to consider while writing a class file.
63
         * @return An appropriate class writer.
64
         */
65
        AsmClassWriter make(int flags);
66

67
        /**
68
         * Creates a new class writer for the given flags, possibly based on a previous class file representation.
69
         *
70
         * @param flags       The flags to consider while writing a class file.
71
         * @param classReader A class reader to consider for writing a class file.
72
         * @return An appropriate class writer.
73
         */
74
        AsmClassWriter make(int flags, AsmClassReader classReader);
75

76
        /**
77
         * Creates a new class writer for the given flags.
78
         *
79
         * @param flags    The flags to consider while writing a class file.
80
         * @param typePool A type pool to use for resolving type information for frame generation.
81
         * @return An appropriate class writer.
82
         */
83
        AsmClassWriter make(int flags, TypePool typePool);
84

85
        /**
86
         * Creates a new class writer for the given flags, possibly based on a previous class file representation.
87
         *
88
         * @param flags       The flags to consider while writing a class file.
89
         * @param classReader A class reader to consider for writing a class file.
90
         * @param typePool    A type pool to use for resolving type information for frame generation.
91
         * @return An appropriate class writer.
92
         */
93
        AsmClassWriter make(int flags, AsmClassReader classReader, TypePool typePool);
94

95
        /**
96
         * Default implementations for factories of {@link AsmClassWriter}s.
97
         */
98
        enum Default implements Factory {
1✔
99

100
            /**
101
             * Uses a processor as it is configured by {@link OpenedClassReader#PROCESSOR_PROPERTY},
102
             * or {@link Default#ASM_FIRST} if no implicit processor is defined.
103
             */
104
            IMPLICIT {
1✔
105
                /**
106
                 * {@inheritDoc}
107
                 */
108
                public AsmClassWriter make(int flags, AsmClassReader classReader, TypePool typePool) {
109
                    return FACTORY.make(flags, classReader, typePool);
1✔
110
                }
111
            },
112

113
            /**
114
             * A factory for a class reader that uses ASM's internal implementation whenever possible.
115
             */
116
            ASM_FIRST {
1✔
117
                /**
118
                 * {@inheritDoc}
119
                 */
120
                public AsmClassWriter make(int flags, AsmClassReader classReader, TypePool typePool) {
121
                    return ClassFileVersion.ofThisVm().isGreaterThan(ClassFileVersion.latest())
1✔
122
                            ? CLASS_FILE_API_ONLY.make(flags, classReader, typePool)
1✔
123
                            : ASM_ONLY.make(flags, classReader, typePool);
1✔
124
                }
125
            },
126

127
            /**
128
             * A factory for a class writer that uses the class file API whenever possible.
129
             */
130
            CLASS_FILE_API_FIRST {
1✔
131
                /**
132
                 * {@inheritDoc}
133
                 */
134
                public AsmClassWriter make(int flags, AsmClassReader classReader, TypePool typePool) {
135
                    return ClassFileVersion.ofThisVm().isAtLeast(ClassFileVersion.JAVA_V24)
×
136
                            ? CLASS_FILE_API_ONLY.make(flags, classReader, typePool)
×
137
                            : ASM_ONLY.make(flags, classReader, typePool);
×
138
                }
139
            },
140

141
            /**
142
             * A factory that will always use ASM's internal implementation.
143
             */
144
            ASM_ONLY {
1✔
145
                /**
146
                 * {@inheritDoc}
147
                 */
148
                public AsmClassWriter make(int flags, AsmClassReader classReader, TypePool typePool) {
149
                    ClassReader unwrapped = classReader.unwrap(ClassReader.class);
1✔
150
                    return new ForAsm(unwrapped == null
1✔
151
                            ? new FrameComputingClassWriter(flags, typePool)
152
                            : new FrameComputingClassWriter(unwrapped, flags, typePool));
153
                }
154
            },
155

156
            /**
157
             * A factory that will always use the Class File API.
158
             */
159
            CLASS_FILE_API_ONLY {
1✔
160
                /**
161
                 * {@inheritDoc}
162
                 */
163
                @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "False positive in FindBugs.")
164
                public AsmClassWriter make(int flags, AsmClassReader classReader, TypePool typePool) {
165
                    Object jdkClassReader = JDK_CLASS_READER == null ? null : classReader.unwrap(JDK_CLASS_READER);
×
166
                    if (jdkClassReader == null) {
×
167
                        return new ForClassFileApi(ForClassFileApi.DISPATCHER.make(flags,
×
168
                                SuperClassResolvingJdkClassWriter.GET_SUPER_CLASS,
169
                                new SuperClassResolvingJdkClassWriter(typePool)));
170
                    } else {
NEW
171
                        return new ForClassFileApi(ForClassFileApi.DISPATCHER.make(jdkClassReader,
×
172
                                flags,
173
                                SuperClassResolvingJdkClassWriter.GET_SUPER_CLASS,
174
                                new SuperClassResolvingJdkClassWriter(typePool)));
175
                    }
176
                }
177
            };
178

179
            /**
180
             * The {@code codes.rafael.asmjdkbridge.JdkClassReader} type or {@code null} if not available.
181
             */
182
            @MaybeNull
183
            private static final Class<?> JDK_CLASS_READER;
184

185
            /**
186
             * The implicit factory to use for writing class files.
187
             */
188
            private static final Factory FACTORY;
189

190
            /*
191
             * Resolves the implicit writer factory, if any and locates a possible {@code JdkClassReader} type.
192
             */
193
            static {
194
                String processor;
195
                try {
196
                    processor = doPrivileged(new GetSystemPropertyAction(OpenedClassReader.PROCESSOR_PROPERTY));
1✔
197
                } catch (Throwable ignored) {
×
198
                    processor = null;
×
199
                }
1✔
200
                FACTORY = processor == null ? Default.ASM_FIRST : Default.valueOf(processor);
1✔
201
                Class<?> type;
202
                try {
203
                    type = ClassFileVersion.ofThisVm().isAtLeast(ClassFileVersion.JAVA_V24)
1✔
204
                        ? Class.forName("codes.rafael.asmjdkbridge.JdkClassReader")
1✔
205
                        : null;
206
                } catch (ClassNotFoundException ignored) {
×
207
                    type = null;
×
208
                }
1✔
209
                JDK_CLASS_READER = type;
1✔
210
            }
1✔
211

212
            /**
213
             * A proxy for {@code java.security.AccessController#doPrivileged} that is activated if available.
214
             *
215
             * @param action The action to execute from a privileged context.
216
             * @param <T>    The type of the action's resolved value.
217
             * @return The action's resolved value.
218
             */
219
            @MaybeNull
220
            @AccessControllerPlugin.Enhance
221
            private static <T> T doPrivileged(PrivilegedAction<T> action) {
222
                return action.run();
×
223
            }
224

225
            /**
226
             * {@inheritDoc}
227
             */
228
            public AsmClassWriter make(int flags) {
229
                return make(flags, TypePool.Empty.INSTANCE);
1✔
230
            }
231

232
            /**
233
             * {@inheritDoc}
234
             */
235
            public AsmClassWriter make(int flags, AsmClassReader classReader) {
236
                return make(flags, classReader, TypePool.Empty.INSTANCE);
1✔
237
            }
238

239
            /**
240
             * {@inheritDoc}
241
             */
242
            public AsmClassWriter make(int flags, TypePool typePool) {
243
                return make(flags, EmptyAsmClassReader.INSTANCE, typePool);
1✔
244
            }
245

246
            /**
247
             * An empty class reader for ASM that never unwraps an underlying implementation.
248
             */
249
            protected enum EmptyAsmClassReader implements AsmClassReader {
1✔
250

251
                /**
252
                 * The singleton instance.
253
                 */
254
                INSTANCE;
1✔
255

256
                /**
257
                 * {@inheritDoc}
258
                 */
259
                @AlwaysNull
260
                public <T> T unwrap(Class<T> type) {
261
                    return null;
1✔
262
                }
263

264
                /**
265
                 * {@inheritDoc}
266
                 */
267
                public void accept(ClassVisitor classVisitor, int flags) {
268
                    throw new UnsupportedOperationException();
×
269
                }
270
            }
271
        }
272

273
        /**
274
         * A class writer factory that suppresses any class reader implementation that might be provided
275
         * upon constructing a class writer.
276
         */
277
        @HashCodeAndEqualsPlugin.Enhance
278
        class Suppressing implements Factory {
279

280
            /**
281
             * The factory to delegate to.
282
             */
283
            private final Factory delegate;
284

285
            /**
286
             * Creates a suppressing class writer factory.
287
             *
288
             * @param delegate The factory to delegate to.
289
             */
290
            public Suppressing(Factory delegate) {
×
291
                this.delegate = delegate;
×
292
            }
×
293

294
            /**
295
             * {@inheritDoc}
296
             */
297
            public AsmClassWriter make(int flags) {
298
                return delegate.make(flags);
×
299
            }
300

301
            /**
302
             * {@inheritDoc}
303
             */
304
            public AsmClassWriter make(int flags, AsmClassReader classReader) {
305
                return delegate.make(flags);
×
306
            }
307

308
            /**
309
             * {@inheritDoc}
310
             */
311
            public AsmClassWriter make(int flags, TypePool typePool) {
312
                return delegate.make(flags, typePool);
×
313
            }
314

315
            /**
316
             * {@inheritDoc}
317
             */
318
            public AsmClassWriter make(int flags, AsmClassReader classReader, TypePool typePool) {
319
                return delegate.make(flags, typePool);
×
320
            }
321
        }
322
    }
323

324
    /**
325
     * Am implementation that uses ASM's internal {@link ClassWriter}.
326
     */
327
    class ForAsm implements AsmClassWriter {
328

329
        /**
330
         * The represented class writer.
331
         */
332
        private final ClassWriter classWriter;
333

334
        /**
335
         * Creates a new class writer based upon ASM's own implementation.
336
         *
337
         * @param classWriter The represented class writer.
338
         */
339
        public ForAsm(ClassWriter classWriter) {
1✔
340
            this.classWriter = classWriter;
1✔
341
        }
1✔
342

343
        /**
344
         * {@inheritDoc}
345
         */
346
        public ClassVisitor getVisitor() {
347
            return classWriter;
1✔
348
        }
349

350
        /**
351
         * {@inheritDoc}
352
         */
353
        public byte[] getBinaryRepresentation() {
354
            return classWriter.toByteArray();
1✔
355
        }
356
    }
357

358
    /**
359
     * A Class File API-based implementation for a class writer.
360
     */
361
    class ForClassFileApi implements AsmClassWriter {
362

363
        /**
364
         * The dispatcher for interacting with a {@code codes.rafael.asmjdkbridge.JdkClassWriter}.
365
         */
366
        private static final JdkClassWriter DISPATCHER = doPrivileged(JavaDispatcher.of(
×
367
                JdkClassWriter.class,
368
                ForClassFileApi.class.getClassLoader()));
×
369

370
        /**
371
         * The represented class writer.
372
         */
373
        private final ClassVisitor classWriter;
374

375
        /**
376
         * Creates a new class file API-based class writer.
377
         *
378
         * @param classWriter The represented class writer.
379
         */
380
        public ForClassFileApi(ClassVisitor classWriter) {
×
381
            if (!DISPATCHER.isInstance(classWriter)) {
×
382
                throw new IllegalArgumentException("Not a JDK class writer: " + classWriter);
×
383
            }
384
            this.classWriter = classWriter;
×
385
        }
×
386

387
        /**
388
         * A proxy for {@code java.security.AccessController#doPrivileged} that is activated if available.
389
         *
390
         * @param action The action to execute from a privileged context.
391
         * @param <T>    The type of the action's resolved value.
392
         * @return The action's resolved value.
393
         */
394
        @AccessControllerPlugin.Enhance
395
        private static <T> T doPrivileged(PrivilegedAction<T> action) {
396
            return action.run();
×
397
        }
398

399
        /**
400
         * {@inheritDoc}
401
         */
402
        public ClassVisitor getVisitor() {
403
            return classWriter;
×
404
        }
405

406
        /**
407
         * {@inheritDoc}
408
         */
409
        public byte[] getBinaryRepresentation() {
410
            return DISPATCHER.toByteArray(classWriter);
×
411
        }
412

413
        /**
414
         * An API to interact with {@code codes.rafael.asmjdkbridge.JdkClassWriter}.
415
         */
416
        @JavaDispatcher.Proxied("codes.rafael.asmjdkbridge.JdkClassWriter")
417
        protected interface JdkClassWriter {
418

419
            /**
420
             * Checks if the supplied instance is a {@code codes.rafael.asmjdkbridge.JdkClassWriter}.
421
             *
422
             * @param value The value to evaluate.
423
             * @return {@code true} if the supplied instance is a  {@code codes.rafael.asmjdkbridge.JdkClassWriter}.
424
             */
425
            @JavaDispatcher.Instance
426
            boolean isInstance(ClassVisitor value);
427

428
            /**
429
             * Create a new {@code codes.rafael.asmjdkbridge.JdkClassWriter}.
430
             *
431
             * @param flags         The flags to consider.
432
             * @param getSuperClass A resolver for the super class.
433
             * @param target        The target to invoke the super class resolver upon.
434
             * @return A new {@code codes.rafael.asmjdkbridge.JdkClassWriter}.
435
             */
436
            @JavaDispatcher.IsConstructor
437
            ClassVisitor make(int flags, Method getSuperClass, Object target);
438

439
            /**
440
             * Create a new {@code codes.rafael.asmjdkbridge.JdkClassWriter}.
441
             *
442
             * @param classReader   The class reader of which to reuse the constant pool.
443
             * @param flags         The flags to consider.
444
             * @param getSuperClass A resolver for the super class.
445
             * @param target        The target to invoke the super class resolver upon.
446
             * @return A new {@code codes.rafael.asmjdkbridge.JdkClassWriter}.
447
             */
448
            @JavaDispatcher.IsConstructor
449
            ClassVisitor make(@JavaDispatcher.Proxied("codes.rafael.asmjdkbridge.JdkClassReader") Object classReader,
450
                              int flags,
451
                              Method getSuperClass,
452
                              Object target);
453

454
            /**
455
             * Reads the created class file byte array from a given {@code codes.rafael.asmjdkbridge.JdkClassWriter}.
456
             *
457
             * @param value The {@code codes.rafael.asmjdkbridge.JdkClassWriter} to read from.
458
             * @return The generated class file.
459
             */
460
            byte[] toByteArray(ClassVisitor value);
461
        }
462
    }
463

464
    /**
465
     * A class writer that piggy-backs on Byte Buddy's {@link TypePool} to avoid class loading or look-up errors when redefining a class.
466
     * This is not available when creating a new class where automatic frame computation is however not normally a requirement.
467
     */
468
    class FrameComputingClassWriter extends ClassWriter {
469

470
        /**
471
         * The type pool to use for computing stack map frames, if required.
472
         */
473
        private final TypePool typePool;
474

475
        /**
476
         * Creates a new frame computing class writer.
477
         *
478
         * @param flags    The flags to be handed to the writer.
479
         * @param typePool The type pool to use for computing stack map frames, if required.
480
         */
481
        public FrameComputingClassWriter(int flags, TypePool typePool) {
482
            super(flags);
1✔
483
            this.typePool = typePool;
1✔
484
        }
1✔
485

486
        /**
487
         * Creates a new frame computing class writer.
488
         *
489
         * @param classReader The class reader from which the original class is read.
490
         * @param flags       The flags to be handed to the writer.
491
         * @param typePool    The type pool to use for computing stack map frames, if required.
492
         */
493
        public FrameComputingClassWriter(ClassReader classReader, int flags, TypePool typePool) {
494
            super(classReader, flags);
1✔
495
            this.typePool = typePool;
1✔
496
        }
1✔
497

498
        /**
499
         * {@inheritDoc}
500
         */
501
        protected String getCommonSuperClass(String leftTypeName, String rightTypeName) {
502
            TypeDescription leftType = typePool.describe(leftTypeName.replace('/', '.')).resolve();
1✔
503
            TypeDescription rightType = typePool.describe(rightTypeName.replace('/', '.')).resolve();
1✔
504
            if (leftType.isAssignableFrom(rightType)) {
1✔
505
                return leftType.getInternalName();
1✔
506
            } else if (leftType.isAssignableTo(rightType)) {
1✔
507
                return rightType.getInternalName();
1✔
508
            } else if (leftType.isInterface() || rightType.isInterface()) {
1✔
509
                return TypeDescription.ForLoadedType.of(Object.class).getInternalName();
1✔
510
            } else {
511
                do {
512
                    TypeDescription.Generic superClass = leftType.getSuperClass();
1✔
513
                    if (superClass == null) {
1✔
514
                        return TypeDescription.ForLoadedType.of(Object.class).getInternalName();
×
515
                    }
516
                    leftType = superClass.asErasure();
1✔
517
                } while (!leftType.isAssignableFrom(rightType));
1✔
518
                return leftType.getInternalName();
1✔
519
            }
520
        }
521
    }
522

523
    /**
524
     * A pseudo-JDK class writer that resolves super classes using a {@link TypePool}, to pass in the constructor.
525
     */
526
    class SuperClassResolvingJdkClassWriter {
527

528
        /**
529
         * The {@link SuperClassResolvingJdkClassWriter#getSuperClass(String)} method.
530
         */
531
        protected static final Method GET_SUPER_CLASS;
532

533
        /*
534
         * Resolve the method instance to use for reflection.
535
         */
536
        static {
537
            Method getSuperClass;
538
            try {
539
                getSuperClass = SuperClassResolvingJdkClassWriter.class.getMethod("getSuperClass", String.class);
×
540
            } catch (NoSuchMethodException e) {
×
541
                throw new IllegalStateException("Failed to resolve own method", e);
×
542
            }
×
543
            GET_SUPER_CLASS = getSuperClass;
×
544
        }
×
545

546
        /**
547
         * The {@link TypePool} to use.
548
         */
549
        private final TypePool typePool;
550

551
        /**
552
         * Creates a super class resolving JDK class writer.
553
         *
554
         * @param typePool The {@link TypePool} to use.
555
         */
556
        public SuperClassResolvingJdkClassWriter(TypePool typePool) {
×
557
            this.typePool = typePool;
×
558
        }
×
559

560
        /**
561
         * Resolves the super class for a given internal class name, or {@code null} if a given class
562
         * represents an interface. The provided class name will never be {@link Object}.
563
         *
564
         * @param internalName The internal name of the class or interface of which to return a super type.
565
         * @return The internal name of the super class or {@code null} if the provided type name represents
566
         * an interface.
567
         */
568
        @MaybeNull
569
        @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Object class can never be passed.")
570
        public String getSuperClass(String internalName) {
571
            TypeDescription typeDescription = typePool.describe(internalName.replace('/', '.')).resolve();
×
572
            return typeDescription.isInterface()
×
573
                    ? null
574
                    : typeDescription.getSuperClass().asErasure().getInternalName();
×
575
        }
576
    }
577
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc