diff options
author | Evgeny Mandrikov <138671+Godin@users.noreply.github.com> | 2019-01-21 18:39:49 +0100 |
---|---|---|
committer | Marc R. Hoffmann <hoffmann@mountainminds.com> | 2019-01-21 18:39:49 +0100 |
commit | d0a0577f70b5bc4f42ce6af603b783835a014b82 (patch) | |
tree | 005420eb5e3ed6b5e84e01e2f71589a3e5fa9bf2 /org.jacoco.core/src/org | |
parent | 519226e7259c0dd36a59c53de9989d99521cc7e9 (diff) | |
download | jacoco-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.java | 141 |
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 }); + } + + } + +} |