Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use condy for probes array in Java 11+ class files #845

Merged
merged 14 commits into from
Mar 5, 2019
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -37,6 +37,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 +134,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 +148,14 @@ public void visitFrame(int type, int nLocal,

@Override
public void visitEnd() {
assertEquals(Boolean.valueOf(expected),
Boolean.valueOf(frames));
if (CondyProbeArrayStrategy.B_DESC
.equals(desc)) {
assertEquals(Boolean.FALSE,
Godin marked this conversation as resolved.
Show resolved Hide resolved
Boolean.valueOf(frames));
} else {
assertEquals(Boolean.valueOf(expected),
Boolean.valueOf(frames));
}
}
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*******************************************************************************
* 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.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 final CondyProbeArrayStrategy strategy = new CondyProbeArrayStrategy(
Godin marked this conversation as resolved.
Show resolved Hide resolved
"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());
// as a workaround for https://bugs.openjdk.java.net/browse/JDK-8216970
// constant should have type Object
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,71 @@
/*******************************************************************************
* 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);
mv.visitLdcInsn(new ConstantDynamic(InstrSupport.DATAFIELD_NAME,
"Ljava/lang/Object;", bootstrapMethod));
mv.visitTypeInsn(Opcodes.CHECKCAST, "[Z");
marchof marked this conversation as resolved.
Show resolved Hide resolved
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
7 changes: 7 additions & 0 deletions org.jacoco.doc/docroot/doc/changes.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ <h1>Change History</h1>

<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>
</ul>

<h2>Release 0.8.3 (2019/01/23)</h2>

<h3>New Features</h3>
Expand Down