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

Exclude from a report a part of bytecode that compiler generates for a synchronized statement and that is hard to cover by tests #501

Merged
merged 11 commits into from
Mar 28, 2017

Conversation

Godin
Copy link
Member

@Godin Godin commented Mar 19, 2017

No description provided.

@Godin
Copy link
Member Author

Godin commented Mar 25, 2017

Analysis of 17391 classes from rt.jar (≈ 65 Mb) from JDK 1.8.0_121 using following build.xml

<project xmlns:jacoco="antlib:org.jacoco.ant" default="report">
  <taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml">
    <classpath path="jacocoant.jar"/>
  </taskdef>
  <target name="report">
    <jacoco:report>
      <executiondata>
        <file file="jacoco.exec"/>
      </executiondata>
      <structure name="rt.jar">
        <classfiles>
          <fileset file="rt.jar"/>
        </classfiles>
      </structure>
    </jacoco:report>
  </target>
</project>

takes 5 seconds on my laptop, both without this change as well as with it. So I believe we are safe to merge this.

@marchof do you have objections or some review comments?

@Godin Godin force-pushed the issue-501 branch 2 times, most recently from c5a88c1 to f0cc333 Compare March 26, 2017 03:01
Copy link
Member

@marchof marchof left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Godin I added several comments for things that could be improved from my point of view (especially the documentation issues).

The unit testing facilities I commented about can be improved later from my point of view.


import org.objectweb.asm.tree.MethodNode;

