Skip to content

Commit

Permalink
Fix JaCoCo instrumented constructor skipped.
Browse files Browse the repository at this point in the history
Strip JaCoCo constructor probes data initialization instructions from `callSuper` instructions for JaCoCo instrumentation was detected. And insert these instructions into the beginning of the original construction.

Note that there can be many instructions before the call super() or this(), so this can still break the instruction compatibility?

Tested by: JaCoCo offline integration tests.

Signed-off-by: ZSmallX <diosmallx@gmail.com>
  • Loading branch information
ZSmallX committed Mar 16, 2023
1 parent 1349544 commit f3020ea
Showing 1 changed file with 50 additions and 5 deletions.
Expand Up @@ -229,15 +229,19 @@ private boolean isJacocoInstrumented(MethodNode ctor) {
if ((node instanceof LdcInsnNode && ((LdcInsnNode) node).cst instanceof ConstantDynamic)) {
ConstantDynamic cst = (ConstantDynamic) ((LdcInsnNode) node).cst;
return cst.getName().equals("$jacocoData");
} else if (insns.length > 1
&& insns[0] instanceof LabelNode
&& insns[1] instanceof MethodInsnNode) {
return "$jacocoInit".equals(((MethodInsnNode) insns[1]).name);
}
}
return false;
}

private boolean isJacocoInstrumentedInConstructorCallSuper(InsnList ctor) {
AbstractInsnNode[] insns = ctor.toArray();
if (insns.length > 1 && insns[0] instanceof LabelNode && insns[1] instanceof MethodInsnNode) {
return "$jacocoInit".equals(((MethodInsnNode) insns[1]).name);
}
return false;
}

/**
* Adds a call $$robo$init, which instantiates a shadow object if required. This is to support
* custom shadows for Jacoco-instrumented classes (except cnstructor shadows).
Expand Down Expand Up @@ -366,7 +370,8 @@ private static boolean isSyntheticAccessorMethod(MethodNode method) {
*
* <ul>
* <li>The original constructor will be stripped of its instructions leading up to, and
* including, the call to super() or this(). It is also renamed to $$robo$$__constructor__
* including, possible instrumented calls (like jacoco) before the call to super() or this()
* and the call to super() or this(). It is also renamed to $$robo$$__constructor__
* <li>A method called __constructor__ is created and its job is to call
* $$robo$$__constructor__. The __constructor__ method is what gets shadowed if a Shadow
* wants to shadow a constructor.
Expand All @@ -384,6 +389,10 @@ protected void instrumentConstructor(MutableClass mutableClass, MethodNode metho
makeMethodPrivate(method);

InsnList callSuper = extractCallToSuperConstructor(mutableClass, method);
if (isJacocoInstrumentedInConstructorCallSuper(callSuper)) {
InsnList initJaCoCo = extractInitJaCoCoFromConstructorCallSuper(callSuper);
method.instructions.insert(initJaCoCo);
}
method.name = directMethodName(mutableClass, ShadowConstants.CONSTRUCTOR_METHOD_NAME);
mutableClass.addMethod(
redirectorMethod(mutableClass, method, ShadowConstants.CONSTRUCTOR_METHOD_NAME));
Expand All @@ -407,6 +416,42 @@ protected void instrumentConstructor(MutableClass mutableClass, MethodNode metho
mutableClass.addMethod(initMethodNode);
}

/**
* JaCoCo instrumentation initializes its probes before constructor to collect the coverage.
*
* <p>Those JaCoCo instrumented instructions are supported to be stripped from call super
* instructions and insert to the original constructor in case of breaking the following
* instructions.
*
* <p>Note that there are still might be some other breaking from other ways.
*/
private InsnList extractInitJaCoCoFromConstructorCallSuper(InsnList callSuper) {
InsnList removedInstructions = new InsnList();

AbstractInsnNode[] insns = callSuper.toArray();
for (int i = 0; i < insns.length; i++) {
AbstractInsnNode node = insns[i];

if (node.getOpcode() == Opcodes.INVOKESTATIC) {
MethodInsnNode mnode = (MethodInsnNode) node;
if ("$jacocoInit".equals(mnode.name)) {
if (i + 1 >= insns.length) {
throw new RuntimeException("huh? JaCoCo instrumented instructions?!");
}

callSuper.remove(insns[i]);
callSuper.remove(insns[i + 1]);
removedInstructions.add(insns[i]);
removedInstructions.add(insns[i + 1]);

return removedInstructions;
}
}
}

throw new RuntimeException("huh? JaCoCo instrumented?!");
}

/**
* Checks to see if there are instructions after RETURN. If there are, it will check to see if
* they belong in the call-to-super, or the shadowable part of the constructor.
Expand Down

0 comments on commit f3020ea

Please sign in to comment.