8318124: JFR: Rewrite instrumentation to use Class-File API

Reviewed-by: mgronlun
This commit is contained in:
Erik Gahlin 2023-10-23 16:00:14 +00:00
parent c1aeac79ba
commit 69c0ae23a3
12 changed files with 781 additions and 751 deletions

@ -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,

@ -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);
}
/**

@ -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());
};
}
}

@ -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 <init> ()");
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("<init>", "()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<ValueDescriptor> fields;
private final List<AnnotationElement> annotationElements;
public EventClassBuilder(List<AnnotationElement> annotationElements, List<ValueDescriptor> 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<? extends Event> 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<jdk.internal.classfile.Annotation> annotations = new ArrayList<>();
for (jdk.jfr.AnnotationElement a : annotationElements) {
List<jdk.internal.classfile.AnnotationElement> 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.
}
}

File diff suppressed because it is too large Load Diff

@ -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());
}
}

@ -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);

@ -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() {

@ -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;

@ -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("<init>", 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());
}
}
}

@ -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.";

@ -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");
}
}