View Javadoc
1   /*
2    * MIT License
3    * Copyright (c) 2006-2025 JMockit developers
4    * See LICENSE file for full license text.
5    */
6   package mockit.asm.classes;
7   
8   import edu.umd.cs.findbugs.annotations.NonNull;
9   import edu.umd.cs.findbugs.annotations.Nullable;
10  
11  import java.util.ArrayList;
12  import java.util.List;
13  
14  import mockit.asm.BaseWriter;
15  import mockit.asm.SignatureWriter;
16  import mockit.asm.constantPool.AttributeWriter;
17  import mockit.asm.constantPool.ConstantPoolGeneration;
18  import mockit.asm.constantPool.DynamicItem;
19  import mockit.asm.fields.FieldVisitor;
20  import mockit.asm.jvmConstants.ClassVersion;
21  import mockit.asm.methods.MethodWriter;
22  import mockit.asm.util.ByteVector;
23  import mockit.asm.util.MethodHandle;
24  import mockit.internal.util.ClassLoad;
25  
26  import org.checkerframework.checker.index.qual.NonNegative;
27  
28  /**
29   * A {@link ClassVisitor} that generates classes in bytecode form, that is, a byte array conforming to the
30   * <a href="https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html">Java class file format</a>.
31   * <p>
32   * It can be used alone, to generate a Java class "from scratch", or with one or more {@link ClassReader} and adapter
33   * class visitor to generate a modified class from one or more existing Java classes.
34   */
35  @SuppressWarnings({ "OverlyCoupledClass", "ClassWithTooManyFields" })
36  public final class ClassWriter extends ClassVisitor {
37      /**
38       * The class bytecode from which this class writer will generate a new/modified class.
39       */
40      @NonNull
41      public final byte[] code;
42  
43      /**
44       * Minor and major version numbers of the class to be generated.
45       */
46      private int classVersion;
47  
48      /**
49       * The constant pool item that contains the internal name of this class.
50       */
51      @NonNegative
52      private int nameItemIndex;
53  
54      /**
55       * The internal name of this class.
56       */
57      private String thisName;
58  
59      /**
60       * The constant pool item that contains the internal name of the super class of this class.
61       */
62      @NonNegative
63      private int superNameItemIndex;
64  
65      @NonNull
66      private final List<AttributeWriter> attributeWriters;
67      @Nullable
68      final BootstrapMethodsWriter bootstrapMethodsWriter;
69      @Nullable
70      private InterfaceWriter interfaceWriter;
71      @Nullable
72      private InnerClassesWriter innerClassesWriter;
73      @NonNull
74      private final List<FieldVisitor> fields;
75      @NonNull
76      private final List<MethodWriter> methods;
77  
78      /**
79       * Initializes a new class writer, applying the following two optimizations that are useful for "mostly add"
80       * bytecode transformations:
81       * <ul>
82       * <li>The constant pool from the original class is copied as is in the new class, which saves time. New constant
83       * pool entries will be added at the end if necessary, but unused constant pool entries <i>won't be
84       * removed</i>.</li>
85       * <li>Methods that are not transformed are copied as is in the new class, directly from the original class bytecode
86       * (i.e. without emitting visit events for all the method instructions), which saves a <i>lot</i> of time.
87       * Untransformed methods are detected by the fact that the {@link ClassReader} receives <code>MethodVisitor</code>
88       * objects that come from a <code>ClassWriter</code> (and not from any other {@link ClassVisitor} instance).</li>
89       * </ul>
90       *
91       * @param classReader
92       *            the {@link ClassReader} used to read the original class; it will be used to copy the entire constant
93       *            pool from the original class and also to copy other fragments of original bytecode where applicable
94       */
95      public ClassWriter(@NonNull ClassReader classReader) {
96          code = classReader.code;
97          classVersion = classReader.getVersion();
98  
99          cp = new ConstantPoolGeneration();
100 
101         bootstrapMethodsWriter = classReader.positionAtBootstrapMethodsAttribute()
102                 ? new BootstrapMethodsWriter(cp, classReader)
103                 : null;
104         new ConstantPoolCopying(classReader, this).copyPool(bootstrapMethodsWriter);
105 
106         attributeWriters = new ArrayList<>(5);
107 
108         if (bootstrapMethodsWriter != null) {
109             attributeWriters.add(bootstrapMethodsWriter);
110         }
111 
112         fields = new ArrayList<>();
113         methods = new ArrayList<>();
114     }
115 
116     public int getClassVersion() {
117         return classVersion;
118     }
119 
120     public String getInternalClassName() {
121         return thisName;
122     }
123 
124     @Override
125     public void visit(int version, int access, @NonNull String name, @NonNull ClassInfo additionalInfo) {
126         classVersion = version;
127         classOrMemberAccess = access;
128         nameItemIndex = cp.newClass(name);
129         thisName = name;
130 
131         createMarkerAttributes(version);
132 
133         String superName = additionalInfo.superName;
134         superNameItemIndex = superName == null ? 0 : cp.newClass(superName);
135 
136         createInterfaceWriterIfApplicable(additionalInfo.interfaces);
137         createSignatureWriterIfApplicable(additionalInfo.signature);
138         createSourceFileWriterIfApplicable(additionalInfo.sourceFileName);
139         createNestWritersIfApplicable(additionalInfo.hostClassName, additionalInfo.nestMembers);
140 
141         if (superName != null) {
142             ClassLoad.addSuperClass(name, superName);
143         }
144     }
145 
146     private void createInterfaceWriterIfApplicable(@NonNull String[] interfaces) {
147         if (interfaces.length > 0) {
148             interfaceWriter = new InterfaceWriter(cp, interfaces);
149         }
150     }
151 
152     private void createSignatureWriterIfApplicable(@Nullable String signature) {
153         if (signature != null) {
154             attributeWriters.add(new SignatureWriter(cp, signature));
155         }
156     }
157 
158     private void createSourceFileWriterIfApplicable(@Nullable String sourceFileName) {
159         if (sourceFileName != null) {
160             attributeWriters.add(new SourceFileWriter(cp, sourceFileName));
161         }
162     }
163 
164     private void createNestWritersIfApplicable(@Nullable String hostClassName, @Nullable String[] memberClassNames) {
165         if (hostClassName != null) {
166             attributeWriters.add(new NestHostWriter(cp, hostClassName));
167         } else if (memberClassNames != null) {
168             attributeWriters.add(new NestMembersWriter(cp, memberClassNames));
169         }
170     }
171 
172     @Override
173     public void visitInnerClass(@NonNull String name, @Nullable String outerName, @Nullable String innerName,
174             int access) {
175         if (innerClassesWriter == null) {
176             innerClassesWriter = new InnerClassesWriter(cp);
177             attributeWriters.add(innerClassesWriter);
178         }
179 
180         innerClassesWriter.add(name, outerName, innerName, access);
181     }
182 
183     @NonNull
184     @Override
185     public FieldVisitor visitField(int access, @NonNull String name, @NonNull String desc, @Nullable String signature,
186             @Nullable Object value) {
187         FieldVisitor field = new FieldVisitor(this, access, name, desc, signature, value);
188         fields.add(field);
189         return field;
190     }
191 
192     @NonNull
193     @Override
194     public MethodWriter visitMethod(int access, @NonNull String name, @NonNull String desc, @Nullable String signature,
195             @Nullable String[] exceptions) {
196         boolean computeFrames = classVersion >= ClassVersion.V7;
197         MethodWriter method = new MethodWriter(this, access, name, desc, signature, exceptions, computeFrames);
198         methods.add(method);
199         return method;
200     }
201 
202     /**
203      * Returns the bytecode of the class that was built with this class writer.
204      */
205     @NonNull
206     @Override
207     public byte[] toByteArray() {
208         cp.checkConstantPoolMaxSize();
209 
210         int size = getBytecodeSize(); // the real size of the bytecode of this class
211 
212         // Allocates a byte vector of this size, in order to avoid unnecessary arraycopy operations in the
213         // ByteVector.enlarge() method.
214         ByteVector out = new ByteVector(size);
215 
216         putClassAttributes(out);
217         putAnnotations(out);
218         return out.getData();
219     }
220 
221     @NonNegative
222     private int getBytecodeSize() {
223         int size = 24 + getMarkerAttributesSize() + getFieldsSize() + getMethodsSize();
224 
225         if (interfaceWriter != null) {
226             size += interfaceWriter.getSize();
227         }
228 
229         for (AttributeWriter attributeWriter : attributeWriters) {
230             size += attributeWriter.getSize();
231         }
232 
233         return size + getAnnotationsSize() + cp.getSize();
234     }
235 
236     @NonNegative
237     private int getFieldsSize() {
238         int size = 0;
239 
240         for (FieldVisitor fv : fields) {
241             size += fv.getSize();
242         }
243 
244         return size;
245     }
246 
247     @NonNegative
248     private int getMethodsSize() {
249         int size = 0;
250 
251         for (MethodWriter mb : methods) {
252             size += mb.getSize();
253         }
254 
255         return size;
256     }
257 
258     private void putClassAttributes(@NonNull ByteVector out) {
259         out.putInt(0xCAFEBABE).putInt(classVersion);
260         cp.put(out);
261 
262         putAccess(out, 0);
263         out.putShort(nameItemIndex).putShort(superNameItemIndex);
264 
265         if (interfaceWriter == null) {
266             out.putShort(0);
267         } else {
268             interfaceWriter.put(out);
269         }
270 
271         BaseWriter.put(out, fields);
272         BaseWriter.put(out, methods);
273 
274         int attributeCount = getAttributeCount();
275         out.putShort(attributeCount);
276 
277         for (AttributeWriter attributeWriter : attributeWriters) {
278             attributeWriter.put(out);
279         }
280 
281         putMarkerAttributes(out);
282     }
283 
284     @NonNegative
285     private int getAttributeCount() {
286         int attributeCount = getMarkerAttributeCount() + attributeWriters.size();
287 
288         if (annotations != null) {
289             attributeCount++;
290         }
291 
292         return attributeCount;
293     }
294 
295     @NonNull
296     public DynamicItem addInvokeDynamicReference(@NonNull String name, @NonNull String desc, @NonNull MethodHandle bsm,
297             @NonNull Object... bsmArgs) {
298         assert bootstrapMethodsWriter != null;
299         return bootstrapMethodsWriter.addInvokeDynamicReference(name, desc, bsm, bsmArgs);
300     }
301 
302     public boolean isJava6OrNewer() {
303         return classVersion >= ClassVersion.V6;
304     }
305 }