Пакет java.lang.classfile
Предоставляет библиотеку для анализа, генерации и преобразования файлов классов.
Пакетjava.lang.classfile содержит модели API для чтения, записи и изменения файлов классов Java, как указано в главе 4 Спецификации виртуальной машины Java. Этот пакет, java.lang.classfile.attribute, java.lang.classfile.constantpool и java.lang.classfile.instruction образуют API файлов классов. Чтение файлов классов
Основным классом для чтения файлов классов являетсяClassModel; мы преобразуем байты в ClassModel с помощью ClassFile.parse(byte[]): ClassModel cm = ClassFile.of().parse(bytes);
parse, позволяющих задать различные параметры обработки. ClassModel — это неизменяемое описание файла класса. Он предоставляет методы доступа к метаданным класса (например, ClassModel.thisClass(), ClassModel.flags()), а также к подчинённым сущностям файла класса (ClassModel.fields(), AttributedElement.attributes()). Объект ClassModel создаётся по требованию; большая часть файла класса анализируется только при фактической необходимости. Из-за ленивой обработки эти модели могут быть небезопасны для использования в нескольких потоках. Кроме того, вызовы методов доступа к моделям могут привести к IllegalArgumentException из-за некорректного формата файла
class, поскольку анализ выполняется по требованию.
Перечислить имена полей и методов класса можно следующим образом:
ClassModel cm = ClassFile.of().parse(bytes);
for (FieldModel fm : cm.fields())
System.out.printf("Field %s%n", fm.fieldName().stringValue());
for (MethodModel mm : cm.methods())
System.out.printf("Method %s%n", mm.methodName().stringValue());
При перечислении методов для каждого из них мы получаем MethodModel; как и ClassModel, он предоставляет доступ к метаданным метода и позволяет перейти к подчинённым сущностям, например к байт-коду тела метода. Таким образом, ClassModel является корнем дерева с дочерними элементами, представляющими поля, методы и атрибуты, а у MethodModel, в свою очередь, есть собственные дочерние элементы (атрибуты, CodeModel и т. д.)
Такие методы, как ClassModel.methods(), позволяют явно обходить структуру класса и сразу переходить к интересующим нас частям. Это полезно для некоторых видов анализа, однако, если требуется обработать весь файл класса, может понадобиться более организованный подход. ClassModel также предоставляет представление файла класса в виде последовательности элементов класса, которая может включать методы, поля, атрибуты и другие элементы, различаемые с помощью сопоставления с образцом. Приведённый выше пример можно переписать так:
ClassModel cm = ClassFile.of().parse(bytes);
for (ClassElement ce : cm) {
switch (ce) {
case MethodModel mm -> System.out.printf("Method %s%n", mm.methodName().stringValue());
case FieldModel fm -> System.out.printf("Field %s%n", fm.fieldName().stringValue());
default -> { }
}
}
Модели, возвращаемые в качестве элементов при обходе ClassModel, сами могут быть источниками элементов. Если требуется пройти по файлу класса и перечислить все классы, к полям и методам которых мы обращаемся, можно выделить элементы класса, описывающие методы, затем — элементы методов, описывающие атрибут кода, и, наконец, элементы кода, описывающие инструкции доступа к полям и вызова:
ClassModel cm = ClassFile.of().parse(bytes);
Set<ClassDesc> dependencies = new HashSet<>();
for (ClassElement ce : cm) {
if (ce instanceof MethodModel mm) {
for (MethodElement me : mm) {
if (me instanceof CodeModel xm) {
for (CodeElement e : xm) {
switch (e) {
case InvokeInstruction i -> dependencies.add(i.owner().asSymbol());
case FieldInstruction i -> dependencies.add(i.owner().asSymbol());
default -> { }
}
}
}
}
}
}
Тот же запрос можно обработать как конвейер потоковой обработки элементов класса:
ClassModel cm = ClassFile.of().parse(bytes);
Set<ClassDesc> dependencies =
cm.elementStream()
.flatMap(ce -> ce instanceof MethodModel mm ? mm.elementStream() : Stream.empty())
.flatMap(me -> me instanceof CodeModel com ? com.elementStream() : Stream.empty())
.<ClassDesc>mapMulti((xe, c) -> {
switch (xe) {
case InvokeInstruction i -> c.accept(i.owner().asSymbol());
case FieldInstruction i -> c.accept(i.owner().asSymbol());
default -> { }
}
})
.collect(toSet());
Модели и элементы
Представление файлов классов в этом API строится на понятиях моделей и элементов. Модели представляют сложные структуры, такие как классы, методы, поля, элементы записей или тело кода метода. Изучать модели можно как с помощью навигации с произвольным доступом (например, через метод доступаClassModel.methods()), так и в виде линейной последовательности элементов. (Элементы также могут быть моделями: FieldModel является также элементом класса.) Для каждого типа модели (например, MethodModel) существует соответствующий тип элемента (MethodElement). Модели и элементы неизменяемы и создаются по требованию, поэтому создание модели не обязательно требует обработки всего её содержимого. Пул констант
Большая часть интересного содержимого файла класса находится в пуле констант.ClassModel предоставляет доступное только для чтения представление пула констант, создаваемое по требованию, через ClassModel.constantPool(). Описания содержимого файла класса часто представлены различными подтипами PoolEntry, например ClassEntry или Utf8Entry. Записи пула констант также доступны через модели и элементы; в приведённом выше примере обхода элемент InvokeInstruction предоставлял метод для owner, соответствующей записи
Constant_Class_info в пуле констант.
Атрибуты
Значительная часть содержимого файла класса хранится в атрибутах; атрибуты встречаются у классов, методов, полей, компонентов записей и у атрибутаCode. Большинство атрибутов представлены как элементы; например, SignatureAttribute является ClassElement, MethodElement и FieldElement, поскольку может присутствовать во всех этих местах и включается при переборе элементов соответствующей модели. Некоторые атрибуты не представлены как элементы; это атрибуты, тесно связанные с другими частями файла класса и логически являющиеся их частью. К ним относятся атрибуты BootstrapMethods, LineNumberTable,
StackMapTable, LocalVariableTable и
LocalVariableTypeTable. Эти атрибуты обрабатываются библиотекой и считаются частью связанной с ними структуры (записи атрибута BootstrapMethods считаются частью пула констант; номера строк и метаданные локальных переменных моделируются как элементы CodeModel.)
Атрибут Code помимо представления в виде MethodElement является также самостоятельной моделью (CodeModel) из-за своей сложной структуры.
Для каждого стандартного атрибута предусмотрен интерфейс (в java.lang.classfile.attribute), который предоставляет содержимое атрибута и фабричные методы для его создания. Например, атрибут Signature определяется классом SignatureAttribute и предоставляет методы доступа к SignatureAttribute.signature(), а также фабричные методы, принимающие Utf8Entry или String.
Пользовательские атрибуты
Преобразование атрибутов между их представлением в файле класса и соответствующим объектным представлением выполняется с помощьюAttributeMapper.
AttributeMapper предоставляет метод AttributeMapper.readAttribute(AttributedElement, ClassReader, int) для преобразования из формата файла класса в экземпляр атрибута и метод AttributeMapper.writeAttribute(BufWriter, Attribute) для обратного преобразования в формат файла класса. Он также содержит метаданные, в том числе имя атрибута, набор сущностей файла класса, к которым применим атрибут, и сведения о том, допускается ли наличие нескольких атрибутов одного типа у одной сущности. Для каждого типа атрибута, определённого в разделе §4.7 Спецификации виртуальной машины Java, существуют встроенные сопоставители атрибутов (в Attributes), а также сопоставители для нескольких распространённых нестандартных атрибутов, используемых JDK, таких как CharacterRangeTable.
Нераспознанные атрибуты передаются как элементы типа UnknownAttribute, предоставляющие доступ только к содержимому атрибута byte[].
Для нестандартных атрибутов пользовательские сопоставители атрибутов можно указать с помощью параметра файла класса ClassFile.AttributeMapperOption.of(Function). Реализации пользовательских атрибутов должны расширять CustomAttribute.
Параметры
ClassFile.of(ClassFile.Option[]) принимает список параметров. ClassFile.Option — базовый интерфейс для некоторых статически перечисленных параметров, а также фабрик для более сложных параметров, включая:
-
ClassFile.AttributeMapperOption.of(Function)-- задаёт формат пользовательских атрибутов -
ClassFile.AttributesProcessingOption-- обработка нераспознанных или проблемных исходных атрибутов (по умолчаниюPASS_ALL_ATTRIBUTES) -
ClassFile.ClassHierarchyResolverOption.of(ClassHierarchyResolver)-- задаёт пользовательский механизм разрешения иерархии классов, используемый при генерации таблицы карт стека -
ClassFile.ConstantPoolSharingOption-- совместное использование пула констант при преобразовании (по умолчаниюSHARED_POOL) -
ClassFile.DeadCodeOption-- удаление недостижимого кода (по умолчаниюPATCH_DEAD_CODE) -
ClassFile.DeadLabelsOption-- фильтрация неразрешённых меток (по умолчаниюFAIL_ON_DEAD_LABELS) -
ClassFile.DebugElementsOption-- обработка отладочной информации, например метаданных локальных переменных (по умолчаниюPASS_DEBUG) -
ClassFile.LineNumbersOption-- обработка номеров строк (по умолчаниюPASS_LINE_NUMBERS) -
ClassFile.ShortJumpsOption-- автоматическая замена коротких переходов на длинные при необходимости (по умолчаниюFIX_SHORT_JUMPS) -
ClassFile.StackMapsOption-- генерация карт стека (по умолчаниюSTACK_MAPS_WHEN_REQUIRED)
ClassFile.AttributeMapperOption и ClassFile.ClassHierarchyResolverOption критически важны для корректного анализа и генерации файлов class. Для анализа пользовательских атрибутов требуется сопоставитель атрибутов. Корректный механизм разрешения необходим для генерации файлов class, байт-код которых ссылается на классы, недоступные системному загрузчику классов, а также в некоторых особых случаях, когда при генерации требуется избежать загрузки системных классов, например в агентах.
Большинство параметров позволяют запросить пропуск определённых частей файла класса при обходе, например отладочной информации или нераспознанных атрибутов. Некоторые параметры позволяют отключить генерацию частей файла класса, например карт стека. Многие из этих параметров позволяют находить компромисс между возможностями и производительностью: обработка отладочной информации и номеров строк требует затрат (как при записи, так и при чтении). Если эти сведения не нужны, их можно отключить с помощью параметров, чтобы повысить производительность.
Запись файлов классов
Генерация файлов классов выполняется с помощью построителей. Для каждого типа сущности, имеющего модель, существует соответствующий тип построителя; классы создаются с помощьюClassBuilder, методы — с помощью MethodBuilder и т. д. Вместо непосредственного создания построителей они передаются в качестве аргумента пользовательской лямбда-функции. Чтобы сгенерировать знакомую программу «hello world», мы запрашиваем построитель класса и используем его для создания построителей методов конструктора и метода main, а затем используем построители методов для создания атрибута Code и построители кода для генерации инструкций:
byte[] bytes = ClassFile.of().build(CD_Hello,
clb -> clb.withFlags(ClassFile.ACC_PUBLIC)
.withMethod(ConstantDescs.INIT_NAME, ConstantDescs.MTD_void,
ClassFile.ACC_PUBLIC,
mb -> mb.withCode(
cob -> cob.aload(0)
.invokespecial(ConstantDescs.CD_Object,
ConstantDescs.INIT_NAME, ConstantDescs.MTD_void)
.return_()))
.withMethod("main", MTD_void_StringArray, ClassFile.ACC_PUBLIC + ClassFile.ACC_STATIC,
mb -> mb.withCode(
cob -> cob.getstatic(CD_System, "out", CD_PrintStream)
.ldc("Hello World")
.invokevirtual(CD_PrintStream, "println", MTD_void_String)
.return_())));
Методы-упрощения ClassBuilder.buildMethodBody позволяют попросить ClassBuilder создать построители кода для непосредственного построения тел методов, минуя пользовательскую лямбда-функцию построителя метода:
byte[] bytes = ClassFile.of().build(CD_Hello,
clb -> clb.withFlags(ClassFile.ACC_PUBLIC)
.withMethodBody(ConstantDescs.INIT_NAME, ConstantDescs.MTD_void,
ClassFile.ACC_PUBLIC,
cob -> cob.aload(0)
.invokespecial(ConstantDescs.CD_Object,
ConstantDescs.INIT_NAME, ConstantDescs.MTD_void)
.return_())
.withMethodBody("main", MTD_void_StringArray, ClassFile.ACC_PUBLIC + ClassFile.ACC_STATIC,
cob -> cob.getstatic(CD_System, "out", CD_PrintStream)
.ldc("Hello World")
.invokevirtual(CD_PrintStream, "println", MTD_void_String)
.return_()));
Построители часто поддерживают несколько способов представления одной и той же сущности на разных уровнях абстракции. Например, инструкцию invokevirtual для вызова println можно было бы сгенерировать с помощью CodeBuilder.invokevirtual, CodeBuilder.invoke или CodeBuilder.with.
Метод-упрощение CodeBuilder.invokevirtual работает так, как если бы он вызывал метод-упрощение CodeBuilder.invoke, который, в свою очередь, работает так, как если бы он вызывал метод CodeBuilder.with. Такое последовательное составление вызовов методов построителя позволяет составлять преобразования (как описано далее).
Символьная информация
Для описания символьной информации о классах и типах API использует абстракции номинальных дескрипторов изjava.lang.constant, такие как ClassDesc и MethodTypeDesc, что менее подвержено ошибкам, чем использование обычных строк. Если для записи пула констант существует номинальное представление, она предоставляет метод, возвращающий соответствующий тип номинального дескриптора; например, метод ClassEntry.asSymbol() возвращает ClassDesc.
В соответствующих случаях построители предоставляют два метода для построения элемента с символьной информацией: один принимает номинальные дескрипторы, другой — записи пула констант.
Проверки согласованности, синтаксические проверки и верификация
API файлов классов выполняет проверки, чтобы убедиться, что аргументы для создания структур файла
class представимы в формате файла class. Значение аргумента, которое невозможно представить его типом данных, отклоняется с исключением IllegalArgumentException. Например, значение int не может выходить за пределы своего типа данных; List не может превышать максимально представимый размер своего табличного типа данных или содержать непредставимый элемент. Ограничения, обусловленные базовым типом данных, например упомянутые выше ограничения для int и List, указаны в соответствующих API. Если не указано иное, в любых структурах String не может превышать 65535 байт при представлении в формате модифицированного UTF-8. Если не указано иное, передача null или массива либо коллекции, содержащих null в качестве элемента, конструктору или методу любого класса или интерфейса API файлов классов приведёт к выбрасыванию исключения NullPointerException.
При построении или преобразовании файлов классов проверки согласованности не выполняются (за исключением проверок на null и представимость аргументов). Все построители и фабричные методы элементов файлов классов принимают предоставленные сведения без неявной проверки, если они представимы в формате файла class. Однако критические несоответствия (например, недопустимая последовательность кода или неразрешённые метки) влияют на работу внутренних инструментов и могут позднее привести к исключениям в процессе построения файла класса. Такие критические исключения выбрасываются как IllegalArgumentException.
Использование номинальных дескрипторов гарантирует, что библиотека API файлов классов применит правильную форму сериализации с учётом контекста. Кроме того, эти номинальные дескрипторы проверяются при создании, поэтому случайно создать их с недопустимым содержимым невозможно. В следующем примере имя класса передаётся методу ClassDesc.of(String) для проверки, а библиотека автоматически преобразует его во внутренний формат имени класса при сериализации в пул констант в виде записи класса.
var validClassEntry = constantPoolBuilder.classEntry(ClassDesc.of("mypackage.MyClass"));
С другой стороны, можно использовать методы построителей и фабрики, которые непосредственно принимают записи пула констант. Записи пула констант также можно создавать напрямую из исходных значений без дополнительных преобразований или проверок, если значения представимы. В следующем примере намеренно используется неверный формат имени класса, который применяется без проверок и преобразований.
var invalidClassEntry = constantPoolBuilder.classEntry(
constantPoolBuilder.utf8Entry("mypackage.MyClass"));
Более сложную проверку файла класса можно выполнить вызовом ClassFile.verify(ClassModel).
Преобразование файлов классов
API обработки файлов классов чаще всего используются для объединения чтения и записи при преобразовании: файл класса читается, вносятся локальные изменения, а большая его часть передаётся без изменений. Для каждого типа построителяXxxBuilder имеет метод with(XxxElement), позволяющий непосредственно передавать построителю элементы, которые требуется оставить без изменений. Если бы мы захотели удалить методы, имена которых начинаются с «debug», мы могли бы получить существующий ClassModel, построить новый файл класса, предоставив ClassBuilder, перебрать элементы исходного ClassModel и передать построителю все элементы, кроме методов, которые требуется удалить:
ClassModel classModel = ClassFile.of().parse(bytes);
byte[] newBytes = ClassFile.of().build(classModel.thisClass().asSymbol(),
classBuilder -> {
for (ClassElement ce : classModel) {
if (!(ce instanceof MethodModel mm
&& mm.methodName().stringValue().startsWith("debug"))) {
classBuilder.with(ce);
}
}
});
Это передаёт построителю каждый элемент класса, кроме соответствующих методам, имена которых начинаются с debug. Разумеется, преобразования могут быть сложнее: можно углубиться в тела методов и инструкции и преобразовать их, однако на каждом уровне повторяется одна и та же структура, поскольку для каждой сущности предусмотрены соответствующие абстракции модели, построителя и элемента.
Преобразование можно рассматривать как операцию «flatMap» над последовательностью элементов: для каждого элемента можно оставить его без изменений, удалить или заменить одним либо несколькими элементами. Поскольку преобразование — очень распространённая операция над файлами классов, для каждого типа модели существует соответствующий тип
XxxTransform (описывающий преобразование последовательности
XxxElement), а для каждого типа построителя предусмотрены методы transformYyy для преобразования его дочерних моделей. Преобразование — это простой функциональный интерфейс, принимающий построитель и элемент; его реализация применяет «flatMap» к элементам, передавая их построителю. Приведённое выше можно записать так:
ClassTransform ct = (builder, element) -> {
if (!(element instanceof MethodModel mm && mm.methodName().stringValue().startsWith("debug")))
builder.with(element);
};
var cc = ClassFile.of();
byte[] newBytes = cc.transformClass(cc.parse(bytes), ct);
Метод-упрощение ClassTransform.dropping позволяет упростить создание того же преобразования и записать приведённый выше пример так:
ClassTransform ct = ClassTransform.dropping(
element -> element instanceof MethodModel mm
&& mm.methodName().stringValue().startsWith("debug"));
Подъём преобразований
Хотя пример с использованием преобразований лишь немного короче, преимущество такого представления заключается в том, что операции преобразования легче комбинировать. Предположим, мы хотим перенаправить вызовы статических методов классаFoo на соответствующий метод класса Bar. Это можно представить как преобразование CodeElement: CodeTransform fooToBar = (b, e) -> {
if (e instanceof InvokeInstruction i
&& i.owner().name().equalsString("Foo")
&& i.opcode() == Opcode.INVOKESTATIC) {
// remove the old element i by doing nothing to the builder
// add a new invokestatic instruction to the builder
b.invokestatic(CD_Bar, i.name().stringValue(), i.typeSymbol(), i.isInterface());
} else {
b.with(e); // leaves the element in place
}
};
Затем мы можем поднять это преобразование элементов кода до преобразования элементов методов. Оно перехватывает элементы методов, соответствующие атрибуту Code, переходит к его элементам кода и применяет к ним преобразование кода, а остальные элементы методов передаёт без изменений:
MethodTransform mt = MethodTransform.transformingCode(fooToBar);
а затем поднять преобразование элементов методов до преобразования элементов классов:
ClassTransform ct = ClassTransform.transformingMethods(mt);
или сразу поднять преобразование кода до преобразования класса:
ClassTransform ct = ClassTransform.transformingMethodBodies(fooToBar);
а затем преобразовать файл класса:
var cc = ClassFile.of();
byte[] newBytes = cc.transformClass(cc.parse(bytes), ct);
Это намного лаконичнее (и менее подвержено ошибкам), чем эквивалентное преобразование, выраженное через непосредственный обход структуры файла класса:
byte[] newBytes = ClassFile.of().build(classModel.thisClass().asSymbol(),
classBuilder -> {
for (ClassElement ce : classModel) {
if (ce instanceof MethodModel mm) {
classBuilder.withMethod(mm.methodName().stringValue(), mm.methodTypeSymbol(),
mm.flags().flagsMask(),
methodBuilder -> {
for (MethodElement me : mm) {
if (me instanceof CodeModel xm) {
methodBuilder.withCode(codeBuilder -> {
for (CodeElement e : xm) {
if (e instanceof InvokeInstruction i && i.owner().asInternalName().equals("Foo")
&& i.opcode() == Opcode.INVOKESTATIC)
codeBuilder.invoke(i.opcode(), CD_Bar,
i.name().stringValue(), i.typeSymbol(), i.isInterface());
else codeBuilder.with(e);
}});
}
else
methodBuilder.with(me);
}
});
}
else
classBuilder.with(ce);
}
});
Композиция преобразований
Преобразования элементов одного типа можно последовательно объединять, передавая результат первого на вход второго. Предположим, мы хотим инструментировать все вызовы методов, выводя имя метода перед его вызовом:CodeTransform instrumentCalls = (b, e) -> {
if (e instanceof InvokeInstruction i) {
b.getstatic(CD_System, "out", CD_PrintStream)
.ldc(i.name().stringValue())
.invokevirtual(CD_PrintStream, "println", MTD_void_String);
}
b.with(e);
};
Затем можно объединить fooToBar и instrumentCalls с помощью CodeTransform.andThen(CodeTransform):
var cc = ClassFile.of();
byte[] newBytes = cc.transformClass(cc.parse(bytes),
ClassTransform.transformingMethods(
MethodTransform.transformingCode(
fooToBar.andThen(instrumentCalls))));
instrumentCalls получит все элементы кода, созданные преобразованием forToBar: как элементы кода исходного файла класса, так и замены (при которых статические вызовы Foo заменяются вызовами Bar). Совместное использование пула констант
Преобразование не ограничивается организацией чтения, преобразования элементов и записи. В большинстве случаев при преобразовании файла класса в него вносятся относительно небольшие изменения. Для оптимизации таких случаев новый файл класса при преобразовании инициализируется копией пула констант исходного файла; это позволяет значительно повысить производительность (методы и атрибуты, которые не преобразуются, можно обрабатывать путём массового копирования их байтов, а не анализа и повторной генерации содержимого). Если совместное использование пула констант нежелательно, его можно отключить с помощью параметраClassFile.ConstantPoolSharingOption. Такое отключение может быть полезным при удалении множества элементов в результате преобразования, поскольку в пуле констант останется много неиспользуемых записей. Обработка неизвестных элементов файла класса при преобразовании
Пользовательские преобразования файлов классов могут не учитывать элементы файлов классов, появившиеся в будущих выпусках JDK. Для обеспечения детерминированной стабильности преобразования файлов классов, которым необходимо обрабатывать все элементы файла класса, следует реализовывать так, чтобы они выбрасывали исключения при запуске на более новой версии JDK, если преобразуемый файл класса имеет более новую версию или если появляется новый неизвестный элемент файла класса. Например, следующие фрагменты преобразований выполняют строгую проверку совместимости:CodeTransform fooToBar = (b, e) -> {
if (ClassFile.latestMajorVersion() > ClassFile.JAVA_22_VERSION) {
throw new IllegalArgumentException("Cannot run on JDK > 22");
}
switch (e) {
case ArrayLoadInstruction i -> doSomething(b, i);
case ArrayStoreInstruction i -> doSomething(b, i);
default -> b.with(e);
}
};
ClassTransform fooToBar = (b, e) -> {
switch (e) {
case ClassFileVersion v when v.majorVersion() > ClassFile.JAVA_22_VERSION ->
throw new IllegalArgumentException("Cannot transform class file version " + v.majorVersion());
default -> doSomething(b, e);
}
};
CodeTransform fooToBar = (b, e) -> {
switch (e) {
case ArrayLoadInstruction i -> doSomething(b, i);
case ArrayStoreInstruction i -> doSomething(b, i);
case BranchInstruction i -> doSomething(b, i);
case ConstantInstruction i -> doSomething(b, i);
case ConvertInstruction i -> doSomething(b, i);
case DiscontinuedInstruction i -> doSomething(b, i);
case FieldInstruction i -> doSomething(b, i);
case InvokeDynamicInstruction i -> doSomething(b, i);
case InvokeInstruction i -> doSomething(b, i);
case LoadInstruction i -> doSomething(b, i);
case StoreInstruction i -> doSomething(b, i);
case IncrementInstruction i -> doSomething(b, i);
case LookupSwitchInstruction i -> doSomething(b, i);
case MonitorInstruction i -> doSomething(b, i);
case NewMultiArrayInstruction i -> doSomething(b, i);
case NewObjectInstruction i -> doSomething(b, i);
case NewPrimitiveArrayInstruction i -> doSomething(b, i);
case NewReferenceArrayInstruction i -> doSomething(b, i);
case NopInstruction i -> doSomething(b, i);
case OperatorInstruction i -> doSomething(b, i);
case ReturnInstruction i -> doSomething(b, i);
case StackInstruction i -> doSomething(b, i);
case TableSwitchInstruction i -> doSomething(b, i);
case ThrowInstruction i -> doSomething(b, i);
case TypeCheckInstruction i -> doSomething(b, i);
case PseudoInstruction i -> doSomething(b, i);
default ->
throw new IllegalArgumentException("An unknown instruction could not be handled by this transformation");
}
};
И наоборот, преобразования файлов классов, которым требуется обрабатывать лишь часть элементов файла класса, не должны учитывать новые неизвестные элементы и могут передавать их дальше. В следующем примере показано такое преобразование кода, совместимое с будущими версиями:
CodeTransform fooToBar = (b, e) -> {
switch (e) {
case ArrayLoadInstruction i -> doSomething(b, i);
case ArrayStoreInstruction i -> doSomething(b, i);
default -> b.with(e);
}
};
Соглашения API
API в значительной степени основан на модели данных формата файла класса, определяющей каждый вид элемента (включая модели и атрибуты) и его свойства. Для каждого вида элемента существует соответствующий интерфейс, описывающий этот элемент, и фабричные методы для его создания. Для некоторых видов элементов также предусмотрены методы-упрощения в соответствующем построителе (например, CodeBuilder.invokevirtual(ClassDesc, String, MethodTypeDesc)).
Большая часть символьной информации в элементах представлена записями пула констант (например, владелец поля представлен как ClassEntry.) Фабричные методы и построители также принимают номинальные дескрипторы из java.lang.constant (например, ClassDesc.)
Стандартные типы данных
В главе 4 Спецификации виртуальной машины Java определено несколько стандартных типов данных в формате файлаclass. В модели API они единообразно представлены как int. Передача API значений этих типов данных, выходящих за допустимые пределы, приводит к исключению IllegalArgumentException. u1- Однобайтовое беззнаковое целое число в диапазоне
[0, 255].
См.DataInput.readUnsignedByte(). u2- Двухбайтовое беззнаковое целое число в диапазоне
[0, 65535].
Эквивалентно типу Javachar. Часто используется для полей флагов, индексов и размеров структур списков.
См.DataInput.readUnsignedShort(). u4- Четырёхбайтовое беззнаковое целое число в диапазоне
[0, 4294967295].
См.DataInput.readInt().
Модель данных
Каждый вид элемента определяется его именем, необязательным указанием кратности (ноль или больше, ноль или один, ровно один) и списком компонентов. Элементами класса являются поля, методы и атрибуты, которые могут присутствовать у классов:ClassElement =
FieldModel*(Utf8Entry name, Utf8Entry descriptor)
| MethodModel*(Utf8Entry name, Utf8Entry descriptor)
| ModuleAttribute?(int flags, ModuleEntry moduleName, Utf8Entry moduleVersion,
List<ModuleRequireInfo> requires, List<ModuleOpenInfo> opens,
List<ModuleExportInfo> exports, List<ModuleProvidesInfo> provides,
List<ClassEntry> uses)
| ModulePackagesAttribute?(List<PackageEntry> packages)
| ModuleTargetAttribute?(Utf8Entry targetPlatform)
| ModuleHashesAttribute?(Utf8Entry algorithm, List<HashInfo> hashes)
| ModuleResolutionAttribute?(int resolutionFlags)
| SourceFileAttribute?(Utf8Entry sourceFile)
| SourceDebugExtensionsAttribute?(byte[] contents)
| CompilationIDAttribute?(Utf8Entry compilationId)
| SourceIDAttribute?(Utf8Entry sourceId)
| NestHostAttribute?(ClassEntry nestHost)
| NestMembersAttribute?(List<ClassEntry> nestMembers)
| RecordAttribute?(List<RecordComponent> components)
| EnclosingMethodAttribute?(ClassEntry className, NameAndTypeEntry method)
| InnerClassesAttribute?(List<InnerClassInfo> classes)
| PermittedSubclassesAttribute?(List<ClassEntry> permittedSubclasses)
| DeclarationElement*
DeclarationElement — элементы, общие для всех объявлений (классов, методов, полей), поэтому они вынесены отдельно: DeclarationElement =
SignatureAttribute?(Utf8Entry signature)
| SyntheticAttribute?()
| DeprecatedAttribute?()
| RuntimeInvisibleAnnotationsAttribute?(List<Annotation> annotations)
| RuntimeVisibleAnnotationsAttribute?(List<Annotation> annotations)
| CustomAttribute*
| UnknownAttribute*
CodeModel (которая моделирует атрибут Code вместе с атрибутами, связанными с кодом: таблицей карт стека, таблицей локальных переменных, таблицей номеров строк и т. д.) FieldElement =
DeclarationElement
| ConstantValueAttribute?(ConstantValueEntry constant)
MethodElement =
DeclarationElement
| CodeModel?()
| AnnotationDefaultAttribute?(ElementValue defaultValue)
| MethodParametersAttribute?(List<MethodParameterInfo> parameters)
| ExceptionsAttribute?(List<ClassEntry> exceptions)
CodeModel уникальна тем, что её элементы упорядоченыCode включают обычные байткоды, а также ряд псевдоинструкций, представляющих цели переходов, метаданные номеров строк, метаданные локальных переменных и блоки catch. CodeElement = Instruction | PseudoInstruction
Instruction =
LoadInstruction(TypeKind type, int slot)
| StoreInstruction(TypeKind type, int slot)
| IncrementInstruction(int slot, int constant)
| BranchInstruction(Opcode opcode, Label target)
| LookupSwitchInstruction(Label defaultTarget, List<SwitchCase> cases)
| TableSwitchInstruction(Label defaultTarget, int low, int high,
List<SwitchCase> cases)
| ReturnInstruction(TypeKind kind)
| ThrowInstruction()
| FieldInstruction(Opcode opcode, FieldRefEntry field)
| InvokeInstruction(Opcode opcode, MemberRefEntry method, boolean isInterface)
| InvokeDynamicInstruction(InvokeDynamicEntry invokedynamic)
| NewObjectInstruction(ClassEntry className)
| NewReferenceArrayInstruction(ClassEntry componentType)
| NewPrimitiveArrayInstruction(TypeKind typeKind)
| NewMultiArrayInstruction(ClassEntry componentType, int dims)
| ArrayLoadInstruction(Opcode opcode)
| ArrayStoreInstruction(Opcode opcode)
| TypeCheckInstruction(Opcode opcode, ClassEntry className)
| ConvertInstruction(TypeKind from, TypeKind to)
| OperatorInstruction(Opcode opcode)
| ConstantInstruction(ConstantDesc constant)
| StackInstruction(Opcode opcode)
| MonitorInstruction(Opcode opcode)
| NopInstruction()
PseudoInstruction =
| LabelTarget(Label label)
| LineNumber(int line)
| ExceptionCatch(Label tryStart, Label tryEnd, Label handler, ClassEntry exception)
| LocalVariable(int slot, Utf8Entry name, Utf8Entry type, Label startScope, Label endScope)
| LocalVariableType(int slot, Utf8Entry name, Utf8Entry type, Label startScope, Label endScope)
| CharacterRange(int rangeStart, int rangeEnd, int flags, Label startScope, Label endScope)
- Начиная с:
- 24
| Класс | Описание |
|---|---|
| AccessFlags | Представляет флаги доступа класса, метода или поля. |
| Annotation | |
| AnnotationElement | |
| AnnotationValue | Представляет структуру element_value или значение пары «элемент-значение» аннотации, как определено в JVMS §4.7.16.1. |
| AnnotationValue.OfAnnotation | Представляет значение-аннотацию пары «элемент-значение». |
| AnnotationValue.OfArray | Представляет значение-массив пары «элемент-значение». |
| AnnotationValue.OfBoolean | Представляет логическое значение пары «элемент-значение». |
| AnnotationValue.OfByte | Представляет значение типа byte пары «элемент-значение». |
| AnnotationValue.OfChar | Представляет значение типа char пары «элемент-значение». |
| AnnotationValue.OfClass | Представляет значение-класс пары «элемент-значение». |
| AnnotationValue.OfConstant | Представляет значение-константу пары «элемент-значение». |
| AnnotationValue.OfDouble | Представляет значение типа double пары «элемент-значение». |
| AnnotationValue.OfEnum | Представляет значение-перечисление пары «элемент-значение». |
| AnnotationValue.OfFloat | Представляет значение типа float пары «элемент-значение». |
| AnnotationValue.OfInt | Представляет значение типа int пары «элемент-значение». |
| AnnotationValue.OfLong | Представляет значение типа long пары «элемент-значение». |
| AnnotationValue.OfShort | Представляет значение типа short пары «элемент-значение». |
| AnnotationValue.OfString | Представляет строковое значение пары «элемент-значение». |
| Attribute<A extends Attribute<A>> | Представляет атрибут (JVMS §4.7) в формате файлов class. |
| AttributedElement | ClassFileElement, описывающий структуру файла class, имеющую атрибуты, например файл class, поле, метод, атрибут Code или компонент записи. |
| AttributeMapper<A extends Attribute<A>> | Двунаправленный преобразователь между представлением атрибута в файле class и его моделью API. |
| AttributeMapper.AttributeStability | Указывает зависимость представления атрибута в файле class от данных. |
| Attributes | Преобразователи атрибутов для предопределённых (JVMS §4.7) и нестандартных атрибутов, специфичных для JDK. |
| BootstrapMethodEntry | Представляет запись в таблице методов начальной загрузки. |
| BufWriter | Расширенные средства записи файлов class для AttributeMapper. |
| ClassBuilder | Построитель файла class. |
| ClassElement | Маркерный интерфейс для элемента-члена ClassModel. |
| ClassFile | Предоставляет возможность анализировать, преобразовывать и создавать файлы class. |
| ClassFile.AttributeMapperOption | Параметр, описывающий пользовательские атрибуты для анализа файлов class. |
| ClassFile.AttributesProcessingOption | Параметр, определяющий, следует ли сохранять или отбрасывать атрибуты, корректность которых невозможно проверить после преобразования. |
| ClassFile.ClassHierarchyResolverOption | Параметр, указывающий разрешатель иерархии классов, который следует использовать при создании карт стека или проверке классов. |
| ClassFile.ConstantPoolSharingOption | Параметр, определяющий, следует ли расширять исходный пул констант при преобразовании файла class. |
| ClassFile.DeadCodeOption | Параметр, определяющий, следует ли удалять недостижимый код для создания карт стека. |
| ClassFile.DeadLabelsOption | Параметр, определяющий, следует ли фильтровать непривязанные метки и, если возможно, удалять содержащие их структуры. |
| ClassFile.DebugElementsOption | Параметр, определяющий, следует ли обрабатывать или отбрасывать отладочные PseudoInstruction при обходе CodeModel или CodeBuilder. |
| ClassFile.LineNumbersOption | Параметр, определяющий, следует ли обрабатывать или отбрасывать LineNumber при обходе CodeModel или CodeBuilder. |
| ClassFile.Option | Параметр, влияющий на анализ или запись файлов class. |
| ClassFile.ShortJumpsOption | Параметр, определяющий, следует ли автоматически заменять короткие переходы эквивалентными инструкциями при необходимости. |
| ClassFile.StackMapsOption | Параметр, определяющий, следует ли создавать карты стека. |
|
ClassFileBuilder<E extends ClassFileElement, B extends ClassFileBuilder<E, |
Построитель CompoundElement, принимающий элементы-члены, которые следует включить в создаваемую структуру. |
| ClassFileElement | Маркерный интерфейс для структур со специальными возможностями в формате файлов
class. |
|
ClassFileTransform<C extends ClassFileTransform<C, |
Преобразование CompoundElement путём обработки отдельных элементов-членов и передачи результатов ClassFileBuilder посредством ClassFileBuilder.transform(CompoundElement, ClassFileTransform). |
| ClassFileVersion | Представляет младший и старший номера версии файла class (JVMS §4.1). |
| ClassHierarchyResolver | Предоставляет сведения об иерархии классов для создания карт стека и проверки. |
| ClassHierarchyResolver.ClassHierarchyInfo | Сведения о разрешённом классе. |
| ClassModel | Представляет файл class. |
| ClassReader | Расширенные средства чтения файлов class для AttributeMapper. |
| ClassSignature | Представляет обобщённую сигнатуру класса или интерфейса, определённую в JVMS §4.7.9.1. |
| ClassTransform | Преобразование потоков ClassElement. |
| CodeBuilder | Построитель атрибутов Code (тел методов). |
| CodeBuilder.BlockCodeBuilder | Построитель блоков кода. |
| CodeBuilder.CatchBuilder | Построитель для добавления блоков catch. |
| CodeElement | Маркерный интерфейс для элемента-члена CodeModel. |
| CodeModel | Представляет тело метода (атрибут Code). |
| CodeTransform | Преобразование потоков CodeElement. |
| CompoundElement<E extends ClassFileElement> | Структура файла class, которую можно рассматривать как совокупность входящих в неё структур-членов. |
| CustomAttribute<T extends CustomAttribute<T>> | Представляет пользовательский атрибут в файле class. |
| FieldBuilder | Построитель полей. |
| FieldElement | Маркерный интерфейс для элемента-члена FieldModel. |
| FieldModel | Представляет поле. |
| FieldTransform | Преобразование потоков FieldElement. |
| Instruction | Представляет исполняемую инструкцию в массиве code атрибута Code метода. |
| Interfaces | Представляет интерфейсы класса (JVMS §4.1). |
| Label | Метка позиции среди инструкций тела метода. |
| MethodBuilder | Построитель методов. |
| MethodElement | Маркерный интерфейс для элемента-члена MethodModel. |
| MethodModel | Представляет метод. |
| MethodSignature | Представляет обобщённую сигнатуру метода или конструктора, определённую в JVMS §4.7.9.1. |
| MethodTransform | Преобразование потоков MethodElement. |
| Opcode | Описывает коды операций набора инструкций JVM, приведённые в JVMS §6.5. |
| Opcode.Kind | Виды кодов операций. |
| PseudoInstruction | |
| Signature | Представляет сигнатуры обобщённых типов Java, определённые в JVMS §4.7.9.1. |
| Signature.ArrayTypeSig | Представляет сигнатуру типа массива. |
| Signature.BaseTypeSig | Представляет сигнатуру примитивного типа (JLS §4.2) или void. |
| Signature.ClassTypeSig | Представляет сигнатуру типа класса или интерфейса, который может иметь параметры типа. |
| Signature.RefTypeSig | Представляет сигнатуру ссылочного типа, которым может быть класс, интерфейс, переменная типа или тип массива. |
| Signature.ThrowableSig | Маркерный интерфейс для сигнатуры типа, который можно выбросить. |
| Signature.TypeArg | Представляет аргумент типа — аргумент параметра типа. |
| Signature.TypeArg.Bounded | Представляет аргумент типа с явно заданной границей типа. |
| Signature.TypeArg.Bounded.WildcardIndicator | Представляет обозначение типа-аргумента с подстановочным знаком. |
| Signature.TypeArg.Unbounded | Представляет аргумент типа с неограниченным подстановочным знаком * или
? в программах Java. |
| Signature.TypeParam | Представляет сигнатуру параметра типа обобщённого класса, интерфейса, метода или конструктора, который вводит переменную типа. |
| Signature.TypeVarSig | Представляет сигнатуру переменной типа. |
| Superclass | Представляет суперкласс класса (JVMS §4.1). |
| TypeAnnotation | Представляет структуру type_annotation (JVMS §4.7.20). |
| TypeAnnotation.CatchTarget | Указывает, что аннотация присутствует на типе с индексом i в объявлении параметра исключения. |
| TypeAnnotation.EmptyTarget | Указывает, что аннотация присутствует на типе в объявлении поля, возвращаемом типе метода, типе вновь созданного объекта или типе-получателе метода или конструктора. |
| TypeAnnotation.FormalParameterTarget | Указывает, что аннотация присутствует на типе в объявлении формального параметра метода, конструктора или лямбда-выражения. |
| TypeAnnotation.LocalVarTarget | Указывает, что аннотация присутствует на типе в объявлении локальной переменной, включая переменную, объявленную как ресурс в операторе try-with-resources. |
| TypeAnnotation.LocalVarTargetInfo | Указывает диапазон смещений в массиве кода, в пределах которого локальная переменная имеет значение, а также индекс этой переменной в массиве локальных переменных текущего кадра. |
| TypeAnnotation.OffsetTarget | Указывает, что аннотация присутствует на типе в выражении instanceof или выражении создания объекта new либо на типе перед :: в выражении ссылки на метод. |
| TypeAnnotation.SupertypeTarget | Указывает, что аннотация присутствует на типе в предложении extends или implements объявления класса или интерфейса. |
| TypeAnnotation.TargetInfo | Указывает, какой тип в объявлении или выражении аннотируется. |
| TypeAnnotation.TargetType | Вид цели, на которой расположена аннотация, как определено в JVMS §4.7.20.1. |
| TypeAnnotation.ThrowsTarget | Указывает, что аннотация присутствует на типе с индексом i в предложении throws объявления метода или конструктора. |
| TypeAnnotation.TypeArgumentTarget | Указывает, что аннотация присутствует либо на типе с индексом i в выражении приведения типа, либо на аргументе типа с индексом i в явном списке аргументов типа для одного из следующих элементов: выражения создания объекта new, оператора явного вызова конструктора, выражения вызова метода или выражения ссылки на метод. |
| TypeAnnotation.TypeParameterBoundTarget | Указывает, что аннотация присутствует на границе с индексом i параметра типа с индексом j в объявлении обобщённого класса, интерфейса, метода или конструктора. |
| TypeAnnotation.TypeParameterTarget | Указывает, что аннотация присутствует в объявлении параметра типа с индексом i обобщённого класса, обобщённого интерфейса, обобщённого метода или обобщённого конструктора. |
| TypeAnnotation.TypePathComponent | JVMS: структура Type_path определяет, какая часть типа аннотирована, как указано в JVMS §4.7.20.2
|
| TypeAnnotation.TypePathComponent.Kind | Вид пути к типу, как определено в JVMS §4.7.20.2
|
| TypeKind | Описывает типы данных, с которыми работает виртуальная машина Java. |
© 1993, 2025, Oracle and/or its affiliates. All rights reserved.
Documentation extracted from Debian's OpenJDK Development Kit package.
Licensed under the GNU General Public License, version 2, with the Classpath Exception.
Various third party code in OpenJDK is licensed under different licenses (see Debian package).
Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.