/* * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * 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. */ package java.lang.classfile; import java.io.IOException; import java.lang.classfile.AttributeMapper.AttributeStability; import java.lang.classfile.attribute.CharacterRangeInfo; import java.lang.classfile.attribute.CodeAttribute; import java.lang.classfile.attribute.LocalVariableInfo; import java.lang.classfile.attribute.LocalVariableTypeInfo; import java.lang.classfile.attribute.ModuleAttribute; import java.lang.classfile.attribute.StackMapTableAttribute; import java.lang.classfile.attribute.UnknownAttribute; import java.lang.classfile.constantpool.ClassEntry; import java.lang.classfile.constantpool.ConstantPoolBuilder; import java.lang.classfile.constantpool.Utf8Entry; import java.lang.classfile.instruction.BranchInstruction; import java.lang.classfile.instruction.CharacterRange; import java.lang.classfile.instruction.DiscontinuedInstruction; import java.lang.classfile.instruction.ExceptionCatch; import java.lang.classfile.instruction.LineNumber; import java.lang.classfile.instruction.LocalVariable; import java.lang.classfile.instruction.LocalVariableType; import java.lang.constant.ClassDesc; import java.lang.reflect.AccessFlag; import java.lang.reflect.ClassFileFormatVersion; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.function.Consumer; import java.util.function.Function; import jdk.internal.classfile.impl.ClassFileImpl; import jdk.internal.classfile.impl.TemporaryConstantPool; import static java.util.Objects.requireNonNull; import static jdk.internal.constant.ConstantUtils.CD_module_info; /** * Provides ability to parse, transform, and generate {@code class} files. * A {@code ClassFile} is a context with a set of options that condition how * parsing and generation are done. * * @since 24 */ public sealed interface ClassFile permits ClassFileImpl { /** * {@return a context with default options} Each subtype of {@link Option} * specifies its default. *
* The default {@link AttributeMapperOption} and {@link * ClassHierarchyResolverOption} may be unsuitable for some {@code class} * files and result in parsing or generation errors. */ static ClassFile of() { return ClassFileImpl.DEFAULT_CONTEXT; } /** * {@return a context with options altered from the default} Equivalent to * {@link #of() ClassFile.of().withOptions(options)}. * @param options the desired processing options */ static ClassFile of(Option... options) { return of().withOptions(options); } /** * {@return a context with altered options from this context} * @param options the desired processing options */ ClassFile withOptions(Option... options); /** * An option that affects the parsing or writing of {@code class} files. * * @see java.lang.classfile##options Options * @sealedGraph * @since 24 */ sealed interface Option { } /** * The option describing user-defined attributes for parsing {@code class} * files. The default does not recognize any user-defined attribute. *
* An {@code AttributeMapperOption} contains a function that maps an * attribute name to a user attribute mapper. The function may return {@code * null} if it does not recognize an attribute name. The returned mapper * must ensure its {@link AttributeMapper#name() name()} is equivalent to * the {@link Utf8Entry#stringValue() stringValue()} of the input {@link * Utf8Entry}. *
* The mapping function in this attribute has lower priority than mappers in
* {@link Attributes}, so it is impossible to override built-in attributes
* with this option. If an attribute is not recognized by any mapper in
* {@link Attributes} and is not assigned a mapper, or recognized, by this
* option, that attribute will be modeled by an {@link UnknownAttribute}.
*
* @see AttributeMapper
* @see CustomAttribute
* @since 24
*/
sealed interface AttributeMapperOption extends Option
permits ClassFileImpl.AttributeMapperOptionImpl {
/**
* {@return an option describing user-defined attributes for parsing}
*
* @param attributeMapper a function mapping attribute names to attribute mappers
*/
static AttributeMapperOption of(Function
* A {@code ClassHierarchyResolverOption} contains a {@link ClassHierarchyResolver}.
* The resolver must be able to process all classes and interfaces, including
* those appearing as the component types of array types, that appear in the
* operand stack of the generated bytecode. If the resolver fails on any
* of the classes and interfaces with an {@link IllegalArgumentException},
* the {@code class} file generation fails.
*
* @see ClassHierarchyResolver
* @jvms 4.10.1.2 Verification Type System
* @since 24
*/
sealed interface ClassHierarchyResolverOption extends Option
permits ClassFileImpl.ClassHierarchyResolverOptionImpl {
/**
* {@return an option describing the class hierarchy resolver to use}
*
* @param classHierarchyResolver the resolver
*/
static ClassHierarchyResolverOption of(ClassHierarchyResolver classHierarchyResolver) {
requireNonNull(classHierarchyResolver);
return new ClassFileImpl.ClassHierarchyResolverOptionImpl(classHierarchyResolver);
}
/**
* {@return the class hierarchy resolver}
*/
ClassHierarchyResolver classHierarchyResolver();
}
/**
* Option describing whether to extend from the original constant pool when
* transforming a {@code class} file. The default is {@link #SHARED_POOL}
* to extend from the original constant pool.
*
* This option affects all overloads of {@link #transformClass transformClass}.
* Extending from the original constant pool keeps the indices into the
* constant pool intact, which enables significant optimizations in processing
* time and minimizes differences between the original and transformed {@code
* class} files, but may result in a bigger transformed {@code class} file
* when many elements of the original {@code class} file are dropped and
* many original constant pool entries become unused.
*
* An alternative to this option is to use {@link #build(ClassEntry,
* ConstantPoolBuilder, Consumer)} directly. It allows extension from
* arbitrary constant pools, and may be useful if a built {@code class} file
* reuses structures from multiple original {@code class} files.
*
* @see ConstantPoolBuilder
* @see #build(ClassEntry, ConstantPoolBuilder, Consumer)
* @see #transformClass(ClassModel, ClassTransform)
* @since 24
*/
enum ConstantPoolSharingOption implements Option {
/**
* Extend the new constant pool from the original constant pool when
* transforming the {@code class} file.
*
* These two transformations below are equivalent:
* {@snippet lang=java :
* ClassModel originalClass = null; // @replace substring=null; replacement=...
* ClassDesc resultClassName = null; // @replace substring=null; replacement=...
* ClassTransform classTransform = null; // @replace substring=null; replacement=...
* var resultOne = ClassFile.of(ConstantPoolSharingOption.SHARED_POOL)
* .transformClass(originalClass, resultClassName, classTransform);
* var resultTwo = ClassFile.of().build(resultClassName, ConstantPoolBuilder.of(originalClass),
* clb -> clb.transform(originalClass, classTransform));
* }
*
* @see ConstantPoolBuilder#of(ClassModel) ConstantPoolBuilder::of(ClassModel)
*/
SHARED_POOL,
/**
* Creates a new constant pool when transforming the {@code class} file.
*
* These two transformations below are equivalent:
* {@snippet lang=java :
* ClassModel originalClass = null; // @replace substring=null; replacement=...
* ClassDesc resultClassName = null; // @replace substring=null; replacement=...
* ClassTransform classTransform = null; // @replace substring=null; replacement=...
* var resultOne = ClassFile.of(ConstantPoolSharingOption.NEW_POOL)
* .transformClass(originalClass, resultClassName, classTransform);
* var resultTwo = ClassFile.of().build(resultClassName, ConstantPoolBuilder.of(),
* clb -> clb.transform(originalClass, classTransform));
* }
*
* @see ConstantPoolBuilder#of() ConstantPoolBuilder::of()
*/
NEW_POOL
}
/**
* The option describing whether to patch out unreachable code for stack map
* generation. The default is {@link #PATCH_DEAD_CODE} to automatically
* patch unreachable code and generate a valid stack map entry for the
* patched code.
*
* The stack map generation process may fail when it encounters unreachable
* code and {@link #KEEP_DEAD_CODE} is set. In such cases, users should
* set {@link StackMapsOption#DROP_STACK_MAPS} and provide their own stack
* maps that passes verification (JVMS {@jvms 4.10.1}).
*
* @see StackMapsOption
* @jvms 4.10.1 Verification by Type Checking
* @since 24
*/
enum DeadCodeOption implements Option {
/**
* Patch unreachable code with dummy code, and generate valid dummy
* stack map entries. This ensures the generated code can pass
* verification (JVMS {@jvms 4.10.1}).
*/
PATCH_DEAD_CODE,
/**
* Keep the unreachable code for the accuracy of the generated {@code
* class} file. Users should set {@link StackMapsOption#DROP_STACK_MAPS}
* to prevent stack map generation from running and provide their own
* {@link StackMapTableAttribute} to a {@link CodeBuilder}.
*/
KEEP_DEAD_CODE
}
/**
* The option describing whether to filter {@linkplain
* CodeBuilder#labelBinding(Label) unbound labels} and drop their
* enclosing structures if possible. The default is {@link
* #FAIL_ON_DEAD_LABELS} to fail fast with an {@link IllegalArgumentException}
* when a {@link PseudoInstruction} refers to an unbound label during
* bytecode generation.
*
* The affected {@link PseudoInstruction}s include {@link ExceptionCatch},
* {@link LocalVariable}, {@link LocalVariableType}, and {@link
* CharacterRange}. Setting this option to {@link #DROP_DEAD_LABELS}
* filters these pseudo-instructions from a {@link CodeBuilder} instead.
* Note that instructions, such as {@link BranchInstruction}, with unbound
* labels always fail-fast with an {@link IllegalArgumentException}.
*
* @see DebugElementsOption
* @since 24
*/
enum DeadLabelsOption implements Option {
/**
* Fail fast on {@linkplain CodeBuilder#labelBinding(Label) unbound
* labels}. This also ensures the accuracy of the generated {@code
* class} files.
*/
FAIL_ON_DEAD_LABELS,
/**
* Filter {@link PseudoInstruction}s with {@linkplain
* CodeBuilder#labelBinding(Label) unbound labels}. Note that
* instructions with unbound labels still cause an {@link
* IllegalArgumentException}.
*/
DROP_DEAD_LABELS
}
/**
* The option describing whether to process or discard debug {@link
* PseudoInstruction}s in the traversal of a {@link CodeModel} or a {@link
* CodeBuilder}. The default is {@link #PASS_DEBUG} to process debug
* pseudo-instructions as all other {@link CodeElement}.
*
* Debug pseudo-instructions include {@link LocalVariable}, {@link
* LocalVariableType}, and {@link CharacterRange}. Discarding debug
* elements may reduce the overhead of parsing or transforming {@code class}
* files and has no impact on the run-time behavior.
*
* @see LineNumbersOption
* @since 24
*/
enum DebugElementsOption implements Option {
/**
* Process debug pseudo-instructions like other member elements of a
* {@link CodeModel}.
*/
PASS_DEBUG,
/**
* Drop debug pseudo-instructions from traversal and builders.
*/
DROP_DEBUG
}
/**
* The option describing whether to process or discard {@link LineNumber}s
* in the traversal of a {@link CodeModel} or a {@link CodeBuilder}. The
* default is {@link #PASS_LINE_NUMBERS} to process all line number entries
* as all other {@link CodeElement}.
*
* Discarding line numbers may reduce the overhead of parsing or transforming
* {@code class} files and has no impact on the run-time behavior.
*
* @see DebugElementsOption
* @since 24
*/
enum LineNumbersOption implements Option {
/**
* Process {@link LineNumber} like other member elements of a {@link
* CodeModel}.
*/
PASS_LINE_NUMBERS,
/**
* Drop {@link LineNumber} from traversal and builders.
*/
DROP_LINE_NUMBERS;
}
/**
* The option describing whether to automatically rewrite short jumps to
* equivalent instructions when necessary. The default is {@link
* #FIX_SHORT_JUMPS} to automatically rewrite.
*
* Due to physical restrictions, some types of instructions cannot encode
* certain jump targets with bci offsets less than -32768 or greater than
* 32767, as they use a {@code s2} to encode such an offset. (The maximum
* length of the {@code code} array is 65535.) These types of instructions
* are called "short jumps".
*
* Disabling rewrite can ensure the physical accuracy of a generated {@code
* class} file and avoid the overhead from a failed first attempt for
* overflowing forward jumps in some cases, if the generated {@code class}
* file is stable.
*
* @see BranchInstruction
* @see DiscontinuedInstruction.JsrInstruction
* @since 24
*/
enum ShortJumpsOption implements Option {
/**
* Automatically convert short jumps to long when necessary.
*
* For an invalid instruction model, a {@link CodeBuilder} may generate
* another or a few other instructions to accomplish the same effect.
*/
FIX_SHORT_JUMPS,
/**
* Fail with an {@link IllegalArgumentException} if short jump overflows.
*
* This is useful to ensure the physical accuracy of a generated {@code
* class} file and avoids the overhead from a failed first attempt for
* overflowing forward jumps in some cases.
*/
FAIL_ON_SHORT_JUMPS
}
/**
* The option describing whether to generate stack maps. The default is
* {@link #STACK_MAPS_WHEN_REQUIRED} to generate stack maps or reuse
* existing ones if compatible.
*
* The {@link StackMapTableAttribute} is a derived property from a {@link
* CodeAttribute Code} attribute to allow a Java Virtual Machine to perform
* verification in one pass. Thus, it is not modeled as part of a {@link
* CodeModel}, but computed on-demand instead via stack maps generation.
*
* Stack map generation may fail with an {@link IllegalArgumentException} if
* there is {@linkplain DeadCodeOption unreachable code} or legacy
* {@linkplain DiscontinuedInstruction.JsrInstruction jump routine}
* instructions. When {@link #DROP_STACK_MAPS} option is used, users can
* provide their own stack maps by supplying a {@link StackMapTableAttribute}
* to a {@link CodeBuilder}.
*
* @see StackMapTableAttribute
* @see DeadCodeOption
* @jvms 4.10.1 Verification by Type Checking
* @since 24
*/
enum StackMapsOption implements Option {
/**
* Generate stack maps or reuse existing ones if compatible. Stack maps
* are present on major versions {@value #JAVA_6_VERSION} or above. For
* these versions, {@link CodeBuilder} tries to reuse compatible stack
* maps information if the code array and exception handlers are still
* compatible after a transformation; otherwise, it runs stack map
* generation. However, it does not fail fast if the major version is
* {@value #JAVA_6_VERSION}, which allows jump subroutine instructions
* that are incompatible with stack maps to exist in the {@code code}
* array.
*/
STACK_MAPS_WHEN_REQUIRED,
/**
* Forces running stack map generation. This runs stack map generation
* unconditionally and fails fast if the generation fails due to any
* reason.
*/
GENERATE_STACK_MAPS,
/**
* Do not run stack map generation. Users must supply their own
* {@link StackMapTableAttribute} to a {@link CodeBuilder} if the code
* has branches or exception handlers; otherwise, the generated code
* will fail verification (JVMS {@jvms 4.10.1}).
*
* This option is required for user-supplied {@link StackMapTableAttribute}
* to be respected. Stack maps on an existing {@link CodeAttribute Code}
* attribute can be reused as below with this option:
* {@snippet lang=java file="PackageSnippets.java" region="manual-reuse-stack-maps"}
*/
DROP_STACK_MAPS
}
/**
* The option describing whether to retain or discard attributes that cannot
* verify their correctness after a transformation. The default is {@link
* #PASS_ALL_ATTRIBUTES} to retain all attributes as-is.
*
* Many attributes only depend on data managed by the Class-File API, such
* as constant pool entries or labels into the {@code code} array. If they
* change, the Class-File API knows their updated values and can write a
* correct version by expanding the structures and recomputing the updated
* indexes, known as "explosion". However, some attributes, such as type
* annotations, depend on arbitrary data that may be modified during
* transformations but the Class-File API does not track, such as index to
* an entry in the {@linkplain ClassModel#interfaces() interfaces} of a
* {@code ClassFile} structure. As a result, the Class-File API cannot
* verify the correctness of such information.
*
* @see AttributeStability
* @since 24
*/
enum AttributesProcessingOption implements Option {
/**
* Retain all original attributes during transformation.
*/
PASS_ALL_ATTRIBUTES,
/**
* Drop attributes with {@link AttributeStability#UNKNOWN} data
* dependency during transformation.
*/
DROP_UNKNOWN_ATTRIBUTES,
/**
* Drop attributes with {@link AttributeStability#UNSTABLE} or higher
* data dependency during transformation.
*/
DROP_UNSTABLE_ATTRIBUTES
}
/**
* Parses a {@code class} file into a {@link ClassModel}.
*
* Due to the on-demand nature of {@code class} file parsing, an {@link
* IllegalArgumentException} may be thrown on any accessor method invocation
* on the returned model or any structure returned by the accessors in the
* structure hierarchy.
*
* @param bytes the bytes of the {@code class} file
* @return the class model
* @throws IllegalArgumentException if the {@code class} file is malformed
* or of a version {@linkplain #latestMajorVersion() not supported}
* by the current runtime
*/
ClassModel parse(byte[] bytes);
/**
* Parses a {@code class} into a {@link ClassModel}.
*
* Due to the on-demand nature of {@code class} file parsing, an {@link
* IllegalArgumentException} may be thrown on any accessor method invocation
* on the returned model or any structure returned by the accessors in the
* structure hierarchy.
*
* @param path the path to the {@code class} file
* @return the class model
* @throws IOException if an I/O error occurs
* @throws IllegalArgumentException if the {@code class} file is malformed
* or of a version {@linkplain #latestMajorVersion() not supported}
* by the current runtime
* @see #parse(byte[])
*/
default ClassModel parse(Path path) throws IOException {
return parse(Files.readAllBytes(path));
}
/**
* Builds a {@code class} file into a byte array.
*
* @param thisClass the name of the class to build
* @param handler a handler that receives a {@link ClassBuilder}
* @return the {@code class} file bytes
* @throws IllegalArgumentException if {@code thisClass} represents a
* primitive type or building encounters a failure
*/
default byte[] build(ClassDesc thisClass,
Consumer super ClassBuilder> handler) {
ConstantPoolBuilder pool = ConstantPoolBuilder.of();
return build(pool.classEntry(thisClass), pool, handler);
}
/**
* Builds a {@code class} file into a byte array using the provided constant
* pool builder.
*
* @param thisClassEntry the name of the class to build
* @param constantPool the constant pool builder
* @param handler a handler that receives a {@link ClassBuilder}
* @return the {@code class} file bytes
* @throws IllegalArgumentException if building encounters a failure
*/
byte[] build(ClassEntry thisClassEntry,
ConstantPoolBuilder constantPool,
Consumer super ClassBuilder> handler);
/**
* Builds a {@code class} file into a file in a file system.
*
* @param path the path to the file to write
* @param thisClass the name of the class to build
* @param handler a handler that receives a {@link ClassBuilder}
* @throws IOException if an I/O error occurs
* @throws IllegalArgumentException if building encounters a failure
*/
default void buildTo(Path path,
ClassDesc thisClass,
Consumer
* This method behaves as if:
* {@snippet lang=java :
* ConstantPoolBuilder cpb = null; // @replace substring=null; replacement=...
* this.build(model.thisClass(), cpb,
* clb -> clb.transform(model, transform));
* }
* where {@code cpb} is determined by {@link ConstantPoolSharingOption}.
*
* @apiNote
* This is named {@code transformClass} instead of {@code transform} for
* consistency with {@link ClassBuilder#transformField}, {@link
* ClassBuilder#transformMethod}, and {@link MethodBuilder#transformCode},
* and to distinguish from {@link ClassFileBuilder#transform}, which is
* more generic and powerful.
*
* @param model the class model to transform
* @param transform the transform
* @return the bytes of the new class
* @throws IllegalArgumentException if building encounters a failure
* @see ConstantPoolSharingOption
*/
default byte[] transformClass(ClassModel model, ClassTransform transform) {
return transformClass(model, model.thisClass(), transform);
}
/**
* Transform one {@code class} file into a new {@code class} file according
* to a {@link ClassTransform}. The transform will receive each element of
* this class, as well as a {@link ClassBuilder} for building the new class.
* The transform is free to preserve, remove, or replace elements as it
* sees fit.
*
* @apiNote
* This is named {@code transformClass} instead of {@code transform} for
* consistency with {@link ClassBuilder#transformField}, {@link
* ClassBuilder#transformMethod}, and {@link MethodBuilder#transformCode},
* and to distinguish from {@link ClassFileBuilder#transform}, which is
* more generic and powerful.
*
* @param model the class model to transform
* @param newClassName new class name
* @param transform the transform
* @return the bytes of the new class
* @throws IllegalArgumentException if building encounters a failure
* @see ConstantPoolSharingOption
*/
default byte[] transformClass(ClassModel model, ClassDesc newClassName, ClassTransform transform) {
return transformClass(model, TemporaryConstantPool.INSTANCE.classEntry(newClassName), transform);
}
/**
* Transform one {@code class} file into a new {@code class} file according
* to a {@link ClassTransform}. The transform will receive each element of
* this class, as well as a {@link ClassBuilder} for building the new class.
* The transform is free to preserve, remove, or replace elements as it
* sees fit.
*
* This method behaves as if:
* {@snippet lang=java :
* ConstantPoolBuilder cpb = null; // @replace substring=null; replacement=...
* this.build(newClassName, cpb, clb -> clb.transform(model, transform));
* }
* where {@code cpb} is determined by {@link ConstantPoolSharingOption}.
*
* @apiNote
* This is named {@code transformClass} instead of {@code transform} for
* consistency with {@link ClassBuilder#transformField}, {@link
* ClassBuilder#transformMethod}, and {@link MethodBuilder#transformCode},
* and to distinguish from {@link ClassFileBuilder#transform}, which is
* more generic and powerful.
*
* @param model the class model to transform
* @param newClassName new class name
* @param transform the transform
* @return the bytes of the new class
* @throws IllegalArgumentException if building encounters a failure
* @see ConstantPoolSharingOption
*/
byte[] transformClass(ClassModel model, ClassEntry newClassName, ClassTransform transform);
/**
* Verify a {@code class} file. All verification errors found will be returned.
*
* @param model the class model to verify
* @return a list of verification errors, or an empty list if no error is
* found
*/
List