mirror of
https://github.com/openjdk/jdk.git
synced 2026-02-02 22:48:35 +00:00
8354949: JFR: Split up the EventInstrumentation class
Reviewed-by: mgronlun, liach
This commit is contained in:
parent
82c249446f
commit
ef0cd1823d
361
src/jdk.jfr/share/classes/jdk/jfr/internal/ClassInspector.java
Normal file
361
src/jdk.jfr/share/classes/jdk/jfr/internal/ClassInspector.java
Normal file
@ -0,0 +1,361 @@
|
||||
/*
|
||||
* Copyright (c) 2016, 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 jdk.jfr.internal;
|
||||
|
||||
import static jdk.jfr.internal.util.Bytecode.classDesc;
|
||||
|
||||
import java.lang.classfile.Annotation;
|
||||
import java.lang.classfile.AnnotationElement;
|
||||
import java.lang.classfile.AnnotationValue;
|
||||
import java.lang.classfile.Attribute;
|
||||
import java.lang.classfile.ClassFile;
|
||||
import java.lang.classfile.ClassModel;
|
||||
import java.lang.classfile.FieldModel;
|
||||
import java.lang.classfile.MethodModel;
|
||||
import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute;
|
||||
import java.lang.constant.ClassDesc;
|
||||
import java.lang.constant.MethodTypeDesc;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.constant.ConstantDescs;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import jdk.jfr.Enabled;
|
||||
import jdk.jfr.Name;
|
||||
import jdk.jfr.Registered;
|
||||
import jdk.jfr.SettingControl;
|
||||
import jdk.jfr.SettingDefinition;
|
||||
import jdk.jfr.internal.util.Bytecode;
|
||||
import jdk.jfr.internal.util.ImplicitFields;
|
||||
import jdk.jfr.internal.util.Bytecode.FieldDesc;
|
||||
import jdk.jfr.internal.util.Bytecode.MethodDesc;
|
||||
import jdk.jfr.internal.util.Bytecode.SettingDesc;
|
||||
import jdk.jfr.internal.util.Utils;
|
||||
|
||||
final class ClassInspector {
|
||||
private static final ClassDesc TYPE_SETTING_DEFINITION = Bytecode.classDesc(SettingDefinition.class);
|
||||
private static final ClassDesc ANNOTATION_REGISTERED = classDesc(Registered.class);
|
||||
private static final ClassDesc ANNOTATION_NAME = classDesc(Name.class);
|
||||
private static final ClassDesc ANNOTATION_ENABLED = classDesc(Enabled.class);
|
||||
private static final ClassDesc ANNOTATION_REMOVE_FIELDS = classDesc(RemoveFields.class);
|
||||
|
||||
private final ClassModel classModel;
|
||||
private final Class<?> superClass;
|
||||
private final boolean isJDK;
|
||||
private final ImplicitFields implicitFields;
|
||||
private final List<SettingDesc> settingsDescs = new ArrayList<>();
|
||||
private final List<FieldDesc> fieldDescs = new ArrayList<>();
|
||||
private final String className;
|
||||
|
||||
ClassInspector(Class<?> superClass, byte[] bytes, boolean isJDK) {
|
||||
this.superClass = superClass;
|
||||
this.classModel = ClassFile.of().parse(bytes);
|
||||
this.isJDK = isJDK;
|
||||
this.className = classModel.thisClass().asInternalName().replace("/", ".");
|
||||
this.implicitFields = determineImplicitFields();
|
||||
}
|
||||
|
||||
String getClassName() {
|
||||
return className;
|
||||
}
|
||||
|
||||
MethodDesc findStaticCommitMethod() {
|
||||
if (!isJDK) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("(");
|
||||
for (FieldDesc field : fieldDescs) {
|
||||
sb.append(field.type().descriptorString());
|
||||
}
|
||||
sb.append(")V");
|
||||
MethodDesc m = MethodDesc.of("commit", sb.toString());
|
||||
for (MethodModel method : classModel.methods()) {
|
||||
if (m.matches(method)) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String getEventName() {
|
||||
String name = annotationValue(ANNOTATION_NAME, String.class);
|
||||
return name == null ? getClassName() : name;
|
||||
}
|
||||
|
||||
boolean isRegistered() {
|
||||
Boolean result = annotationValue(ANNOTATION_REGISTERED, Boolean.class);
|
||||
if (result != null) {
|
||||
return result.booleanValue();
|
||||
}
|
||||
if (superClass != null) {
|
||||
Registered r = superClass.getAnnotation(Registered.class);
|
||||
if (r != null) {
|
||||
return r.value();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean isEnabled() {
|
||||
Boolean result = annotationValue(ANNOTATION_ENABLED, Boolean.class);
|
||||
if (result != null) {
|
||||
return result.booleanValue();
|
||||
}
|
||||
if (superClass != null) {
|
||||
Enabled e = superClass.getAnnotation(Enabled.class);
|
||||
if (e != null) {
|
||||
return e.value();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean hasStaticMethod(MethodDesc method) {
|
||||
for (MethodModel m : classModel.methods()) {
|
||||
if (Modifier.isStatic(m.flags().flagsMask())) {
|
||||
return method.matches(m);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static boolean isValidField(int access, ClassDesc classDesc) {
|
||||
String className = classDesc.packageName();
|
||||
if (!className.isEmpty()) {
|
||||
className = className + ".";
|
||||
}
|
||||
className += classDesc.displayName();
|
||||
return isValidField(access, className);
|
||||
}
|
||||
|
||||
static boolean isValidField(int access, String className) {
|
||||
if (Modifier.isTransient(access) || Modifier.isStatic(access)) {
|
||||
return false;
|
||||
}
|
||||
return Type.isValidJavaFieldType(className);
|
||||
}
|
||||
|
||||
List<SettingDesc> getSettings() {
|
||||
return settingsDescs;
|
||||
}
|
||||
|
||||
List<FieldDesc> getFields() {
|
||||
return fieldDescs;
|
||||
}
|
||||
|
||||
boolean hasDuration() {
|
||||
return implicitFields.hasDuration();
|
||||
}
|
||||
|
||||
boolean hasStackTrace() {
|
||||
return implicitFields.hasStackTrace();
|
||||
}
|
||||
|
||||
boolean hasEventThread() {
|
||||
return implicitFields.hasEventThread();
|
||||
}
|
||||
|
||||
ClassDesc getClassDesc() {
|
||||
return classModel.thisClass().asSymbol();
|
||||
}
|
||||
|
||||
ClassModel getClassModel() {
|
||||
return classModel;
|
||||
}
|
||||
|
||||
boolean isJDK() {
|
||||
return isJDK;
|
||||
}
|
||||
|
||||
private ImplicitFields determineImplicitFields() {
|
||||
if (isJDK) {
|
||||
Class<?> eventClass = MirrorEvents.find(isJDK, getClassName());
|
||||
if (eventClass != null) {
|
||||
return new ImplicitFields(eventClass);
|
||||
}
|
||||
}
|
||||
ImplicitFields ifs = new ImplicitFields(superClass);
|
||||
String[] value = annotationValue(ANNOTATION_REMOVE_FIELDS, String[].class);
|
||||
if (value != null) {
|
||||
ifs.removeFields(value);
|
||||
}
|
||||
return ifs;
|
||||
}
|
||||
|
||||
private List<AnnotationValue> getAnnotationValues(ClassDesc classDesc) {
|
||||
List<AnnotationValue> list = new ArrayList<>();
|
||||
for (Attribute<?> attribute: classModel.attributes()) {
|
||||
if (attribute instanceof RuntimeVisibleAnnotationsAttribute rvaa) {
|
||||
for (Annotation a : rvaa.annotations()) {
|
||||
if (a.classSymbol().equals(classDesc) && a.elements().size() == 1) {
|
||||
AnnotationElement ae = a.elements().getFirst();
|
||||
if (ae.name().equalsString("value")) {
|
||||
list.add(ae.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
// Only supports String, String[] and Boolean values
|
||||
private <T> T annotationValue(ClassDesc classDesc, Class<T> type) {
|
||||
for (AnnotationValue a : getAnnotationValues(classDesc)) {
|
||||
if (a instanceof AnnotationValue.OfBoolean ofb && type.equals(Boolean.class)) {
|
||||
Boolean b = ofb.booleanValue();
|
||||
return (T) b;
|
||||
}
|
||||
if (a instanceof AnnotationValue.OfString ofs && type.equals(String.class)) {
|
||||
String s = ofs.stringValue();
|
||||
return (T) s;
|
||||
}
|
||||
if (a instanceof AnnotationValue.OfArray ofa && type.equals(String[].class)) {
|
||||
List<AnnotationValue> list = ofa.values();
|
||||
String[] array = new String[list.size()];
|
||||
int index = 0;
|
||||
for (AnnotationValue av : list) {
|
||||
var avs = (AnnotationValue.OfString) av;
|
||||
array[index++] = avs.stringValue();
|
||||
}
|
||||
return (T) array;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void buildSettings() {
|
||||
Set<String> foundMethods = new HashSet<>();
|
||||
buildClassSettings(foundMethods);
|
||||
buildSuperClassSettings(foundMethods);
|
||||
}
|
||||
|
||||
private void buildClassSettings(Set<String> foundMethods) {
|
||||
for (MethodModel m : classModel.methods()) {
|
||||
for (Attribute<?> attribute : m.attributes()) {
|
||||
if (attribute instanceof RuntimeVisibleAnnotationsAttribute rvaa) {
|
||||
for (Annotation a : rvaa.annotations()) {
|
||||
// We can't really validate the method at this
|
||||
// stage. We would need to check that the parameter
|
||||
// is an instance of SettingControl.
|
||||
if (a.classSymbol().equals(TYPE_SETTING_DEFINITION)) {
|
||||
String name = m.methodName().stringValue();
|
||||
// Use @Name if it exists
|
||||
for (Annotation nameCandidate : rvaa.annotations()) {
|
||||
if (nameCandidate.className().equalsString(ANNOTATION_NAME.descriptorString())) {
|
||||
if (nameCandidate.elements().size() == 1) {
|
||||
AnnotationElement ae = nameCandidate.elements().getFirst();
|
||||
if (ae.name().equalsString("value")) {
|
||||
if (ae.value() instanceof AnnotationValue.OfString s) {
|
||||
name = Utils.validJavaIdentifier(s.stringValue(), name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add setting if method returns boolean and has one parameter
|
||||
MethodTypeDesc mtd = m.methodTypeSymbol();
|
||||
if (ConstantDescs.CD_boolean.equals(mtd.returnType())) {
|
||||
if (mtd.parameterList().size() == 1) {
|
||||
ClassDesc type = mtd.parameterList().getFirst();
|
||||
if (type.isClassOrInterface()) {
|
||||
String methodName = m.methodName().stringValue();
|
||||
foundMethods.add(methodName);
|
||||
settingsDescs.add(new SettingDesc(type, methodName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void buildSuperClassSettings(Set<String> foundMethods) {
|
||||
for (Class<?> c = superClass; jdk.internal.event.Event.class != c; c = c.getSuperclass()) {
|
||||
for (java.lang.reflect.Method method : c.getDeclaredMethods()) {
|
||||
if (!foundMethods.contains(method.getName())) {
|
||||
buildSettingsMethod(foundMethods, method);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void buildSettingsMethod(Set<String> foundMethods, java.lang.reflect.Method method) {
|
||||
// Skip private methods in base classes
|
||||
if (!Modifier.isPrivate(method.getModifiers())) {
|
||||
if (method.getReturnType().equals(Boolean.TYPE)) {
|
||||
if (method.getParameterCount() == 1) {
|
||||
Class<?> type = method.getParameters()[0].getType();
|
||||
if (SettingControl.class.isAssignableFrom(type)) {
|
||||
ClassDesc paramType = Bytecode.classDesc(type);
|
||||
foundMethods.add(method.getName());
|
||||
settingsDescs.add(new SettingDesc(paramType, method.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void buildFields() {
|
||||
Set<String> foundFields = new HashSet<>();
|
||||
// These two fields are added by native as 'transient' so they will be
|
||||
// ignored by the loop below.
|
||||
// The benefit of adding them manually is that we can
|
||||
// control in which order they occur and we can add @Name, @Description
|
||||
// in Java, instead of in native. It also means code for adding implicit
|
||||
// fields for native can be reused by Java.
|
||||
fieldDescs.add(ImplicitFields.FIELD_START_TIME);
|
||||
if (implicitFields.hasDuration()) {
|
||||
fieldDescs.add(ImplicitFields.FIELD_DURATION);
|
||||
}
|
||||
for (FieldModel field : classModel.fields()) {
|
||||
if (!foundFields.contains(field.fieldName().stringValue()) && isValidField(field.flags().flagsMask(), field.fieldTypeSymbol())) {
|
||||
fieldDescs.add(FieldDesc.of(field.fieldTypeSymbol(), field.fieldName().stringValue()));
|
||||
foundFields.add(field.fieldName().stringValue());
|
||||
}
|
||||
}
|
||||
for (Class<?> c = superClass; jdk.internal.event.Event.class != c; c = c.getSuperclass()) {
|
||||
for (Field field : c.getDeclaredFields()) {
|
||||
// Skip private fields in base classes
|
||||
if (!Modifier.isPrivate(field.getModifiers())) {
|
||||
if (isValidField(field.getModifiers(), field.getType().getName())) {
|
||||
String fieldName = field.getName();
|
||||
if (!foundFields.contains(fieldName)) {
|
||||
fieldDescs.add(FieldDesc.of(field.getType(), fieldName));
|
||||
foundFields.add(fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -25,71 +25,49 @@
|
||||
|
||||
package jdk.jfr.internal;
|
||||
|
||||
import java.lang.constant.ClassDesc;
|
||||
import java.lang.constant.MethodTypeDesc;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import static jdk.jfr.internal.util.Bytecode.classDesc;
|
||||
import static jdk.jfr.internal.util.Bytecode.getfield;
|
||||
import static jdk.jfr.internal.util.Bytecode.invokestatic;
|
||||
import static jdk.jfr.internal.util.Bytecode.invokevirtual;
|
||||
import static jdk.jfr.internal.util.Bytecode.putfield;
|
||||
|
||||
import java.lang.classfile.Annotation;
|
||||
import java.lang.classfile.AnnotationElement;
|
||||
import java.lang.classfile.AnnotationValue;
|
||||
import java.lang.classfile.ClassBuilder;
|
||||
import java.lang.classfile.ClassElement;
|
||||
import java.lang.classfile.ClassModel;
|
||||
import java.lang.classfile.ClassFile;
|
||||
import java.lang.classfile.CodeBuilder;
|
||||
import java.lang.classfile.CodeBuilder.BlockCodeBuilder;
|
||||
import java.lang.classfile.FieldModel;
|
||||
import java.lang.classfile.Label;
|
||||
import java.lang.classfile.MethodModel;
|
||||
import java.lang.classfile.MethodTransform;
|
||||
import java.lang.classfile.TypeKind;
|
||||
import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute;
|
||||
import java.lang.constant.ClassDesc;
|
||||
import java.lang.constant.MethodTypeDesc;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import jdk.jfr.Event;
|
||||
import jdk.jfr.SettingControl;
|
||||
import jdk.jfr.internal.event.EventConfiguration;
|
||||
import jdk.jfr.internal.event.EventWriter;
|
||||
import jdk.jfr.Enabled;
|
||||
import jdk.jfr.Event;
|
||||
import jdk.jfr.Name;
|
||||
import jdk.jfr.Registered;
|
||||
import jdk.jfr.SettingControl;
|
||||
import jdk.jfr.SettingDefinition;
|
||||
import jdk.jfr.internal.util.Utils;
|
||||
import jdk.jfr.internal.util.Bytecode;
|
||||
import jdk.jfr.internal.util.ImplicitFields;
|
||||
import jdk.jfr.internal.util.Bytecode.FieldDesc;
|
||||
import jdk.jfr.internal.util.Bytecode.MethodDesc;
|
||||
import static jdk.jfr.internal.util.Bytecode.invokevirtual;
|
||||
import static jdk.jfr.internal.util.Bytecode.invokestatic;
|
||||
import static jdk.jfr.internal.util.Bytecode.getfield;
|
||||
import static jdk.jfr.internal.util.Bytecode.putfield;
|
||||
import static jdk.jfr.internal.util.Bytecode.classDesc;
|
||||
import jdk.jfr.internal.util.Bytecode.SettingDesc;
|
||||
import jdk.jfr.internal.util.ImplicitFields;
|
||||
|
||||
/**
|
||||
* Class responsible for adding instrumentation to a subclass of {@link Event}.
|
||||
*
|
||||
*/
|
||||
final class EventInstrumentation {
|
||||
private static final FieldDesc FIELD_EVENT_CONFIGURATION = FieldDesc.of(Object.class, "eventConfiguration");
|
||||
|
||||
private record SettingDesc(ClassDesc paramType, String methodName) {
|
||||
}
|
||||
|
||||
private static final FieldDesc FIELD_DURATION = FieldDesc.of(long.class, ImplicitFields.DURATION);
|
||||
private static final FieldDesc FIELD_EVENT_CONFIGURATION = FieldDesc.of(Object.class, "eventConfiguration");;
|
||||
private static final FieldDesc FIELD_START_TIME = FieldDesc.of(long.class, ImplicitFields.START_TIME);
|
||||
private static final ClassDesc ANNOTATION_ENABLED = classDesc(Enabled.class);
|
||||
private static final ClassDesc ANNOTATION_NAME = classDesc(Name.class);
|
||||
private static final ClassDesc ANNOTATION_REGISTERED = classDesc(Registered.class);
|
||||
private static final ClassDesc ANNOTATION_REMOVE_FIELDS = classDesc(RemoveFields.class);
|
||||
private static final ClassDesc TYPE_EVENT_CONFIGURATION = classDesc(EventConfiguration.class);
|
||||
private static final ClassDesc TYPE_ISE = Bytecode.classDesc(IllegalStateException.class);
|
||||
private static final ClassDesc TYPE_ISE = classDesc(IllegalStateException.class);
|
||||
private static final ClassDesc TYPE_EVENT_WRITER = classDesc(EventWriter.class);
|
||||
private static final ClassDesc TYPE_OBJECT = Bytecode.classDesc(Object.class);
|
||||
private static final ClassDesc TYPE_SETTING_DEFINITION = Bytecode.classDesc(SettingDefinition.class);
|
||||
private static final ClassDesc TYPE_OBJECT = classDesc(Object.class);
|
||||
|
||||
private static final MethodDesc METHOD_BEGIN = MethodDesc.of("begin", "()V");
|
||||
private static final MethodDesc METHOD_COMMIT = MethodDesc.of("commit", "()V");
|
||||
private static final MethodDesc METHOD_DURATION = MethodDesc.of("duration", "(J)J");
|
||||
@ -104,301 +82,234 @@ final class EventInstrumentation {
|
||||
private static final MethodDesc METHOD_SHOULD_COMMIT_LONG = MethodDesc.of("shouldCommit", "(J)Z");
|
||||
private static final MethodDesc METHOD_TIME_STAMP = MethodDesc.of("timestamp", "()J");
|
||||
|
||||
private final ClassModel classModel;
|
||||
private final List<SettingDesc> settingDescs;
|
||||
private final List<FieldDesc> fieldDescs;;
|
||||
private final String eventName;
|
||||
private final String className;
|
||||
private final Class<?> superClass;
|
||||
private final boolean untypedEventConfiguration;
|
||||
private final MethodDesc staticCommitMethod;
|
||||
private final ClassInspector inspector;
|
||||
private final long eventTypeId;
|
||||
private final ClassDesc eventClassDesc;
|
||||
private final MethodDesc staticCommitMethod;
|
||||
private final boolean untypedEventConfiguration;
|
||||
private final boolean guardEventConfiguration;
|
||||
private final boolean isJDK;
|
||||
private final Map<MethodDesc, Consumer<CodeBuilder>> methodUpdates = new LinkedHashMap<>();
|
||||
private final ImplicitFields implicitFields;
|
||||
|
||||
EventInstrumentation(Class<?> superClass, byte[] bytes, long id, boolean isJDK, boolean guardEventConfiguration) {
|
||||
/**
|
||||
* Creates an EventInstrumentation object.
|
||||
*
|
||||
* @param inspector class inspector
|
||||
* @param id the event type ID to use
|
||||
* @param guardEventConfiguration guard against event configuration being null.
|
||||
* Needed when instrumentation is added before
|
||||
* registration (bytesForEagerInstrumentation)
|
||||
*/
|
||||
EventInstrumentation(ClassInspector inspector, long id, boolean guardEventConfiguration) {
|
||||
inspector.buildFields();
|
||||
if (!inspector.isJDK()) {
|
||||
// Only user-defined events have custom settings.
|
||||
inspector.buildSettings();
|
||||
}
|
||||
this.inspector = inspector;
|
||||
this.eventTypeId = id;
|
||||
this.superClass = superClass;
|
||||
this.isJDK = isJDK;
|
||||
this.classModel = createClassModel(bytes);
|
||||
this.className = classModel.thisClass().asInternalName().replace("/", ".");
|
||||
String name = annotationValue(classModel, ANNOTATION_NAME, String.class);
|
||||
this.eventName = name == null ? className : name;
|
||||
this.implicitFields = determineImplicitFields();
|
||||
this.settingDescs = buildSettingDescs(superClass, classModel);
|
||||
this.fieldDescs = buildFieldDescs(superClass, classModel);
|
||||
this.staticCommitMethod = isJDK ? findStaticCommitMethod(classModel, fieldDescs) : null;
|
||||
this.untypedEventConfiguration = hasUntypedConfiguration();
|
||||
// Corner case when we are forced to generate bytecode
|
||||
// (bytesForEagerInstrumentation)
|
||||
// We can't reference EventConfiguration::isEnabled() before event class has
|
||||
// been registered,
|
||||
// so we add a guard against a null reference.
|
||||
this.guardEventConfiguration = guardEventConfiguration;
|
||||
this.eventClassDesc = inspector.getClassDesc();
|
||||
this.staticCommitMethod = inspector.findStaticCommitMethod();
|
||||
this.untypedEventConfiguration = hasUntypedConfiguration();
|
||||
}
|
||||
|
||||
private ImplicitFields determineImplicitFields() {
|
||||
if (isJDK) {
|
||||
Class<?> eventClass = MirrorEvents.find(isJDK, className);
|
||||
if (eventClass != null) {
|
||||
return new ImplicitFields(eventClass);
|
||||
byte[] buildInstrumented() {
|
||||
return ClassFile.of().transformClass(inspector.getClassModel(), this::transform);
|
||||
}
|
||||
|
||||
private void transform(ClassBuilder clb, ClassElement cle) {
|
||||
if (cle instanceof MethodModel method && instrumentable(method) instanceof Consumer<CodeBuilder> modification) {
|
||||
clb.transformMethod(method, MethodTransform.transformingCode((codeBuilder, _) -> modification.accept(codeBuilder)));
|
||||
} else {
|
||||
clb.with(cle);
|
||||
}
|
||||
}
|
||||
|
||||
private Consumer<CodeBuilder> instrumentable(MethodModel method) {
|
||||
if (isMethod(method, METHOD_IS_ENABLED)) {
|
||||
return this::methodIsEnabled;
|
||||
}
|
||||
if (isMethod(method, METHOD_BEGIN)) {
|
||||
return this::methodBegin;
|
||||
}
|
||||
if (isMethod(method, METHOD_END)) {
|
||||
return this::methodEnd;
|
||||
}
|
||||
if (isMethod(method, METHOD_EVENT_SHOULD_COMMIT)) {
|
||||
return this::methodShouldCommit;
|
||||
}
|
||||
if (staticCommitMethod == null && isMethod(method, METHOD_COMMIT)) {
|
||||
return this::methodCommit;
|
||||
}
|
||||
if (inspector.isJDK() && isStatic(method)) {
|
||||
if (isMethod(method, METHOD_ENABLED)) {
|
||||
return this::methodEnabledStatic;
|
||||
}
|
||||
}
|
||||
ImplicitFields ifs = new ImplicitFields(superClass);
|
||||
String[] value = annotationValue(classModel, ANNOTATION_REMOVE_FIELDS, String[].class);
|
||||
if (value != null) {
|
||||
ifs.removeFields(value);
|
||||
}
|
||||
return ifs;
|
||||
}
|
||||
|
||||
static MethodDesc findStaticCommitMethod(ClassModel classModel, List<FieldDesc> fields) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("(");
|
||||
for (FieldDesc field : fields) {
|
||||
sb.append(field.type().descriptorString());
|
||||
}
|
||||
sb.append(")V");
|
||||
MethodDesc m = MethodDesc.of("commit", sb.toString());
|
||||
for (MethodModel method : classModel.methods()) {
|
||||
String d = method.methodTypeSymbol().descriptorString();
|
||||
if (method.methodName().equalsString("commit") && m.descriptor().descriptorString().equals(d)) {
|
||||
return m;
|
||||
if (isMethod(method, METHOD_SHOULD_COMMIT_LONG)) {
|
||||
return this::methodShouldCommitStatic;
|
||||
}
|
||||
if (isMethod(method, METHOD_TIME_STAMP)) {
|
||||
return this::methodTimestamp;
|
||||
}
|
||||
if (staticCommitMethod != null && isMethod(method, staticCommitMethod)) {
|
||||
return this::methodCommit;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean hasUntypedConfiguration() {
|
||||
for (FieldModel f : classModel.fields()) {
|
||||
if (f.fieldName().equalsString(FIELD_EVENT_CONFIGURATION.name())) {
|
||||
return f.fieldType().equalsString(TYPE_OBJECT.descriptorString());
|
||||
}
|
||||
}
|
||||
throw new InternalError("Class missing configuration field");
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return classModel.thisClass().asInternalName().replace("/", ".");
|
||||
}
|
||||
|
||||
private ClassModel createClassModel(byte[] bytes) {
|
||||
return ClassFile.of().parse(bytes);
|
||||
}
|
||||
|
||||
boolean isRegistered() {
|
||||
Boolean result = annotationValue(classModel, ANNOTATION_REGISTERED, Boolean.class);
|
||||
if (result != null) {
|
||||
return result.booleanValue();
|
||||
}
|
||||
if (superClass != null) {
|
||||
Registered r = superClass.getAnnotation(Registered.class);
|
||||
if (r != null) {
|
||||
return r.value();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean isEnabled() {
|
||||
Boolean result = annotationValue(classModel, ANNOTATION_ENABLED, Boolean.class);
|
||||
if (result != null) {
|
||||
return result.booleanValue();
|
||||
}
|
||||
if (superClass != null) {
|
||||
Enabled e = superClass.getAnnotation(Enabled.class);
|
||||
if (e != null) {
|
||||
return e.value();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
// Only supports String, String[] and Boolean values
|
||||
private static <T> T annotationValue(ClassModel classModel, ClassDesc classDesc, Class<T> type) {
|
||||
String typeDescriptor = classDesc.descriptorString();
|
||||
for (ClassElement ce : classModel) {
|
||||
if (ce instanceof RuntimeVisibleAnnotationsAttribute rvaa) {
|
||||
for (Annotation a : rvaa.annotations()) {
|
||||
if (a.className().equalsString(typeDescriptor)) {
|
||||
if (a.elements().size() == 1) {
|
||||
AnnotationElement ae = a.elements().getFirst();
|
||||
if (ae.name().equalsString("value")) {
|
||||
if (ae.value() instanceof AnnotationValue.OfBoolean ofb && type.equals(Boolean.class)) {
|
||||
Boolean b = ofb.booleanValue();
|
||||
return (T)b;
|
||||
}
|
||||
if (ae.value() instanceof AnnotationValue.OfString ofs && type.equals(String.class)) {
|
||||
String s = ofs.stringValue();
|
||||
return (T)s;
|
||||
}
|
||||
if (ae.value() instanceof AnnotationValue.OfArray ofa && type.equals(String[].class)) {
|
||||
List<AnnotationValue> list = ofa.values();
|
||||
String[] array = new String[list.size()];
|
||||
int index = 0;
|
||||
for (AnnotationValue av : list) {
|
||||
var avs = (AnnotationValue.OfString)av;
|
||||
array[index++] = avs.stringValue();
|
||||
}
|
||||
return (T)array;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<SettingDesc> buildSettingDescs(Class<?> superClass, ClassModel classModel) {
|
||||
Set<String> methodSet = new HashSet<>();
|
||||
List<SettingDesc> settingDescs = new ArrayList<>();
|
||||
for (MethodModel m : classModel.methods()) {
|
||||
for (var me : m) {
|
||||
if (me instanceof RuntimeVisibleAnnotationsAttribute rvaa) {
|
||||
for (Annotation a : rvaa.annotations()) {
|
||||
// We can't really validate the method at this
|
||||
// stage. We would need to check that the parameter
|
||||
// is an instance of SettingControl.
|
||||
if (a.className().equalsString(TYPE_SETTING_DEFINITION.descriptorString())) {
|
||||
String name = m.methodName().stringValue();
|
||||
// Use @Name if it exists
|
||||
for (Annotation nameCandidate : rvaa.annotations()) {
|
||||
if (nameCandidate.className().equalsString(ANNOTATION_NAME.descriptorString())) {
|
||||
if (nameCandidate.elements().size() == 1) {
|
||||
AnnotationElement ae = nameCandidate.elements().getFirst();
|
||||
if (ae.name().equalsString("value")) {
|
||||
if (ae.value() instanceof AnnotationValue.OfString s) {
|
||||
name = Utils.validJavaIdentifier(s.stringValue(), name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add setting if method returns boolean and has one parameter
|
||||
MethodTypeDesc mtd = m.methodTypeSymbol();
|
||||
if ("Z".equals(mtd.returnType().descriptorString())) {
|
||||
if (mtd.parameterList().size() == 1) {
|
||||
ClassDesc type = mtd.parameterList().getFirst();
|
||||
if (type.isClassOrInterface()) {
|
||||
String methodName = m.methodName().stringValue();
|
||||
methodSet.add(methodName);
|
||||
settingDescs.add(new SettingDesc(type, methodName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Class<?> c = superClass; jdk.internal.event.Event.class != c; c = c.getSuperclass()) {
|
||||
for (java.lang.reflect.Method method : c.getDeclaredMethods()) {
|
||||
if (!methodSet.contains(method.getName())) {
|
||||
// skip private method in base classes
|
||||
if (!Modifier.isPrivate(method.getModifiers())) {
|
||||
if (method.getReturnType().equals(Boolean.TYPE)) {
|
||||
if (method.getParameterCount() == 1) {
|
||||
Class<?> type = method.getParameters()[0].getType();
|
||||
if (SettingControl.class.isAssignableFrom(type)) {
|
||||
ClassDesc paramType = Bytecode.classDesc(type);
|
||||
methodSet.add(method.getName());
|
||||
settingDescs.add(new SettingDesc(paramType, method.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return settingDescs;
|
||||
}
|
||||
|
||||
private List<FieldDesc> buildFieldDescs(Class<?> superClass, ClassModel classModel) {
|
||||
Set<String> fieldSet = new HashSet<>();
|
||||
List<FieldDesc> fieldDescs = new ArrayList<>(classModel.fields().size());
|
||||
// These two fields are added by native as 'transient' so they will be
|
||||
// ignored by the loop below.
|
||||
// The benefit of adding them manually is that we can
|
||||
// control in which order they occur and we can add @Name, @Description
|
||||
// in Java, instead of in native. It also means code for adding implicit
|
||||
// fields for native can be reused by Java.
|
||||
fieldDescs.add(FIELD_START_TIME);
|
||||
if (implicitFields.hasDuration()) {
|
||||
fieldDescs.add(FIELD_DURATION);
|
||||
}
|
||||
for (FieldModel field : classModel.fields()) {
|
||||
if (!fieldSet.contains(field.fieldName().stringValue()) && isValidField(field.flags().flagsMask(), field.fieldTypeSymbol())) {
|
||||
FieldDesc fi = FieldDesc.of(field.fieldTypeSymbol(), field.fieldName().stringValue());
|
||||
fieldDescs.add(fi);
|
||||
fieldSet.add(field.fieldName().stringValue());
|
||||
}
|
||||
}
|
||||
for (Class<?> c = superClass; jdk.internal.event.Event.class != c; c = c.getSuperclass()) {
|
||||
for (Field field : c.getDeclaredFields()) {
|
||||
// skip private field in base classes
|
||||
if (!Modifier.isPrivate(field.getModifiers())) {
|
||||
if (isValidField(field.getModifiers(), field.getType().getName())) {
|
||||
String fieldName = field.getName();
|
||||
if (!fieldSet.contains(fieldName)) {
|
||||
fieldDescs.add(FieldDesc.of(field.getType(), fieldName));
|
||||
fieldSet.add(fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fieldDescs;
|
||||
}
|
||||
|
||||
public static boolean isValidField(int access, ClassDesc classDesc) {
|
||||
String className = classDesc.packageName();
|
||||
if (!className.isEmpty()) {
|
||||
className = className + ".";
|
||||
}
|
||||
className += classDesc.displayName();
|
||||
return isValidField(access, className);
|
||||
}
|
||||
|
||||
public static boolean isValidField(int access, String className) {
|
||||
if (Modifier.isTransient(access) || Modifier.isStatic(access)) {
|
||||
return false;
|
||||
}
|
||||
return Type.isValidJavaFieldType(className);
|
||||
}
|
||||
|
||||
public byte[] buildInstrumented() {
|
||||
makeInstrumented();
|
||||
return toByteArray();
|
||||
}
|
||||
|
||||
byte[] toByteArray() {
|
||||
return ClassFile.of().build(classModel.thisClass().asSymbol(), classBuilder -> {
|
||||
for (ClassElement ce : classModel) {
|
||||
boolean updated = false;
|
||||
if (ce instanceof MethodModel method) {
|
||||
Consumer<CodeBuilder> methodUpdate = findMethodUpdate(method);
|
||||
if (methodUpdate != null) {
|
||||
classBuilder.withMethod(method.methodName().stringValue(), method.methodTypeSymbol(), method.flags().flagsMask(), methodBuilder -> {
|
||||
methodBuilder.withCode(methodUpdate);
|
||||
});
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
if (!updated) {
|
||||
classBuilder.with(ce);
|
||||
}
|
||||
private void methodIsEnabled(CodeBuilder codeBuilder) {
|
||||
Label nullLabel = codeBuilder.newLabel();
|
||||
if (guardEventConfiguration) {
|
||||
getEventConfiguration(codeBuilder);
|
||||
codeBuilder.ifnull(nullLabel);
|
||||
}
|
||||
getEventConfiguration(codeBuilder);
|
||||
invokevirtual(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_IS_ENABLED);
|
||||
codeBuilder.ireturn();
|
||||
if (guardEventConfiguration) {
|
||||
codeBuilder.labelBinding(nullLabel);
|
||||
codeBuilder.iconst_0();
|
||||
codeBuilder.ireturn();
|
||||
}
|
||||
}
|
||||
|
||||
private void methodBegin(CodeBuilder codeBuilder) {
|
||||
if (!inspector.hasDuration()) {
|
||||
throwMissingDuration(codeBuilder, "begin");
|
||||
} else {
|
||||
codeBuilder.aload(0);
|
||||
invokestatic(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_TIME_STAMP);
|
||||
putfield(codeBuilder, eventClassDesc, ImplicitFields.FIELD_START_TIME);
|
||||
codeBuilder.return_();
|
||||
}
|
||||
}
|
||||
|
||||
private void methodEnd(CodeBuilder codeBuilder) {
|
||||
if (!inspector.hasDuration()) {
|
||||
throwMissingDuration(codeBuilder, "end");
|
||||
} else {
|
||||
codeBuilder.aload(0);
|
||||
codeBuilder.aload(0);
|
||||
getfield(codeBuilder, eventClassDesc, ImplicitFields.FIELD_START_TIME);
|
||||
invokestatic(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_DURATION);
|
||||
putfield(codeBuilder, eventClassDesc, ImplicitFields.FIELD_DURATION);
|
||||
codeBuilder.return_();
|
||||
}
|
||||
}
|
||||
|
||||
private void methodShouldCommit(CodeBuilder codeBuilder) {
|
||||
Label fail = codeBuilder.newLabel();
|
||||
if (guardEventConfiguration) {
|
||||
getEventConfiguration(codeBuilder);
|
||||
codeBuilder.ifnull(fail);
|
||||
}
|
||||
// if (!eventConfiguration.shouldCommit(duration) goto fail;
|
||||
getEventConfiguration(codeBuilder);
|
||||
codeBuilder.aload(0);
|
||||
getfield(codeBuilder, eventClassDesc, ImplicitFields.FIELD_DURATION);
|
||||
invokevirtual(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_EVENT_CONFIGURATION_SHOULD_COMMIT);
|
||||
codeBuilder.ifeq(fail);
|
||||
List<SettingDesc> settingDescs = inspector.getSettings();
|
||||
for (int index = 0; index < settingDescs.size(); index++) {
|
||||
SettingDesc sd = settingDescs.get(index);
|
||||
// if (!settingsMethod(eventConfiguration.settingX)) goto fail;
|
||||
codeBuilder.aload(0);
|
||||
getEventConfiguration(codeBuilder);
|
||||
codeBuilder.loadConstant(index);
|
||||
invokevirtual(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_EVENT_CONFIGURATION_GET_SETTING);
|
||||
MethodTypeDesc mdesc = MethodTypeDesc.ofDescriptor("(" + sd.paramType().descriptorString() + ")Z");
|
||||
codeBuilder.checkcast(sd.paramType());
|
||||
codeBuilder.invokevirtual(eventClassDesc, sd.methodName(), mdesc);
|
||||
codeBuilder.ifeq(fail);
|
||||
}
|
||||
// return true
|
||||
codeBuilder.iconst_1();
|
||||
codeBuilder.ireturn();
|
||||
// return false
|
||||
codeBuilder.labelBinding(fail);
|
||||
codeBuilder.iconst_0();
|
||||
codeBuilder.ireturn();
|
||||
}
|
||||
|
||||
private void methodCommit(CodeBuilder codeBuilder) {
|
||||
Label excluded = codeBuilder.newLabel();
|
||||
Label end = codeBuilder.newLabel();
|
||||
codeBuilder.trying(blockCodeBuilder -> {
|
||||
if (staticCommitMethod != null) {
|
||||
updateStaticCommit(blockCodeBuilder, excluded);
|
||||
} else {
|
||||
updateInstanceCommit(blockCodeBuilder, end, excluded);
|
||||
}
|
||||
// stack: [integer]
|
||||
// notified -> restart event write attempt
|
||||
blockCodeBuilder.ifeq(blockCodeBuilder.startLabel());
|
||||
// stack: []
|
||||
blockCodeBuilder.goto_(end);
|
||||
}, catchBuilder -> {
|
||||
catchBuilder.catchingAll(catchAllHandler -> {
|
||||
getEventWriter(catchAllHandler);
|
||||
// stack: [ex] [EW]
|
||||
catchAllHandler.dup();
|
||||
// stack: [ex] [EW] [EW]
|
||||
Label rethrow = catchAllHandler.newLabel();
|
||||
catchAllHandler.ifnull(rethrow);
|
||||
// stack: [ex] [EW]
|
||||
catchAllHandler.dup();
|
||||
// stack: [ex] [EW] [EW]
|
||||
invokevirtual(catchAllHandler, TYPE_EVENT_WRITER, METHOD_RESET);
|
||||
catchAllHandler.labelBinding(rethrow);
|
||||
// stack:[ex] [EW]
|
||||
catchAllHandler.pop();
|
||||
// stack:[ex]
|
||||
catchAllHandler.athrow();
|
||||
});
|
||||
});
|
||||
codeBuilder.labelBinding(excluded);
|
||||
// stack: [EW]
|
||||
codeBuilder.pop();
|
||||
codeBuilder.labelBinding(end);
|
||||
// stack: []
|
||||
codeBuilder.return_();
|
||||
}
|
||||
|
||||
public byte[] buildUninstrumented() {
|
||||
makeUninstrumented();
|
||||
return toByteArray();
|
||||
private void methodEnabledStatic(CodeBuilder codeBuilder) {
|
||||
Label nullLabel = codeBuilder.newLabel();
|
||||
if (guardEventConfiguration) {
|
||||
getEventConfiguration(codeBuilder);
|
||||
codeBuilder.ifnull(nullLabel);
|
||||
}
|
||||
getEventConfiguration(codeBuilder);
|
||||
invokevirtual(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_IS_ENABLED);
|
||||
codeBuilder.ireturn();
|
||||
if (guardEventConfiguration) {
|
||||
codeBuilder.labelBinding(nullLabel);
|
||||
codeBuilder.iconst_0();
|
||||
codeBuilder.ireturn();
|
||||
}
|
||||
}
|
||||
|
||||
private void methodTimestamp(CodeBuilder codeBuilder) {
|
||||
invokestatic(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_TIME_STAMP);
|
||||
codeBuilder.lreturn();
|
||||
}
|
||||
|
||||
private void methodShouldCommitStatic(CodeBuilder codeBuilder) {
|
||||
Label fail = codeBuilder.newLabel();
|
||||
if (guardEventConfiguration) {
|
||||
// if (eventConfiguration == null) goto fail;
|
||||
getEventConfiguration(codeBuilder);
|
||||
codeBuilder.ifnull(fail);
|
||||
}
|
||||
// return eventConfiguration.shouldCommit(duration);
|
||||
getEventConfiguration(codeBuilder);
|
||||
codeBuilder.lload(0);
|
||||
codeBuilder.invokevirtual(TYPE_EVENT_CONFIGURATION, METHOD_EVENT_CONFIGURATION_SHOULD_COMMIT.name(), METHOD_EVENT_CONFIGURATION_SHOULD_COMMIT.descriptor());
|
||||
codeBuilder.ireturn();
|
||||
// fail:
|
||||
codeBuilder.labelBinding(fail);
|
||||
// return false
|
||||
codeBuilder.iconst_0();
|
||||
codeBuilder.ireturn();
|
||||
}
|
||||
|
||||
private void throwMissingDuration(CodeBuilder codeBuilder, String method) {
|
||||
@ -406,144 +317,7 @@ final class EventInstrumentation {
|
||||
Bytecode.throwException(codeBuilder, TYPE_ISE, message);
|
||||
}
|
||||
|
||||
private void makeInstrumented() {
|
||||
// MyEvent#isEnabled()
|
||||
updateEnabledMethod(METHOD_IS_ENABLED);
|
||||
|
||||
// MyEvent#begin()
|
||||
updateMethod(METHOD_BEGIN, codeBuilder -> {
|
||||
if (!implicitFields.hasDuration()) {
|
||||
throwMissingDuration(codeBuilder, "begin");
|
||||
} else {
|
||||
codeBuilder.aload(0);
|
||||
invokestatic(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_TIME_STAMP);
|
||||
putfield(codeBuilder, getEventClassDesc(), FIELD_START_TIME);
|
||||
codeBuilder.return_();
|
||||
}
|
||||
});
|
||||
|
||||
// MyEvent#end()
|
||||
updateMethod(METHOD_END, codeBuilder -> {
|
||||
if (!implicitFields.hasDuration()) {
|
||||
throwMissingDuration(codeBuilder, "end");
|
||||
} else {
|
||||
codeBuilder.aload(0);
|
||||
codeBuilder.aload(0);
|
||||
getfield(codeBuilder, getEventClassDesc(), FIELD_START_TIME);
|
||||
invokestatic(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_DURATION);
|
||||
putfield(codeBuilder, getEventClassDesc(), FIELD_DURATION);
|
||||
codeBuilder.return_();
|
||||
}
|
||||
});
|
||||
|
||||
// MyEvent#commit() or static MyEvent#commit(...)
|
||||
MethodDesc m = staticCommitMethod == null ? METHOD_COMMIT : staticCommitMethod;
|
||||
updateMethod(m, codeBuilder -> {
|
||||
Label excluded = codeBuilder.newLabel();
|
||||
Label end = codeBuilder.newLabel();
|
||||
codeBuilder.trying(blockCodeBuilder -> {
|
||||
if (staticCommitMethod != null) {
|
||||
updateStaticCommit(blockCodeBuilder, excluded);
|
||||
} else {
|
||||
updateInstanceCommit(blockCodeBuilder, end, excluded);
|
||||
}
|
||||
// stack: [integer]
|
||||
// notified -> restart event write attempt
|
||||
blockCodeBuilder.ifeq(blockCodeBuilder.startLabel());
|
||||
// stack: []
|
||||
blockCodeBuilder.goto_(end);
|
||||
}, catchBuilder -> {
|
||||
catchBuilder.catchingAll(catchAllHandler -> {
|
||||
getEventWriter(catchAllHandler);
|
||||
// stack: [ex] [EW]
|
||||
catchAllHandler.dup();
|
||||
// stack: [ex] [EW] [EW]
|
||||
Label rethrow = catchAllHandler.newLabel();
|
||||
catchAllHandler.ifnull(rethrow);
|
||||
// stack: [ex] [EW]
|
||||
catchAllHandler.dup();
|
||||
// stack: [ex] [EW] [EW]
|
||||
invokevirtual(catchAllHandler, TYPE_EVENT_WRITER, METHOD_RESET);
|
||||
catchAllHandler.labelBinding(rethrow);
|
||||
// stack:[ex] [EW]
|
||||
catchAllHandler.pop();
|
||||
// stack:[ex]
|
||||
catchAllHandler.athrow();
|
||||
});
|
||||
});
|
||||
codeBuilder.labelBinding(excluded);
|
||||
// stack: [EW]
|
||||
codeBuilder.pop();
|
||||
codeBuilder.labelBinding(end);
|
||||
// stack: []
|
||||
codeBuilder.return_();
|
||||
});
|
||||
|
||||
// MyEvent#shouldCommit()
|
||||
updateMethod(METHOD_EVENT_SHOULD_COMMIT, codeBuilder -> {
|
||||
Label fail = codeBuilder.newLabel();
|
||||
if (guardEventConfiguration) {
|
||||
getEventConfiguration(codeBuilder);
|
||||
codeBuilder.ifnull(fail);
|
||||
}
|
||||
// if (!eventConfiguration.shouldCommit(duration) goto fail;
|
||||
getEventConfiguration(codeBuilder);
|
||||
codeBuilder.aload(0);
|
||||
getfield(codeBuilder, getEventClassDesc(), FIELD_DURATION);
|
||||
invokevirtual(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_EVENT_CONFIGURATION_SHOULD_COMMIT);
|
||||
codeBuilder.ifeq(fail);
|
||||
for (int index = 0; index < settingDescs.size(); index++) {
|
||||
SettingDesc sd = settingDescs.get(index);
|
||||
// if (!settingsMethod(eventConfiguration.settingX)) goto fail;
|
||||
codeBuilder.aload(0);
|
||||
getEventConfiguration(codeBuilder);
|
||||
codeBuilder.loadConstant(index);
|
||||
invokevirtual(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_EVENT_CONFIGURATION_GET_SETTING);
|
||||
MethodTypeDesc mdesc = MethodTypeDesc.ofDescriptor("(" + sd.paramType().descriptorString() + ")Z");
|
||||
codeBuilder.checkcast(sd.paramType());
|
||||
codeBuilder.invokevirtual(getEventClassDesc(), sd.methodName(), mdesc);
|
||||
codeBuilder.ifeq(fail);
|
||||
}
|
||||
// return true
|
||||
codeBuilder.iconst_1();
|
||||
codeBuilder.ireturn();
|
||||
// return false
|
||||
codeBuilder.labelBinding(fail);
|
||||
codeBuilder.iconst_0();
|
||||
codeBuilder.ireturn();
|
||||
});
|
||||
|
||||
if (isJDK) {
|
||||
if (hasStaticMethod(METHOD_ENABLED)) {
|
||||
updateEnabledMethod(METHOD_ENABLED);
|
||||
}
|
||||
|
||||
updateIfStaticMethodExists(METHOD_SHOULD_COMMIT_LONG, codeBuilder -> {
|
||||
Label fail = codeBuilder.newLabel();
|
||||
if (guardEventConfiguration) {
|
||||
// if (eventConfiguration == null) goto fail;
|
||||
getEventConfiguration(codeBuilder);
|
||||
codeBuilder.ifnull(fail);
|
||||
}
|
||||
// return eventConfiguration.shouldCommit(duration);
|
||||
getEventConfiguration(codeBuilder);
|
||||
codeBuilder.lload(0);
|
||||
codeBuilder.invokevirtual(TYPE_EVENT_CONFIGURATION, METHOD_EVENT_CONFIGURATION_SHOULD_COMMIT.name(), METHOD_EVENT_CONFIGURATION_SHOULD_COMMIT.descriptor());
|
||||
codeBuilder.ireturn();
|
||||
// fail:
|
||||
codeBuilder.labelBinding(fail);
|
||||
// return false
|
||||
codeBuilder.iconst_0();
|
||||
codeBuilder.ireturn();
|
||||
});
|
||||
updateIfStaticMethodExists(METHOD_TIME_STAMP, codeBuilder -> {
|
||||
invokestatic(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_TIME_STAMP);
|
||||
codeBuilder.lreturn();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void updateStaticCommit(BlockCodeBuilder blockCodeBuilder, Label excluded) {
|
||||
private void updateStaticCommit(BlockCodeBuilder blockCodeBuilder, Label excluded) {
|
||||
// indexes the argument type array, the argument type array does not include
|
||||
// 'this'
|
||||
int argIndex = 0;
|
||||
@ -576,7 +350,7 @@ final class EventInstrumentation {
|
||||
invokevirtual(blockCodeBuilder, TYPE_EVENT_WRITER, EventWriterMethod.PUT_LONG.method());
|
||||
fieldIndex++;
|
||||
// stack: [EW]
|
||||
if (implicitFields.hasDuration()) {
|
||||
if (inspector.hasDuration()) {
|
||||
// write duration
|
||||
blockCodeBuilder.dup();
|
||||
// stack: [EW], [EW]
|
||||
@ -588,14 +362,14 @@ final class EventInstrumentation {
|
||||
fieldIndex++;
|
||||
}
|
||||
// stack: [EW]
|
||||
if (implicitFields.hasEventThread()) {
|
||||
if (inspector.hasEventThread()) {
|
||||
// write eventThread
|
||||
blockCodeBuilder.dup();
|
||||
// stack: [EW], [EW]
|
||||
invokevirtual(blockCodeBuilder, TYPE_EVENT_WRITER, EventWriterMethod.PUT_EVENT_THREAD.method());
|
||||
}
|
||||
// stack: [EW]
|
||||
if (implicitFields.hasStackTrace()) {
|
||||
if (inspector.hasStackTrace()) {
|
||||
// write stackTrace
|
||||
blockCodeBuilder.dup();
|
||||
// stack: [EW], [EW]
|
||||
@ -603,6 +377,7 @@ final class EventInstrumentation {
|
||||
}
|
||||
// stack: [EW]
|
||||
// write custom fields
|
||||
List<FieldDesc> fieldDescs = inspector.getFields();
|
||||
while (fieldIndex < fieldDescs.size()) {
|
||||
blockCodeBuilder.dup();
|
||||
// stack: [EW], [EW]
|
||||
@ -622,19 +397,19 @@ final class EventInstrumentation {
|
||||
// stack: [int]
|
||||
}
|
||||
|
||||
void updateInstanceCommit(BlockCodeBuilder blockCodeBuilder, Label end, Label excluded) {
|
||||
private void updateInstanceCommit(BlockCodeBuilder blockCodeBuilder, Label end, Label excluded) {
|
||||
// if (!isEnable()) {
|
||||
// return;
|
||||
// }
|
||||
blockCodeBuilder.aload(0);
|
||||
invokevirtual(blockCodeBuilder, getEventClassDesc(), METHOD_IS_ENABLED);
|
||||
invokevirtual(blockCodeBuilder, eventClassDesc, METHOD_IS_ENABLED);
|
||||
Label l0 = blockCodeBuilder.newLabel();
|
||||
blockCodeBuilder.ifne(l0);
|
||||
blockCodeBuilder.return_();
|
||||
blockCodeBuilder.labelBinding(l0);
|
||||
// long startTime = this.startTime
|
||||
blockCodeBuilder.aload(0);
|
||||
getfield(blockCodeBuilder, getEventClassDesc(), FIELD_START_TIME);
|
||||
getfield(blockCodeBuilder, eventClassDesc, ImplicitFields.FIELD_START_TIME);
|
||||
blockCodeBuilder.lstore(1);
|
||||
// if (startTime == 0) {
|
||||
// startTime = EventWriter.timestamp();
|
||||
@ -654,7 +429,7 @@ final class EventInstrumentation {
|
||||
// }
|
||||
blockCodeBuilder.labelBinding(durationEvent);
|
||||
blockCodeBuilder.aload(0);
|
||||
getfield(blockCodeBuilder, getEventClassDesc(), FIELD_DURATION);
|
||||
getfield(blockCodeBuilder, eventClassDesc, ImplicitFields.FIELD_DURATION);
|
||||
blockCodeBuilder.lconst_0();
|
||||
blockCodeBuilder.lcmp();
|
||||
blockCodeBuilder.ifne(commit);
|
||||
@ -662,11 +437,11 @@ final class EventInstrumentation {
|
||||
invokestatic(blockCodeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_TIME_STAMP);
|
||||
blockCodeBuilder.lload(1);
|
||||
blockCodeBuilder.lsub();
|
||||
putfield(blockCodeBuilder, getEventClassDesc(), FIELD_DURATION);
|
||||
putfield(blockCodeBuilder, eventClassDesc, ImplicitFields.FIELD_DURATION);
|
||||
blockCodeBuilder.labelBinding(commit);
|
||||
// if (shouldCommit()) {
|
||||
blockCodeBuilder.aload(0);
|
||||
invokevirtual(blockCodeBuilder, getEventClassDesc(), METHOD_EVENT_SHOULD_COMMIT);
|
||||
invokevirtual(blockCodeBuilder, eventClassDesc, METHOD_EVENT_SHOULD_COMMIT);
|
||||
blockCodeBuilder.ifeq(end);
|
||||
getEventWriter(blockCodeBuilder);
|
||||
// stack: [EW]
|
||||
@ -687,39 +462,40 @@ final class EventInstrumentation {
|
||||
invokevirtual(blockCodeBuilder, TYPE_EVENT_WRITER, EventWriterMethod.PUT_LONG.method());
|
||||
fieldIndex++;
|
||||
// stack: [EW]
|
||||
if (implicitFields.hasDuration()) {
|
||||
if (inspector.hasDuration()) {
|
||||
// write duration
|
||||
blockCodeBuilder.dup();
|
||||
// stack: [EW] [EW]
|
||||
blockCodeBuilder.aload(0);
|
||||
// stack: [EW] [EW] [this]
|
||||
getfield(blockCodeBuilder, getEventClassDesc(), FIELD_DURATION);
|
||||
getfield(blockCodeBuilder, eventClassDesc, ImplicitFields.FIELD_DURATION);
|
||||
// stack: [EW] [EW] [long]
|
||||
invokevirtual(blockCodeBuilder, TYPE_EVENT_WRITER, EventWriterMethod.PUT_LONG.method());
|
||||
fieldIndex++;
|
||||
}
|
||||
// stack: [EW]
|
||||
if (implicitFields.hasEventThread()) {
|
||||
if (inspector.hasEventThread()) {
|
||||
// write eventThread
|
||||
blockCodeBuilder.dup();
|
||||
// stack: [EW] [EW]
|
||||
invokevirtual(blockCodeBuilder, TYPE_EVENT_WRITER, EventWriterMethod.PUT_EVENT_THREAD.method());
|
||||
}
|
||||
// stack: [EW]
|
||||
if (implicitFields.hasStackTrace()) {
|
||||
if (inspector.hasStackTrace()) {
|
||||
// write stack trace
|
||||
blockCodeBuilder.dup();
|
||||
// stack: [EW] [EW]
|
||||
invokevirtual(blockCodeBuilder, TYPE_EVENT_WRITER, EventWriterMethod.PUT_STACK_TRACE.method());
|
||||
}
|
||||
// stack: [EW]
|
||||
List<FieldDesc> fieldDescs = inspector.getFields();
|
||||
while (fieldIndex < fieldDescs.size()) {
|
||||
FieldDesc field = fieldDescs.get(fieldIndex);
|
||||
blockCodeBuilder.dup();
|
||||
// stack: [EW] [EW]
|
||||
blockCodeBuilder.aload(0);
|
||||
// stack: [EW] [EW] [this]
|
||||
getfield(blockCodeBuilder, getEventClassDesc(), field);
|
||||
getfield(blockCodeBuilder, eventClassDesc, field);
|
||||
// stack: [EW] [EW] <T>
|
||||
EventWriterMethod eventMethod = EventWriterMethod.lookupMethod(field);
|
||||
invokevirtual(blockCodeBuilder, TYPE_EVENT_WRITER, eventMethod.method());
|
||||
@ -731,90 +507,33 @@ final class EventInstrumentation {
|
||||
// stack:[int]
|
||||
}
|
||||
|
||||
private void updateEnabledMethod(MethodDesc method) {
|
||||
updateMethod(method, codeBuilder -> {
|
||||
Label nullLabel = codeBuilder.newLabel();
|
||||
if (guardEventConfiguration) {
|
||||
getEventConfiguration(codeBuilder);
|
||||
codeBuilder.ifnull(nullLabel);
|
||||
}
|
||||
getEventConfiguration(codeBuilder);
|
||||
invokevirtual(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_IS_ENABLED);
|
||||
codeBuilder.ireturn();
|
||||
if (guardEventConfiguration) {
|
||||
codeBuilder.labelBinding(nullLabel);
|
||||
codeBuilder.iconst_0();
|
||||
codeBuilder.ireturn();
|
||||
}
|
||||
});
|
||||
private static boolean isStatic(MethodModel method) {
|
||||
return (method.flags().flagsMask() & ClassFile.ACC_STATIC) != 0;
|
||||
}
|
||||
|
||||
private void updateIfStaticMethodExists(MethodDesc method, Consumer<CodeBuilder> code) {
|
||||
if (hasStaticMethod(method)) {
|
||||
updateMethod(method, code);
|
||||
}
|
||||
private static boolean isMethod(MethodModel m, MethodDesc desc) {
|
||||
return desc.matches(m);
|
||||
}
|
||||
|
||||
private boolean hasStaticMethod(MethodDesc method) {
|
||||
for (MethodModel m : classModel.methods()) {
|
||||
if (m.methodName().equalsString(method.name()) && m.methodTypeSymbol().equals(method.descriptor())) {
|
||||
return Modifier.isStatic(m.flags().flagsMask());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void getEventWriter(CodeBuilder codeBuilder) {
|
||||
private static void getEventWriter(CodeBuilder codeBuilder) {
|
||||
invokestatic(codeBuilder, TYPE_EVENT_WRITER, METHOD_GET_EVENT_WRITER);
|
||||
}
|
||||
|
||||
private void getEventConfiguration(CodeBuilder codeBuilder) {
|
||||
if (untypedEventConfiguration) {
|
||||
codeBuilder.getstatic(getEventClassDesc(), FIELD_EVENT_CONFIGURATION.name(), TYPE_OBJECT);
|
||||
codeBuilder.getstatic(eventClassDesc, FIELD_EVENT_CONFIGURATION.name(), TYPE_OBJECT);
|
||||
codeBuilder.checkcast(TYPE_EVENT_CONFIGURATION);
|
||||
} else {
|
||||
codeBuilder.getstatic(getEventClassDesc(), FIELD_EVENT_CONFIGURATION.name(), TYPE_EVENT_CONFIGURATION);
|
||||
codeBuilder.getstatic(eventClassDesc, FIELD_EVENT_CONFIGURATION.name(), TYPE_EVENT_CONFIGURATION);
|
||||
}
|
||||
}
|
||||
|
||||
private void makeUninstrumented() {
|
||||
updateExistingWithReturnFalse(METHOD_EVENT_SHOULD_COMMIT);
|
||||
updateExistingWithReturnFalse(METHOD_IS_ENABLED);
|
||||
updateExistingWithEmptyVoidMethod(METHOD_COMMIT);
|
||||
if (staticCommitMethod != null) {
|
||||
updateExistingWithEmptyVoidMethod(staticCommitMethod);
|
||||
private boolean hasUntypedConfiguration() {
|
||||
for (FieldModel f : inspector.getClassModel().fields()) {
|
||||
if (f.fieldName().equalsString(FIELD_EVENT_CONFIGURATION.name())) {
|
||||
return f.fieldType().equalsString(TYPE_OBJECT.descriptorString());
|
||||
}
|
||||
}
|
||||
updateExistingWithEmptyVoidMethod(METHOD_BEGIN);
|
||||
updateExistingWithEmptyVoidMethod(METHOD_END);
|
||||
}
|
||||
|
||||
private final void updateExistingWithEmptyVoidMethod(MethodDesc voidMethod) {
|
||||
updateMethod(voidMethod, codeBuilder -> {
|
||||
codeBuilder.return_();
|
||||
});
|
||||
}
|
||||
|
||||
private final void updateExistingWithReturnFalse(MethodDesc voidMethod) {
|
||||
updateMethod(voidMethod, codeBuilder -> {
|
||||
codeBuilder.iconst_0();
|
||||
codeBuilder.ireturn();
|
||||
});
|
||||
}
|
||||
|
||||
private Consumer<CodeBuilder> findMethodUpdate(MethodModel mm) {
|
||||
MethodDesc m = MethodDesc.of(mm.methodName().stringValue(), mm.methodType().stringValue());
|
||||
return methodUpdates.get(m);
|
||||
}
|
||||
|
||||
private void updateMethod(MethodDesc method, Consumer<CodeBuilder> codeBuilder) {
|
||||
methodUpdates.put(method, codeBuilder);
|
||||
}
|
||||
|
||||
private ClassDesc getEventClassDesc() {
|
||||
return classModel.thisClass().asSymbol();
|
||||
}
|
||||
|
||||
public String getEventName() {
|
||||
return eventName;
|
||||
throw new InternalError("Class missing configuration field");
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,7 +72,8 @@ final class JVMUpcalls {
|
||||
}
|
||||
boolean jdkClass = Utils.isJDKClass(clazz);
|
||||
Logger.log(LogTag.JFR_SYSTEM, LogLevel.INFO, "Adding instrumentation to event class " + clazz.getName() + " using retransform");
|
||||
EventInstrumentation ei = new EventInstrumentation(clazz.getSuperclass(), oldBytes, traceId, jdkClass, false);
|
||||
ClassInspector c = new ClassInspector(clazz.getSuperclass(), oldBytes, jdkClass);
|
||||
EventInstrumentation ei = new EventInstrumentation(c, traceId, false);
|
||||
byte[] bytes = ei.buildInstrumented();
|
||||
Bytecode.log(clazz.getName(), bytes);
|
||||
return bytes;
|
||||
@ -105,9 +106,9 @@ final class JVMUpcalls {
|
||||
}
|
||||
String eventName = "<Unknown>";
|
||||
try {
|
||||
EventInstrumentation ei = new EventInstrumentation(superClass, oldBytes, traceId, bootClassLoader, true);
|
||||
eventName = ei.getEventName();
|
||||
if (!JVMSupport.shouldInstrument(bootClassLoader, ei.getEventName())) {
|
||||
ClassInspector c = new ClassInspector(superClass, oldBytes, bootClassLoader);
|
||||
eventName = c.getEventName();
|
||||
if (!JVMSupport.shouldInstrument(bootClassLoader, c.getEventName())) {
|
||||
Logger.log(LogTag.JFR_SYSTEM, LogLevel.INFO, "Skipping instrumentation for " + eventName + " since container support is missing");
|
||||
return oldBytes;
|
||||
}
|
||||
@ -118,14 +119,15 @@ final class JVMUpcalls {
|
||||
// No need to generate bytecode if:
|
||||
// 1) Event class is disabled, and there is not an external configuration that overrides.
|
||||
// 2) Event class has @Registered(false)
|
||||
if (!mr.isEnabled(ei.getEventName()) && !ei.isEnabled() || !ei.isRegistered()) {
|
||||
if (!mr.isEnabled(c.getEventName()) && !c.isEnabled() || !c.isRegistered()) {
|
||||
Logger.log(LogTag.JFR_SYSTEM, LogLevel.INFO, "Skipping instrumentation for event type " + eventName + " since event was disabled on class load");
|
||||
return oldBytes;
|
||||
}
|
||||
}
|
||||
Logger.log(LogTag.JFR_SYSTEM, LogLevel.INFO, "Adding " + (forceInstrumentation ? "forced " : "") + "instrumentation for event type " + eventName + " during initial class load");
|
||||
EventInstrumentation ei = new EventInstrumentation(c, traceId, true);
|
||||
byte[] bytes = ei.buildInstrumented();
|
||||
Bytecode.log(ei.getClassName() + "(" + traceId + ")", bytes);
|
||||
Logger.log(LogTag.JFR_SYSTEM, LogLevel.INFO, "Adding " + (forceInstrumentation ? "forced " : "") + "instrumentation for event type " + eventName + " during initial class load");
|
||||
Bytecode.log(c.getClassName() + "(" + traceId + ")", bytes);
|
||||
return bytes;
|
||||
} catch (Throwable t) {
|
||||
Logger.log(LogTag.JFR_SYSTEM, LogLevel.WARN, "Unexpected error when adding instrumentation for event type " + eventName + ". " + t.getMessage());
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2023, 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
|
||||
@ -33,6 +33,7 @@ import jdk.jfr.internal.Logger;
|
||||
import jdk.jfr.internal.LogLevel;
|
||||
import jdk.jfr.internal.LogTag;
|
||||
import java.lang.classfile.CodeBuilder;
|
||||
import java.lang.classfile.MethodModel;
|
||||
import java.lang.classfile.ClassModel;
|
||||
import java.lang.classfile.ClassFile;
|
||||
import jdk.internal.classfile.components.ClassPrinter;
|
||||
@ -74,6 +75,12 @@ public final class Bytecode {
|
||||
MethodTypeDesc mtd = MethodTypeDesc.of(returnDesc, parameterDesc);
|
||||
return new MethodDesc(methodName, mtd);
|
||||
}
|
||||
|
||||
public boolean matches(MethodModel m) {
|
||||
return this.descriptor().equals(m.methodTypeSymbol()) && m.methodName().equalsString(this.name());
|
||||
}
|
||||
}
|
||||
public record SettingDesc(ClassDesc paramType, String methodName) {
|
||||
}
|
||||
|
||||
public static ClassDesc classDesc(ValueDescriptor v) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2023, 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
|
||||
@ -28,6 +28,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jdk.jfr.internal.RemoveFields;
|
||||
import jdk.jfr.internal.util.Bytecode.FieldDesc;
|
||||
/**
|
||||
* Class that describes fields that was not directly named
|
||||
* in the event definition.
|
||||
@ -37,6 +38,8 @@ public final class ImplicitFields {
|
||||
public static final String DURATION = "duration";
|
||||
public static final String EVENT_THREAD = "eventThread";
|
||||
public static final String STACK_TRACE = "stackTrace";
|
||||
public static final FieldDesc FIELD_DURATION = FieldDesc.of(long.class, DURATION);
|
||||
public static final FieldDesc FIELD_START_TIME = FieldDesc.of(long.class, START_TIME);
|
||||
|
||||
private final List<String> fields = new ArrayList<>(4);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user