diff options
author | Evgeny Mandrikov <138671+Godin@users.noreply.github.com> | 2019-03-05 06:53:49 +0100 |
---|---|---|
committer | Marc R. Hoffmann <hoffmann@mountainminds.com> | 2019-03-05 06:53:49 +0100 |
commit | 36b4e9c7103441d556a1667b4485960f5bdfeff8 (patch) | |
tree | fdf32b3ba388e592850d6a2c5e7746b8491dcfe2 | |
parent | faf49f9418f7bc8f1a198de3afd941ff953d3abe (diff) | |
download | platform_external_jacoco-36b4e9c7103441d556a1667b4485960f5bdfeff8.tar.gz platform_external_jacoco-36b4e9c7103441d556a1667b4485960f5bdfeff8.tar.bz2 platform_external_jacoco-36b4e9c7103441d556a1667b4485960f5bdfeff8.zip |
Use condy for probes array in Java 11+ class files (#845)
7 files changed, 268 insertions, 19 deletions
diff --git a/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/InstrumentTest.java b/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/InstrumentTest.java index ff49aa8d..76f31b83 100644 --- a/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/InstrumentTest.java +++ b/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/InstrumentTest.java @@ -33,7 +33,7 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; /** * Unit tests for {@link Instrument}. @@ -138,16 +138,16 @@ public class InstrumentTest extends CommandTestBase { final ClassReader reader = InstrSupport .classReaderFor(InputStreams.readFully(in)); in.close(); - final Set<String> fields = new HashSet<String>(); + final Set<String> methods = new HashSet<String>(); reader.accept(new ClassVisitor(InstrSupport.ASM_API_VERSION) { @Override - public FieldVisitor visitField(int access, String name, String desc, - String signature, Object value) { - fields.add(name); + public MethodVisitor visitMethod(int access, String name, + String descriptor, String signature, String[] exceptions) { + methods.add(name); return null; } }, 0); - assertTrue(fields.contains("$jacocoData")); + assertTrue(methods.contains("$jacocoInit")); } } diff --git a/org.jacoco.core.test/src/org/jacoco/core/instr/ClassFileVersionsTest.java b/org.jacoco.core.test/src/org/jacoco/core/instr/ClassFileVersionsTest.java index f1f8d16c..9a9bd557 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/instr/ClassFileVersionsTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/instr/ClassFileVersionsTest.java @@ -12,6 +12,7 @@ package org.jacoco.core.instr; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; import static org.objectweb.asm.Opcodes.ACC_SUPER; import static org.objectweb.asm.Opcodes.ALOAD; @@ -37,6 +38,7 @@ import static org.objectweb.asm.Opcodes.V9; import java.io.IOException; +import org.jacoco.core.internal.instr.CondyProbeArrayStrategy; import org.jacoco.core.internal.instr.InstrSupport; import org.jacoco.core.runtime.IRuntime; import org.jacoco.core.runtime.SystemPropertiesRuntime; @@ -133,7 +135,7 @@ public class ClassFileVersionsTest { @Override public MethodVisitor visitMethod(int access, String name, - String desc, String signature, + final String desc, String signature, String[] exceptions) { return new MethodVisitor(InstrSupport.ASM_API_VERSION) { boolean frames = false; @@ -147,8 +149,15 @@ public class ClassFileVersionsTest { @Override public void visitEnd() { - assertEquals(Boolean.valueOf(expected), - Boolean.valueOf(frames)); + if (CondyProbeArrayStrategy.B_DESC + .equals(desc)) { + assertFalse( + "CondyProbeArrayStrategy does not need frames", + frames); + } else { + assertEquals(Boolean.valueOf(expected), + Boolean.valueOf(frames)); + } } }; } diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/instr/CondyProbeArrayStrategyTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/instr/CondyProbeArrayStrategyTest.java new file mode 100644 index 00000000..6397a59b --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/instr/CondyProbeArrayStrategyTest.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * 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.internal.instr; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.jacoco.core.runtime.OfflineInstrumentationAccessGenerator; +import org.junit.Before; +import org.junit.Test; +import org.objectweb.asm.ConstantDynamic; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TypeInsnNode; +import org.objectweb.asm.tree.VarInsnNode; + +public class CondyProbeArrayStrategyTest { + + private CondyProbeArrayStrategy strategy; + + @Before + public void setup() { + strategy = new CondyProbeArrayStrategy("ClassName", true, 1L, + new OfflineInstrumentationAccessGenerator()); + } + + @Test + public void should_store_instance_using_condy_and_checkcast() { + final MethodNode m = new MethodNode(); + final int maxStack = strategy.storeInstance(m, false, 1); + + assertEquals(1, maxStack); + + final ConstantDynamic constantDynamic = (ConstantDynamic) ((LdcInsnNode) m.instructions + .get(0)).cst; + assertEquals("$jacocoData", constantDynamic.getName()); + assertEquals("Ljava/lang/Object;", constantDynamic.getDescriptor()); + + final Handle bootstrapMethod = constantDynamic.getBootstrapMethod(); + assertEquals(Opcodes.H_INVOKESTATIC, bootstrapMethod.getTag()); + assertEquals("ClassName", bootstrapMethod.getOwner()); + assertEquals("$jacocoInit", bootstrapMethod.getName()); + assertEquals( + "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)[Z", + bootstrapMethod.getDesc()); + assertTrue(bootstrapMethod.isInterface()); + + final TypeInsnNode castInstruction = (TypeInsnNode) m.instructions + .get(1); + assertEquals(Opcodes.CHECKCAST, castInstruction.getOpcode()); + assertEquals("[Z", castInstruction.desc); + + final VarInsnNode storeInstruction = (VarInsnNode) m.instructions + .get(2); + assertEquals(Opcodes.ASTORE, storeInstruction.getOpcode()); + assertEquals(1, storeInstruction.var); + + assertEquals(3, m.instructions.size()); + } + + @Test + public void should_not_add_fields() { + final ClassNode c = new ClassNode(); + strategy.addMembers(c, 1); + + assertEquals(0, c.fields.size()); + } + + @Test + public void should_add_bootstrap_method() { + final ClassNode c = new ClassNode(); + strategy.addMembers(c, 1); + + assertEquals(1, c.methods.size()); + + final MethodNode m = c.methods.get(0); + assertEquals(Opcodes.ACC_SYNTHETIC | Opcodes.ACC_PRIVATE + | Opcodes.ACC_STATIC, m.access); + assertEquals("$jacocoInit", m.name); + assertEquals( + "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)[Z", + m.desc); + + assertEquals(4, m.maxStack); + assertEquals(3, m.maxLocals); + } + +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/instr/ProbeArrayStrategyFactoryTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/instr/ProbeArrayStrategyFactoryTest.java index 1354b60f..f1d1282a 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/instr/ProbeArrayStrategyFactoryTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/instr/ProbeArrayStrategyFactoryTest.java @@ -212,16 +212,65 @@ public class ProbeArrayStrategyFactoryTest { } @Test - public void testModule() { + public void test_java9_module() { + final IProbeArrayStrategy strategy = createForModule(Opcodes.V9); + assertEquals(NoneProbeArrayStrategy.class, strategy.getClass()); + } + + @Test + public void test_java11_class() { + final IProbeArrayStrategy strategy = test(Opcodes.V11, 0, true, true, + true); + + assertEquals(CondyProbeArrayStrategy.class, strategy.getClass()); + assertNoDataField(); + assertCondyBootstrapMethod(); + } + + @Test + public void test_java11_interface_with_clinit_and_methods() { + final IProbeArrayStrategy strategy = test(Opcodes.V11, + Opcodes.ACC_INTERFACE, true, true, true); + + assertEquals(CondyProbeArrayStrategy.class, strategy.getClass()); + assertNoDataField(); + assertCondyBootstrapMethod(); + } + + @Test + public void test_java11_interface_with_clinit() { + final IProbeArrayStrategy strategy = test(Opcodes.V11, + Opcodes.ACC_INTERFACE, true, false, true); + + assertEquals(LocalProbeArrayStrategy.class, strategy.getClass()); + assertNoDataField(); + assertNoInitMethod(); + } + + @Test + public void test_java11_interface_without_code() { + final IProbeArrayStrategy strategy = test(Opcodes.V11, + Opcodes.ACC_INTERFACE, false, false, true); + + assertEquals(NoneProbeArrayStrategy.class, strategy.getClass()); + assertNoDataField(); + assertNoInitMethod(); + } + + @Test + public void test_java11_module() { + final IProbeArrayStrategy strategy = createForModule(Opcodes.V11); + assertEquals(NoneProbeArrayStrategy.class, strategy.getClass()); + } + + private IProbeArrayStrategy createForModule(int version) { final ClassWriter writer = new ClassWriter(0); - writer.visit(Opcodes.V9, Opcodes.ACC_MODULE, "module-info", null, null, + writer.visit(version, Opcodes.ACC_MODULE, "module-info", null, null, null); writer.visitModule("module", 0, null).visitEnd(); writer.visitEnd(); - - final IProbeArrayStrategy strategy = ProbeArrayStrategyFactory - .createFor(0, new ClassReader(writer.toByteArray()), generator); - assertEquals(NoneProbeArrayStrategy.class, strategy.getClass()); + return ProbeArrayStrategyFactory.createFor(0, + new ClassReader(writer.toByteArray()), generator); } private IProbeArrayStrategy test(int version, int access, boolean clinit, @@ -272,9 +321,9 @@ public class ProbeArrayStrategyFactoryTest { this.desc = desc; } - void assertInitMethod(boolean frames) { + void assertInitMethod(String expectedDesc, boolean frames) { assertEquals(InstrSupport.INITMETHOD_NAME, name); - assertEquals(InstrSupport.INITMETHOD_DESC, desc); + assertEquals(expectedDesc, desc); assertEquals(InstrSupport.INITMETHOD_ACC, access); assertEquals(Boolean.valueOf(frames), Boolean.valueOf(frames)); } @@ -373,12 +422,19 @@ public class ProbeArrayStrategyFactoryTest { void assertInitMethod(boolean frames) { assertEquals(cv.methods.size(), 1); - cv.methods.get(0).assertInitMethod(frames); + cv.methods.get(0).assertInitMethod(InstrSupport.INITMETHOD_DESC, + frames); + } + + void assertCondyBootstrapMethod() { + assertEquals(cv.methods.size(), 1); + cv.methods.get(0).assertInitMethod(CondyProbeArrayStrategy.B_DESC, + false); } void assertInitAndClinitMethods() { assertEquals(2, cv.methods.size()); - cv.methods.get(0).assertInitMethod(true); + cv.methods.get(0).assertInitMethod(InstrSupport.INITMETHOD_DESC, true); cv.methods.get(1).assertClinit(); } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/CondyProbeArrayStrategy.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/CondyProbeArrayStrategy.java new file mode 100644 index 00000000..ca2fb60d --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/CondyProbeArrayStrategy.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * 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.internal.instr; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ConstantDynamic; +import org.jacoco.core.runtime.IExecutionDataAccessorGenerator; +import org.objectweb.asm.Handle; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/** + * This strategy for Java 11+ class files uses {@link ConstantDynamic} to hold + * the probe array and adds bootstrap method requesting the probe array from the + * runtime. + */ +public class CondyProbeArrayStrategy implements IProbeArrayStrategy { + + /** + * Descriptor of the bootstrap method. + */ + public static final String B_DESC = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)[Z"; + + private final String className; + + private final boolean isInterface; + + private final long classId; + + private final IExecutionDataAccessorGenerator accessorGenerator; + + CondyProbeArrayStrategy(final String className, final boolean isInterface, + final long classId, + final IExecutionDataAccessorGenerator accessorGenerator) { + this.className = className; + this.isInterface = isInterface; + this.classId = classId; + this.accessorGenerator = accessorGenerator; + } + + public int storeInstance(final MethodVisitor mv, final boolean clinit, + final int variable) { + final Handle bootstrapMethod = new Handle(Opcodes.H_INVOKESTATIC, + className, InstrSupport.INITMETHOD_NAME, B_DESC, isInterface); + // As a workaround for https://bugs.openjdk.java.net/browse/JDK-8216970 + // constant should have type Object + mv.visitLdcInsn(new ConstantDynamic(InstrSupport.DATAFIELD_NAME, + "Ljava/lang/Object;", bootstrapMethod)); + mv.visitTypeInsn(Opcodes.CHECKCAST, "[Z"); + mv.visitVarInsn(Opcodes.ASTORE, variable); + return 1; + } + + public void addMembers(final ClassVisitor cv, final int probeCount) { + final MethodVisitor mv = cv.visitMethod(InstrSupport.INITMETHOD_ACC, + InstrSupport.INITMETHOD_NAME, B_DESC, null, null); + final int maxStack = accessorGenerator.generateDataAccessor(classId, + className, probeCount, mv); + mv.visitInsn(Opcodes.ARETURN); + mv.visitMaxs(maxStack, 3); + mv.visitEnd(); + } + +} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeArrayStrategyFactory.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeArrayStrategyFactory.java index 0c668817..72c8dd68 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeArrayStrategyFactory.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeArrayStrategyFactory.java @@ -50,6 +50,10 @@ public final class ProbeArrayStrategyFactory { if (counter.getCount() == 0) { return new NoneProbeArrayStrategy(); } + if (version >= Opcodes.V11 && counter.hasMethods()) { + return new CondyProbeArrayStrategy(className, true, classId, + accessorGenerator); + } if (version >= Opcodes.V1_8 && counter.hasMethods()) { return new InterfaceFieldProbeArrayStrategy(className, classId, counter.getCount(), accessorGenerator); @@ -58,6 +62,10 @@ public final class ProbeArrayStrategyFactory { counter.getCount(), accessorGenerator); } } else { + if (version >= Opcodes.V11) { + return new CondyProbeArrayStrategy(className, false, classId, + accessorGenerator); + } return new ClassFieldProbeArrayStrategy(className, classId, InstrSupport.needsFrames(version), accessorGenerator); } diff --git a/org.jacoco.doc/docroot/doc/changes.html b/org.jacoco.doc/docroot/doc/changes.html index ea9c75b7..15250c84 100644 --- a/org.jacoco.doc/docroot/doc/changes.html +++ b/org.jacoco.doc/docroot/doc/changes.html @@ -22,6 +22,9 @@ <h3>New Features</h3> <ul> + <li>Instrumentation does not add synthetic field to Java 11+ class files, + however still adds synthetic method + (GitHub <a href="https://github.com/jacoco/jacoco/issues/845">#845</a>).</li> <li>Branches added by the Kotlin compiler version 1.3.30 for suspending lambdas and functions are filtered out during generation of report (GitHub <a href="https://github.com/jacoco/jacoco/issues/849">#849</a>).</li> |