public interface IFilter {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the central API for filters. We should add some JavaDoc here. Especially about the usage contract: E.g. IFilter instances are re-used and must be thread safe.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marchof done ✅


import org.objectweb.asm.tree.AbstractInsnNode;

public interface IFilterOutput {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JavaDoc would be nice too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marchof done ✅


public interface IFilterOutput {

void ignore(AbstractInsnNode from, AbstractInsnNode to);
Copy link
Member

@marchof marchof Mar 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Javadoc: What exactly is the semantics of from and to?

Copy link
Member

@marchof marchof Mar 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any constraints regarding from and to nodes? "to must be a successor of from in the instruction sequence. from is inclusive, to is exclusive".

Copy link
Member Author

@Godin Godin Mar 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marchof Actually both are inclusive. And we already use term "successor" in analysis of CFG, where its usage implies that not each instruction is a "successor" of preceding instruction from a list 😉 so not sure that "successor" will be a good unambiguous word here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marchof Javadoc added ✅

m.visitInsn(Opcodes.RETURN);

new SynchronizedFilter().filter(m, this);
assertEquals(m.instructions.get(12), from);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what means 12 or 16 here? Maybe we should enumerate instructions with comments?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marchof fixed ✅

import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.MethodNode;

public class SynchronizedFilterTest implements IFilterOutput {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my point of view in JaCoCo core our unit tests should create full coverage on its own (with all paths). The validation tests come on top. Currently it looks like the only purpose of the unit test is the fill ECJ case.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marchof I agree that code generated by javac should be captured in unit test and will do this.

However regarding

full coverage on its own (with all paths)

I doubt that generation of synthetic non-realistic bytecode in unit test or change of the code just to achieve full branch/path coverage makes sense. This is probably not that hard here for SynchronizedFilter, but I expect this to be really tedious in case of filter for try-with-resources.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marchof BTW we probably can exclude/separate verification tests from unit test coverage to enforce practice of completeness of unit tests.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marchof added bytecode that javac produces and negative test. However this does not lead to a full branch coverage (few are still missed), but at least main paths are covered ✅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Godin No problem. I assume we will need a (well tested) general purpose matcher facility soon. Then the number of branches within the specific filters will greatly reduce.

synchronized (lock) { // $line-monitorEnter$
nop(); // $line-body$
} // $line-monitorExit$
} // $line-after$
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about another nop() here? Otherwise we rely on the implicit return.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Such usage seems legitime to me given that implicit return is always presented and always assigned to such line as verified by another test. Moreover we already use such pattern in Target03 and EnumImplicitMethods, however in InterfaceDefaultMethodsTarget and InterfaceOnlyDefaultMethodsTarget we use explicit return statements 😆 And BTW I've just realized that we test "before" and "after" only in case of exceptions, but not in case of control structures 😉

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marchof added nop() here so that to have a symmetry with before marker at least in this test ✅

@@ -147,4 +148,8 @@ public void visitLookupSwitchInsnWithProbes(final Label dflt,
final int[] keys, final Label[] labels, final IFrame frame) {
}

public void accept(final MethodNode methodNode,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whooo, here we start mixing the ASM event and tree API. I consider this as a temporary hack until we fully move to the tree API ;-)

Nevertheless we should clearly document the method, especially the point in time when it is called.

Copy link
Member Author

@Godin Godin Mar 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Nothing is more permanent than the temporary" 😉 but future will show. We kind of already had such mix - see org.jacoco.core.internal.flow.LabelFlowAnalyzer#markLabels(org.objectweb.asm.tree.MethodNode), and exactly this gave me an idea of how to use ASM Tree API for filters without massive changes of everything, so this mix is worth it.

Copy link
Member Author

@Godin Godin Mar 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevertheless we should clearly document the method, especially the point in time when it is called.

@marchof Not sure I understand this point - do you want to document method or its call site or both? And in any case - do you have idea about wording?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Godin The method itself:

/**
 * This method can be overwritten to hook into the process of emitting the
 * instructions of this method as <code>visitX()</code> events.
 * 
 * @param methodNode
 *            the content to emit
 * @param methodVisitor
 *            A visitor to emit the content to. Note that this is not
 *            necessarily this visitor instance but some wrapper which
 *            calculates the probes.
 */

Copy link
Member Author

@Godin Godin Mar 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marchof done ✅

@@ -108,6 +108,7 @@ public void visitEnd() {
public void analyzeClass(final ClassReader reader) {
final ClassVisitor visitor = createAnalyzingVisitor(
CRC64.checksum(reader.b), reader.getClassName());
// TODO maybe we can SKIP_FRAMES here?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree in the comment but separate issue? On the other hand I'm not sure what the benefit would be as frames are ignored at analysis time.

Copy link
Member Author

@Godin Godin Mar 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marchof I believe that in this case FrameNode won't appear in methodNode.instructions and since they anyway not used for analysis, we won't be wasting CPU cycles to skip them in filters - see method SynchronizedFilter.Matcher#next(). While this doesn't play any (or at least not much) role for SynchronizedFilter, the same method is used in filter for try-with-resources (#500). And now that you reminded me about that - probably I can demonstrate observable impact on performance using its implementation prior to the last optimizations. I'm fine with postponing this, however this shouldn't be forgotten and in fact I almost forgot about this, but good that left this comment.

Copy link
Member Author

@Godin Godin Mar 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marchof I removed comment from this PR as anyway it will be presented in #500 , so that we can come back to this later. ✅

for (final TryCatchBlockNode n : methodNode.tryCatchBlocks) {
n.accept(methodVisitor);
}
for (int i = 0; i < methodNode.instructions.size(); i++) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a InsnList is traversed by index an internal array is created lazily. This can probably be avoided by using the linked list API.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marchof as link in Javadoc shows - this was inspired by implementation of org.objectweb.asm.tree.MethodNode#accept(org.objectweb.asm.MethodVisitor), so strictly speaking there is no change in behavior in regards to creation of array. But you right that we can avoid this. And if so, then I'm wondering - how to prevent regression of this? Probably it can/should be avoided for instrumentation too? Are there other places that trigger or might trigger its creation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact I was wrong - default implementation in org.objectweb.asm.tree.MethodNode#accept(org.objectweb.asm.MethodVisitor) does not construct array, don't know from where opposite came to my mind. Still wondering how to prevent such regressions, especially given that MethodNode#instructions is accessible by filters now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only suggestions that come to mind are:

  1. Add build-time class file scanning for use of InsnList.get(int).
  2. Create InsnList subclass that throws and use it in unittests. I guess this would require subclassing ClassAnalyzer to override visitMethod, which probably requires reimplementing Analyzer.analyzeClass/createAnalyzingVisitor.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More suggestions:

  1. A reflection based assertion that InsnList.cache is stays null
  2. A JaCoCo coverage check that method InsnList.toArray() is not executed during our tests.

But I woudn't worry too much right now - unless performance testing shows a bottle neck here.

Copy link
Member Author

@Godin Godin Mar 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And one more suggestion 😆 - InsnList as well as whole MethodNode comes to us via MethodSanitizer#visitEnd() in ClassProbesAdapter, where we can replace value of field instructions (that is public in MethodNode) by a new instance with overriden method toArray().

But I woudn't worry too much right now - unless performance testing shows a bottle neck here.

I've checked that our code doesn't trigger creation of array anymore.
So indeed - let's don't worry about this right now. ✅

@@ -25,6 +27,8 @@

private Instruction predecessor;

public AbstractInsnNode node;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it is always set after the instance is created this should be final and set within the constructor.

For consistency I would prefer to make it private with a public getter.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marchof indeed - this is a leftover of an early prototyping.

Copy link
Member Author

@Godin Godin Mar 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marchof fixed ✅

if (tryCatch.start == tryCatch.handler) {
continue;
}
final AbstractInsnNode to = new Matcher(tryCatch.handler).match();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Number of allocations of Matcher can be reduced here - can allocate and reuse just one for the whole lifetime of a method instead of allocation for TryCatchBlockNode.

@marchof
Copy link
Member

marchof commented Mar 28, 2017

@Godin Thanks for following up all my picky comments ;-) Let's merge this and continue with another filtering PR. I assume the overall implementation will improve step by step as we see more requirements and use cases.

@Godin
Copy link
Member Author

Godin commented Mar 28, 2017

@marchof Hooray! 😋 Will do squash and merge quickly and then will rebase #500 so that to continue with filter that doesn't require changes outside of filter 😉

@Godin Godin merged commit eaef191 into master Mar 28, 2017
@Godin Godin deleted the issue-501 branch March 28, 2017 17:20
@Godin Godin removed this from IN PROGRESS in Current work items Mar 28, 2017
@Godin Godin moved this from TODO to DONE in Filtering Mar 28, 2017
@jacoco jacoco locked as resolved and limited conversation to collaborators Jan 11, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
Filtering
  
Done
Development

Successfully merging this pull request may close these issues.

None yet

3 participants