aboutsummaryrefslogtreecommitdiff
path: root/org.jacoco.core/src/org
diff options
context:
space:
mode:
authorEvgeny Mandrikov <138671+Godin@users.noreply.github.com>2019-01-21 18:39:49 +0100
committerMarc R. Hoffmann <hoffmann@mountainminds.com>2019-01-21 18:39:49 +0100
commitd0a0577f70b5bc4f42ce6af603b783835a014b82 (patch)
tree005420eb5e3ed6b5e84e01e2f71589a3e5fa9bf2 /org.jacoco.core/src/org
parent519226e7259c0dd36a59c53de9989d99521cc7e9 (diff)
downloadjacoco-d0a0577f70b5bc4f42ce6af603b783835a014b82.tar.gz
Agent can inject class into Java 9+ bootstrap class loader (#829)
Diffstat (limited to 'org.jacoco.core/src/org')
-rw-r--r--org.jacoco.core/src/org/jacoco/core/runtime/InjectedClassRuntime.java141
1 files changed, 141 insertions, 0 deletions
diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/InjectedClassRuntime.java b/org.jacoco.core/src/org/jacoco/core/runtime/InjectedClassRuntime.java
new file mode 100644
index 00000000..ee7aa1ac
--- /dev/null
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/InjectedClassRuntime.java
@@ -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 });
+ }
+
+ }
+
+}