Skip to content

Commit

Permalink
Use condy for probes array in Java 11+ class files (#845)
Browse files Browse the repository at this point in the history
  • Loading branch information
Godin authored and marchof committed Mar 5, 2019
1 parent faf49f9 commit 36b4e9c
Show file tree
Hide file tree
Showing 7 changed files with 268 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
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}.
Expand Down Expand Up @@ -138,16 +138,16 @@ private void assertInstrumented(File classfile) throws IOException {
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"));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -37,6 +38,7 @@

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;
Expand Down Expand Up @@ -133,7 +135,7 @@ private void assertFrames(byte[] source, final boolean expected) {

@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;
Expand All @@ -147,8 +149,15 @@ public void visitFrame(int type, int nLocal,

@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));
}
}
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -212,16 +212,65 @@ public void testClinitAndMethodsInterface8() {
}

@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,
Expand Down Expand Up @@ -272,9 +321,9 @@ private static class AddedMethod {
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));
}
Expand Down Expand Up @@ -373,12 +422,19 @@ void assertNoDataField() {

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();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ public static IProbeArrayStrategy createFor(final long classId,
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);
Expand All @@ -58,6 +62,10 @@ public static IProbeArrayStrategy createFor(final long classId,
counter.getCount(), accessorGenerator);
}
} else {
if (version >= Opcodes.V11) {
return new CondyProbeArrayStrategy(className, false, classId,
accessorGenerator);
}
return new ClassFieldProbeArrayStrategy(className, classId,
InstrSupport.needsFrames(version), accessorGenerator);
}
Expand Down
3 changes: 3 additions & 0 deletions org.jacoco.doc/docroot/doc/changes.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ <h2>Snapshot Build @qualified.bundle.version@ (@build.date@)</h2>

<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>
Expand Down

0 comments on commit 36b4e9c

Please sign in to comment.