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