diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java index bcf7daf8189..161cbe380cf 100644 --- a/src/java.base/share/classes/module-info.java +++ b/src/java.base/share/classes/module-info.java @@ -187,15 +187,20 @@ module java.base { exports jdk.internal.classfile to jdk.jartool, jdk.jdeps, + jdk.jfr, jdk.jlink, jdk.jshell; exports jdk.internal.classfile.attribute to jdk.jartool, jdk.jdeps, + jdk.jfr, jdk.jlink; + exports jdk.internal.classfile.components to + jdk.jfr; exports jdk.internal.classfile.constantpool to jdk.jartool, jdk.jdeps, + jdk.jfr, jdk.jlink; exports jdk.internal.classfile.instruction to jdk.jdeps, diff --git a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedEvent.java index b0ffe88b9c7..9ec117ffc88 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2024, 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 @@ -31,7 +31,7 @@ import java.util.List; import jdk.jfr.EventType; import jdk.jfr.ValueDescriptor; -import jdk.jfr.internal.EventInstrumentation; +import jdk.jfr.internal.util.Utils; import jdk.jfr.internal.consumer.ObjectContext; /** @@ -57,7 +57,7 @@ public final class RecordedEvent extends RecordedObject { * @return stack trace, or {@code null} if doesn't exist for the event */ public RecordedStackTrace getStackTrace() { - return getTyped(EventInstrumentation.FIELD_STACK_TRACE, RecordedStackTrace.class, null); + return getTyped(Utils.FIELD_STACK_TRACE, RecordedStackTrace.class, null); } /** @@ -67,7 +67,7 @@ public final class RecordedEvent extends RecordedObject { * @return thread, or {@code null} if doesn't exist for the event */ public RecordedThread getThread() { - return getTyped(EventInstrumentation.FIELD_EVENT_THREAD, RecordedThread.class, null); + return getTyped(Utils.FIELD_EVENT_THREAD, RecordedThread.class, null); } /** diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/ASMToolkit.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/ASMToolkit.java deleted file mode 100644 index f9e088c8fdf..00000000000 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/ASMToolkit.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2016, 2022, 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 java.io.ByteArrayOutputStream; -import java.io.PrintWriter; - -import jdk.internal.org.objectweb.asm.ClassReader; -import jdk.internal.org.objectweb.asm.Type; -import jdk.internal.org.objectweb.asm.util.TraceClassVisitor; -import jdk.jfr.ValueDescriptor; - -final class ASMToolkit { - public static final Type TYPE_STRING = Type.getType(String.class); - private static final Type TYPE_THREAD = Type.getType(Thread.class); - private static final Type TYPE_CLASS = Type.getType(Class.class); - - public static Type toType(ValueDescriptor v) { - return switch (v.getTypeName()) { - case "byte" -> Type.BYTE_TYPE; - case "short" -> Type.SHORT_TYPE; - case "int" -> Type.INT_TYPE; - case "long" ->Type.LONG_TYPE; - case "double" -> Type.DOUBLE_TYPE; - case "float" -> Type.FLOAT_TYPE; - case "char" -> Type.CHAR_TYPE; - case "boolean" -> Type.BOOLEAN_TYPE; - case "java.lang.String" -> TYPE_STRING; - case "java.lang.Thread" -> TYPE_THREAD; - case "java.lang.Class" -> TYPE_CLASS; - default -> throw new Error("Not a valid type " + v.getTypeName()); - }; - } - - /** - * Converts "int" into "I" and "java.lang.String" into "Ljava/lang/String;" - * - * @param typeName - * type - * - * @return descriptor - */ - public static String getDescriptor(String typeName) { - return switch (typeName) { - case "int" -> "I"; - case "long" -> "J"; - case "boolean" -> "Z"; - case "float" -> "F"; - case "double" -> "D"; - case "short" -> "S"; - case "char" -> "C"; - case "byte" -> "B"; - default -> Type.getObjectType(getInternalName(typeName)).getDescriptor(); - }; - } - - /** - * Converts java.lang.String into java/lang/String - * - * @param className - * - * @return internal name - */ - public static String getInternalName(String className) { - return className.replace(".", "/"); - } - - public static void logASM(String className, byte[] bytes) { - Logger.log(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.INFO, "Generated bytecode for class " + className); - if (Logger.shouldLog(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.TRACE)) { - ClassReader cr = new ClassReader(bytes); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - PrintWriter w = new PrintWriter(baos); - w.println("Bytecode:"); - cr.accept(new TraceClassVisitor(w), 0); - Logger.log(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.TRACE, baos.toString()); - }; - } -} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventClassBuilder.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventClassBuilder.java index 70bd3eef1f4..3d661f725e8 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventClassBuilder.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventClassBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2023, 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 @@ -25,117 +25,112 @@ package jdk.jfr.internal; +import static jdk.jfr.internal.util.Bytecode.invokespecial; + +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.lang.reflect.AccessFlag; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; -import jdk.internal.org.objectweb.asm.AnnotationVisitor; -import jdk.internal.org.objectweb.asm.ClassWriter; -import jdk.internal.org.objectweb.asm.Label; -import jdk.internal.org.objectweb.asm.MethodVisitor; -import jdk.internal.org.objectweb.asm.Opcodes; -import jdk.internal.org.objectweb.asm.Type; -import jdk.internal.org.objectweb.asm.commons.GeneratorAdapter; -import jdk.internal.org.objectweb.asm.commons.Method; +import jdk.internal.classfile.AnnotationValue; +import jdk.internal.classfile.ClassBuilder; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.attribute.RuntimeVisibleAnnotationsAttribute; import jdk.jfr.AnnotationElement; import jdk.jfr.Event; import jdk.jfr.ValueDescriptor; - +import jdk.jfr.internal.util.Bytecode; +import jdk.jfr.internal.util.Bytecode.MethodDesc; // Helper class for building dynamic events public final class EventClassBuilder { - - private static final Type TYPE_EVENT = Type.getType(Event.class); - private static final Type TYPE_IOBE = Type.getType(IndexOutOfBoundsException.class); - private static final Method DEFAULT_CONSTRUCTOR = Method.getMethod("void ()"); - private static final Method SET_METHOD = Method.getMethod("void set (int, java.lang.Object)"); + private static final ClassDesc TYPE_EVENT = Bytecode.classDesc(Event.class); + private static final ClassDesc TYPE_IOBE = Bytecode.classDesc(IndexOutOfBoundsException.class); + private static final MethodDesc DEFAULT_CONSTRUCTOR = MethodDesc.of("", "()V"); + private static final MethodDesc SET_METHOD = MethodDesc.of("set", "(ILjava/lang/Object;)V"); private static final AtomicLong idCounter = new AtomicLong(); - private final ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + private final String fullClassName; - private final Type type; + private final ClassDesc type; private final List fields; private final List annotationElements; public EventClassBuilder(List annotationElements, List fields) { this.fullClassName = "jdk.jfr.DynamicEvent" + idCounter.incrementAndGet(); - this.type = Type.getType("L" + fullClassName.replace(".", "/") + ";"); + this.type = ClassDesc.of(fullClassName); this.fields = fields; this.annotationElements = annotationElements; } public Class build() { - buildClassInfo(); - buildConstructor(); - buildFields(); - buildSetMethod(); - endClass(); - byte[] bytes = classWriter.toByteArray(); - ASMToolkit.logASM(fullClassName, bytes); + byte[] bytes = Classfile.of().build(ClassDesc.of(fullClassName), cb -> build(cb)); + Bytecode.log(fullClassName, bytes); return SecuritySupport.defineClass(Event.class, bytes).asSubclass(Event.class); } - private void endClass() { - classWriter.visitEnd(); + void build(ClassBuilder builder) { + buildClassInfo(builder); + buildConstructor(builder); + buildFields(builder); + buildSetMethod(builder); } - private void buildSetMethod() { - GeneratorAdapter ga = new GeneratorAdapter(Opcodes.ACC_PUBLIC, SET_METHOD, null, null, classWriter); - int index = 0; - for (ValueDescriptor v : fields) { - ga.loadArg(0); - ga.visitLdcInsn(index); - Label notEqual = new Label(); - ga.ifICmp(GeneratorAdapter.NE, notEqual); - ga.loadThis(); - ga.loadArg(1); - Type fieldType = ASMToolkit.toType(v); - ga.unbox(ASMToolkit.toType(v)); - ga.putField(type, v.getName(), fieldType); - ga.visitInsn(Opcodes.RETURN); - ga.visitLabel(notEqual); - index++; - } - ga.throwException(TYPE_IOBE, "Index must between 0 and " + fields.size()); - ga.endMethod(); - } - - private void buildConstructor() { - MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, DEFAULT_CONSTRUCTOR.getName(), DEFAULT_CONSTRUCTOR.getDescriptor(), null, null); - mv.visitIntInsn(Opcodes.ALOAD, 0); - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, TYPE_EVENT.getInternalName(), DEFAULT_CONSTRUCTOR.getName(), DEFAULT_CONSTRUCTOR.getDescriptor(), false); - mv.visitInsn(Opcodes.RETURN); - mv.visitMaxs(0, 0); - } - - private void buildClassInfo() { - String internalSuperName = ASMToolkit.getInternalName(Event.class.getName()); - String internalClassName = type.getInternalName(); - classWriter.visit(52, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER, internalClassName, null, internalSuperName, null); - - for (AnnotationElement a : annotationElements) { - String descriptor = ASMToolkit.getDescriptor(a.getTypeName()); - AnnotationVisitor av = classWriter.visitAnnotation(descriptor, true); - for (ValueDescriptor v : a.getValueDescriptors()) { - Object value = a.getValue(v.getName()); - String name = v.getName(); - if (v.isArray()) { - AnnotationVisitor arrayVisitor = av.visitArray(name); - Object[] array = (Object[]) value; - for (int i = 0; i < array.length; i++) { - arrayVisitor.visit(null, array[i]); - } - arrayVisitor.visitEnd(); - } else { - av.visit(name, value); - } + private void buildSetMethod(ClassBuilder builder) { + // void Event::set(int index, Object value); + builder.withMethod(SET_METHOD.name(), SET_METHOD.descriptor(), Classfile.ACC_PUBLIC, methodBuilder -> methodBuilder.withCode(codeBuilder -> { + int index = 0; + for (ValueDescriptor v : fields) { + codeBuilder.iload(1); + codeBuilder.ldc(index); + Label notEqual = codeBuilder.newLabel(); + codeBuilder.if_icmpne(notEqual); + codeBuilder.aload(0); // this + codeBuilder.aload(2); // value + ClassDesc cd = Bytecode.classDesc(v); + Bytecode.unbox(codeBuilder, cd); + codeBuilder.putfield(type, v.getName(), cd); + codeBuilder.return_(); + codeBuilder.labelBinding(notEqual); + index++; } - av.visitEnd(); - } + Bytecode.throwException(codeBuilder, TYPE_IOBE, "Index must between 0 and " + fields.size()); + })); } - private void buildFields() { + private void buildConstructor(ClassBuilder builder) { + builder.withMethod(ConstantDescs.INIT_NAME, ConstantDescs.MTD_void, Classfile.ACC_PUBLIC, methodBuilder -> methodBuilder.withCode(codeBuilder -> { + codeBuilder.aload(0); + invokespecial(codeBuilder, TYPE_EVENT, DEFAULT_CONSTRUCTOR); + codeBuilder.return_(); + })); + } + + private void buildClassInfo(ClassBuilder builder) { + builder.withSuperclass(Bytecode.classDesc(Event.class)); + builder.withFlags(AccessFlag.FINAL, AccessFlag.PUBLIC, AccessFlag.SUPER); + List annotations = new ArrayList<>(); + for (jdk.jfr.AnnotationElement a : annotationElements) { + List list = new ArrayList<>(); + for (ValueDescriptor v : a.getValueDescriptors()) { + // ValueDescriptor can only hold primitive + // No need to care about classes/enums + var value = a.getValue(v.getName()); + var av = AnnotationValue.of(value); + var ae = jdk.internal.classfile.AnnotationElement.of(v.getName(), av); + list.add(ae); + } + ClassDesc cd = ClassDesc.of(a.getTypeName()); + annotations.add(jdk.internal.classfile.Annotation.of(cd, list)); + } + builder.with(RuntimeVisibleAnnotationsAttribute.of(annotations)); + } + + private void buildFields(ClassBuilder builder) { for (ValueDescriptor v : fields) { - String internal = ASMToolkit.getDescriptor(v.getTypeName()); - classWriter.visitField(Opcodes.ACC_PRIVATE, v.getName(), internal, null, null); + builder.withField(v.getName(), Bytecode.classDesc(v), Classfile.ACC_PRIVATE); // No need to store annotations on field since they will be replaced anyway. } } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java index 9c3770c8fad..fcf00a7db9b 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java @@ -25,117 +25,125 @@ 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.lang.reflect.Parameter; 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 jdk.internal.org.objectweb.asm.ClassReader; -import jdk.internal.org.objectweb.asm.ClassWriter; -import jdk.internal.org.objectweb.asm.Label; -import jdk.internal.org.objectweb.asm.MethodVisitor; -import jdk.internal.org.objectweb.asm.Opcodes; -import jdk.internal.org.objectweb.asm.Type; -import jdk.internal.org.objectweb.asm.commons.Method; -import jdk.internal.org.objectweb.asm.tree.AnnotationNode; -import jdk.internal.org.objectweb.asm.tree.ClassNode; -import jdk.internal.org.objectweb.asm.tree.FieldNode; -import jdk.internal.org.objectweb.asm.tree.MethodNode; +import jdk.internal.classfile.Annotation; +import jdk.internal.classfile.AnnotationElement; +import jdk.internal.classfile.AnnotationValue; +import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.CodeBuilder; +import jdk.internal.classfile.CodeBuilder.BlockCodeBuilder; +import jdk.internal.classfile.FieldModel; +import jdk.internal.classfile.Label; +import jdk.internal.classfile.MethodModel; +import jdk.internal.classfile.Opcode; +import jdk.internal.classfile.TypeKind; +import jdk.internal.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +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.event.EventConfiguration; -import jdk.jfr.internal.event.EventWriter; import jdk.jfr.internal.util.Utils; +import jdk.jfr.internal.util.Bytecode; +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; /** * Class responsible for adding instrumentation to a subclass of {@link Event}. * */ -public final class EventInstrumentation { +final class EventInstrumentation { - record SettingInfo(Type paramType, String methodName) { + private record SettingDesc(ClassDesc paramType, String methodName) { } - record FieldInfo(String name, String descriptor) { - } + private static final FieldDesc FIELD_DURATION = FieldDesc.of(long.class, Utils.FIELD_DURATION); + private static final FieldDesc FIELD_EVENT_CONFIGURATION = FieldDesc.of(Object.class, "eventConfiguration");; + private static final FieldDesc FIELD_START_TIME = FieldDesc.of(long.class, Utils.FIELD_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 TYPE_EVENT_CONFIGURATION = classDesc(EventConfiguration.class); + private static final ClassDesc TYPE_EVENT_WRITER = classDesc(EventWriter.class); + private static final ClassDesc TYPE_EVENT_WRITER_FACTORY = ClassDesc.of("jdk.jfr.internal.event.EventWriterFactory"); + private static final ClassDesc TYPE_OBJECT = Bytecode.classDesc(Object.class); + private static final ClassDesc TYPE_SETTING_DEFINITION = Bytecode.classDesc(SettingDefinition.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"); + private static final MethodDesc METHOD_ENABLED = MethodDesc.of("enabled", "()Z"); + private static final MethodDesc METHOD_END = MethodDesc.of("end", "()V"); + private static final MethodDesc METHOD_EVENT_CONFIGURATION_SHOULD_COMMIT = MethodDesc.of("shouldCommit", "(J)Z"); + private static final MethodDesc METHOD_EVENT_CONFIGURATION_GET_SETTING = MethodDesc.of("getSetting", SettingControl.class, int.class); + private static final MethodDesc METHOD_EVENT_SHOULD_COMMIT = MethodDesc.of("shouldCommit", "()Z"); + private static final MethodDesc METHOD_GET_EVENT_WRITER_KEY = MethodDesc.of("getEventWriter", "(J)" + TYPE_EVENT_WRITER.descriptorString()); + private static final MethodDesc METHOD_IS_ENABLED = MethodDesc.of("isEnabled", "()Z"); + private static final MethodDesc METHOD_RESET = MethodDesc.of("reset", "()V"); + private static final MethodDesc METHOD_SHOULD_COMMIT_LONG = MethodDesc.of("shouldCommit", "(J)Z"); + private static final MethodDesc METHOD_TIME_STAMP = MethodDesc.of("timestamp", "()J"); - public static final String FIELD_EVENT_THREAD = "eventThread"; - public static final String FIELD_STACK_TRACE = "stackTrace"; - public static final String FIELD_DURATION = "duration"; - - static final String FIELD_EVENT_CONFIGURATION = "eventConfiguration"; - static final String FIELD_START_TIME = "startTime"; - - private static final String ANNOTATION_NAME_DESCRIPTOR = Type.getDescriptor(Name.class); - private static final String ANNOTATION_REGISTERED_DESCRIPTOR = Type.getDescriptor(Registered.class); - private static final String ANNOTATION_ENABLED_DESCRIPTOR = Type.getDescriptor(Enabled.class); - private static final Type TYPE_EVENT_CONFIGURATION = Type.getType(EventConfiguration.class); - private static final Type TYPE_EVENT_WRITER = Type.getType(EventWriter.class); - private static final Type TYPE_EVENT_WRITER_FACTORY = Type.getType("Ljdk/jfr/internal/event/EventWriterFactory;"); - private static final Type TYPE_SETTING_CONTROL = Type.getType(SettingControl.class); - private static final String TYPE_OBJECT_DESCRIPTOR = Type.getDescriptor(Object.class); - private static final String TYPE_EVENT_CONFIGURATION_DESCRIPTOR = TYPE_EVENT_CONFIGURATION.getDescriptor(); - private static final String TYPE_SETTING_DEFINITION_DESCRIPTOR = Type.getDescriptor(SettingDefinition.class); - private static final Method METHOD_COMMIT = new Method("commit", Type.VOID_TYPE, new Type[0]); - private static final Method METHOD_BEGIN = new Method("begin", Type.VOID_TYPE, new Type[0]); - private static final Method METHOD_END = new Method("end", Type.VOID_TYPE, new Type[0]); - private static final Method METHOD_IS_ENABLED = new Method("isEnabled", Type.BOOLEAN_TYPE, new Type[0]); - private static final Method METHOD_TIME_STAMP = new Method("timestamp", Type.LONG_TYPE, new Type[0]); - private static final Method METHOD_GET_EVENT_WRITER_KEY = new Method("getEventWriter", TYPE_EVENT_WRITER, new Type[] { Type.LONG_TYPE }); - private static final Method METHOD_EVENT_SHOULD_COMMIT = new Method("shouldCommit", Type.BOOLEAN_TYPE, new Type[0]); - private static final Method METHOD_EVENT_CONFIGURATION_SHOULD_COMMIT = new Method("shouldCommit", Type.BOOLEAN_TYPE, new Type[] { Type.LONG_TYPE }); - private static final Method METHOD_EVENT_CONFIGURATION_GET_SETTING = new Method("getSetting", TYPE_SETTING_CONTROL, new Type[] { Type.INT_TYPE }); - private static final Method METHOD_DURATION = new Method("duration", Type.LONG_TYPE, new Type[] { Type.LONG_TYPE }); - private static final Method METHOD_RESET = new Method("reset", "()V"); - private static final Method METHOD_ENABLED = new Method("enabled", Type.BOOLEAN_TYPE, new Type[0]); - private static final Method METHOD_SHOULD_COMMIT_LONG = new Method("shouldCommit", Type.BOOLEAN_TYPE, new Type[] { Type.LONG_TYPE }); - - private final ClassNode classNode; - private final List settingInfos; - private final List fieldInfos;; + private final ClassModel classModel; + private final List settingDescs; + private final List fieldDescs;; private final String eventName; private final Class superClass; private final boolean untypedEventConfiguration; - private final Method staticCommitMethod; + private final MethodDesc staticCommitMethod; private final long eventTypeId; private final boolean guardEventConfiguration; private final boolean isJDK; + private final Map> methodUpdates = new LinkedHashMap<>(); EventInstrumentation(Class superClass, byte[] bytes, long id, boolean isJDK, boolean guardEventConfiguration) { this.eventTypeId = id; this.superClass = superClass; - this.classNode = createClassNode(bytes); - this.settingInfos = buildSettingInfos(superClass, classNode); - this.fieldInfos = buildFieldInfos(superClass, classNode); - String n = annotationValue(classNode, ANNOTATION_NAME_DESCRIPTOR, String.class); - this.eventName = n == null ? classNode.name.replace("/", ".") : n; - this.staticCommitMethod = isJDK ? findStaticCommitMethod(classNode, fieldInfos) : null; + this.classModel = createClassModel(bytes); + this.settingDescs = buildSettingDescs(superClass, classModel); + this.fieldDescs = buildFieldDescs(superClass, classModel); + String n = annotationValue(classModel, ANNOTATION_NAME, String.class); + this.eventName = n == null ? classModel.thisClass().asInternalName().replace("/", ".") : n; + 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, + // 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.isJDK = isJDK; } - public static Method findStaticCommitMethod(ClassNode classNode, List fields) { + static MethodDesc findStaticCommitMethod(ClassModel classModel, List fields) { StringBuilder sb = new StringBuilder(); sb.append("("); - for (FieldInfo field : fields) { - sb.append(field.descriptor); + for (FieldDesc field : fields) { + sb.append(field.type().descriptorString()); } sb.append(")V"); - Method m = new Method("commit", sb.toString()); - for (MethodNode method : classNode.methods) { - if ("commit".equals(method.name) && m.getDescriptor().equals(method.desc)) { + 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; } } @@ -143,27 +151,24 @@ public final class EventInstrumentation { } private boolean hasUntypedConfiguration() { - for (FieldNode field : classNode.fields) { - if (FIELD_EVENT_CONFIGURATION.equals(field.name)) { - return field.desc.equals(TYPE_OBJECT_DESCRIPTOR); + 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 classNode.name.replace("/", "."); + return classModel.thisClass().asInternalName().replace("/", "."); } - private ClassNode createClassNode(byte[] bytes) { - ClassNode classNode = new ClassNode(); - ClassReader classReader = new ClassReader(bytes); - classReader.accept(classNode, 0); - return classNode; + private ClassModel createClassModel(byte[] bytes) { + return Classfile.of().parse(bytes); } boolean isRegistered() { - Boolean result = annotationValue(classNode, ANNOTATION_REGISTERED_DESCRIPTOR, Boolean.class); + Boolean result = annotationValue(classModel, ANNOTATION_REGISTERED, Boolean.class); if (result != null) { return result.booleanValue(); } @@ -177,7 +182,7 @@ public final class EventInstrumentation { } boolean isEnabled() { - Boolean result = annotationValue(classNode, ANNOTATION_ENABLED_DESCRIPTOR, Boolean.class); + Boolean result = annotationValue(classModel, ANNOTATION_ENABLED, Boolean.class); if (result != null) { return result.booleanValue(); } @@ -191,18 +196,23 @@ public final class EventInstrumentation { } @SuppressWarnings("unchecked") - private static T annotationValue(ClassNode classNode, String typeDescriptor, Class type) { - if (classNode.visibleAnnotations != null) { - for (AnnotationNode a : classNode.visibleAnnotations) { - if (typeDescriptor.equals(a.desc)) { - List values = a.values; - if (values != null && values.size() == 2) { - Object key = values.get(0); - Object value = values.get(1); - if (key instanceof String keyName && value != null) { - if (type == value.getClass()) { - if ("value".equals(keyName)) { - return (T) value; + // Only supports String and Boolean values + private static T annotationValue(ClassModel classModel, ClassDesc classDesc, Class type) { + String typeDescriptor = classDesc.descriptorString(); + for (ClassElement ce : classModel.elements()) { + 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; } } } @@ -213,99 +223,119 @@ public final class EventInstrumentation { return null; } - private static List buildSettingInfos(Class superClass, ClassNode classNode) { + private static List buildSettingDescs(Class superClass, ClassModel classModel) { Set methodSet = new HashSet<>(); - List settingInfos = new ArrayList<>(); - for (MethodNode m : classNode.methods) { - if (m.visibleAnnotations != null) { - for (AnnotationNode an : m.visibleAnnotations) { - // We can't really validate the method at this - // stage. We would need to check that the parameter - // is an instance of SettingControl. - if (TYPE_SETTING_DEFINITION_DESCRIPTOR.equals(an.desc)) { - String name = m.name; - for (AnnotationNode nameCandidate : m.visibleAnnotations) { - if (ANNOTATION_NAME_DESCRIPTOR.equals(nameCandidate.desc)) { - List values = nameCandidate.values; - if (values.size() == 1 && values.getFirst() instanceof String s) { - name = Utils.validJavaIdentifier(s, name); + List settingDescs = new ArrayList<>(); + for (MethodModel m : classModel.methods()) { + for (var me : m.elements()) { + 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); + } + } + } } } - } - Type returnType = Type.getReturnType(m.desc); - if (returnType.equals(Type.getType(Boolean.TYPE))) { - Type[] args = Type.getArgumentTypes(m.desc); - if (args.length == 1) { - Type paramType = args[0]; - methodSet.add(m.name); - settingInfos.add(new SettingInfo(paramType, m.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; c != jdk.internal.event.Event.class; c = c.getSuperclass()) { + 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) { - Parameter param = method.getParameters()[0]; - Type paramType = Type.getType(param.getType()); - methodSet.add(method.getName()); - settingInfos.add(new SettingInfo(paramType, method.getName())); + 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 settingInfos; + return settingDescs; } - private static List buildFieldInfos(Class superClass, ClassNode classNode) { + private static List buildFieldDescs(Class superClass, ClassModel classModel) { Set fieldSet = new HashSet<>(); - List fieldInfos = new ArrayList<>(classNode.fields.size()); + List 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. - fieldInfos.add(new FieldInfo("startTime", Type.LONG_TYPE.getDescriptor())); - fieldInfos.add(new FieldInfo("duration", Type.LONG_TYPE.getDescriptor())); - for (FieldNode field : classNode.fields) { - if (!fieldSet.contains(field.name) && isValidField(field.access, Type.getType(field.desc).getClassName())) { - FieldInfo fi = new FieldInfo(field.name, field.desc); - fieldInfos.add(fi); - fieldSet.add(field.name); + fieldDescs.add(FIELD_START_TIME); + 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; c != jdk.internal.event.Event.class; c = c.getSuperclass()) { + 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)) { - Type fieldType = Type.getType(field.getType()); - fieldInfos.add(new FieldInfo(fieldName, fieldType.getDescriptor())); + fieldDescs.add(FieldDesc.of(field.getType(), fieldName)); fieldSet.add(fieldName); } } } } } - return fieldInfos; + 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 jdk.jfr.internal.Type.isValidJavaFieldType(className); + return Type.isValidJavaFieldType(className); } public byte[] buildInstrumented() { @@ -313,11 +343,24 @@ public final class EventInstrumentation { return toByteArray(); } - private byte[] toByteArray() { - ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); - classNode.accept(cw); - cw.visitEnd(); - return cw.toByteArray(); + byte[] toByteArray() { + return Classfile.of().build(classModel.thisClass().asSymbol(), classBuilder -> { + for (ClassElement ce : classModel) { + boolean updated = false; + if (ce instanceof MethodModel method) { + Consumer 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); + } + } + }); } public byte[] buildUninstrumented() { @@ -330,416 +373,347 @@ public final class EventInstrumentation { updateEnabledMethod(METHOD_IS_ENABLED); // MyEvent#begin() - updateMethod(METHOD_BEGIN, methodVisitor -> { - methodVisitor.visitIntInsn(Opcodes.ALOAD, 0); - invokeStatic(methodVisitor, TYPE_EVENT_CONFIGURATION.getInternalName(), METHOD_TIME_STAMP); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, getInternalClassName(), FIELD_START_TIME, "J"); - methodVisitor.visitInsn(Opcodes.RETURN); + updateMethod(METHOD_BEGIN, codeBuilder -> { + codeBuilder.aload(0); + invokestatic(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_TIME_STAMP); + putfield(codeBuilder, getEventClassDesc(), FIELD_START_TIME); + codeBuilder.return_(); }); // MyEvent#end() - updateMethod(METHOD_END, methodVisitor -> { - methodVisitor.visitIntInsn(Opcodes.ALOAD, 0); - methodVisitor.visitIntInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_START_TIME, "J"); - invokeStatic(methodVisitor, TYPE_EVENT_CONFIGURATION.getInternalName(), METHOD_DURATION); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, getInternalClassName(), FIELD_DURATION, "J"); - methodVisitor.visitInsn(Opcodes.RETURN); - methodVisitor.visitMaxs(0, 0); + updateMethod(METHOD_END, codeBuilder -> { + 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(...) - if (staticCommitMethod != null) { - updateExistingWithEmptyVoidMethod(METHOD_COMMIT); - updateMethod(staticCommitMethod, mv -> { - // indexes the argument type array, the argument type array does not include - // 'this' - int argIndex = 0; - // indexes the proper slot in the local variable table, takes type size into - // account, therefore sometimes argIndex != slotIndex - int slotIndex = 0; - int fieldIndex = 0; - Type[] argumentTypes = Type.getArgumentTypes(staticCommitMethod.getDescriptor()); - mv.visitCode(); - Label start = new Label(); - Label endTryBlock = new Label(); - Label exceptionHandler = new Label(); - mv.visitTryCatchBlock(start, endTryBlock, exceptionHandler, "java/lang/Throwable"); - mv.visitLabel(start); - getEventWriter(mv); - // stack: [EW] - mv.visitInsn(Opcodes.DUP); - // stack: [EW], [EW] - // write begin event - getEventConfiguration(mv); - // stack: [EW], [EW], [EventConfiguration] - mv.visitLdcInsn(eventTypeId); - // stack: [EW], [EW], [EventConfiguration] [long] - visitMethod(mv, Opcodes.INVOKEVIRTUAL, TYPE_EVENT_WRITER, EventWriterMethod.BEGIN_EVENT.asASM()); - // stack: [EW], [integer] - Label excluded = new Label(); - mv.visitJumpInsn(Opcodes.IFEQ, excluded); - // stack: [EW] - // write startTime - mv.visitInsn(Opcodes.DUP); - // stack: [EW], [EW] - mv.visitVarInsn(argumentTypes[argIndex].getOpcode(Opcodes.ILOAD), slotIndex); - // stack: [EW], [EW], [long] - slotIndex += argumentTypes[argIndex++].getSize(); - visitMethod(mv, Opcodes.INVOKEVIRTUAL, TYPE_EVENT_WRITER, EventWriterMethod.PUT_LONG.asASM()); - // stack: [EW] - fieldIndex++; - // write duration - mv.visitInsn(Opcodes.DUP); - // stack: [EW], [EW] - mv.visitVarInsn(argumentTypes[argIndex].getOpcode(Opcodes.ILOAD), slotIndex); - // stack: [EW], [EW], [long] - slotIndex += argumentTypes[argIndex++].getSize(); - visitMethod(mv, Opcodes.INVOKEVIRTUAL, TYPE_EVENT_WRITER, EventWriterMethod.PUT_LONG.asASM()); - // stack: [EW] - fieldIndex++; - // write eventThread - mv.visitInsn(Opcodes.DUP); - // stack: [EW], [EW] - visitMethod(mv, Opcodes.INVOKEVIRTUAL, TYPE_EVENT_WRITER, EventWriterMethod.PUT_EVENT_THREAD.asASM()); - // stack: [EW] - // write stackTrace - mv.visitInsn(Opcodes.DUP); - // stack: [EW], [EW] - visitMethod(mv, Opcodes.INVOKEVIRTUAL, TYPE_EVENT_WRITER, EventWriterMethod.PUT_STACK_TRACE.asASM()); - // stack: [EW] - // write custom fields - while (fieldIndex < fieldInfos.size()) { - mv.visitInsn(Opcodes.DUP); - // stack: [EW], [EW] - mv.visitVarInsn(argumentTypes[argIndex].getOpcode(Opcodes.ILOAD), slotIndex); - // stack:[EW], [EW], [field] - slotIndex += argumentTypes[argIndex++].getSize(); - FieldInfo field = fieldInfos.get(fieldIndex); - EventWriterMethod eventMethod = EventWriterMethod.lookupMethod(field); - visitMethod(mv, Opcodes.INVOKEVIRTUAL, TYPE_EVENT_WRITER, eventMethod.asASM()); - // stack: [EW] - fieldIndex++; + 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: [EW] - // write end event (writer already on stack) - visitMethod(mv, Opcodes.INVOKEVIRTUAL, TYPE_EVENT_WRITER, EventWriterMethod.END_EVENT.asASM()); - // stack [integer] + // stack: [integer] // notified -> restart event write attempt - mv.visitJumpInsn(Opcodes.IFEQ, start); - // stack: - mv.visitLabel(endTryBlock); - Label end = new Label(); - mv.visitJumpInsn(Opcodes.GOTO, end); - mv.visitLabel(exceptionHandler); - // stack: [ex] - mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] { "java/lang/Throwable" }); - getEventWriter(mv); - // stack: [ex] [EW] - mv.visitInsn(Opcodes.DUP); - // stack: [ex] [EW] [EW] - Label rethrow = new Label(); - mv.visitJumpInsn(Opcodes.IFNULL, rethrow); - // stack: [ex] [EW] - mv.visitInsn(Opcodes.DUP); - // stack: [ex] [EW] [EW] - visitMethod(mv, Opcodes.INVOKEVIRTUAL, TYPE_EVENT_WRITER, METHOD_RESET); - mv.visitLabel(rethrow); - // stack:[ex] [EW] - mv.visitFrame(Opcodes.F_SAME, 0, null, 2, new Object[] { "java/lang/Throwable", TYPE_EVENT_WRITER.getInternalName() }); - mv.visitInsn(Opcodes.POP); - // stack:[ex] - mv.visitInsn(Opcodes.ATHROW); - mv.visitLabel(excluded); - // stack: [EW] - mv.visitFrame(Opcodes.F_SAME, 0, null, 1, new Object[] { TYPE_EVENT_WRITER.getInternalName() }); - mv.visitInsn(Opcodes.POP); - mv.visitLabel(end); - // stack: - mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); - mv.visitInsn(Opcodes.RETURN); - mv.visitMaxs(0, 0); - mv.visitEnd(); + 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.if_null(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.throwInstruction(); + }); }); - } else { - updateMethod(METHOD_COMMIT, methodVisitor -> { - // if (!isEnable()) { - // return; - // } - methodVisitor.visitCode(); - Label start = new Label(); - Label endTryBlock = new Label(); - Label exceptionHandler = new Label(); - methodVisitor.visitTryCatchBlock(start, endTryBlock, exceptionHandler, "java/lang/Throwable"); - methodVisitor.visitLabel(start); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, getInternalClassName(), METHOD_IS_ENABLED.getName(), METHOD_IS_ENABLED.getDescriptor(), false); - Label l0 = new Label(); - methodVisitor.visitJumpInsn(Opcodes.IFNE, l0); - methodVisitor.visitInsn(Opcodes.RETURN); - methodVisitor.visitLabel(l0); - methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); - // long startTime = this.startTime - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_START_TIME, "J"); - methodVisitor.visitVarInsn(Opcodes.LSTORE, 1); - // if (startTime == 0) { - // startTime = EventWriter.timestamp(); - // } else { - methodVisitor.visitVarInsn(Opcodes.LLOAD, 1); - methodVisitor.visitInsn(Opcodes.LCONST_0); - methodVisitor.visitInsn(Opcodes.LCMP); - Label durationalEvent = new Label(); - methodVisitor.visitJumpInsn(Opcodes.IFNE, durationalEvent); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, TYPE_EVENT_CONFIGURATION.getInternalName(), METHOD_TIME_STAMP.getName(), METHOD_TIME_STAMP.getDescriptor(), false); - methodVisitor.visitVarInsn(Opcodes.LSTORE, 1); - Label commit = new Label(); - methodVisitor.visitJumpInsn(Opcodes.GOTO, commit); - // if (duration == 0) { - // duration = EventWriter.timestamp() - startTime; - // } - // } - methodVisitor.visitLabel(durationalEvent); - methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_DURATION, "J"); - methodVisitor.visitInsn(Opcodes.LCONST_0); - methodVisitor.visitInsn(Opcodes.LCMP); - methodVisitor.visitJumpInsn(Opcodes.IFNE, commit); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, TYPE_EVENT_CONFIGURATION.getInternalName(), METHOD_TIME_STAMP.getName(), METHOD_TIME_STAMP.getDescriptor(), false); - methodVisitor.visitVarInsn(Opcodes.LLOAD, 1); - methodVisitor.visitInsn(Opcodes.LSUB); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, getInternalClassName(), FIELD_DURATION, "J"); - methodVisitor.visitLabel(commit); - // if (shouldCommit()) { - methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - invokeVirtual(methodVisitor, getInternalClassName(), METHOD_EVENT_SHOULD_COMMIT); - Label end = new Label(); - methodVisitor.visitJumpInsn(Opcodes.IFEQ, end); - getEventWriter(methodVisitor); - // stack: [EW] - methodVisitor.visitInsn(Opcodes.DUP); - // stack: [EW] [EW] - getEventConfiguration(methodVisitor); - // stack: [EW] [EW] [EC] - methodVisitor.visitLdcInsn(eventTypeId); - invokeVirtual(methodVisitor, TYPE_EVENT_WRITER, EventWriterMethod.BEGIN_EVENT.asmMethod); - Label excluded = new Label(); - // stack: [EW] [int] - methodVisitor.visitJumpInsn(Opcodes.IFEQ, excluded); - // stack: [EW] - int fieldIndex = 0; - methodVisitor.visitInsn(Opcodes.DUP); - // stack: [EW] [EW] - methodVisitor.visitVarInsn(Opcodes.LLOAD, 1); - // stack: [EW] [EW] [long] - invokeVirtual(methodVisitor, TYPE_EVENT_WRITER, EventWriterMethod.PUT_LONG.asmMethod); - // stack: [EW] - fieldIndex++; - methodVisitor.visitInsn(Opcodes.DUP); - // stack: [EW] [EW] - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - // stack: [EW] [EW] [this] - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_DURATION, "J"); - // stack: [EW] [EW] [long] - invokeVirtual(methodVisitor, TYPE_EVENT_WRITER, EventWriterMethod.PUT_LONG.asmMethod); - // stack: [EW] - fieldIndex++; - methodVisitor.visitInsn(Opcodes.DUP); - // stack: [EW] [EW] - invokeVirtual(methodVisitor, TYPE_EVENT_WRITER, EventWriterMethod.PUT_EVENT_THREAD.asASM()); - // stack: [EW] - methodVisitor.visitInsn(Opcodes.DUP); - // stack: [EW] [EW] - invokeVirtual(methodVisitor, TYPE_EVENT_WRITER, EventWriterMethod.PUT_STACK_TRACE.asASM()); - // stack: [EW] - while (fieldIndex < fieldInfos.size()) { - FieldInfo field = fieldInfos.get(fieldIndex); - methodVisitor.visitInsn(Opcodes.DUP); - // stack: [EW] [EW] - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - // stack: [EW] [EW] [this] - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), field.name, field.descriptor); - // stack: [EW] [EW] - EventWriterMethod eventMethod = EventWriterMethod.lookupMethod(field); - invokeVirtual(methodVisitor, TYPE_EVENT_WRITER, eventMethod.asmMethod); - // stack: [EW] - fieldIndex++; - } - // stack:[EW] - invokeVirtual(methodVisitor, TYPE_EVENT_WRITER, EventWriterMethod.END_EVENT.asASM()); - // stack [int] - // notified -> restart event write attempt - methodVisitor.visitJumpInsn(Opcodes.IFEQ, start); - methodVisitor.visitLabel(endTryBlock); - methodVisitor.visitJumpInsn(Opcodes.GOTO, end); - methodVisitor.visitLabel(exceptionHandler); - // stack: [ex] - methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] { "java/lang/Throwable" }); - getEventWriter(methodVisitor); - // stack: [ex] [EW] - methodVisitor.visitInsn(Opcodes.DUP); - // stack: [ex] [EW] [EW] - Label rethrow = new Label(); - methodVisitor.visitJumpInsn(Opcodes.IFNULL, rethrow); - // stack: [ex] [EW] - methodVisitor.visitInsn(Opcodes.DUP); - // stack: [ex] [EW] [EW] - visitMethod(methodVisitor, Opcodes.INVOKEVIRTUAL, TYPE_EVENT_WRITER, METHOD_RESET); - methodVisitor.visitLabel(rethrow); - // stack:[ex] [EW] - methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 2, new Object[] { "java/lang/Throwable", TYPE_EVENT_WRITER.getInternalName() }); - methodVisitor.visitInsn(Opcodes.POP); - // stack:[ex] - methodVisitor.visitInsn(Opcodes.ATHROW); - methodVisitor.visitLabel(excluded); - // stack: [EW] - methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 1, new Object[] { TYPE_EVENT_WRITER.getInternalName() }); - methodVisitor.visitInsn(Opcodes.POP); - methodVisitor.visitLabel(end); - // stack: - methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); - methodVisitor.visitInsn(Opcodes.RETURN); - methodVisitor.visitMaxs(0, 0); - methodVisitor.visitEnd(); - }); - } + codeBuilder.labelBinding(excluded); + // stack: [EW] + codeBuilder.pop(); + codeBuilder.labelBinding(end); + // stack: [] + codeBuilder.return_(); + }); // MyEvent#shouldCommit() - updateMethod(METHOD_EVENT_SHOULD_COMMIT, methodVisitor -> { - Label fail = new Label(); + updateMethod(METHOD_EVENT_SHOULD_COMMIT, codeBuilder -> { + Label fail = codeBuilder.newLabel(); if (guardEventConfiguration) { - getEventConfiguration(methodVisitor); - methodVisitor.visitJumpInsn(Opcodes.IFNULL, fail); + getEventConfiguration(codeBuilder); + codeBuilder.if_null(fail); } // if (!eventConfiguration.shouldCommit(duration) goto fail; - getEventConfiguration(methodVisitor); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_DURATION, "J"); - invokeVirtual(methodVisitor, TYPE_EVENT_CONFIGURATION, METHOD_EVENT_CONFIGURATION_SHOULD_COMMIT); - methodVisitor.visitJumpInsn(Opcodes.IFEQ, fail); - for (int index = 0; index < settingInfos.size(); index++) { - SettingInfo si = settingInfos.get(index); + 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; - methodVisitor.visitIntInsn(Opcodes.ALOAD, 0); - if (untypedEventConfiguration) { - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, getInternalClassName(), FIELD_EVENT_CONFIGURATION, TYPE_OBJECT_DESCRIPTOR); - } else { - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, getInternalClassName(), FIELD_EVENT_CONFIGURATION, TYPE_EVENT_CONFIGURATION_DESCRIPTOR); - } - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, TYPE_EVENT_CONFIGURATION.getInternalName()); - methodVisitor.visitLdcInsn(index); - invokeVirtual(methodVisitor, TYPE_EVENT_CONFIGURATION, METHOD_EVENT_CONFIGURATION_GET_SETTING); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, si.paramType().getInternalName()); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, getInternalClassName(), si.methodName, "(" + si.paramType().getDescriptor() + ")Z", false); - methodVisitor.visitJumpInsn(Opcodes.IFEQ, fail); + codeBuilder.aload(0); + getEventConfiguration(codeBuilder); + codeBuilder.checkcast(TYPE_EVENT_CONFIGURATION); + codeBuilder.ldc(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 - methodVisitor.visitInsn(Opcodes.ICONST_1); - methodVisitor.visitInsn(Opcodes.IRETURN); + codeBuilder.iconst_1(); + codeBuilder.ireturn(); // return false - methodVisitor.visitLabel(fail); - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitInsn(Opcodes.IRETURN); + codeBuilder.labelBinding(fail); + codeBuilder.iconst_0(); + codeBuilder.ireturn(); }); if (isJDK) { if (hasStaticMethod(METHOD_ENABLED)) { updateEnabledMethod(METHOD_ENABLED); - }; - updateIfStaticMethodExists(METHOD_SHOULD_COMMIT_LONG, methodVisitor -> { - Label fail = new Label(); + } + + updateIfStaticMethodExists(METHOD_SHOULD_COMMIT_LONG, codeBuilder -> { + Label fail = codeBuilder.newLabel(); if (guardEventConfiguration) { // if (eventConfiguration == null) goto fail; - getEventConfiguration(methodVisitor); - methodVisitor.visitJumpInsn(Opcodes.IFNULL, fail); + getEventConfiguration(codeBuilder); + codeBuilder.if_null(fail); } // return eventConfiguration.shouldCommit(duration); - getEventConfiguration(methodVisitor); - methodVisitor.visitVarInsn(Opcodes.LLOAD, 0); - invokeVirtual(methodVisitor, TYPE_EVENT_CONFIGURATION, METHOD_EVENT_CONFIGURATION_SHOULD_COMMIT); - methodVisitor.visitInsn(Opcodes.IRETURN); + 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: - methodVisitor.visitLabel(fail); + codeBuilder.labelBinding(fail); // return false - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitInsn(Opcodes.IRETURN); - methodVisitor.visitMaxs(0, 0); - methodVisitor.visitEnd(); + codeBuilder.iconst_0(); + codeBuilder.ireturn(); }); - updateIfStaticMethodExists(METHOD_TIME_STAMP, methodVisitor -> { - invokeStatic(methodVisitor, TYPE_EVENT_CONFIGURATION.getInternalName(), METHOD_TIME_STAMP); - methodVisitor.visitInsn(Opcodes.LRETURN); - methodVisitor.visitMaxs(0, 0); - methodVisitor.visitEnd(); + updateIfStaticMethodExists(METHOD_TIME_STAMP, codeBuilder -> { + invokestatic(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_TIME_STAMP); + codeBuilder.lreturn(); }); } } - private void updateEnabledMethod(Method method) { - updateMethod(method, methodVisitor -> { - Label nullLabel = new Label(); + void updateStaticCommit(BlockCodeBuilder blockCodeBuilder, Label excluded) { + // indexes the argument type array, the argument type array does not include + // 'this' + int argIndex = 0; + // indexes the proper slot in the local variable table, takes type size into + // account, therefore sometimes argIndex != slotIndex + int slotIndex = 0; + int fieldIndex = 0; + ClassDesc[] argumentTypes = staticCommitMethod.descriptor().parameterArray(); + TypeKind tk = null; + getEventWriter(blockCodeBuilder); + // stack: [EW], + blockCodeBuilder.dup(); + // stack: [EW], [EW] + // write begin event + getEventConfiguration(blockCodeBuilder); + // stack: [EW], [EW], [EventConfiguration] + blockCodeBuilder.constantInstruction(Opcode.LDC2_W, eventTypeId); + // stack: [EW], [EW], [EventConfiguration] [long] + invokevirtual(blockCodeBuilder, TYPE_EVENT_WRITER, EventWriterMethod.BEGIN_EVENT.method()); + // stack: [EW], [integer] + blockCodeBuilder.ifeq(excluded); + // stack: [EW] + // write startTime + blockCodeBuilder.dup(); + // stack: [EW], [EW] + tk = TypeKind.from(argumentTypes[argIndex++]); + blockCodeBuilder.loadInstruction(tk, slotIndex); + // stack: [EW], [EW], [long] + slotIndex += tk.slotSize(); + invokevirtual(blockCodeBuilder, TYPE_EVENT_WRITER, EventWriterMethod.PUT_LONG.method()); + // stack: [EW] + fieldIndex++; + // write duration + blockCodeBuilder.dup(); + // stack: [EW], [EW] + tk = TypeKind.from(argumentTypes[argIndex++]); + blockCodeBuilder.loadInstruction(tk, slotIndex); + // stack: [EW], [EW], [long] + slotIndex += tk.slotSize(); + invokevirtual(blockCodeBuilder, TYPE_EVENT_WRITER, EventWriterMethod.PUT_LONG.method()); + // stack: [EW] + fieldIndex++; + // write eventThread + blockCodeBuilder.dup(); + // stack: [EW], [EW] + invokevirtual(blockCodeBuilder, TYPE_EVENT_WRITER, EventWriterMethod.PUT_EVENT_THREAD.method()); + // stack: [EW] + // write stackTrace + blockCodeBuilder.dup(); + // stack: [EW], [EW] + invokevirtual(blockCodeBuilder, TYPE_EVENT_WRITER, EventWriterMethod.PUT_STACK_TRACE.method()); + // stack: [EW] + // write custom fields + while (fieldIndex < fieldDescs.size()) { + blockCodeBuilder.dup(); + // stack: [EW], [EW] + tk = TypeKind.from(argumentTypes[argIndex++]); + blockCodeBuilder.loadInstruction(tk, slotIndex); + // stack:[EW], [EW], [field] + slotIndex += tk.slotSize(); + FieldDesc field = fieldDescs.get(fieldIndex); + EventWriterMethod eventMethod = EventWriterMethod.lookupMethod(field); + invokevirtual(blockCodeBuilder, TYPE_EVENT_WRITER, eventMethod.method()); + // stack: [EW] + fieldIndex++; + } + // stack: [EW] + // write end event (writer already on stack) + invokevirtual(blockCodeBuilder, TYPE_EVENT_WRITER, EventWriterMethod.END_EVENT.method()); + // stack: [int] + } + + void updateInstanceCommit(BlockCodeBuilder blockCodeBuilder, Label end, Label excluded) { + // if (!isEnable()) { + // return; + // } + blockCodeBuilder.aload(0); + invokevirtual(blockCodeBuilder, getEventClassDesc(), 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); + blockCodeBuilder.lstore(1); + // if (startTime == 0) { + // startTime = EventWriter.timestamp(); + // } else { + blockCodeBuilder.lload(1); + blockCodeBuilder.lconst_0(); + blockCodeBuilder.lcmp(); + Label durationEvent = blockCodeBuilder.newLabel(); + blockCodeBuilder.ifne(durationEvent); + invokestatic(blockCodeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_TIME_STAMP); + blockCodeBuilder.lstore(1); + Label commit = blockCodeBuilder.newLabel(); + blockCodeBuilder.goto_(commit); + // if (duration == 0) { + // duration = EventWriter.timestamp() - startTime; + // } + // } + blockCodeBuilder.labelBinding(durationEvent); + blockCodeBuilder.aload(0); + getfield(blockCodeBuilder, getEventClassDesc(), FIELD_DURATION); + blockCodeBuilder.lconst_0(); + blockCodeBuilder.lcmp(); + blockCodeBuilder.ifne(commit); + blockCodeBuilder.aload(0); + invokestatic(blockCodeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_TIME_STAMP); + blockCodeBuilder.lload(1); + blockCodeBuilder.lsub(); + putfield(blockCodeBuilder, getEventClassDesc(), FIELD_DURATION); + blockCodeBuilder.labelBinding(commit); + // if (shouldCommit()) { + blockCodeBuilder.aload(0); + invokevirtual(blockCodeBuilder, getEventClassDesc(), METHOD_EVENT_SHOULD_COMMIT); + blockCodeBuilder.ifeq(end); + getEventWriter(blockCodeBuilder); + // stack: [EW] + blockCodeBuilder.dup(); + // stack: [EW] [EW] + getEventConfiguration(blockCodeBuilder); + // stack: [EW] [EW] [EC] + blockCodeBuilder.constantInstruction(Opcode.LDC2_W, eventTypeId); + invokevirtual(blockCodeBuilder, TYPE_EVENT_WRITER, EventWriterMethod.BEGIN_EVENT.method()); + // stack: [EW] [int] + blockCodeBuilder.ifeq(excluded); + // stack: [EW] + int fieldIndex = 0; + blockCodeBuilder.dup(); + // stack: [EW] [EW] + blockCodeBuilder.lload(1); + // stack: [EW] [EW] [long] + invokevirtual(blockCodeBuilder, TYPE_EVENT_WRITER, EventWriterMethod.PUT_LONG.method()); + // stack: [EW] + fieldIndex++; + blockCodeBuilder.dup(); + // stack: [EW] [EW] + blockCodeBuilder.aload(0); + // stack: [EW] [EW] [this] + getfield(blockCodeBuilder, getEventClassDesc(), FIELD_DURATION); + // stack: [EW] [EW] [long] + invokevirtual(blockCodeBuilder, TYPE_EVENT_WRITER, EventWriterMethod.PUT_LONG.method()); + // stack: [EW] + fieldIndex++; + blockCodeBuilder.dup(); + // stack: [EW] [EW] + invokevirtual(blockCodeBuilder, TYPE_EVENT_WRITER, EventWriterMethod.PUT_EVENT_THREAD.method()); + // stack: [EW] + blockCodeBuilder.dup(); + // stack: [EW] [EW] + invokevirtual(blockCodeBuilder, TYPE_EVENT_WRITER, EventWriterMethod.PUT_STACK_TRACE.method()); + // stack: [EW] + 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); + // stack: [EW] [EW] + EventWriterMethod eventMethod = EventWriterMethod.lookupMethod(field); + invokevirtual(blockCodeBuilder, TYPE_EVENT_WRITER, eventMethod.method()); + // stack: [EW] + fieldIndex++; + } + // stack:[EW] + invokevirtual(blockCodeBuilder, TYPE_EVENT_WRITER, EventWriterMethod.END_EVENT.method()); + // stack:[int] + } + + private void updateEnabledMethod(MethodDesc method) { + updateMethod(method, codeBuilder -> { + Label nullLabel = codeBuilder.newLabel(); if (guardEventConfiguration) { - getEventConfiguration(methodVisitor); - methodVisitor.visitJumpInsn(Opcodes.IFNULL, nullLabel); + getEventConfiguration(codeBuilder); + codeBuilder.branchInstruction(Opcode.IFNULL, nullLabel); } - getEventConfiguration(methodVisitor); - invokeVirtual(methodVisitor, TYPE_EVENT_CONFIGURATION, METHOD_IS_ENABLED); - methodVisitor.visitInsn(Opcodes.IRETURN); + getEventConfiguration(codeBuilder); + invokevirtual(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_IS_ENABLED); + codeBuilder.ireturn(); if (guardEventConfiguration) { - methodVisitor.visitLabel(nullLabel); - methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitInsn(Opcodes.IRETURN); + codeBuilder.labelBinding(nullLabel); + codeBuilder.iconst_0(); + codeBuilder.ireturn(); } - methodVisitor.visitMaxs(0, 0); - methodVisitor.visitEnd(); }); } - private void updateIfStaticMethodExists(Method method, Consumer code) { + private void updateIfStaticMethodExists(MethodDesc method, Consumer code) { if (hasStaticMethod(method)) { updateMethod(method, code); } } - private boolean hasStaticMethod(Method method) { - for (MethodNode m : classNode.methods) { - if (m.name.equals(method.getName()) && m.desc.equals(method.getDescriptor())) { - return Modifier.isStatic(m.access); + 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(MethodVisitor mv) { - mv.visitLdcInsn(EventWriterKey.getKey()); - visitMethod(mv, Opcodes.INVOKESTATIC, TYPE_EVENT_WRITER_FACTORY, METHOD_GET_EVENT_WRITER_KEY); + private void getEventWriter(CodeBuilder codeBuilder) { + codeBuilder.ldc(EventWriterKey.getKey()); + invokestatic(codeBuilder, TYPE_EVENT_WRITER_FACTORY, METHOD_GET_EVENT_WRITER_KEY); } - private void visitMethod(final MethodVisitor mv, final int opcode, final Type type, final Method method) { - mv.visitMethodInsn(opcode, type.getInternalName(), method.getName(), method.getDescriptor(), false); - } - - private static void invokeStatic(MethodVisitor methodVisitor, String className, Method m) { - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, className, m.getName(), m.getDescriptor(), false); - } - - private static void invokeVirtual(MethodVisitor methodVisitor, String className, Method m) { - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, className, m.getName(), m.getDescriptor(), false); - } - - private void invokeVirtual(MethodVisitor methodVisitor, Type type, Method method) { - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, type.getInternalName(), method.getName(), method.getDescriptor(), false); - } - - private void getEventConfiguration(MethodVisitor methodVisitor) { + private void getEventConfiguration(CodeBuilder codeBuilder) { if (untypedEventConfiguration) { - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, getInternalClassName(), FIELD_EVENT_CONFIGURATION, TYPE_OBJECT_DESCRIPTOR); + codeBuilder.getstatic(getEventClassDesc(), FIELD_EVENT_CONFIGURATION.name(), TYPE_OBJECT); } else { - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, getInternalClassName(), FIELD_EVENT_CONFIGURATION, TYPE_EVENT_CONFIGURATION_DESCRIPTOR); + codeBuilder.getstatic(getEventClassDesc(), FIELD_EVENT_CONFIGURATION.name(), TYPE_EVENT_CONFIGURATION); } } @@ -754,43 +728,30 @@ public final class EventInstrumentation { updateExistingWithEmptyVoidMethod(METHOD_END); } - private final void updateExistingWithEmptyVoidMethod(Method voidMethod) { - updateMethod(voidMethod, methodVisitor -> { - methodVisitor.visitInsn(Opcodes.RETURN); + private final void updateExistingWithEmptyVoidMethod(MethodDesc voidMethod) { + updateMethod(voidMethod, codeBuilder -> { + codeBuilder.return_(); }); } - private final void updateExistingWithReturnFalse(Method voidMethod) { - updateMethod(voidMethod, methodVisitor -> { - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitInsn(Opcodes.IRETURN); + private final void updateExistingWithReturnFalse(MethodDesc voidMethod) { + updateMethod(voidMethod, codeBuilder -> { + codeBuilder.iconst_0(); + codeBuilder.ireturn(); }); } - private MethodNode getMethodNode(Method method) { - for (MethodNode m : classNode.methods) { - if (m.name.equals(method.getName()) && m.desc.equals(method.getDescriptor())) { - return m; - } - } - return null; + private Consumer findMethodUpdate(MethodModel mm) { + MethodDesc m = MethodDesc.of(mm.methodName().stringValue(), mm.methodType().stringValue()); + return methodUpdates.get(m); } - private final void updateMethod(Method method, Consumer code) { - MethodNode old = getMethodNode(method); - int index = classNode.methods.indexOf(old); - classNode.methods.remove(old); - MethodVisitor mv = classNode.visitMethod(old.access, old.name, old.desc, null, null); - mv.visitCode(); - code.accept(mv); - mv.visitMaxs(0, 0); - MethodNode newMethod = getMethodNode(method); - classNode.methods.remove(newMethod); - classNode.methods.add(index, newMethod); + private void updateMethod(MethodDesc method, Consumer codeBuilder) { + methodUpdates.put(method, codeBuilder); } - private String getInternalClassName() { - return classNode.name; + private ClassDesc getEventClassDesc() { + return classModel.thisClass().asSymbol(); } public String getEventName() { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventWriterMethod.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventWriterMethod.java index 5497aa19455..e1cbbb99d56 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventWriterMethod.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventWriterMethod.java @@ -25,38 +25,38 @@ package jdk.jfr.internal; -import jdk.internal.org.objectweb.asm.commons.Method; -import jdk.jfr.internal.EventInstrumentation.FieldInfo; -import jdk.jfr.internal.event.EventConfiguration; +import jdk.jfr.internal.util.Bytecode.FieldDesc; +import jdk.jfr.internal.util.Bytecode.MethodDesc; +import jdk.jfr.internal.util.Utils; public enum EventWriterMethod { - BEGIN_EVENT("(" + jdk.internal.org.objectweb.asm.Type.getType(EventConfiguration.class).getDescriptor() + "J)Z", "???", "beginEvent"), - END_EVENT("()Z", "???", "endEvent"), - PUT_BYTE("(B)V", "byte", "putByte"), - PUT_SHORT("(S)V", "short", "putShort"), - PUT_INT("(I)V", "int", "putInt"), - PUT_LONG("(J)V", "long", "putLong"), - PUT_FLOAT("(F)V", "float", "putFloat"), - PUT_DOUBLE("(D)V", "double", "putDouble"), - PUT_CHAR("(C)V", "char", "putChar"), - PUT_BOOLEAN("(Z)V", "boolean", "putBoolean"), - PUT_THREAD("(Ljava/lang/Thread;)V", Type.THREAD.getName(), "putThread"), - PUT_CLASS("(Ljava/lang/Class;)V", Type.CLASS.getName(), "putClass"), - PUT_STRING("(Ljava/lang/String;)V", Type.STRING.getName(), "putString"), - PUT_EVENT_THREAD("()V", Type.THREAD.getName(), "putEventThread"), - PUT_STACK_TRACE("()V", Type.TYPES_PREFIX + "StackTrace", "putStackTrace"); + BEGIN_EVENT("beginEvent", "(Ljdk/jfr/internal/event/EventConfiguration;J)Z", "???"), + END_EVENT("endEvent", "()Z", "???"), + PUT_BYTE("putByte", "(B)V", "B"), + PUT_SHORT("putShort", "(S)V", "S"), + PUT_INT("putInt", "(I)V", "I"), + PUT_LONG("putLong", "(J)V", "J"), + PUT_FLOAT("putFloat", "(F)V", "F"), + PUT_DOUBLE("putDouble", "(D)V", "D"), + PUT_CHAR("putChar", "(C)V", "C"), + PUT_BOOLEAN("putBoolean", "(Z)V", "Z"), + PUT_THREAD("putThread", "(Ljava/lang/Thread;)V", "Ljava/lang/Thread;"), + PUT_CLASS("putClass", "(Ljava/lang/Class;)V", "Ljava/lang/Class;"), + PUT_STRING("putString", "(Ljava/lang/String;)V", "Ljava/lang/String;"), + PUT_EVENT_THREAD("putEventThread", "()V", "???"), + PUT_STACK_TRACE("putStackTrace", "()V", "???"); - final Method asmMethod; - final String typeDescriptor; + final MethodDesc method; + final String fieldType; - EventWriterMethod(String paramSignature, String typeName, String methodName) { - this.typeDescriptor = ASMToolkit.getDescriptor(typeName); - this.asmMethod = new Method(methodName, paramSignature); + EventWriterMethod(String methodName, String paramType, String fieldType) { + this.fieldType = fieldType; + this.method = MethodDesc.of(methodName, paramType); } - public Method asASM() { - return asmMethod; + public MethodDesc method() { + return method; } /** @@ -67,16 +67,16 @@ public enum EventWriterMethod { * * @return the method */ - public static EventWriterMethod lookupMethod(FieldInfo field) { + public static EventWriterMethod lookupMethod(FieldDesc field) { // event thread - if (field.name().equals(EventInstrumentation.FIELD_EVENT_THREAD)) { + if (field.name().equals(Utils.FIELD_EVENT_THREAD)) { return EventWriterMethod.PUT_EVENT_THREAD; } for (EventWriterMethod m : EventWriterMethod.values()) { - if (field.descriptor().equals(m.typeDescriptor)) { + if (field.type().descriptorString().equals(m.fieldType)) { return m; } } - throw new Error("Unknown type " + field.descriptor()); + throw new Error("Unknown field type " + field.type()); } } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMUpcalls.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMUpcalls.java index 91f2e3980c2..38ac6a680bf 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMUpcalls.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMUpcalls.java @@ -28,7 +28,7 @@ import java.lang.reflect.Modifier; import jdk.jfr.internal.event.EventConfiguration; import jdk.jfr.internal.instrument.JDKEvents; -import jdk.jfr.internal.util.Utils; +import jdk.jfr.internal.util.Bytecode; /** * All upcalls from the JVM should go through this class. * @@ -75,7 +75,7 @@ final class JVMUpcalls { Logger.log(LogTag.JFR_SYSTEM, LogLevel.INFO, "Adding instrumentation to event class " + clazz.getName() + " using retransform"); EventInstrumentation ei = new EventInstrumentation(clazz.getSuperclass(), oldBytes, traceId, bootClassLoader, false); byte[] bytes = ei.buildInstrumented(); - ASMToolkit.logASM(clazz.getName(), bytes); + Bytecode.log(clazz.getName(), bytes); return bytes; } return JDKEvents.retransformCallback(clazz, oldBytes); @@ -126,7 +126,7 @@ final class JVMUpcalls { EventWriterKey.ensureEventWriterFactory(); Logger.log(LogTag.JFR_SYSTEM, LogLevel.INFO, "Adding " + (forceInstrumentation ? "forced " : "") + "instrumentation for event type " + eventName + " during initial class load"); byte[] bytes = ei.buildInstrumented(); - ASMToolkit.logASM(ei.getClassName() + "(" + traceId + ")", bytes); + Bytecode.log(ei.getClassName() + "(" + traceId + ")", bytes); return bytes; } catch (Throwable t) { Logger.log(LogTag.JFR_SYSTEM, LogLevel.WARN, "Unexpected error when adding instrumentation for event type " + eventName); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java index 42c74ee64c9..d858c56354e 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java @@ -76,26 +76,26 @@ public final class TypeLibrary { private static ValueDescriptor createStartTimeField() { var annos = createStandardAnnotations("Start Time", null); annos.add(new jdk.jfr.AnnotationElement(Timestamp.class, Timestamp.TICKS)); - return PrivateAccess.getInstance().newValueDescriptor(EventInstrumentation.FIELD_START_TIME, Type.LONG, annos, 0, false, - EventInstrumentation.FIELD_START_TIME); + return PrivateAccess.getInstance().newValueDescriptor(Utils.FIELD_START_TIME, Type.LONG, annos, 0, false, + Utils.FIELD_START_TIME); } private static ValueDescriptor createStackTraceField() { var annos = createStandardAnnotations("Stack Trace", "Stack Trace starting from the method the event was committed in"); - return PrivateAccess.getInstance().newValueDescriptor(EventInstrumentation.FIELD_STACK_TRACE, Type.STACK_TRACE, annos, 0, true, - EventInstrumentation.FIELD_STACK_TRACE); + return PrivateAccess.getInstance().newValueDescriptor(Utils.FIELD_STACK_TRACE, Type.STACK_TRACE, annos, 0, true, + Utils.FIELD_STACK_TRACE); } private static ValueDescriptor createThreadField() { var annos = createStandardAnnotations("Event Thread", "Thread in which event was committed in"); - return PrivateAccess.getInstance().newValueDescriptor(EventInstrumentation.FIELD_EVENT_THREAD, Type.THREAD, annos, 0, true, - EventInstrumentation.FIELD_EVENT_THREAD); + return PrivateAccess.getInstance().newValueDescriptor(Utils.FIELD_EVENT_THREAD, Type.THREAD, annos, 0, true, + Utils.FIELD_EVENT_THREAD); } private static ValueDescriptor createDurationField() { var annos = createStandardAnnotations("Duration", null); annos.add(new jdk.jfr.AnnotationElement(Timespan.class, Timespan.TICKS)); - return PrivateAccess.getInstance().newValueDescriptor(EventInstrumentation.FIELD_DURATION, Type.LONG, annos, 0, false, EventInstrumentation.FIELD_DURATION); + return PrivateAccess.getInstance().newValueDescriptor(Utils.FIELD_DURATION, Type.LONG, annos, 0, false, Utils.FIELD_DURATION); } public static synchronized void initialize() { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventParser.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventParser.java index c77c30eb622..451dd426368 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventParser.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventParser.java @@ -25,7 +25,7 @@ package jdk.jfr.internal.consumer; -import static jdk.jfr.internal.EventInstrumentation.FIELD_DURATION; +import static jdk.jfr.internal.util.Utils.FIELD_DURATION; import java.io.IOException; import java.util.List; diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Bytecode.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Bytecode.java new file mode 100644 index 00000000000..76dd8850a29 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Bytecode.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2023, 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.util; + +import jdk.jfr.ValueDescriptor; +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.lang.constant.MethodTypeDesc; +import java.util.Objects; +import jdk.jfr.internal.Logger; +import jdk.jfr.internal.LogLevel; +import jdk.jfr.internal.LogTag; +import jdk.internal.classfile.CodeBuilder; +import jdk.internal.classfile.ClassModel; +import jdk.internal.classfile.Classfile; +import jdk.internal.classfile.components.ClassPrinter; + +/** + * Helper class when working with bytecode. + */ +public final class Bytecode { + + private static final ClassDesc CD_Thread = classDesc(Thread.class); + + public record ClassMethodDesc(ClassDesc type, MethodDesc method) { + public static ClassMethodDesc of(Class clazz, String method, String desrciptor) { + return new ClassMethodDesc(classDesc(clazz), MethodDesc.of(method, desrciptor)); + } + } + + public record FieldDesc(ClassDesc type, String name) { + public static FieldDesc of(ClassDesc type, String name) { + return new FieldDesc(type, name); + } + + public static FieldDesc of(Class type, String name) { + return of(classDesc(type), name); + } + } + + public record MethodDesc(String name, MethodTypeDesc descriptor) { + public static MethodDesc of(String methodName, String descriptor) { + return new MethodDesc(methodName, MethodTypeDesc.ofDescriptor(descriptor)); + } + + public static MethodDesc of(String methodName, Class returnType, Class... parameters) { + ClassDesc[] parameterDesc = new ClassDesc[parameters.length]; + for (int i = 0; i < parameterDesc.length; i++) { + parameterDesc[i] = classDesc(parameters[i]); + } + ClassDesc returnDesc = classDesc(returnType); + MethodTypeDesc mtd = MethodTypeDesc.of(returnDesc, parameterDesc); + return new MethodDesc(methodName, mtd); + } + } + + public static ClassDesc classDesc(ValueDescriptor v) { + String typeName = v.getTypeName(); + return switch (typeName) { + case "boolean" -> ConstantDescs.CD_boolean; + case "byte" -> ConstantDescs.CD_byte; + case "short" -> ConstantDescs.CD_short; + case "char" -> ConstantDescs.CD_char; + case "int" -> ConstantDescs.CD_int; + case "long" -> ConstantDescs.CD_long; + case "double" -> ConstantDescs.CD_double; + case "float" -> ConstantDescs.CD_float; + case "java.lang.String" -> ConstantDescs.CD_String; + case "java.lang.Class" -> ConstantDescs.CD_Class; + case "java.lang.Thread" -> CD_Thread; + default -> throw new InternalError("Unsupported JFR type " + v.getTypeName()); + }; + } + + public static ClassDesc classDesc(Class clazz) { + return ClassDesc.ofDescriptor(clazz.descriptorString()); + } + + public static void getfield(CodeBuilder codeBuilder, ClassDesc owner, FieldDesc field) { + codeBuilder.getfield(owner, field.name(), field.type()); + } + + public static void putfield(CodeBuilder codeBuilder, ClassDesc owner, FieldDesc field) { + codeBuilder.putfield(owner, field.name(), field.type()); + } + + public static void invokestatic(CodeBuilder codeBuilder, ClassDesc owner, MethodDesc method) { + codeBuilder.invokestatic(owner, method.name(), method.descriptor()); + } + + public static void invokespecial(CodeBuilder codeBuilder, ClassDesc owner, MethodDesc method) { + codeBuilder.invokespecial(owner, method.name(), method.descriptor()); + } + + public static void invokevirtual(CodeBuilder codeBuilder, ClassDesc owner, MethodDesc method) { + codeBuilder.invokevirtual(owner, method.name(), method.descriptor()); + } + + public static void invokevirtual(CodeBuilder codeBuilder, ClassMethodDesc cmd) { + invokevirtual(codeBuilder, cmd.type(), cmd.method()); + } + + public static void unbox(CodeBuilder codeBuilder, ClassDesc type) { + if (!type.isPrimitive()) { + codeBuilder.checkcast(type); + return; + } + ClassMethodDesc unboxer = switch (type.descriptorString()) { + case "B" -> ClassMethodDesc.of(Byte.class, "byteValue", "()B"); + case "S" -> ClassMethodDesc.of(Short.class, "shortValue", "()S"); + case "C" -> ClassMethodDesc.of(Character.class, "charValue", "()C"); + case "I" -> ClassMethodDesc.of(Integer.class, "intValue", "()I"); + case "J" -> ClassMethodDesc.of(Long.class, "longValue", "()J"); + case "F" -> ClassMethodDesc.of(Float.class, "floatValue", "()F"); + case "D" -> ClassMethodDesc.of(Double.class, "doubleValue", "()D"); + case "Z" -> ClassMethodDesc.of(Boolean.class, "booleanValue", "()Z"); + default -> throw new InternalError("Unsupported JFR type " + type.descriptorString()); + }; + codeBuilder.checkcast(unboxer.type()); + invokevirtual(codeBuilder, unboxer); + } + + public static void throwException(CodeBuilder cb, ClassDesc type, String message) { + Objects.requireNonNull(message); + cb.new_(type); + cb.dup(); + cb.ldc(message); + MethodDesc md = MethodDesc.of("", void.class, String.class); + invokespecial(cb, type, md); + cb.athrow(); + } + + public static void log(String className, byte[] bytes) { + Logger.log(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.INFO, "Generated bytecode for class " + className); + if (Logger.shouldLog(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.TRACE)) { + StringBuilder out = new StringBuilder(); + out.append("Bytecode:"); + out.append(System.lineSeparator()); + ClassModel classModel = Classfile.of().parse(bytes); + ClassPrinter.toYaml(classModel, ClassPrinter.Verbosity.TRACE_ALL, out::append); + Logger.log(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.TRACE, out.toString()); + } + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Utils.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Utils.java index 1b739712985..4be05abca08 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Utils.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Utils.java @@ -56,6 +56,11 @@ import jdk.jfr.internal.settings.StackTraceSetting; import jdk.jfr.internal.settings.ThresholdSetting; public final class Utils { + public static final String FIELD_DURATION = "duration"; + public static final String FIELD_STACK_TRACE = "stackTrace"; + public static final String FIELD_START_TIME = "startTime"; + public static final String FIELD_EVENT_THREAD = "eventThread"; + private static final Object flushObject = new Object(); private static final String LEGACY_EVENT_NAME_PREFIX = "com.oracle.jdk."; diff --git a/test/jdk/jdk/jfr/jvm/TestEventWriterLog.java b/test/jdk/jdk/jfr/jvm/TestEventWriterLog.java index a242eede559..9f11ee04863 100644 --- a/test/jdk/jdk/jfr/jvm/TestEventWriterLog.java +++ b/test/jdk/jdk/jfr/jvm/TestEventWriterLog.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 @@ -37,6 +37,6 @@ public class TestEventWriterLog { public static void main(String[] args) throws Exception { ProcessBuilder pb = ProcessTools.createJavaProcessBuilder("-Xlog:jfr+system+bytecode=trace", "-XX:StartFlightRecording", "-version"); OutputAnalyzer output = new OutputAnalyzer(pb.start()); - output.shouldContain("extends jdk/jfr/events/AbstractJDKEvent"); + output.shouldContain("superclass: jdk/jfr/events/AbstractJDKEvent"); } }