-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Probes inserted at locals that will be overridden #767
Comments
@mkroghj Thanks for reporting this! In your example the first argument (slot 0) is overwritten. What compiler created such bytecode? |
Due to JVM spec this seems to be allowed (even if not emitted by javac or other compilers we're testing). |
This is a reduced program from the R8 compiler: R8 generates optimized code and will re-use locals when available. |
Out of curiosity: What was the original source code of the method? What is the purpose of
when the long value is not used at all? Or do you mean by reduced that you reduced the byte code? |
The bytecode stems from something larger where local 0 is used for further computation. I just kept removing until the error would disappear. |
Overwriting variables is not a problem as such. The issue seems to be specific to the situation where the last slot used by method parameters is overwritten with a two slot value (long or double). Here is a JUnit reproducer for the issue: https://gist.github.com/marchof/6561c2344c4b284582dc4c778590ca72 As simple solution would be to keep a safety distance of one slot to the method parameters:
@Godin WDYT? |
@marchof While I agree that we should fix this to not cause failure. As far as I understood - R8 is a shrinker and minificator, not compiler, so usage of it while gathering coverage is questionable. Sounds like usage of obfuscators which should not be used together with coverage tools. I'm wondering - can we detect when this distance is needed? to get rid of negative aspects of simple solution proposed by you. |
I encountered this same scenario. My situation is as follows: We have an annotation processor that that transforms code into continuations, providing async/await to java (https://github.com/ixaris/ixaris-oss/tree/master/commons-async/lib). This is causing this same issue. In my case, however, I cannot opt to transform the code after coverage.
In an attempt to fix this, I looked at #782 and modified some code suggested in that thread, to generate a hit method and mark hits by calling this method instead of embedding the code directly in each method. An additional benefit of this is that is it trivially easy to check if the hit was already recorded before updating the array, making better use of memory caching. This has resolved the issue without any slowdown. I will submit a pull request with these changes for your consideration. |
@bvella just my two cents
IMO main thing discussed in #782 (cache line) is not much related to main thing discussed here (local variable).
While maybe there is no slowdown in your scenario, such significant changes require validation on variety of scenarios - IMO reading of a field is different from reading from local variable in terms of performance. |
@bvella btw most interesting question - wondering why solution described here in #767 (comment) not suitable for you? |
@marchof what are the other options? As far as I can see we already do two traversals (first being
but not sure that any of these is worth the efforts. Therefore implemented your proposal in #893 and made following measurement: for |
@Godin My concern with my proposal also was runtime overhead (increased locals). Not sure though what the overhead with a typical code base would be. |
Hi @Godin, apologies for not replying earlier. The asynchronous transformer aggressively tries to reuse locals to reduce the size of the continuation. It is difficult to explain in a couple of lines but in a nutshell, stackless continuations work by recreating the method's locals and operand stack, which means every local and operand on the stack would need to be passed as a parameter. For the sake of reducing the required parameters, locals are aggressively reused. I see the following ways for jacoco to work with locals reuse:
IMHO, approach 2 is potentially the best because
I have a patch for approach 2, extracted from @Godin's branch https://github.com/jacoco/jacoco/commits/issue-782, generalised a bit to avoid specific instanceof checks. Internally, we are using this patch. If you think 3 and 4 are viable, I can have a go at them and run some benchmarks. |
@bvella We implemented approach 1) in #893. Can you please test this whether it works for your scenario? You either build the branch yourself of find the corresponding build here: https://ci.appveyor.com/project/JaCoCo/jacoco/builds/25107476/artifacts |
As described in #1412 it looks like only the Kotlin compiler may create such bytecode. |
When injecting probes into functions jacoco assumes that the arguments are not overridden as per the following JVM bytecode for class Test:
After instrumentation, the foo function looks as follows, which is not valid (therefore, the format is a bit different from javap):
Here, the probe is stored into local 1, however, local 1 is overriden by lstore 0 that takes up two locals.
Steps to reproduce
java -cp out.jar -javaagent:jacocoagent.jar=destfile=foo.txt,dumponexit=true,output=true Test
JaCoCo version: 0.8.3
Operating system: Linux
Tool integration: Command line (and gradle)
Expected behaviour
Running:
java -cp out.jar -javaagent:jacocoagent.jar=destfile=foo.txt,dumponexit=true,output=true Test
should ouput 15
Actual behaviour
The text was updated successfully, but these errors were encountered: