Skip to content
Permalink
Browse files
6805: Event emission only on exceptions
Reviewed-by: hirt
  • Loading branch information
Joshua Matsuoka committed Jul 2, 2020
1 parent 20462f2 commit 3eb5cb2b915fa3c93a91357034dadca9bd63d1f9
@@ -44,6 +44,7 @@ public abstract class TransformDescriptor {
public static final String ATTRIBUTE_CLASS_PREFIX = "classprefix"; //$NON-NLS-1$
public static final String ATTRIBUTE_ALLOW_TO_STRING = "allowtostring"; //$NON-NLS-1$
public static final String ATTRIBUTE_ALLOW_CONVERTER = "allowconverter"; //$NON-NLS-1$
public static final String ATTRIBUTE_EMIT_ON_EXCEPTION = "emitonexception"; //$NON-NLS-1$

public static final String DEFAULT_CLASS_PREFIX = "__JFREvent"; //$NON-NLS-1$

@@ -277,6 +277,7 @@ private static void addDefaults(HashMap<String, String> globalDefaults) {
globalDefaults.put(TransformDescriptor.ATTRIBUTE_ALLOW_TO_STRING, "false"); //$NON-NLS-1$
// For safety reasons, allowing converters is opt-in
globalDefaults.put(TransformDescriptor.ATTRIBUTE_ALLOW_CONVERTER, "false"); //$NON-NLS-1$
globalDefaults.put(TransformDescriptor.ATTRIBUTE_EMIT_ON_EXCEPTION, "false"); //$NON-NLS-1$
}

private static Parameter parseParameter(int index, XMLStreamReader streamReader) throws XMLStreamException {
@@ -66,6 +66,7 @@ public class JFRTransformDescriptor extends TransformDescriptor {
private final boolean useRethrow;
private final boolean allowToString;
private final boolean allowConverter;
private final boolean emitOnException;
private final List<Parameter> parameters;
private final ReturnValue returnValue;
private final List<Field> fields;
@@ -82,6 +83,7 @@ public JFRTransformDescriptor(String id, String className, Method method,
useRethrow = getBoolean(ATTRIBUTE_RETHROW, false);
allowToString = getBoolean(ATTRIBUTE_ALLOW_TO_STRING, false);
allowConverter = getBoolean(ATTRIBUTE_ALLOW_CONVERTER, false);
emitOnException = getBoolean(ATTRIBUTE_EMIT_ON_EXCEPTION, false);
this.parameters = parameters;
this.fields = fields;
this.returnValue = returnValue;
@@ -149,6 +151,10 @@ public boolean isAllowToString() {
public boolean isAllowConverter() {
return allowConverter;
}

public boolean isEmitOnException() {
return emitOnException;
}

private String initializeClassPrefix() {
String prefix = getTransformationAttribute(ATTRIBUTE_CLASS_PREFIX);
@@ -66,6 +66,7 @@ public class JFRNextMethodAdvisor extends AdviceAdapter {

private Label tryBegin = new Label();
private Label tryEnd = new Label();
private Label catchBegin = new Label();

private boolean shouldInstrumentThrow;

@@ -79,31 +80,41 @@ protected JFRNextMethodAdvisor(JFRTransformDescriptor transformDescriptor, Class
this.returnTypeRef = Type.getReturnType(desc);
this.eventType = Type.getObjectType(transformDescriptor.getEventClassName());

this.shouldInstrumentThrow = !transformDescriptor.isUseRethrow(); // don't instrument inner throws if rethrow is enabled
this.shouldInstrumentThrow = !transformDescriptor.isUseRethrow() || !transformDescriptor.isEmitOnException(); // don't instrument inner throws if rethrow is enabled
}

@Override
public void visitCode() {
super.visitCode();

if (transformDescriptor.isUseRethrow()) {
if (transformDescriptor.isUseRethrow() || transformDescriptor.isEmitOnException()) {
visitLabel(tryBegin);
}
}

@Override
public void visitEnd() {
if (transformDescriptor.isUseRethrow()) {
if (transformDescriptor.isUseRethrow() && !transformDescriptor.isEmitOnException()) {
visitLabel(tryEnd);
visitTryCatchBlock(tryBegin, tryEnd, tryEnd, THROWABLE_BINARY_NAME);

visitFrame(Opcodes.F_NEW, 0, null, 1, new Object[] {THROWABLE_BINARY_NAME});

// Simply rethrow. Event commits are instrumented by onMethodExit()
shouldInstrumentThrow = true;
visitInsn(ATHROW);
} else if (transformDescriptor.isEmitOnException()) {
visitLabel(tryEnd);
visitTryCatchBlock(tryBegin, tryEnd, catchBegin, THROWABLE_BINARY_NAME);
if (!transformDescriptor.isUseRethrow()) {
visitFrame(Opcodes.F_NEW, 0, null, 1, new Object[] {THROWABLE_BINARY_NAME});
visitInsn(RETURN);
} else {
visitFrame(Opcodes.F_NEW, 0, null, 1, new Object[] {THROWABLE_BINARY_NAME});
shouldInstrumentThrow = true;
visitInsn(ATHROW);
}
}

super.visitEnd();
}

@@ -229,10 +240,12 @@ private void writeAttribute(Attribute param, Type type) {

@Override
protected void onMethodExit(int opcode) {
if (transformDescriptor.isEmitOnException()) {
visitLabel(catchBegin);
}
if (opcode == ATHROW && !shouldInstrumentThrow) {
return;
}

if (returnTypeRef.getSort() != Type.VOID && opcode != ATHROW) {
ReturnValue returnValue = transformDescriptor.getReturnValue();
if (returnValue != null) {
@@ -36,6 +36,13 @@
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element type="xs:boolean" name="emitonexception" minOccurs="0">
<xs:annotation>
<xs:documentation>
Causes events to only be emitted if the instrmented method throws an exception
</xs:documentation>
</xs:annotation>
</xs:element>
</xs:all>
</xs:complexType>

@@ -38,7 +38,7 @@
import org.openjdk.jmc.agent.converters.test.TestConverterTransforms;

@RunWith(Suite.class)
@SuiteClasses({TestDefaultTransformRegistry.class, TestUtils.class, TestJFRTransformer.class, TestConverterTransforms.class, TestProbeDefinitionValidation.class, TestCompressedFrameTransformation.class})
@SuiteClasses({TestDefaultTransformRegistry.class, TestUtils.class, TestJFRTransformer.class, TestConverterTransforms.class, TestProbeDefinitionValidation.class, TestCompressedFrameTransformation.class, TestEmitOnlyOnException.class})

public class AllTests {
}
@@ -35,6 +35,9 @@

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.io.PrintWriter;
import java.lang.management.ManagementFactory;

import java.util.ArrayList;
@@ -51,13 +54,16 @@
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.AdviceAdapter;
import org.objectweb.asm.util.CheckClassAdapter;
import org.objectweb.asm.util.TraceClassVisitor;
import org.openjdk.jmc.agent.Field;
import org.openjdk.jmc.agent.Method;
import org.openjdk.jmc.agent.Parameter;
import org.openjdk.jmc.agent.ReturnValue;
import org.openjdk.jmc.agent.jfr.JFRTransformDescriptor;
import org.openjdk.jmc.agent.jfrnext.impl.JFRNextEventClassGenerator;
import org.openjdk.jmc.agent.jmx.AgentControllerMXBean;
import org.openjdk.jmc.agent.test.util.TestToolkit;
import org.openjdk.jmc.agent.util.TypeUtils;

public class TestDefineEventProbes {
@@ -92,6 +98,7 @@ public class TestDefineEventProbes {
public void testDefineEventProbes() throws Exception {
boolean exceptionThrown = false;
try {
//dumpByteCode(TestToolkit.getByteCode(InstrumentMe.class));
InstrumentMe.printHelloWorldJFR6();
} catch (Exception e) {
e.printStackTrace(System.err);
@@ -170,4 +177,13 @@ private void doDefineEventProbes(String xmlDescription) throws Exception {
public void test() {
//Dummy method for instrumentation
}

public void dumpByteCode(byte[] transformedClass) throws IOException {
// If we've asked for verbose information, we write the generated class
// and also dump the registry contents to stdout.
TraceClassVisitor visitor = new TraceClassVisitor(new PrintWriter(System.out));
CheckClassAdapter checkAdapter = new CheckClassAdapter(visitor);
ClassReader reader = new ClassReader(transformedClass);
reader.accept(checkAdapter, 0);
}
}
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020 Red Hat Inc. 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.test;

public class TestDummy {

public void testWithoutException() {
System.out.println("I'm going to return now. bye!");
return;
}

public void testWithException() throws Exception {
System.out.println("I'm going to throw an exception now. bye!");
throw new RuntimeException();
}
}
@@ -0,0 +1,124 @@
/*
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020 Red Hat Inc. 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.test;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.PrintWriter;

import org.junit.Test;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Type;
import org.objectweb.asm.util.CheckClassAdapter;
import org.objectweb.asm.util.TraceClassVisitor;
import org.openjdk.jmc.agent.TransformRegistry;
import org.openjdk.jmc.agent.Transformer;
import org.openjdk.jmc.agent.impl.DefaultTransformRegistry;
import org.openjdk.jmc.agent.test.util.TestToolkit;

public class TestEmitOnlyOnException {

private static final String AGENT_OBJECT_NAME = "org.openjdk.jmc.jfr.agent:type=AgentController"; //$NON-NLS-1$
private static final String EVENT_ID = "demo.jfr.test";
private static final String EVENT_NAME = "JFR Emit on Exception Event %TEST_NAME%";
private static final String EVENT_DESCRIPTION = "JFR Emit on Exception Event %TEST_NAME%";
private static final String EVENT_PATH = "demo/emitonexceptionevent";
private static final String EVENT_CLASS_NAME = "org.openjdk.jmc.agent.test.TestDummy";
private static final String METHOD_NAME = "testWithException";
private static final String METHOD_NAME_2 = "testWithoutException";
private static final String METHOD_DESCRIPTOR = "()V";

private static final String XML_DESCRIPTION = "<jfragent>"
+ "<config>"
+ "<emitonexception>true</emitonexception>"
+ "</config>"
+ "<events>"
+ "<event id=\"" + EVENT_ID + "\">"
+ "<name>" + EVENT_NAME + "</name>"
+ "<description>" + EVENT_DESCRIPTION + "</description>"
+ "<path>" + EVENT_PATH + "</path>"
+ "<stacktrace>true</stacktrace>"
+ "<class>" + EVENT_CLASS_NAME + "</class>"
+ "<method>"
+ "<name>" + METHOD_NAME + "</name>"
+ "<descriptor>" + METHOD_DESCRIPTOR + "</descriptor>"
+ "</method>"
+ "<location>WRAP</location>"
+ "</event>"
+ "<event id=\"" + EVENT_ID + "2" + "\">"
+ "<name>" + EVENT_NAME + "2" + "</name>"
+ "<description>" + EVENT_DESCRIPTION + "2" + "</description>"
+ "<path>" + EVENT_PATH + "</path>"
+ "<stacktrace>true</stacktrace>"
+ "<class>" + EVENT_CLASS_NAME + "</class>"
+ "<method>"
+ "<name>" + METHOD_NAME_2 + "</name>"
+ "<descriptor>" + METHOD_DESCRIPTOR + "</descriptor>"
+ "</method>"
+ "<location>WRAP</location>"
+ "</event>"
+ "</events>"
+ "</jfragent>";

@Test
public void testEmitOnException() throws Exception {
TestDummy t = new TestDummy();
TransformRegistry registry = DefaultTransformRegistry.from(new ByteArrayInputStream(XML_DESCRIPTION.getBytes())); //$NON-NLS-1$
assertTrue(registry.hasPendingTransforms(Type.getInternalName(TestDummy.class)));

Transformer jfrTransformer = new Transformer(registry);
byte[] transformedClass = jfrTransformer.transform(TestDummy.class.getClassLoader(),
Type.getInternalName(TestDummy.class), TestDummy.class, null,
TestToolkit.getByteCode(TestDummy.class));

assertNotNull(transformedClass);
try {
t.testWithoutException();
t.testWithException();
} catch (Exception e) {}
}

public void dumpByteCode(byte[] transformedClass) throws IOException {
// If we've asked for verbose information, we write the generated class
// and also dump the registry contents to stdout.
TraceClassVisitor visitor = new TraceClassVisitor(new PrintWriter(System.out));
CheckClassAdapter checkAdapter = new CheckClassAdapter(visitor);
ClassReader reader = new ClassReader(transformedClass);
reader.accept(checkAdapter, 0);
}
}

0 comments on commit 3eb5cb2

Please sign in to comment.