/* |
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
* |
|
* This code is free software; you can redistribute it and/or modify it |
|
* under the terms of the GNU General Public License version 2 only, as |
|
* published by the Free Software Foundation. Oracle designates this |
|
* particular file as subject to the "Classpath" exception as provided |
|
* by Oracle in the LICENSE file that accompanied this code. |
|
* |
|
* This code is distributed in the hope that it will be useful, but WITHOUT |
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
* version 2 for more details (a copy is included in the LICENSE file that |
|
* accompanied this code). |
|
* |
|
* You should have received a copy of the GNU General Public License version |
|
* 2 along with this work; if not, write to the Free Software Foundation, |
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
* |
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
* or visit www.oracle.com if you need additional information or have any |
|
* questions. |
|
*/ |
|
/* |
|
* This file is available under and governed by the GNU General Public |
|
* License version 2 only, as published by the Free Software Foundation. |
|
* However, the following notice accompanied the original version of this |
|
* file: |
|
* |
|
* ASM: a very small and fast Java bytecode manipulation framework |
|
* Copyright (c) 2000-2011 INRIA, France Telecom |
|
* All rights reserved. |
|
* |
|
* Redistribution and use in source and binary forms, with or without |
|
* modification, are permitted provided that the following conditions |
|
* are met: |
|
* 1. Redistributions of source code must retain the above copyright |
|
* notice, this list of conditions and the following disclaimer. |
|
* 2. Redistributions in binary form must reproduce the above copyright |
|
* notice, this list of conditions and the following disclaimer in the |
|
* documentation and/or other materials provided with the distribution. |
|
* 3. Neither the name of the copyright holders nor the names of its |
|
* contributors may be used to endorse or promote products derived from |
|
* this software without specific prior written permission. |
|
* |
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
|
* THE POSSIBILITY OF SUCH DAMAGE. |
|
*/ |
|
package jdk.internal.org.objectweb.asm; |
|
/** |
|
* A {@link ClassVisitor} that generates a corresponding ClassFile structure, as defined in the Java |
|
* Virtual Machine Specification (JVMS). It can be used alone, to generate a Java class "from |
|
* scratch", or with one or more {@link ClassReader} and adapter {@link ClassVisitor} to generate a |
|
* modified class from one or more existing Java classes. |
|
* |
|
* @see <a href="https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html">JVMS 4</a> |
|
* @author Eric Bruneton |
|
*/ |
|
public class ClassWriter extends ClassVisitor { |
|
/** |
|
* A flag to automatically compute the maximum stack size and the maximum number of local |
|
* variables of methods. If this flag is set, then the arguments of the {@link |
|
* MethodVisitor#visitMaxs} method of the {@link MethodVisitor} returned by the {@link |
|
* #visitMethod} method will be ignored, and computed automatically from the signature and the |
|
* bytecode of each method. |
|
* |
|
* <p><b>Note:</b> for classes whose version is {@link Opcodes#V1_7} of more, this option requires |
|
* valid stack map frames. The maximum stack size is then computed from these frames, and from the |
|
* bytecode instructions in between. If stack map frames are not present or must be recomputed, |
|
* used {@link #COMPUTE_FRAMES} instead. |
|
* |
|
* @see #ClassWriter(int) |
|
*/ |
|
public static final int COMPUTE_MAXS = 1; |
|
/** |
|
* A flag to automatically compute the stack map frames of methods from scratch. If this flag is |
|
* set, then the calls to the {@link MethodVisitor#visitFrame} method are ignored, and the stack |
|
* map frames are recomputed from the methods bytecode. The arguments of the {@link |
|
* MethodVisitor#visitMaxs} method are also ignored and recomputed from the bytecode. In other |
|
* words, {@link #COMPUTE_FRAMES} implies {@link #COMPUTE_MAXS}. |
|
* |
|
* @see #ClassWriter(int) |
|
*/ |
|
public static final int COMPUTE_FRAMES = 2; |
|
// Note: fields are ordered as in the ClassFile structure, and those related to attributes are |
|
// ordered as in Section 4.7 of the JVMS. |
|
/** |
|
* The minor_version and major_version fields of the JVMS ClassFile structure. minor_version is |
|
* stored in the 16 most significant bits, and major_version in the 16 least significant bits. |
|
*/ |
|
private int version; |
|
/** The symbol table for this class (contains the constant_pool and the BootstrapMethods). */ |
|
private final SymbolTable symbolTable; |
|
/** |
|
* The access_flags field of the JVMS ClassFile structure. This field can contain ASM specific |
|
* access flags, such as {@link Opcodes#ACC_DEPRECATED} or {}@link Opcodes#ACC_RECORD}, which are |
|
* removed when generating the ClassFile structure. |
|
*/ |
|
private int accessFlags; |
|
/** The this_class field of the JVMS ClassFile structure. */ |
|
private int thisClass; |
|
/** The super_class field of the JVMS ClassFile structure. */ |
|
private int superClass; |
|
/** The interface_count field of the JVMS ClassFile structure. */ |
|
private int interfaceCount; |
|
/** The 'interfaces' array of the JVMS ClassFile structure. */ |
|
private int[] interfaces; |
|
/** |
|
* The fields of this class, stored in a linked list of {@link FieldWriter} linked via their |
|
* {@link FieldWriter#fv} field. This field stores the first element of this list. |
|
*/ |
|
private FieldWriter firstField; |
|
/** |
|
* The fields of this class, stored in a linked list of {@link FieldWriter} linked via their |
|
* {@link FieldWriter#fv} field. This field stores the last element of this list. |
|
*/ |
|
private FieldWriter lastField; |
|
/** |
|
* The methods of this class, stored in a linked list of {@link MethodWriter} linked via their |
|
* {@link MethodWriter#mv} field. This field stores the first element of this list. |
|
*/ |
|
private MethodWriter firstMethod; |
|
/** |
|
* The methods of this class, stored in a linked list of {@link MethodWriter} linked via their |
|
* {@link MethodWriter#mv} field. This field stores the last element of this list. |
|
*/ |
|
private MethodWriter lastMethod; |
|
/** The number_of_classes field of the InnerClasses attribute, or 0. */ |
|
private int numberOfInnerClasses; |
|
/** The 'classes' array of the InnerClasses attribute, or {@literal null}. */ |
|
private ByteVector innerClasses; |
|
/** The class_index field of the EnclosingMethod attribute, or 0. */ |
|
private int enclosingClassIndex; |
|
/** The method_index field of the EnclosingMethod attribute. */ |
|
private int enclosingMethodIndex; |
|
/** The signature_index field of the Signature attribute, or 0. */ |
|
private int signatureIndex; |
|
/** The source_file_index field of the SourceFile attribute, or 0. */ |
|
private int sourceFileIndex; |
|
/** The debug_extension field of the SourceDebugExtension attribute, or {@literal null}. */ |
|
private ByteVector debugExtension; |
|
/** |
|
* The last runtime visible annotation of this class. The previous ones can be accessed with the |
|
* {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. |
|
*/ |
|
private AnnotationWriter lastRuntimeVisibleAnnotation; |
|
/** |
|
* The last runtime invisible annotation of this class. The previous ones can be accessed with the |
|
* {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. |
|
*/ |
|
private AnnotationWriter lastRuntimeInvisibleAnnotation; |
|
/** |
|
* The last runtime visible type annotation of this class. The previous ones can be accessed with |
|
* the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. |
|
*/ |
|
private AnnotationWriter lastRuntimeVisibleTypeAnnotation; |
|
/** |
|
* The last runtime invisible type annotation of this class. The previous ones can be accessed |
|
* with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. |
|
*/ |
|
private AnnotationWriter lastRuntimeInvisibleTypeAnnotation; |
|
/** The Module attribute of this class, or {@literal null}. */ |
|
private ModuleWriter moduleWriter; |
|
/** The host_class_index field of the NestHost attribute, or 0. */ |
|
private int nestHostClassIndex; |
|
/** The number_of_classes field of the NestMembers attribute, or 0. */ |
|
private int numberOfNestMemberClasses; |
|
/** The 'classes' array of the NestMembers attribute, or {@literal null}. */ |
|
private ByteVector nestMemberClasses; |
|
/** The number_of_classes field of the PermittedSubclasses attribute, or 0. */ |
|
private int numberOfPermittedSubclassClasses; |
|
/** The 'classes' array of the PermittedSubclasses attribute, or {@literal null}. */ |
|
private ByteVector permittedSubclassClasses; |
|
/** |
|
* The record components of this class, stored in a linked list of {@link RecordComponentWriter} |
|
* linked via their {@link RecordComponentWriter#delegate} field. This field stores the first |
|
* element of this list. |
|
*/ |
|
private RecordComponentWriter firstRecordComponent; |
|
/** |
|
* The record components of this class, stored in a linked list of {@link RecordComponentWriter} |
|
* linked via their {@link RecordComponentWriter#delegate} field. This field stores the last |
|
* element of this list. |
|
*/ |
|
private RecordComponentWriter lastRecordComponent; |
|
/** |
|
* The first non standard attribute of this class. The next ones can be accessed with the {@link |
|
* Attribute#nextAttribute} field. May be {@literal null}. |
|
* |
|
* <p><b>WARNING</b>: this list stores the attributes in the <i>reverse</i> order of their visit. |
|
* firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link |
|
* #toByteArray} method writes the attributes in the order defined by this list, i.e. in the |
|
* reverse order specified by the user. |
|
*/ |
|
private Attribute firstAttribute; |
|
/** |
|
* Indicates what must be automatically computed in {@link MethodWriter}. Must be one of {@link |
|
* MethodWriter#COMPUTE_NOTHING}, {@link MethodWriter#COMPUTE_MAX_STACK_AND_LOCAL}, {@link |
|
* MethodWriter#COMPUTE_INSERTED_FRAMES}, or {@link MethodWriter#COMPUTE_ALL_FRAMES}. |
|
*/ |
|
private int compute; |
|
// ----------------------------------------------------------------------------------------------- |
|
// Constructor |
|
// ----------------------------------------------------------------------------------------------- |
|
/** |
|
* Constructs a new {@link ClassWriter} object. |
|
* |
|
* @param flags option flags that can be used to modify the default behavior of this class. Must |
|
* be zero or more of {@link #COMPUTE_MAXS} and {@link #COMPUTE_FRAMES}. |
|
*/ |
|
public ClassWriter(final int flags) { |
|
this(null, flags); |
|
} |
|
/** |
|
* Constructs a new {@link ClassWriter} object and enables optimizations for "mostly add" bytecode |
|
* transformations. These optimizations are the following: |
|
* |
|
* <ul> |
|
* <li>The constant pool and bootstrap methods from the original class are copied as is in the |
|
* new class, which saves time. New constant pool entries and new bootstrap methods will be |
|
* added at the end if necessary, but unused constant pool entries or bootstrap methods |
|
* <i>won't be removed</i>. |
|
* <li>Methods that are not transformed are copied as is in the new class, directly from the |
|
* original class bytecode (i.e. without emitting visit events for all the method |
|
* instructions), which saves a <i>lot</i> of time. Untransformed methods are detected by |
|
* the fact that the {@link ClassReader} receives {@link MethodVisitor} objects that come |
|
* from a {@link ClassWriter} (and not from any other {@link ClassVisitor} instance). |
|
* </ul> |
|
* |
|
* @param classReader the {@link ClassReader} used to read the original class. It will be used to |
|
* copy the entire constant pool and bootstrap methods from the original class and also to |
|
* copy other fragments of original bytecode where applicable. |
|
* @param flags option flags that can be used to modify the default behavior of this class.Must be |
|
* zero or more of {@link #COMPUTE_MAXS} and {@link #COMPUTE_FRAMES}. <i>These option flags do |
|
* not affect methods that are copied as is in the new class. This means that neither the |
|
* maximum stack size nor the stack frames will be computed for these methods</i>. |
|
*/ |
|
public ClassWriter(final ClassReader classReader, final int flags) { |
|
super(/* latest api = */ Opcodes.ASM8); |
|
symbolTable = classReader == null ? new SymbolTable(this) : new SymbolTable(this, classReader); |
|
if ((flags & COMPUTE_FRAMES) != 0) { |
|
this.compute = MethodWriter.COMPUTE_ALL_FRAMES; |
|
} else if ((flags & COMPUTE_MAXS) != 0) { |
|
this.compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL; |
|
} else { |
|
this.compute = MethodWriter.COMPUTE_NOTHING; |
|
} |
|
} |
|
// ----------------------------------------------------------------------------------------------- |
|
// Implementation of the ClassVisitor abstract class |
|
// ----------------------------------------------------------------------------------------------- |
|
@Override |
|
public final void visit( |
|
final int version, |
|
final int access, |
|
final String name, |
|
final String signature, |
|
final String superName, |
|
final String[] interfaces) { |
|
this.version = version; |
|
this.accessFlags = access; |
|
this.thisClass = symbolTable.setMajorVersionAndClassName(version & 0xFFFF, name); |
|
if (signature != null) { |
|
this.signatureIndex = symbolTable.addConstantUtf8(signature); |
|
} |
|
this.superClass = superName == null ? 0 : symbolTable.addConstantClass(superName).index; |
|
if (interfaces != null && interfaces.length > 0) { |
|
interfaceCount = interfaces.length; |
|
this.interfaces = new int[interfaceCount]; |
|
for (int i = 0; i < interfaceCount; ++i) { |
|
this.interfaces[i] = symbolTable.addConstantClass(interfaces[i]).index; |
|
} |
|
} |
|
if (compute == MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL && (version & 0xFFFF) >= Opcodes.V1_7) { |
|
compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES; |
|
} |
|
} |
|
@Override |
|
public final void visitSource(final String file, final String debug) { |
|
if (file != null) { |
|
sourceFileIndex = symbolTable.addConstantUtf8(file); |
|
} |
|
if (debug != null) { |
|
debugExtension = new ByteVector().encodeUtf8(debug, 0, Integer.MAX_VALUE); |
|
} |
|
} |
|
@Override |
|
public final ModuleVisitor visitModule( |
|
final String name, final int access, final String version) { |
|
return moduleWriter = |
|
new ModuleWriter( |
|
symbolTable, |
|
symbolTable.addConstantModule(name).index, |
|
access, |
|
version == null ? 0 : symbolTable.addConstantUtf8(version)); |
|
} |
|
@Override |
|
public final void visitNestHost(final String nestHost) { |
|
nestHostClassIndex = symbolTable.addConstantClass(nestHost).index; |
|
} |
|
@Override |
|
public final void visitOuterClass( |
|
final String owner, final String name, final String descriptor) { |
|
enclosingClassIndex = symbolTable.addConstantClass(owner).index; |
|
if (name != null && descriptor != null) { |
|
enclosingMethodIndex = symbolTable.addConstantNameAndType(name, descriptor); |
|
} |
|
} |
|
@Override |
|
public final AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { |
|
if (visible) { |
|
return lastRuntimeVisibleAnnotation = |
|
AnnotationWriter.create(symbolTable, descriptor, lastRuntimeVisibleAnnotation); |
|
} else { |
|
return lastRuntimeInvisibleAnnotation = |
|
AnnotationWriter.create(symbolTable, descriptor, lastRuntimeInvisibleAnnotation); |
|
} |
|
} |
|
@Override |
|
public final AnnotationVisitor visitTypeAnnotation( |
|
final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { |
|
if (visible) { |
|
return lastRuntimeVisibleTypeAnnotation = |
|
AnnotationWriter.create( |
|
symbolTable, typeRef, typePath, descriptor, lastRuntimeVisibleTypeAnnotation); |
|
} else { |
|
return lastRuntimeInvisibleTypeAnnotation = |
|
AnnotationWriter.create( |
|
symbolTable, typeRef, typePath, descriptor, lastRuntimeInvisibleTypeAnnotation); |
|
} |
|
} |
|
@Override |
|
public final void visitAttribute(final Attribute attribute) { |
|
// Store the attributes in the <i>reverse</i> order of their visit by this method. |
|
attribute.nextAttribute = firstAttribute; |
|
firstAttribute = attribute; |
|
} |
|
@Override |
|
public final void visitNestMember(final String nestMember) { |
|
if (nestMemberClasses == null) { |
|
nestMemberClasses = new ByteVector(); |
|
} |
|
++numberOfNestMemberClasses; |
|
nestMemberClasses.putShort(symbolTable.addConstantClass(nestMember).index); |
|
} |
|
/** |
|
* <b>Experimental, use at your own risk.</b> |
|
* |
|
* @param permittedSubclass the internal name of a permitted subclass. |
|
* @deprecated this API is experimental. |
|
*/ |
|
@Override |
|
@Deprecated |
|
public final void visitPermittedSubclassExperimental(final String permittedSubclass) { |
|
if (permittedSubclassClasses == null) { |
|
permittedSubclassClasses = new ByteVector(); |
|
} |
|
++numberOfPermittedSubclassClasses; |
|
permittedSubclassClasses.putShort(symbolTable.addConstantClass(permittedSubclass).index); |
|
} |
|
@Override |
|
public final void visitInnerClass( |
|
final String name, final String outerName, final String innerName, final int access) { |
|
if (innerClasses == null) { |
|
innerClasses = new ByteVector(); |
|
} |
|
// Section 4.7.6 of the JVMS states "Every CONSTANT_Class_info entry in the constant_pool table |
|
// which represents a class or interface C that is not a package member must have exactly one |
|
// corresponding entry in the classes array". To avoid duplicates we keep track in the info |
|
// field of the Symbol of each CONSTANT_Class_info entry C whether an inner class entry has |
|
// already been added for C. If so, we store the index of this inner class entry (plus one) in |
|
// the info field. This trick allows duplicate detection in O(1) time. |
|
Symbol nameSymbol = symbolTable.addConstantClass(name); |
|
if (nameSymbol.info == 0) { |
|
++numberOfInnerClasses; |
|
innerClasses.putShort(nameSymbol.index); |
|
innerClasses.putShort(outerName == null ? 0 : symbolTable.addConstantClass(outerName).index); |
|
innerClasses.putShort(innerName == null ? 0 : symbolTable.addConstantUtf8(innerName)); |
|
innerClasses.putShort(access); |
|
nameSymbol.info = numberOfInnerClasses; |
|
} |
|
// Else, compare the inner classes entry nameSymbol.info - 1 with the arguments of this method |
|
// and throw an exception if there is a difference? |
|
} |
|
@Override |
|
public final RecordComponentVisitor visitRecordComponent( |
|
final String name, final String descriptor, final String signature) { |
|
RecordComponentWriter recordComponentWriter = |
|
new RecordComponentWriter(symbolTable, name, descriptor, signature); |
|
if (firstRecordComponent == null) { |
|
firstRecordComponent = recordComponentWriter; |
|
} else { |
|
lastRecordComponent.delegate = recordComponentWriter; |
|
} |
|
return lastRecordComponent = recordComponentWriter; |
|
} |
|
@Override |
|
public final FieldVisitor visitField( |
|
final int access, |
|
final String name, |
|
final String descriptor, |
|
final String signature, |
|
final Object value) { |
|
FieldWriter fieldWriter = |
|
new FieldWriter(symbolTable, access, name, descriptor, signature, value); |
|
if (firstField == null) { |
|
firstField = fieldWriter; |
|
} else { |
|
lastField.fv = fieldWriter; |
|
} |
|
return lastField = fieldWriter; |
|
} |
|
@Override |
|
public final MethodVisitor visitMethod( |
|
final int access, |
|
final String name, |
|
final String descriptor, |
|
final String signature, |
|
final String[] exceptions) { |
|
MethodWriter methodWriter = |
|
new MethodWriter(symbolTable, access, name, descriptor, signature, exceptions, compute); |
|
if (firstMethod == null) { |
|
firstMethod = methodWriter; |
|
} else { |
|
lastMethod.mv = methodWriter; |
|
} |
|
return lastMethod = methodWriter; |
|
} |
|
@Override |
|
public final void visitEnd() { |
|
// Nothing to do. |
|
} |
|
// ----------------------------------------------------------------------------------------------- |
|
// Other public methods |
|
// ----------------------------------------------------------------------------------------------- |
|
/** |
|
* Returns the content of the class file that was built by this ClassWriter. |
|
* |
|
* @return the binary content of the JVMS ClassFile structure that was built by this ClassWriter. |
|
* @throws ClassTooLargeException if the constant pool of the class is too large. |
|
* @throws MethodTooLargeException if the Code attribute of a method is too large. |
|
*/ |
|
public byte[] toByteArray() { |
|
// First step: compute the size in bytes of the ClassFile structure. |
|
// The magic field uses 4 bytes, 10 mandatory fields (minor_version, major_version, |
|
// constant_pool_count, access_flags, this_class, super_class, interfaces_count, fields_count, |
|
// methods_count and attributes_count) use 2 bytes each, and each interface uses 2 bytes too. |
|
int size = 24 + 2 * interfaceCount; |
|
int fieldsCount = 0; |
|
FieldWriter fieldWriter = firstField; |
|
while (fieldWriter != null) { |
|
++fieldsCount; |
|
size += fieldWriter.computeFieldInfoSize(); |
|
fieldWriter = (FieldWriter) fieldWriter.fv; |
|
} |
|
int methodsCount = 0; |
|
MethodWriter methodWriter = firstMethod; |
|
while (methodWriter != null) { |
|
++methodsCount; |
|
size += methodWriter.computeMethodInfoSize(); |
|
methodWriter = (MethodWriter) methodWriter.mv; |
|
} |
|
// For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. |
|
int attributesCount = 0; |
|
if (innerClasses != null) { |
|
++attributesCount; |
|
size += 8 + innerClasses.length; |
|
symbolTable.addConstantUtf8(Constants.INNER_CLASSES); |
|
} |
|
if (enclosingClassIndex != 0) { |
|
++attributesCount; |
|
size += 10; |
|
symbolTable.addConstantUtf8(Constants.ENCLOSING_METHOD); |
|
} |
|
if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && (version & 0xFFFF) < Opcodes.V1_5) { |
|
++attributesCount; |
|
size += 6; |
|
symbolTable.addConstantUtf8(Constants.SYNTHETIC); |
|
} |
|
if (signatureIndex != 0) { |
|
++attributesCount; |
|
size += 8; |
|
symbolTable.addConstantUtf8(Constants.SIGNATURE); |
|
} |
|
if (sourceFileIndex != 0) { |
|
++attributesCount; |
|
size += 8; |
|
symbolTable.addConstantUtf8(Constants.SOURCE_FILE); |
|
} |
|
if (debugExtension != null) { |
|
++attributesCount; |
|
size += 6 + debugExtension.length; |
|
symbolTable.addConstantUtf8(Constants.SOURCE_DEBUG_EXTENSION); |
|
} |
|
if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { |
|
++attributesCount; |
|
size += 6; |
|
symbolTable.addConstantUtf8(Constants.DEPRECATED); |
|
} |
|
if (lastRuntimeVisibleAnnotation != null) { |
|
++attributesCount; |
|
size += |
|
lastRuntimeVisibleAnnotation.computeAnnotationsSize( |
|
Constants.RUNTIME_VISIBLE_ANNOTATIONS); |
|
} |
|
if (lastRuntimeInvisibleAnnotation != null) { |
|
++attributesCount; |
|
size += |
|
lastRuntimeInvisibleAnnotation.computeAnnotationsSize( |
|
Constants.RUNTIME_INVISIBLE_ANNOTATIONS); |
|
} |
|
if (lastRuntimeVisibleTypeAnnotation != null) { |
|
++attributesCount; |
|
size += |
|
lastRuntimeVisibleTypeAnnotation.computeAnnotationsSize( |
|
Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS); |
|
} |
|
if (lastRuntimeInvisibleTypeAnnotation != null) { |
|
++attributesCount; |
|
size += |
|
lastRuntimeInvisibleTypeAnnotation.computeAnnotationsSize( |
|
Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS); |
|
} |
|
if (symbolTable.computeBootstrapMethodsSize() > 0) { |
|
++attributesCount; |
|
size += symbolTable.computeBootstrapMethodsSize(); |
|
} |
|
if (moduleWriter != null) { |
|
attributesCount += moduleWriter.getAttributeCount(); |
|
size += moduleWriter.computeAttributesSize(); |
|
} |
|
if (nestHostClassIndex != 0) { |
|
++attributesCount; |
|
size += 8; |
|
symbolTable.addConstantUtf8(Constants.NEST_HOST); |
|
} |
|
if (nestMemberClasses != null) { |
|
++attributesCount; |
|
size += 8 + nestMemberClasses.length; |
|
symbolTable.addConstantUtf8(Constants.NEST_MEMBERS); |
|
} |
|
if (permittedSubclassClasses != null) { |
|
++attributesCount; |
|
size += 8 + permittedSubclassClasses.length; |
|
symbolTable.addConstantUtf8(Constants.PERMITTED_SUBCLASSES); |
|
} |
|
int recordComponentCount = 0; |
|
int recordSize = 0; |
|
if ((accessFlags & Opcodes.ACC_RECORD) != 0 || firstRecordComponent != null) { |
|
RecordComponentWriter recordComponentWriter = firstRecordComponent; |
|
while (recordComponentWriter != null) { |
|
++recordComponentCount; |
|
recordSize += recordComponentWriter.computeRecordComponentInfoSize(); |
|
recordComponentWriter = (RecordComponentWriter) recordComponentWriter.delegate; |
|
} |
|
++attributesCount; |
|
size += 8 + recordSize; |
|
symbolTable.addConstantUtf8(Constants.RECORD); |
|
} |
|
if (firstAttribute != null) { |
|
attributesCount += firstAttribute.getAttributeCount(); |
|
size += firstAttribute.computeAttributesSize(symbolTable); |
|
} |
|
// IMPORTANT: this must be the last part of the ClassFile size computation, because the previous |
|
// statements can add attribute names to the constant pool, thereby changing its size! |
|
size += symbolTable.getConstantPoolLength(); |
|
int constantPoolCount = symbolTable.getConstantPoolCount(); |
|
if (constantPoolCount > 0xFFFF) { |
|
throw new ClassTooLargeException(symbolTable.getClassName(), constantPoolCount); |
|
} |
|
// Second step: allocate a ByteVector of the correct size (in order to avoid any array copy in |
|
// dynamic resizes) and fill it with the ClassFile content. |
|
ByteVector result = new ByteVector(size); |
|
result.putInt(0xCAFEBABE).putInt(version); |
|
symbolTable.putConstantPool(result); |
|
int mask = (version & 0xFFFF) < Opcodes.V1_5 ? Opcodes.ACC_SYNTHETIC : 0; |
|
result.putShort(accessFlags & ~mask).putShort(thisClass).putShort(superClass); |
|
result.putShort(interfaceCount); |
|
for (int i = 0; i < interfaceCount; ++i) { |
|
result.putShort(interfaces[i]); |
|
} |
|
result.putShort(fieldsCount); |
|
fieldWriter = firstField; |
|
while (fieldWriter != null) { |
|
fieldWriter.putFieldInfo(result); |
|
fieldWriter = (FieldWriter) fieldWriter.fv; |
|
} |
|
result.putShort(methodsCount); |
|
boolean hasFrames = false; |
|
boolean hasAsmInstructions = false; |
|
methodWriter = firstMethod; |
|
while (methodWriter != null) { |
|
hasFrames |= methodWriter.hasFrames(); |
|
hasAsmInstructions |= methodWriter.hasAsmInstructions(); |
|
methodWriter.putMethodInfo(result); |
|
methodWriter = (MethodWriter) methodWriter.mv; |
|
} |
|
// For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. |
|
result.putShort(attributesCount); |
|
if (innerClasses != null) { |
|
result |
|
.putShort(symbolTable.addConstantUtf8(Constants.INNER_CLASSES)) |
|
.putInt(innerClasses.length + 2) |
|
.putShort(numberOfInnerClasses) |
|
.putByteArray(innerClasses.data, 0, innerClasses.length); |
|
} |
|
if (enclosingClassIndex != 0) { |
|
result |
|
.putShort(symbolTable.addConstantUtf8(Constants.ENCLOSING_METHOD)) |
|
.putInt(4) |
|
.putShort(enclosingClassIndex) |
|
.putShort(enclosingMethodIndex); |
|
} |
|
if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && (version & 0xFFFF) < Opcodes.V1_5) { |
|
result.putShort(symbolTable.addConstantUtf8(Constants.SYNTHETIC)).putInt(0); |
|
} |
|
if (signatureIndex != 0) { |
|
result |
|
.putShort(symbolTable.addConstantUtf8(Constants.SIGNATURE)) |
|
.putInt(2) |
|
.putShort(signatureIndex); |
|
} |
|
if (sourceFileIndex != 0) { |
|
result |
|
.putShort(symbolTable.addConstantUtf8(Constants.SOURCE_FILE)) |
|
.putInt(2) |
|
.putShort(sourceFileIndex); |
|
} |
|
if (debugExtension != null) { |
|
int length = debugExtension.length; |
|
result |
|
.putShort(symbolTable.addConstantUtf8(Constants.SOURCE_DEBUG_EXTENSION)) |
|
.putInt(length) |
|
.putByteArray(debugExtension.data, 0, length); |
|
} |
|
if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { |
|
result.putShort(symbolTable.addConstantUtf8(Constants.DEPRECATED)).putInt(0); |
|
} |
|
AnnotationWriter.putAnnotations( |
|
symbolTable, |
|
lastRuntimeVisibleAnnotation, |
|
lastRuntimeInvisibleAnnotation, |
|
lastRuntimeVisibleTypeAnnotation, |
|
lastRuntimeInvisibleTypeAnnotation, |
|
result); |
|
symbolTable.putBootstrapMethods(result); |
|
if (moduleWriter != null) { |
|
moduleWriter.putAttributes(result); |
|
} |
|
if (nestHostClassIndex != 0) { |
|
result |
|
.putShort(symbolTable.addConstantUtf8(Constants.NEST_HOST)) |
|
.putInt(2) |
|
.putShort(nestHostClassIndex); |
|
} |
|
if (nestMemberClasses != null) { |
|
result |
|
.putShort(symbolTable.addConstantUtf8(Constants.NEST_MEMBERS)) |
|
.putInt(nestMemberClasses.length + 2) |
|
.putShort(numberOfNestMemberClasses) |
|
.putByteArray(nestMemberClasses.data, 0, nestMemberClasses.length); |
|
} |
|
if (permittedSubclassClasses != null) { |
|
result |
|
.putShort(symbolTable.addConstantUtf8(Constants.PERMITTED_SUBCLASSES)) |
|
.putInt(permittedSubclassClasses.length + 2) |
|
.putShort(numberOfPermittedSubclassClasses) |
|
.putByteArray(permittedSubclassClasses.data, 0, permittedSubclassClasses.length); |
|
} |
|
if ((accessFlags & Opcodes.ACC_RECORD) != 0 || firstRecordComponent != null) { |
|
result |
|
.putShort(symbolTable.addConstantUtf8(Constants.RECORD)) |
|
.putInt(recordSize + 2) |
|
.putShort(recordComponentCount); |
|
RecordComponentWriter recordComponentWriter = firstRecordComponent; |
|
while (recordComponentWriter != null) { |
|
recordComponentWriter.putRecordComponentInfo(result); |
|
recordComponentWriter = (RecordComponentWriter) recordComponentWriter.delegate; |
|
} |
|
} |
|
if (firstAttribute != null) { |
|
firstAttribute.putAttributes(symbolTable, result); |
|
} |
|
// Third step: replace the ASM specific instructions, if any. |
|
if (hasAsmInstructions) { |
|
return replaceAsmInstructions(result.data, hasFrames); |
|
} else { |
|
return result.data; |
|
} |
|
} |
|
/** |
|
* Returns the equivalent of the given class file, with the ASM specific instructions replaced |
|
* with standard ones. This is done with a ClassReader -> ClassWriter round trip. |
|
* |
|
* @param classFile a class file containing ASM specific instructions, generated by this |
|
* ClassWriter. |
|
* @param hasFrames whether there is at least one stack map frames in 'classFile'. |
|
* @return an equivalent of 'classFile', with the ASM specific instructions replaced with standard |
|
* ones. |
|
*/ |
|
private byte[] replaceAsmInstructions(final byte[] classFile, final boolean hasFrames) { |
|
final Attribute[] attributes = getAttributePrototypes(); |
|
firstField = null; |
|
lastField = null; |
|
firstMethod = null; |
|
lastMethod = null; |
|
lastRuntimeVisibleAnnotation = null; |
|
lastRuntimeInvisibleAnnotation = null; |
|
lastRuntimeVisibleTypeAnnotation = null; |
|
lastRuntimeInvisibleTypeAnnotation = null; |
|
moduleWriter = null; |
|
nestHostClassIndex = 0; |
|
numberOfNestMemberClasses = 0; |
|
nestMemberClasses = null; |
|
numberOfPermittedSubclassClasses = 0; |
|
permittedSubclassClasses = null; |
|
firstRecordComponent = null; |
|
lastRecordComponent = null; |
|
firstAttribute = null; |
|
compute = hasFrames ? MethodWriter.COMPUTE_INSERTED_FRAMES : MethodWriter.COMPUTE_NOTHING; |
|
new ClassReader(classFile, 0, /* checkClassVersion = */ false) |
|
.accept( |
|
this, |
|
attributes, |
|
(hasFrames ? ClassReader.EXPAND_FRAMES : 0) | ClassReader.EXPAND_ASM_INSNS); |
|
return toByteArray(); |
|
} |
|
/** |
|
* Returns the prototypes of the attributes used by this class, its fields and its methods. |
|
* |
|
* @return the prototypes of the attributes used by this class, its fields and its methods. |
|
*/ |
|
private Attribute[] getAttributePrototypes() { |
|
Attribute.Set attributePrototypes = new Attribute.Set(); |
|
attributePrototypes.addAttributes(firstAttribute); |
|
FieldWriter fieldWriter = firstField; |
|
while (fieldWriter != null) { |
|
fieldWriter.collectAttributePrototypes(attributePrototypes); |
|
fieldWriter = (FieldWriter) fieldWriter.fv; |
|
} |
|
MethodWriter methodWriter = firstMethod; |
|
while (methodWriter != null) { |
|
methodWriter.collectAttributePrototypes(attributePrototypes); |
|
methodWriter = (MethodWriter) methodWriter.mv; |
|
} |
|
RecordComponentWriter recordComponentWriter = firstRecordComponent; |
|
while (recordComponentWriter != null) { |
|
recordComponentWriter.collectAttributePrototypes(attributePrototypes); |
|
recordComponentWriter = (RecordComponentWriter) recordComponentWriter.delegate; |
|
} |
|
return attributePrototypes.toArray(); |
|
} |
|
// ----------------------------------------------------------------------------------------------- |
|
// Utility methods: constant pool management for Attribute sub classes |
|
// ----------------------------------------------------------------------------------------------- |
|
/** |
|
* Adds a number or string constant to the constant pool of the class being build. Does nothing if |
|
* the constant pool already contains a similar item. <i>This method is intended for {@link |
|
* Attribute} sub classes, and is normally not needed by class generators or adapters.</i> |
|
* |
|
* @param value the value of the constant to be added to the constant pool. This parameter must be |
|
* an {@link Integer}, a {@link Float}, a {@link Long}, a {@link Double} or a {@link String}. |
|
* @return the index of a new or already existing constant item with the given value. |
|
*/ |
|
public int newConst(final Object value) { |
|
return symbolTable.addConstant(value).index; |
|
} |
|
/** |
|
* Adds an UTF8 string to the constant pool of the class being build. Does nothing if the constant |
|
* pool already contains a similar item. <i>This method is intended for {@link Attribute} sub |
|
* classes, and is normally not needed by class generators or adapters.</i> |
|
* |
|
* @param value the String value. |
|
* @return the index of a new or already existing UTF8 item. |
|
*/ |
|
// DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility). |
|
public int newUTF8(final String value) { |
|
return symbolTable.addConstantUtf8(value); |
|
} |
|
/** |
|
* Adds a class reference to the constant pool of the class being build. Does nothing if the |
|
* constant pool already contains a similar item. <i>This method is intended for {@link Attribute} |
|
* sub classes, and is normally not needed by class generators or adapters.</i> |
|
* |
|
* @param value the internal name of the class. |
|
* @return the index of a new or already existing class reference item. |
|
*/ |
|
public int newClass(final String value) { |
|
return symbolTable.addConstantClass(value).index; |
|
} |
|
/** |
|
* Adds a method type reference to the constant pool of the class being build. Does nothing if the |
|
* constant pool already contains a similar item. <i>This method is intended for {@link Attribute} |
|
* sub classes, and is normally not needed by class generators or adapters.</i> |
|
* |
|
* @param methodDescriptor method descriptor of the method type. |
|
* @return the index of a new or already existing method type reference item. |
|
*/ |
|
public int newMethodType(final String methodDescriptor) { |
|
return symbolTable.addConstantMethodType(methodDescriptor).index; |
|
} |
|
/** |
|
* Adds a module reference to the constant pool of the class being build. Does nothing if the |
|
* constant pool already contains a similar item. <i>This method is intended for {@link Attribute} |
|
* sub classes, and is normally not needed by class generators or adapters.</i> |
|
* |
|
* @param moduleName name of the module. |
|
* @return the index of a new or already existing module reference item. |
|
*/ |
|
public int newModule(final String moduleName) { |
|
return symbolTable.addConstantModule(moduleName).index; |
|
} |
|
/** |
|
* Adds a package reference to the constant pool of the class being build. Does nothing if the |
|
* constant pool already contains a similar item. <i>This method is intended for {@link Attribute} |
|
* sub classes, and is normally not needed by class generators or adapters.</i> |
|
* |
|
* @param packageName name of the package in its internal form. |
|
* @return the index of a new or already existing module reference item. |
|
*/ |
|
public int newPackage(final String packageName) { |
|
return symbolTable.addConstantPackage(packageName).index; |
|
} |
|
/** |
|
* Adds a handle to the constant pool of the class being build. Does nothing if the constant pool |
|
* already contains a similar item. <i>This method is intended for {@link Attribute} sub classes, |
|
* and is normally not needed by class generators or adapters.</i> |
|
* |
|
* @param tag the kind of this handle. Must be {@link Opcodes#H_GETFIELD}, {@link |
|
* Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link |
|
* Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, |
|
* {@link Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}. |
|
* @param owner the internal name of the field or method owner class. |
|
* @param name the name of the field or method. |
|
* @param descriptor the descriptor of the field or method. |
|
* @return the index of a new or already existing method type reference item. |
|
* @deprecated this method is superseded by {@link #newHandle(int, String, String, String, |
|
* boolean)}. |
|
*/ |
|
@Deprecated |
|
public int newHandle( |
|
final int tag, final String owner, final String name, final String descriptor) { |
|
return newHandle(tag, owner, name, descriptor, tag == Opcodes.H_INVOKEINTERFACE); |
|
} |
|
/** |
|
* Adds a handle to the constant pool of the class being build. Does nothing if the constant pool |
|
* already contains a similar item. <i>This method is intended for {@link Attribute} sub classes, |
|
* and is normally not needed by class generators or adapters.</i> |
|
* |
|
* @param tag the kind of this handle. Must be {@link Opcodes#H_GETFIELD}, {@link |
|
* Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link |
|
* Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, |
|
* {@link Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}. |
|
* @param owner the internal name of the field or method owner class. |
|
* @param name the name of the field or method. |
|
* @param descriptor the descriptor of the field or method. |
|
* @param isInterface true if the owner is an interface. |
|
* @return the index of a new or already existing method type reference item. |
|
*/ |
|
public int newHandle( |
|
final int tag, |
|
final String owner, |
|
final String name, |
|
final String descriptor, |
|
final boolean isInterface) { |
|
return symbolTable.addConstantMethodHandle(tag, owner, name, descriptor, isInterface).index; |
|
} |
|
/** |
|
* Adds a dynamic constant reference to the constant pool of the class being build. Does nothing |
|
* if the constant pool already contains a similar item. <i>This method is intended for {@link |
|
* Attribute} sub classes, and is normally not needed by class generators or adapters.</i> |
|
* |
|
* @param name name of the invoked method. |
|
* @param descriptor field descriptor of the constant type. |
|
* @param bootstrapMethodHandle the bootstrap method. |
|
* @param bootstrapMethodArguments the bootstrap method constant arguments. |
|
* @return the index of a new or already existing dynamic constant reference item. |
|
*/ |
|
public int newConstantDynamic( |
|
final String name, |
|
final String descriptor, |
|
final Handle bootstrapMethodHandle, |
|
final Object... bootstrapMethodArguments) { |
|
return symbolTable.addConstantDynamic( |
|
name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments) |
|
.index; |
|
} |
|
/** |
|
* Adds an invokedynamic reference to the constant pool of the class being build. Does nothing if |
|
* the constant pool already contains a similar item. <i>This method is intended for {@link |
|
* Attribute} sub classes, and is normally not needed by class generators or adapters.</i> |
|
* |
|
* @param name name of the invoked method. |
|
* @param descriptor descriptor of the invoke method. |
|
* @param bootstrapMethodHandle the bootstrap method. |
|
* @param bootstrapMethodArguments the bootstrap method constant arguments. |
|
* @return the index of a new or already existing invokedynamic reference item. |
|
*/ |
|
public int newInvokeDynamic( |
|
final String name, |
|
final String descriptor, |
|
final Handle bootstrapMethodHandle, |
|
final Object... bootstrapMethodArguments) { |
|
return symbolTable.addConstantInvokeDynamic( |
|
name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments) |
|
.index; |
|
} |
|
/** |
|
* Adds a field reference to the constant pool of the class being build. Does nothing if the |
|
* constant pool already contains a similar item. <i>This method is intended for {@link Attribute} |
|
* sub classes, and is normally not needed by class generators or adapters.</i> |
|
* |
|
* @param owner the internal name of the field's owner class. |
|
* @param name the field's name. |
|
* @param descriptor the field's descriptor. |
|
* @return the index of a new or already existing field reference item. |
|
*/ |
|
public int newField(final String owner, final String name, final String descriptor) { |
|
return symbolTable.addConstantFieldref(owner, name, descriptor).index; |
|
} |
|
/** |
|
* Adds a method reference to the constant pool of the class being build. Does nothing if the |
|
* constant pool already contains a similar item. <i>This method is intended for {@link Attribute} |
|
* sub classes, and is normally not needed by class generators or adapters.</i> |
|
* |
|
* @param owner the internal name of the method's owner class. |
|
* @param name the method's name. |
|
* @param descriptor the method's descriptor. |
|
* @param isInterface {@literal true} if {@code owner} is an interface. |
|
* @return the index of a new or already existing method reference item. |
|
*/ |
|
public int newMethod( |
|
final String owner, final String name, final String descriptor, final boolean isInterface) { |
|
return symbolTable.addConstantMethodref(owner, name, descriptor, isInterface).index; |
|
} |
|
/** |
|
* Adds a name and type to the constant pool of the class being build. Does nothing if the |
|
* constant pool already contains a similar item. <i>This method is intended for {@link Attribute} |
|
* sub classes, and is normally not needed by class generators or adapters.</i> |
|
* |
|
* @param name a name. |
|
* @param descriptor a type descriptor. |
|
* @return the index of a new or already existing name and type item. |
|
*/ |
|
public int newNameType(final String name, final String descriptor) { |
|
return symbolTable.addConstantNameAndType(name, descriptor); |
|
} |
|
// ----------------------------------------------------------------------------------------------- |
|
// Default method to compute common super classes when computing stack map frames |
|
// ----------------------------------------------------------------------------------------------- |
|
/** |
|
* Returns the common super type of the two given types. The default implementation of this method |
|
* <i>loads</i> the two given classes and uses the java.lang.Class methods to find the common |
|
* super class. It can be overridden to compute this common super type in other ways, in |
|
* particular without actually loading any class, or to take into account the class that is |
|
* currently being generated by this ClassWriter, which can of course not be loaded since it is |
|
* under construction. |
|
* |
|
* @param type1 the internal name of a class. |
|
* @param type2 the internal name of another class. |
|
* @return the internal name of the common super class of the two given classes. |
|
*/ |
|
protected String getCommonSuperClass(final String type1, final String type2) { |
|
ClassLoader classLoader = getClassLoader(); |
|
Class<?> class1; |
|
try { |
|
class1 = Class.forName(type1.replace('/', '.'), false, classLoader); |
|
} catch (ClassNotFoundException e) { |
|
throw new TypeNotPresentException(type1, e); |
|
} |
|
Class<?> class2; |
|
try { |
|
class2 = Class.forName(type2.replace('/', '.'), false, classLoader); |
|
} catch (ClassNotFoundException e) { |
|
throw new TypeNotPresentException(type2, e); |
|
} |
|
if (class1.isAssignableFrom(class2)) { |
|
return type1; |
|
} |
|
if (class2.isAssignableFrom(class1)) { |
|
return type2; |
|
} |
|
if (class1.isInterface() || class2.isInterface()) { |
|
return "java/lang/Object"; |
|
} else { |
|
do { |
|
class1 = class1.getSuperclass(); |
|
} while (!class1.isAssignableFrom(class2)); |
|
return class1.getName().replace('.', '/'); |
|
} |
|
} |
|
/** |
|
* Returns the {@link ClassLoader} to be used by the default implementation of {@link |
|
* #getCommonSuperClass(String, String)}, that of this {@link ClassWriter}'s runtime type by |
|
* default. |
|
* |
|
* @return ClassLoader |
|
*/ |
|
protected ClassLoader getClassLoader() { |
|
return getClass().getClassLoader(); |
|
} |
|
} |