1 /*
  2  * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 
 26 package jdk.jfr.internal;
 27 
 28 import static jdk.jfr.internal.util.Bytecode.invokespecial;
 29 
 30 import java.lang.constant.ClassDesc;
 31 import java.lang.constant.ConstantDescs;
 32 import java.lang.reflect.AccessFlag;
 33 import java.util.ArrayList;
 34 import java.util.List;
 35 import java.util.concurrent.atomic.AtomicLong;
 36 
 37 import java.lang.classfile.AnnotationValue;
 38 import java.lang.classfile.ClassBuilder;
 39 import java.lang.classfile.ClassFile;
 40 import java.lang.classfile.Label;
 41 import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute;
 42 
 43 import jdk.internal.misc.PreviewFeatures;
 44 
 45 import jdk.jfr.AnnotationElement;
 46 import jdk.jfr.Event;
 47 import jdk.jfr.ValueDescriptor;
 48 import jdk.jfr.internal.util.Bytecode;
 49 import jdk.jfr.internal.util.Bytecode.MethodDesc;
 50 
 51 // Helper class for building dynamic events
 52 public final class EventClassBuilder {
 53     private static final ClassDesc TYPE_EVENT = Bytecode.classDesc(Event.class);
 54     private static final ClassDesc TYPE_IOBE = Bytecode.classDesc(IndexOutOfBoundsException.class);
 55     private static final MethodDesc DEFAULT_CONSTRUCTOR = MethodDesc.of("<init>", "()V");
 56     private static final MethodDesc SET_METHOD = MethodDesc.of("set", "(ILjava/lang/Object;)V");
 57     private static final AtomicLong idCounter = new AtomicLong();
 58 
 59     private final String fullClassName;
 60     private final ClassDesc type;
 61     private final List<ValueDescriptor> fields;
 62     private final List<AnnotationElement> annotationElements;
 63 
 64     public EventClassBuilder(List<AnnotationElement> annotationElements, List<ValueDescriptor> fields) {
 65         this.fullClassName = "jdk.jfr.DynamicEvent" + idCounter.incrementAndGet();
 66         this.type = ClassDesc.of(fullClassName);
 67         this.fields = fields;
 68         this.annotationElements = annotationElements;
 69     }
 70 
 71     public Class<? extends Event> build() {
 72         byte[] bytes = ClassFile.of().build(ClassDesc.of(fullClassName), cb -> build(cb));
 73         Bytecode.log(fullClassName, bytes);
 74         return SecuritySupport.defineClass(Event.class, bytes).asSubclass(Event.class);
 75     }
 76 
 77     void build(ClassBuilder builder) {
 78         buildClassInfo(builder);
 79         buildConstructor(builder);
 80         buildFields(builder);
 81         buildSetMethod(builder);
 82     }
 83 
 84     private void buildSetMethod(ClassBuilder builder) {
 85         // void Event::set(int index, Object value);
 86         builder.withMethod(SET_METHOD.name(), SET_METHOD.descriptor(), ClassFile.ACC_PUBLIC, methodBuilder -> methodBuilder.withCode(codeBuilder -> {
 87             int index = 0;
 88             for (ValueDescriptor v : fields) {
 89                 codeBuilder.iload(1);
 90                 codeBuilder.loadConstant(index);
 91                 Label notEqual = codeBuilder.newLabel();
 92                 codeBuilder.if_icmpne(notEqual);
 93                 codeBuilder.aload(0); // this
 94                 codeBuilder.aload(2); // value
 95                 ClassDesc cd = Bytecode.classDesc(v);
 96                 Bytecode.unbox(codeBuilder, cd);
 97                 codeBuilder.putfield(type, v.getName(), cd);
 98                 codeBuilder.return_();
 99                 codeBuilder.labelBinding(notEqual);
100                 index++;
101             }
102             Bytecode.throwException(codeBuilder, TYPE_IOBE, "Index must between 0 and " + fields.size());
103         }));
104     }
105 
106     private void buildConstructor(ClassBuilder builder) {
107         builder.withMethod(ConstantDescs.INIT_NAME, ConstantDescs.MTD_void, ClassFile.ACC_PUBLIC, methodBuilder -> methodBuilder.withCode(codeBuilder -> {
108             codeBuilder.aload(0);
109             invokespecial(codeBuilder, TYPE_EVENT, DEFAULT_CONSTRUCTOR);
110             codeBuilder.return_();
111         }));
112     }
113 
114     private void buildClassInfo(ClassBuilder builder) {
115         builder.withSuperclass(Bytecode.classDesc(Event.class));
116         builder.withFlags(AccessFlag.FINAL, AccessFlag.PUBLIC, (PreviewFeatures.isEnabled() ? AccessFlag.IDENTITY : AccessFlag.SUPER));
117         List<java.lang.classfile.Annotation> annotations = new ArrayList<>();
118         for (jdk.jfr.AnnotationElement a : annotationElements) {
119             List<java.lang.classfile.AnnotationElement> list = new ArrayList<>();
120             for (ValueDescriptor v : a.getValueDescriptors()) {
121                 // ValueDescriptor can only hold primitive
122                 // No need to care about classes/enums
123                 var value = a.getValue(v.getName());
124                 var av = AnnotationValue.of(value);
125                 var ae = java.lang.classfile.AnnotationElement.of(v.getName(), av);
126                 list.add(ae);
127             }
128             ClassDesc cd = ClassDesc.of(a.getTypeName());
129             annotations.add(java.lang.classfile.Annotation.of(cd, list));
130         }
131         builder.with(RuntimeVisibleAnnotationsAttribute.of(annotations));
132     }
133 
134     private void buildFields(ClassBuilder builder) {
135         for (ValueDescriptor v : fields) {
136             builder.withField(v.getName(), Bytecode.classDesc(v), ClassFile.ACC_PRIVATE);
137             // No need to store annotations on field since they will be replaced anyway.
138         }
139     }
140 }