From 7462429174b66ff85a7ea265ed0b4cfb3dcf6e8e Mon Sep 17 00:00:00 2001 From: kxu Date: Wed, 27 Nov 2019 16:53:17 -0500 Subject: [PATCH 01/27] WIP: BCI agent path-syntax evaluation --- .../org/openjdk/jmc/agent/IAttribute.java | 43 +++++++ .../java/org/openjdk/jmc/agent/Parameter.java | 2 +- .../jmc/agent/TransformDescriptor.java | 4 +- .../org/openjdk/jmc/agent/Transformer.java | 26 +++-- .../java/org/openjdk/jmc/agent/Watch.java | 61 ++++++++++ .../agent/impl/DefaultTransformRegistry.java | 54 ++++++++- .../jmc/agent/jfr/JFRTransformDescriptor.java | 9 +- .../jfrnext/impl/JFRNextClassVisitor.java | 10 +- .../impl/JFRNextEventClassGenerator.java | 24 ++-- .../jfrnext/impl/JFRNextMethodAdvisor.java | 105 +++++++++++++++++- .../jmc/agent/util/FieldReference.java | 60 ++++++++++ .../jmc/agent/util/InspectionClassLoader.java | 46 ++++++++ .../jmc/agent/util/ReferenceChain.java | 80 +++++++++++++ .../org/openjdk/jmc/agent/util/TypeUtils.java | 88 ++++++++++++++- 14 files changed, 576 insertions(+), 36 deletions(-) create mode 100644 core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/IAttribute.java create mode 100644 core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Watch.java create mode 100644 core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/FieldReference.java create mode 100644 core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java create mode 100644 core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/ReferenceChain.java diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/IAttribute.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/IAttribute.java new file mode 100644 index 000000000..fac316408 --- /dev/null +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/IAttribute.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at http://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.openjdk.jmc.agent; + +public interface IAttribute { + String getName(); + String getFieldName(); + String getDescription(); + String getContentType(); + String getRelationKey(); + String getConverterClassName(); +} diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Parameter.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Parameter.java index 2c2852121..517fc2a06 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Parameter.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Parameter.java @@ -37,7 +37,7 @@ /** * Metadata for a parameter to be logged by the agent. */ -public final class Parameter { +public final class Parameter implements IAttribute { public static final int INDEX_INVALID = -2; public static final int INDEX_RETURN = -1; diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/TransformDescriptor.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/TransformDescriptor.java index b755a642d..e925809d3 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/TransformDescriptor.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/TransformDescriptor.java @@ -105,8 +105,8 @@ protected String getTransformationAttribute(String attribute) { * @return the instantiated {@link TransformDescriptor}. */ public static TransformDescriptor create( - String id, String internalName, Method method, Map values, List parameters) { - return new JFRTransformDescriptor(id, internalName, method, values, parameters); + String id, String internalName, Method method, Map values, List parameters, List watches) { + return new JFRTransformDescriptor(id, internalName, method, values, parameters, watches); } @Override diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java index 4d5812580..dde65584a 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java @@ -47,8 +47,11 @@ import org.openjdk.jmc.agent.jfr.VersionResolver.JFRVersion; import org.openjdk.jmc.agent.jfr.impl.JFRClassVisitor; import org.openjdk.jmc.agent.jfrnext.impl.JFRNextClassVisitor; +import org.openjdk.jmc.agent.util.InspectionClassLoader; +import org.openjdk.jmc.agent.util.TypeUtils; public class Transformer implements ClassFileTransformer { + private TransformRegistry registry; public Transformer(TransformRegistry registry) { @@ -65,16 +68,23 @@ public byte[] transform( registry.storeClassPreInstrumentation(className, classfileBuffer); byte[] instrumentedClassfileBuffer = registry.isRevertIntrumentation() ? registry.getClassPreInstrumentation(className) : classfileBuffer; - return doTransforms(registry.getTransformData(className), instrumentedClassfileBuffer, loader, protectionDomain); + try { + // Don't reuse this class loader instance. We want the loader and its class to unload after we're done. + classBeingRedefined = new InspectionClassLoader(loader, TypeUtils.getCanonicalName(className), classfileBuffer, protectionDomain) + .loadClass(TypeUtils.getCanonicalName(className)); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e); // This should not happen + } + return doTransforms(registry.getTransformData(className), instrumentedClassfileBuffer, loader, classBeingRedefined, protectionDomain); } private byte[] doTransforms( List transformDataList, byte[] classfileBuffer, ClassLoader definingClassLoader, - ProtectionDomain protectionDomain) { + Class classBeingRedefined, ProtectionDomain protectionDomain) { for (TransformDescriptor td : transformDataList) { if (td.isPendingTransforms()) { // FIXME: Optimization, should do all transforms to one class in one go, instead of creating one class writer per transform. - classfileBuffer = doTransform(td, classfileBuffer, definingClassLoader, protectionDomain); + classfileBuffer = doTransform(td, classfileBuffer, definingClassLoader, classBeingRedefined, protectionDomain); td.setPendingTransforms(false); } } @@ -82,13 +92,13 @@ private byte[] doTransforms( } private byte[] doTransform( - TransformDescriptor td, byte[] classfileBuffer, ClassLoader definingClassLoader, + TransformDescriptor td, byte[] classfileBuffer, ClassLoader definingClassLoader, Class classBeingRedefined, ProtectionDomain protectionDomain) { - return doJFRLogging((JFRTransformDescriptor) td, classfileBuffer, definingClassLoader, protectionDomain); + return doJFRLogging((JFRTransformDescriptor) td, classfileBuffer, definingClassLoader, classBeingRedefined, protectionDomain); } private byte[] doJFRLogging( - JFRTransformDescriptor td, byte[] classfileBuffer, ClassLoader definingClassLoader, + JFRTransformDescriptor td, byte[] classfileBuffer, ClassLoader definingClassLoader, Class classBeingRedefined, ProtectionDomain protectionDomain) { if (VersionResolver.getAvailableJFRVersion() == JFRVersion.NONE) { Logger.getLogger(getClass().getName()).log(Level.SEVERE, @@ -98,8 +108,8 @@ private byte[] doJFRLogging( try { ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassVisitor visitor = VersionResolver.getAvailableJFRVersion() == JFRVersion.JFRNEXT - ? new JFRNextClassVisitor(classWriter, td, definingClassLoader, protectionDomain) - : new JFRClassVisitor(classWriter, td, definingClassLoader, protectionDomain); + ? new JFRNextClassVisitor(classWriter, td, definingClassLoader, classBeingRedefined, protectionDomain) + : new JFRClassVisitor(classWriter, td, definingClassLoader, protectionDomain); // TODO: support path-syntax evaluation for legacy API ClassReader reader = new ClassReader(classfileBuffer); reader.accept(visitor, 0); return classWriter.toByteArray(); diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Watch.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Watch.java new file mode 100644 index 000000000..bb5dd7c60 --- /dev/null +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Watch.java @@ -0,0 +1,61 @@ +package org.openjdk.jmc.agent; + +import org.openjdk.jmc.agent.util.ReferenceChain; +import org.openjdk.jmc.agent.util.TypeUtils; + +public class Watch implements IAttribute { + + private final String name; + private final String expression; + private final String fieldName; + private final String description; + private final String contentType; + private final String relationKey; + private final String converterClassName; + + public Watch(String name, String expression, String description, String contentType, String relationKey, String converterClassName) { + this.name = name; + this.expression = expression; + this.description = description; + this.contentType = contentType; + this.relationKey = relationKey; + this.converterClassName = converterClassName; + this.fieldName = "field" + TypeUtils.deriveIdentifierPart(name); + + // TODO: validate expression + } + + @Override + public String getName() { + return this.name; + } + + @Override + public String getFieldName() { + return this.fieldName; + } + + @Override + public String getDescription() { + return this.description; + } + + @Override + public String getContentType() { + return this.contentType; + } + + @Override + public String getRelationKey() { + return this.relationKey; + } + + @Override + public String getConverterClassName() { + return this.converterClassName; + } + + public ReferenceChain resolveReferenceChain(Class callerClass) throws NoSuchFieldException { + return new ReferenceChain(callerClass, expression); + } +} diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/impl/DefaultTransformRegistry.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/impl/DefaultTransformRegistry.java index 0e1945393..d82c8dbe9 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/impl/DefaultTransformRegistry.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/impl/DefaultTransformRegistry.java @@ -52,12 +52,15 @@ import org.openjdk.jmc.agent.Parameter; import org.openjdk.jmc.agent.TransformDescriptor; import org.openjdk.jmc.agent.TransformRegistry; +import org.openjdk.jmc.agent.Watch; import org.openjdk.jmc.agent.jfr.JFRTransformDescriptor; +import org.openjdk.jmc.agent.util.TypeUtils; public class DefaultTransformRegistry implements TransformRegistry { private static final String XML_ATTRIBUTE_NAME_ID = "id"; //$NON-NLS-1$ private static final String XML_ELEMENT_NAME_EVENT = "event"; //$NON-NLS-1$ private static final String XML_ELEMENT_METHOD_NAME = "method"; //$NON-NLS-1$ + private static final String XML_ELEMENT_WATCH_NAME = "watch"; //$NON-NLS-1$ private static final String XML_ELEMENT_PARAMETER_NAME = "parameter"; //$NON-NLS-1$ // Global override section @@ -149,6 +152,7 @@ private static TransformDescriptor parseTransformData( streamReader.next(); Map values = new HashMap<>(); List parameters = new LinkedList<>(); + List watches = new LinkedList<>(); Method method = null; while (streamReader.hasNext()) { if (streamReader.isStartElement()) { @@ -157,6 +161,10 @@ private static TransformDescriptor parseTransformData( method = parseMethod(streamReader, parameters); continue; } + if (XML_ELEMENT_WATCH_NAME.equals(name)) { + watches.add(parseWatch(streamReader)); + continue; + } streamReader.next(); if (streamReader.hasText()) { String value = streamReader.getText(); @@ -174,7 +182,7 @@ private static TransformDescriptor parseTransformData( streamReader.next(); } transfer(globalDefaults, values); - return TransformDescriptor.create(id, getInternalName(values.get("class")), method, values, parameters); //$NON-NLS-1$ + return TransformDescriptor.create(id, TypeUtils.getInternalName(values.get("class")), method, values, parameters, watches); //$NON-NLS-1$ } private static void transfer(HashMap globalDefaults, Map values) { @@ -242,7 +250,7 @@ private static Parameter parseParameter(int index, XMLStreamReader streamReader) contentType = value; } else if ("relationkey".equals(key)) { //$NON-NLS-1$ relationKey = value; - } else if ("converter".equals(key)) { + } else if ("converter".equals(key)) { //$NON-NLS-1$ converterClassName = value; } } @@ -256,8 +264,46 @@ private static Parameter parseParameter(int index, XMLStreamReader streamReader) return new Parameter(index, name, description, contentType, relationKey, converterClassName); } - private static String getInternalName(String className) { - return className.replace('.', '/'); + private static Watch parseWatch(XMLStreamReader streamReader) throws XMLStreamException { + streamReader.next(); + String name = null; + String expression = null; + String description = null; + String contentType = null; + String relationKey = null; + String converterClassName = null; + + while (streamReader.hasNext()) { + if (streamReader.isStartElement()) { + String key = streamReader.getName().getLocalPart(); + streamReader.next(); + if (streamReader.hasText()) { + String value = streamReader.getText(); + if (value != null) { + value = value.trim(); + } + if ("name".equals(key)) { //$NON-NLS-1$ + name = value; + } else if ("expression".equals(key)) { + expression = value; + } else if ("description".equals(key)) { //$NON-NLS-1$ + description = value; + } else if ("contenttype".equals(key)) { //$NON-NLS-1$ + contentType = value; + } else if ("relationkey".equals(key)) { //$NON-NLS-1$ + relationKey = value; + } else if ("converter".equals(key)) { //$NON-NLS-1$ + converterClassName = value; + } + } + } else if (streamReader.isEndElement()) { + if (XML_ELEMENT_WATCH_NAME.equals(streamReader.getName().getLocalPart())) { + break; + } + } + streamReader.next(); + } + return new Watch(name, expression, description, contentType, relationKey, converterClassName); } private static Method parseMethod(XMLStreamReader streamReader, List parameters) diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/JFRTransformDescriptor.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/JFRTransformDescriptor.java index 89158c6e0..74976c9be 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/JFRTransformDescriptor.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/JFRTransformDescriptor.java @@ -41,6 +41,7 @@ import org.openjdk.jmc.agent.Method; import org.openjdk.jmc.agent.Parameter; import org.openjdk.jmc.agent.TransformDescriptor; +import org.openjdk.jmc.agent.Watch; import org.openjdk.jmc.agent.util.TypeUtils; public class JFRTransformDescriptor extends TransformDescriptor { @@ -60,9 +61,10 @@ public class JFRTransformDescriptor extends TransformDescriptor { private final boolean allowToString; private final boolean allowConverter; private final List parameters; + private final List watches; public JFRTransformDescriptor(String id, String className, Method method, - Map transformationAttributes, List parameters) { + Map transformationAttributes, List parameters, List watches) { super(id, className, method, transformationAttributes); classPrefix = initializeClassPrefix(); eventName = initializeEventName(); @@ -74,6 +76,7 @@ public JFRTransformDescriptor(String id, String className, Method method, allowToString = getBoolean(ATTRIBUTE_ALLOW_TO_STRING, false); allowConverter = getBoolean(ATTRIBUTE_ALLOW_CONVERTER, false); this.parameters = parameters; + this.watches = watches; } public String getEventClassName() { @@ -175,6 +178,10 @@ public List getParameters() { return parameters; } + public List getWatches() { + return watches; + } + public boolean isAllowedFieldType(Type type) { if (isAllowToString()) { return true; diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextClassVisitor.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextClassVisitor.java index 68109d3c6..e769a30bf 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextClassVisitor.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextClassVisitor.java @@ -47,13 +47,15 @@ public class JFRNextClassVisitor extends ClassVisitor { private final JFRTransformDescriptor transformDescriptor; private final ClassLoader definingClassLoader; + private final Class classBeingRedefined; private final ProtectionDomain protectionDomain; public JFRNextClassVisitor(ClassWriter cv, JFRTransformDescriptor descriptor, ClassLoader definingLoader, - ProtectionDomain protectionDomain) { + Class classBeingRedefined, ProtectionDomain protectionDomain) { super(Opcodes.ASM5, cv); this.transformDescriptor = descriptor; this.definingClassLoader = definingLoader; + this.classBeingRedefined = classBeingRedefined; this.protectionDomain = protectionDomain; } @@ -62,7 +64,7 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); if (name.equals(transformDescriptor.getMethod().getName()) && desc.equals(transformDescriptor.getMethod().getSignature())) { - return new JFRNextMethodAdvisor(transformDescriptor, Opcodes.ASM5, mv, access, name, desc); + return new JFRNextMethodAdvisor(transformDescriptor, classBeingRedefined, Opcodes.ASM5, mv, access, name, desc); } return mv; } @@ -87,9 +89,9 @@ private void reflectiveRegister(Class generateEventClass) throws Exception { private Class generateEventClass() throws Exception { try { - return Class.forName(transformDescriptor.getEventClassName().replace('/', '.')); + return Class.forName(TypeUtils.getCanonicalName(transformDescriptor.getEventClassName())); } catch (ClassNotFoundException e) { - byte[] eventClass = JFRNextEventClassGenerator.generateEventClass(transformDescriptor); + byte[] eventClass = JFRNextEventClassGenerator.generateEventClass(transformDescriptor, classBeingRedefined); return TypeUtils.defineClass(transformDescriptor.getEventClassName(), eventClass, 0, eventClass.length, definingClassLoader, protectionDomain); } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextEventClassGenerator.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextEventClassGenerator.java index c1b22d9d5..887c3db55 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextEventClassGenerator.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextEventClassGenerator.java @@ -42,14 +42,16 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.openjdk.jmc.agent.Agent; +import org.openjdk.jmc.agent.IAttribute; import org.openjdk.jmc.agent.Parameter; +import org.openjdk.jmc.agent.Watch; import org.openjdk.jmc.agent.jfr.JFRTransformDescriptor; import org.openjdk.jmc.agent.util.TypeUtils; public class JFRNextEventClassGenerator { private static final String CLASS_EVENT = "jdk/jfr/Event"; //$NON-NLS-1$ - public static byte[] generateEventClass(JFRTransformDescriptor td) throws Exception { + public static byte[] generateEventClass(JFRTransformDescriptor td, Class classBeingRedefined) throws Exception { ClassWriter cw = new ClassWriter(0); // FIXME: Perhaps switch to Opcodes V9 when there is one. cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, td.getEventClassName(), null, CLASS_EVENT, null); @@ -58,13 +60,13 @@ public static byte[] generateEventClass(JFRTransformDescriptor td) throws Except String parameterizedClassName = TypeUtils.parameterize(td.getEventClassName()); generateClassAnnotations(cw, td); - generateAttributeFields(cw, td); + generateAttributeFields(cw, td, classBeingRedefined); generateInit(cw, td.getEventClassName(), parameterizedClassName); cw.visitEnd(); return cw.toByteArray(); } - private static void generateAttributeFields(ClassWriter cw, JFRTransformDescriptor td) { + private static void generateAttributeFields(ClassWriter cw, JFRTransformDescriptor td, Class classBeingRedefined) throws NoSuchFieldException { Type[] args = Type.getArgumentTypes(td.getMethod().getSignature()); for (Parameter param : td.getParameters()) { if (param.isReturn()) { @@ -73,34 +75,38 @@ private static void generateAttributeFields(ClassWriter cw, JFRTransformDescript createField(cw, td, param, args[param.getIndex()]); } } + + for (Watch watch : td.getWatches()) { + createField(cw, td, watch, watch.resolveReferenceChain(classBeingRedefined).getType()); + } } - private static void createField(ClassWriter cw, JFRTransformDescriptor td, Parameter param, Type type) { + private static void createField(ClassWriter cw, JFRTransformDescriptor td, IAttribute attribute, Type type) { if (!td.isAllowedFieldType(type)) { Logger.getLogger(JFRNextEventClassGenerator.class.getName()) - .warning("Skipped generating field in event class for parameter " + param + " and type " + type //$NON-NLS-1$ //$NON-NLS-2$ + .warning("Skipped generating field in event class for attribute " + attribute + " and type " + type //$NON-NLS-1$ //$NON-NLS-2$ + " because of configuration settings!"); //$NON-NLS-1$ return; } String fieldType = getFieldType(type); - FieldVisitor fv = cw.visitField(Opcodes.ACC_PROTECTED, param.getFieldName(), fieldType, null, null); + FieldVisitor fv = cw.visitField(Opcodes.ACC_PROTECTED, attribute.getFieldName(), fieldType, null, null); // Name AnnotationVisitor av = fv.visitAnnotation("Ljdk/jfr/Label;", true); - av.visit("value", param.getName()); + av.visit("value", attribute.getName()); av.visitEnd(); // Description av = fv.visitAnnotation("Ljdk/jfr/Description;", true); - av.visit("value", param.getDescription()); + av.visit("value", attribute.getDescription()); av.visitEnd(); // "ContentType" // We support the old JDK 7 style content types transparently. // We also support user defined content types and a single string value annotation parameter to the annotation. - String contentTypeAnnotation = getContentTypeAnnotation(param.getContentType()); + String contentTypeAnnotation = getContentTypeAnnotation(attribute.getContentType()); if (contentTypeAnnotation != null) { String[] contentTypeAnnotationInfo = contentTypeAnnotation.split(";"); av = fv.visitAnnotation(contentTypeAnnotationInfo[0] + ";", true); diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java index 986c0560f..19f89f8d3 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java @@ -37,10 +37,18 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.AdviceAdapter; +import org.openjdk.jmc.agent.IAttribute; import org.openjdk.jmc.agent.Parameter; +import org.openjdk.jmc.agent.Watch; import org.openjdk.jmc.agent.jfr.JFRTransformDescriptor; +import org.openjdk.jmc.agent.util.FieldReference; +import org.openjdk.jmc.agent.util.ReferenceChain; import org.openjdk.jmc.agent.util.TypeUtils; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + /** * Code emitter for JFR next, i.e. the version of JFR distributed with JDK 9 and later. */ @@ -48,6 +56,7 @@ public class JFRNextMethodAdvisor extends AdviceAdapter { private static final String THROWABLE_BINARY_NAME = "java/lang/Throwable"; //$NON-NLS-1$ private final JFRTransformDescriptor transformDescriptor; + private final Class classBeingRedefined; private final Type[] argumentTypesRef; private final Type returnTypeRef; private final Type eventType; @@ -58,10 +67,11 @@ public class JFRNextMethodAdvisor extends AdviceAdapter { private boolean shouldInstrumentThrow; - protected JFRNextMethodAdvisor(JFRTransformDescriptor transformDescriptor, int api, MethodVisitor mv, int access, - String name, String desc) { + protected JFRNextMethodAdvisor(JFRTransformDescriptor transformDescriptor, Class classBeingRedefined, int api, MethodVisitor mv, int access, + String name, String desc) { super(api, mv, access, name, desc); this.transformDescriptor = transformDescriptor; + this.classBeingRedefined = classBeingRedefined; // These are not accessible from the super type (made private), so must save an extra reference. :/ this.argumentTypesRef = Type.getArgumentTypes(desc); this.returnTypeRef = Type.getReturnType(desc); @@ -111,17 +121,102 @@ private void createEvent() { if (transformDescriptor.isAllowedFieldType(argumentType)) { mv.visitInsn(DUP); loadArg(param.getIndex()); - writeParameter(param, argumentType); + writeAttribute(param, argumentType); } } } + for (Watch watch : transformDescriptor.getWatches()) { + ReferenceChain refChain; + try { + refChain = watch.resolveReferenceChain(classBeingRedefined).normalize(); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); // TODO: figure out what to do with this error + } + if (transformDescriptor.isAllowedFieldType(refChain.getType())) { + mv.visitInsn(DUP); + loadWatch(watch, refChain); + writeAttribute(watch, refChain.getType()); + } + } + mv.visitMethodInsn(INVOKEVIRTUAL, transformDescriptor.getEventClassName(), "begin", "()V", false); //$NON-NLS-1$ //$NON-NLS-2$ eventLocal = newLocal(eventType); mv.visitVarInsn(ASTORE, eventLocal); } - private void writeParameter(Parameter param, Type type) { + private void loadWatch(Watch watch, ReferenceChain refChain) { + Type type = refChain.getType(); + boolean isStatic = Modifier.isStatic(getAccess()); + Label nullCase = new Label(); + Label continueCase = new Label(); + List localVarVerifications = new ArrayList<>(); + if (!isStatic) { + localVarVerifications.add(Type.getInternalName(classBeingRedefined)); // "this" + } + for (Type argType : argumentTypesRef) { + localVarVerifications.add(TypeUtils.getFrameVerificationType(argType)); + } + + // Assumes the reference chain is normalized already. See ReferenceChain.normalize() + List refs = refChain.getReferences(); + for (int i = 0; i < refs.size(); i++) { + if (i != 0) { + // null check + mv.visitInsn(DUP); + mv.visitJumpInsn(IFNULL, nullCase); + } + + FieldReference ref = refs.get(i); + if (ref instanceof FieldReference.ThisReference) { + if (isStatic) { + throw new IllegalStateException("unexpected \"this\" reference in a static method"); + } + if (i != 0) { + throw new IllegalStateException("unexpected position of \"this\" reference"); + } + loadArg(0); // "this" + continue; + } + + int opcode; + if (Modifier.isStatic(ref.getModifiers())) { + if (i != 0) { + throw new IllegalStateException("unexpected position of a static reference"); + } + opcode = GETSTATIC; + } else { + if (i == 0 && isStatic) { + throw new IllegalStateException("unexpected position of a dynamic reference in a static method"); + } + opcode = GETFIELD; + } + + mv.visitFieldInsn(opcode, ref.getMemberingType().getInternalName(), ref.getName(), ref.getType().getDescriptor()); + } + // loaded value, jump to writing attribute + mv.visitJumpInsn(GOTO, continueCase); + + // null reference on path, load zero value + mv.visitLabel(nullCase); + mv.visitFrame(F_NEW, + localVarVerifications.size(), + localVarVerifications.toArray(), + 4, + new Object[]{eventType.getInternalName(), eventType.getInternalName(), eventType.getInternalName(), Type.getInternalName(Object.class)}); + mv.visitInsn(POP); + mv.visitInsn(TypeUtils.getConstZeroOpcode(type)); + + // must verify frame for jump targets + mv.visitLabel(continueCase); + mv.visitFrame(F_NEW, + localVarVerifications.size(), + localVarVerifications.toArray(), + 4, + new Object[]{eventType.getInternalName(), eventType.getInternalName(), eventType.getInternalName(), TypeUtils.getFrameVerificationType(type)}); + } + + private void writeAttribute(IAttribute param, Type type) { if (TypeUtils.shouldStringify(param, type)) { TypeUtils.stringify(mv, param, type); type = TypeUtils.STRING_TYPE; @@ -155,7 +250,7 @@ private void emitSettingReturnParam(int opcode, Parameter returnParam) { dupX2(); pop(); } - writeParameter(returnParam, returnTypeRef); + writeAttribute(returnParam, returnTypeRef); } private void commitEvent() { diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/FieldReference.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/FieldReference.java new file mode 100644 index 000000000..97cd02ba8 --- /dev/null +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/FieldReference.java @@ -0,0 +1,60 @@ +package org.openjdk.jmc.agent.util; + +import java.lang.reflect.Field; + +import org.objectweb.asm.Type; + +public class FieldReference { + private final Class memberingClass; + private final Field field; + + public FieldReference(Class memberingClass, Field type) { + this.memberingClass = memberingClass; + this.field = type; + } + + public Class getMemberingClass() { + return memberingClass; + } + + public Type getMemberingType() { + return Type.getType(getMemberingClass()); + } + + public Field getField() { + return field; + } + + public String getName() { + return getField().getName(); + } + + public Type getType() { + return Type.getType(getField().getType()); + } + + public int getModifiers() { + return getField().getModifiers(); + } + + public static class ThisReference extends FieldReference { + public ThisReference(Class memberingClass) { + super(memberingClass, null); + } + + @Override + public String getName() { + return null; + } + + @Override + public Type getType() { + return getMemberingType(); + } + + @Override + public int getModifiers() { + return 0; + } + } +} diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java new file mode 100644 index 000000000..ec1c82a04 --- /dev/null +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java @@ -0,0 +1,46 @@ +package org.openjdk.jmc.agent.util; + +import java.security.ProtectionDomain; + +// One-time use loader for reflective class inspection. An InspectionClassLoader only loads one class. +public class InspectionClassLoader extends ClassLoader { + private final ClassLoader parent; + private final String name; + private final byte[] classfileBuffer; + private final ProtectionDomain protectionDomain; + + public InspectionClassLoader(ClassLoader parent, String name, byte[] classfileBuffer, ProtectionDomain protectionDomain) { + this.parent = parent; + this.name = name; + this.classfileBuffer = classfileBuffer; + this.protectionDomain = protectionDomain; + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + if (!this.name.equals(name)) { + return parent.loadClass(name); + } + + return loadClass(name, false); + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + Class clazz = findLoadedClass(name); + if (clazz != null) { + return clazz; + } + + return findClass(name); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (!name.equals(this.name)) { + throw new ClassNotFoundException(name); + } + + return defineClass(name, classfileBuffer, 0, classfileBuffer.length, protectionDomain); + } +} diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/ReferenceChain.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/ReferenceChain.java new file mode 100644 index 000000000..8e8c9b967 --- /dev/null +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/ReferenceChain.java @@ -0,0 +1,80 @@ +package org.openjdk.jmc.agent.util; + +import org.objectweb.asm.Type; + +import java.lang.reflect.Modifier; +import java.util.LinkedList; +import java.util.List; + +public class ReferenceChain { + private final Class callerClass; + private final List references; + + public ReferenceChain(Class callerClass, String pathExpression) throws NoSuchFieldException { + this.callerClass = callerClass; + this.references = new LinkedList<>(); + + if (pathExpression == null || pathExpression.length() == 0) { + throw new IllegalArgumentException("Expect a non-null and non-empty path expression"); + } + + Class memberingClass = callerClass; + for (String name : pathExpression.split("\\.")) { + FieldReference ref; + if ("this".equals(name)) { + if (!references.isEmpty()) { + throw new IllegalArgumentException("Unexpected \"this\""); + } + + ref = new FieldReference.ThisReference(memberingClass); + } else { + ref = new FieldReference(memberingClass, TypeUtils.getFieldOnHierarchy(memberingClass, name)); + // TODO: access check + memberingClass = ref.getField().getType(); + } + + references.add(ref); + } + } + + private ReferenceChain(Class callerClass, List references) { + this.callerClass = callerClass; + this.references = references; + } + + public Class getCallerClass() { + return callerClass; + } + + public List getReferences() { + return references; + } + + public ReferenceChain normalize() { + List oldRefs = getReferences(); + List newRefs = new LinkedList<>(); + + // Take shortcut on static reference + for (FieldReference ref : oldRefs) { + if (Modifier.isStatic(ref.getModifiers())) { + newRefs.clear(); + } + newRefs.add(ref); + } + + // Reduce static final reference to constant + // TODO: investigate possibility for different static initializer behaviour if loaded by different loaders + for (FieldReference ref : newRefs) { + // TODO + } + + return new ReferenceChain(callerClass, newRefs); + } + + public Type getType() { + if (references.isEmpty()) { + return Type.getType(callerClass); + } + return references.get(references.size() - 1).getType(); + } +} diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java index caff0e772..ca7007e1a 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java @@ -37,7 +37,10 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.security.ProtectionDomain; +import java.util.Arrays; +import java.util.LinkedList; import java.util.List; +import java.util.Queue; import java.util.logging.Level; import java.util.logging.Logger; @@ -45,6 +48,7 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.openjdk.jmc.agent.Agent; +import org.openjdk.jmc.agent.IAttribute; import org.openjdk.jmc.agent.Parameter; import org.openjdk.jmc.agent.jfr.impl.JFRUtils; @@ -214,12 +218,12 @@ public static String getNamePart(String fqcn) { return fqcn; } - public static void stringify(MethodVisitor mv, Parameter param, Type argumentType) { + public static void stringify(MethodVisitor mv, IAttribute attribute, Type argumentType) { mv.visitMethodInsn(Opcodes.INVOKESTATIC, INAME, "toString", //$NON-NLS-1$ "(Ljava/lang/Object;)Ljava/lang/String;", false); //$NON-NLS-1$ } - public static boolean shouldStringify(Parameter param, Type argumentType) { + public static boolean shouldStringify(IAttribute attribute, Type argumentType) { if (argumentType.getSort() == Type.ARRAY || argumentType.getSort() == Type.OBJECT) { return !argumentType.getInternalName().equals(STRING_INTERNAL_NAME); } @@ -246,6 +250,86 @@ public static String parameterize(String className) { return "L" + className + ";"; //$NON-NLS-1$ //$NON-NLS-2$ } + public static String getInternalName(String className) { + return className.replace('.', '/'); + } + + public static String getCanonicalName(String binaryName) { + return binaryName.replace('/', '.'); + } + + public static Field getFieldOnHierarchy(Class clazz, String name) throws NoSuchFieldException { + Queue> q = new LinkedList<>(); + q.add(clazz); + + while (!q.isEmpty()) { + Class targetClass = q.remove(); + try { + return targetClass.getDeclaredField(name); + } catch (NoSuchFieldException e) { + // ignore + } + + q.addAll(Arrays.asList(targetClass.getInterfaces())); + Class superClass = targetClass.getSuperclass(); + if (superClass != null) { + q.add(targetClass.getSuperclass()); + } + } + + throw new NoSuchFieldException(String.format("cannot find field %s in class %s", name, clazz.getName())); + } + + public static int getConstZeroOpcode(Type type) { + switch (type.getSort()) { + case Type.BOOLEAN: + case Type.BYTE: + case Type.CHAR: + case Type.SHORT: + case Type.INT: + return Opcodes.ICONST_0; + case Type.FLOAT: + return Opcodes.FCONST_0; + case Type.LONG: + return Opcodes.LCONST_0; + case Type.DOUBLE: + return Opcodes.DCONST_0; + case Type.ARRAY: + case Type.OBJECT: + return Opcodes.ACONST_NULL; + case Type.METHOD: + case Type.VOID: + throw new UnsupportedOperationException(); + default: + throw new AssertionError(); + } + } + + public static Object getFrameVerificationType(Type type) { + switch (type.getSort()) { + case Type.BOOLEAN: + case Type.BYTE: + case Type.CHAR: + case Type.SHORT: + case Type.INT: + return Opcodes.INTEGER; + case Type.FLOAT: + return Opcodes.FLOAT; + case Type.LONG: + return Opcodes.LONG; + case Type.DOUBLE: + return Opcodes.DOUBLE; + case Type.ARRAY: + case Type.OBJECT: + return type.getInternalName(); + case Type.METHOD: + case Type.VOID: + throw new UnsupportedOperationException(); + default: + throw new AssertionError(); + } + } + /** * Type agnostic array toString() which also handles primitive arrays. */ From 1964111f72cbb5a76afceb7b7acc54fbab238479 Mon Sep 17 00:00:00 2001 From: kxu Date: Fri, 29 Nov 2019 12:44:42 -0500 Subject: [PATCH 02/27] WIP: fix loading "this" --- .../openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java index 19f89f8d3..9b7af3289 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java @@ -175,7 +175,7 @@ private void loadWatch(Watch watch, ReferenceChain refChain) { if (i != 0) { throw new IllegalStateException("unexpected position of \"this\" reference"); } - loadArg(0); // "this" + mv.visitVarInsn(ALOAD, 0); // load "this" continue; } From 5e46ed6fd489883e5458de0f629c211813a8a454 Mon Sep 17 00:00:00 2001 From: kxu Date: Fri, 29 Nov 2019 13:47:10 -0500 Subject: [PATCH 03/27] WIP: avoid loading instrumentation pending classes with parent loader --- .../org/openjdk/jmc/agent/Transformer.java | 2 +- .../jmc/agent/util/InspectionClassLoader.java | 42 +++++++++++++------ .../jmc/agent/util/ReferenceChain.java | 11 ++--- 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java index dde65584a..600563605 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java @@ -70,7 +70,7 @@ public byte[] transform( registry.getClassPreInstrumentation(className) : classfileBuffer; try { // Don't reuse this class loader instance. We want the loader and its class to unload after we're done. - classBeingRedefined = new InspectionClassLoader(loader, TypeUtils.getCanonicalName(className), classfileBuffer, protectionDomain) + classBeingRedefined = new InspectionClassLoader(loader, registry) .loadClass(TypeUtils.getCanonicalName(className)); } catch (ClassNotFoundException e) { throw new IllegalStateException(e); // This should not happen diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java index ec1c82a04..130c3c013 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java @@ -1,28 +1,32 @@ package org.openjdk.jmc.agent.util; -import java.security.ProtectionDomain; +import org.openjdk.jmc.agent.TransformRegistry; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; // One-time use loader for reflective class inspection. An InspectionClassLoader only loads one class. public class InspectionClassLoader extends ClassLoader { private final ClassLoader parent; - private final String name; - private final byte[] classfileBuffer; - private final ProtectionDomain protectionDomain; + private final TransformRegistry registry; - public InspectionClassLoader(ClassLoader parent, String name, byte[] classfileBuffer, ProtectionDomain protectionDomain) { + public InspectionClassLoader(ClassLoader parent, TransformRegistry registry) { this.parent = parent; - this.name = name; - this.classfileBuffer = classfileBuffer; - this.protectionDomain = protectionDomain; + this.registry = registry; } @Override public Class loadClass(String name) throws ClassNotFoundException { - if (!this.name.equals(name)) { + if (!registry.hasPendingTransforms(TypeUtils.getInternalName(name))) { return parent.loadClass(name); } - return loadClass(name, false); + try { + return loadClass(name, false); + } catch (ClassNotFoundException e) { + return parent.loadClass(name); + } } @Override @@ -37,10 +41,24 @@ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundE @Override protected Class findClass(String name) throws ClassNotFoundException { - if (!name.equals(this.name)) { + InputStream is = parent.getResourceAsStream(TypeUtils.getInternalName(name) + ".class"); + if (is == null) { throw new ClassNotFoundException(name); } - return defineClass(name, classfileBuffer, 0, classfileBuffer.length, protectionDomain); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int nRead; + byte[] data = new byte[1024]; + try { + while ((nRead = is.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + buffer.flush(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + byte[] bytes = buffer.toByteArray(); + return defineClass(name, bytes, 0, bytes.length); } } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/ReferenceChain.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/ReferenceChain.java index 8e8c9b967..2ff541224 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/ReferenceChain.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/ReferenceChain.java @@ -30,6 +30,7 @@ public ReferenceChain(Class callerClass, String pathExpression) throws NoSuch } else { ref = new FieldReference(memberingClass, TypeUtils.getFieldOnHierarchy(memberingClass, name)); // TODO: access check + // TODO: handle nested access memberingClass = ref.getField().getType(); } @@ -61,11 +62,11 @@ public ReferenceChain normalize() { } newRefs.add(ref); } - - // Reduce static final reference to constant - // TODO: investigate possibility for different static initializer behaviour if loaded by different loaders - for (FieldReference ref : newRefs) { - // TODO + // Don't reduce static final reference to constant. The value could be different if loaded via different class loaders. + + // prepend "this" + if (!newRefs.isEmpty() && !Modifier.isStatic(newRefs.get(0).getModifiers()) && !(newRefs.get(0) instanceof FieldReference.ThisReference)) { + newRefs.add(0, new FieldReference.ThisReference(callerClass)); } return new ReferenceChain(callerClass, newRefs); From ad7d64f6b4a4d4aee1f4d7a3ccbced1664d02f55 Mon Sep 17 00:00:00 2001 From: kxu Date: Fri, 29 Nov 2019 15:06:46 -0500 Subject: [PATCH 04/27] WIP: enforce access checks on reference chains --- .../openjdk/jmc/agent/util/AccessUtils.java | 201 ++++++++++++++++++ .../jmc/agent/util/FieldReference.java | 10 + .../jmc/agent/util/ReferenceChain.java | 6 +- .../org/openjdk/jmc/agent/util/TypeUtils.java | 22 -- 4 files changed, 215 insertions(+), 24 deletions(-) create mode 100644 core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java new file mode 100644 index 000000000..4842e9a0e --- /dev/null +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java @@ -0,0 +1,201 @@ +package org.openjdk.jmc.agent.util; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.Queue; + +public class AccessUtils { + public static Field getFieldOnHierarchy(Class clazz, String name) throws NoSuchFieldException { + Queue> q = new LinkedList<>(); + q.add(clazz); + + while (!q.isEmpty()) { + Class targetClass = q.remove(); + try { + return targetClass.getDeclaredField(name); + } catch (NoSuchFieldException e) { + // ignore + } + + q.addAll(Arrays.asList(targetClass.getInterfaces())); + Class superClass = targetClass.getSuperclass(); + if (superClass != null) { + q.add(targetClass.getSuperclass()); + } + } + + throw new NoSuchFieldException(String.format("cannot find field %s in class %s", name, clazz.getName())); + } + + public static boolean isAccessible(Class targetClass, Field field, Class currentClass) { + int modifiers = field.getModifiers(); + + Class memberClass = field.getDeclaringClass(); + if (Modifier.isStatic(modifiers)) { + targetClass = null; + } + + return verifyMemberAccess(targetClass, memberClass, currentClass, modifiers); + } + + public static boolean verifyMemberAccess(Class targetClass, Class memberClass, Class currentClass, int modifiers) { + if (currentClass == memberClass) { + return true; + } + + if (!verifyModuleAccess(memberClass, currentClass)) { + return false; + } + + boolean gotIsSameClassPackage = false; + boolean isSameClassPackage = false; + + if (!Modifier.isPublic(getClassAccessFlags(memberClass))) { + isSameClassPackage = isSameClassPackage(currentClass, memberClass); + gotIsSameClassPackage = true; + if (!isSameClassPackage) { + return false; + } + } + + // At this point we know that currentClass can access memberClass. + + if (Modifier.isPublic(modifiers)) { + return true; + } + + // Check for nestmate access if member is private + if (Modifier.isPrivate(modifiers)) { + // Note: targetClass may be outside the nest, but that is okay + // as long as memberClass is in the nest. + if (areNestMates(currentClass, memberClass)) { + return true; + } + } + + boolean successSoFar = false; + + if (Modifier.isProtected(modifiers)) { + // See if currentClass is a subclass of memberClass + if (isSubclassOf(currentClass, memberClass)) { + successSoFar = true; + } + } + + if (!successSoFar && !Modifier.isPrivate(modifiers)) { + if (!gotIsSameClassPackage) { + isSameClassPackage = isSameClassPackage(currentClass, + memberClass); + gotIsSameClassPackage = true; + } + + if (isSameClassPackage) { + successSoFar = true; + } + } + + if (!successSoFar) { + return false; + } + + // Additional test for protected instance members + // and protected constructors: JLS 6.6.2 + if (targetClass != null && Modifier.isProtected(modifiers) && + targetClass != currentClass) + { + if (!gotIsSameClassPackage) { + isSameClassPackage = isSameClassPackage(currentClass, memberClass); + gotIsSameClassPackage = true; + } + if (!isSameClassPackage) { + if (!isSubclassOf(targetClass, currentClass)) { + return false; + } + } + } + + return true; + } + + public static boolean verifyModuleAccess(Class targetClass, Class callerClass) { + String version = System.getProperty("java.version"); + if (Integer.parseInt(version.substring(0, version.indexOf("."))) < 9) { + return true; // There is no module for pre-java 9 + } + + Object targetModule; + Object callerModule; + try { + Method getModuleMethod = Class.class.getDeclaredMethod("getModule"); + targetModule = getModuleMethod.invoke(targetClass); + callerModule = getModuleMethod.invoke(callerClass); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); // this should not happen + } + + if (targetModule == callerModule) { + return true; + } + + String pkg = getPackageName(targetClass); + try { + Method isExportedMethod = targetModule.getClass().getDeclaredMethod("isExported", String.class, Class.forName("java.lang.Module")); + return (boolean) isExportedMethod.invoke(targetModule, pkg, callerModule); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); // this should not happen + } + } + + // TODO: verify same behaviour as Class.getPackageName() + public static String getPackageName(Class clazz) { + while (clazz.isArray()) { + clazz = clazz.getComponentType(); + } + if (clazz.isPrimitive()) { + return "java.lang"; + } + + String cn = clazz.getName(); + int dot = cn.lastIndexOf('.'); + return (dot != -1) ? cn.substring(0, dot).intern() : ""; + } + + // TODO: verify same behaviour as Reflection.getClassAccessFlags(Class c) + public static int getClassAccessFlags(Class c) { + return c.getModifiers(); + } + + public static boolean isSameClassPackage(Class lhs, Class rhs) { + if (lhs.getClassLoader() != rhs.getClassLoader()) + return false; + return getPackageName(lhs).equals(getPackageName(rhs)); + } + + // TODO: verify same behaviour as Class.isNestmateOf (since java 11) + public static boolean areNestMates(Class lhs, Class rhs) { + String lhsBaseName = lhs.getName(); + if (lhsBaseName.indexOf("$") > 0) { + lhsBaseName = lhsBaseName.substring(0, lhsBaseName.indexOf("$")); + } + String rhsBaseName = rhs.getName(); + if (rhsBaseName.indexOf("$") > 0) { + rhsBaseName = rhsBaseName.substring(0, rhsBaseName.indexOf("$")); + } + + return lhsBaseName.equals(rhsBaseName); + } + + public static boolean isSubclassOf(Class queryClass, Class ofClass) { + while (queryClass != null) { + if (queryClass == ofClass) { + return true; + } + queryClass = queryClass.getSuperclass(); + } + return false; + } +} diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/FieldReference.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/FieldReference.java index 97cd02ba8..c5bdb4a81 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/FieldReference.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/FieldReference.java @@ -37,6 +37,11 @@ public int getModifiers() { return getField().getModifiers(); } + @Override + public String toString() { + return String.format("%s.%s", getMemberingClass().getName(), getName()); + } + public static class ThisReference extends FieldReference { public ThisReference(Class memberingClass) { super(memberingClass, null); @@ -56,5 +61,10 @@ public Type getType() { public int getModifiers() { return 0; } + + @Override + public String toString() { + return "\"this\""; + } } } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/ReferenceChain.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/ReferenceChain.java index 2ff541224..565e7d4bb 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/ReferenceChain.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/ReferenceChain.java @@ -28,8 +28,10 @@ public ReferenceChain(Class callerClass, String pathExpression) throws NoSuch ref = new FieldReference.ThisReference(memberingClass); } else { - ref = new FieldReference(memberingClass, TypeUtils.getFieldOnHierarchy(memberingClass, name)); - // TODO: access check + ref = new FieldReference(memberingClass, AccessUtils.getFieldOnHierarchy(memberingClass, name)); + if (!AccessUtils.isAccessible(ref.getMemberingClass(), ref.getField(), callerClass)) { + throw new IllegalArgumentException(String.format("%s cannot be accessed from %s", ref, callerClass)); + } // TODO: handle nested access memberingClass = ref.getField().getType(); } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java index ca7007e1a..d2d07c030 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java @@ -258,28 +258,6 @@ public static String getCanonicalName(String binaryName) { return binaryName.replace('/', '.'); } - public static Field getFieldOnHierarchy(Class clazz, String name) throws NoSuchFieldException { - Queue> q = new LinkedList<>(); - q.add(clazz); - - while (!q.isEmpty()) { - Class targetClass = q.remove(); - try { - return targetClass.getDeclaredField(name); - } catch (NoSuchFieldException e) { - // ignore - } - - q.addAll(Arrays.asList(targetClass.getInterfaces())); - Class superClass = targetClass.getSuperclass(); - if (superClass != null) { - q.add(targetClass.getSuperclass()); - } - } - - throw new NoSuchFieldException(String.format("cannot find field %s in class %s", name, clazz.getName())); - } - public static int getConstZeroOpcode(Type type) { switch (type.getSort()) { case Type.BOOLEAN: From b58a44e485b06211202398d7354f6ee70db5658a Mon Sep 17 00:00:00 2001 From: kxu Date: Tue, 3 Dec 2019 11:55:04 -0500 Subject: [PATCH 05/27] WIP: implement implicit upwards reference in a nest --- .../org/openjdk/jmc/agent/Transformer.java | 2 +- .../openjdk/jmc/agent/util/AccessUtils.java | 77 +++++++++++++++---- .../jmc/agent/util/FieldReference.java | 31 +++++++- .../jmc/agent/util/InspectionClassLoader.java | 18 +++-- .../jmc/agent/util/ReferenceChain.java | 57 ++++++++++---- 5 files changed, 148 insertions(+), 37 deletions(-) diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java index 600563605..f718df47a 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java @@ -70,7 +70,7 @@ public byte[] transform( registry.getClassPreInstrumentation(className) : classfileBuffer; try { // Don't reuse this class loader instance. We want the loader and its class to unload after we're done. - classBeingRedefined = new InspectionClassLoader(loader, registry) + classBeingRedefined = new InspectionClassLoader(loader) .loadClass(TypeUtils.getCanonicalName(className)); } catch (ClassNotFoundException e) { throw new IllegalStateException(e); // This should not happen diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java index 4842e9a0e..e9e16b554 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java @@ -4,11 +4,26 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; +import java.util.List; import java.util.Queue; public class AccessUtils { + public static Field getFieldInOuterClasses(Class clazz, String name) throws NoSuchFieldException { + Class c = clazz; + while (c != null) { + try { + return c.getDeclaredField(name); + } catch (NoSuchFieldException e) { + c = c.getEnclosingClass(); + } + } + + throw new NoSuchFieldException(String.format("cannot find field %s in outer classes of %s", name, clazz.getName())); + } + public static Field getFieldOnHierarchy(Class clazz, String name) throws NoSuchFieldException { Queue> q = new LinkedList<>(); q.add(clazz); @@ -175,20 +190,6 @@ public static boolean isSameClassPackage(Class lhs, Class rhs) { return getPackageName(lhs).equals(getPackageName(rhs)); } - // TODO: verify same behaviour as Class.isNestmateOf (since java 11) - public static boolean areNestMates(Class lhs, Class rhs) { - String lhsBaseName = lhs.getName(); - if (lhsBaseName.indexOf("$") > 0) { - lhsBaseName = lhsBaseName.substring(0, lhsBaseName.indexOf("$")); - } - String rhsBaseName = rhs.getName(); - if (rhsBaseName.indexOf("$") > 0) { - rhsBaseName = rhsBaseName.substring(0, rhsBaseName.indexOf("$")); - } - - return lhsBaseName.equals(rhsBaseName); - } - public static boolean isSubclassOf(Class queryClass, Class ofClass) { while (queryClass != null) { if (queryClass == ofClass) { @@ -198,4 +199,52 @@ public static boolean isSubclassOf(Class queryClass, Class ofClass) { } return false; } + + // Polyfill Class.getNestMembers() for pre-11 runtime. + // This function does not fully respect the definition of nesting from JVM's perspective. It's only used for + // validating access. + public static Class[] getNestMembers(Class clazz) { + List> classes = new ArrayList<>(); + classes.add(getNestHost(clazz)); + int i = 0; + while (i < classes.size()) { + classes.addAll(Arrays.asList(classes.get(i).getDeclaredClasses())); + i++; + } + + return classes.toArray(new Class[0]); + } + + // Polyfill Class.isNestMateOf() for pre-11 runtime + // This function does not fully respect the definition of nesting from JVM's perspective. It's only used for + // validating access. + public static boolean areNestMates(Class lhs, Class rhs) { + return getNestHost(lhs).equals(getNestHost(rhs)); + } + + // Polyfill Class.getNestHost() for pre-11 runtime + // This function does not fully respect the definition of nesting from JVM's perspective. It's only used for + // validating access. + public static Class getNestHost(Class clazz) { + // array types, primitive types, and void belong to the nests consisting only of theme, and are the nest hosts. + if (clazz.isArray()) { + return clazz; + } + + if (clazz.isPrimitive()) { + return clazz; + } + + if (Void.class.equals(clazz)) { + return clazz; + } + + while (true) { + if (clazz.getEnclosingClass() == null) { + return clazz; + } + + clazz = clazz.getEnclosingClass(); + } + } } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/FieldReference.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/FieldReference.java index c5bdb4a81..906e19dea 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/FieldReference.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/FieldReference.java @@ -2,6 +2,7 @@ import java.lang.reflect.Field; +import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; public class FieldReference { @@ -39,7 +40,7 @@ public int getModifiers() { @Override public String toString() { - return String.format("%s.%s", getMemberingClass().getName(), getName()); + return String.format("%s.%s : %s", TypeUtils.getInternalName(getMemberingClass().getName()), getName(), getType().getClassName()); } public static class ThisReference extends FieldReference { @@ -67,4 +68,32 @@ public String toString() { return "\"this\""; } } + + public static class OutwardsCastingReference extends FieldReference { + private Class targetClass; + + public OutwardsCastingReference(Class thisClass, Class targetClass) { + super(thisClass, null); + this.targetClass = targetClass; + + if (thisClass.getEnclosingClass() != targetClass) { + throw new IllegalArgumentException(String.format("%s is not the direct outer class of %s", targetClass.getName(), thisClass.getName())); + } + } + + @Override + public String getName() { + return "this$0"; + } + + @Override + public Type getType() { + return Type.getType(targetClass); + } + + @Override + public int getModifiers() { + return Opcodes.ACC_PRIVATE | Opcodes.ACC_SYNTHETIC; + } + } } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java index 130c3c013..e73e0aff6 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java @@ -1,7 +1,5 @@ package org.openjdk.jmc.agent.util; -import org.openjdk.jmc.agent.TransformRegistry; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -9,16 +7,14 @@ // One-time use loader for reflective class inspection. An InspectionClassLoader only loads one class. public class InspectionClassLoader extends ClassLoader { private final ClassLoader parent; - private final TransformRegistry registry; - public InspectionClassLoader(ClassLoader parent, TransformRegistry registry) { + public InspectionClassLoader(ClassLoader parent) { this.parent = parent; - this.registry = registry; } @Override public Class loadClass(String name) throws ClassNotFoundException { - if (!registry.hasPendingTransforms(TypeUtils.getInternalName(name))) { + if (name.startsWith("java.lang.")) { return parent.loadClass(name); } @@ -36,7 +32,13 @@ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundE return clazz; } - return findClass(name); + clazz = findClass(name); + + if (resolve) { + resolveClass(clazz); + } + + return clazz; } @Override @@ -48,7 +50,7 @@ protected Class findClass(String name) throws ClassNotFoundException { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); int nRead; - byte[] data = new byte[1024]; + byte[] data = new byte[1024]; // 1024 is chosen arbitrarily try { while ((nRead = is.read(data, 0, data.length)) != -1) { buffer.write(data, 0, nRead); diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/ReferenceChain.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/ReferenceChain.java index 565e7d4bb..399dcecf6 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/ReferenceChain.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/ReferenceChain.java @@ -2,6 +2,7 @@ import org.objectweb.asm.Type; +import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.LinkedList; import java.util.List; @@ -18,24 +19,54 @@ public ReferenceChain(Class callerClass, String pathExpression) throws NoSuch throw new IllegalArgumentException("Expect a non-null and non-empty path expression"); } + // TODO: refactor by converting to a state machine Class memberingClass = callerClass; - for (String name : pathExpression.split("\\.")) { - FieldReference ref; - if ("this".equals(name)) { - if (!references.isEmpty()) { - throw new IllegalArgumentException("Unexpected \"this\""); + String[] names = pathExpression.split("\\."); + for (int i = 0; i < names.length; i++) { + String name = names[i]; + + if (i == 0) { + if ("this".equals(name)) { + references.add(new FieldReference.ThisReference(memberingClass)); + continue; } + } + + Field field; + try { + field = AccessUtils.getFieldOnHierarchy(memberingClass, name); + } catch (NoSuchFieldException e) { + if (i == 0) { // implicit reference to nest member's field can only be the first element on the chain + try { + field = AccessUtils.getFieldInOuterClasses(memberingClass, name); + } catch (NoSuchFieldException e1) { + throw new NoSuchFieldException(String.format("cannot find field %s in %s or its outer classes", name, memberingClass)); + } + + if (Modifier.isPrivate(field.getModifiers())) { + throw new UnsupportedOperationException("bridge methods not yet supported"); + } + + if (Modifier.isStatic(field.getModifiers())) { + memberingClass = field.getDeclaringClass(); + } else { + references.add(new FieldReference.ThisReference(memberingClass)); + while (memberingClass != field.getDeclaringClass()) { + references.add(new FieldReference.OutwardsCastingReference(memberingClass, memberingClass.getEnclosingClass())); + memberingClass = memberingClass.getEnclosingClass(); + } + } - ref = new FieldReference.ThisReference(memberingClass); - } else { - ref = new FieldReference(memberingClass, AccessUtils.getFieldOnHierarchy(memberingClass, name)); - if (!AccessUtils.isAccessible(ref.getMemberingClass(), ref.getField(), callerClass)) { - throw new IllegalArgumentException(String.format("%s cannot be accessed from %s", ref, callerClass)); + } else { + throw e; } - // TODO: handle nested access - memberingClass = ref.getField().getType(); } - + + FieldReference ref = new FieldReference(memberingClass, field); + if (!AccessUtils.isAccessible(memberingClass, field, callerClass)) { + throw new IllegalArgumentException(String.format("%s cannot be accessed from %s", ref, callerClass)); + } + memberingClass = ref.getField().getType(); references.add(ref); } } From 3f2f4023d75c72b7594cb41357f3b304e4b77e5d Mon Sep 17 00:00:00 2001 From: kxu Date: Wed, 4 Dec 2019 10:29:03 -0500 Subject: [PATCH 06/27] WIP: syntax parsing with state machine --- .../jmc/agent/util/FieldReference.java | 1 + .../jmc/agent/util/InspectionClassLoader.java | 2 +- .../jmc/agent/util/ReferenceChain.java | 308 +++++++++++++++--- 3 files changed, 258 insertions(+), 53 deletions(-) diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/FieldReference.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/FieldReference.java index 906e19dea..2c95bfb0c 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/FieldReference.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/FieldReference.java @@ -69,6 +69,7 @@ public String toString() { } } + // implicit qualified "this" public static class OutwardsCastingReference extends FieldReference { private Class targetClass; diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java index e73e0aff6..051d87481 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java @@ -14,7 +14,7 @@ public InspectionClassLoader(ClassLoader parent) { @Override public Class loadClass(String name) throws ClassNotFoundException { - if (name.startsWith("java.lang.")) { + if (name.startsWith("java.")) { return parent.loadClass(name); } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/ReferenceChain.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/ReferenceChain.java index 399dcecf6..2075e9812 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/ReferenceChain.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/ReferenceChain.java @@ -4,8 +4,10 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; +import java.util.Queue; public class ReferenceChain { private final Class callerClass; @@ -13,62 +15,13 @@ public class ReferenceChain { public ReferenceChain(Class callerClass, String pathExpression) throws NoSuchFieldException { this.callerClass = callerClass; - this.references = new LinkedList<>(); +// this.references = new LinkedList<>(); if (pathExpression == null || pathExpression.length() == 0) { throw new IllegalArgumentException("Expect a non-null and non-empty path expression"); } - // TODO: refactor by converting to a state machine - Class memberingClass = callerClass; - String[] names = pathExpression.split("\\."); - for (int i = 0; i < names.length; i++) { - String name = names[i]; - - if (i == 0) { - if ("this".equals(name)) { - references.add(new FieldReference.ThisReference(memberingClass)); - continue; - } - } - - Field field; - try { - field = AccessUtils.getFieldOnHierarchy(memberingClass, name); - } catch (NoSuchFieldException e) { - if (i == 0) { // implicit reference to nest member's field can only be the first element on the chain - try { - field = AccessUtils.getFieldInOuterClasses(memberingClass, name); - } catch (NoSuchFieldException e1) { - throw new NoSuchFieldException(String.format("cannot find field %s in %s or its outer classes", name, memberingClass)); - } - - if (Modifier.isPrivate(field.getModifiers())) { - throw new UnsupportedOperationException("bridge methods not yet supported"); - } - - if (Modifier.isStatic(field.getModifiers())) { - memberingClass = field.getDeclaringClass(); - } else { - references.add(new FieldReference.ThisReference(memberingClass)); - while (memberingClass != field.getDeclaringClass()) { - references.add(new FieldReference.OutwardsCastingReference(memberingClass, memberingClass.getEnclosingClass())); - memberingClass = memberingClass.getEnclosingClass(); - } - } - - } else { - throw e; - } - } - - FieldReference ref = new FieldReference(memberingClass, field); - if (!AccessUtils.isAccessible(memberingClass, field, callerClass)) { - throw new IllegalArgumentException(String.format("%s cannot be accessed from %s", ref, callerClass)); - } - memberingClass = ref.getField().getType(); - references.add(ref); - } + references = new ExpressionStateMachine(callerClass, pathExpression).solve(); } private ReferenceChain(Class callerClass, List references) { @@ -98,7 +51,10 @@ public ReferenceChain normalize() { // Don't reduce static final reference to constant. The value could be different if loaded via different class loaders. // prepend "this" - if (!newRefs.isEmpty() && !Modifier.isStatic(newRefs.get(0).getModifiers()) && !(newRefs.get(0) instanceof FieldReference.ThisReference)) { + if (!newRefs.isEmpty() + && !Modifier.isStatic(newRefs.get(0).getModifiers()) + && !(newRefs.get(0) instanceof FieldReference.ThisReference) + && !(newRefs.get(0) instanceof FieldReference.OutwardsCastingReference)) { newRefs.add(0, new FieldReference.ThisReference(callerClass)); } @@ -111,4 +67,252 @@ public Type getType() { } return references.get(references.size() - 1).getType(); } + + // TODO: major refactor for readability + private static class ExpressionStateMachine { + + private final Class caller; + private final String expression; + + private Class memberingClass; + private Queue tokens; + + private List referenceChain; + + public ExpressionStateMachine(Class caller, String expression) { + this.caller = caller; + this.expression = expression; + } + + public List solve() { + tokens = new LinkedList<>(Arrays.asList(expression.split("\\."))); + referenceChain = new LinkedList<>(); + + startState(); + return referenceChain; + } + + private void startState() { + memberingClass = caller; + + String token = tokens.poll(); + if (token == null) { + rejectedState(); + } + + if ("this".equals(token)) { + thisLiteralState(caller); + return; + } + + { + Field field = null; + try { + field = AccessUtils.getFieldOnHierarchy(memberingClass, token); + } catch (NoSuchFieldException e) { + // no op + } + if (field != null) { + fieldNameState(field); + return; + } + } + + { + Field field = null; + try { + field = AccessUtils.getFieldInOuterClasses(memberingClass, token); + } catch (NoSuchFieldException e) { + // no op + } + if (field != null) { + referenceChain.add(new FieldReference.ThisReference(memberingClass)); + while (memberingClass != field.getDeclaringClass()) { + referenceChain.add(new FieldReference.OutwardsCastingReference(memberingClass, memberingClass.getEnclosingClass())); + memberingClass = memberingClass.getEnclosingClass(); + } +// classNameState(field.getDeclaringClass()); + fieldNameState(field); + return; + } + } + + { + Class nestedClass = null; + for (Class clazz : caller.getDeclaredClasses()) { + if (clazz.getSimpleName().equals(token)) { + nestedClass = clazz; + break; + } + } + if (nestedClass != null) { + classNameState(nestedClass); + return; + } + } + + { + String className = caller.getPackage().getName() + "." + token; + if (caller.getPackage().getName().isEmpty()) { + className = token; + } + + try { + Class clazz = caller.getClassLoader().loadClass(className); + classNameState(clazz); + return; + } catch (ClassNotFoundException e) { + // no op + } + } + + { + String className = "java.lang." + token; + try { + Class clazz = ClassLoader.getSystemClassLoader().loadClass(className); + classNameState(clazz); + return; + } catch (ClassNotFoundException e) { + // no op + } + } + + { + // TODO: use class loader + for (Package pkg : Package.getPackages()) { + if (pkg.getName().startsWith(token)) { + packageNameState(token); + return; + } + } + } + + rejectedState(); + } + + private void packageNameState(String partialPackageName) { + String token = tokens.poll(); + if (token == null) { + rejectedState(); + return; + } + + // TODO: use class loader + if (Package.getPackage(partialPackageName) != null) { + try { + Class clazz = caller.getClassLoader().loadClass(partialPackageName + "." + token); + classNameState(clazz); + return; + } catch (ClassNotFoundException e) { + // no op + } + } + + partialPackageName = partialPackageName + "." + token; + for (Package pkg : Package.getPackages()) { + if (pkg.getName().startsWith(partialPackageName)) { + packageNameState(partialPackageName); + return; + } + } + + rejectedState(); + } + + private void classNameState(Class clazz) { + memberingClass = clazz; + + String token = tokens.poll(); + if (token == null) { + rejectedState(); + return; + } + + if ("this".equals(token)) { + thisLiteralState(clazz); + return; + } + + if ("class".equals(token)) { + classLiteralState(); + return; + } + + { + for (Class c : memberingClass.getDeclaredClasses()) { + if (c.getSimpleName().equals(token)) { + classNameState(c); + return; + } + } + } + + try { + Field field = AccessUtils.getFieldOnHierarchy(memberingClass, token); + fieldNameState(field); + } catch (NoSuchFieldException e) { + rejectedState(); + } + } + + private void fieldNameState(Field field) { + FieldReference ref = new FieldReference(memberingClass, field); + referenceChain.add(ref); + memberingClass = field.getType(); + + String token = tokens.poll(); + if (token == null) { + return; + } + + try { + field = AccessUtils.getFieldOnHierarchy(memberingClass, token); + fieldNameState(field); + } catch (NoSuchFieldException e) { + rejectedState(); + } + } + + private void thisLiteralState(Class target) { + if (target == null) { + throw new IllegalArgumentException(); + } + + if (caller.equals(target)) { + referenceChain.add(new FieldReference.ThisReference(caller)); + } else { + Class clazz = caller; + referenceChain.add(new FieldReference.ThisReference(caller)); + while (clazz != target) { + if (clazz == null || clazz.getEnclosingClass() == null) { + rejectedState(); + return; + } + + referenceChain.add(new FieldReference.OutwardsCastingReference(clazz, clazz.getEnclosingClass())); + clazz = clazz.getEnclosingClass(); + } + } + + String token = tokens.poll(); + if (token == null) { + return; + } + + try { + Field field = AccessUtils.getFieldOnHierarchy(memberingClass, token); + fieldNameState(field); + } catch (NoSuchFieldException e) { + rejectedState(); + } + } + + private void classLiteralState() { + throw new UnsupportedOperationException(); + } + + private void rejectedState() throws IllegalArgumentException { + throw new IllegalArgumentException(); + } + } } From 036b324e18b6caf638ad3243e0da4fa140bdb96a Mon Sep 17 00:00:00 2001 From: kxu Date: Wed, 11 Dec 2019 10:34:04 -0500 Subject: [PATCH 07/27] WIP: state machine for parsing expression --- .../java/org/openjdk/jmc/agent/Watch.java | 2 +- .../jfrnext/impl/JFRNextMethodAdvisor.java | 4 +- .../openjdk/jmc/agent/util/AccessUtils.java | 2 +- .../jmc/agent/util/InspectionClassLoader.java | 11 +- .../jmc/agent/util/ReferenceChain.java | 318 -------- .../util/{ => expression}/FieldReference.java | 3 +- .../expression/IllegalSyntaxException.java | 19 + .../agent/util/expression/ReferenceChain.java | 740 ++++++++++++++++++ 8 files changed, 770 insertions(+), 329 deletions(-) delete mode 100644 core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/ReferenceChain.java rename core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/{ => expression}/FieldReference.java (96%) create mode 100644 core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/IllegalSyntaxException.java create mode 100644 core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Watch.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Watch.java index bb5dd7c60..4ab826209 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Watch.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Watch.java @@ -1,6 +1,6 @@ package org.openjdk.jmc.agent; -import org.openjdk.jmc.agent.util.ReferenceChain; +import org.openjdk.jmc.agent.util.expression.ReferenceChain; import org.openjdk.jmc.agent.util.TypeUtils; public class Watch implements IAttribute { diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java index 9b7af3289..b82f94713 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java @@ -41,8 +41,8 @@ import org.openjdk.jmc.agent.Parameter; import org.openjdk.jmc.agent.Watch; import org.openjdk.jmc.agent.jfr.JFRTransformDescriptor; -import org.openjdk.jmc.agent.util.FieldReference; -import org.openjdk.jmc.agent.util.ReferenceChain; +import org.openjdk.jmc.agent.util.expression.FieldReference; +import org.openjdk.jmc.agent.util.expression.ReferenceChain; import org.openjdk.jmc.agent.util.TypeUtils; import java.lang.reflect.Modifier; diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java index e9e16b554..1bb8d1ce5 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java @@ -15,7 +15,7 @@ public static Field getFieldInOuterClasses(Class clazz, String name) throws N Class c = clazz; while (c != null) { try { - return c.getDeclaredField(name); + return getFieldOnHierarchy(c, name); } catch (NoSuchFieldException e) { c = c.getEnclosingClass(); } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java index 051d87481..05639e50d 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java @@ -6,22 +6,21 @@ // One-time use loader for reflective class inspection. An InspectionClassLoader only loads one class. public class InspectionClassLoader extends ClassLoader { - private final ClassLoader parent; public InspectionClassLoader(ClassLoader parent) { - this.parent = parent; + super(parent); } @Override public Class loadClass(String name) throws ClassNotFoundException { if (name.startsWith("java.")) { - return parent.loadClass(name); + return getParent().loadClass(name); } try { - return loadClass(name, false); + return loadClass(name, true); } catch (ClassNotFoundException e) { - return parent.loadClass(name); + return getParent().loadClass(name); } } @@ -43,7 +42,7 @@ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundE @Override protected Class findClass(String name) throws ClassNotFoundException { - InputStream is = parent.getResourceAsStream(TypeUtils.getInternalName(name) + ".class"); + InputStream is = getParent().getResourceAsStream(TypeUtils.getInternalName(name) + ".class"); if (is == null) { throw new ClassNotFoundException(name); } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/ReferenceChain.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/ReferenceChain.java deleted file mode 100644 index 2075e9812..000000000 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/ReferenceChain.java +++ /dev/null @@ -1,318 +0,0 @@ -package org.openjdk.jmc.agent.util; - -import org.objectweb.asm.Type; - -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; - -public class ReferenceChain { - private final Class callerClass; - private final List references; - - public ReferenceChain(Class callerClass, String pathExpression) throws NoSuchFieldException { - this.callerClass = callerClass; -// this.references = new LinkedList<>(); - - if (pathExpression == null || pathExpression.length() == 0) { - throw new IllegalArgumentException("Expect a non-null and non-empty path expression"); - } - - references = new ExpressionStateMachine(callerClass, pathExpression).solve(); - } - - private ReferenceChain(Class callerClass, List references) { - this.callerClass = callerClass; - this.references = references; - } - - public Class getCallerClass() { - return callerClass; - } - - public List getReferences() { - return references; - } - - public ReferenceChain normalize() { - List oldRefs = getReferences(); - List newRefs = new LinkedList<>(); - - // Take shortcut on static reference - for (FieldReference ref : oldRefs) { - if (Modifier.isStatic(ref.getModifiers())) { - newRefs.clear(); - } - newRefs.add(ref); - } - // Don't reduce static final reference to constant. The value could be different if loaded via different class loaders. - - // prepend "this" - if (!newRefs.isEmpty() - && !Modifier.isStatic(newRefs.get(0).getModifiers()) - && !(newRefs.get(0) instanceof FieldReference.ThisReference) - && !(newRefs.get(0) instanceof FieldReference.OutwardsCastingReference)) { - newRefs.add(0, new FieldReference.ThisReference(callerClass)); - } - - return new ReferenceChain(callerClass, newRefs); - } - - public Type getType() { - if (references.isEmpty()) { - return Type.getType(callerClass); - } - return references.get(references.size() - 1).getType(); - } - - // TODO: major refactor for readability - private static class ExpressionStateMachine { - - private final Class caller; - private final String expression; - - private Class memberingClass; - private Queue tokens; - - private List referenceChain; - - public ExpressionStateMachine(Class caller, String expression) { - this.caller = caller; - this.expression = expression; - } - - public List solve() { - tokens = new LinkedList<>(Arrays.asList(expression.split("\\."))); - referenceChain = new LinkedList<>(); - - startState(); - return referenceChain; - } - - private void startState() { - memberingClass = caller; - - String token = tokens.poll(); - if (token == null) { - rejectedState(); - } - - if ("this".equals(token)) { - thisLiteralState(caller); - return; - } - - { - Field field = null; - try { - field = AccessUtils.getFieldOnHierarchy(memberingClass, token); - } catch (NoSuchFieldException e) { - // no op - } - if (field != null) { - fieldNameState(field); - return; - } - } - - { - Field field = null; - try { - field = AccessUtils.getFieldInOuterClasses(memberingClass, token); - } catch (NoSuchFieldException e) { - // no op - } - if (field != null) { - referenceChain.add(new FieldReference.ThisReference(memberingClass)); - while (memberingClass != field.getDeclaringClass()) { - referenceChain.add(new FieldReference.OutwardsCastingReference(memberingClass, memberingClass.getEnclosingClass())); - memberingClass = memberingClass.getEnclosingClass(); - } -// classNameState(field.getDeclaringClass()); - fieldNameState(field); - return; - } - } - - { - Class nestedClass = null; - for (Class clazz : caller.getDeclaredClasses()) { - if (clazz.getSimpleName().equals(token)) { - nestedClass = clazz; - break; - } - } - if (nestedClass != null) { - classNameState(nestedClass); - return; - } - } - - { - String className = caller.getPackage().getName() + "." + token; - if (caller.getPackage().getName().isEmpty()) { - className = token; - } - - try { - Class clazz = caller.getClassLoader().loadClass(className); - classNameState(clazz); - return; - } catch (ClassNotFoundException e) { - // no op - } - } - - { - String className = "java.lang." + token; - try { - Class clazz = ClassLoader.getSystemClassLoader().loadClass(className); - classNameState(clazz); - return; - } catch (ClassNotFoundException e) { - // no op - } - } - - { - // TODO: use class loader - for (Package pkg : Package.getPackages()) { - if (pkg.getName().startsWith(token)) { - packageNameState(token); - return; - } - } - } - - rejectedState(); - } - - private void packageNameState(String partialPackageName) { - String token = tokens.poll(); - if (token == null) { - rejectedState(); - return; - } - - // TODO: use class loader - if (Package.getPackage(partialPackageName) != null) { - try { - Class clazz = caller.getClassLoader().loadClass(partialPackageName + "." + token); - classNameState(clazz); - return; - } catch (ClassNotFoundException e) { - // no op - } - } - - partialPackageName = partialPackageName + "." + token; - for (Package pkg : Package.getPackages()) { - if (pkg.getName().startsWith(partialPackageName)) { - packageNameState(partialPackageName); - return; - } - } - - rejectedState(); - } - - private void classNameState(Class clazz) { - memberingClass = clazz; - - String token = tokens.poll(); - if (token == null) { - rejectedState(); - return; - } - - if ("this".equals(token)) { - thisLiteralState(clazz); - return; - } - - if ("class".equals(token)) { - classLiteralState(); - return; - } - - { - for (Class c : memberingClass.getDeclaredClasses()) { - if (c.getSimpleName().equals(token)) { - classNameState(c); - return; - } - } - } - - try { - Field field = AccessUtils.getFieldOnHierarchy(memberingClass, token); - fieldNameState(field); - } catch (NoSuchFieldException e) { - rejectedState(); - } - } - - private void fieldNameState(Field field) { - FieldReference ref = new FieldReference(memberingClass, field); - referenceChain.add(ref); - memberingClass = field.getType(); - - String token = tokens.poll(); - if (token == null) { - return; - } - - try { - field = AccessUtils.getFieldOnHierarchy(memberingClass, token); - fieldNameState(field); - } catch (NoSuchFieldException e) { - rejectedState(); - } - } - - private void thisLiteralState(Class target) { - if (target == null) { - throw new IllegalArgumentException(); - } - - if (caller.equals(target)) { - referenceChain.add(new FieldReference.ThisReference(caller)); - } else { - Class clazz = caller; - referenceChain.add(new FieldReference.ThisReference(caller)); - while (clazz != target) { - if (clazz == null || clazz.getEnclosingClass() == null) { - rejectedState(); - return; - } - - referenceChain.add(new FieldReference.OutwardsCastingReference(clazz, clazz.getEnclosingClass())); - clazz = clazz.getEnclosingClass(); - } - } - - String token = tokens.poll(); - if (token == null) { - return; - } - - try { - Field field = AccessUtils.getFieldOnHierarchy(memberingClass, token); - fieldNameState(field); - } catch (NoSuchFieldException e) { - rejectedState(); - } - } - - private void classLiteralState() { - throw new UnsupportedOperationException(); - } - - private void rejectedState() throws IllegalArgumentException { - throw new IllegalArgumentException(); - } - } -} diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/FieldReference.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/FieldReference.java similarity index 96% rename from core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/FieldReference.java rename to core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/FieldReference.java index 2c95bfb0c..467b37781 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/FieldReference.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/FieldReference.java @@ -1,9 +1,10 @@ -package org.openjdk.jmc.agent.util; +package org.openjdk.jmc.agent.util.expression; import java.lang.reflect.Field; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; +import org.openjdk.jmc.agent.util.TypeUtils; public class FieldReference { private final Class memberingClass; diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/IllegalSyntaxException.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/IllegalSyntaxException.java new file mode 100644 index 000000000..a65e82ddb --- /dev/null +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/IllegalSyntaxException.java @@ -0,0 +1,19 @@ +package org.openjdk.jmc.agent.util.expression; + +public class IllegalSyntaxException extends Exception { + public IllegalSyntaxException() { + super(); + } + + public IllegalSyntaxException(String message) { + super(message); + } + + public IllegalSyntaxException(String message, Throwable cause) { + super(message, cause); + } + + public IllegalSyntaxException(Throwable cause) { + super(cause); + } +} diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java new file mode 100644 index 000000000..6b258a0c2 --- /dev/null +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java @@ -0,0 +1,740 @@ +package org.openjdk.jmc.agent.util.expression; + +import org.objectweb.asm.Type; +import org.openjdk.jmc.agent.util.AccessUtils; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Scanner; + +public class ReferenceChain { + private final Class callerClass; + private final List references; + + // DEBUG + public static void main(String[] args) { + String context = ""; + String expression = ""; + while (true) { + String tmp; + + Scanner scnr = new Scanner(System.in); + System.out.println("Context: "); + tmp = scnr.nextLine(); + if (!tmp.isEmpty()) context = tmp; + System.out.println("Expression: "); + tmp = scnr.nextLine(); + if (!tmp.isEmpty()) expression = tmp; + + String className = context.substring(0, context.indexOf('#')); + String methodName = context.substring(context.indexOf('#') + 1); + try { + Class clazz = Class.forName(className); + Method method = null; + for (Method m : clazz.getMethods()) { + if (m.getName().equals(methodName)) { + method = m; + } + } + + + new ExpressionStateMachine(clazz, method, expression).solve(); + System.out.println("done"); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + public ReferenceChain(Class callerClass, String pathExpression) throws NoSuchFieldException { + this.callerClass = callerClass; +// this.references = new LinkedList<>(); + + if (pathExpression == null || pathExpression.length() == 0) { + throw new IllegalArgumentException("Expect a non-null and non-empty path expression"); + } + +// references = new ExpressionStateMachine(callerClass, pathExpression).solve(); + references = null; + } + + private ReferenceChain(Class callerClass, List references) { + this.callerClass = callerClass; + this.references = references; + } + + public Class getCallerClass() { + return callerClass; + } + + public List getReferences() { + return references; + } + + public ReferenceChain normalize() { + List oldRefs = getReferences(); + List newRefs = new LinkedList<>(); + + // Take shortcut on static reference + for (FieldReference ref : oldRefs) { + if (Modifier.isStatic(ref.getModifiers())) { + newRefs.clear(); + } + newRefs.add(ref); + } + // Don't reduce static final reference to constant. The value could be different if loaded via different class loaders. + + // prepend "this" + if (!newRefs.isEmpty() + && !Modifier.isStatic(newRefs.get(0).getModifiers()) + && !(newRefs.get(0) instanceof FieldReference.ThisReference)) { + newRefs.add(0, new FieldReference.ThisReference(callerClass)); + } + + return new ReferenceChain(callerClass, newRefs); + } + + public Type getType() { + if (references.isEmpty()) { + return Type.getType(callerClass); + } + return references.get(references.size() - 1).getType(); + } + + /* + Expression + -> this + | TypeName . this + | FieldAccess + + TypeName + -> TypeIdentifier + | PackageOrTypeName Dot TypeIdentifier + + PackageOrTypeName + -> identifier + | PackageOrTypeName . identifier + + TypeIdentifier + -> identifier + + FieldAccess + -> Expression . identifier + | super . identifier + | TypeName . super . identifier + | FieldName + + FieldName + -> identifier + + identifier // terminal symbols + -> [A-z_]+[A-z0-9_]* + */ + private static class ExpressionStateMachine { + private final Class caller; + private final Method method; + private final String expression; + + private List tokens = null; + private Iterator iterator = null; + private List referenceChain = null; + + private ExpressionStateMachine(Class caller, Method method, String expression) { + this.caller = caller; + this.method = method; + this.expression = expression; + } + + private void solve() throws IllegalSyntaxException { + tokens = new LinkedList<>(Arrays.asList(expression.split("\\."))); + iterator = tokens.iterator(); + referenceChain = new LinkedList<>(); + + enterStartState(); + } + + private void enterStartState() throws IllegalSyntaxException { + if (!iterator.hasNext()) { + enterIllegalState("unexpected end of input"); + } + + String token = iterator.next(); // first identifier + + // "this" + if (tryEnterThisState(caller, token)) { + return; + } + + // "super" + if (tryEnterSuperState(caller, token)) { + return; + } + + // local/inherited field reference + if (tryEnterFieldReferenceState(caller, token)) { + return; + } + + // nested field reference + if (tryEntryNestedFieldReferenceState(token)) { + return; + } + + // outer class reference + if (tryEnterOuterClassState(token)) { + return; + } + + // inner class reference + if (tryEnterInnerClassState(caller, token)) { + return; + } + + // CallerClass + if (tryEnterSameClassState(token)) { + return; + } + + // ClassWithInTheSamePackage + if (tryEnterClassState(token)) { + return; + } + + // com.full.qualified.pkg.ClassName + if (tryEnterPackageState(expression)) { + return; + } + + // partially.qualified.pkg.ClassName + if (tryEnterPackageState(caller.getPackage(), expression)) { + return; + } + + // eg. Object => java.lang.Object + if (tryEnterPackageState(Package.getPackage("java.lang"), expression)) { + return; + } + + enterIllegalState(String.format("unrecognized token: %s", token)); + } + + private boolean tryEnterThisState(Class targetClass, String thisLiteral) throws IllegalSyntaxException { + if (!"this".equals(thisLiteral)) { + return false; + } + + enterThisState(targetClass); + return true; + } + + // "^this" or qualified . "this" expression (outwards casting) + private void enterThisState(Class targetClass) throws IllegalSyntaxException { + doOutwardsCasting(targetClass); + + if (!iterator.hasNext()) { // accepted state + return; + } + String token = iterator.next(); + + // this.prop + if (tryEnterFieldReferenceState(targetClass, token)) { + return; + } + + enterIllegalState(String.format("unrecognized token: %s", token)); + } + + private boolean tryEnterSuperState(Class targetClass, String superLiteral) throws IllegalSyntaxException { + if (!"super".equals(superLiteral)) { + return false; + } + + enterSuperState(targetClass); + return true; + } + + // "^super" or qualified . "super" expression (outwards casting) + private void enterSuperState(Class targetClass) throws IllegalSyntaxException { + doOutwardsCasting(targetClass); + + Class superClass = targetClass.getSuperclass(); + // TODO: add super reference + System.out.printf("super class reference from %s to %s\n", caller, superClass); + + if (!iterator.hasNext()) { // rejected state + enterIllegalState("unexpected end of input"); + } + String token = iterator.next(); + + // this.prop + if (tryEnterFieldReferenceState(targetClass, token)) { + return; + } + + enterIllegalState(String.format("unrecognized token: %s", token)); + } + + private boolean tryEnterFieldReferenceState(Class memberingClass, String fieldName) throws IllegalSyntaxException { + try { + Field field = AccessUtils.getFieldOnHierarchy(memberingClass, fieldName); + enterFieldReferenceState(memberingClass, field); + return true; + } catch (NoSuchFieldException e) { + return false; + } + } + + private boolean tryEnterFieldReferenceStateFromStaticContext(Class memberingClass, String fieldName) throws IllegalSyntaxException { + try { + Field field = AccessUtils.getFieldOnHierarchy(memberingClass, fieldName); + if (!Modifier.isStatic(field.getModifiers())) { + enterIllegalState("illegal reference to a dynamic field from a static context"); + } + enterFieldReferenceState(memberingClass, field); + return true; + } catch (NoSuchFieldException e) { + return false; + } + } + + private void enterFieldReferenceState(Class memberingClass, Field field) throws IllegalSyntaxException { + // TODO: add field reference + System.out.printf("field reference %s.%s:%s\n", memberingClass.getName(), field.getName(), field.getType().getName()); + + if (!iterator.hasNext()) { // accepted state + return; + } + String token = iterator.next(); + + // prop.prop2 + if (tryEnterFieldReferenceState(field.getType(), token)) { + return; + } + + enterIllegalState(String.format("unrecognized token: %s", token)); + } + + private boolean tryEntryNestedFieldReferenceState(String fieldName) throws IllegalSyntaxException { + Class enclosing = caller.getEnclosingClass(); + while (enclosing != null) { + try { + Field field = AccessUtils.getFieldOnHierarchy(enclosing, fieldName); + enterNestedFieldReferenceState(enclosing, field); + return true; + } catch (NoSuchFieldException e) { + enclosing = enclosing.getEnclosingClass(); + } + } + + return false; + } + + private void enterNestedFieldReferenceState(Class enclosingClass, Field field) throws IllegalSyntaxException { + doOutwardsCasting(enclosingClass); + + // TODO: add field reference + System.out.printf("nested field reference %s.%s:%s\n", enclosingClass.getName(), field.getName(), field.getType().getName()); + + if (!iterator.hasNext()) { // accepted state + return; + } + String token = iterator.next(); + + // nestedProp.prop + if (tryEnterFieldReferenceState(field.getType(), token)) { + return; + } + + enterIllegalState(String.format("unrecognized token: %s", token)); + } + + private boolean tryEnterOuterClassState(String simpleClassName) throws IllegalSyntaxException { + Class enclosing = caller.getEnclosingClass(); + while (enclosing != null) { + if (enclosing.getSimpleName().equals(simpleClassName)) { + enterOuterClassState(enclosing); + return true; + } + + enclosing = enclosing.getEnclosingClass(); + } + + return false; + } + + private boolean tryEnterOuterClassState(Package pkg, String className) throws IllegalSyntaxException { + String fqcn = pkg.getName().isEmpty() ? className : pkg.getName() + "." + className; + try { + Class clazz = caller.getClassLoader().loadClass(fqcn); + Class enclosing = caller.getEnclosingClass(); + while (enclosing != null) { + if (enclosing.equals(clazz)) { + enterOuterClassState(enclosing); + return true; + } + + enclosing = enclosing.getEnclosingClass(); + } + } catch (ClassNotFoundException e) { + // no op + } + + return false; + } + + private boolean tryEnterOuterClassState(Class currentClass, String simpleClassName) throws IllegalSyntaxException { + Class clazz = null; + for (Class c : currentClass.getDeclaredClasses()) { + if (c.getSimpleName().equals(simpleClassName)) { + clazz = c; + break; + } + } + + Class enclosing = caller.getEnclosingClass(); + while (enclosing != null) { + if (enclosing.equals(clazz)) { + enterOuterClassState(enclosing); + return true; + } + + enclosing = enclosing.getEnclosingClass(); + } + + return false; + } + + private void enterOuterClassState(Class targetClass) throws IllegalSyntaxException { + // static context + if (!iterator.hasNext()) { // rejected state + enterIllegalState("unexpected end of input"); + } + String token = iterator.next(); + + // OuterClass.this + if (tryEnterThisState(targetClass, token)) { + return; + } + + // OuterClass.super + if (tryEnterSuperState(targetClass, token)) { + return; + } + + // OuterClass.STATIC_PROP + if (tryEnterFieldReferenceStateFromStaticContext(targetClass, token)) { + return; + } + + // OuterClass.ThisClass + if (tryEnterSameClassState(targetClass, token)) { + return; + } + + // OuterMostClass.OuterClass + if (tryEnterOuterClassState(targetClass, token)) { + return; + } + + // OuterClass.OtherClass + if (tryEnterNestMateClass(targetClass, token)) { + return; + } + + enterIllegalState(String.format("unrecognized token: %s", token)); + } + + private boolean tryEnterInnerClassState(Class currentClass, String simpleClassName) throws IllegalSyntaxException { + for (Class innerClass : currentClass.getDeclaredClasses()) { + if (innerClass.getSimpleName().equals(simpleClassName)) { + enterInnerClassState(innerClass); + return true; + } + } + + return false; + } + + private void enterInnerClassState(Class targetClass) throws IllegalSyntaxException { + // static context + if (!iterator.hasNext()) { // rejected state + enterIllegalState("unexpected end of input"); + } + String token = iterator.next(); + + // InnerClass.STATIC_PROP + if (tryEnterFieldReferenceStateFromStaticContext(targetClass, token)) { + return; + } + + // InnerClass.InnerMostClass + if (tryEnterInnerClassState(targetClass, token)) { + return; + } + + enterIllegalState(String.format("unrecognized token: %s", token)); + } + + // target class is not a inner or outer class of the caller class, but is a classmate + private boolean tryEnterNestMateClass(Class currentClass, String simpleClassName) throws IllegalSyntaxException { + Class clazz = null; + for (Class c : currentClass.getDeclaredClasses()) { + if (c.getSimpleName().equals(simpleClassName)) { + clazz = c; + break; + } + } + + if (clazz == null) { + return false; + } + + if (!AccessUtils.areNestMates(clazz, caller)) { + return false; + } + + // check caller is not an outer class of clazz + Class enclosing = clazz; + while (enclosing != null) { + if (caller.equals(enclosing)) { + return false; + } + enclosing = enclosing.getEnclosingClass(); + } + + // check clazz if not an outer class of caller + enclosing = caller; + while (enclosing != null) { + if (clazz.equals(enclosing)) { + return false; + } + enclosing = enclosing.getEnclosingClass(); + } + + return true; + } + + + private void enterNestMateClass(Class targetClass) throws IllegalSyntaxException { + // static context + if (!iterator.hasNext()) { // rejected state + enterIllegalState("unexpected end of input"); + } + String token = iterator.next(); + + // NestMateClass.STATIC_PROP + if (tryEnterFieldReferenceStateFromStaticContext(targetClass, token)) { + return; + } + + // NestMateClass.NestMatesInnerClass + if (tryEnterNestMateClass(targetClass, token)) { + return; + } + + enterIllegalState(String.format("unrecognized token: %s", token)); + } + + private boolean tryEnterSameClassState(String simpleClassName) throws IllegalSyntaxException { + if (caller.getSimpleName().equals(simpleClassName)) { + enterSameClassState(); + return true; + } + + return false; + } + + private boolean tryEnterSameClassState(Package pkg, String simpleClassName) throws IllegalSyntaxException { + String fqcn = pkg.getName().isEmpty() ? simpleClassName : pkg.getName() + "." + simpleClassName; + if (caller.getName().equals(fqcn)) { + enterSameClassState(); + return true; + } + + return false; + } + + private boolean tryEnterSameClassState(Class currentClass, String simpleClassName) throws IllegalSyntaxException { + Class clazz = null; + for (Class c : currentClass.getDeclaredClasses()) { + if (c.getSimpleName().equals(simpleClassName)) { + clazz = c; + break; + } + } + + if (caller.equals(clazz)) { + enterSameClassState(); + return true; + } + + return false; + } + + private void enterSameClassState() throws IllegalSyntaxException { + // static context + if (!iterator.hasNext()) { // rejected state + enterIllegalState("unexpected end of input"); + } + String token = iterator.next(); + + // CallerClass.this => this + if (tryEnterThisState(caller, token)) { + return; + } + + // CallerClass.super => super + if (tryEnterSuperState(caller, token)) { + return; + } + + // CallerClass.STATIC_PROP + if (tryEnterFieldReferenceStateFromStaticContext(caller, token)) { + return; + } + + if (tryEnterInnerClassState(caller, token)) { + return; + } + + enterIllegalState(String.format("unrecognized token: %s", token)); + } + + private boolean tryEnterClassState(String simpleClassName) throws IllegalSyntaxException { + return tryEnterClassState(caller.getPackage(), simpleClassName); + } + + private boolean tryEnterClassState(Class currentClass, String simpleClassName) throws IllegalSyntaxException { + for (Class c : currentClass.getDeclaredClasses()) { + if (c.getSimpleName().equals(simpleClassName)) { + enterClassState(c); + return true; + } + } + + return false; + } + + private boolean tryEnterClassState(Package pkg, String simpleClassName) throws IllegalSyntaxException { + String fqcn = pkg.getName().isEmpty() ? simpleClassName : pkg.getName() + "." + simpleClassName; + + try { + Class c = caller.getClassLoader().loadClass(fqcn); + enterClassState(c); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + + private void enterClassState(Class targetClass) throws IllegalSyntaxException { + // static context + if (!iterator.hasNext()) { // rejected state + enterIllegalState("unexpected end of input"); + } + String token = iterator.next(); + + if (tryEnterFieldReferenceStateFromStaticContext(targetClass, token)) { + return; + } + + if (tryEnterClassState(targetClass, token)) { + return; + } + + enterIllegalState(String.format("unrecognized token: %s", token)); + } + + // Full qualified package named prefixed expression + private boolean tryEnterPackageState(String fqpnPrefixedExpression) throws IllegalSyntaxException { + // ClassLoader.getPackage(String) or ClassLoader.getPackages() is not reliable when no class from that package is yet loaded + int stop = 0; + Class clazz = null; + while (stop < fqpnPrefixedExpression.length()) { + stop = fqpnPrefixedExpression.indexOf('.', stop + 1); + if (stop == -1) { + break; + } + + String fqcn = fqpnPrefixedExpression.substring(0, stop); + try { + clazz = caller.getClassLoader().loadClass(fqcn); + break; + } catch (ClassNotFoundException e) { + // no op + } + } + + if (clazz == null) { + return false; + } + + Package pkg = clazz.getPackage(); + + tokens = new LinkedList<>(Arrays.asList(fqpnPrefixedExpression.split("\\."))); + iterator = tokens.iterator(); + int length = pkg.getName().split("\\.").length; + for (int i = 0; i < length; i++) { + iterator.next(); + } + + enterPackageState(pkg); + return true; + } + + // Partially qualified package named prefixed expression + private boolean tryEnterPackageState(Package pkg, String pqpnPrefixedExpression) throws IllegalSyntaxException { + String pkgPrefix = pkg.getName().isEmpty() ? "" : pkg.getName() + "."; + return tryEnterPackageState(pkgPrefix + pqpnPrefixedExpression); + } + + private void enterPackageState(Package pkg) throws IllegalSyntaxException { + if (!iterator.hasNext()) { // rejected state + enterIllegalState("unexpected end of input"); + } + String token = iterator.next(); + + if (tryEnterSameClassState(pkg, token)) { + return; + } + + if (tryEnterOuterClassState(pkg, token)) { + return; + } + + if (tryEnterClassState(pkg, token)) { + return; + } + + enterIllegalState(String.format("unrecognized token: %s", token)); + } + + private void enterIllegalState(String msg) throws IllegalSyntaxException { + throw new IllegalSyntaxException(msg); + } + + private void doOutwardsCasting(Class targetClass) throws IllegalSyntaxException { + // TODO: add this reference + System.out.printf("this reference on %s\n", targetClass.getName()); + + // need to do outwards casting first + if (!targetClass.equals(caller)) { + Class c = caller; + while (!targetClass.equals(c.getEnclosingClass())) { + Class enclosing = c.getEnclosingClass(); + if (enclosing == null) { + enterIllegalState(String.format("%s is not an enclosing class of %s", targetClass.getName(), caller.getName())); + } + // TODO: add outwards casting from c to enclosing + System.out.printf("outwards casting from %s to %s\n", c.getName(), enclosing.getName()); + c = enclosing; + } + } + } + } +} From 4cc7b9eaf3a125152fae64e99253c2737a9048c2 Mon Sep 17 00:00:00 2001 From: kxu Date: Wed, 11 Dec 2019 12:58:00 -0500 Subject: [PATCH 08/27] WIP: add check for access and static context. improves error messages --- .../openjdk/jmc/agent/util/AccessUtils.java | 5 +- .../agent/util/expression/ReferenceChain.java | 123 +++++++++++------- 2 files changed, 81 insertions(+), 47 deletions(-) diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java index 1bb8d1ce5..0ac2d590a 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java @@ -18,7 +18,7 @@ public static Field getFieldInOuterClasses(Class clazz, String name) throws N return getFieldOnHierarchy(c, name); } catch (NoSuchFieldException e) { c = c.getEnclosingClass(); - } + } } throw new NoSuchFieldException(String.format("cannot find field %s in outer classes of %s", name, clazz.getName())); @@ -120,8 +120,7 @@ public static boolean verifyMemberAccess(Class targetClass, Class memberCl // Additional test for protected instance members // and protected constructors: JLS 6.6.2 if (targetClass != null && Modifier.isProtected(modifiers) && - targetClass != currentClass) - { + targetClass != currentClass) { if (!gotIsSameClassPackage) { isSameClassPackage = isSameClassPackage(currentClass, memberClass); gotIsSameClassPackage = true; diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java index 6b258a0c2..5d3f3da9b 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java @@ -160,7 +160,7 @@ private void solve() throws IllegalSyntaxException { private void enterStartState() throws IllegalSyntaxException { if (!iterator.hasNext()) { - enterIllegalState("unexpected end of input"); + enterIllegalState("Unexpected end of input: expects 'this', 'super', a field name, a class name, a package name, or a package name fragment"); } String token = iterator.next(); // first identifier @@ -176,12 +176,12 @@ private void enterStartState() throws IllegalSyntaxException { } // local/inherited field reference - if (tryEnterFieldReferenceState(caller, token)) { + if (tryEnterFieldReferenceState(caller, token, Modifier.isStatic(method.getModifiers()))) { return; } // nested field reference - if (tryEntryNestedFieldReferenceState(token)) { + if (tryEntryNestedFieldReferenceState(token)) { // static class? return; } @@ -215,12 +215,12 @@ private void enterStartState() throws IllegalSyntaxException { return; } - // eg. Object => java.lang.Object + // eg. Object => java.lang.Object, Integer => java.lang.Integer if (tryEnterPackageState(Package.getPackage("java.lang"), expression)) { return; } - enterIllegalState(String.format("unrecognized token: %s", token)); + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); } private boolean tryEnterThisState(Class targetClass, String thisLiteral) throws IllegalSyntaxException { @@ -234,6 +234,10 @@ private boolean tryEnterThisState(Class targetClass, String thisLiteral) thro // "^this" or qualified . "this" expression (outwards casting) private void enterThisState(Class targetClass) throws IllegalSyntaxException { + if (Modifier.isStatic(method.getModifiers())) { + enterIllegalState(String.format("'%s.this' cannot be referenced from a static context", targetClass.getName())); + } + doOutwardsCasting(targetClass); if (!iterator.hasNext()) { // accepted state @@ -242,11 +246,11 @@ private void enterThisState(Class targetClass) throws IllegalSyntaxException String token = iterator.next(); // this.prop - if (tryEnterFieldReferenceState(targetClass, token)) { + if (tryEnterFieldReferenceState(targetClass, token, false)) { return; } - enterIllegalState(String.format("unrecognized token: %s", token)); + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); } private boolean tryEnterSuperState(Class targetClass, String superLiteral) throws IllegalSyntaxException { @@ -260,6 +264,10 @@ private boolean tryEnterSuperState(Class targetClass, String superLiteral) th // "^super" or qualified . "super" expression (outwards casting) private void enterSuperState(Class targetClass) throws IllegalSyntaxException { + if (Modifier.isStatic(method.getModifiers())) { + enterIllegalState(String.format("'%s.super' cannot be referenced from a static context", targetClass.getName())); + } + doOutwardsCasting(targetClass); Class superClass = targetClass.getSuperclass(); @@ -272,37 +280,41 @@ private void enterSuperState(Class targetClass) throws IllegalSyntaxException String token = iterator.next(); // this.prop - if (tryEnterFieldReferenceState(targetClass, token)) { + if (tryEnterFieldReferenceState(targetClass, token, false)) { return; } - enterIllegalState(String.format("unrecognized token: %s", token)); + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); } - private boolean tryEnterFieldReferenceState(Class memberingClass, String fieldName) throws IllegalSyntaxException { + private boolean tryEnterFieldReferenceState(Class memberingClass, String fieldName, boolean fromStaticContext) throws IllegalSyntaxException { try { Field field = AccessUtils.getFieldOnHierarchy(memberingClass, fieldName); - enterFieldReferenceState(memberingClass, field); + enterFieldReferenceState(memberingClass, field, fromStaticContext); return true; } catch (NoSuchFieldException e) { return false; } } - private boolean tryEnterFieldReferenceStateFromStaticContext(Class memberingClass, String fieldName) throws IllegalSyntaxException { - try { - Field field = AccessUtils.getFieldOnHierarchy(memberingClass, fieldName); - if (!Modifier.isStatic(field.getModifiers())) { - enterIllegalState("illegal reference to a dynamic field from a static context"); + private void enterFieldReferenceState(Class memberingClass, Field field, boolean fromStaticContext) throws IllegalSyntaxException { + if (fromStaticContext && !Modifier.isStatic(field.getModifiers())) { + enterIllegalState(String.format("Non-static field '%s' cannot be referenced from a static context", field.getName())); + } + + if (!AccessUtils.isAccessible(memberingClass, field, caller)) { + String access; + if (Modifier.isPrivate(field.getModifiers())) { + access = "private"; + } else if (Modifier.isProtected(field.getModifiers())) { + access = "protected"; + } else { + access = "package-private"; } - enterFieldReferenceState(memberingClass, field); - return true; - } catch (NoSuchFieldException e) { - return false; + + enterIllegalState(String.format("'%s' has %s access in '%s'", field.getName(), access, field.getDeclaringClass().getName())); } - } - private void enterFieldReferenceState(Class memberingClass, Field field) throws IllegalSyntaxException { // TODO: add field reference System.out.printf("field reference %s.%s:%s\n", memberingClass.getName(), field.getName(), field.getType().getName()); @@ -312,11 +324,11 @@ private void enterFieldReferenceState(Class memberingClass, Field field) thro String token = iterator.next(); // prop.prop2 - if (tryEnterFieldReferenceState(field.getType(), token)) { + if (tryEnterFieldReferenceState(field.getType(), token, false)) { return; } - enterIllegalState(String.format("unrecognized token: %s", token)); + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); } private boolean tryEntryNestedFieldReferenceState(String fieldName) throws IllegalSyntaxException { @@ -335,6 +347,23 @@ private boolean tryEntryNestedFieldReferenceState(String fieldName) throws Illeg } private void enterNestedFieldReferenceState(Class enclosingClass, Field field) throws IllegalSyntaxException { + if (!Modifier.isStatic(field.getModifiers())) { + Class c = caller; + // check there is no static class in between before reaching this enclosed class + while (c != null && !c.equals(enclosingClass)) { + if (Modifier.isStatic(c.getModifiers())) { + enterIllegalState(String.format("Non-static field '%s' cannot be referenced from a static context", field.getName())); + } + + c = caller.getEnclosingClass(); + } + } + + // this is syntactically allowed, but we don't support it for now + if (Modifier.isPrivate(field.getModifiers())) { + enterIllegalState(new UnsupportedOperationException("Private member access between nestmates is not supported")); + } + doOutwardsCasting(enclosingClass); // TODO: add field reference @@ -346,11 +375,11 @@ private void enterNestedFieldReferenceState(Class enclosingClass, Field field String token = iterator.next(); // nestedProp.prop - if (tryEnterFieldReferenceState(field.getType(), token)) { + if (tryEnterFieldReferenceState(field.getType(), token, false)) { return; } - enterIllegalState(String.format("unrecognized token: %s", token)); + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); } private boolean tryEnterOuterClassState(String simpleClassName) throws IllegalSyntaxException { @@ -412,7 +441,7 @@ private boolean tryEnterOuterClassState(Class currentClass, String simpleClas private void enterOuterClassState(Class targetClass) throws IllegalSyntaxException { // static context if (!iterator.hasNext()) { // rejected state - enterIllegalState("unexpected end of input"); + enterIllegalState("Unexpected end of input: expects 'this', 'super', a static field name, or an inner class name"); } String token = iterator.next(); @@ -427,7 +456,7 @@ private void enterOuterClassState(Class targetClass) throws IllegalSyntaxExce } // OuterClass.STATIC_PROP - if (tryEnterFieldReferenceStateFromStaticContext(targetClass, token)) { + if (tryEnterFieldReferenceState(targetClass, token, true)) { return; } @@ -446,7 +475,7 @@ private void enterOuterClassState(Class targetClass) throws IllegalSyntaxExce return; } - enterIllegalState(String.format("unrecognized token: %s", token)); + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); } private boolean tryEnterInnerClassState(Class currentClass, String simpleClassName) throws IllegalSyntaxException { @@ -463,21 +492,21 @@ private boolean tryEnterInnerClassState(Class currentClass, String simpleClas private void enterInnerClassState(Class targetClass) throws IllegalSyntaxException { // static context if (!iterator.hasNext()) { // rejected state - enterIllegalState("unexpected end of input"); + enterIllegalState("Unexpected end of input: expects a static field name or an inner class name"); } String token = iterator.next(); // InnerClass.STATIC_PROP - if (tryEnterFieldReferenceStateFromStaticContext(targetClass, token)) { + if (tryEnterFieldReferenceState(targetClass, token, true)) { return; } - // InnerClass.InnerMostClass + // InnerClass.InnerMoreClass if (tryEnterInnerClassState(targetClass, token)) { return; } - enterIllegalState(String.format("unrecognized token: %s", token)); + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); } // target class is not a inner or outer class of the caller class, but is a classmate @@ -523,12 +552,12 @@ private boolean tryEnterNestMateClass(Class currentClass, String simpleClassN private void enterNestMateClass(Class targetClass) throws IllegalSyntaxException { // static context if (!iterator.hasNext()) { // rejected state - enterIllegalState("unexpected end of input"); + enterIllegalState("Unexpected end of input: expects a static field name or an inner class name"); } String token = iterator.next(); // NestMateClass.STATIC_PROP - if (tryEnterFieldReferenceStateFromStaticContext(targetClass, token)) { + if (tryEnterFieldReferenceState(targetClass, token, false)) { return; } @@ -537,7 +566,7 @@ private void enterNestMateClass(Class targetClass) throws IllegalSyntaxExcept return; } - enterIllegalState(String.format("unrecognized token: %s", token)); + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); } private boolean tryEnterSameClassState(String simpleClassName) throws IllegalSyntaxException { @@ -579,7 +608,7 @@ private boolean tryEnterSameClassState(Class currentClass, String simpleClass private void enterSameClassState() throws IllegalSyntaxException { // static context if (!iterator.hasNext()) { // rejected state - enterIllegalState("unexpected end of input"); + enterIllegalState("Unexpected end of input: expects a static field name or an inner class name"); } String token = iterator.next(); @@ -594,7 +623,7 @@ private void enterSameClassState() throws IllegalSyntaxException { } // CallerClass.STATIC_PROP - if (tryEnterFieldReferenceStateFromStaticContext(caller, token)) { + if (tryEnterFieldReferenceState(caller, token, true)) { return; } @@ -602,7 +631,7 @@ private void enterSameClassState() throws IllegalSyntaxException { return; } - enterIllegalState(String.format("unrecognized token: %s", token)); + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); } private boolean tryEnterClassState(String simpleClassName) throws IllegalSyntaxException { @@ -635,19 +664,21 @@ private boolean tryEnterClassState(Package pkg, String simpleClassName) throws I private void enterClassState(Class targetClass) throws IllegalSyntaxException { // static context if (!iterator.hasNext()) { // rejected state - enterIllegalState("unexpected end of input"); + enterIllegalState("Unexpected end of input: expects a static field name or an inner class name"); } String token = iterator.next(); - if (tryEnterFieldReferenceStateFromStaticContext(targetClass, token)) { + // ClassName.STATIC_PROP + if (tryEnterFieldReferenceState(targetClass, token, true)) { return; } + // ClassName.InnerClass if (tryEnterClassState(targetClass, token)) { return; } - enterIllegalState(String.format("unrecognized token: %s", token)); + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); } // Full qualified package named prefixed expression @@ -695,7 +726,7 @@ private boolean tryEnterPackageState(Package pkg, String pqpnPrefixedExpression) private void enterPackageState(Package pkg) throws IllegalSyntaxException { if (!iterator.hasNext()) { // rejected state - enterIllegalState("unexpected end of input"); + enterIllegalState("Unexpected end of input: expects a class name"); } String token = iterator.next(); @@ -711,13 +742,17 @@ private void enterPackageState(Package pkg) throws IllegalSyntaxException { return; } - enterIllegalState(String.format("unrecognized token: %s", token)); + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); } private void enterIllegalState(String msg) throws IllegalSyntaxException { throw new IllegalSyntaxException(msg); } + private void enterIllegalState(Throwable throwable) throws IllegalSyntaxException { + throw new IllegalSyntaxException(throwable); + } + private void doOutwardsCasting(Class targetClass) throws IllegalSyntaxException { // TODO: add this reference System.out.printf("this reference on %s\n", targetClass.getName()); From af33e32f3003128bbd6dd29845fa6f56a54be0d3 Mon Sep 17 00:00:00 2001 From: kxu Date: Thu, 12 Dec 2019 11:17:59 -0500 Subject: [PATCH 09/27] implemented path-syntax evaluation --- .../java/org/openjdk/jmc/agent/Watch.java | 6 +- .../impl/JFRNextEventClassGenerator.java | 5 +- .../jfrnext/impl/JFRNextMethodAdvisor.java | 35 +- .../util/expression/ExpressionResolver.java | 693 +++++++++++++++++ .../agent/util/expression/FieldReference.java | 64 +- .../agent/util/expression/ReferenceChain.java | 734 +----------------- 6 files changed, 759 insertions(+), 778 deletions(-) create mode 100644 core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Watch.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Watch.java index 4ab826209..aaa0cdfde 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Watch.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Watch.java @@ -1,5 +1,7 @@ package org.openjdk.jmc.agent; +import org.openjdk.jmc.agent.util.expression.ExpressionResolver; +import org.openjdk.jmc.agent.util.expression.IllegalSyntaxException; import org.openjdk.jmc.agent.util.expression.ReferenceChain; import org.openjdk.jmc.agent.util.TypeUtils; @@ -55,7 +57,7 @@ public String getConverterClassName() { return this.converterClassName; } - public ReferenceChain resolveReferenceChain(Class callerClass) throws NoSuchFieldException { - return new ReferenceChain(callerClass, expression); + public ReferenceChain resolveReferenceChain(Class callerClass, boolean fromStaticContext) throws IllegalSyntaxException { + return new ExpressionResolver(callerClass, expression, fromStaticContext).solve(); } } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextEventClassGenerator.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextEventClassGenerator.java index 887c3db55..efc766c9e 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextEventClassGenerator.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextEventClassGenerator.java @@ -47,6 +47,7 @@ import org.openjdk.jmc.agent.Watch; import org.openjdk.jmc.agent.jfr.JFRTransformDescriptor; import org.openjdk.jmc.agent.util.TypeUtils; +import org.openjdk.jmc.agent.util.expression.IllegalSyntaxException; public class JFRNextEventClassGenerator { private static final String CLASS_EVENT = "jdk/jfr/Event"; //$NON-NLS-1$ @@ -66,7 +67,7 @@ public static byte[] generateEventClass(JFRTransformDescriptor td, Class clas return cw.toByteArray(); } - private static void generateAttributeFields(ClassWriter cw, JFRTransformDescriptor td, Class classBeingRedefined) throws NoSuchFieldException { + private static void generateAttributeFields(ClassWriter cw, JFRTransformDescriptor td, Class classBeingRedefined) throws IllegalSyntaxException { Type[] args = Type.getArgumentTypes(td.getMethod().getSignature()); for (Parameter param : td.getParameters()) { if (param.isReturn()) { @@ -77,7 +78,7 @@ private static void generateAttributeFields(ClassWriter cw, JFRTransformDescript } for (Watch watch : td.getWatches()) { - createField(cw, td, watch, watch.resolveReferenceChain(classBeingRedefined).getType()); + createField(cw, td, watch, watch.resolveReferenceChain(classBeingRedefined, false).getType()); } } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java index b82f94713..867af425e 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java @@ -42,6 +42,7 @@ import org.openjdk.jmc.agent.Watch; import org.openjdk.jmc.agent.jfr.JFRTransformDescriptor; import org.openjdk.jmc.agent.util.expression.FieldReference; +import org.openjdk.jmc.agent.util.expression.IllegalSyntaxException; import org.openjdk.jmc.agent.util.expression.ReferenceChain; import org.openjdk.jmc.agent.util.TypeUtils; @@ -129,11 +130,11 @@ private void createEvent() { for (Watch watch : transformDescriptor.getWatches()) { ReferenceChain refChain; try { - refChain = watch.resolveReferenceChain(classBeingRedefined).normalize(); - } catch (NoSuchFieldException e) { - throw new RuntimeException(e); // TODO: figure out what to do with this error - } - if (transformDescriptor.isAllowedFieldType(refChain.getType())) { + refChain = watch.resolveReferenceChain(classBeingRedefined, Modifier.isStatic(getAccess())).normalize(); + } catch (IllegalSyntaxException e) { + throw new RuntimeException(e); // TODO: figure out what to do with this error + } + if (transformDescriptor.isAllowedFieldType(refChain.getType())) { mv.visitInsn(DUP); loadWatch(watch, refChain); writeAttribute(watch, refChain.getType()); @@ -169,29 +170,11 @@ private void loadWatch(Watch watch, ReferenceChain refChain) { FieldReference ref = refs.get(i); if (ref instanceof FieldReference.ThisReference) { - if (isStatic) { - throw new IllegalStateException("unexpected \"this\" reference in a static method"); - } - if (i != 0) { - throw new IllegalStateException("unexpected position of \"this\" reference"); - } - mv.visitVarInsn(ALOAD, 0); // load "this" - continue; - } - - int opcode; - if (Modifier.isStatic(ref.getModifiers())) { - if (i != 0) { - throw new IllegalStateException("unexpected position of a static reference"); - } - opcode = GETSTATIC; - } else { - if (i == 0 && isStatic) { - throw new IllegalStateException("unexpected position of a dynamic reference in a static method"); - } - opcode = GETFIELD; + mv.visitVarInsn(ALOAD, 0); // load "this" + continue; } + int opcode = Modifier.isStatic(ref.getModifiers()) ? GETSTATIC : GETFIELD; mv.visitFieldInsn(opcode, ref.getMemberingType().getInternalName(), ref.getName(), ref.getType().getDescriptor()); } // loaded value, jump to writing attribute diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java new file mode 100644 index 000000000..5fcacc2ca --- /dev/null +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java @@ -0,0 +1,693 @@ +package org.openjdk.jmc.agent.util.expression; + +import org.openjdk.jmc.agent.util.AccessUtils; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +/* + Expression + -> this + | TypeName . this + | FieldAccess + + TypeName + -> TypeIdentifier + | PackageOrTypeName Dot TypeIdentifier + + PackageOrTypeName + -> identifier + | PackageOrTypeName . identifier + + TypeIdentifier + -> identifier + + FieldAccess + -> Expression . identifier + | super . identifier + | TypeName . super . identifier + | FieldName + + FieldName + -> identifier + + identifier // terminal symbols + -> [A-z_]+[A-z0-9_]* + */ +public class ExpressionResolver { + private final Class caller; + private final String expression; + private final boolean fromStaticContext; + + private List tokens = null; + private Iterator iterator = null; + private List referenceChain = null; + + public ExpressionResolver(Class caller, String expression, boolean fromStaticContext) { + this.caller = caller; + this.expression = expression; + this.fromStaticContext = fromStaticContext; + } + + public ReferenceChain solve() throws IllegalSyntaxException { + tokens = new LinkedList<>(Arrays.asList(expression.split("\\."))); + iterator = tokens.iterator(); + referenceChain = new LinkedList<>(); + + enterStartState(); + + return new ReferenceChain(caller, referenceChain, false); + } + + private void enterStartState() throws IllegalSyntaxException { + if (!iterator.hasNext()) { + enterIllegalState("Unexpected end of input: expects 'this', 'super', a field name, a class name, a package name, or a package name fragment"); + } + + String token = iterator.next(); // first identifier + + // "this" + if (tryEnterThisState(caller, token)) { + return; + } + + // "super" + if (tryEnterSuperState(caller, token)) { + return; + } + + // local/inherited field reference + if (tryEnterFieldReferenceState(caller, token, fromStaticContext)) { + return; + } + + // nested field reference + if (tryEntryNestedFieldReferenceState(token)) { // static class? + return; + } + + // outer class reference + if (tryEnterOuterClassState(token)) { + return; + } + + // inner class reference + if (tryEnterInnerClassState(caller, token)) { + return; + } + + // CallerClass + if (tryEnterSameClassState(token)) { + return; + } + + // ClassWithInTheSamePackage + if (tryEnterClassState(token)) { + return; + } + + // com.full.qualified.pkg.ClassName + if (tryEnterPackageState(expression)) { + return; + } + + // partially.qualified.pkg.ClassName + if (tryEnterPackageState(caller.getPackage(), expression)) { + return; + } + + // eg. Object => java.lang.Object, Integer => java.lang.Integer + if (tryEnterPackageState(Package.getPackage("java.lang"), expression)) { + return; + } + + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); + } + + private boolean tryEnterThisState(Class targetClass, String thisLiteral) throws IllegalSyntaxException { + if (!"this".equals(thisLiteral)) { + return false; + } + + enterThisState(targetClass); + return true; + } + + // "^this" or "Qualified.this" expression (casting to an enclosing class) + private void enterThisState(Class targetClass) throws IllegalSyntaxException { + if (fromStaticContext) { + enterIllegalState(String.format("'%s.this' cannot be referenced from a static context", targetClass.getName())); + } + + doOutwardsCasting(targetClass); + + if (!iterator.hasNext()) { // accepted state + return; + } + String token = iterator.next(); + + // this.prop + if (tryEnterFieldReferenceState(targetClass, token, false)) { + return; + } + + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); + } + + private boolean tryEnterSuperState(Class targetClass, String superLiteral) throws IllegalSyntaxException { + if (!"super".equals(superLiteral)) { + return false; + } + + enterSuperState(targetClass); + return true; + } + + // "^super" or "Qualified.super" expression + private void enterSuperState(Class targetClass) throws IllegalSyntaxException { + if (fromStaticContext) { + enterIllegalState(String.format("'%s.super' cannot be referenced from a static context", targetClass.getName())); + } + + doOutwardsCasting(targetClass); + + Class superClass = targetClass.getSuperclass(); + if (superClass == null) { // almost would never happen, java.lang classes are not transformable + enterIllegalState(String.format("'%s' has no super class", targetClass.getName())); + } + + if (!iterator.hasNext()) { // rejected state + enterIllegalState("unexpected end of input"); + } + String token = iterator.next(); + + // super.prop + if (tryEnterFieldReferenceState(superClass, token, false)) { + return; + } + + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); + } + + private boolean tryEnterFieldReferenceState(Class memberingClass, String fieldName, boolean fromStaticContext) throws IllegalSyntaxException { + try { + Field field = AccessUtils.getFieldOnHierarchy(memberingClass, fieldName); + enterFieldReferenceState(memberingClass, field, fromStaticContext); + return true; + } catch (NoSuchFieldException e) { + return false; + } + } + + private void enterFieldReferenceState(Class memberingClass, Field field, boolean fromStaticContext) throws IllegalSyntaxException { + if (fromStaticContext && !Modifier.isStatic(field.getModifiers())) { + enterIllegalState(String.format("Non-static field '%s' cannot be referenced from a static context", field.getName())); + } + + if (!AccessUtils.isAccessible(memberingClass, field, caller)) { + String access; + if (Modifier.isPrivate(field.getModifiers())) { + access = "private"; + } else if (Modifier.isProtected(field.getModifiers())) { + access = "protected"; + } else { + access = "package-private"; + } + + enterIllegalState(String.format("'%s' has %s access in '%s'", field.getName(), access, field.getDeclaringClass().getName())); + } + + referenceChain.add(new FieldReference(memberingClass, field)); + + if (!iterator.hasNext()) { // accepted state + return; + } + String token = iterator.next(); + + // prop.prop2 + if (tryEnterFieldReferenceState(field.getType(), token, false)) { + return; + } + + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); + } + + private boolean tryEntryNestedFieldReferenceState(String fieldName) throws IllegalSyntaxException { + Class enclosing = caller.getEnclosingClass(); + while (enclosing != null) { + try { + Field field = AccessUtils.getFieldOnHierarchy(enclosing, fieldName); + enterNestedFieldReferenceState(enclosing, field); + return true; + } catch (NoSuchFieldException e) { + enclosing = enclosing.getEnclosingClass(); + } + } + + return false; + } + + private void enterNestedFieldReferenceState(Class enclosingClass, Field field) throws IllegalSyntaxException { + if (!Modifier.isStatic(field.getModifiers())) { + Class c = caller.getEnclosingClass(); // the inner class is always static if it has a static method + // check there is no static class in between, before reaching the enclosing class + while (!c.equals(enclosingClass)) { + if (Modifier.isStatic(c.getModifiers())) { + enterIllegalState(String.format("Non-static field '%s' cannot be referenced from a static context", field.getName())); + } + c = c.getEnclosingClass(); + } + } + + // this is syntactically allowed, but we don't support it for now + if (Modifier.isPrivate(field.getModifiers())) { + enterIllegalState(new UnsupportedOperationException("Private member access between nestmates is not supported")); + } + + if (!Modifier.isStatic(field.getModifiers())) { + doOutwardsCasting(enclosingClass); // cast to outer class instance only when accessing non-static fields + } + + referenceChain.add(new FieldReference(enclosingClass, field)); + + if (!iterator.hasNext()) { // accepted state + return; + } + String token = iterator.next(); + + // nestedProp.prop + if (tryEnterFieldReferenceState(field.getType(), token, false)) { + return; + } + + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); + } + + private boolean tryEnterOuterClassState(String simpleClassName) throws IllegalSyntaxException { + Class enclosing = caller.getEnclosingClass(); + while (enclosing != null) { + if (enclosing.getSimpleName().equals(simpleClassName)) { + enterOuterClassState(enclosing); + return true; + } + + enclosing = enclosing.getEnclosingClass(); + } + + return false; + } + + private boolean tryEnterOuterClassState(Package pkg, String className) throws IllegalSyntaxException { + String fqcn = pkg.getName().isEmpty() ? className : pkg.getName() + "." + className; + try { + Class clazz = caller.getClassLoader().loadClass(fqcn); + Class enclosing = caller.getEnclosingClass(); + while (enclosing != null) { + if (enclosing.equals(clazz)) { + enterOuterClassState(enclosing); + return true; + } + + enclosing = enclosing.getEnclosingClass(); + } + } catch (ClassNotFoundException e) { + // no op + } + + return false; + } + + private boolean tryEnterOuterClassState(Class currentClass, String simpleClassName) throws IllegalSyntaxException { + Class clazz = null; + for (Class c : currentClass.getDeclaredClasses()) { + if (c.getSimpleName().equals(simpleClassName)) { + clazz = c; + break; + } + } + + Class enclosing = caller.getEnclosingClass(); + while (enclosing != null) { + if (enclosing.equals(clazz)) { + enterOuterClassState(enclosing); + return true; + } + + enclosing = enclosing.getEnclosingClass(); + } + + return false; + } + + private void enterOuterClassState(Class targetClass) throws IllegalSyntaxException { + // static context + if (!iterator.hasNext()) { // rejected state + enterIllegalState("Unexpected end of input: expects 'this', 'super', a static field name, or an inner class name"); + } + String token = iterator.next(); + + // OuterClass.this + if (tryEnterThisState(targetClass, token)) { + return; + } + + // OuterClass.super + if (tryEnterSuperState(targetClass, token)) { + return; + } + + // OuterClass.STATIC_PROP + if (tryEnterFieldReferenceState(targetClass, token, true)) { + return; + } + + // OuterClass.ThisClass + if (tryEnterSameClassState(targetClass, token)) { + return; + } + + // OuterMostClass.OuterClass + if (tryEnterOuterClassState(targetClass, token)) { + return; + } + + // OuterClass.OtherClass + if (tryEnterNestMateClass(targetClass, token)) { + return; + } + + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); + } + + private boolean tryEnterInnerClassState(Class currentClass, String simpleClassName) throws IllegalSyntaxException { + for (Class innerClass : currentClass.getDeclaredClasses()) { + if (innerClass.getSimpleName().equals(simpleClassName)) { + enterInnerClassState(innerClass); + return true; + } + } + + return false; + } + + private void enterInnerClassState(Class targetClass) throws IllegalSyntaxException { + // static context + if (!iterator.hasNext()) { // rejected state + enterIllegalState("Unexpected end of input: expects a static field name or an inner class name"); + } + String token = iterator.next(); + + // InnerClass.STATIC_PROP + if (tryEnterFieldReferenceState(targetClass, token, true)) { + return; + } + + // InnerClass.InnerMoreClass + if (tryEnterInnerClassState(targetClass, token)) { + return; + } + + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); + } + + // target class is not a inner or outer class of the caller class, but is a classmate + private boolean tryEnterNestMateClass(Class currentClass, String simpleClassName) throws IllegalSyntaxException { + Class clazz = null; + for (Class c : currentClass.getDeclaredClasses()) { + if (c.getSimpleName().equals(simpleClassName)) { + clazz = c; + break; + } + } + + if (clazz == null) { + return false; + } + + if (!AccessUtils.areNestMates(clazz, caller)) { + return false; + } + + // check caller is not an outer class of clazz + Class enclosing = clazz; + while (enclosing != null) { + if (caller.equals(enclosing)) { + return false; + } + enclosing = enclosing.getEnclosingClass(); + } + + // check clazz if not an outer class of caller + enclosing = caller; + while (enclosing != null) { + if (clazz.equals(enclosing)) { + return false; + } + enclosing = enclosing.getEnclosingClass(); + } + + enterNestMateClass(clazz); + return true; + } + + + private void enterNestMateClass(Class targetClass) throws IllegalSyntaxException { + // static context + if (!iterator.hasNext()) { // rejected state + enterIllegalState("Unexpected end of input: expects a static field name or an inner class name"); + } + String token = iterator.next(); + + // NestMateClass.STATIC_PROP + if (tryEnterFieldReferenceState(targetClass, token, false)) { + return; + } + + // NestMateClass.NestMatesInnerClass + if (tryEnterNestMateClass(targetClass, token)) { + return; + } + + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); + } + + private boolean tryEnterSameClassState(String simpleClassName) throws IllegalSyntaxException { + if (caller.getSimpleName().equals(simpleClassName)) { + enterSameClassState(); + return true; + } + + return false; + } + + private boolean tryEnterSameClassState(Package pkg, String simpleClassName) throws IllegalSyntaxException { + String fqcn = pkg.getName().isEmpty() ? simpleClassName : pkg.getName() + "." + simpleClassName; + if (caller.getName().equals(fqcn)) { + enterSameClassState(); + return true; + } + + return false; + } + + private boolean tryEnterSameClassState(Class currentClass, String simpleClassName) throws IllegalSyntaxException { + Class clazz = null; + for (Class c : currentClass.getDeclaredClasses()) { + if (c.getSimpleName().equals(simpleClassName)) { + clazz = c; + break; + } + } + + if (caller.equals(clazz)) { + enterSameClassState(); + return true; + } + + return false; + } + + private void enterSameClassState() throws IllegalSyntaxException { + // static context + if (!iterator.hasNext()) { // rejected state + enterIllegalState("Unexpected end of input: expects a static field name or an inner class name"); + } + String token = iterator.next(); + + // CallerClass.this => this + if (tryEnterThisState(caller, token)) { + return; + } + + // CallerClass.super => super + if (tryEnterSuperState(caller, token)) { + return; + } + + // CallerClass.STATIC_PROP + if (tryEnterFieldReferenceState(caller, token, true)) { + return; + } + + if (tryEnterInnerClassState(caller, token)) { + return; + } + + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); + } + + private boolean tryEnterClassState(String simpleClassName) throws IllegalSyntaxException { + return tryEnterClassState(caller.getPackage(), simpleClassName); + } + + private boolean tryEnterClassState(Class currentClass, String simpleClassName) throws IllegalSyntaxException { + for (Class c : currentClass.getDeclaredClasses()) { + if (c.getSimpleName().equals(simpleClassName)) { + enterClassState(c); + return true; + } + } + + return false; + } + + private boolean tryEnterClassState(Package pkg, String simpleClassName) throws IllegalSyntaxException { + String fqcn = pkg.getName().isEmpty() ? simpleClassName : pkg.getName() + "." + simpleClassName; + + try { + Class c = caller.getClassLoader().loadClass(fqcn); + enterClassState(c); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + + private void enterClassState(Class targetClass) throws IllegalSyntaxException { + // static context + if (!iterator.hasNext()) { // rejected state + enterIllegalState("Unexpected end of input: expects a static field name or an inner class name"); + } + String token = iterator.next(); + + // ClassName.STATIC_PROP + if (tryEnterFieldReferenceState(targetClass, token, true)) { + return; + } + + // ClassName.InnerClass + if (tryEnterClassState(targetClass, token)) { + return; + } + + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); + } + + // Full qualified package named prefixed expression + private boolean tryEnterPackageState(String fqpnPrefixedExpression) throws IllegalSyntaxException { + // ClassLoader.getPackage(String) or ClassLoader.getPackages() is not reliable when no class from that package is yet loaded + int stop = 0; + Class clazz = null; + while (stop < fqpnPrefixedExpression.length()) { + stop = fqpnPrefixedExpression.indexOf('.', stop + 1); + if (stop == -1) { + break; + } + + String fqcn = fqpnPrefixedExpression.substring(0, stop); + try { + clazz = caller.getClassLoader().loadClass(fqcn); + break; + } catch (ClassNotFoundException e) { + // no op + } + } + + if (clazz == null) { + return false; + } + + Package pkg = clazz.getPackage(); + + tokens = new LinkedList<>(Arrays.asList(fqpnPrefixedExpression.split("\\."))); + iterator = tokens.iterator(); + int length = pkg.getName().split("\\.").length; + for (int i = 0; i < length; i++) { + iterator.next(); + } + + enterPackageState(pkg); + return true; + } + + // Partially qualified package named prefixed expression + private boolean tryEnterPackageState(Package pkg, String pqpnPrefixedExpression) throws IllegalSyntaxException { + String pkgPrefix = pkg.getName().isEmpty() ? "" : pkg.getName() + "."; + return tryEnterPackageState(pkgPrefix + pqpnPrefixedExpression); + } + + private void enterPackageState(Package pkg) throws IllegalSyntaxException { + if (!iterator.hasNext()) { // rejected state + enterIllegalState("Unexpected end of input: expects a class name"); + } + String token = iterator.next(); + + if (tryEnterSameClassState(pkg, token)) { + return; + } + + if (tryEnterOuterClassState(pkg, token)) { + return; + } + + if (tryEnterClassState(pkg, token)) { + return; + } + + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); + } + + private void enterIllegalState(String msg) throws IllegalSyntaxException { + throw new IllegalSyntaxException(msg); + } + + private void enterIllegalState(Throwable throwable) throws IllegalSyntaxException { + throw new IllegalSyntaxException(throwable); + } + + private void doOutwardsCasting(Class targetClass) throws IllegalSyntaxException { + referenceChain.add(new FieldReference.ThisReference(caller)); + + // need to do outwards casting first + if (!targetClass.equals(caller)) { + Class c = caller; + int i = 0; // depth of inner class nesting, used for this$i reference to enclosing classes + while (!targetClass.equals(c.getEnclosingClass())) { + Class enclosing = c.getEnclosingClass(); + if (enclosing == null) { + enterIllegalState(String.format("%s is not an enclosing class of %s", targetClass.getName(), caller.getName())); + } + + i++; + c = enclosing; + } + + + c = caller; + while (!targetClass.equals(c.getEnclosingClass())) { + Class enclosing = c.getEnclosingClass(); + if (enclosing == null) { + enterIllegalState(String.format("%s is not an enclosing class of %s", targetClass.getName(), caller.getName())); + } + referenceChain.add(new FieldReference.QualifiedThisReference(c, enclosing, i--)); + c = enclosing; + } + + referenceChain.add(new FieldReference.QualifiedThisReference(c, c.getEnclosingClass(), i--)); + } + } +} \ No newline at end of file diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/FieldReference.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/FieldReference.java index 467b37781..65126a7d7 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/FieldReference.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/FieldReference.java @@ -9,16 +9,16 @@ public class FieldReference { private final Class memberingClass; private final Field field; - - public FieldReference(Class memberingClass, Field type) { + + public FieldReference(Class memberingClass, Field field) { this.memberingClass = memberingClass; - this.field = type; + this.field = field; } public Class getMemberingClass() { return memberingClass; } - + public Type getMemberingType() { return Type.getType(getMemberingClass()); } @@ -26,13 +26,17 @@ public Type getMemberingType() { public Field getField() { return field; } - + public String getName() { return getField().getName(); } - + public Type getType() { - return Type.getType(getField().getType()); + return Type.getType(getReferencedClass()); + } + + public Class getReferencedClass() { + return getField().getType(); } public int getModifiers() { @@ -41,17 +45,19 @@ public int getModifiers() { @Override public String toString() { - return String.format("%s.%s : %s", TypeUtils.getInternalName(getMemberingClass().getName()), getName(), getType().getClassName()); + return String.format("FieldRef %s.%s:%s", TypeUtils.getInternalName(getMemberingClass().getName()), getName(), getType().getClassName()); } + // for "this" expression and "Qualified.this" expression public static class ThisReference extends FieldReference { - public ThisReference(Class memberingClass) { - super(memberingClass, null); + + public ThisReference(Class enclosingClass) { // the caller class is technically an enclosing class of itself + super(enclosingClass, null); } @Override public String getName() { - return null; + return "this"; } @Override @@ -59,6 +65,11 @@ public Type getType() { return getMemberingType(); } + @Override + public Class getReferencedClass() { + return getMemberingClass(); + } + @Override public int getModifiers() { return 0; @@ -66,36 +77,39 @@ public int getModifiers() { @Override public String toString() { - return "\"this\""; + return getName(); } } - // implicit qualified "this" - public static class OutwardsCastingReference extends FieldReference { - private Class targetClass; - - public OutwardsCastingReference(Class thisClass, Class targetClass) { - super(thisClass, null); - this.targetClass = targetClass; - - if (thisClass.getEnclosingClass() != targetClass) { - throw new IllegalArgumentException(String.format("%s is not the direct outer class of %s", targetClass.getName(), thisClass.getName())); - } + // "Qualified.this" + public static class QualifiedThisReference extends FieldReference { + private Class enclosingClass; + private int index; + + public QualifiedThisReference(Class innerClass, Class enclosingClass, int index) { + super(innerClass, null); + this.enclosingClass = enclosingClass; + this.index = index; } @Override public String getName() { - return "this$0"; + return "this$" + index; } @Override public Type getType() { - return Type.getType(targetClass); + return Type.getType(enclosingClass); } @Override public int getModifiers() { return Opcodes.ACC_PRIVATE | Opcodes.ACC_SYNTHETIC; } + + @Override + public String toString() { + return getName(); + } } } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java index 5d3f3da9b..ca9b964f0 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java @@ -1,71 +1,20 @@ package org.openjdk.jmc.agent.util.expression; import org.objectweb.asm.Type; -import org.openjdk.jmc.agent.util.AccessUtils; -import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.Arrays; -import java.util.Iterator; import java.util.LinkedList; import java.util.List; -import java.util.Scanner; public class ReferenceChain { private final Class callerClass; private final List references; + private final boolean normalized; - // DEBUG - public static void main(String[] args) { - String context = ""; - String expression = ""; - while (true) { - String tmp; - - Scanner scnr = new Scanner(System.in); - System.out.println("Context: "); - tmp = scnr.nextLine(); - if (!tmp.isEmpty()) context = tmp; - System.out.println("Expression: "); - tmp = scnr.nextLine(); - if (!tmp.isEmpty()) expression = tmp; - - String className = context.substring(0, context.indexOf('#')); - String methodName = context.substring(context.indexOf('#') + 1); - try { - Class clazz = Class.forName(className); - Method method = null; - for (Method m : clazz.getMethods()) { - if (m.getName().equals(methodName)) { - method = m; - } - } - - - new ExpressionStateMachine(clazz, method, expression).solve(); - System.out.println("done"); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - public ReferenceChain(Class callerClass, String pathExpression) throws NoSuchFieldException { - this.callerClass = callerClass; -// this.references = new LinkedList<>(); - - if (pathExpression == null || pathExpression.length() == 0) { - throw new IllegalArgumentException("Expect a non-null and non-empty path expression"); - } - -// references = new ExpressionStateMachine(callerClass, pathExpression).solve(); - references = null; - } - - private ReferenceChain(Class callerClass, List references) { + public ReferenceChain(Class callerClass, List references, boolean normalized) { this.callerClass = callerClass; this.references = references; + this.normalized = normalized; } public Class getCallerClass() { @@ -76,6 +25,10 @@ public List getReferences() { return references; } + public boolean isNormalized() { + return normalized; + } + public ReferenceChain normalize() { List oldRefs = getReferences(); List newRefs = new LinkedList<>(); @@ -85,18 +38,20 @@ public ReferenceChain normalize() { if (Modifier.isStatic(ref.getModifiers())) { newRefs.clear(); } + newRefs.add(ref); } + // Don't reduce static final reference to constant. The value could be different if loaded via different class loaders. - // prepend "this" + // prepend "this" if starts with non-static field reference if (!newRefs.isEmpty() && !Modifier.isStatic(newRefs.get(0).getModifiers()) && !(newRefs.get(0) instanceof FieldReference.ThisReference)) { newRefs.add(0, new FieldReference.ThisReference(callerClass)); } - return new ReferenceChain(callerClass, newRefs); + return new ReferenceChain(callerClass, newRefs, true); } public Type getType() { @@ -105,671 +60,4 @@ public Type getType() { } return references.get(references.size() - 1).getType(); } - - /* - Expression - -> this - | TypeName . this - | FieldAccess - - TypeName - -> TypeIdentifier - | PackageOrTypeName Dot TypeIdentifier - - PackageOrTypeName - -> identifier - | PackageOrTypeName . identifier - - TypeIdentifier - -> identifier - - FieldAccess - -> Expression . identifier - | super . identifier - | TypeName . super . identifier - | FieldName - - FieldName - -> identifier - - identifier // terminal symbols - -> [A-z_]+[A-z0-9_]* - */ - private static class ExpressionStateMachine { - private final Class caller; - private final Method method; - private final String expression; - - private List tokens = null; - private Iterator iterator = null; - private List referenceChain = null; - - private ExpressionStateMachine(Class caller, Method method, String expression) { - this.caller = caller; - this.method = method; - this.expression = expression; - } - - private void solve() throws IllegalSyntaxException { - tokens = new LinkedList<>(Arrays.asList(expression.split("\\."))); - iterator = tokens.iterator(); - referenceChain = new LinkedList<>(); - - enterStartState(); - } - - private void enterStartState() throws IllegalSyntaxException { - if (!iterator.hasNext()) { - enterIllegalState("Unexpected end of input: expects 'this', 'super', a field name, a class name, a package name, or a package name fragment"); - } - - String token = iterator.next(); // first identifier - - // "this" - if (tryEnterThisState(caller, token)) { - return; - } - - // "super" - if (tryEnterSuperState(caller, token)) { - return; - } - - // local/inherited field reference - if (tryEnterFieldReferenceState(caller, token, Modifier.isStatic(method.getModifiers()))) { - return; - } - - // nested field reference - if (tryEntryNestedFieldReferenceState(token)) { // static class? - return; - } - - // outer class reference - if (tryEnterOuterClassState(token)) { - return; - } - - // inner class reference - if (tryEnterInnerClassState(caller, token)) { - return; - } - - // CallerClass - if (tryEnterSameClassState(token)) { - return; - } - - // ClassWithInTheSamePackage - if (tryEnterClassState(token)) { - return; - } - - // com.full.qualified.pkg.ClassName - if (tryEnterPackageState(expression)) { - return; - } - - // partially.qualified.pkg.ClassName - if (tryEnterPackageState(caller.getPackage(), expression)) { - return; - } - - // eg. Object => java.lang.Object, Integer => java.lang.Integer - if (tryEnterPackageState(Package.getPackage("java.lang"), expression)) { - return; - } - - enterIllegalState(String.format("Unrecognized symbol '%s'", token)); - } - - private boolean tryEnterThisState(Class targetClass, String thisLiteral) throws IllegalSyntaxException { - if (!"this".equals(thisLiteral)) { - return false; - } - - enterThisState(targetClass); - return true; - } - - // "^this" or qualified . "this" expression (outwards casting) - private void enterThisState(Class targetClass) throws IllegalSyntaxException { - if (Modifier.isStatic(method.getModifiers())) { - enterIllegalState(String.format("'%s.this' cannot be referenced from a static context", targetClass.getName())); - } - - doOutwardsCasting(targetClass); - - if (!iterator.hasNext()) { // accepted state - return; - } - String token = iterator.next(); - - // this.prop - if (tryEnterFieldReferenceState(targetClass, token, false)) { - return; - } - - enterIllegalState(String.format("Unrecognized symbol '%s'", token)); - } - - private boolean tryEnterSuperState(Class targetClass, String superLiteral) throws IllegalSyntaxException { - if (!"super".equals(superLiteral)) { - return false; - } - - enterSuperState(targetClass); - return true; - } - - // "^super" or qualified . "super" expression (outwards casting) - private void enterSuperState(Class targetClass) throws IllegalSyntaxException { - if (Modifier.isStatic(method.getModifiers())) { - enterIllegalState(String.format("'%s.super' cannot be referenced from a static context", targetClass.getName())); - } - - doOutwardsCasting(targetClass); - - Class superClass = targetClass.getSuperclass(); - // TODO: add super reference - System.out.printf("super class reference from %s to %s\n", caller, superClass); - - if (!iterator.hasNext()) { // rejected state - enterIllegalState("unexpected end of input"); - } - String token = iterator.next(); - - // this.prop - if (tryEnterFieldReferenceState(targetClass, token, false)) { - return; - } - - enterIllegalState(String.format("Unrecognized symbol '%s'", token)); - } - - private boolean tryEnterFieldReferenceState(Class memberingClass, String fieldName, boolean fromStaticContext) throws IllegalSyntaxException { - try { - Field field = AccessUtils.getFieldOnHierarchy(memberingClass, fieldName); - enterFieldReferenceState(memberingClass, field, fromStaticContext); - return true; - } catch (NoSuchFieldException e) { - return false; - } - } - - private void enterFieldReferenceState(Class memberingClass, Field field, boolean fromStaticContext) throws IllegalSyntaxException { - if (fromStaticContext && !Modifier.isStatic(field.getModifiers())) { - enterIllegalState(String.format("Non-static field '%s' cannot be referenced from a static context", field.getName())); - } - - if (!AccessUtils.isAccessible(memberingClass, field, caller)) { - String access; - if (Modifier.isPrivate(field.getModifiers())) { - access = "private"; - } else if (Modifier.isProtected(field.getModifiers())) { - access = "protected"; - } else { - access = "package-private"; - } - - enterIllegalState(String.format("'%s' has %s access in '%s'", field.getName(), access, field.getDeclaringClass().getName())); - } - - // TODO: add field reference - System.out.printf("field reference %s.%s:%s\n", memberingClass.getName(), field.getName(), field.getType().getName()); - - if (!iterator.hasNext()) { // accepted state - return; - } - String token = iterator.next(); - - // prop.prop2 - if (tryEnterFieldReferenceState(field.getType(), token, false)) { - return; - } - - enterIllegalState(String.format("Unrecognized symbol '%s'", token)); - } - - private boolean tryEntryNestedFieldReferenceState(String fieldName) throws IllegalSyntaxException { - Class enclosing = caller.getEnclosingClass(); - while (enclosing != null) { - try { - Field field = AccessUtils.getFieldOnHierarchy(enclosing, fieldName); - enterNestedFieldReferenceState(enclosing, field); - return true; - } catch (NoSuchFieldException e) { - enclosing = enclosing.getEnclosingClass(); - } - } - - return false; - } - - private void enterNestedFieldReferenceState(Class enclosingClass, Field field) throws IllegalSyntaxException { - if (!Modifier.isStatic(field.getModifiers())) { - Class c = caller; - // check there is no static class in between before reaching this enclosed class - while (c != null && !c.equals(enclosingClass)) { - if (Modifier.isStatic(c.getModifiers())) { - enterIllegalState(String.format("Non-static field '%s' cannot be referenced from a static context", field.getName())); - } - - c = caller.getEnclosingClass(); - } - } - - // this is syntactically allowed, but we don't support it for now - if (Modifier.isPrivate(field.getModifiers())) { - enterIllegalState(new UnsupportedOperationException("Private member access between nestmates is not supported")); - } - - doOutwardsCasting(enclosingClass); - - // TODO: add field reference - System.out.printf("nested field reference %s.%s:%s\n", enclosingClass.getName(), field.getName(), field.getType().getName()); - - if (!iterator.hasNext()) { // accepted state - return; - } - String token = iterator.next(); - - // nestedProp.prop - if (tryEnterFieldReferenceState(field.getType(), token, false)) { - return; - } - - enterIllegalState(String.format("Unrecognized symbol '%s'", token)); - } - - private boolean tryEnterOuterClassState(String simpleClassName) throws IllegalSyntaxException { - Class enclosing = caller.getEnclosingClass(); - while (enclosing != null) { - if (enclosing.getSimpleName().equals(simpleClassName)) { - enterOuterClassState(enclosing); - return true; - } - - enclosing = enclosing.getEnclosingClass(); - } - - return false; - } - - private boolean tryEnterOuterClassState(Package pkg, String className) throws IllegalSyntaxException { - String fqcn = pkg.getName().isEmpty() ? className : pkg.getName() + "." + className; - try { - Class clazz = caller.getClassLoader().loadClass(fqcn); - Class enclosing = caller.getEnclosingClass(); - while (enclosing != null) { - if (enclosing.equals(clazz)) { - enterOuterClassState(enclosing); - return true; - } - - enclosing = enclosing.getEnclosingClass(); - } - } catch (ClassNotFoundException e) { - // no op - } - - return false; - } - - private boolean tryEnterOuterClassState(Class currentClass, String simpleClassName) throws IllegalSyntaxException { - Class clazz = null; - for (Class c : currentClass.getDeclaredClasses()) { - if (c.getSimpleName().equals(simpleClassName)) { - clazz = c; - break; - } - } - - Class enclosing = caller.getEnclosingClass(); - while (enclosing != null) { - if (enclosing.equals(clazz)) { - enterOuterClassState(enclosing); - return true; - } - - enclosing = enclosing.getEnclosingClass(); - } - - return false; - } - - private void enterOuterClassState(Class targetClass) throws IllegalSyntaxException { - // static context - if (!iterator.hasNext()) { // rejected state - enterIllegalState("Unexpected end of input: expects 'this', 'super', a static field name, or an inner class name"); - } - String token = iterator.next(); - - // OuterClass.this - if (tryEnterThisState(targetClass, token)) { - return; - } - - // OuterClass.super - if (tryEnterSuperState(targetClass, token)) { - return; - } - - // OuterClass.STATIC_PROP - if (tryEnterFieldReferenceState(targetClass, token, true)) { - return; - } - - // OuterClass.ThisClass - if (tryEnterSameClassState(targetClass, token)) { - return; - } - - // OuterMostClass.OuterClass - if (tryEnterOuterClassState(targetClass, token)) { - return; - } - - // OuterClass.OtherClass - if (tryEnterNestMateClass(targetClass, token)) { - return; - } - - enterIllegalState(String.format("Unrecognized symbol '%s'", token)); - } - - private boolean tryEnterInnerClassState(Class currentClass, String simpleClassName) throws IllegalSyntaxException { - for (Class innerClass : currentClass.getDeclaredClasses()) { - if (innerClass.getSimpleName().equals(simpleClassName)) { - enterInnerClassState(innerClass); - return true; - } - } - - return false; - } - - private void enterInnerClassState(Class targetClass) throws IllegalSyntaxException { - // static context - if (!iterator.hasNext()) { // rejected state - enterIllegalState("Unexpected end of input: expects a static field name or an inner class name"); - } - String token = iterator.next(); - - // InnerClass.STATIC_PROP - if (tryEnterFieldReferenceState(targetClass, token, true)) { - return; - } - - // InnerClass.InnerMoreClass - if (tryEnterInnerClassState(targetClass, token)) { - return; - } - - enterIllegalState(String.format("Unrecognized symbol '%s'", token)); - } - - // target class is not a inner or outer class of the caller class, but is a classmate - private boolean tryEnterNestMateClass(Class currentClass, String simpleClassName) throws IllegalSyntaxException { - Class clazz = null; - for (Class c : currentClass.getDeclaredClasses()) { - if (c.getSimpleName().equals(simpleClassName)) { - clazz = c; - break; - } - } - - if (clazz == null) { - return false; - } - - if (!AccessUtils.areNestMates(clazz, caller)) { - return false; - } - - // check caller is not an outer class of clazz - Class enclosing = clazz; - while (enclosing != null) { - if (caller.equals(enclosing)) { - return false; - } - enclosing = enclosing.getEnclosingClass(); - } - - // check clazz if not an outer class of caller - enclosing = caller; - while (enclosing != null) { - if (clazz.equals(enclosing)) { - return false; - } - enclosing = enclosing.getEnclosingClass(); - } - - return true; - } - - - private void enterNestMateClass(Class targetClass) throws IllegalSyntaxException { - // static context - if (!iterator.hasNext()) { // rejected state - enterIllegalState("Unexpected end of input: expects a static field name or an inner class name"); - } - String token = iterator.next(); - - // NestMateClass.STATIC_PROP - if (tryEnterFieldReferenceState(targetClass, token, false)) { - return; - } - - // NestMateClass.NestMatesInnerClass - if (tryEnterNestMateClass(targetClass, token)) { - return; - } - - enterIllegalState(String.format("Unrecognized symbol '%s'", token)); - } - - private boolean tryEnterSameClassState(String simpleClassName) throws IllegalSyntaxException { - if (caller.getSimpleName().equals(simpleClassName)) { - enterSameClassState(); - return true; - } - - return false; - } - - private boolean tryEnterSameClassState(Package pkg, String simpleClassName) throws IllegalSyntaxException { - String fqcn = pkg.getName().isEmpty() ? simpleClassName : pkg.getName() + "." + simpleClassName; - if (caller.getName().equals(fqcn)) { - enterSameClassState(); - return true; - } - - return false; - } - - private boolean tryEnterSameClassState(Class currentClass, String simpleClassName) throws IllegalSyntaxException { - Class clazz = null; - for (Class c : currentClass.getDeclaredClasses()) { - if (c.getSimpleName().equals(simpleClassName)) { - clazz = c; - break; - } - } - - if (caller.equals(clazz)) { - enterSameClassState(); - return true; - } - - return false; - } - - private void enterSameClassState() throws IllegalSyntaxException { - // static context - if (!iterator.hasNext()) { // rejected state - enterIllegalState("Unexpected end of input: expects a static field name or an inner class name"); - } - String token = iterator.next(); - - // CallerClass.this => this - if (tryEnterThisState(caller, token)) { - return; - } - - // CallerClass.super => super - if (tryEnterSuperState(caller, token)) { - return; - } - - // CallerClass.STATIC_PROP - if (tryEnterFieldReferenceState(caller, token, true)) { - return; - } - - if (tryEnterInnerClassState(caller, token)) { - return; - } - - enterIllegalState(String.format("Unrecognized symbol '%s'", token)); - } - - private boolean tryEnterClassState(String simpleClassName) throws IllegalSyntaxException { - return tryEnterClassState(caller.getPackage(), simpleClassName); - } - - private boolean tryEnterClassState(Class currentClass, String simpleClassName) throws IllegalSyntaxException { - for (Class c : currentClass.getDeclaredClasses()) { - if (c.getSimpleName().equals(simpleClassName)) { - enterClassState(c); - return true; - } - } - - return false; - } - - private boolean tryEnterClassState(Package pkg, String simpleClassName) throws IllegalSyntaxException { - String fqcn = pkg.getName().isEmpty() ? simpleClassName : pkg.getName() + "." + simpleClassName; - - try { - Class c = caller.getClassLoader().loadClass(fqcn); - enterClassState(c); - return true; - } catch (ClassNotFoundException e) { - return false; - } - } - - private void enterClassState(Class targetClass) throws IllegalSyntaxException { - // static context - if (!iterator.hasNext()) { // rejected state - enterIllegalState("Unexpected end of input: expects a static field name or an inner class name"); - } - String token = iterator.next(); - - // ClassName.STATIC_PROP - if (tryEnterFieldReferenceState(targetClass, token, true)) { - return; - } - - // ClassName.InnerClass - if (tryEnterClassState(targetClass, token)) { - return; - } - - enterIllegalState(String.format("Unrecognized symbol '%s'", token)); - } - - // Full qualified package named prefixed expression - private boolean tryEnterPackageState(String fqpnPrefixedExpression) throws IllegalSyntaxException { - // ClassLoader.getPackage(String) or ClassLoader.getPackages() is not reliable when no class from that package is yet loaded - int stop = 0; - Class clazz = null; - while (stop < fqpnPrefixedExpression.length()) { - stop = fqpnPrefixedExpression.indexOf('.', stop + 1); - if (stop == -1) { - break; - } - - String fqcn = fqpnPrefixedExpression.substring(0, stop); - try { - clazz = caller.getClassLoader().loadClass(fqcn); - break; - } catch (ClassNotFoundException e) { - // no op - } - } - - if (clazz == null) { - return false; - } - - Package pkg = clazz.getPackage(); - - tokens = new LinkedList<>(Arrays.asList(fqpnPrefixedExpression.split("\\."))); - iterator = tokens.iterator(); - int length = pkg.getName().split("\\.").length; - for (int i = 0; i < length; i++) { - iterator.next(); - } - - enterPackageState(pkg); - return true; - } - - // Partially qualified package named prefixed expression - private boolean tryEnterPackageState(Package pkg, String pqpnPrefixedExpression) throws IllegalSyntaxException { - String pkgPrefix = pkg.getName().isEmpty() ? "" : pkg.getName() + "."; - return tryEnterPackageState(pkgPrefix + pqpnPrefixedExpression); - } - - private void enterPackageState(Package pkg) throws IllegalSyntaxException { - if (!iterator.hasNext()) { // rejected state - enterIllegalState("Unexpected end of input: expects a class name"); - } - String token = iterator.next(); - - if (tryEnterSameClassState(pkg, token)) { - return; - } - - if (tryEnterOuterClassState(pkg, token)) { - return; - } - - if (tryEnterClassState(pkg, token)) { - return; - } - - enterIllegalState(String.format("Unrecognized symbol '%s'", token)); - } - - private void enterIllegalState(String msg) throws IllegalSyntaxException { - throw new IllegalSyntaxException(msg); - } - - private void enterIllegalState(Throwable throwable) throws IllegalSyntaxException { - throw new IllegalSyntaxException(throwable); - } - - private void doOutwardsCasting(Class targetClass) throws IllegalSyntaxException { - // TODO: add this reference - System.out.printf("this reference on %s\n", targetClass.getName()); - - // need to do outwards casting first - if (!targetClass.equals(caller)) { - Class c = caller; - while (!targetClass.equals(c.getEnclosingClass())) { - Class enclosing = c.getEnclosingClass(); - if (enclosing == null) { - enterIllegalState(String.format("%s is not an enclosing class of %s", targetClass.getName(), caller.getName())); - } - // TODO: add outwards casting from c to enclosing - System.out.printf("outwards casting from %s to %s\n", c.getName(), enclosing.getName()); - c = enclosing; - } - } - } - } } From a803e7e770051cb0ebaf7fe3ec27b20d379f3797 Mon Sep 17 00:00:00 2001 From: kxu Date: Thu, 12 Dec 2019 14:31:25 -0500 Subject: [PATCH 10/27] remove unnecessary null checks --- .../jfrnext/impl/JFRNextMethodAdvisor.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java index 867af425e..09a2c1884 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java @@ -41,10 +41,10 @@ import org.openjdk.jmc.agent.Parameter; import org.openjdk.jmc.agent.Watch; import org.openjdk.jmc.agent.jfr.JFRTransformDescriptor; +import org.openjdk.jmc.agent.util.TypeUtils; import org.openjdk.jmc.agent.util.expression.FieldReference; import org.openjdk.jmc.agent.util.expression.IllegalSyntaxException; import org.openjdk.jmc.agent.util.expression.ReferenceChain; -import org.openjdk.jmc.agent.util.TypeUtils; import java.lang.reflect.Modifier; import java.util.ArrayList; @@ -162,20 +162,20 @@ private void loadWatch(Watch watch, ReferenceChain refChain) { // Assumes the reference chain is normalized already. See ReferenceChain.normalize() List refs = refChain.getReferences(); for (int i = 0; i < refs.size(); i++) { - if (i != 0) { - // null check - mv.visitInsn(DUP); - mv.visitJumpInsn(IFNULL, nullCase); - } - FieldReference ref = refs.get(i); if (ref instanceof FieldReference.ThisReference) { - mv.visitVarInsn(ALOAD, 0); // load "this" - continue; + mv.visitVarInsn(ALOAD, 0); // load "this" + } else { + mv.visitFieldInsn(Modifier.isStatic(ref.getModifiers()) ? GETSTATIC : GETFIELD, + ref.getMemberingType().getInternalName(), + ref.getName(), ref.getType().getDescriptor()); } - int opcode = Modifier.isStatic(ref.getModifiers()) ? GETSTATIC : GETFIELD; - mv.visitFieldInsn(opcode, ref.getMemberingType().getInternalName(), ref.getName(), ref.getType().getDescriptor()); + // null check for field references + if (!(ref instanceof FieldReference.ThisReference) && !(ref instanceof FieldReference.QualifiedThisReference) && i != refs.size() - 1) { + mv.visitInsn(DUP); + mv.visitJumpInsn(IFNULL, nullCase); + } } // loaded value, jump to writing attribute mv.visitJumpInsn(GOTO, continueCase); From 040a42d8e18480ef5ec6029e6f29b612cbc6cbc8 Mon Sep 17 00:00:00 2001 From: kxu Date: Fri, 13 Dec 2019 15:07:58 -0500 Subject: [PATCH 11/27] add final modifiers --- .../openjdk/jmc/agent/util/expression/ExpressionResolver.java | 2 +- .../org/openjdk/jmc/agent/util/expression/FieldReference.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java index 5fcacc2ca..f561f8756 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java @@ -17,7 +17,7 @@ TypeName -> TypeIdentifier - | PackageOrTypeName Dot TypeIdentifier + | PackageOrTypeName . TypeIdentifier PackageOrTypeName -> identifier diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/FieldReference.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/FieldReference.java index 65126a7d7..2ad1670a6 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/FieldReference.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/FieldReference.java @@ -83,8 +83,8 @@ public String toString() { // "Qualified.this" public static class QualifiedThisReference extends FieldReference { - private Class enclosingClass; - private int index; + private final Class enclosingClass; + private final int index; public QualifiedThisReference(Class innerClass, Class enclosingClass, int index) { super(innerClass, null); From db3d6266b076f69b7114d1120665cbb1c477f731 Mon Sep 17 00:00:00 2001 From: kxu Date: Mon, 16 Dec 2019 12:39:06 -0500 Subject: [PATCH 12/27] WIP: refactor --- .../jfrnext/impl/JFRNextMethodAdvisor.java | 51 +++-- .../util/expression/ExpressionResolver.java | 97 ++++----- .../agent/util/expression/FieldReference.java | 115 ---------- .../expression/IReferenceChainElement.java | 204 ++++++++++++++++++ .../agent/util/expression/ReferenceChain.java | 55 +++-- 5 files changed, 315 insertions(+), 207 deletions(-) delete mode 100644 core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/FieldReference.java create mode 100644 core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/IReferenceChainElement.java diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java index 09a2c1884..632a8eec9 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java @@ -42,7 +42,7 @@ import org.openjdk.jmc.agent.Watch; import org.openjdk.jmc.agent.jfr.JFRTransformDescriptor; import org.openjdk.jmc.agent.util.TypeUtils; -import org.openjdk.jmc.agent.util.expression.FieldReference; +import org.openjdk.jmc.agent.util.expression.IReferenceChainElement; import org.openjdk.jmc.agent.util.expression.IllegalSyntaxException; import org.openjdk.jmc.agent.util.expression.ReferenceChain; @@ -136,7 +136,7 @@ private void createEvent() { } if (transformDescriptor.isAllowedFieldType(refChain.getType())) { mv.visitInsn(DUP); - loadWatch(watch, refChain); + loadWatch(refChain); writeAttribute(watch, refChain.getType()); } } @@ -146,7 +146,7 @@ private void createEvent() { mv.visitVarInsn(ASTORE, eventLocal); } - private void loadWatch(Watch watch, ReferenceChain refChain) { + private void loadWatch(ReferenceChain refChain) { Type type = refChain.getType(); boolean isStatic = Modifier.isStatic(getAccess()); Label nullCase = new Label(); @@ -160,24 +160,47 @@ private void loadWatch(Watch watch, ReferenceChain refChain) { } // Assumes the reference chain is normalized already. See ReferenceChain.normalize() - List refs = refChain.getReferences(); + List refs = refChain.getReferences(); for (int i = 0; i < refs.size(); i++) { - FieldReference ref = refs.get(i); - if (ref instanceof FieldReference.ThisReference) { + IReferenceChainElement ref = refs.get(i); + + if (ref instanceof IReferenceChainElement.ThisReference) { mv.visitVarInsn(ALOAD, 0); // load "this" - } else { - mv.visitFieldInsn(Modifier.isStatic(ref.getModifiers()) ? GETSTATIC : GETFIELD, + continue; + } + + if (ref instanceof IReferenceChainElement.FieldReference) { + mv.visitFieldInsn(ref.isStatic() ? GETSTATIC : GETFIELD, ref.getMemberingType().getInternalName(), - ref.getName(), ref.getType().getDescriptor()); + ((IReferenceChainElement.FieldReference) ref).getName(), ref.getReferencedType().getDescriptor()); + + // null check for field references + if (i < refs.size() - 1) { // Skip null check for final reference. Null is acceptable here + mv.visitInsn(DUP); + mv.visitJumpInsn(IFNULL, nullCase); + } + + continue; } - // null check for field references - if (!(ref instanceof FieldReference.ThisReference) && !(ref instanceof FieldReference.QualifiedThisReference) && i != refs.size() - 1) { - mv.visitInsn(DUP); - mv.visitJumpInsn(IFNULL, nullCase); + if (ref instanceof IReferenceChainElement.QualifiedThisReference) { + int suffix = ((IReferenceChainElement.QualifiedThisReference) ref).getDepth(); + Class c = ref.getMemberingClass(); + while (!ref.getReferencedClass().equals(c)) { + mv.visitFieldInsn(GETFIELD, + Type.getType(c).getInternalName(), + "this$" + (suffix--), + Type.getType(c.getEnclosingClass()).getDescriptor()); + c = c.getEnclosingClass(); + } + + continue; } + + throw new UnsupportedOperationException("Unsupported reference chain element type"); } - // loaded value, jump to writing attribute + + // loaded a value, jump to writing attribute mv.visitJumpInsn(GOTO, continueCase); // null reference on path, load zero value diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java index f561f8756..7c6bb5260 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java @@ -10,7 +10,7 @@ import java.util.List; /* - Expression + Expression // a subset of Java primary expression (without array accesses) -> this | TypeName . this | FieldAccess @@ -45,7 +45,7 @@ public class ExpressionResolver { private List tokens = null; private Iterator iterator = null; - private List referenceChain = null; + private ReferenceChain referenceChain = null; public ExpressionResolver(Class caller, String expression, boolean fromStaticContext) { this.caller = caller; @@ -53,14 +53,15 @@ public ExpressionResolver(Class caller, String expression, boolean fromStatic this.fromStaticContext = fromStaticContext; } - public ReferenceChain solve() throws IllegalSyntaxException { - tokens = new LinkedList<>(Arrays.asList(expression.split("\\."))); - iterator = tokens.iterator(); - referenceChain = new LinkedList<>(); + public static ReferenceChain solve(Class caller, String expression, boolean fromStaticContext) throws IllegalSyntaxException { + ExpressionResolver resolver = new ExpressionResolver(caller, expression, fromStaticContext); + resolver.tokens = new LinkedList<>(Arrays.asList(expression.split("\\."))); + resolver.iterator = resolver.tokens.iterator(); + resolver.referenceChain = new ReferenceChain(caller); + + resolver.enterStartState(); - enterStartState(); - - return new ReferenceChain(caller, referenceChain, false); + return resolver.referenceChain; } private void enterStartState() throws IllegalSyntaxException { @@ -128,22 +129,28 @@ private void enterStartState() throws IllegalSyntaxException { enterIllegalState(String.format("Unrecognized symbol '%s'", token)); } - private boolean tryEnterThisState(Class targetClass, String thisLiteral) throws IllegalSyntaxException { + private boolean tryEnterThisState(Class enclosingClass, String thisLiteral) throws IllegalSyntaxException { if (!"this".equals(thisLiteral)) { return false; } - enterThisState(targetClass); + enterThisState(enclosingClass); return true; } // "^this" or "Qualified.this" expression (casting to an enclosing class) - private void enterThisState(Class targetClass) throws IllegalSyntaxException { + private void enterThisState(Class enclosingClass) throws IllegalSyntaxException { if (fromStaticContext) { - enterIllegalState(String.format("'%s.this' cannot be referenced from a static context", targetClass.getName())); + enterIllegalState(String.format("'%s.this' cannot be referenced from a static context", enclosingClass.getName())); } - doOutwardsCasting(targetClass); + // cast to outer class instance only when accessing non-static fields + referenceChain.append(new IReferenceChainElement.ThisReference(caller)); + try { + referenceChain.append(new IReferenceChainElement.QualifiedThisReference(caller, enclosingClass)); + } catch (IllegalArgumentException e) { + enterIllegalState(e); + } if (!iterator.hasNext()) { // accepted state return; @@ -151,7 +158,7 @@ private void enterThisState(Class targetClass) throws IllegalSyntaxException String token = iterator.next(); // this.prop - if (tryEnterFieldReferenceState(targetClass, token, false)) { + if (tryEnterFieldReferenceState(enclosingClass, token, false)) { return; } @@ -168,16 +175,22 @@ private boolean tryEnterSuperState(Class targetClass, String superLiteral) th } // "^super" or "Qualified.super" expression - private void enterSuperState(Class targetClass) throws IllegalSyntaxException { + private void enterSuperState(Class enclosingClass) throws IllegalSyntaxException { if (fromStaticContext) { - enterIllegalState(String.format("'%s.super' cannot be referenced from a static context", targetClass.getName())); + enterIllegalState(String.format("'%s.super' cannot be referenced from a static context", enclosingClass.getName())); } - doOutwardsCasting(targetClass); + // cast to outer class instance only when accessing non-static fields + referenceChain.append(new IReferenceChainElement.ThisReference(caller)); + try { + referenceChain.append(new IReferenceChainElement.QualifiedThisReference(caller, enclosingClass)); + } catch (IllegalArgumentException e) { + enterIllegalState(e); + } - Class superClass = targetClass.getSuperclass(); + Class superClass = enclosingClass.getSuperclass(); if (superClass == null) { // almost would never happen, java.lang classes are not transformable - enterIllegalState(String.format("'%s' has no super class", targetClass.getName())); + enterIllegalState(String.format("'%s' has no super class", enclosingClass.getName())); } if (!iterator.hasNext()) { // rejected state @@ -221,7 +234,7 @@ private void enterFieldReferenceState(Class memberingClass, Field field, bool enterIllegalState(String.format("'%s' has %s access in '%s'", field.getName(), access, field.getDeclaringClass().getName())); } - referenceChain.add(new FieldReference(memberingClass, field)); + referenceChain.append(new IReferenceChainElement.FieldReference(memberingClass, field)); if (!iterator.hasNext()) { // accepted state return; @@ -269,10 +282,16 @@ private void enterNestedFieldReferenceState(Class enclosingClass, Field field } if (!Modifier.isStatic(field.getModifiers())) { - doOutwardsCasting(enclosingClass); // cast to outer class instance only when accessing non-static fields + // cast to outer class instance only when accessing non-static fields + referenceChain.append(new IReferenceChainElement.ThisReference(caller)); + try { + referenceChain.append(new IReferenceChainElement.QualifiedThisReference(caller, enclosingClass)); + } catch (IllegalArgumentException e) { + enterIllegalState(e); + } } - referenceChain.add(new FieldReference(enclosingClass, field)); + referenceChain.append(new IReferenceChainElement.FieldReference(enclosingClass, field)); if (!iterator.hasNext()) { // accepted state return; @@ -658,36 +677,4 @@ private void enterIllegalState(String msg) throws IllegalSyntaxException { private void enterIllegalState(Throwable throwable) throws IllegalSyntaxException { throw new IllegalSyntaxException(throwable); } - - private void doOutwardsCasting(Class targetClass) throws IllegalSyntaxException { - referenceChain.add(new FieldReference.ThisReference(caller)); - - // need to do outwards casting first - if (!targetClass.equals(caller)) { - Class c = caller; - int i = 0; // depth of inner class nesting, used for this$i reference to enclosing classes - while (!targetClass.equals(c.getEnclosingClass())) { - Class enclosing = c.getEnclosingClass(); - if (enclosing == null) { - enterIllegalState(String.format("%s is not an enclosing class of %s", targetClass.getName(), caller.getName())); - } - - i++; - c = enclosing; - } - - - c = caller; - while (!targetClass.equals(c.getEnclosingClass())) { - Class enclosing = c.getEnclosingClass(); - if (enclosing == null) { - enterIllegalState(String.format("%s is not an enclosing class of %s", targetClass.getName(), caller.getName())); - } - referenceChain.add(new FieldReference.QualifiedThisReference(c, enclosing, i--)); - c = enclosing; - } - - referenceChain.add(new FieldReference.QualifiedThisReference(c, c.getEnclosingClass(), i--)); - } - } } \ No newline at end of file diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/FieldReference.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/FieldReference.java deleted file mode 100644 index 2ad1670a6..000000000 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/FieldReference.java +++ /dev/null @@ -1,115 +0,0 @@ -package org.openjdk.jmc.agent.util.expression; - -import java.lang.reflect.Field; - -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; -import org.openjdk.jmc.agent.util.TypeUtils; - -public class FieldReference { - private final Class memberingClass; - private final Field field; - - public FieldReference(Class memberingClass, Field field) { - this.memberingClass = memberingClass; - this.field = field; - } - - public Class getMemberingClass() { - return memberingClass; - } - - public Type getMemberingType() { - return Type.getType(getMemberingClass()); - } - - public Field getField() { - return field; - } - - public String getName() { - return getField().getName(); - } - - public Type getType() { - return Type.getType(getReferencedClass()); - } - - public Class getReferencedClass() { - return getField().getType(); - } - - public int getModifiers() { - return getField().getModifiers(); - } - - @Override - public String toString() { - return String.format("FieldRef %s.%s:%s", TypeUtils.getInternalName(getMemberingClass().getName()), getName(), getType().getClassName()); - } - - // for "this" expression and "Qualified.this" expression - public static class ThisReference extends FieldReference { - - public ThisReference(Class enclosingClass) { // the caller class is technically an enclosing class of itself - super(enclosingClass, null); - } - - @Override - public String getName() { - return "this"; - } - - @Override - public Type getType() { - return getMemberingType(); - } - - @Override - public Class getReferencedClass() { - return getMemberingClass(); - } - - @Override - public int getModifiers() { - return 0; - } - - @Override - public String toString() { - return getName(); - } - } - - // "Qualified.this" - public static class QualifiedThisReference extends FieldReference { - private final Class enclosingClass; - private final int index; - - public QualifiedThisReference(Class innerClass, Class enclosingClass, int index) { - super(innerClass, null); - this.enclosingClass = enclosingClass; - this.index = index; - } - - @Override - public String getName() { - return "this$" + index; - } - - @Override - public Type getType() { - return Type.getType(enclosingClass); - } - - @Override - public int getModifiers() { - return Opcodes.ACC_PRIVATE | Opcodes.ACC_SYNTHETIC; - } - - @Override - public String toString() { - return getName(); - } - } -} diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/IReferenceChainElement.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/IReferenceChainElement.java new file mode 100644 index 000000000..45907f77c --- /dev/null +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/IReferenceChainElement.java @@ -0,0 +1,204 @@ +package org.openjdk.jmc.agent.util.expression; + +import org.objectweb.asm.Type; +import org.openjdk.jmc.agent.util.AccessUtils; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Objects; + + +public interface IReferenceChainElement { + // class/interface which the reference is from + Class getMemberingClass(); + + // class/interface which the reference is to + Class getReferencedClass(); + + // the type of the class/interface which the reference is from + Type getMemberingType(); + + // the type of the class/interface which the reference is to + Type getReferencedType(); + + // if the reference is allow from a caller + boolean isAccessibleFrom(Class caller); + + // if this reference is static + boolean isStatic(); + + class FieldReference implements IReferenceChainElement { + private final Class memberingClass; + private final Field field; + + public FieldReference(Class memberingClass, Field field) { + this.memberingClass = memberingClass; + this.field = field; + + try { + AccessUtils.getFieldOnHierarchy(memberingClass, field.getName()); + } catch (NoSuchFieldException e) { + throw new IllegalArgumentException(String.format("'%s' is not a field of '%s'", field.getName(), memberingClass.getName())); + } + } + + @Override + public Class getMemberingClass() { + return memberingClass; + } + + @Override + public Class getReferencedClass() { + return field.getType(); + } + + @Override + public Type getMemberingType() { + return Type.getType(getMemberingClass()); + } + + @Override + public Type getReferencedType() { + return Type.getType(getReferencedClass()); + } + + @Override + public boolean isAccessibleFrom(Class caller) { + return AccessUtils.isAccessible(memberingClass, field, caller); + } + + @Override + public boolean isStatic() { + return Modifier.isStatic(field.getModifiers()); + } + + @Override + public String toString() { + return String.format("%s.%s:%s", getMemberingClass().getName(), getName(), getReferencedClass().getName()); + } + + public Field getField() { + return field; + } + + public String getName() { + return getField().getName(); + } + } + + class ThisReference implements IReferenceChainElement { + private final Class clazz; + + public ThisReference(Class clazz) { + this.clazz = clazz; + + Objects.requireNonNull(clazz, "Class is not nullable"); + } + + @Override + public Class getMemberingClass() { + return clazz; + } + + @Override + public Class getReferencedClass() { + return clazz; + } + + @Override + public Type getMemberingType() { + return Type.getType(getMemberingClass()); + } + + @Override + public Type getReferencedType() { + return Type.getType(getReferencedClass()); + } + + @Override + public boolean isAccessibleFrom(Class caller) { + return clazz.equals(caller); + } + + @Override + public boolean isStatic() { + return false; + } + + @Override + public String toString() { + return "this"; + } + } + + class QualifiedThisReference implements IReferenceChainElement { + private final Class innerClass; + private final Class enclosingClass; + private final int depth; + + public QualifiedThisReference(Class innerClass, Class enclosingClass) { + this.innerClass = innerClass; + this.enclosingClass = enclosingClass; + + Class c = innerClass; + int d = 0; // depth of inner class nesting, used for this$i reference to enclosing classes + while (!enclosingClass.equals(c.getEnclosingClass())) { + Class enclosing = c.getEnclosingClass(); + if (enclosing == null) { + throw new IllegalArgumentException(String.format("%s is not an enclosing class of %s", enclosingClass.getName(), innerClass.getName())); + } + + d++; + c = enclosing; + } + + this.depth = d; + } + + @Override + public Class getMemberingClass() { + return innerClass; + } + + @Override + public Class getReferencedClass() { + return enclosingClass; + } + + @Override + public Type getMemberingType() { + return Type.getType(getMemberingClass()); + } + + @Override + public Type getReferencedType() { + return Type.getType(getReferencedClass()); + } + + @Override + public boolean isAccessibleFrom(Class caller) { + Class c = caller; + while (c != null) { + if (c.equals(innerClass)) { + return true; + } + c = c.getEnclosingClass(); + } + return false; + } + + @Override + public boolean isStatic() { + return false; + } + + @Override + public String toString() { + return String.format("%s.this", getReferencedClass().getName()); + } + + public int getDepth() { + return depth; + } + } +} diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java index ca9b964f0..e91d3881d 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java @@ -8,56 +8,65 @@ public class ReferenceChain { private final Class callerClass; - private final List references; - private final boolean normalized; + private final List references; - public ReferenceChain(Class callerClass, List references, boolean normalized) { + public ReferenceChain(Class callerClass) { this.callerClass = callerClass; - this.references = references; - this.normalized = normalized; + this.references = new LinkedList<>(); } public Class getCallerClass() { return callerClass; } - public List getReferences() { + public List getReferences() { return references; } - public boolean isNormalized() { - return normalized; - } - public ReferenceChain normalize() { - List oldRefs = getReferences(); - List newRefs = new LinkedList<>(); + List oldRefs = getReferences(); + List newRefs = new LinkedList<>(); - // Take shortcut on static reference - for (FieldReference ref : oldRefs) { - if (Modifier.isStatic(ref.getModifiers())) { + // Take shortcuts on static references + for (IReferenceChainElement ref : oldRefs) { + if (ref.isStatic()) { newRefs.clear(); } newRefs.add(ref); } - // Don't reduce static final reference to constant. The value could be different if loaded via different class loaders. + // Don't reduce static final references to constants. The value could be different, or even stochastic, if + // loaded via different class loaders. (eg. logic in static initializers) - // prepend "this" if starts with non-static field reference - if (!newRefs.isEmpty() - && !Modifier.isStatic(newRefs.get(0).getModifiers()) - && !(newRefs.get(0) instanceof FieldReference.ThisReference)) { - newRefs.add(0, new FieldReference.ThisReference(callerClass)); + // prepend "this" if starts with non-static field reference + if (newRefs.isEmpty()) { + newRefs.add(0, new IReferenceChainElement.ThisReference(callerClass)); // implicit "this" + } else if (newRefs.get(0) instanceof IReferenceChainElement.FieldReference && !newRefs.get(0).isStatic()) { + newRefs.add(0, new IReferenceChainElement.ThisReference(callerClass)); // prop => this.prop } - return new ReferenceChain(callerClass, newRefs, true); + ReferenceChain ret = new ReferenceChain(callerClass); + ret.references.addAll(newRefs); + return ret; } public Type getType() { if (references.isEmpty()) { return Type.getType(callerClass); } - return references.get(references.size() - 1).getType(); + return references.get(references.size() - 1).getReferencedType(); + } + + public void append(IReferenceChainElement ref) { + references.add(ref); + } + + public boolean isStatic() { + if (references.isEmpty()) { + return false; + } + + return references.get(0).isStatic(); } } From 92b92502fa9106e2aac0fe4fc977ef43d3757dbe Mon Sep 17 00:00:00 2001 From: kxu Date: Mon, 16 Dec 2019 14:19:38 -0500 Subject: [PATCH 13/27] add a QualifiedThisReference only when making qualified .this or .super --- .../java/org/openjdk/jmc/agent/Watch.java | 10 ++++---- .../util/expression/ExpressionResolver.java | 23 +++++++++++-------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Watch.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Watch.java index aaa0cdfde..6e2c018b8 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Watch.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Watch.java @@ -6,7 +6,7 @@ import org.openjdk.jmc.agent.util.TypeUtils; public class Watch implements IAttribute { - + private final String name; private final String expression; private final String fieldName; @@ -23,10 +23,8 @@ public Watch(String name, String expression, String description, String contentT this.relationKey = relationKey; this.converterClassName = converterClassName; this.fieldName = "field" + TypeUtils.deriveIdentifierPart(name); - - // TODO: validate expression } - + @Override public String getName() { return this.name; @@ -56,8 +54,8 @@ public String getRelationKey() { public String getConverterClassName() { return this.converterClassName; } - + public ReferenceChain resolveReferenceChain(Class callerClass, boolean fromStaticContext) throws IllegalSyntaxException { - return new ExpressionResolver(callerClass, expression, fromStaticContext).solve(); + return ExpressionResolver.solve(callerClass, expression, fromStaticContext); } } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java index 7c6bb5260..b501fb130 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java @@ -47,7 +47,7 @@ public class ExpressionResolver { private Iterator iterator = null; private ReferenceChain referenceChain = null; - public ExpressionResolver(Class caller, String expression, boolean fromStaticContext) { + private ExpressionResolver(Class caller, String expression, boolean fromStaticContext) { this.caller = caller; this.expression = expression; this.fromStaticContext = fromStaticContext; @@ -146,10 +146,12 @@ private void enterThisState(Class enclosingClass) throws IllegalSyntaxExcepti // cast to outer class instance only when accessing non-static fields referenceChain.append(new IReferenceChainElement.ThisReference(caller)); - try { - referenceChain.append(new IReferenceChainElement.QualifiedThisReference(caller, enclosingClass)); - } catch (IllegalArgumentException e) { - enterIllegalState(e); + if (!caller.equals(enclosingClass)) { + try { + referenceChain.append(new IReferenceChainElement.QualifiedThisReference(caller, enclosingClass)); + } catch (IllegalArgumentException e) { + enterIllegalState(e); + } } if (!iterator.hasNext()) { // accepted state @@ -180,12 +182,13 @@ private void enterSuperState(Class enclosingClass) throws IllegalSyntaxExcept enterIllegalState(String.format("'%s.super' cannot be referenced from a static context", enclosingClass.getName())); } - // cast to outer class instance only when accessing non-static fields referenceChain.append(new IReferenceChainElement.ThisReference(caller)); - try { - referenceChain.append(new IReferenceChainElement.QualifiedThisReference(caller, enclosingClass)); - } catch (IllegalArgumentException e) { - enterIllegalState(e); + if (!caller.equals(enclosingClass)) { + try { + referenceChain.append(new IReferenceChainElement.QualifiedThisReference(caller, enclosingClass)); + } catch (IllegalArgumentException e) { + enterIllegalState(e); + } } Class superClass = enclosingClass.getSuperclass(); From e93a31e25bcbf524d76beae5cd7fdfce595e1e0c Mon Sep 17 00:00:00 2001 From: kxu Date: Mon, 16 Dec 2019 16:17:53 -0500 Subject: [PATCH 14/27] refactor error handling. cache resolved reference chain --- .../java/org/openjdk/jmc/agent/Watch.java | 18 ++++++++++++--- .../impl/JFRNextEventClassGenerator.java | 2 +- .../jfrnext/impl/JFRNextMethodAdvisor.java | 22 +++++++++++-------- .../util/expression/ExpressionResolver.java | 18 ++++----------- .../agent/util/expression/ReferenceChain.java | 2 +- 5 files changed, 34 insertions(+), 28 deletions(-) diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Watch.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Watch.java index 6e2c018b8..7ddc8ab22 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Watch.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Watch.java @@ -14,7 +14,10 @@ public class Watch implements IAttribute { private final String contentType; private final String relationKey; private final String converterClassName; - + + private Class resolvingCaller; + private ReferenceChain referenceChain; + public Watch(String name, String expression, String description, String contentType, String relationKey, String converterClassName) { this.name = name; this.expression = expression; @@ -30,6 +33,10 @@ public String getName() { return this.name; } + public String getExpression() { + return expression; + } + @Override public String getFieldName() { return this.fieldName; @@ -55,7 +62,12 @@ public String getConverterClassName() { return this.converterClassName; } - public ReferenceChain resolveReferenceChain(Class callerClass, boolean fromStaticContext) throws IllegalSyntaxException { - return ExpressionResolver.solve(callerClass, expression, fromStaticContext); + public ReferenceChain resolveReferenceChain(Class callerClass) throws IllegalSyntaxException { + if (!callerClass.equals(resolvingCaller)) { + resolvingCaller = callerClass; + referenceChain = ExpressionResolver.solve(callerClass, expression); + } + + return referenceChain; } } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextEventClassGenerator.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextEventClassGenerator.java index efc766c9e..c1a427982 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextEventClassGenerator.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextEventClassGenerator.java @@ -78,7 +78,7 @@ private static void generateAttributeFields(ClassWriter cw, JFRTransformDescript } for (Watch watch : td.getWatches()) { - createField(cw, td, watch, watch.resolveReferenceChain(classBeingRedefined, false).getType()); + createField(cw, td, watch, watch.resolveReferenceChain(classBeingRedefined).getType()); } } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java index 632a8eec9..c60bbcd9d 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java @@ -108,10 +108,14 @@ public void visitEnd() { @Override protected void onMethodEnter() { - createEvent(); + try { + createEvent(); + } catch (IllegalSyntaxException e) { + throw new RuntimeException(e); + } } - private void createEvent() { + private void createEvent() throws IllegalSyntaxException { mv.visitTypeInsn(NEW, transformDescriptor.getEventClassName()); mv.visitInsn(DUP); mv.visitInsn(DUP); @@ -128,13 +132,13 @@ private void createEvent() { } for (Watch watch : transformDescriptor.getWatches()) { - ReferenceChain refChain; - try { - refChain = watch.resolveReferenceChain(classBeingRedefined, Modifier.isStatic(getAccess())).normalize(); - } catch (IllegalSyntaxException e) { - throw new RuntimeException(e); // TODO: figure out what to do with this error - } - if (transformDescriptor.isAllowedFieldType(refChain.getType())) { + ReferenceChain refChain = watch.resolveReferenceChain(classBeingRedefined).normalize(); + + if (!refChain.isStatic() && Modifier.isStatic(getAccess())) { + throw new IllegalSyntaxException("Illegal non-static reference from a static context: " + watch.getExpression()); + } + + if (transformDescriptor.isAllowedFieldType(refChain.getType())) { mv.visitInsn(DUP); loadWatch(refChain); writeAttribute(watch, refChain.getType()); diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java index b501fb130..be5f88b4b 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java @@ -41,20 +41,18 @@ public class ExpressionResolver { private final Class caller; private final String expression; - private final boolean fromStaticContext; private List tokens = null; private Iterator iterator = null; private ReferenceChain referenceChain = null; - private ExpressionResolver(Class caller, String expression, boolean fromStaticContext) { + private ExpressionResolver(Class caller, String expression) { this.caller = caller; this.expression = expression; - this.fromStaticContext = fromStaticContext; } - public static ReferenceChain solve(Class caller, String expression, boolean fromStaticContext) throws IllegalSyntaxException { - ExpressionResolver resolver = new ExpressionResolver(caller, expression, fromStaticContext); + public static ReferenceChain solve(Class caller, String expression) throws IllegalSyntaxException { + ExpressionResolver resolver = new ExpressionResolver(caller, expression); resolver.tokens = new LinkedList<>(Arrays.asList(expression.split("\\."))); resolver.iterator = resolver.tokens.iterator(); resolver.referenceChain = new ReferenceChain(caller); @@ -82,7 +80,7 @@ private void enterStartState() throws IllegalSyntaxException { } // local/inherited field reference - if (tryEnterFieldReferenceState(caller, token, fromStaticContext)) { + if (tryEnterFieldReferenceState(caller, token, false)) { // assuming accessing from a non-static context return; } @@ -140,10 +138,6 @@ private boolean tryEnterThisState(Class enclosingClass, String thisLiteral) t // "^this" or "Qualified.this" expression (casting to an enclosing class) private void enterThisState(Class enclosingClass) throws IllegalSyntaxException { - if (fromStaticContext) { - enterIllegalState(String.format("'%s.this' cannot be referenced from a static context", enclosingClass.getName())); - } - // cast to outer class instance only when accessing non-static fields referenceChain.append(new IReferenceChainElement.ThisReference(caller)); if (!caller.equals(enclosingClass)) { @@ -178,10 +172,6 @@ private boolean tryEnterSuperState(Class targetClass, String superLiteral) th // "^super" or "Qualified.super" expression private void enterSuperState(Class enclosingClass) throws IllegalSyntaxException { - if (fromStaticContext) { - enterIllegalState(String.format("'%s.super' cannot be referenced from a static context", enclosingClass.getName())); - } - referenceChain.append(new IReferenceChainElement.ThisReference(caller)); if (!caller.equals(enclosingClass)) { try { diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java index e91d3881d..0eeb1d5a3 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java @@ -61,7 +61,7 @@ public Type getType() { public void append(IReferenceChainElement ref) { references.add(ref); } - + public boolean isStatic() { if (references.isEmpty()) { return false; From 1c7b0ac61e9d98e149a98be08f241c0b2d5d19d1 Mon Sep 17 00:00:00 2001 From: kxu Date: Mon, 16 Dec 2019 16:34:05 -0500 Subject: [PATCH 15/27] rename "watch" to "field" --- .../jmc/agent/{Watch.java => Field.java} | 6 +++--- .../openjdk/jmc/agent/TransformDescriptor.java | 4 ++-- .../agent/impl/DefaultTransformRegistry.java | 18 +++++++++--------- .../jmc/agent/jfr/JFRTransformDescriptor.java | 12 ++++++------ .../impl/JFRNextEventClassGenerator.java | 6 +++--- .../jfrnext/impl/JFRNextMethodAdvisor.java | 14 +++++++------- 6 files changed, 30 insertions(+), 30 deletions(-) rename core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/{Watch.java => Field.java} (94%) diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Watch.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Field.java similarity index 94% rename from core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Watch.java rename to core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Field.java index 7ddc8ab22..980a33c26 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Watch.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Field.java @@ -5,7 +5,7 @@ import org.openjdk.jmc.agent.util.expression.ReferenceChain; import org.openjdk.jmc.agent.util.TypeUtils; -public class Watch implements IAttribute { +public class Field implements IAttribute { private final String name; private final String expression; @@ -18,7 +18,7 @@ public class Watch implements IAttribute { private Class resolvingCaller; private ReferenceChain referenceChain; - public Watch(String name, String expression, String description, String contentType, String relationKey, String converterClassName) { + public Field(String name, String expression, String description, String contentType, String relationKey, String converterClassName) { this.name = name; this.expression = expression; this.description = description; @@ -67,7 +67,7 @@ public ReferenceChain resolveReferenceChain(Class callerClass) throws Illegal resolvingCaller = callerClass; referenceChain = ExpressionResolver.solve(callerClass, expression); } - + return referenceChain; } } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/TransformDescriptor.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/TransformDescriptor.java index e925809d3..c61d2870f 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/TransformDescriptor.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/TransformDescriptor.java @@ -105,8 +105,8 @@ protected String getTransformationAttribute(String attribute) { * @return the instantiated {@link TransformDescriptor}. */ public static TransformDescriptor create( - String id, String internalName, Method method, Map values, List parameters, List watches) { - return new JFRTransformDescriptor(id, internalName, method, values, parameters, watches); + String id, String internalName, Method method, Map values, List parameters, List fields) { + return new JFRTransformDescriptor(id, internalName, method, values, parameters, fields); } @Override diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/impl/DefaultTransformRegistry.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/impl/DefaultTransformRegistry.java index d82c8dbe9..74fffeeb5 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/impl/DefaultTransformRegistry.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/impl/DefaultTransformRegistry.java @@ -52,7 +52,7 @@ import org.openjdk.jmc.agent.Parameter; import org.openjdk.jmc.agent.TransformDescriptor; import org.openjdk.jmc.agent.TransformRegistry; -import org.openjdk.jmc.agent.Watch; +import org.openjdk.jmc.agent.Field; import org.openjdk.jmc.agent.jfr.JFRTransformDescriptor; import org.openjdk.jmc.agent.util.TypeUtils; @@ -60,7 +60,7 @@ public class DefaultTransformRegistry implements TransformRegistry { private static final String XML_ATTRIBUTE_NAME_ID = "id"; //$NON-NLS-1$ private static final String XML_ELEMENT_NAME_EVENT = "event"; //$NON-NLS-1$ private static final String XML_ELEMENT_METHOD_NAME = "method"; //$NON-NLS-1$ - private static final String XML_ELEMENT_WATCH_NAME = "watch"; //$NON-NLS-1$ + private static final String XML_ELEMENT_FIELD_NAME = "field"; //$NON-NLS-1$ private static final String XML_ELEMENT_PARAMETER_NAME = "parameter"; //$NON-NLS-1$ // Global override section @@ -152,7 +152,7 @@ private static TransformDescriptor parseTransformData( streamReader.next(); Map values = new HashMap<>(); List parameters = new LinkedList<>(); - List watches = new LinkedList<>(); + List fields = new LinkedList<>(); Method method = null; while (streamReader.hasNext()) { if (streamReader.isStartElement()) { @@ -161,8 +161,8 @@ private static TransformDescriptor parseTransformData( method = parseMethod(streamReader, parameters); continue; } - if (XML_ELEMENT_WATCH_NAME.equals(name)) { - watches.add(parseWatch(streamReader)); + if (XML_ELEMENT_FIELD_NAME.equals(name)) { + fields.add(parseField(streamReader)); continue; } streamReader.next(); @@ -182,7 +182,7 @@ private static TransformDescriptor parseTransformData( streamReader.next(); } transfer(globalDefaults, values); - return TransformDescriptor.create(id, TypeUtils.getInternalName(values.get("class")), method, values, parameters, watches); //$NON-NLS-1$ + return TransformDescriptor.create(id, TypeUtils.getInternalName(values.get("class")), method, values, parameters, fields); //$NON-NLS-1$ } private static void transfer(HashMap globalDefaults, Map values) { @@ -264,7 +264,7 @@ private static Parameter parseParameter(int index, XMLStreamReader streamReader) return new Parameter(index, name, description, contentType, relationKey, converterClassName); } - private static Watch parseWatch(XMLStreamReader streamReader) throws XMLStreamException { + private static Field parseField(XMLStreamReader streamReader) throws XMLStreamException { streamReader.next(); String name = null; String expression = null; @@ -297,13 +297,13 @@ private static Watch parseWatch(XMLStreamReader streamReader) throws XMLStreamEx } } } else if (streamReader.isEndElement()) { - if (XML_ELEMENT_WATCH_NAME.equals(streamReader.getName().getLocalPart())) { + if (XML_ELEMENT_FIELD_NAME.equals(streamReader.getName().getLocalPart())) { break; } } streamReader.next(); } - return new Watch(name, expression, description, contentType, relationKey, converterClassName); + return new Field(name, expression, description, contentType, relationKey, converterClassName); } private static Method parseMethod(XMLStreamReader streamReader, List parameters) diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/JFRTransformDescriptor.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/JFRTransformDescriptor.java index 74976c9be..8545de322 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/JFRTransformDescriptor.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/JFRTransformDescriptor.java @@ -38,10 +38,10 @@ import java.util.logging.Logger; import org.objectweb.asm.Type; +import org.openjdk.jmc.agent.Field; import org.openjdk.jmc.agent.Method; import org.openjdk.jmc.agent.Parameter; import org.openjdk.jmc.agent.TransformDescriptor; -import org.openjdk.jmc.agent.Watch; import org.openjdk.jmc.agent.util.TypeUtils; public class JFRTransformDescriptor extends TransformDescriptor { @@ -61,10 +61,10 @@ public class JFRTransformDescriptor extends TransformDescriptor { private final boolean allowToString; private final boolean allowConverter; private final List parameters; - private final List watches; + private final List fields; public JFRTransformDescriptor(String id, String className, Method method, - Map transformationAttributes, List parameters, List watches) { + Map transformationAttributes, List parameters, List fields) { super(id, className, method, transformationAttributes); classPrefix = initializeClassPrefix(); eventName = initializeEventName(); @@ -76,7 +76,7 @@ public JFRTransformDescriptor(String id, String className, Method method, allowToString = getBoolean(ATTRIBUTE_ALLOW_TO_STRING, false); allowConverter = getBoolean(ATTRIBUTE_ALLOW_CONVERTER, false); this.parameters = parameters; - this.watches = watches; + this.fields = fields; } public String getEventClassName() { @@ -178,8 +178,8 @@ public List getParameters() { return parameters; } - public List getWatches() { - return watches; + public List getFields() { + return fields; } public boolean isAllowedFieldType(Type type) { diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextEventClassGenerator.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextEventClassGenerator.java index c1a427982..279572e24 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextEventClassGenerator.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextEventClassGenerator.java @@ -44,7 +44,7 @@ import org.openjdk.jmc.agent.Agent; import org.openjdk.jmc.agent.IAttribute; import org.openjdk.jmc.agent.Parameter; -import org.openjdk.jmc.agent.Watch; +import org.openjdk.jmc.agent.Field; import org.openjdk.jmc.agent.jfr.JFRTransformDescriptor; import org.openjdk.jmc.agent.util.TypeUtils; import org.openjdk.jmc.agent.util.expression.IllegalSyntaxException; @@ -77,8 +77,8 @@ private static void generateAttributeFields(ClassWriter cw, JFRTransformDescript } } - for (Watch watch : td.getWatches()) { - createField(cw, td, watch, watch.resolveReferenceChain(classBeingRedefined).getType()); + for (Field field : td.getFields()) { + createField(cw, td, field, field.resolveReferenceChain(classBeingRedefined).getType()); } } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java index c60bbcd9d..b40e9bf60 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java @@ -37,9 +37,9 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.AdviceAdapter; +import org.openjdk.jmc.agent.Field; import org.openjdk.jmc.agent.IAttribute; import org.openjdk.jmc.agent.Parameter; -import org.openjdk.jmc.agent.Watch; import org.openjdk.jmc.agent.jfr.JFRTransformDescriptor; import org.openjdk.jmc.agent.util.TypeUtils; import org.openjdk.jmc.agent.util.expression.IReferenceChainElement; @@ -131,17 +131,17 @@ private void createEvent() throws IllegalSyntaxException { } } - for (Watch watch : transformDescriptor.getWatches()) { - ReferenceChain refChain = watch.resolveReferenceChain(classBeingRedefined).normalize(); + for (Field field : transformDescriptor.getFields()) { + ReferenceChain refChain = field.resolveReferenceChain(classBeingRedefined).normalize(); if (!refChain.isStatic() && Modifier.isStatic(getAccess())) { - throw new IllegalSyntaxException("Illegal non-static reference from a static context: " + watch.getExpression()); + throw new IllegalSyntaxException("Illegal non-static reference from a static context: " + field.getExpression()); } if (transformDescriptor.isAllowedFieldType(refChain.getType())) { mv.visitInsn(DUP); - loadWatch(refChain); - writeAttribute(watch, refChain.getType()); + loadField(refChain); + writeAttribute(field, refChain.getType()); } } @@ -150,7 +150,7 @@ private void createEvent() throws IllegalSyntaxException { mv.visitVarInsn(ASTORE, eventLocal); } - private void loadWatch(ReferenceChain refChain) { + private void loadField(ReferenceChain refChain) { Type type = refChain.getType(); boolean isStatic = Modifier.isStatic(getAccess()); Label nullCase = new Label(); From 1019ca0db0f2294613dc295f8307b418cb5e0dba Mon Sep 17 00:00:00 2001 From: kxu Date: Mon, 16 Dec 2019 17:28:30 -0500 Subject: [PATCH 16/27] change indentations to using tabs. add license headers --- .../agent/{IAttribute.java => Attribute.java} | 20 +- .../java/org/openjdk/jmc/agent/Field.java | 139 +- .../java/org/openjdk/jmc/agent/Parameter.java | 2 +- .../org/openjdk/jmc/agent/Transformer.java | 3 +- .../impl/JFRNextEventClassGenerator.java | 4 +- .../jfrnext/impl/JFRNextMethodAdvisor.java | 44 +- .../openjdk/jmc/agent/util/AccessUtils.java | 478 +++--- .../jmc/agent/util/InspectionClassLoader.java | 126 +- .../org/openjdk/jmc/agent/util/TypeUtils.java | 105 +- .../util/expression/ExpressionResolver.java | 1365 +++++++++-------- .../expression/IReferenceChainElement.java | 204 --- .../expression/IllegalSyntaxException.java | 56 +- .../agent/util/expression/ReferenceChain.java | 157 +- .../expression/ReferenceChainElement.java | 238 +++ 14 files changed, 1573 insertions(+), 1368 deletions(-) rename core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/{IAttribute.java => Attribute.java} (90%) delete mode 100644 core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/IReferenceChainElement.java create mode 100644 core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChainElement.java diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/IAttribute.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Attribute.java similarity index 90% rename from core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/IAttribute.java rename to core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Attribute.java index fac316408..8bf3083c5 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/IAttribute.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Attribute.java @@ -30,14 +30,18 @@ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - package org.openjdk.jmc.agent; -public interface IAttribute { - String getName(); - String getFieldName(); - String getDescription(); - String getContentType(); - String getRelationKey(); - String getConverterClassName(); +public interface Attribute { + String getName(); + + String getFieldName(); + + String getDescription(); + + String getContentType(); + + String getRelationKey(); + + String getConverterClassName(); } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Field.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Field.java index 980a33c26..44ae24188 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Field.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Field.java @@ -1,3 +1,35 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at http://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package org.openjdk.jmc.agent; import org.openjdk.jmc.agent.util.expression.ExpressionResolver; @@ -5,69 +37,70 @@ import org.openjdk.jmc.agent.util.expression.ReferenceChain; import org.openjdk.jmc.agent.util.TypeUtils; -public class Field implements IAttribute { +public class Field implements Attribute { - private final String name; - private final String expression; - private final String fieldName; - private final String description; - private final String contentType; - private final String relationKey; - private final String converterClassName; + private final String name; + private final String expression; + private final String fieldName; + private final String description; + private final String contentType; + private final String relationKey; + private final String converterClassName; - private Class resolvingCaller; - private ReferenceChain referenceChain; + private Class resolvingCaller; + private ReferenceChain referenceChain; - public Field(String name, String expression, String description, String contentType, String relationKey, String converterClassName) { - this.name = name; - this.expression = expression; - this.description = description; - this.contentType = contentType; - this.relationKey = relationKey; - this.converterClassName = converterClassName; - this.fieldName = "field" + TypeUtils.deriveIdentifierPart(name); - } + public Field(String name, String expression, String description, String contentType, String relationKey, + String converterClassName) { + this.name = name; + this.expression = expression; + this.description = description; + this.contentType = contentType; + this.relationKey = relationKey; + this.converterClassName = converterClassName; + this.fieldName = "field" + TypeUtils.deriveIdentifierPart(name); + } - @Override - public String getName() { - return this.name; - } + @Override + public String getName() { + return this.name; + } - public String getExpression() { - return expression; - } + public String getExpression() { + return expression; + } - @Override - public String getFieldName() { - return this.fieldName; - } + @Override + public String getFieldName() { + return this.fieldName; + } - @Override - public String getDescription() { - return this.description; - } + @Override + public String getDescription() { + return this.description; + } - @Override - public String getContentType() { - return this.contentType; - } + @Override + public String getContentType() { + return this.contentType; + } - @Override - public String getRelationKey() { - return this.relationKey; - } + @Override + public String getRelationKey() { + return this.relationKey; + } - @Override - public String getConverterClassName() { - return this.converterClassName; - } + @Override + public String getConverterClassName() { + return this.converterClassName; + } - public ReferenceChain resolveReferenceChain(Class callerClass) throws IllegalSyntaxException { - if (!callerClass.equals(resolvingCaller)) { - resolvingCaller = callerClass; - referenceChain = ExpressionResolver.solve(callerClass, expression); - } + public ReferenceChain resolveReferenceChain(Class callerClass) throws IllegalSyntaxException { + if (!callerClass.equals(resolvingCaller)) { + resolvingCaller = callerClass; + referenceChain = ExpressionResolver.solve(callerClass, expression); + } - return referenceChain; - } + return referenceChain; + } } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Parameter.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Parameter.java index 517fc2a06..65e68394c 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Parameter.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Parameter.java @@ -37,7 +37,7 @@ /** * Metadata for a parameter to be logged by the agent. */ -public final class Parameter implements IAttribute { +public final class Parameter implements Attribute { public static final int INDEX_INVALID = -2; public static final int INDEX_RETURN = -1; diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java index f718df47a..997147773 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java @@ -70,8 +70,7 @@ public byte[] transform( registry.getClassPreInstrumentation(className) : classfileBuffer; try { // Don't reuse this class loader instance. We want the loader and its class to unload after we're done. - classBeingRedefined = new InspectionClassLoader(loader) - .loadClass(TypeUtils.getCanonicalName(className)); + classBeingRedefined = new InspectionClassLoader(loader).loadClass(TypeUtils.getCanonicalName(className)); } catch (ClassNotFoundException e) { throw new IllegalStateException(e); // This should not happen } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextEventClassGenerator.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextEventClassGenerator.java index 279572e24..22b814195 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextEventClassGenerator.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextEventClassGenerator.java @@ -42,7 +42,7 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.openjdk.jmc.agent.Agent; -import org.openjdk.jmc.agent.IAttribute; +import org.openjdk.jmc.agent.Attribute; import org.openjdk.jmc.agent.Parameter; import org.openjdk.jmc.agent.Field; import org.openjdk.jmc.agent.jfr.JFRTransformDescriptor; @@ -82,7 +82,7 @@ private static void generateAttributeFields(ClassWriter cw, JFRTransformDescript } } - private static void createField(ClassWriter cw, JFRTransformDescriptor td, IAttribute attribute, Type type) { + private static void createField(ClassWriter cw, JFRTransformDescriptor td, Attribute attribute, Type type) { if (!td.isAllowedFieldType(type)) { Logger.getLogger(JFRNextEventClassGenerator.class.getName()) .warning("Skipped generating field in event class for attribute " + attribute + " and type " + type //$NON-NLS-1$ //$NON-NLS-2$ diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java index b40e9bf60..373213649 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java @@ -38,11 +38,11 @@ import org.objectweb.asm.Type; import org.objectweb.asm.commons.AdviceAdapter; import org.openjdk.jmc.agent.Field; -import org.openjdk.jmc.agent.IAttribute; +import org.openjdk.jmc.agent.Attribute; import org.openjdk.jmc.agent.Parameter; import org.openjdk.jmc.agent.jfr.JFRTransformDescriptor; import org.openjdk.jmc.agent.util.TypeUtils; -import org.openjdk.jmc.agent.util.expression.IReferenceChainElement; +import org.openjdk.jmc.agent.util.expression.ReferenceChainElement; import org.openjdk.jmc.agent.util.expression.IllegalSyntaxException; import org.openjdk.jmc.agent.util.expression.ReferenceChain; @@ -164,19 +164,19 @@ private void loadField(ReferenceChain refChain) { } // Assumes the reference chain is normalized already. See ReferenceChain.normalize() - List refs = refChain.getReferences(); + List refs = refChain.getReferences(); for (int i = 0; i < refs.size(); i++) { - IReferenceChainElement ref = refs.get(i); + ReferenceChainElement ref = refs.get(i); - if (ref instanceof IReferenceChainElement.ThisReference) { + if (ref instanceof ReferenceChainElement.ThisReference) { mv.visitVarInsn(ALOAD, 0); // load "this" continue; } - if (ref instanceof IReferenceChainElement.FieldReference) { - mv.visitFieldInsn(ref.isStatic() ? GETSTATIC : GETFIELD, - ref.getMemberingType().getInternalName(), - ((IReferenceChainElement.FieldReference) ref).getName(), ref.getReferencedType().getDescriptor()); + if (ref instanceof ReferenceChainElement.FieldReference) { + mv.visitFieldInsn(ref.isStatic() ? GETSTATIC : GETFIELD, ref.getMemberingType().getInternalName(), + ((ReferenceChainElement.FieldReference) ref).getName(), + ref.getReferencedType().getDescriptor()); // null check for field references if (i < refs.size() - 1) { // Skip null check for final reference. Null is acceptable here @@ -187,13 +187,11 @@ private void loadField(ReferenceChain refChain) { continue; } - if (ref instanceof IReferenceChainElement.QualifiedThisReference) { - int suffix = ((IReferenceChainElement.QualifiedThisReference) ref).getDepth(); + if (ref instanceof ReferenceChainElement.QualifiedThisReference) { + int suffix = ((ReferenceChainElement.QualifiedThisReference) ref).getDepth(); Class c = ref.getMemberingClass(); while (!ref.getReferencedClass().equals(c)) { - mv.visitFieldInsn(GETFIELD, - Type.getType(c).getInternalName(), - "this$" + (suffix--), + mv.visitFieldInsn(GETFIELD, Type.getType(c).getInternalName(), "this$" + (suffix--), Type.getType(c.getEnclosingClass()).getDescriptor()); c = c.getEnclosingClass(); } @@ -209,24 +207,20 @@ private void loadField(ReferenceChain refChain) { // null reference on path, load zero value mv.visitLabel(nullCase); - mv.visitFrame(F_NEW, - localVarVerifications.size(), - localVarVerifications.toArray(), - 4, - new Object[]{eventType.getInternalName(), eventType.getInternalName(), eventType.getInternalName(), Type.getInternalName(Object.class)}); + mv.visitFrame(F_NEW, localVarVerifications.size(), localVarVerifications.toArray(), 4, + new Object[] {eventType.getInternalName(), eventType.getInternalName(), eventType.getInternalName(), + Type.getInternalName(Object.class)}); mv.visitInsn(POP); mv.visitInsn(TypeUtils.getConstZeroOpcode(type)); // must verify frame for jump targets mv.visitLabel(continueCase); - mv.visitFrame(F_NEW, - localVarVerifications.size(), - localVarVerifications.toArray(), - 4, - new Object[]{eventType.getInternalName(), eventType.getInternalName(), eventType.getInternalName(), TypeUtils.getFrameVerificationType(type)}); + mv.visitFrame(F_NEW, localVarVerifications.size(), localVarVerifications.toArray(), 4, + new Object[] {eventType.getInternalName(), eventType.getInternalName(), eventType.getInternalName(), + TypeUtils.getFrameVerificationType(type)}); } - private void writeAttribute(IAttribute param, Type type) { + private void writeAttribute(Attribute param, Type type) { if (TypeUtils.shouldStringify(param, type)) { TypeUtils.stringify(mv, param, type); type = TypeUtils.STRING_TYPE; diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java index 0ac2d590a..28ad2264f 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java @@ -1,3 +1,35 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at http://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package org.openjdk.jmc.agent.util; import java.lang.reflect.Field; @@ -11,239 +43,215 @@ import java.util.Queue; public class AccessUtils { - public static Field getFieldInOuterClasses(Class clazz, String name) throws NoSuchFieldException { - Class c = clazz; - while (c != null) { - try { - return getFieldOnHierarchy(c, name); - } catch (NoSuchFieldException e) { - c = c.getEnclosingClass(); - } - } - - throw new NoSuchFieldException(String.format("cannot find field %s in outer classes of %s", name, clazz.getName())); - } - - public static Field getFieldOnHierarchy(Class clazz, String name) throws NoSuchFieldException { - Queue> q = new LinkedList<>(); - q.add(clazz); - - while (!q.isEmpty()) { - Class targetClass = q.remove(); - try { - return targetClass.getDeclaredField(name); - } catch (NoSuchFieldException e) { - // ignore - } - - q.addAll(Arrays.asList(targetClass.getInterfaces())); - Class superClass = targetClass.getSuperclass(); - if (superClass != null) { - q.add(targetClass.getSuperclass()); - } - } - - throw new NoSuchFieldException(String.format("cannot find field %s in class %s", name, clazz.getName())); - } - - public static boolean isAccessible(Class targetClass, Field field, Class currentClass) { - int modifiers = field.getModifiers(); - - Class memberClass = field.getDeclaringClass(); - if (Modifier.isStatic(modifiers)) { - targetClass = null; - } - - return verifyMemberAccess(targetClass, memberClass, currentClass, modifiers); - } - - public static boolean verifyMemberAccess(Class targetClass, Class memberClass, Class currentClass, int modifiers) { - if (currentClass == memberClass) { - return true; - } - - if (!verifyModuleAccess(memberClass, currentClass)) { - return false; - } - - boolean gotIsSameClassPackage = false; - boolean isSameClassPackage = false; - - if (!Modifier.isPublic(getClassAccessFlags(memberClass))) { - isSameClassPackage = isSameClassPackage(currentClass, memberClass); - gotIsSameClassPackage = true; - if (!isSameClassPackage) { - return false; - } - } - - // At this point we know that currentClass can access memberClass. - - if (Modifier.isPublic(modifiers)) { - return true; - } - - // Check for nestmate access if member is private - if (Modifier.isPrivate(modifiers)) { - // Note: targetClass may be outside the nest, but that is okay - // as long as memberClass is in the nest. - if (areNestMates(currentClass, memberClass)) { - return true; - } - } - - boolean successSoFar = false; - - if (Modifier.isProtected(modifiers)) { - // See if currentClass is a subclass of memberClass - if (isSubclassOf(currentClass, memberClass)) { - successSoFar = true; - } - } - - if (!successSoFar && !Modifier.isPrivate(modifiers)) { - if (!gotIsSameClassPackage) { - isSameClassPackage = isSameClassPackage(currentClass, - memberClass); - gotIsSameClassPackage = true; - } - - if (isSameClassPackage) { - successSoFar = true; - } - } - - if (!successSoFar) { - return false; - } - - // Additional test for protected instance members - // and protected constructors: JLS 6.6.2 - if (targetClass != null && Modifier.isProtected(modifiers) && - targetClass != currentClass) { - if (!gotIsSameClassPackage) { - isSameClassPackage = isSameClassPackage(currentClass, memberClass); - gotIsSameClassPackage = true; - } - if (!isSameClassPackage) { - if (!isSubclassOf(targetClass, currentClass)) { - return false; - } - } - } - - return true; - } - - public static boolean verifyModuleAccess(Class targetClass, Class callerClass) { - String version = System.getProperty("java.version"); - if (Integer.parseInt(version.substring(0, version.indexOf("."))) < 9) { - return true; // There is no module for pre-java 9 - } - - Object targetModule; - Object callerModule; - try { - Method getModuleMethod = Class.class.getDeclaredMethod("getModule"); - targetModule = getModuleMethod.invoke(targetClass); - callerModule = getModuleMethod.invoke(callerClass); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); // this should not happen - } - - if (targetModule == callerModule) { - return true; - } - - String pkg = getPackageName(targetClass); - try { - Method isExportedMethod = targetModule.getClass().getDeclaredMethod("isExported", String.class, Class.forName("java.lang.Module")); - return (boolean) isExportedMethod.invoke(targetModule, pkg, callerModule); - } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); // this should not happen - } - } - - // TODO: verify same behaviour as Class.getPackageName() - public static String getPackageName(Class clazz) { - while (clazz.isArray()) { - clazz = clazz.getComponentType(); - } - if (clazz.isPrimitive()) { - return "java.lang"; - } - - String cn = clazz.getName(); - int dot = cn.lastIndexOf('.'); - return (dot != -1) ? cn.substring(0, dot).intern() : ""; - } - - // TODO: verify same behaviour as Reflection.getClassAccessFlags(Class c) - public static int getClassAccessFlags(Class c) { - return c.getModifiers(); - } - - public static boolean isSameClassPackage(Class lhs, Class rhs) { - if (lhs.getClassLoader() != rhs.getClassLoader()) - return false; - return getPackageName(lhs).equals(getPackageName(rhs)); - } - - public static boolean isSubclassOf(Class queryClass, Class ofClass) { - while (queryClass != null) { - if (queryClass == ofClass) { - return true; - } - queryClass = queryClass.getSuperclass(); - } - return false; - } - - // Polyfill Class.getNestMembers() for pre-11 runtime. - // This function does not fully respect the definition of nesting from JVM's perspective. It's only used for - // validating access. - public static Class[] getNestMembers(Class clazz) { - List> classes = new ArrayList<>(); - classes.add(getNestHost(clazz)); - int i = 0; - while (i < classes.size()) { - classes.addAll(Arrays.asList(classes.get(i).getDeclaredClasses())); - i++; - } - - return classes.toArray(new Class[0]); - } - - // Polyfill Class.isNestMateOf() for pre-11 runtime - // This function does not fully respect the definition of nesting from JVM's perspective. It's only used for - // validating access. - public static boolean areNestMates(Class lhs, Class rhs) { - return getNestHost(lhs).equals(getNestHost(rhs)); - } - - // Polyfill Class.getNestHost() for pre-11 runtime - // This function does not fully respect the definition of nesting from JVM's perspective. It's only used for - // validating access. - public static Class getNestHost(Class clazz) { - // array types, primitive types, and void belong to the nests consisting only of theme, and are the nest hosts. - if (clazz.isArray()) { - return clazz; - } - - if (clazz.isPrimitive()) { - return clazz; - } - - if (Void.class.equals(clazz)) { - return clazz; - } - - while (true) { - if (clazz.getEnclosingClass() == null) { - return clazz; - } - - clazz = clazz.getEnclosingClass(); - } - } + public static Field getFieldOnHierarchy(Class clazz, String name) throws NoSuchFieldException { + Queue> q = new LinkedList<>(); + q.add(clazz); + + while (!q.isEmpty()) { + Class targetClass = q.remove(); + try { + return targetClass.getDeclaredField(name); + } catch (NoSuchFieldException e) { + // ignore + } + + q.addAll(Arrays.asList(targetClass.getInterfaces())); + Class superClass = targetClass.getSuperclass(); + if (superClass != null) { + q.add(targetClass.getSuperclass()); + } + } + + throw new NoSuchFieldException(String.format("cannot find field %s in class %s", name, clazz.getName())); + } + + public static boolean isAccessible(Class targetClass, Field field, Class currentClass) { + int modifiers = field.getModifiers(); + + Class memberClass = field.getDeclaringClass(); + if (Modifier.isStatic(modifiers)) { + targetClass = null; + } + + return verifyMemberAccess(targetClass, memberClass, currentClass, modifiers); + } + + public static boolean verifyMemberAccess(Class targetClass, Class memberClass, Class currentClass, + int modifiers) { + if (currentClass == memberClass) { + return true; + } + + if (!verifyModuleAccess(memberClass, currentClass)) { + return false; + } + + boolean gotIsSameClassPackage = false; + boolean isSameClassPackage = false; + + if (!Modifier.isPublic(getClassAccessFlags(memberClass))) { + isSameClassPackage = isSameClassPackage(currentClass, memberClass); + gotIsSameClassPackage = true; + if (!isSameClassPackage) { + return false; + } + } + + // At this point we know that currentClass can access memberClass. + + if (Modifier.isPublic(modifiers)) { + return true; + } + + // Check for nestmate access if member is private + if (Modifier.isPrivate(modifiers)) { + // Note: targetClass may be outside the nest, but that is okay + // as long as memberClass is in the nest. + if (areNestMates(currentClass, memberClass)) { + return true; + } + } + + boolean successSoFar = false; + + if (Modifier.isProtected(modifiers)) { + // See if currentClass is a subclass of memberClass + if (isSubclassOf(currentClass, memberClass)) { + successSoFar = true; + } + } + + if (!successSoFar && !Modifier.isPrivate(modifiers)) { + if (!gotIsSameClassPackage) { + isSameClassPackage = isSameClassPackage(currentClass, memberClass); + gotIsSameClassPackage = true; + } + + if (isSameClassPackage) { + successSoFar = true; + } + } + + if (!successSoFar) { + return false; + } + + // Additional test for protected instance members + // and protected constructors: JLS 6.6.2 + if (targetClass != null && Modifier.isProtected(modifiers) && targetClass != currentClass) { + if (!gotIsSameClassPackage) { + isSameClassPackage = isSameClassPackage(currentClass, memberClass); + } + if (!isSameClassPackage) { + return isSubclassOf(targetClass, currentClass); + } + } + + return true; + } + + public static boolean verifyModuleAccess(Class targetClass, Class callerClass) { + String version = System.getProperty("java.version"); + if (Integer.parseInt(version.substring(0, version.indexOf("."))) < 9) { + return true; // There is no module for pre-java 9 + } + + Object targetModule; + Object callerModule; + try { + Method getModuleMethod = Class.class.getDeclaredMethod("getModule"); + targetModule = getModuleMethod.invoke(targetClass); + callerModule = getModuleMethod.invoke(callerClass); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); // this should not happen + } + + if (targetModule == callerModule) { + return true; + } + + String pkg = getPackageName(targetClass); + try { + Method isExportedMethod = targetModule.getClass() + .getDeclaredMethod("isExported", String.class, Class.forName("java.lang.Module")); + return (boolean) isExportedMethod.invoke(targetModule, pkg, callerModule); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); // this should not happen + } + } + + // polyfill for Class.getPackageName(Class) + public static String getPackageName(Class clazz) { + return clazz.getPackage().getName(); + + } + + // polyfill for Reflection.getClassAccessFlags(Class) + public static int getClassAccessFlags(Class c) { + return c.getModifiers(); + } + + public static boolean isSameClassPackage(Class lhs, Class rhs) { + if (lhs.getClassLoader() != rhs.getClassLoader()) + return false; + return getPackageName(lhs).equals(getPackageName(rhs)); + } + + public static boolean isSubclassOf(Class queryClass, Class ofClass) { + while (queryClass != null) { + if (queryClass == ofClass) { + return true; + } + queryClass = queryClass.getSuperclass(); + } + return false; + } + + // Polyfill Class.getNestMembers() for pre-11 runtime. + // This function does not fully respect the definition of nesting from JVM's perspective. It's only used for + // validating access. + public static Class[] getNestMembers(Class clazz) { + List> classes = new ArrayList<>(); + classes.add(getNestHost(clazz)); + int i = 0; + while (i < classes.size()) { + classes.addAll(Arrays.asList(classes.get(i).getDeclaredClasses())); + i++; + } + + return classes.toArray(new Class[0]); + } + + // Polyfill Class.isNestMateOf() for pre-11 runtime + // This function does not fully respect the definition of nesting from JVM's perspective. It's only used for + // validating access. + public static boolean areNestMates(Class lhs, Class rhs) { + return getNestHost(lhs).equals(getNestHost(rhs)); + } + + // Polyfill Class.getNestHost() for pre-11 runtime + // This function does not fully respect the definition of nesting from JVM's perspective. It's only used for + // validating access. + public static Class getNestHost(Class clazz) { + // array types, primitive types, and void belong to the nests consisting only of theme, and are the nest hosts. + if (clazz.isArray()) { + return clazz; + } + + if (clazz.isPrimitive()) { + return clazz; + } + + if (Void.class.equals(clazz)) { + return clazz; + } + + while (true) { + if (clazz.getEnclosingClass() == null) { + return clazz; + } + + clazz = clazz.getEnclosingClass(); + } + } } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java index 05639e50d..d95496974 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java @@ -1,65 +1,97 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at http://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package org.openjdk.jmc.agent.util; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -// One-time use loader for reflective class inspection. An InspectionClassLoader only loads one class. +// One-time use loader for reflective class inspection. Don't keep static reference to one of these. public class InspectionClassLoader extends ClassLoader { - public InspectionClassLoader(ClassLoader parent) { - super(parent); - } + public InspectionClassLoader(ClassLoader parent) { + super(parent); + } - @Override - public Class loadClass(String name) throws ClassNotFoundException { - if (name.startsWith("java.")) { - return getParent().loadClass(name); - } + @Override + public Class loadClass(String name) throws ClassNotFoundException { + if (name.startsWith("java.")) { + return getParent().loadClass(name); + } - try { - return loadClass(name, true); - } catch (ClassNotFoundException e) { - return getParent().loadClass(name); - } - } + try { + return loadClass(name, true); + } catch (ClassNotFoundException e) { + return getParent().loadClass(name); + } + } - @Override - protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - Class clazz = findLoadedClass(name); - if (clazz != null) { - return clazz; - } + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + Class clazz = findLoadedClass(name); + if (clazz != null) { + return clazz; + } - clazz = findClass(name); + clazz = findClass(name); - if (resolve) { - resolveClass(clazz); - } + if (resolve) { + resolveClass(clazz); + } - return clazz; - } + return clazz; + } - @Override - protected Class findClass(String name) throws ClassNotFoundException { - InputStream is = getParent().getResourceAsStream(TypeUtils.getInternalName(name) + ".class"); - if (is == null) { - throw new ClassNotFoundException(name); - } + @Override + protected Class findClass(String name) throws ClassNotFoundException { + InputStream is = getParent().getResourceAsStream(TypeUtils.getInternalName(name) + ".class"); + if (is == null) { + throw new ClassNotFoundException(name); + } - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - int nRead; - byte[] data = new byte[1024]; // 1024 is chosen arbitrarily - try { - while ((nRead = is.read(data, 0, data.length)) != -1) { - buffer.write(data, 0, nRead); - buffer.flush(); - } - } catch (IOException e) { - throw new RuntimeException(e); - } + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int nRead; + byte[] data = new byte[1024]; // 1024 is chosen arbitrarily + try { + while ((nRead = is.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + buffer.flush(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } - byte[] bytes = buffer.toByteArray(); - return defineClass(name, bytes, 0, bytes.length); - } + byte[] bytes = buffer.toByteArray(); + return defineClass(name, bytes, 0, bytes.length); + } } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java index d2d07c030..35299fda9 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java @@ -37,10 +37,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.security.ProtectionDomain; -import java.util.Arrays; -import java.util.LinkedList; import java.util.List; -import java.util.Queue; import java.util.logging.Level; import java.util.logging.Logger; @@ -48,7 +45,7 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.openjdk.jmc.agent.Agent; -import org.openjdk.jmc.agent.IAttribute; +import org.openjdk.jmc.agent.Attribute; import org.openjdk.jmc.agent.Parameter; import org.openjdk.jmc.agent.jfr.impl.JFRUtils; @@ -126,13 +123,14 @@ public static String toString(Object o) { } public static Class defineClass( - String eventClassName, byte[] eventClass, int i, int length, ClassLoader definingClassLoader, - ProtectionDomain protectionDomain) { + String eventClassName, byte[] eventClass, int i, int length, ClassLoader definingClassLoader, + ProtectionDomain protectionDomain) { try { - return (Class) UNSAFE_DEFINE_CLASS_METHOD.invoke(UNSAFE, eventClassName, eventClass, i, length, - definingClassLoader, protectionDomain); + return (Class) UNSAFE_DEFINE_CLASS_METHOD + .invoke(UNSAFE, eventClassName, eventClass, i, length, definingClassLoader, protectionDomain); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - Agent.getLogger().log(Level.SEVERE, "Failed to dynamically define the class " + eventClassName, e); //$NON-NLS-1$ + Agent.getLogger() + .log(Level.SEVERE, "Failed to dynamically define the class " + eventClassName, e); //$NON-NLS-1$ } return null; } @@ -218,12 +216,12 @@ public static String getNamePart(String fqcn) { return fqcn; } - public static void stringify(MethodVisitor mv, IAttribute attribute, Type argumentType) { + public static void stringify(MethodVisitor mv, Attribute attribute, Type argumentType) { mv.visitMethodInsn(Opcodes.INVOKESTATIC, INAME, "toString", //$NON-NLS-1$ "(Ljava/lang/Object;)Ljava/lang/String;", false); //$NON-NLS-1$ } - public static boolean shouldStringify(IAttribute attribute, Type argumentType) { + public static boolean shouldStringify(Attribute attribute, Type argumentType) { if (argumentType.getSort() == Type.ARRAY || argumentType.getSort() == Type.OBJECT) { return !argumentType.getInternalName().equals(STRING_INTERNAL_NAME); } @@ -242,8 +240,7 @@ public static Parameter findReturnParam(List parameters) { /** * Transforms a FQN in internal form, so that it can be used in e.g. formal descriptors. * - * @param className - * the fully qualified class name in internal form. + * @param className the fully qualified class name in internal form. * @return the transformed class name. */ public static String parameterize(String className) { @@ -260,53 +257,53 @@ public static String getCanonicalName(String binaryName) { public static int getConstZeroOpcode(Type type) { switch (type.getSort()) { - case Type.BOOLEAN: - case Type.BYTE: - case Type.CHAR: - case Type.SHORT: - case Type.INT: - return Opcodes.ICONST_0; - case Type.FLOAT: - return Opcodes.FCONST_0; - case Type.LONG: - return Opcodes.LCONST_0; - case Type.DOUBLE: - return Opcodes.DCONST_0; - case Type.ARRAY: - case Type.OBJECT: - return Opcodes.ACONST_NULL; - case Type.METHOD: - case Type.VOID: - throw new UnsupportedOperationException(); - default: - throw new AssertionError(); + case Type.BOOLEAN: + case Type.BYTE: + case Type.CHAR: + case Type.SHORT: + case Type.INT: + return Opcodes.ICONST_0; + case Type.FLOAT: + return Opcodes.FCONST_0; + case Type.LONG: + return Opcodes.LCONST_0; + case Type.DOUBLE: + return Opcodes.DCONST_0; + case Type.ARRAY: + case Type.OBJECT: + return Opcodes.ACONST_NULL; + case Type.METHOD: + case Type.VOID: + throw new UnsupportedOperationException(); + default: + throw new AssertionError(); } } public static Object getFrameVerificationType(Type type) { switch (type.getSort()) { - case Type.BOOLEAN: - case Type.BYTE: - case Type.CHAR: - case Type.SHORT: - case Type.INT: - return Opcodes.INTEGER; - case Type.FLOAT: - return Opcodes.FLOAT; - case Type.LONG: - return Opcodes.LONG; - case Type.DOUBLE: - return Opcodes.DOUBLE; - case Type.ARRAY: - case Type.OBJECT: - return type.getInternalName(); - case Type.METHOD: - case Type.VOID: - throw new UnsupportedOperationException(); - default: - throw new AssertionError(); + case Type.BOOLEAN: + case Type.BYTE: + case Type.CHAR: + case Type.SHORT: + case Type.INT: + return Opcodes.INTEGER; + case Type.FLOAT: + return Opcodes.FLOAT; + case Type.LONG: + return Opcodes.LONG; + case Type.DOUBLE: + return Opcodes.DOUBLE; + case Type.ARRAY: + case Type.OBJECT: + return type.getInternalName(); + case Type.METHOD: + case Type.VOID: + throw new UnsupportedOperationException(); + default: + throw new AssertionError(); } - } + } /** * Type agnostic array toString() which also handles primitive arrays. diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java index be5f88b4b..567786128 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java @@ -1,7 +1,37 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at http://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package org.openjdk.jmc.agent.util.expression; -import org.openjdk.jmc.agent.util.AccessUtils; - import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Arrays; @@ -9,665 +39,676 @@ import java.util.LinkedList; import java.util.List; -/* - Expression // a subset of Java primary expression (without array accesses) - -> this - | TypeName . this - | FieldAccess - - TypeName - -> TypeIdentifier - | PackageOrTypeName . TypeIdentifier - - PackageOrTypeName - -> identifier - | PackageOrTypeName . identifier - - TypeIdentifier - -> identifier - - FieldAccess - -> Expression . identifier - | super . identifier - | TypeName . super . identifier - | FieldName - - FieldName - -> identifier - - identifier // terminal symbols - -> [A-z_]+[A-z0-9_]* - */ +import org.openjdk.jmc.agent.util.AccessUtils; + +/* + Expression // a subset of Java primary expression (without array accesses) + -> this + | TypeName . this + | FieldAccess + + TypeName + -> TypeIdentifier + | PackageOrTypeName . TypeIdentifier + + PackageOrTypeName + -> identifier + | PackageOrTypeName . identifier + + TypeIdentifier + -> identifier + + FieldAccess + -> Expression . identifier + | super . identifier + | TypeName . super . identifier + | FieldName + + FieldName + -> identifier + + identifier // terminal symbols + -> [A-z_]+[A-z0-9_]* + */ public class ExpressionResolver { - private final Class caller; - private final String expression; - - private List tokens = null; - private Iterator iterator = null; - private ReferenceChain referenceChain = null; - - private ExpressionResolver(Class caller, String expression) { - this.caller = caller; - this.expression = expression; - } - - public static ReferenceChain solve(Class caller, String expression) throws IllegalSyntaxException { - ExpressionResolver resolver = new ExpressionResolver(caller, expression); - resolver.tokens = new LinkedList<>(Arrays.asList(expression.split("\\."))); - resolver.iterator = resolver.tokens.iterator(); - resolver.referenceChain = new ReferenceChain(caller); - - resolver.enterStartState(); - - return resolver.referenceChain; - } - - private void enterStartState() throws IllegalSyntaxException { - if (!iterator.hasNext()) { - enterIllegalState("Unexpected end of input: expects 'this', 'super', a field name, a class name, a package name, or a package name fragment"); - } - - String token = iterator.next(); // first identifier - - // "this" - if (tryEnterThisState(caller, token)) { - return; - } - - // "super" - if (tryEnterSuperState(caller, token)) { - return; - } - - // local/inherited field reference - if (tryEnterFieldReferenceState(caller, token, false)) { // assuming accessing from a non-static context - return; - } - - // nested field reference - if (tryEntryNestedFieldReferenceState(token)) { // static class? - return; - } - - // outer class reference - if (tryEnterOuterClassState(token)) { - return; - } - - // inner class reference - if (tryEnterInnerClassState(caller, token)) { - return; - } - - // CallerClass - if (tryEnterSameClassState(token)) { - return; - } - - // ClassWithInTheSamePackage - if (tryEnterClassState(token)) { - return; - } - - // com.full.qualified.pkg.ClassName - if (tryEnterPackageState(expression)) { - return; - } - - // partially.qualified.pkg.ClassName - if (tryEnterPackageState(caller.getPackage(), expression)) { - return; - } - - // eg. Object => java.lang.Object, Integer => java.lang.Integer - if (tryEnterPackageState(Package.getPackage("java.lang"), expression)) { - return; - } - - enterIllegalState(String.format("Unrecognized symbol '%s'", token)); - } - - private boolean tryEnterThisState(Class enclosingClass, String thisLiteral) throws IllegalSyntaxException { - if (!"this".equals(thisLiteral)) { - return false; - } - - enterThisState(enclosingClass); - return true; - } - - // "^this" or "Qualified.this" expression (casting to an enclosing class) - private void enterThisState(Class enclosingClass) throws IllegalSyntaxException { - // cast to outer class instance only when accessing non-static fields - referenceChain.append(new IReferenceChainElement.ThisReference(caller)); - if (!caller.equals(enclosingClass)) { - try { - referenceChain.append(new IReferenceChainElement.QualifiedThisReference(caller, enclosingClass)); - } catch (IllegalArgumentException e) { - enterIllegalState(e); - } - } - - if (!iterator.hasNext()) { // accepted state - return; - } - String token = iterator.next(); - - // this.prop - if (tryEnterFieldReferenceState(enclosingClass, token, false)) { - return; - } - - enterIllegalState(String.format("Unrecognized symbol '%s'", token)); - } - - private boolean tryEnterSuperState(Class targetClass, String superLiteral) throws IllegalSyntaxException { - if (!"super".equals(superLiteral)) { - return false; - } - - enterSuperState(targetClass); - return true; - } - - // "^super" or "Qualified.super" expression - private void enterSuperState(Class enclosingClass) throws IllegalSyntaxException { - referenceChain.append(new IReferenceChainElement.ThisReference(caller)); - if (!caller.equals(enclosingClass)) { - try { - referenceChain.append(new IReferenceChainElement.QualifiedThisReference(caller, enclosingClass)); - } catch (IllegalArgumentException e) { - enterIllegalState(e); - } - } - - Class superClass = enclosingClass.getSuperclass(); - if (superClass == null) { // almost would never happen, java.lang classes are not transformable - enterIllegalState(String.format("'%s' has no super class", enclosingClass.getName())); - } - - if (!iterator.hasNext()) { // rejected state - enterIllegalState("unexpected end of input"); - } - String token = iterator.next(); - - // super.prop - if (tryEnterFieldReferenceState(superClass, token, false)) { - return; - } - - enterIllegalState(String.format("Unrecognized symbol '%s'", token)); - } - - private boolean tryEnterFieldReferenceState(Class memberingClass, String fieldName, boolean fromStaticContext) throws IllegalSyntaxException { - try { - Field field = AccessUtils.getFieldOnHierarchy(memberingClass, fieldName); - enterFieldReferenceState(memberingClass, field, fromStaticContext); - return true; - } catch (NoSuchFieldException e) { - return false; - } - } - - private void enterFieldReferenceState(Class memberingClass, Field field, boolean fromStaticContext) throws IllegalSyntaxException { - if (fromStaticContext && !Modifier.isStatic(field.getModifiers())) { - enterIllegalState(String.format("Non-static field '%s' cannot be referenced from a static context", field.getName())); - } - - if (!AccessUtils.isAccessible(memberingClass, field, caller)) { - String access; - if (Modifier.isPrivate(field.getModifiers())) { - access = "private"; - } else if (Modifier.isProtected(field.getModifiers())) { - access = "protected"; - } else { - access = "package-private"; - } - - enterIllegalState(String.format("'%s' has %s access in '%s'", field.getName(), access, field.getDeclaringClass().getName())); - } - - referenceChain.append(new IReferenceChainElement.FieldReference(memberingClass, field)); - - if (!iterator.hasNext()) { // accepted state - return; - } - String token = iterator.next(); - - // prop.prop2 - if (tryEnterFieldReferenceState(field.getType(), token, false)) { - return; - } - - enterIllegalState(String.format("Unrecognized symbol '%s'", token)); - } - - private boolean tryEntryNestedFieldReferenceState(String fieldName) throws IllegalSyntaxException { - Class enclosing = caller.getEnclosingClass(); - while (enclosing != null) { - try { - Field field = AccessUtils.getFieldOnHierarchy(enclosing, fieldName); - enterNestedFieldReferenceState(enclosing, field); - return true; - } catch (NoSuchFieldException e) { - enclosing = enclosing.getEnclosingClass(); - } - } - - return false; - } - - private void enterNestedFieldReferenceState(Class enclosingClass, Field field) throws IllegalSyntaxException { - if (!Modifier.isStatic(field.getModifiers())) { - Class c = caller.getEnclosingClass(); // the inner class is always static if it has a static method - // check there is no static class in between, before reaching the enclosing class - while (!c.equals(enclosingClass)) { - if (Modifier.isStatic(c.getModifiers())) { - enterIllegalState(String.format("Non-static field '%s' cannot be referenced from a static context", field.getName())); - } - c = c.getEnclosingClass(); - } - } - - // this is syntactically allowed, but we don't support it for now - if (Modifier.isPrivate(field.getModifiers())) { - enterIllegalState(new UnsupportedOperationException("Private member access between nestmates is not supported")); - } - - if (!Modifier.isStatic(field.getModifiers())) { - // cast to outer class instance only when accessing non-static fields - referenceChain.append(new IReferenceChainElement.ThisReference(caller)); - try { - referenceChain.append(new IReferenceChainElement.QualifiedThisReference(caller, enclosingClass)); - } catch (IllegalArgumentException e) { - enterIllegalState(e); - } - } - - referenceChain.append(new IReferenceChainElement.FieldReference(enclosingClass, field)); - - if (!iterator.hasNext()) { // accepted state - return; - } - String token = iterator.next(); - - // nestedProp.prop - if (tryEnterFieldReferenceState(field.getType(), token, false)) { - return; - } - - enterIllegalState(String.format("Unrecognized symbol '%s'", token)); - } - - private boolean tryEnterOuterClassState(String simpleClassName) throws IllegalSyntaxException { - Class enclosing = caller.getEnclosingClass(); - while (enclosing != null) { - if (enclosing.getSimpleName().equals(simpleClassName)) { - enterOuterClassState(enclosing); - return true; - } - - enclosing = enclosing.getEnclosingClass(); - } - - return false; - } - - private boolean tryEnterOuterClassState(Package pkg, String className) throws IllegalSyntaxException { - String fqcn = pkg.getName().isEmpty() ? className : pkg.getName() + "." + className; - try { - Class clazz = caller.getClassLoader().loadClass(fqcn); - Class enclosing = caller.getEnclosingClass(); - while (enclosing != null) { - if (enclosing.equals(clazz)) { - enterOuterClassState(enclosing); - return true; - } - - enclosing = enclosing.getEnclosingClass(); - } - } catch (ClassNotFoundException e) { - // no op - } - - return false; - } - - private boolean tryEnterOuterClassState(Class currentClass, String simpleClassName) throws IllegalSyntaxException { - Class clazz = null; - for (Class c : currentClass.getDeclaredClasses()) { - if (c.getSimpleName().equals(simpleClassName)) { - clazz = c; - break; - } - } - - Class enclosing = caller.getEnclosingClass(); - while (enclosing != null) { - if (enclosing.equals(clazz)) { - enterOuterClassState(enclosing); - return true; - } - - enclosing = enclosing.getEnclosingClass(); - } - - return false; - } - - private void enterOuterClassState(Class targetClass) throws IllegalSyntaxException { - // static context - if (!iterator.hasNext()) { // rejected state - enterIllegalState("Unexpected end of input: expects 'this', 'super', a static field name, or an inner class name"); - } - String token = iterator.next(); - - // OuterClass.this - if (tryEnterThisState(targetClass, token)) { - return; - } - - // OuterClass.super - if (tryEnterSuperState(targetClass, token)) { - return; - } - - // OuterClass.STATIC_PROP - if (tryEnterFieldReferenceState(targetClass, token, true)) { - return; - } - - // OuterClass.ThisClass - if (tryEnterSameClassState(targetClass, token)) { - return; - } - - // OuterMostClass.OuterClass - if (tryEnterOuterClassState(targetClass, token)) { - return; - } - - // OuterClass.OtherClass - if (tryEnterNestMateClass(targetClass, token)) { - return; - } - - enterIllegalState(String.format("Unrecognized symbol '%s'", token)); - } - - private boolean tryEnterInnerClassState(Class currentClass, String simpleClassName) throws IllegalSyntaxException { - for (Class innerClass : currentClass.getDeclaredClasses()) { - if (innerClass.getSimpleName().equals(simpleClassName)) { - enterInnerClassState(innerClass); - return true; - } - } - - return false; - } - - private void enterInnerClassState(Class targetClass) throws IllegalSyntaxException { - // static context - if (!iterator.hasNext()) { // rejected state - enterIllegalState("Unexpected end of input: expects a static field name or an inner class name"); - } - String token = iterator.next(); - - // InnerClass.STATIC_PROP - if (tryEnterFieldReferenceState(targetClass, token, true)) { - return; - } - - // InnerClass.InnerMoreClass - if (tryEnterInnerClassState(targetClass, token)) { - return; - } - - enterIllegalState(String.format("Unrecognized symbol '%s'", token)); - } - - // target class is not a inner or outer class of the caller class, but is a classmate - private boolean tryEnterNestMateClass(Class currentClass, String simpleClassName) throws IllegalSyntaxException { - Class clazz = null; - for (Class c : currentClass.getDeclaredClasses()) { - if (c.getSimpleName().equals(simpleClassName)) { - clazz = c; - break; - } - } - - if (clazz == null) { - return false; - } - - if (!AccessUtils.areNestMates(clazz, caller)) { - return false; - } - - // check caller is not an outer class of clazz - Class enclosing = clazz; - while (enclosing != null) { - if (caller.equals(enclosing)) { - return false; - } - enclosing = enclosing.getEnclosingClass(); - } - - // check clazz if not an outer class of caller - enclosing = caller; - while (enclosing != null) { - if (clazz.equals(enclosing)) { - return false; - } - enclosing = enclosing.getEnclosingClass(); - } - - enterNestMateClass(clazz); - return true; - } - - - private void enterNestMateClass(Class targetClass) throws IllegalSyntaxException { - // static context - if (!iterator.hasNext()) { // rejected state - enterIllegalState("Unexpected end of input: expects a static field name or an inner class name"); - } - String token = iterator.next(); - - // NestMateClass.STATIC_PROP - if (tryEnterFieldReferenceState(targetClass, token, false)) { - return; - } - - // NestMateClass.NestMatesInnerClass - if (tryEnterNestMateClass(targetClass, token)) { - return; - } - - enterIllegalState(String.format("Unrecognized symbol '%s'", token)); - } - - private boolean tryEnterSameClassState(String simpleClassName) throws IllegalSyntaxException { - if (caller.getSimpleName().equals(simpleClassName)) { - enterSameClassState(); - return true; - } - - return false; - } - - private boolean tryEnterSameClassState(Package pkg, String simpleClassName) throws IllegalSyntaxException { - String fqcn = pkg.getName().isEmpty() ? simpleClassName : pkg.getName() + "." + simpleClassName; - if (caller.getName().equals(fqcn)) { - enterSameClassState(); - return true; - } - - return false; - } - - private boolean tryEnterSameClassState(Class currentClass, String simpleClassName) throws IllegalSyntaxException { - Class clazz = null; - for (Class c : currentClass.getDeclaredClasses()) { - if (c.getSimpleName().equals(simpleClassName)) { - clazz = c; - break; - } - } - - if (caller.equals(clazz)) { - enterSameClassState(); - return true; - } - - return false; - } - - private void enterSameClassState() throws IllegalSyntaxException { - // static context - if (!iterator.hasNext()) { // rejected state - enterIllegalState("Unexpected end of input: expects a static field name or an inner class name"); - } - String token = iterator.next(); - - // CallerClass.this => this - if (tryEnterThisState(caller, token)) { - return; - } - - // CallerClass.super => super - if (tryEnterSuperState(caller, token)) { - return; - } - - // CallerClass.STATIC_PROP - if (tryEnterFieldReferenceState(caller, token, true)) { - return; - } - - if (tryEnterInnerClassState(caller, token)) { - return; - } - - enterIllegalState(String.format("Unrecognized symbol '%s'", token)); - } - - private boolean tryEnterClassState(String simpleClassName) throws IllegalSyntaxException { - return tryEnterClassState(caller.getPackage(), simpleClassName); - } - - private boolean tryEnterClassState(Class currentClass, String simpleClassName) throws IllegalSyntaxException { - for (Class c : currentClass.getDeclaredClasses()) { - if (c.getSimpleName().equals(simpleClassName)) { - enterClassState(c); - return true; - } - } - - return false; - } - - private boolean tryEnterClassState(Package pkg, String simpleClassName) throws IllegalSyntaxException { - String fqcn = pkg.getName().isEmpty() ? simpleClassName : pkg.getName() + "." + simpleClassName; - - try { - Class c = caller.getClassLoader().loadClass(fqcn); - enterClassState(c); - return true; - } catch (ClassNotFoundException e) { - return false; - } - } - - private void enterClassState(Class targetClass) throws IllegalSyntaxException { - // static context - if (!iterator.hasNext()) { // rejected state - enterIllegalState("Unexpected end of input: expects a static field name or an inner class name"); - } - String token = iterator.next(); - - // ClassName.STATIC_PROP - if (tryEnterFieldReferenceState(targetClass, token, true)) { - return; - } - - // ClassName.InnerClass - if (tryEnterClassState(targetClass, token)) { - return; - } - - enterIllegalState(String.format("Unrecognized symbol '%s'", token)); - } - - // Full qualified package named prefixed expression - private boolean tryEnterPackageState(String fqpnPrefixedExpression) throws IllegalSyntaxException { - // ClassLoader.getPackage(String) or ClassLoader.getPackages() is not reliable when no class from that package is yet loaded - int stop = 0; - Class clazz = null; - while (stop < fqpnPrefixedExpression.length()) { - stop = fqpnPrefixedExpression.indexOf('.', stop + 1); - if (stop == -1) { - break; - } - - String fqcn = fqpnPrefixedExpression.substring(0, stop); - try { - clazz = caller.getClassLoader().loadClass(fqcn); - break; - } catch (ClassNotFoundException e) { - // no op - } - } - - if (clazz == null) { - return false; - } - - Package pkg = clazz.getPackage(); - - tokens = new LinkedList<>(Arrays.asList(fqpnPrefixedExpression.split("\\."))); - iterator = tokens.iterator(); - int length = pkg.getName().split("\\.").length; - for (int i = 0; i < length; i++) { - iterator.next(); - } - - enterPackageState(pkg); - return true; - } - - // Partially qualified package named prefixed expression - private boolean tryEnterPackageState(Package pkg, String pqpnPrefixedExpression) throws IllegalSyntaxException { - String pkgPrefix = pkg.getName().isEmpty() ? "" : pkg.getName() + "."; - return tryEnterPackageState(pkgPrefix + pqpnPrefixedExpression); - } - - private void enterPackageState(Package pkg) throws IllegalSyntaxException { - if (!iterator.hasNext()) { // rejected state - enterIllegalState("Unexpected end of input: expects a class name"); - } - String token = iterator.next(); - - if (tryEnterSameClassState(pkg, token)) { - return; - } - - if (tryEnterOuterClassState(pkg, token)) { - return; - } - - if (tryEnterClassState(pkg, token)) { - return; - } - - enterIllegalState(String.format("Unrecognized symbol '%s'", token)); - } - - private void enterIllegalState(String msg) throws IllegalSyntaxException { - throw new IllegalSyntaxException(msg); - } - - private void enterIllegalState(Throwable throwable) throws IllegalSyntaxException { - throw new IllegalSyntaxException(throwable); - } + private final Class caller; + private final String expression; + + private List tokens = null; + private Iterator iterator = null; + private ReferenceChain referenceChain = null; + + private ExpressionResolver(Class caller, String expression) { + this.caller = caller; + this.expression = expression; + } + + public static ReferenceChain solve(Class caller, String expression) throws IllegalSyntaxException { + ExpressionResolver resolver = new ExpressionResolver(caller, expression); + resolver.tokens = new LinkedList<>(Arrays.asList(expression.split("\\."))); + resolver.iterator = resolver.tokens.iterator(); + resolver.referenceChain = new ReferenceChain(caller); + + resolver.enterStartState(); + + return resolver.referenceChain; + } + + private void enterStartState() throws IllegalSyntaxException { + if (!iterator.hasNext()) { + enterIllegalState("Unexpected end of input: expects 'this', 'super', a field name, a class name, a package name, or a package name fragment"); + } + + String token = iterator.next(); // first identifier + + // "this" + if (tryEnterThisState(caller, token)) { + return; + } + + // "super" + if (tryEnterSuperState(caller, token)) { + return; + } + + // local/inherited field reference + if (tryEnterFieldReferenceState(caller, token, false)) { // assuming accessing from a non-static context + return; + } + + // nested field reference + if (tryEntryNestedFieldReferenceState(token)) { // static class? + return; + } + + // outer class reference + if (tryEnterOuterClassState(token)) { + return; + } + + // inner class reference + if (tryEnterInnerClassState(caller, token)) { + return; + } + + // CallerClass + if (tryEnterSameClassState(token)) { + return; + } + + // ClassWithInTheSamePackage + if (tryEnterClassState(token)) { + return; + } + + // com.full.qualified.pkg.ClassName + if (tryEnterPackageState(expression)) { + return; + } + + // partially.qualified.pkg.ClassName + if (tryEnterPackageState(caller.getPackage(), expression)) { + return; + } + + // eg. Object => java.lang.Object, Integer => java.lang.Integer + if (tryEnterPackageState(Package.getPackage("java.lang"), expression)) { + return; + } + + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); + } + + private boolean tryEnterThisState(Class enclosingClass, String thisLiteral) throws IllegalSyntaxException { + if (!"this".equals(thisLiteral)) { + return false; + } + + enterThisState(enclosingClass); + return true; + } + + // "^this" or "Qualified.this" expression (casting to an enclosing class) + private void enterThisState(Class enclosingClass) throws IllegalSyntaxException { + // cast to outer class instance only when accessing non-static fields + referenceChain.append(new ReferenceChainElement.ThisReference(caller)); + if (!caller.equals(enclosingClass)) { + try { + referenceChain.append(new ReferenceChainElement.QualifiedThisReference(caller, enclosingClass)); + } catch (IllegalArgumentException e) { + enterIllegalState(e); + } + } + + if (!iterator.hasNext()) { // accepted state + return; + } + String token = iterator.next(); + + // this.prop + if (tryEnterFieldReferenceState(enclosingClass, token, false)) { + return; + } + + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); + } + + private boolean tryEnterSuperState(Class targetClass, String superLiteral) throws IllegalSyntaxException { + if (!"super".equals(superLiteral)) { + return false; + } + + enterSuperState(targetClass); + return true; + } + + // "^super" or "Qualified.super" expression + private void enterSuperState(Class enclosingClass) throws IllegalSyntaxException { + referenceChain.append(new ReferenceChainElement.ThisReference(caller)); + if (!caller.equals(enclosingClass)) { + try { + referenceChain.append(new ReferenceChainElement.QualifiedThisReference(caller, enclosingClass)); + } catch (IllegalArgumentException e) { + enterIllegalState(e); + } + } + + Class superClass = enclosingClass.getSuperclass(); + if (superClass == null) { // almost would never happen, java.lang classes are not transformable + enterIllegalState(String.format("'%s' has no super class", enclosingClass.getName())); + } + + if (!iterator.hasNext()) { // rejected state + enterIllegalState("unexpected end of input"); + } + String token = iterator.next(); + + // super.prop + if (tryEnterFieldReferenceState(superClass, token, false)) { + return; + } + + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); + } + + private boolean tryEnterFieldReferenceState(Class memberingClass, String fieldName, boolean fromStaticContext) + throws IllegalSyntaxException { + try { + Field field = AccessUtils.getFieldOnHierarchy(memberingClass, fieldName); + enterFieldReferenceState(memberingClass, field, fromStaticContext); + return true; + } catch (NoSuchFieldException e) { + return false; + } + } + + private void enterFieldReferenceState(Class memberingClass, Field field, boolean fromStaticContext) + throws IllegalSyntaxException { + if (fromStaticContext && !Modifier.isStatic(field.getModifiers())) { + enterIllegalState( + String.format("Non-static field '%s' cannot be referenced from a static context", field.getName())); + } + + if (!AccessUtils.isAccessible(memberingClass, field, caller)) { + String access; + if (Modifier.isPrivate(field.getModifiers())) { + access = "private"; + } else if (Modifier.isProtected(field.getModifiers())) { + access = "protected"; + } else { + access = "package-private"; + } + + enterIllegalState(String.format("'%s' has %s access in '%s'", field.getName(), access, + field.getDeclaringClass().getName())); + } + + referenceChain.append(new ReferenceChainElement.FieldReference(memberingClass, field)); + + if (!iterator.hasNext()) { // accepted state + return; + } + String token = iterator.next(); + + // prop.prop2 + if (tryEnterFieldReferenceState(field.getType(), token, false)) { + return; + } + + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); + } + + private boolean tryEntryNestedFieldReferenceState(String fieldName) throws IllegalSyntaxException { + Class enclosing = caller.getEnclosingClass(); + while (enclosing != null) { + try { + Field field = AccessUtils.getFieldOnHierarchy(enclosing, fieldName); + enterNestedFieldReferenceState(enclosing, field); + return true; + } catch (NoSuchFieldException e) { + enclosing = enclosing.getEnclosingClass(); + } + } + + return false; + } + + private void enterNestedFieldReferenceState(Class enclosingClass, Field field) throws IllegalSyntaxException { + if (!Modifier.isStatic(field.getModifiers())) { + Class c = caller.getEnclosingClass(); // the inner class is always static if it has a static method + // check there is no static class in between, before reaching the enclosing class + while (!c.equals(enclosingClass)) { + if (Modifier.isStatic(c.getModifiers())) { + enterIllegalState(String.format("Non-static field '%s' cannot be referenced from a static context", + field.getName())); + } + c = c.getEnclosingClass(); + } + } + + // this is syntactically allowed, but we don't support it for now + if (Modifier.isPrivate(field.getModifiers())) { + enterIllegalState( + new UnsupportedOperationException("Private member access between nestmates is not supported")); + } + + if (!Modifier.isStatic(field.getModifiers())) { + // cast to outer class instance only when accessing non-static fields + referenceChain.append(new ReferenceChainElement.ThisReference(caller)); + try { + referenceChain.append(new ReferenceChainElement.QualifiedThisReference(caller, enclosingClass)); + } catch (IllegalArgumentException e) { + enterIllegalState(e); + } + } + + referenceChain.append(new ReferenceChainElement.FieldReference(enclosingClass, field)); + + if (!iterator.hasNext()) { // accepted state + return; + } + String token = iterator.next(); + + // nestedProp.prop + if (tryEnterFieldReferenceState(field.getType(), token, false)) { + return; + } + + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); + } + + private boolean tryEnterOuterClassState(String simpleClassName) throws IllegalSyntaxException { + Class enclosing = caller.getEnclosingClass(); + while (enclosing != null) { + if (enclosing.getSimpleName().equals(simpleClassName)) { + enterOuterClassState(enclosing); + return true; + } + + enclosing = enclosing.getEnclosingClass(); + } + + return false; + } + + private boolean tryEnterOuterClassState(Package pkg, String className) throws IllegalSyntaxException { + String fqcn = pkg.getName().isEmpty() ? className : pkg.getName() + "." + className; + try { + Class clazz = caller.getClassLoader().loadClass(fqcn); + Class enclosing = caller.getEnclosingClass(); + while (enclosing != null) { + if (enclosing.equals(clazz)) { + enterOuterClassState(enclosing); + return true; + } + + enclosing = enclosing.getEnclosingClass(); + } + } catch (ClassNotFoundException e) { + // no op + } + + return false; + } + + private boolean tryEnterOuterClassState(Class currentClass, String simpleClassName) + throws IllegalSyntaxException { + Class clazz = null; + for (Class c : currentClass.getDeclaredClasses()) { + if (c.getSimpleName().equals(simpleClassName)) { + clazz = c; + break; + } + } + + Class enclosing = caller.getEnclosingClass(); + while (enclosing != null) { + if (enclosing.equals(clazz)) { + enterOuterClassState(enclosing); + return true; + } + + enclosing = enclosing.getEnclosingClass(); + } + + return false; + } + + private void enterOuterClassState(Class targetClass) throws IllegalSyntaxException { + // static context + if (!iterator.hasNext()) { // rejected state + enterIllegalState( + "Unexpected end of input: expects 'this', 'super', a static field name, or an inner class name"); + } + String token = iterator.next(); + + // OuterClass.this + if (tryEnterThisState(targetClass, token)) { + return; + } + + // OuterClass.super + if (tryEnterSuperState(targetClass, token)) { + return; + } + + // OuterClass.STATIC_PROP + if (tryEnterFieldReferenceState(targetClass, token, true)) { + return; + } + + // OuterClass.ThisClass + if (tryEnterSameClassState(targetClass, token)) { + return; + } + + // OuterMostClass.OuterClass + if (tryEnterOuterClassState(targetClass, token)) { + return; + } + + // OuterClass.OtherClass + if (tryEnterNestMateClass(targetClass, token)) { + return; + } + + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); + } + + private boolean tryEnterInnerClassState(Class currentClass, String simpleClassName) + throws IllegalSyntaxException { + for (Class innerClass : currentClass.getDeclaredClasses()) { + if (innerClass.getSimpleName().equals(simpleClassName)) { + enterInnerClassState(innerClass); + return true; + } + } + + return false; + } + + private void enterInnerClassState(Class targetClass) throws IllegalSyntaxException { + // static context + if (!iterator.hasNext()) { // rejected state + enterIllegalState("Unexpected end of input: expects a static field name or an inner class name"); + } + String token = iterator.next(); + + // InnerClass.STATIC_PROP + if (tryEnterFieldReferenceState(targetClass, token, true)) { + return; + } + + // InnerClass.InnerMoreClass + if (tryEnterInnerClassState(targetClass, token)) { + return; + } + + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); + } + + // target class is not a inner or outer class of the caller class, but is a classmate + private boolean tryEnterNestMateClass(Class currentClass, String simpleClassName) throws IllegalSyntaxException { + Class clazz = null; + for (Class c : currentClass.getDeclaredClasses()) { + if (c.getSimpleName().equals(simpleClassName)) { + clazz = c; + break; + } + } + + if (clazz == null) { + return false; + } + + if (!AccessUtils.areNestMates(clazz, caller)) { + return false; + } + + // check caller is not an outer class of clazz + Class enclosing = clazz; + while (enclosing != null) { + if (caller.equals(enclosing)) { + return false; + } + enclosing = enclosing.getEnclosingClass(); + } + + // check clazz if not an outer class of caller + enclosing = caller; + while (enclosing != null) { + if (clazz.equals(enclosing)) { + return false; + } + enclosing = enclosing.getEnclosingClass(); + } + + enterNestMateClass(clazz); + return true; + } + + private void enterNestMateClass(Class targetClass) throws IllegalSyntaxException { + // static context + if (!iterator.hasNext()) { // rejected state + enterIllegalState("Unexpected end of input: expects a static field name or an inner class name"); + } + String token = iterator.next(); + + // NestMateClass.STATIC_PROP + if (tryEnterFieldReferenceState(targetClass, token, false)) { + return; + } + + // NestMateClass.NestMatesInnerClass + if (tryEnterNestMateClass(targetClass, token)) { + return; + } + + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); + } + + private boolean tryEnterSameClassState(String simpleClassName) throws IllegalSyntaxException { + if (caller.getSimpleName().equals(simpleClassName)) { + enterSameClassState(); + return true; + } + + return false; + } + + private boolean tryEnterSameClassState(Package pkg, String simpleClassName) throws IllegalSyntaxException { + String fqcn = pkg.getName().isEmpty() ? simpleClassName : pkg.getName() + "." + simpleClassName; + if (caller.getName().equals(fqcn)) { + enterSameClassState(); + return true; + } + + return false; + } + + private boolean tryEnterSameClassState(Class currentClass, String simpleClassName) + throws IllegalSyntaxException { + Class clazz = null; + for (Class c : currentClass.getDeclaredClasses()) { + if (c.getSimpleName().equals(simpleClassName)) { + clazz = c; + break; + } + } + + if (caller.equals(clazz)) { + enterSameClassState(); + return true; + } + + return false; + } + + private void enterSameClassState() throws IllegalSyntaxException { + // static context + if (!iterator.hasNext()) { // rejected state + enterIllegalState("Unexpected end of input: expects a static field name or an inner class name"); + } + String token = iterator.next(); + + // CallerClass.this => this + if (tryEnterThisState(caller, token)) { + return; + } + + // CallerClass.super => super + if (tryEnterSuperState(caller, token)) { + return; + } + + // CallerClass.STATIC_PROP + if (tryEnterFieldReferenceState(caller, token, true)) { + return; + } + + if (tryEnterInnerClassState(caller, token)) { + return; + } + + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); + } + + private boolean tryEnterClassState(String simpleClassName) throws IllegalSyntaxException { + return tryEnterClassState(caller.getPackage(), simpleClassName); + } + + private boolean tryEnterClassState(Class currentClass, String simpleClassName) throws IllegalSyntaxException { + for (Class c : currentClass.getDeclaredClasses()) { + if (c.getSimpleName().equals(simpleClassName)) { + enterClassState(c); + return true; + } + } + + return false; + } + + private boolean tryEnterClassState(Package pkg, String simpleClassName) throws IllegalSyntaxException { + String fqcn = pkg.getName().isEmpty() ? simpleClassName : pkg.getName() + "." + simpleClassName; + + try { + Class c = caller.getClassLoader().loadClass(fqcn); + enterClassState(c); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + + private void enterClassState(Class targetClass) throws IllegalSyntaxException { + // static context + if (!iterator.hasNext()) { // rejected state + enterIllegalState("Unexpected end of input: expects a static field name or an inner class name"); + } + String token = iterator.next(); + + // ClassName.STATIC_PROP + if (tryEnterFieldReferenceState(targetClass, token, true)) { + return; + } + + // ClassName.InnerClass + if (tryEnterClassState(targetClass, token)) { + return; + } + + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); + } + + // Full qualified package named prefixed expression + private boolean tryEnterPackageState(String fqpnPrefixedExpression) throws IllegalSyntaxException { + // ClassLoader.getPackage(String) or ClassLoader.getPackages() is not reliable when no class from that package is yet loaded + int stop = 0; + Class clazz = null; + while (stop < fqpnPrefixedExpression.length()) { + stop = fqpnPrefixedExpression.indexOf('.', stop + 1); + if (stop == -1) { + break; + } + + String fqcn = fqpnPrefixedExpression.substring(0, stop); + try { + clazz = caller.getClassLoader().loadClass(fqcn); + break; + } catch (ClassNotFoundException e) { + // no op + } + } + + if (clazz == null) { + return false; + } + + Package pkg = clazz.getPackage(); + + tokens = new LinkedList<>(Arrays.asList(fqpnPrefixedExpression.split("\\."))); + iterator = tokens.iterator(); + int length = pkg.getName().split("\\.").length; + for (int i = 0; i < length; i++) { + iterator.next(); + } + + enterPackageState(pkg); + return true; + } + + // Partially qualified package named prefixed expression + private boolean tryEnterPackageState(Package pkg, String pqpnPrefixedExpression) throws IllegalSyntaxException { + String pkgPrefix = pkg.getName().isEmpty() ? "" : pkg.getName() + "."; + return tryEnterPackageState(pkgPrefix + pqpnPrefixedExpression); + } + + private void enterPackageState(Package pkg) throws IllegalSyntaxException { + if (!iterator.hasNext()) { // rejected state + enterIllegalState("Unexpected end of input: expects a class name"); + } + String token = iterator.next(); + + if (tryEnterSameClassState(pkg, token)) { + return; + } + + if (tryEnterOuterClassState(pkg, token)) { + return; + } + + if (tryEnterClassState(pkg, token)) { + return; + } + + enterIllegalState(String.format("Unrecognized symbol '%s'", token)); + } + + private void enterIllegalState(String msg) throws IllegalSyntaxException { + throw new IllegalSyntaxException(msg); + } + + private void enterIllegalState(Throwable throwable) throws IllegalSyntaxException { + throw new IllegalSyntaxException(throwable); + } } \ No newline at end of file diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/IReferenceChainElement.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/IReferenceChainElement.java deleted file mode 100644 index 45907f77c..000000000 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/IReferenceChainElement.java +++ /dev/null @@ -1,204 +0,0 @@ -package org.openjdk.jmc.agent.util.expression; - -import org.objectweb.asm.Type; -import org.openjdk.jmc.agent.util.AccessUtils; - -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.Objects; - - -public interface IReferenceChainElement { - // class/interface which the reference is from - Class getMemberingClass(); - - // class/interface which the reference is to - Class getReferencedClass(); - - // the type of the class/interface which the reference is from - Type getMemberingType(); - - // the type of the class/interface which the reference is to - Type getReferencedType(); - - // if the reference is allow from a caller - boolean isAccessibleFrom(Class caller); - - // if this reference is static - boolean isStatic(); - - class FieldReference implements IReferenceChainElement { - private final Class memberingClass; - private final Field field; - - public FieldReference(Class memberingClass, Field field) { - this.memberingClass = memberingClass; - this.field = field; - - try { - AccessUtils.getFieldOnHierarchy(memberingClass, field.getName()); - } catch (NoSuchFieldException e) { - throw new IllegalArgumentException(String.format("'%s' is not a field of '%s'", field.getName(), memberingClass.getName())); - } - } - - @Override - public Class getMemberingClass() { - return memberingClass; - } - - @Override - public Class getReferencedClass() { - return field.getType(); - } - - @Override - public Type getMemberingType() { - return Type.getType(getMemberingClass()); - } - - @Override - public Type getReferencedType() { - return Type.getType(getReferencedClass()); - } - - @Override - public boolean isAccessibleFrom(Class caller) { - return AccessUtils.isAccessible(memberingClass, field, caller); - } - - @Override - public boolean isStatic() { - return Modifier.isStatic(field.getModifiers()); - } - - @Override - public String toString() { - return String.format("%s.%s:%s", getMemberingClass().getName(), getName(), getReferencedClass().getName()); - } - - public Field getField() { - return field; - } - - public String getName() { - return getField().getName(); - } - } - - class ThisReference implements IReferenceChainElement { - private final Class clazz; - - public ThisReference(Class clazz) { - this.clazz = clazz; - - Objects.requireNonNull(clazz, "Class is not nullable"); - } - - @Override - public Class getMemberingClass() { - return clazz; - } - - @Override - public Class getReferencedClass() { - return clazz; - } - - @Override - public Type getMemberingType() { - return Type.getType(getMemberingClass()); - } - - @Override - public Type getReferencedType() { - return Type.getType(getReferencedClass()); - } - - @Override - public boolean isAccessibleFrom(Class caller) { - return clazz.equals(caller); - } - - @Override - public boolean isStatic() { - return false; - } - - @Override - public String toString() { - return "this"; - } - } - - class QualifiedThisReference implements IReferenceChainElement { - private final Class innerClass; - private final Class enclosingClass; - private final int depth; - - public QualifiedThisReference(Class innerClass, Class enclosingClass) { - this.innerClass = innerClass; - this.enclosingClass = enclosingClass; - - Class c = innerClass; - int d = 0; // depth of inner class nesting, used for this$i reference to enclosing classes - while (!enclosingClass.equals(c.getEnclosingClass())) { - Class enclosing = c.getEnclosingClass(); - if (enclosing == null) { - throw new IllegalArgumentException(String.format("%s is not an enclosing class of %s", enclosingClass.getName(), innerClass.getName())); - } - - d++; - c = enclosing; - } - - this.depth = d; - } - - @Override - public Class getMemberingClass() { - return innerClass; - } - - @Override - public Class getReferencedClass() { - return enclosingClass; - } - - @Override - public Type getMemberingType() { - return Type.getType(getMemberingClass()); - } - - @Override - public Type getReferencedType() { - return Type.getType(getReferencedClass()); - } - - @Override - public boolean isAccessibleFrom(Class caller) { - Class c = caller; - while (c != null) { - if (c.equals(innerClass)) { - return true; - } - c = c.getEnclosingClass(); - } - return false; - } - - @Override - public boolean isStatic() { - return false; - } - - @Override - public String toString() { - return String.format("%s.this", getReferencedClass().getName()); - } - - public int getDepth() { - return depth; - } - } -} diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/IllegalSyntaxException.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/IllegalSyntaxException.java index a65e82ddb..a0e2c7b97 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/IllegalSyntaxException.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/IllegalSyntaxException.java @@ -1,19 +1,51 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at http://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package org.openjdk.jmc.agent.util.expression; public class IllegalSyntaxException extends Exception { - public IllegalSyntaxException() { - super(); - } + public IllegalSyntaxException() { + super(); + } - public IllegalSyntaxException(String message) { - super(message); - } + public IllegalSyntaxException(String message) { + super(message); + } - public IllegalSyntaxException(String message, Throwable cause) { - super(message, cause); - } + public IllegalSyntaxException(String message, Throwable cause) { + super(message, cause); + } - public IllegalSyntaxException(Throwable cause) { - super(cause); - } + public IllegalSyntaxException(Throwable cause) { + super(cause); + } } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java index 0eeb1d5a3..f6e81b111 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java @@ -1,72 +1,103 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at http://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package org.openjdk.jmc.agent.util.expression; import org.objectweb.asm.Type; -import java.lang.reflect.Modifier; import java.util.LinkedList; import java.util.List; public class ReferenceChain { - private final Class callerClass; - private final List references; - - public ReferenceChain(Class callerClass) { - this.callerClass = callerClass; - this.references = new LinkedList<>(); - } - - public Class getCallerClass() { - return callerClass; - } - - public List getReferences() { - return references; - } - - public ReferenceChain normalize() { - List oldRefs = getReferences(); - List newRefs = new LinkedList<>(); - - // Take shortcuts on static references - for (IReferenceChainElement ref : oldRefs) { - if (ref.isStatic()) { - newRefs.clear(); - } - - newRefs.add(ref); - } - - // Don't reduce static final references to constants. The value could be different, or even stochastic, if - // loaded via different class loaders. (eg. logic in static initializers) - - // prepend "this" if starts with non-static field reference - if (newRefs.isEmpty()) { - newRefs.add(0, new IReferenceChainElement.ThisReference(callerClass)); // implicit "this" - } else if (newRefs.get(0) instanceof IReferenceChainElement.FieldReference && !newRefs.get(0).isStatic()) { - newRefs.add(0, new IReferenceChainElement.ThisReference(callerClass)); // prop => this.prop - } - - ReferenceChain ret = new ReferenceChain(callerClass); - ret.references.addAll(newRefs); - return ret; - } - - public Type getType() { - if (references.isEmpty()) { - return Type.getType(callerClass); - } - return references.get(references.size() - 1).getReferencedType(); - } - - public void append(IReferenceChainElement ref) { - references.add(ref); - } - - public boolean isStatic() { - if (references.isEmpty()) { - return false; - } - - return references.get(0).isStatic(); - } + private final Class callerClass; + private final List references; + + public ReferenceChain(Class callerClass) { + this.callerClass = callerClass; + this.references = new LinkedList<>(); + } + + public Class getCallerClass() { + return callerClass; + } + + public List getReferences() { + return references; + } + + public ReferenceChain normalize() { + List oldRefs = getReferences(); + List newRefs = new LinkedList<>(); + + // Take shortcuts on static references + for (ReferenceChainElement ref : oldRefs) { + if (ref.isStatic()) { + newRefs.clear(); + } + + newRefs.add(ref); + } + + // Don't reduce static final references to constants. The value could be different, or even stochastic, if + // loaded via different class loaders. (eg. logic in static initializers) + + // prepend "this" if starts with non-static field reference + if (newRefs.isEmpty()) { + newRefs.add(0, new ReferenceChainElement.ThisReference(callerClass)); // implicit "this" + } else if (newRefs.get(0) instanceof ReferenceChainElement.FieldReference && !newRefs.get(0).isStatic()) { + newRefs.add(0, new ReferenceChainElement.ThisReference(callerClass)); // prop => this.prop + } + + ReferenceChain ret = new ReferenceChain(callerClass); + ret.references.addAll(newRefs); + return ret; + } + + public Type getType() { + if (references.isEmpty()) { + return Type.getType(callerClass); + } + return references.get(references.size() - 1).getReferencedType(); + } + + public void append(ReferenceChainElement ref) { + references.add(ref); + } + + public boolean isStatic() { + if (references.isEmpty()) { + return false; + } + + return references.get(0).isStatic(); + } } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChainElement.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChainElement.java new file mode 100644 index 000000000..d546bc137 --- /dev/null +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChainElement.java @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at http://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjdk.jmc.agent.util.expression; + +import org.objectweb.asm.Type; +import org.openjdk.jmc.agent.util.AccessUtils; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Objects; + +public interface ReferenceChainElement { + // class/interface which the reference is from + Class getMemberingClass(); + + // class/interface which the reference is to + Class getReferencedClass(); + + // the type of the class/interface which the reference is from + Type getMemberingType(); + + // the type of the class/interface which the reference is to + Type getReferencedType(); + + // if the reference is allow from a caller + boolean isAccessibleFrom(Class caller); + + // if this reference is static + boolean isStatic(); + + class FieldReference implements ReferenceChainElement { + private final Class memberingClass; + private final Field field; + + public FieldReference(Class memberingClass, Field field) { + this.memberingClass = memberingClass; + this.field = field; + + try { + AccessUtils.getFieldOnHierarchy(memberingClass, field.getName()); + } catch (NoSuchFieldException e) { + throw new IllegalArgumentException( + String.format("'%s' is not a field of '%s'", field.getName(), memberingClass.getName())); + } + } + + @Override + public Class getMemberingClass() { + return memberingClass; + } + + @Override + public Class getReferencedClass() { + return field.getType(); + } + + @Override + public Type getMemberingType() { + return Type.getType(getMemberingClass()); + } + + @Override + public Type getReferencedType() { + return Type.getType(getReferencedClass()); + } + + @Override + public boolean isAccessibleFrom(Class caller) { + return AccessUtils.isAccessible(memberingClass, field, caller); + } + + @Override + public boolean isStatic() { + return Modifier.isStatic(field.getModifiers()); + } + + @Override + public String toString() { + return String.format("%s.%s:%s", getMemberingClass().getName(), getName(), getReferencedClass().getName()); + } + + public Field getField() { + return field; + } + + public String getName() { + return getField().getName(); + } + } + + class ThisReference implements ReferenceChainElement { + private final Class clazz; + + public ThisReference(Class clazz) { + this.clazz = clazz; + + Objects.requireNonNull(clazz, "Class is not nullable"); + } + + @Override + public Class getMemberingClass() { + return clazz; + } + + @Override + public Class getReferencedClass() { + return clazz; + } + + @Override + public Type getMemberingType() { + return Type.getType(getMemberingClass()); + } + + @Override + public Type getReferencedType() { + return Type.getType(getReferencedClass()); + } + + @Override + public boolean isAccessibleFrom(Class caller) { + return clazz.equals(caller); + } + + @Override + public boolean isStatic() { + return false; + } + + @Override + public String toString() { + return "this"; + } + } + + class QualifiedThisReference implements ReferenceChainElement { + private final Class innerClass; + private final Class enclosingClass; + private final int depth; + + public QualifiedThisReference(Class innerClass, Class enclosingClass) { + this.innerClass = innerClass; + this.enclosingClass = enclosingClass; + + Class c = innerClass; + int d = 0; // depth of inner class nesting, used for this$i reference to enclosing classes + while (!enclosingClass.equals(c.getEnclosingClass())) { + Class enclosing = c.getEnclosingClass(); + if (enclosing == null) { + throw new IllegalArgumentException( + String.format("%s is not an enclosing class of %s", enclosingClass.getName(), + innerClass.getName())); + } + + d++; + c = enclosing; + } + + this.depth = d; + } + + @Override + public Class getMemberingClass() { + return innerClass; + } + + @Override + public Class getReferencedClass() { + return enclosingClass; + } + + @Override + public Type getMemberingType() { + return Type.getType(getMemberingClass()); + } + + @Override + public Type getReferencedType() { + return Type.getType(getReferencedClass()); + } + + @Override + public boolean isAccessibleFrom(Class caller) { + Class c = caller; + while (c != null) { + if (c.equals(innerClass)) { + return true; + } + c = c.getEnclosingClass(); + } + return false; + } + + @Override + public boolean isStatic() { + return false; + } + + @Override + public String toString() { + return String.format("%s.this", getReferencedClass().getName()); + } + + public int getDepth() { + return depth; + } + } +} From e05ea8bf1b4740b128cbe5611ffa0ec65a8aadd0 Mon Sep 17 00:00:00 2001 From: kxu Date: Tue, 17 Dec 2019 11:08:51 -0500 Subject: [PATCH 17/27] support legacy JFR api --- .../org/openjdk/jmc/agent/Transformer.java | 2 +- .../jmc/agent/jfr/impl/JFRClassVisitor.java | 10 +- .../jfr/impl/JFREventClassGenerator.java | 35 +++--- .../jmc/agent/jfr/impl/JFRMethodAdvisor.java | 111 +++++++++++++++++- 4 files changed, 133 insertions(+), 25 deletions(-) diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java index 997147773..244e3aa20 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java @@ -108,7 +108,7 @@ private byte[] doJFRLogging( ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassVisitor visitor = VersionResolver.getAvailableJFRVersion() == JFRVersion.JFRNEXT ? new JFRNextClassVisitor(classWriter, td, definingClassLoader, classBeingRedefined, protectionDomain) - : new JFRClassVisitor(classWriter, td, definingClassLoader, protectionDomain); // TODO: support path-syntax evaluation for legacy API + : new JFRClassVisitor(classWriter, td, definingClassLoader, classBeingRedefined, protectionDomain); ClassReader reader = new ClassReader(classfileBuffer); reader.accept(visitor, 0); return classWriter.toByteArray(); diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRClassVisitor.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRClassVisitor.java index 814e1e273..a81262c04 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRClassVisitor.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRClassVisitor.java @@ -46,13 +46,15 @@ public class JFRClassVisitor extends ClassVisitor implements Opcodes { private final JFRTransformDescriptor transformDescriptor; private final ClassLoader definingClassLoader; + private final Class classBeingRedefined; private final ProtectionDomain protectionDomain; - public JFRClassVisitor(ClassWriter cv, JFRTransformDescriptor descriptor, ClassLoader definingLoader, - ProtectionDomain protectionDomain) { + public JFRClassVisitor(ClassWriter cv, JFRTransformDescriptor descriptor, ClassLoader definingLoader, + Class classBeingRedefined, ProtectionDomain protectionDomain) { super(Opcodes.ASM5, cv); this.transformDescriptor = descriptor; this.definingClassLoader = definingLoader; + this.classBeingRedefined = classBeingRedefined; this.protectionDomain = protectionDomain; } @@ -61,7 +63,7 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); if (name.equals(transformDescriptor.getMethod().getName()) && desc.equals(transformDescriptor.getMethod().getSignature())) { - return new JFRMethodAdvisor(transformDescriptor, Opcodes.ASM5, mv, access, name, desc); + return new JFRMethodAdvisor(transformDescriptor, classBeingRedefined, Opcodes.ASM5, mv, access, name, desc); } return mv; } @@ -79,7 +81,7 @@ public void visitEnd() { } private Class generateEventClass() throws Exception { - byte[] eventClass = JFREventClassGenerator.generateEventClass(transformDescriptor); + byte[] eventClass = JFREventClassGenerator.generateEventClass(transformDescriptor, classBeingRedefined); return TypeUtils.defineClass(transformDescriptor.getEventClassName(), eventClass, 0, eventClass.length, definingClassLoader, protectionDomain); } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFREventClassGenerator.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFREventClassGenerator.java index ba6ebeb6d..f6211c20d 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFREventClassGenerator.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFREventClassGenerator.java @@ -41,9 +41,12 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; +import org.openjdk.jmc.agent.Attribute; +import org.openjdk.jmc.agent.Field; import org.openjdk.jmc.agent.Parameter; import org.openjdk.jmc.agent.jfr.JFRTransformDescriptor; import org.openjdk.jmc.agent.util.TypeUtils; +import org.openjdk.jmc.agent.util.expression.IllegalSyntaxException; public class JFREventClassGenerator { private static final String CLASS_NAME_INSTANT_EVENT = "com/oracle/jrockit/jfr/InstantEvent"; //$NON-NLS-1$ @@ -59,7 +62,7 @@ public class JFREventClassGenerator { * @throws Exception * if the event class could not be generated. */ - public static byte[] generateEventClass(JFRTransformDescriptor td) throws Exception { + public static byte[] generateEventClass(JFRTransformDescriptor td, Class classBeingRedefined) throws Exception { ClassWriter cw = new ClassWriter(0); // TODO: Add support for different locations cw.visit(Opcodes.V1_7, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, td.getEventClassName(), null, @@ -70,7 +73,7 @@ public static byte[] generateEventClass(JFRTransformDescriptor td) throws Except String parameterizedClassName = TypeUtils.parameterize(td.getEventClassName()); generateClassAnnotations(cw, td); generateTokenField(cw); - generateAttributeFields(cw, td); + generateAttributeFields(cw, td, classBeingRedefined); generateClinit(cw, td.getEventClassName(), parameterizedClassName); generateInit(cw, td.getEventClassName(), parameterizedClassName); cw.visitEnd(); @@ -78,7 +81,7 @@ public static byte[] generateEventClass(JFRTransformDescriptor td) throws Except return cw.toByteArray(); } - private static void generateAttributeFields(ClassWriter cw, JFRTransformDescriptor td) { + private static void generateAttributeFields(ClassWriter cw, JFRTransformDescriptor td, Class classBeingRedefined) throws IllegalSyntaxException { Type[] args = Type.getArgumentTypes(td.getMethod().getSignature()); for (Parameter param : td.getParameters()) { if (param.isReturn()) { @@ -87,31 +90,35 @@ private static void generateAttributeFields(ClassWriter cw, JFRTransformDescript createField(cw, td, param, args[param.getIndex()]); } } + + for (Field field : td.getFields()) { + createField(cw, td, field, field.resolveReferenceChain(classBeingRedefined).getType()); + } } - private static void createField(ClassWriter cw, JFRTransformDescriptor td, Parameter param, Type type) { + private static void createField(ClassWriter cw, JFRTransformDescriptor td, Attribute attribute, Type type) { if (!td.isAllowedFieldType(type)) { Logger.getLogger(JFREventClassGenerator.class.getName()) - .warning("Skipped generating field in event class for parameter " + param + " and type " + type //$NON-NLS-1$ //$NON-NLS-2$ + .warning("Skipped generating field in event class for parameter " + attribute + " and type " + type //$NON-NLS-1$ //$NON-NLS-2$ + " because of configuration settings!"); //$NON-NLS-1$ return; } String fieldType = getFieldType(type); - FieldVisitor fv = cw.visitField(Opcodes.ACC_PUBLIC, param.getFieldName(), fieldType, null, null); + FieldVisitor fv = cw.visitField(Opcodes.ACC_PUBLIC, attribute.getFieldName(), fieldType, null, null); AnnotationVisitor av = fv.visitAnnotation("Lcom/oracle/jrockit/jfr/ValueDefinition;", true); //$NON-NLS-1$ - if (param.getName() != null) { - av.visit("name", param.getName()); //$NON-NLS-1$ + if (attribute.getName() != null) { + av.visit("name", attribute.getName()); //$NON-NLS-1$ } - if (param.getDescription() != null) { - av.visit("description", param.getDescription()); //$NON-NLS-1$ + if (attribute.getDescription() != null) { + av.visit("description", attribute.getDescription()); //$NON-NLS-1$ } - if (param.getContentType() != null) { - av.visitEnum("contentType", "Lcom/oracle/jrockit/jfr/ContentType;", param.getContentType()); //$NON-NLS-1$ //$NON-NLS-2$ + if (attribute.getContentType() != null) { + av.visitEnum("contentType", "Lcom/oracle/jrockit/jfr/ContentType;", attribute.getContentType()); //$NON-NLS-1$ //$NON-NLS-2$ } - if (param.getRelationKey() != null) { - av.visit("relationKey", param.getRelationKey()); //$NON-NLS-1$ + if (attribute.getRelationKey() != null) { + av.visit("relationKey", attribute.getRelationKey()); //$NON-NLS-1$ } av.visitEnd(); fv.visitEnd(); diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRMethodAdvisor.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRMethodAdvisor.java index 622674ae4..e1083f832 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRMethodAdvisor.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRMethodAdvisor.java @@ -37,9 +37,18 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.AdviceAdapter; +import org.openjdk.jmc.agent.Attribute; +import org.openjdk.jmc.agent.Field; import org.openjdk.jmc.agent.Parameter; import org.openjdk.jmc.agent.jfr.JFRTransformDescriptor; import org.openjdk.jmc.agent.util.TypeUtils; +import org.openjdk.jmc.agent.util.expression.IllegalSyntaxException; +import org.openjdk.jmc.agent.util.expression.ReferenceChain; +import org.openjdk.jmc.agent.util.expression.ReferenceChainElement; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; /** * Code emitter for JFR distributed with pre-JDK 9 releases. Probably works with JRockit too. ;) @@ -48,6 +57,7 @@ public class JFRMethodAdvisor extends AdviceAdapter { private static final String THROWABLE_BINARY_NAME = "java/lang/Throwable"; //$NON-NLS-1$ private final JFRTransformDescriptor transformDescriptor; + private final Class classBeingRedefined; private final Type[] argumentTypesRef; private final Type returnTypeRef; private final Type eventType; @@ -58,10 +68,11 @@ public class JFRMethodAdvisor extends AdviceAdapter { private boolean shouldInstrumentThrow; - protected JFRMethodAdvisor(JFRTransformDescriptor transformDescriptor, int api, MethodVisitor mv, int access, + protected JFRMethodAdvisor(JFRTransformDescriptor transformDescriptor, Class classBeingRedefined, int api, MethodVisitor mv, int access, String name, String desc) { super(api, mv, access, name, desc); this.transformDescriptor = transformDescriptor; + this.classBeingRedefined = classBeingRedefined; // These are not accessible from the super type (made private), so must save an extra reference. :/ this.argumentTypesRef = Type.getArgumentTypes(desc); this.returnTypeRef = Type.getReturnType(desc); @@ -97,10 +108,14 @@ public void visitEnd() { @Override protected void onMethodEnter() { - createEvent(); + try { + createEvent(); + } catch (IllegalSyntaxException e) { + throw new RuntimeException(e); + } } - private void createEvent() { + private void createEvent() throws IllegalSyntaxException { mv.visitTypeInsn(NEW, transformDescriptor.getEventClassName()); mv.visitInsn(DUP); mv.visitInsn(DUP); @@ -111,17 +126,101 @@ private void createEvent() { if (transformDescriptor.isAllowedFieldType(argumentType)) { mv.visitInsn(DUP); loadArg(param.getIndex()); - writeParameter(param, argumentType); + writeAttribute(param, argumentType); } } } + for (Field field : transformDescriptor.getFields()) { + ReferenceChain refChain = field.resolveReferenceChain(classBeingRedefined).normalize(); + + if (!refChain.isStatic() && Modifier.isStatic(getAccess())) { + throw new IllegalSyntaxException("Illegal non-static reference from a static context: " + field.getExpression()); + } + + if (transformDescriptor.isAllowedFieldType(refChain.getType())) { + mv.visitInsn(DUP); + loadField(refChain); + writeAttribute(field, refChain.getType()); + } + } + mv.visitMethodInsn(INVOKEVIRTUAL, transformDescriptor.getEventClassName(), "begin", "()V", false); //$NON-NLS-1$ //$NON-NLS-2$ eventLocal = newLocal(eventType); mv.visitVarInsn(ASTORE, eventLocal); } - private void writeParameter(Parameter param, Type type) { + private void loadField(ReferenceChain refChain) { + Type type = refChain.getType(); + boolean isStatic = Modifier.isStatic(getAccess()); + Label nullCase = new Label(); + Label continueCase = new Label(); + List localVarVerifications = new ArrayList<>(); + if (!isStatic) { + localVarVerifications.add(Type.getInternalName(classBeingRedefined)); // "this" + } + for (Type argType : argumentTypesRef) { + localVarVerifications.add(TypeUtils.getFrameVerificationType(argType)); + } + + // Assumes the reference chain is normalized already. See ReferenceChain.normalize() + List refs = refChain.getReferences(); + for (int i = 0; i < refs.size(); i++) { + ReferenceChainElement ref = refs.get(i); + + if (ref instanceof ReferenceChainElement.ThisReference) { + mv.visitVarInsn(ALOAD, 0); // load "this" + continue; + } + + if (ref instanceof ReferenceChainElement.FieldReference) { + mv.visitFieldInsn(ref.isStatic() ? GETSTATIC : GETFIELD, ref.getMemberingType().getInternalName(), + ((ReferenceChainElement.FieldReference) ref).getName(), + ref.getReferencedType().getDescriptor()); + + // null check for field references + if (i < refs.size() - 1) { // Skip null check for final reference. Null is acceptable here + mv.visitInsn(DUP); + mv.visitJumpInsn(IFNULL, nullCase); + } + + continue; + } + + if (ref instanceof ReferenceChainElement.QualifiedThisReference) { + int suffix = ((ReferenceChainElement.QualifiedThisReference) ref).getDepth(); + Class c = ref.getMemberingClass(); + while (!ref.getReferencedClass().equals(c)) { + mv.visitFieldInsn(GETFIELD, Type.getType(c).getInternalName(), "this$" + (suffix--), + Type.getType(c.getEnclosingClass()).getDescriptor()); + c = c.getEnclosingClass(); + } + + continue; + } + + throw new UnsupportedOperationException("Unsupported reference chain element type"); + } + + // loaded a value, jump to writing attribute + mv.visitJumpInsn(GOTO, continueCase); + + // null reference on path, load zero value + mv.visitLabel(nullCase); + mv.visitFrame(F_NEW, localVarVerifications.size(), localVarVerifications.toArray(), 4, + new Object[] {eventType.getInternalName(), eventType.getInternalName(), eventType.getInternalName(), + Type.getInternalName(Object.class)}); + mv.visitInsn(POP); + mv.visitInsn(TypeUtils.getConstZeroOpcode(type)); + + // must verify frame for jump targets + mv.visitLabel(continueCase); + mv.visitFrame(F_NEW, localVarVerifications.size(), localVarVerifications.toArray(), 4, + new Object[] {eventType.getInternalName(), eventType.getInternalName(), eventType.getInternalName(), + TypeUtils.getFrameVerificationType(type)}); + } + + private void writeAttribute(Attribute param, Type type) { if (TypeUtils.shouldStringify(param, type)) { TypeUtils.stringify(mv, param, type); type = TypeUtils.STRING_TYPE; @@ -155,7 +254,7 @@ private void emitSettingReturnParam(int opcode, Parameter returnParam) { dupX2(); pop(); } - writeParameter(returnParam, returnTypeRef); + writeAttribute(returnParam, returnTypeRef); } private void commitEvent() { From 5c19b765f3b2f1e69397b26d56a43d3b434f54c4 Mon Sep 17 00:00:00 2001 From: kxu Date: Tue, 17 Dec 2019 15:18:14 -0500 Subject: [PATCH 18/27] add test cases for field capture feature --- .../openjdk/jmc/agent/test/InstrumentMe.java | 61 +++++++- .../jmc/agent/test/jfrprobes_template.xml | 142 ++++++++++++++++++ 2 files changed, 197 insertions(+), 6 deletions(-) diff --git a/core/org.openjdk.jmc.agent/src/test/java/org/openjdk/jmc/agent/test/InstrumentMe.java b/core/org.openjdk.jmc.agent/src/test/java/org/openjdk/jmc/agent/test/InstrumentMe.java index a17715177..bf5d1f75e 100644 --- a/core/org.openjdk.jmc.agent/src/test/java/org/openjdk/jmc/agent/test/InstrumentMe.java +++ b/core/org.openjdk.jmc.agent/src/test/java/org/openjdk/jmc/agent/test/InstrumentMe.java @@ -41,6 +41,25 @@ import org.openjdk.jmc.agent.test.util.TestToolkit; public class InstrumentMe { + public static final String STATIC_STRING_FIELD = "org.openjdk.jmc.agent.test.InstrumentMe.STATIC_STRING_FIELD"; + public static final MyPojo STATIC_OBJECT_FIELD = new MyPojo(); + public static final MyPojo STATIC_NULL_FIELD = null; + + public final String instanceStringField = "org.openjdk.jmc.agent.test.InstrumentMe.instanceStringField"; + + public static class MyPojo { + public String instanceStringField = "org.openjdk.jmc.agent.test.InstrumentMe.MyPojo.instanceStringField"; + public static String STATIC_STRING_FIELD = "org.openjdk.jmc.agent.test.InstrumentMe.MyPojo.STATIC_STRING_FIELD"; + } + + public class MyInnerClass extends InstrumentMe{ + private final String innerClassField = "org.openjdk.jmc.agent.test.InstrumentMe.MyInnerClass.innerClassField"; + + public void instrumentationPoint() { + // no op + } + } + public static void main(String[] args) throws InterruptedException, IOException { Thread runner = new Thread(new Runner(), "InstrumentMe Runner"); runner.setDaemon(true); @@ -92,6 +111,8 @@ private static void runInstance(InstrumentMe instance) throws InterruptedExcepti } catch (RuntimeException e) { System.out.println("#IJFR10. Caught a RuntimeException: " + e.getMessage()); } + instance.printInstanceHelloWorldJFR11(); + instance.printInstanceHelloWorldJFR12(); } private static void runStatic() throws InterruptedException { @@ -111,18 +132,21 @@ private static void runStatic() throws InterruptedException { try { printHelloWorldJFR8(); } catch (RuntimeException e) { - System.out.println("#IJFR8. Caught a RuntimeException: " + e.getMessage()); + System.out.println("#SJFR8. Caught a RuntimeException: " + e.getMessage()); } try { printHelloWorldJFR9(); } catch (RuntimeException e) { - System.out.println("#IJFR9. Caught a RuntimeException: " + e.getMessage()); + System.out.println("#SJFR9. Caught a RuntimeException: " + e.getMessage()); } try { printHelloWorldJFR10(); } catch (RuntimeException e) { - System.out.println("#IJFR10. Caught a RuntimeException: " + e.getMessage()); + System.out.println("#SJFR10. Caught a RuntimeException: " + e.getMessage()); } + printHelloWorldJFR11(); + printHelloWorldJFR12(); + printHelloWorldJFR13(); } private static Collection createGurkList() { @@ -204,19 +228,19 @@ public static void printHelloWorldJFR7() throws InterruptedException { } public static void printHelloWorldJFR8() throws InterruptedException { - System.out.println("#IJFR8. About to throw a RuntimeException"); //$NON-NLS-1$ + System.out.println("#SJFR8. About to throw a RuntimeException"); //$NON-NLS-1$ Thread.sleep(1000); (new ArrayList<>()).get(1); } public static void printHelloWorldJFR9() throws InterruptedException { - System.out.println("#IJFR9. About to throw a RuntimeException"); //$NON-NLS-1$ + System.out.println("#SJFR9. About to throw a RuntimeException"); //$NON-NLS-1$ Thread.sleep(1000); (new ArrayList<>()).get(1); } public static void printHelloWorldJFR10() throws InterruptedException { - System.out.println("#IJFR10. About to throw a RuntimeException"); //$NON-NLS-1$ + System.out.println("#SJFR10. About to throw a RuntimeException"); //$NON-NLS-1$ Thread.sleep(1000); try { @@ -227,6 +251,21 @@ public static void printHelloWorldJFR10() throws InterruptedException { } } + public static void printHelloWorldJFR11() throws InterruptedException { + System.out.println("#SJFR11. Capturing static field 'STATIC_STRING_FIELD'"); //$NON-NLS-1$ + Thread.sleep(1000); + } + + public static void printHelloWorldJFR12() throws InterruptedException { + System.out.println("#SJFR12. Capturing 'STATIC_OBJECT_FIELD.STATIC_STRING_FIELD' and 'STATIC_OBJECT_FIELD.instanceStringField'"); //$NON-NLS-1$ + Thread.sleep(1000); + } + + public static void printHelloWorldJFR13() throws InterruptedException { + System.out.println("#SJFR13. Capturing 'STATIC_NULL_FIELD.STATIC_STRING_FIELD' and 'STATIC_NULL_FIELD.instanceStringField'"); //$NON-NLS-1$ + Thread.sleep(1000); + } + public void printInstanceHelloWorld1() throws InterruptedException { System.out.println("#I1. Hello World!"); //$NON-NLS-1$ Thread.sleep(1000); @@ -320,4 +359,14 @@ public void printInstanceHelloWorldJFR10() throws InterruptedException { throw e; } } + + public void printInstanceHelloWorldJFR11() throws InterruptedException { + System.out.println("#IJFR11. Capturing instance field 'instanceStringField'"); //$NON-NLS-1$ + Thread.sleep(1000); + } + + public void printInstanceHelloWorldJFR12() throws InterruptedException { + System.out.println("#IJFR12. Capturing fields from nested class 'InstrumentMe.MyInnerClass'"); //$NON-NLS-1$ + new MyInnerClass().instrumentationPoint(); + } } diff --git a/core/org.openjdk.jmc.agent/src/test/resources/org/openjdk/jmc/agent/test/jfrprobes_template.xml b/core/org.openjdk.jmc.agent/src/test/resources/org/openjdk/jmc/agent/test/jfrprobes_template.xml index 376936108..4239a1f92 100644 --- a/core/org.openjdk.jmc.agent/src/test/resources/org/openjdk/jmc/agent/test/jfrprobes_template.xml +++ b/core/org.openjdk.jmc.agent/src/test/resources/org/openjdk/jmc/agent/test/jfrprobes_template.xml @@ -87,6 +87,74 @@ WRAP + + JFR Hello World Event 11 %TEST_NAME% + Defined in the xml file and added by the agent. Should record the value of static reference 'STATIC_STRING_FIELD' + demo/jfrhelloworldevent11 + true + org.openjdk.jmc.agent.test.InstrumentMe + + printHelloWorldJFR11 + ()V + + + 'STATIC_STRING_FIELD' + Capturing static field with simple field name + STATIC_STRING_FIELD + + + 'InstrumentMe.STATIC_STRING_FIELD' + Capturing static field with class name prefixed field name + InstrumentMe.STATIC_STRING_FIELD + + + 'org.openjdk.jmc.agent.test.InstrumentMe.STATIC_STRING_FIELD' + Capturing static field with full qualified class prefixed name + org.openjdk.jmc.agent.test.InstrumentMe.STATIC_STRING_FIELD + + + + JFR Hello World Event 12 %TEST_NAME% + Defined in the xml file and added by the agent. Should record the values of 'STATIC_OBJECT_FIELD.STATIC_STRING_FIELD' and 'STATIC_OBJECT_FIELD.instanceStringField' + demo/jfrhelloworldevent12 + true + org.openjdk.jmc.agent.test.InstrumentMe + + printHelloWorldJFR12 + ()V + + + 'STATIC_OBJECT_FIELD.STATIC_STRING_FIELD' + Capturing static field on a object reference + STATIC_OBJECT_FIELD.STATIC_STRING_FIELD + + + 'STATIC_OBJECT_FIELD.instanceStringField' + Capturing non-static field on a object reference + STATIC_OBJECT_FIELD.instanceStringField + + + + JFR Hello World Event 13 %TEST_NAME% + Defined in the xml file and added by the agent. Should record the values of static 'STATIC_NULL_FIELD.STATIC_STRING_FIELD' and 'STATIC_NULL_FIELD.instanceStringField' + demo/jfrhelloworldevent13 + true + org.openjdk.jmc.agent.test.InstrumentMe + + printHelloWorldJFR11 + ()V + + + 'STATIC_NULL_FIELD.STATIC_STRING_FIELD' + Capturing static field on a null object reference + STATIC_NULL_FIELD.STATIC_STRING_FIELD + + + 'STATIC_NULL_FIELD.instanceStringField' + Capturing non-static field on a null object reference + STATIC_NULL_FIELD.instanceStringField + + JFR Hello World Instance Event 1 %TEST_NAME% Defined in the xml file and added by the agent. @@ -240,5 +308,79 @@ true + + JFR Hello World Instance Event 11 %TEST_NAME% + Defined in the xml file and added by the agent. Should record the value of instance reference 'instanceStringField' + demo/jfrhelloworldeventI11 + true + org.openjdk.jmc.agent.test.InstrumentMe + + printInstanceHelloWorldJFR11 + ()V + + + 'instanceStringField' + Capturing instance field with simple field name + instanceStringField + + + 'this.instanceStringField' + Capturing instance field with "this" prefixed field name + this.instanceStringField + + + 'InstrumentMe.this.instanceStringField' + Capturing instance field with qualified "this" prefixed field name + InstrumentMe.this.instanceStringField + + + + JFR Hello World Instance Event 12 %TEST_NAME% + Defined in the xml file and added by the agent. Should record the values of various references + demo/jfrhelloworldeventI12 + true + org.openjdk.jmc.agent.test.InstrumentMe$MyInnerClass + + instrumentationPoint + ()V + + + 'innerClassField' + Capturing inner class field with simple field name + innerClassField + + + 'this.innerClassField' + Capturing inner class field with "this" prefixed field name + this.innerClassField + + + + 'instanceStringField' + Capturing outer class field with simple field name + instanceStringField + + + 'InstrumentMe.this.instanceStringField' + Capturing outer class field with qualified "this" prefixed field name + InstrumentMe.this.instanceStringField + + + 'super.instanceStringField' + Capturing super class field with "super" prefixed field name + super.instanceStringField + + + + 'STATIC_STRING_FIELD' + Capturing outer class field with simple field name + STATIC_STRING_FIELD + + + 'InstrumentMe.STATIC_STRING_FIELD' + Capturing outer class field with class name prefixed field name + InstrumentMe.STATIC_STRING_FIELD + + \ No newline at end of file From a3e0c61e08794d84a2c5020223b8cf008b1c4c25 Mon Sep 17 00:00:00 2001 From: kxu Date: Tue, 17 Dec 2019 16:41:25 -0500 Subject: [PATCH 19/27] error on private member access between nestmates --- .../jmc/agent/util/expression/ExpressionResolver.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java index 567786128..050437a37 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java @@ -263,6 +263,12 @@ private void enterFieldReferenceState(Class memberingClass, Field field, bool field.getDeclaringClass().getName())); } + if (!caller.equals(memberingClass) && Modifier.isPrivate(field.getModifiers()) && AccessUtils + .areNestMates(caller, memberingClass)) { + enterIllegalState( + new UnsupportedOperationException("Private member access between nestmates is not supported")); + } + referenceChain.append(new ReferenceChainElement.FieldReference(memberingClass, field)); if (!iterator.hasNext()) { // accepted state From caaa7e7ef1adb64f1f1451b514e14c00c5a5c80d Mon Sep 17 00:00:00 2001 From: kxu Date: Tue, 17 Dec 2019 17:12:10 -0500 Subject: [PATCH 20/27] resolve conflicts after merging from master --- .../org/openjdk/jmc/agent/ReturnValue.java | 18 +++++++++++++++-- .../jmc/agent/TransformDescriptor.java | 2 +- .../agent/impl/DefaultTransformRegistry.java | 10 ++++++++-- .../jmc/agent/jfr/impl/JFRMethodAdvisor.java | 20 ++----------------- .../jfrnext/impl/JFRNextMethodAdvisor.java | 20 ++----------------- .../openjdk/jmc/agent/test/InstrumentMe.java | 2 +- 6 files changed, 30 insertions(+), 42 deletions(-) diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/ReturnValue.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/ReturnValue.java index 1d1da20ab..6bf6eb9f5 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/ReturnValue.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/ReturnValue.java @@ -37,16 +37,20 @@ /** * Metadata for a return value to be logged by the agent. */ -public final class ReturnValue { +public final class ReturnValue implements Attribute { private final String name; private final String fieldName; private final String description; private final String contentType; + private final String relationKey; + private final String converterClassName; - public ReturnValue(String name, String description, String contentType) { + public ReturnValue(String name, String description, String contentType, String relationKey, String converterClassName) { this.name = name == null ? "Return Value" : name; this.description = description; this.contentType = contentType; + this.relationKey = relationKey; + this.converterClassName = converterClassName; this.fieldName = "field" + TypeUtils.deriveIdentifierPart(this.name); //$NON-NLS-1$ } @@ -62,6 +66,16 @@ public String getContentType() { return contentType; } + @Override + public String getRelationKey() { + return relationKey; + } + + @Override + public String getConverterClassName() { + return converterClassName; + } + public String getFieldName() { return fieldName; } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/TransformDescriptor.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/TransformDescriptor.java index 62fa6b580..7a62a90e0 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/TransformDescriptor.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/TransformDescriptor.java @@ -105,7 +105,7 @@ protected String getTransformationAttribute(String attribute) { * @return the instantiated {@link TransformDescriptor}. */ public static TransformDescriptor create( - String id, String internalName, Method method, Map values, List parameters, ReturnValue returnValue) { + String id, String internalName, Method method, Map values, List parameters, ReturnValue returnValue, List fields) { return new JFRTransformDescriptor(id, internalName, method, values, parameters, returnValue, fields); } diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/impl/DefaultTransformRegistry.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/impl/DefaultTransformRegistry.java index 02b9781c0..ff451c5d0 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/impl/DefaultTransformRegistry.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/impl/DefaultTransformRegistry.java @@ -185,7 +185,7 @@ private static TransformDescriptor parseTransformData( streamReader.next(); } transfer(globalDefaults, values); - return TransformDescriptor.create(id, getInternalName(values.get("class")), method, values, parameters, returnValue[0], fields); //$NON-NLS-1$ + return TransformDescriptor.create(id, TypeUtils.getInternalName(values.get("class")), method, values, parameters, returnValue[0], fields); //$NON-NLS-1$ } private static void transfer(HashMap globalDefaults, Map values) { @@ -314,6 +314,8 @@ private static ReturnValue parseReturnValue(XMLStreamReader streamReader) throws String name = null; String description = null; String contentType = null; + String relationKey = null; + String converterClassName = null; while (streamReader.hasNext()) { if (streamReader.isStartElement()) { @@ -330,6 +332,10 @@ private static ReturnValue parseReturnValue(XMLStreamReader streamReader) throws description = value; } else if ("contenttype".equals(key)) { //$NON-NLS-1$ contentType = value; + } else if ("relationkey".equals(key)) { //$NON-NLS-1$ + relationKey = value; + } else if ("converter".equals(key)) { //$NON-NLS-1$ + converterClassName = value; } } } else if (streamReader.isEndElement()) { @@ -339,7 +345,7 @@ private static ReturnValue parseReturnValue(XMLStreamReader streamReader) throws } streamReader.next(); } - return new ReturnValue(name, description, contentType); + return new ReturnValue(name, description, contentType, relationKey, converterClassName); } private static Method parseMethod(XMLStreamReader streamReader, List parameters, ReturnValue[] returnValue) diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRMethodAdvisor.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRMethodAdvisor.java index 3c1b27667..d44391dad 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRMethodAdvisor.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRMethodAdvisor.java @@ -126,7 +126,7 @@ private void createEvent() throws IllegalSyntaxException { if (transformDescriptor.isAllowedFieldType(argumentType)) { mv.visitInsn(DUP); loadArg(param.getIndex()); - writeParameter(param, argumentType); + writeAttribute(param, argumentType); } } @@ -220,14 +220,6 @@ private void loadField(ReferenceChain refChain) { } private void writeAttribute(Attribute param, Type type) { - if (TypeUtils.shouldStringify(param, type)) { - TypeUtils.stringify(mv, param, type); - type = TypeUtils.STRING_TYPE; - } - putField(Type.getObjectType(transformDescriptor.getEventClassName()), param.getFieldName(), type); - } - - private void writeParameter(Parameter param, Type type) { if (TypeUtils.shouldStringify(type)) { TypeUtils.stringify(mv); type = TypeUtils.STRING_TYPE; @@ -235,14 +227,6 @@ private void writeParameter(Parameter param, Type type) { putField(Type.getObjectType(transformDescriptor.getEventClassName()), param.getFieldName(), type); } - private void writeReturnValue(ReturnValue returnValue, Type type) { - if (TypeUtils.shouldStringify(type)) { - TypeUtils.stringify(mv); - type = TypeUtils.STRING_TYPE; - } - putField(Type.getObjectType(transformDescriptor.getEventClassName()), returnValue.getFieldName(), type); - } - @Override protected void onMethodExit(int opcode) { if (opcode == ATHROW && !shouldInstrumentThrow) { @@ -269,7 +253,7 @@ private void emitSettingReturnParam(int opcode, ReturnValue returnValue) { dupX2(); pop(); } - writeReturnValue(returnValue, returnTypeRef); + writeAttribute(returnValue, returnTypeRef); } private void commitEvent() { diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java index 1ed1d07f3..6b95a1b3e 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java @@ -126,7 +126,7 @@ private void createEvent() throws IllegalSyntaxException { if (transformDescriptor.isAllowedFieldType(argumentType)) { mv.visitInsn(DUP); loadArg(param.getIndex()); - writeParameter(param, argumentType); + writeAttribute(param, argumentType); } } @@ -220,14 +220,6 @@ private void loadField(ReferenceChain refChain) { } private void writeAttribute(Attribute param, Type type) { - if (TypeUtils.shouldStringify(param, type)) { - TypeUtils.stringify(mv, param, type); - type = TypeUtils.STRING_TYPE; - } - putField(Type.getObjectType(transformDescriptor.getEventClassName()), param.getFieldName(), type); - } - - private void writeParameter(Parameter param, Type type) { if (TypeUtils.shouldStringify(type)) { TypeUtils.stringify(mv); type = TypeUtils.STRING_TYPE; @@ -235,14 +227,6 @@ private void writeParameter(Parameter param, Type type) { putField(Type.getObjectType(transformDescriptor.getEventClassName()), param.getFieldName(), type); } - private void writeReturnValue(ReturnValue returnValue, Type type) { - if (TypeUtils.shouldStringify(type)) { - TypeUtils.stringify(mv); - type = TypeUtils.STRING_TYPE; - } - putField(Type.getObjectType(transformDescriptor.getEventClassName()), returnValue.getFieldName(), type); - } - @Override protected void onMethodExit(int opcode) { if (opcode == ATHROW && !shouldInstrumentThrow) { @@ -269,7 +253,7 @@ private void emitSettingReturnParam(int opcode, ReturnValue returnValue) { dupX2(); pop(); } - writeReturnValue(returnValue, returnTypeRef); + writeAttribute(returnValue, returnTypeRef); } private void commitEvent() { diff --git a/core/org.openjdk.jmc.agent/src/test/java/org/openjdk/jmc/agent/test/InstrumentMe.java b/core/org.openjdk.jmc.agent/src/test/java/org/openjdk/jmc/agent/test/InstrumentMe.java index bf5d1f75e..1397879a3 100644 --- a/core/org.openjdk.jmc.agent/src/test/java/org/openjdk/jmc/agent/test/InstrumentMe.java +++ b/core/org.openjdk.jmc.agent/src/test/java/org/openjdk/jmc/agent/test/InstrumentMe.java @@ -246,7 +246,7 @@ public static void printHelloWorldJFR10() throws InterruptedException { try { (new ArrayList<>()).get(1); } catch (RuntimeException e) { - System.out.println("#IJFR10. Caught a RuntimeException: " + e.getMessage()); //$NON-NLS-1$ + System.out.println("#SJFR10. Caught a RuntimeException: " + e.getMessage()); //$NON-NLS-1$ throw e; } } From 9782c758a8f3c2fcf8e9d1a4e96ecc60aa213303 Mon Sep 17 00:00:00 2001 From: kxu Date: Thu, 19 Dec 2019 10:14:48 -0500 Subject: [PATCH 21/27] update license header year --- .../src/main/java/org/openjdk/jmc/agent/Attribute.java | 2 +- .../src/main/java/org/openjdk/jmc/agent/Field.java | 2 +- .../src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java | 2 +- .../java/org/openjdk/jmc/agent/util/InspectionClassLoader.java | 2 +- .../src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java | 2 -- .../openjdk/jmc/agent/util/expression/ExpressionResolver.java | 2 +- .../jmc/agent/util/expression/IllegalSyntaxException.java | 2 +- .../org/openjdk/jmc/agent/util/expression/ReferenceChain.java | 2 +- .../jmc/agent/util/expression/ReferenceChainElement.java | 2 +- 9 files changed, 8 insertions(+), 10 deletions(-) diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Attribute.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Attribute.java index 8bf3083c5..3c445e3a3 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Attribute.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Attribute.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Field.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Field.java index 44ae24188..16a641157 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Field.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Field.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java index 28ad2264f..b98cde31c 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java index d95496974..32498692e 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java index 2a1b639ba..c009eccc8 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java @@ -44,8 +44,6 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.openjdk.jmc.agent.Agent; -import org.openjdk.jmc.agent.Attribute; -import org.openjdk.jmc.agent.Parameter; import org.openjdk.jmc.agent.jfr.impl.JFRUtils; /** diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java index 050437a37..ec8d4e071 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/IllegalSyntaxException.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/IllegalSyntaxException.java index a0e2c7b97..fe98a20e5 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/IllegalSyntaxException.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/IllegalSyntaxException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java index f6e81b111..549f8dfb8 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChainElement.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChainElement.java index d546bc137..44789dff6 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChainElement.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChainElement.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * From c35788f2fd736996a30f59fb1fbb6f4353840d5f Mon Sep 17 00:00:00 2001 From: kxu Date: Thu, 19 Dec 2019 11:14:34 -0500 Subject: [PATCH 22/27] add javadoc --- .../openjdk/jmc/agent/util/AccessUtils.java | 109 ++++++++++++++++-- .../jmc/agent/util/InspectionClassLoader.java | 4 +- .../org/openjdk/jmc/agent/util/TypeUtils.java | 30 +++++ .../agent/util/expression/ReferenceChain.java | 34 +++++- 4 files changed, 163 insertions(+), 14 deletions(-) diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java index b98cde31c..1f7c92d1a 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java @@ -42,7 +42,22 @@ import java.util.List; import java.util.Queue; -public class AccessUtils { +/** + * Helper methods for checking accessibility, implied as modifiers, from various contexts. + */ +public final class AccessUtils { + private AccessUtils() { + throw new UnsupportedOperationException("Toolkit!"); //$NON-NLS-1$ + } + + /** + * Like Class.getDeclaredField, but also gets fields declared by ancestors and interfaces. + * + * @param clazz the class to lookup from + * @param name the name of the field + * @return the {@code Field} object for the specified field in this class + * @throws NoSuchFieldException if a field with the specified name is not found. + */ public static Field getFieldOnHierarchy(Class clazz, String name) throws NoSuchFieldException { Queue> q = new LinkedList<>(); q.add(clazz); @@ -65,6 +80,14 @@ public static Field getFieldOnHierarchy(Class clazz, String name) throws NoSu throw new NoSuchFieldException(String.format("cannot find field %s in class %s", name, clazz.getName())); } + /** + * Checks whether a field can be accessed from a caller context. + * + * @param targetClass the class being referenced + * @param field the field being accessed + * @param currentClass the caller class + * @return whether the field is accessible from given context + */ public static boolean isAccessible(Class targetClass, Field field, Class currentClass) { int modifiers = field.getModifiers(); @@ -76,6 +99,15 @@ public static boolean isAccessible(Class targetClass, Field field, Class c return verifyMemberAccess(targetClass, memberClass, currentClass, modifiers); } + /** + * Checks whether the field/method/inner class modifier allows access from a caller context + * + * @param targetClass the class being referenced + * @param memberClass the class declaring the field/method/inner class + * @param currentClass the caller class + * @param modifiers member access modifiers in bit flags as a integer + * @return + */ public static boolean verifyMemberAccess(Class targetClass, Class memberClass, Class currentClass, int modifiers) { if (currentClass == memberClass) { @@ -150,6 +182,15 @@ public static boolean verifyMemberAccess(Class targetClass, Class memberCl return true; } + /** + * Check whether the module has the class exported for the caller to access. + * + * For Pre-9 Java runtime, this function always returns true. + * + * @param targetClass the class being accessed + * @param callerClass the caller class + * @return whether the class is accessible + */ public static boolean verifyModuleAccess(Class targetClass, Class callerClass) { String version = System.getProperty("java.version"); if (Integer.parseInt(version.substring(0, version.indexOf("."))) < 9) { @@ -180,23 +221,48 @@ public static boolean verifyModuleAccess(Class targetClass, Class callerCl } } - // polyfill for Class.getPackageName(Class) + /** + * polyfill for Class.getPackageName(Class) for pre-9 Java. + * + * @param clazz the class to lookup the package name against + * @return the name of the package containing the class + */ public static String getPackageName(Class clazz) { return clazz.getPackage().getName(); } - // polyfill for Reflection.getClassAccessFlags(Class) + /** + * Polyfill for Reflection.getClassAccessFlags(Class) as + * jdk.internal.reflect.Reflection is not exported. + * + * @param c the class being inspected + * @return the access flags written to the class file + */ public static int getClassAccessFlags(Class c) { return c.getModifiers(); } + /** + * Check whether the two classes exist in the same package + * + * @param lhs the first class + * @param rhs the second class + * @return whether the given classes exist in the same package + */ public static boolean isSameClassPackage(Class lhs, Class rhs) { if (lhs.getClassLoader() != rhs.getClassLoader()) return false; return getPackageName(lhs).equals(getPackageName(rhs)); } + /** + * Check whether a class is a subclass of the other + * + * @param queryClass the subclass + * @param ofClass the superclass + * @return whether it's a subclass-superclass relationship + */ public static boolean isSubclassOf(Class queryClass, Class ofClass) { while (queryClass != null) { if (queryClass == ofClass) { @@ -207,9 +273,15 @@ public static boolean isSubclassOf(Class queryClass, Class ofClass) { return false; } - // Polyfill Class.getNestMembers() for pre-11 runtime. - // This function does not fully respect the definition of nesting from JVM's perspective. It's only used for - // validating access. + /** + * Polyfill Class.getNestMembers() for pre-11 runtime. + * + * This function does not fully respect the definition of nesting from JVM's perspective. It's only used for + * validating access. + * + * @param clazz the class to inspect against + * @return an array of all nest members + */ public static Class[] getNestMembers(Class clazz) { List> classes = new ArrayList<>(); classes.add(getNestHost(clazz)); @@ -222,16 +294,29 @@ public static Class[] getNestMembers(Class clazz) { return classes.toArray(new Class[0]); } - // Polyfill Class.isNestMateOf() for pre-11 runtime - // This function does not fully respect the definition of nesting from JVM's perspective. It's only used for - // validating access. + /** + * Polyfill Class.isNestMateOf() for pre-11 runtime. + * + * This function does not fully respect the definition of nesting from JVM's perspective. It's only used for + * validating access. + * + * @param lhs the first class + * @param rhs the second class + * @return whether the given classes are nestmates + */ public static boolean areNestMates(Class lhs, Class rhs) { return getNestHost(lhs).equals(getNestHost(rhs)); } - // Polyfill Class.getNestHost() for pre-11 runtime - // This function does not fully respect the definition of nesting from JVM's perspective. It's only used for - // validating access. + /** + * Polyfill Class.getNestHost() for pre-11 runtime. + * + * This function does not fully respect the definition of nesting from JVM's perspective. It's only used for + * validating access. + * + * @param clazz the class the inspect against + * @return the nesthost of the class + */ public static Class getNestHost(Class clazz) { // array types, primitive types, and void belong to the nests consisting only of theme, and are the nest hosts. if (clazz.isArray()) { diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java index 32498692e..54821ec30 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java @@ -36,7 +36,9 @@ import java.io.IOException; import java.io.InputStream; -// One-time use loader for reflective class inspection. Don't keep static reference to one of these. +/** + * One-time use loader for reflective class inspection. Don't keep static reference to one of these. + */ public class InspectionClassLoader extends ClassLoader { public InspectionClassLoader(ClassLoader parent) { diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java index c009eccc8..b659907e7 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java @@ -235,14 +235,37 @@ public static String parameterize(String className) { return "L" + className + ";"; //$NON-NLS-1$ //$NON-NLS-2$ } + /** + * Converts a canonical class name into the internal form (binary name). + * + * eg. com.company.project converts into com/company/project + * + * @param className the canonical class name + * @return the internal form + */ public static String getInternalName(String className) { return className.replace('.', '/'); } + /** + * Converts a internal class name (binary name) into the canonical form. + * + * ie. com/company/project converts into com.company.project + * + * @param binaryName the internal class name + * @return in canonical form + */ public static String getCanonicalName(String binaryName) { return binaryName.replace('/', '.'); } + /** + * Returns the constant loading instruction that pushes a zero value of the given type onto the operand stack. A + * null reference is pushed if the given type is an object or an array. + * + * @param type the type of the operand + * @return the instruction + */ public static int getConstZeroOpcode(Type type) { switch (type.getSort()) { case Type.BOOLEAN: @@ -268,6 +291,13 @@ public static int getConstZeroOpcode(Type type) { } } + /** + * Returns a array element for ASM's MethodVisitor.visitFrame() method used for frame verification of + * a given type. + * + * @param type the type of the element on the operand stack or in the local variable table + * @return a array element for MethodVisitor.visitFrame()'s parameter + */ public static Object getFrameVerificationType(Type type) { switch (type.getSort()) { case Type.BOOLEAN: diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java index 549f8dfb8..cf0c42c10 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java @@ -37,23 +37,45 @@ import java.util.LinkedList; import java.util.List; -public class ReferenceChain { +/** + * A ReferenceChain instance presents a field reference expression. + * + * eg. OuterClass.this.field.STATIC_FIELD is a reference chain consisting elements: a qualified-this + * reference and two field reference (field and STATIC_FIELD). + * + */ +public final class ReferenceChain { private final Class callerClass; private final List references; + /** + * @param callerClass the caller class making this reference + */ public ReferenceChain(Class callerClass) { this.callerClass = callerClass; this.references = new LinkedList<>(); } + /** + * @return the caller class making this reference + */ public Class getCallerClass() { return callerClass; } + /** + * @return all elements on the reference chain + */ public List getReferences() { return references; } + /** + * Reduces the reference chain to prepend "this" or qualified-this references if necessary, and short-circuits on + * static references + * + * @return the normalized reference chain + */ public ReferenceChain normalize() { List oldRefs = getReferences(); List newRefs = new LinkedList<>(); @@ -82,6 +104,9 @@ public ReferenceChain normalize() { return ret; } + /** + * @return the type of the last reference element + */ public Type getType() { if (references.isEmpty()) { return Type.getType(callerClass); @@ -89,10 +114,17 @@ public Type getType() { return references.get(references.size() - 1).getReferencedType(); } + /** + * Appends a ReferenceChainElement to the chain + * @param ref ReferenceChainElement to be appended + */ public void append(ReferenceChainElement ref) { references.add(ref); } + /** + * @return whether the reference is valid from a static context + */ public boolean isStatic() { if (references.isEmpty()) { return false; From c78ce9470aebb66294b64cec89947db55a3a7276 Mon Sep 17 00:00:00 2001 From: kxu Date: Thu, 19 Dec 2019 14:47:54 -0500 Subject: [PATCH 23/27] lazy-load class with InspectionClassLoader --- .../java/org/openjdk/jmc/agent/Transformer.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java index 244e3aa20..81e707894 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java @@ -68,11 +68,14 @@ public byte[] transform( registry.storeClassPreInstrumentation(className, classfileBuffer); byte[] instrumentedClassfileBuffer = registry.isRevertIntrumentation() ? registry.getClassPreInstrumentation(className) : classfileBuffer; - try { - // Don't reuse this class loader instance. We want the loader and its class to unload after we're done. - classBeingRedefined = new InspectionClassLoader(loader).loadClass(TypeUtils.getCanonicalName(className)); - } catch (ClassNotFoundException e) { - throw new IllegalStateException(e); // This should not happen + if (classBeingRedefined == null) { + try { + // Don't reuse this class loader instance. We want the loader and its class to unload after we're done. + classBeingRedefined = new InspectionClassLoader(loader) + .loadClass(TypeUtils.getCanonicalName(className)); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e); // This should not happen + } } return doTransforms(registry.getTransformData(className), instrumentedClassfileBuffer, loader, classBeingRedefined, protectionDomain); } From 4bb163b962a17b5c912d2615004b3a2d962186e2 Mon Sep 17 00:00:00 2001 From: kxu Date: Fri, 10 Jan 2020 13:32:53 -0500 Subject: [PATCH 24/27] update TestSetTransforms with new changes --- .../org/openjdk/jmc/agent/test/TestSetTransforms.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/org.openjdk.jmc.agent/src/test/java/org/openjdk/jmc/agent/test/TestSetTransforms.java b/core/org.openjdk.jmc.agent/src/test/java/org/openjdk/jmc/agent/test/TestSetTransforms.java index bcf9bdf29..bf94a7f06 100644 --- a/core/org.openjdk.jmc.agent/src/test/java/org/openjdk/jmc/agent/test/TestSetTransforms.java +++ b/core/org.openjdk.jmc.agent/src/test/java/org/openjdk/jmc/agent/test/TestSetTransforms.java @@ -51,6 +51,7 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.commons.AdviceAdapter; +import org.openjdk.jmc.agent.Field; import org.openjdk.jmc.agent.Method; import org.openjdk.jmc.agent.Parameter; import org.openjdk.jmc.agent.ReturnValue; @@ -118,13 +119,13 @@ public void testSetTransforms() throws Exception { private void injectFailingEvent() throws Exception { Method method = new Method(METHOD_NAME, METHOD_DESCRIPTOR); - Map attributes = new HashMap(); + Map attributes = new HashMap<>(); attributes.put("path", EVENT_PATH); attributes.put("name", EVENT_NAME); attributes.put("description", EVENT_DESCRIPTION); - ReturnValue retVal = new ReturnValue(null, "", null); - JFRTransformDescriptor eventTd = new JFRTransformDescriptor(EVENT_ID, - EVENT_CLASS_NAME.replace(".", "/"), method, attributes, new ArrayList(), retVal); + ReturnValue retVal = new ReturnValue(null, "", null, null, null); + JFRTransformDescriptor eventTd = new JFRTransformDescriptor(EVENT_ID, TypeUtils.getInternalName(EVENT_CLASS_NAME), + method, attributes, new ArrayList(), retVal, new ArrayList()); ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM5, classWriter) { @@ -150,7 +151,7 @@ protected void onMethodExit(int opcode) { } }; - byte[] eventClass = JFRNextEventClassGenerator.generateEventClass(eventTd); + byte[] eventClass = JFRNextEventClassGenerator.generateEventClass(eventTd, InstrumentMe.class); ClassReader reader = new ClassReader(eventClass); reader.accept(classVisitor, 0); byte[] modifiedEvent = classWriter.toByteArray(); From 09abbb99901dd190d4e9c807976f5dd0cd183862 Mon Sep 17 00:00:00 2001 From: kxu Date: Mon, 13 Jan 2020 11:48:26 -0500 Subject: [PATCH 25/27] remove unused function and to re-trigger GitHub actions --- .../expression/ReferenceChainElement.java | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChainElement.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChainElement.java index 44789dff6..81bb2a8c1 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChainElement.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChainElement.java @@ -52,8 +52,6 @@ public interface ReferenceChainElement { // the type of the class/interface which the reference is to Type getReferencedType(); - // if the reference is allow from a caller - boolean isAccessibleFrom(Class caller); // if this reference is static boolean isStatic(); @@ -94,11 +92,6 @@ public Type getReferencedType() { return Type.getType(getReferencedClass()); } - @Override - public boolean isAccessibleFrom(Class caller) { - return AccessUtils.isAccessible(memberingClass, field, caller); - } - @Override public boolean isStatic() { return Modifier.isStatic(field.getModifiers()); @@ -147,11 +140,6 @@ public Type getReferencedType() { return Type.getType(getReferencedClass()); } - @Override - public boolean isAccessibleFrom(Class caller) { - return clazz.equals(caller); - } - @Override public boolean isStatic() { return false; @@ -209,18 +197,6 @@ public Type getReferencedType() { return Type.getType(getReferencedClass()); } - @Override - public boolean isAccessibleFrom(Class caller) { - Class c = caller; - while (c != null) { - if (c.equals(innerClass)) { - return true; - } - c = c.getEnclosingClass(); - } - return false; - } - @Override public boolean isStatic() { return false; From 10e48d582fedcf38dfaa62cc5432ff3bea6ace3a Mon Sep 17 00:00:00 2001 From: kxu Date: Tue, 14 Jan 2020 12:12:54 -0500 Subject: [PATCH 26/27] update license header year values --- .../src/main/java/org/openjdk/jmc/agent/Attribute.java | 2 +- .../src/main/java/org/openjdk/jmc/agent/Field.java | 2 +- .../src/main/java/org/openjdk/jmc/agent/Parameter.java | 2 +- .../src/main/java/org/openjdk/jmc/agent/ReturnValue.java | 2 +- .../main/java/org/openjdk/jmc/agent/TransformDescriptor.java | 2 +- .../src/main/java/org/openjdk/jmc/agent/Transformer.java | 2 +- .../org/openjdk/jmc/agent/impl/DefaultTransformRegistry.java | 2 +- .../org/openjdk/jmc/agent/jfr/JFRTransformDescriptor.java | 2 +- .../java/org/openjdk/jmc/agent/jfr/impl/JFRClassVisitor.java | 2 +- .../openjdk/jmc/agent/jfr/impl/JFREventClassGenerator.java | 2 +- .../java/org/openjdk/jmc/agent/jfr/impl/JFRMethodAdvisor.java | 2 +- .../openjdk/jmc/agent/jfrnext/impl/JFRNextClassVisitor.java | 2 +- .../jmc/agent/jfrnext/impl/JFRNextEventClassGenerator.java | 2 +- .../openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java | 2 +- .../src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java | 2 +- .../org/openjdk/jmc/agent/util/InspectionClassLoader.java | 2 +- .../src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java | 2 +- .../openjdk/jmc/agent/util/expression/ExpressionResolver.java | 2 +- .../jmc/agent/util/expression/IllegalSyntaxException.java | 2 +- .../org/openjdk/jmc/agent/util/expression/ReferenceChain.java | 2 +- .../jmc/agent/util/expression/ReferenceChainElement.java | 2 +- .../test/java/org/openjdk/jmc/agent/test/InstrumentMe.java | 2 +- .../java/org/openjdk/jmc/agent/test/TestSetTransforms.java | 4 ++-- .../org/openjdk/jmc/agent/test/jfrprobes_template.xml | 2 +- 24 files changed, 25 insertions(+), 25 deletions(-) diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Attribute.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Attribute.java index 3c445e3a3..82b0bf7c3 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Attribute.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Attribute.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Field.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Field.java index 16a641157..9be365b53 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Field.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Field.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Parameter.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Parameter.java index 82b188a21..e613c8683 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Parameter.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Parameter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/ReturnValue.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/ReturnValue.java index 6bf6eb9f5..1f8d15b7b 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/ReturnValue.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/ReturnValue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/TransformDescriptor.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/TransformDescriptor.java index 7a62a90e0..32f2e0e0d 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/TransformDescriptor.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/TransformDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java index 67020d52d..7f7fdebe4 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Transformer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/impl/DefaultTransformRegistry.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/impl/DefaultTransformRegistry.java index b26e776ad..6a3e9966d 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/impl/DefaultTransformRegistry.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/impl/DefaultTransformRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/JFRTransformDescriptor.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/JFRTransformDescriptor.java index 2971d7c37..f320abd0a 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/JFRTransformDescriptor.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/JFRTransformDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRClassVisitor.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRClassVisitor.java index a81262c04..0ec1f49f5 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRClassVisitor.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRClassVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFREventClassGenerator.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFREventClassGenerator.java index a892b77c0..8b45363b4 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFREventClassGenerator.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFREventClassGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRMethodAdvisor.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRMethodAdvisor.java index d44391dad..4a272c2c7 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRMethodAdvisor.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRMethodAdvisor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextClassVisitor.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextClassVisitor.java index e769a30bf..5e2d7d62a 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextClassVisitor.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextClassVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextEventClassGenerator.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextEventClassGenerator.java index c2a60ec96..4c8f6de4c 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextEventClassGenerator.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextEventClassGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java index 6b95a1b3e..5b46e1b42 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextMethodAdvisor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java index 1f7c92d1a..4c268a6cc 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/AccessUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java index 54821ec30..e56464fd9 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/InspectionClassLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java index b659907e7..e2d95d2d6 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java index ec8d4e071..ddc594cd8 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ExpressionResolver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/IllegalSyntaxException.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/IllegalSyntaxException.java index fe98a20e5..d88719bdd 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/IllegalSyntaxException.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/IllegalSyntaxException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java index cf0c42c10..b78532faf 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChain.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChainElement.java b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChainElement.java index 81bb2a8c1..103efa07c 100644 --- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChainElement.java +++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/expression/ReferenceChainElement.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/test/java/org/openjdk/jmc/agent/test/InstrumentMe.java b/core/org.openjdk.jmc.agent/src/test/java/org/openjdk/jmc/agent/test/InstrumentMe.java index 1397879a3..e1678eaeb 100644 --- a/core/org.openjdk.jmc.agent/src/test/java/org/openjdk/jmc/agent/test/InstrumentMe.java +++ b/core/org.openjdk.jmc.agent/src/test/java/org/openjdk/jmc/agent/test/InstrumentMe.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/test/java/org/openjdk/jmc/agent/test/TestSetTransforms.java b/core/org.openjdk.jmc.agent/src/test/java/org/openjdk/jmc/agent/test/TestSetTransforms.java index bf94a7f06..f6d6264ae 100644 --- a/core/org.openjdk.jmc.agent/src/test/java/org/openjdk/jmc/agent/test/TestSetTransforms.java +++ b/core/org.openjdk.jmc.agent/src/test/java/org/openjdk/jmc/agent/test/TestSetTransforms.java @@ -1,6 +1,6 @@ /* - * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2019, Red Hat Inc. All rights reserved. + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020, Red Hat Inc. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/core/org.openjdk.jmc.agent/src/test/resources/org/openjdk/jmc/agent/test/jfrprobes_template.xml b/core/org.openjdk.jmc.agent/src/test/resources/org/openjdk/jmc/agent/test/jfrprobes_template.xml index 2f4ddc377..faab5f924 100644 --- a/core/org.openjdk.jmc.agent/src/test/resources/org/openjdk/jmc/agent/test/jfrprobes_template.xml +++ b/core/org.openjdk.jmc.agent/src/test/resources/org/openjdk/jmc/agent/test/jfrprobes_template.xml @@ -1,6 +1,6 @@