|
| 1 | +/* |
| 2 | + * Copyright (c) 2016, 2025, 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 | +package jdk.jfr.internal; |
| 26 | + |
| 27 | +import static jdk.jfr.internal.util.Bytecode.classDesc; |
| 28 | + |
| 29 | +import java.lang.classfile.Annotation; |
| 30 | +import java.lang.classfile.AnnotationElement; |
| 31 | +import java.lang.classfile.AnnotationValue; |
| 32 | +import java.lang.classfile.Attribute; |
| 33 | +import java.lang.classfile.ClassFile; |
| 34 | +import java.lang.classfile.ClassModel; |
| 35 | +import java.lang.classfile.FieldModel; |
| 36 | +import java.lang.classfile.MethodModel; |
| 37 | +import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; |
| 38 | +import java.lang.constant.ClassDesc; |
| 39 | +import java.lang.constant.MethodTypeDesc; |
| 40 | +import java.lang.reflect.Field; |
| 41 | +import java.lang.reflect.Modifier; |
| 42 | +import java.lang.constant.ConstantDescs; |
| 43 | +import java.util.ArrayList; |
| 44 | +import java.util.HashSet; |
| 45 | +import java.util.List; |
| 46 | +import java.util.Set; |
| 47 | + |
| 48 | +import jdk.jfr.Enabled; |
| 49 | +import jdk.jfr.Name; |
| 50 | +import jdk.jfr.Registered; |
| 51 | +import jdk.jfr.SettingControl; |
| 52 | +import jdk.jfr.SettingDefinition; |
| 53 | +import jdk.jfr.internal.util.Bytecode; |
| 54 | +import jdk.jfr.internal.util.ImplicitFields; |
| 55 | +import jdk.jfr.internal.util.Bytecode.FieldDesc; |
| 56 | +import jdk.jfr.internal.util.Bytecode.MethodDesc; |
| 57 | +import jdk.jfr.internal.util.Bytecode.SettingDesc; |
| 58 | +import jdk.jfr.internal.util.Utils; |
| 59 | + |
| 60 | +final class ClassInspector { |
| 61 | + private static final ClassDesc TYPE_SETTING_DEFINITION = Bytecode.classDesc(SettingDefinition.class); |
| 62 | + private static final ClassDesc ANNOTATION_REGISTERED = classDesc(Registered.class); |
| 63 | + private static final ClassDesc ANNOTATION_NAME = classDesc(Name.class); |
| 64 | + private static final ClassDesc ANNOTATION_ENABLED = classDesc(Enabled.class); |
| 65 | + private static final ClassDesc ANNOTATION_REMOVE_FIELDS = classDesc(RemoveFields.class); |
| 66 | + |
| 67 | + private final ClassModel classModel; |
| 68 | + private final Class<?> superClass; |
| 69 | + private final boolean isJDK; |
| 70 | + private final ImplicitFields implicitFields; |
| 71 | + private final List<SettingDesc> settingsDescs = new ArrayList<>(); |
| 72 | + private final List<FieldDesc> fieldDescs = new ArrayList<>(); |
| 73 | + private final String className; |
| 74 | + |
| 75 | + ClassInspector(Class<?> superClass, byte[] bytes, boolean isJDK) { |
| 76 | + this.superClass = superClass; |
| 77 | + this.classModel = ClassFile.of().parse(bytes); |
| 78 | + this.isJDK = isJDK; |
| 79 | + this.className = classModel.thisClass().asInternalName().replace("/", "."); |
| 80 | + this.implicitFields = determineImplicitFields(); |
| 81 | + } |
| 82 | + |
| 83 | + String getClassName() { |
| 84 | + return className; |
| 85 | + } |
| 86 | + |
| 87 | + MethodDesc findStaticCommitMethod() { |
| 88 | + if (!isJDK) { |
| 89 | + return null; |
| 90 | + } |
| 91 | + StringBuilder sb = new StringBuilder(); |
| 92 | + sb.append("("); |
| 93 | + for (FieldDesc field : fieldDescs) { |
| 94 | + sb.append(field.type().descriptorString()); |
| 95 | + } |
| 96 | + sb.append(")V"); |
| 97 | + MethodDesc m = MethodDesc.of("commit", sb.toString()); |
| 98 | + for (MethodModel method : classModel.methods()) { |
| 99 | + if (m.matches(method)) { |
| 100 | + return m; |
| 101 | + } |
| 102 | + } |
| 103 | + return null; |
| 104 | + } |
| 105 | + |
| 106 | + String getEventName() { |
| 107 | + String name = annotationValue(ANNOTATION_NAME, String.class); |
| 108 | + return name == null ? getClassName() : name; |
| 109 | + } |
| 110 | + |
| 111 | + boolean isRegistered() { |
| 112 | + Boolean result = annotationValue(ANNOTATION_REGISTERED, Boolean.class); |
| 113 | + if (result != null) { |
| 114 | + return result.booleanValue(); |
| 115 | + } |
| 116 | + if (superClass != null) { |
| 117 | + Registered r = superClass.getAnnotation(Registered.class); |
| 118 | + if (r != null) { |
| 119 | + return r.value(); |
| 120 | + } |
| 121 | + } |
| 122 | + return true; |
| 123 | + } |
| 124 | + |
| 125 | + boolean isEnabled() { |
| 126 | + Boolean result = annotationValue(ANNOTATION_ENABLED, Boolean.class); |
| 127 | + if (result != null) { |
| 128 | + return result.booleanValue(); |
| 129 | + } |
| 130 | + if (superClass != null) { |
| 131 | + Enabled e = superClass.getAnnotation(Enabled.class); |
| 132 | + if (e != null) { |
| 133 | + return e.value(); |
| 134 | + } |
| 135 | + } |
| 136 | + return true; |
| 137 | + } |
| 138 | + |
| 139 | + boolean hasStaticMethod(MethodDesc method) { |
| 140 | + for (MethodModel m : classModel.methods()) { |
| 141 | + if (Modifier.isStatic(m.flags().flagsMask())) { |
| 142 | + return method.matches(m); |
| 143 | + } |
| 144 | + } |
| 145 | + return false; |
| 146 | + } |
| 147 | + |
| 148 | + static boolean isValidField(int access, ClassDesc classDesc) { |
| 149 | + String className = classDesc.packageName(); |
| 150 | + if (!className.isEmpty()) { |
| 151 | + className = className + "."; |
| 152 | + } |
| 153 | + className += classDesc.displayName(); |
| 154 | + return isValidField(access, className); |
| 155 | + } |
| 156 | + |
| 157 | + static boolean isValidField(int access, String className) { |
| 158 | + if (Modifier.isTransient(access) || Modifier.isStatic(access)) { |
| 159 | + return false; |
| 160 | + } |
| 161 | + return Type.isValidJavaFieldType(className); |
| 162 | + } |
| 163 | + |
| 164 | + List<SettingDesc> getSettings() { |
| 165 | + return settingsDescs; |
| 166 | + } |
| 167 | + |
| 168 | + List<FieldDesc> getFields() { |
| 169 | + return fieldDescs; |
| 170 | + } |
| 171 | + |
| 172 | + boolean hasDuration() { |
| 173 | + return implicitFields.hasDuration(); |
| 174 | + } |
| 175 | + |
| 176 | + boolean hasStackTrace() { |
| 177 | + return implicitFields.hasStackTrace(); |
| 178 | + } |
| 179 | + |
| 180 | + boolean hasEventThread() { |
| 181 | + return implicitFields.hasEventThread(); |
| 182 | + } |
| 183 | + |
| 184 | + ClassDesc getClassDesc() { |
| 185 | + return classModel.thisClass().asSymbol(); |
| 186 | + } |
| 187 | + |
| 188 | + ClassModel getClassModel() { |
| 189 | + return classModel; |
| 190 | + } |
| 191 | + |
| 192 | + boolean isJDK() { |
| 193 | + return isJDK; |
| 194 | + } |
| 195 | + |
| 196 | + private ImplicitFields determineImplicitFields() { |
| 197 | + if (isJDK) { |
| 198 | + Class<?> eventClass = MirrorEvents.find(isJDK, getClassName()); |
| 199 | + if (eventClass != null) { |
| 200 | + return new ImplicitFields(eventClass); |
| 201 | + } |
| 202 | + } |
| 203 | + ImplicitFields ifs = new ImplicitFields(superClass); |
| 204 | + String[] value = annotationValue(ANNOTATION_REMOVE_FIELDS, String[].class); |
| 205 | + if (value != null) { |
| 206 | + ifs.removeFields(value); |
| 207 | + } |
| 208 | + return ifs; |
| 209 | + } |
| 210 | + |
| 211 | + private List<AnnotationValue> getAnnotationValues(ClassDesc classDesc) { |
| 212 | + List<AnnotationValue> list = new ArrayList<>(); |
| 213 | + for (Attribute<?> attribute: classModel.attributes()) { |
| 214 | + if (attribute instanceof RuntimeVisibleAnnotationsAttribute rvaa) { |
| 215 | + for (Annotation a : rvaa.annotations()) { |
| 216 | + if (a.classSymbol().equals(classDesc) && a.elements().size() == 1) { |
| 217 | + AnnotationElement ae = a.elements().getFirst(); |
| 218 | + if (ae.name().equalsString("value")) { |
| 219 | + list.add(ae.value()); |
| 220 | + } |
| 221 | + } |
| 222 | + } |
| 223 | + } |
| 224 | + } |
| 225 | + return list; |
| 226 | + } |
| 227 | + |
| 228 | + @SuppressWarnings("unchecked") |
| 229 | + // Only supports String, String[] and Boolean values |
| 230 | + private <T> T annotationValue(ClassDesc classDesc, Class<T> type) { |
| 231 | + for (AnnotationValue a : getAnnotationValues(classDesc)) { |
| 232 | + if (a instanceof AnnotationValue.OfBoolean ofb && type.equals(Boolean.class)) { |
| 233 | + Boolean b = ofb.booleanValue(); |
| 234 | + return (T) b; |
| 235 | + } |
| 236 | + if (a instanceof AnnotationValue.OfString ofs && type.equals(String.class)) { |
| 237 | + String s = ofs.stringValue(); |
| 238 | + return (T) s; |
| 239 | + } |
| 240 | + if (a instanceof AnnotationValue.OfArray ofa && type.equals(String[].class)) { |
| 241 | + List<AnnotationValue> list = ofa.values(); |
| 242 | + String[] array = new String[list.size()]; |
| 243 | + int index = 0; |
| 244 | + for (AnnotationValue av : list) { |
| 245 | + var avs = (AnnotationValue.OfString) av; |
| 246 | + array[index++] = avs.stringValue(); |
| 247 | + } |
| 248 | + return (T) array; |
| 249 | + } |
| 250 | + } |
| 251 | + return null; |
| 252 | + } |
| 253 | + |
| 254 | + void buildSettings() { |
| 255 | + Set<String> foundMethods = new HashSet<>(); |
| 256 | + buildClassSettings(foundMethods); |
| 257 | + buildSuperClassSettings(foundMethods); |
| 258 | + } |
| 259 | + |
| 260 | + private void buildClassSettings(Set<String> foundMethods) { |
| 261 | + for (MethodModel m : classModel.methods()) { |
| 262 | + for (Attribute<?> attribute : m.attributes()) { |
| 263 | + if (attribute instanceof RuntimeVisibleAnnotationsAttribute rvaa) { |
| 264 | + for (Annotation a : rvaa.annotations()) { |
| 265 | + // We can't really validate the method at this |
| 266 | + // stage. We would need to check that the parameter |
| 267 | + // is an instance of SettingControl. |
| 268 | + if (a.classSymbol().equals(TYPE_SETTING_DEFINITION)) { |
| 269 | + String name = m.methodName().stringValue(); |
| 270 | + // Use @Name if it exists |
| 271 | + for (Annotation nameCandidate : rvaa.annotations()) { |
| 272 | + if (nameCandidate.className().equalsString(ANNOTATION_NAME.descriptorString())) { |
| 273 | + if (nameCandidate.elements().size() == 1) { |
| 274 | + AnnotationElement ae = nameCandidate.elements().getFirst(); |
| 275 | + if (ae.name().equalsString("value")) { |
| 276 | + if (ae.value() instanceof AnnotationValue.OfString s) { |
| 277 | + name = Utils.validJavaIdentifier(s.stringValue(), name); |
| 278 | + } |
| 279 | + } |
| 280 | + } |
| 281 | + } |
| 282 | + } |
| 283 | + // Add setting if method returns boolean and has one parameter |
| 284 | + MethodTypeDesc mtd = m.methodTypeSymbol(); |
| 285 | + if (ConstantDescs.CD_boolean.equals(mtd.returnType())) { |
| 286 | + if (mtd.parameterList().size() == 1) { |
| 287 | + ClassDesc type = mtd.parameterList().getFirst(); |
| 288 | + if (type.isClassOrInterface()) { |
| 289 | + String methodName = m.methodName().stringValue(); |
| 290 | + foundMethods.add(methodName); |
| 291 | + settingsDescs.add(new SettingDesc(type, methodName)); |
| 292 | + } |
| 293 | + } |
| 294 | + } |
| 295 | + } |
| 296 | + } |
| 297 | + } |
| 298 | + } |
| 299 | + } |
| 300 | + } |
| 301 | + |
| 302 | + private void buildSuperClassSettings(Set<String> foundMethods) { |
| 303 | + for (Class<?> c = superClass; jdk.internal.event.Event.class != c; c = c.getSuperclass()) { |
| 304 | + for (java.lang.reflect.Method method : c.getDeclaredMethods()) { |
| 305 | + if (!foundMethods.contains(method.getName())) { |
| 306 | + buildSettingsMethod(foundMethods, method); |
| 307 | + } |
| 308 | + } |
| 309 | + } |
| 310 | + } |
| 311 | + |
| 312 | + private void buildSettingsMethod(Set<String> foundMethods, java.lang.reflect.Method method) { |
| 313 | + // Skip private methods in base classes |
| 314 | + if (!Modifier.isPrivate(method.getModifiers())) { |
| 315 | + if (method.getReturnType().equals(Boolean.TYPE)) { |
| 316 | + if (method.getParameterCount() == 1) { |
| 317 | + Class<?> type = method.getParameters()[0].getType(); |
| 318 | + if (SettingControl.class.isAssignableFrom(type)) { |
| 319 | + ClassDesc paramType = Bytecode.classDesc(type); |
| 320 | + foundMethods.add(method.getName()); |
| 321 | + settingsDescs.add(new SettingDesc(paramType, method.getName())); |
| 322 | + } |
| 323 | + } |
| 324 | + } |
| 325 | + } |
| 326 | + } |
| 327 | + |
| 328 | + void buildFields() { |
| 329 | + Set<String> foundFields = new HashSet<>(); |
| 330 | + // These two fields are added by native as 'transient' so they will be |
| 331 | + // ignored by the loop below. |
| 332 | + // The benefit of adding them manually is that we can |
| 333 | + // control in which order they occur and we can add @Name, @Description |
| 334 | + // in Java, instead of in native. It also means code for adding implicit |
| 335 | + // fields for native can be reused by Java. |
| 336 | + fieldDescs.add(ImplicitFields.FIELD_START_TIME); |
| 337 | + if (implicitFields.hasDuration()) { |
| 338 | + fieldDescs.add(ImplicitFields.FIELD_DURATION); |
| 339 | + } |
| 340 | + for (FieldModel field : classModel.fields()) { |
| 341 | + if (!foundFields.contains(field.fieldName().stringValue()) && isValidField(field.flags().flagsMask(), field.fieldTypeSymbol())) { |
| 342 | + fieldDescs.add(FieldDesc.of(field.fieldTypeSymbol(), field.fieldName().stringValue())); |
| 343 | + foundFields.add(field.fieldName().stringValue()); |
| 344 | + } |
| 345 | + } |
| 346 | + for (Class<?> c = superClass; jdk.internal.event.Event.class != c; c = c.getSuperclass()) { |
| 347 | + for (Field field : c.getDeclaredFields()) { |
| 348 | + // Skip private fields in base classes |
| 349 | + if (!Modifier.isPrivate(field.getModifiers())) { |
| 350 | + if (isValidField(field.getModifiers(), field.getType().getName())) { |
| 351 | + String fieldName = field.getName(); |
| 352 | + if (!foundFields.contains(fieldName)) { |
| 353 | + fieldDescs.add(FieldDesc.of(field.getType(), fieldName)); |
| 354 | + foundFields.add(fieldName); |
| 355 | + } |
| 356 | + } |
| 357 | + } |
| 358 | + } |
| 359 | + } |
| 360 | + } |
| 361 | +} |
0 commit comments