forked from bazelbuild/rules_scala
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Agent can inject class into Java 9+ bootstrap class loader (bazelbuil…
- Loading branch information
Showing
5 changed files
with
261 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
62 changes: 62 additions & 0 deletions
62
org.jacoco.core.test/src/org/jacoco/core/runtime/InjectedClassRuntimeTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/******************************************************************************* | ||
* Copyright (c) 2009, 2019 Mountainminds GmbH & Co. KG and Contributors | ||
* All rights reserved. This program and the accompanying materials | ||
* are made available under the terms of the Eclipse Public License v1.0 | ||
* which accompanies this distribution, and is available at | ||
* http://www.eclipse.org/legal/epl-v10.html | ||
* | ||
* Contributors: | ||
* Evgeny Mandrikov - initial API and implementation | ||
* | ||
*******************************************************************************/ | ||
package org.jacoco.core.runtime; | ||
|
||
import static org.junit.Assert.assertTrue; | ||
import static org.junit.Assert.fail; | ||
|
||
import java.lang.reflect.InvocationTargetException; | ||
|
||
import org.junit.BeforeClass; | ||
import org.junit.Rule; | ||
import org.junit.Test; | ||
import org.junit.internal.AssumptionViolatedException; | ||
import org.junit.rules.TestName; | ||
|
||
/** | ||
* Unit test for {@link InjectedClassRuntime}. | ||
*/ | ||
public class InjectedClassRuntimeTest extends RuntimeTestBase { | ||
|
||
@Rule | ||
public TestName testName = new TestName(); | ||
|
||
@BeforeClass | ||
public static void requires_at_least_Java_9() { | ||
try { | ||
Class.forName("java.lang.Module"); | ||
} catch (final ClassNotFoundException e) { | ||
throw new AssumptionViolatedException( | ||
"this test requires at least Java 9"); | ||
} | ||
} | ||
|
||
@Override | ||
public IRuntime createRuntime() { | ||
return new InjectedClassRuntime(InjectedClassRuntimeTest.class, | ||
testName.getMethodName()); | ||
} | ||
|
||
@Test | ||
public void startup_should_not_create_duplicate_class_definition() | ||
throws Exception { | ||
try { | ||
createRuntime().startup(null); | ||
fail("exception expected"); | ||
} catch (final InvocationTargetException e) { | ||
assertTrue(e.getCause() instanceof LinkageError); | ||
assertTrue(e.getCause().getMessage() | ||
.contains("duplicate class definition")); | ||
} | ||
} | ||
|
||
} |
141 changes: 141 additions & 0 deletions
141
org.jacoco.core/src/org/jacoco/core/runtime/InjectedClassRuntime.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
/******************************************************************************* | ||
* Copyright (c) 2009, 2019 Mountainminds GmbH & Co. KG and Contributors | ||
* All rights reserved. This program and the accompanying materials | ||
* are made available under the terms of the Eclipse Public License v1.0 | ||
* which accompanies this distribution, and is available at | ||
* http://www.eclipse.org/legal/epl-v10.html | ||
* | ||
* Contributors: | ||
* Evgeny Mandrikov - initial API and implementation | ||
* | ||
*******************************************************************************/ | ||
package org.jacoco.core.runtime; | ||
|
||
import org.objectweb.asm.ClassWriter; | ||
import org.objectweb.asm.MethodVisitor; | ||
import org.objectweb.asm.Opcodes; | ||
|
||
/** | ||
* {@link IRuntime} which defines a new class using | ||
* {@code java.lang.invoke.MethodHandles.Lookup.defineClass} introduced in Java | ||
* 9. Module where class will be defined must be opened to at least module of | ||
* this class. | ||
*/ | ||
public class InjectedClassRuntime extends AbstractRuntime { | ||
|
||
private static final String FIELD_NAME = "data"; | ||
|
||
private static final String FIELD_TYPE = "Ljava/lang/Object;"; | ||
|
||
private final Class<?> locator; | ||
|
||
private final String injectedClassName; | ||
|
||
/** | ||
* Creates a new runtime which will define a class to the same class loader | ||
* and in the same package and protection domain as given class. | ||
* | ||
* @param locator | ||
* class to identify the target class loader and package | ||
* @param simpleClassName | ||
* simple name of the class to be defined | ||
*/ | ||
public InjectedClassRuntime(final Class<?> locator, | ||
final String simpleClassName) { | ||
this.locator = locator; | ||
this.injectedClassName = locator.getPackage().getName().replace('.', | ||
'/') + '/' + simpleClassName; | ||
} | ||
|
||
@Override | ||
public void startup(final RuntimeData data) throws Exception { | ||
super.startup(data); | ||
Lookup // | ||
.privateLookupIn(locator, Lookup.lookup()) // | ||
.defineClass(createClass(injectedClassName)) // | ||
.getField(FIELD_NAME) // | ||
.set(null, data); | ||
} | ||
|
||
public void shutdown() { | ||
// nothing to do | ||
} | ||
|
||
public int generateDataAccessor(final long classid, final String classname, | ||
final int probecount, final MethodVisitor mv) { | ||
mv.visitFieldInsn(Opcodes.GETSTATIC, injectedClassName, FIELD_NAME, | ||
FIELD_TYPE); | ||
|
||
RuntimeData.generateAccessCall(classid, classname, probecount, mv); | ||
|
||
return 6; | ||
} | ||
|
||
private static byte[] createClass(final String name) { | ||
final ClassWriter cw = new ClassWriter(0); | ||
cw.visit(Opcodes.V9, Opcodes.ACC_SYNTHETIC | Opcodes.ACC_PUBLIC, | ||
name.replace('.', '/'), null, "java/lang/Object", null); | ||
cw.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, FIELD_NAME, | ||
FIELD_TYPE, null, null); | ||
cw.visitEnd(); | ||
return cw.toByteArray(); | ||
} | ||
|
||
/** | ||
* Provides access to classes {@code java.lang.invoke.MethodHandles} and | ||
* {@code java.lang.invoke.MethodHandles.Lookup} introduced in Java 8. | ||
*/ | ||
private static class Lookup { | ||
|
||
private final Object instance; | ||
|
||
private Lookup(final Object instance) { | ||
this.instance = instance; | ||
} | ||
|
||
/** | ||
* @return a lookup object for the caller of this method | ||
*/ | ||
static Lookup lookup() throws Exception { | ||
return new Lookup(Class // | ||
.forName("java.lang.invoke.MethodHandles") // | ||
.getMethod("lookup") // | ||
.invoke(null)); | ||
} | ||
|
||
/** | ||
* See corresponding method introduced in Java 9. | ||
* | ||
* @param targetClass | ||
* the target class | ||
* @param lookup | ||
* the caller lookup object | ||
* @return a lookup object for the target class, with private access | ||
*/ | ||
static Lookup privateLookupIn(final Class<?> targetClass, | ||
final Lookup lookup) throws Exception { | ||
return new Lookup(Class // | ||
.forName("java.lang.invoke.MethodHandles") // | ||
.getMethod("privateLookupIn", Class.class, | ||
Class.forName( | ||
"java.lang.invoke.MethodHandles$Lookup")) // | ||
.invoke(null, targetClass, lookup.instance)); | ||
} | ||
|
||
/** | ||
* See corresponding method introduced in Java 9. | ||
* | ||
* @param bytes | ||
* the class bytes | ||
* @return class | ||
*/ | ||
Class<?> defineClass(final byte[] bytes) throws Exception { | ||
return (Class<?>) Class // | ||
.forName("java.lang.invoke.MethodHandles$Lookup") | ||
.getMethod("defineClass", byte[].class) | ||
.invoke(this.instance, new Object[] { bytes }); | ||
} | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters