-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Exclude from a report a part of bytecode that compiler generates for a try-with-resources #500
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
Conversation
@Godin Count me deeply impressed!!! And I'm very sorry that I couldn't immediately jump into both PRs. So+Mo are travel dates, so I hope to study it in detail then. |
@marchof no worry, will be cool to get all this on board, but no need to rush - let's do a proper careful review and testing. Slowly, but at least we are progressing on this big subject. And I personally start to see the light at the end of the tunnel, which wasn't the case few months ago. FYI next week I'm also kind of traveling - will attend DevoxxUS, and hope to not be offline all the time. |
@Godin It would be useful if the members of |
@bjkail Hey, glad to see you here! Actually was about to ask if you're interested in this subject and if so maybe you could leave some valuable comments as before 😉 And here they are 😋 You are right on all points - let's cleanup and harden this PoC. Also wondering WDTY about approach in general? About it we'd better add explanatory comments also. As well as estimate impact on performance of analysis - can imagine some optimizations... but traded them on the correctness and attempt to keep relative simplicity of code for now. Anyway thank you! we really appreciate your help 👍 |
@Godin The overall approach seems reasonable to me of skipping ignored instructions in Last time I thought about t-w-r, I was thinking to require the t-w-r close sequence be replicated at the end of the try block and each catch block (including Throwable) to ensure it was either written by a compiler or fully correctly hand-written. My motivation was to avoid filtering out a badly hand-written close sequence (e.g., at the end of try block only but not all catch blocks or missing Throwable block, etc.), but the algorithm to do the rigid matching seemed quite complex, especially for custom finally blocks and nested t-w-r (thus my suggested testcases). However, I am now thinking your approach is fine. There is some risk of false positive (matching a hand-written close sequence), but the risk is low since the close sequence is relatively complex, and we can leave detecting badly hand-written close sequences (like missing close sequence on some possible code path) to static analyzers like FindBugs. |
As an anecdote, last time I investigated t-w-r was because we kept regressing coverage in classes that use t-w-r heavily because drops go unnoticed due to all the uncovered generated code. I tried forcing all the generate code paths to be executed (our code/tests made it easy to force all the conditions: null resource, exception with null resource, exception with non-null resource, close exception, exception and close exception), but I ultimately gave up when I discovered javac emits completely dead code:
So, I think this will be a great feature to have in JaCoCo :-). |
50d98bc
to
1adc2d1
Compare
Indeed - I forgot that 3.8 Gb of 6865 JARs in my local Maven repository also includes some JARs with sources. However sources are not downloaded by default, so not that much: 309 artifacts with sources, their corresponding JARs with classes account for 125 Mb, and diversity also maybe not that big - see log.txt
ECJ filter is not triggered by any of the above scanned JARs. In case of javac first instruction of a first ignored block is a line number corresponding to Doing the same test using For bigger set of artifacts with sources will need to think about how to obtain them effectively: don't know how many sources is stored in cache in our corporate proxy as they are not downloaded by default; and anyway will need to think about how to get list no matter from where to download - central or proxy. |
assertLine("returnInBody.open", ICounter.FULLY_COVERED); | ||
// without filter next line has branches: | ||
// TODO with javac 1.8.0_31 part of return instructions belong to close | ||
// assertLine("returnInBody.close", ICounter.EMPTY); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case of
private static Object returnInBody() throws IOException {
try ( // $line-returnInBody.try$
Closeable r = new Resource() // $line-returnInBody.open$
) {
return read(r); // $line-returnInBody.return$
} // $line-returnInBody.close$
}
we correctly find instructions, but javac 7 as well as javac 8 up to 8u77 (tested public updates) generates:
LINENUMBER $line-returnInBody.return$
...
ASTORE
LINENUMBER $line-returnInBody.close$
... // close resource
ALOAD
ARETURN
LINENUMBER $line-returnInBody.try$
... // save exception
LINENUMBER $line-returnInBody.close$
... // close resource
while javac starting from 8u92 (notice additional LINENUMBER
before ALOAD
) as well as latest 9 EA:
LINENUMBER $line-returnInBody.return$
...
ASTORE
LINENUMBER $line-returnInBody.close$
... // close resource
LINENUMBER $line-returnInBody.return$
ALOAD
ARETURN
LINENUMBER $line-returnInBody.try$
... // save exception
LINENUMBER $line-returnInBody.close$
... // close resource
Looks like this relates to JDK-8134759 (log of bisect - bisect.txt)
There is no such problem when finally
is present, but this case is covered by another existing test.
So I'm wondering what can we do with this test?
Travis provides JDK 8u31 by default and we also test JDK 8 EA. Can imagine that for the bytecode version 7 generated by javac we will be testing old behavior, while will be testing and using updated stable JDK 8 in Travis as well as locally.
But maybe @marchof you have some other thoughts/ideas?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Godin Parse java.version
, and then skip or adjust the asserts? We have precedent in BadCycleInterfaceTest
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bjkail java.version
is about version of JRE and BadCycleInterfaceTest
is exactly about runtime behavior. This will work here if we assume that execution and compilation of tests use the same JDK version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Godin You're right. Is that too fragile? If so, the only other option that comes to mind is to use ASM to detect the missing line number. That would weaken the test a bit, but perhaps it's better than nothing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@marchof Excuse me for the delay with reply - was at DevoxFR, where BTW session "Java Code Coverage Mechics" was a quite success.
On a subject:
@bjkail java.version
maybe not so fragile.
ASM to detect the missing line number
is fragile in terms that it allows compiler to regress on this.
Maybe our integration tests for filtering are too specific.
I personally like such specificity for the following reasons
- not all examples here reach full coverage (no uncovered lines and branches) and also IMO will not reach in case of future filters
- consistency across tests
- allows to see exactly what user will see
- allows to catch regressions in compilers
We already have plenty of validation test for expected covered/missed lines
Apparently not for such tricky control flows as shown here 😉
Do you see any troubles to simply bump version of javac 8 up to latest update as in my initial proposal - "past is past"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Godin We have a large user base out there where older JDK versions still might be in use. The primary purpose of the integration tests for filters should be to ensure that the filters fulfill their purpose. I would imagine filter integration tests consisting of source code only (maybe with annotated lines for the intentionally un-covered ones).
My primary concern is maintainability. If you think we can handle the specific checks in the long run, I can live with the current approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a large user base out there where older JDK versions still might be in use.
I haven't said that we should stop testing all old JDK versions - only range of JDK 8 till 8u92.
JDK 7 will be testing the same bytecode that generates JDK 8 from this range. And this range was shown to be working with other existing test cases.
I wish that we run tests against range of all public updates of all supported JDK versions, but this is quite overkill and we are not doing this anyway 😉
Some other options:
- version of compiler can be passed by Maven into tests, but then such conditional assertions should be disabled in IDEs
- tests can perform execution of compiler so that they will precisely know version independently from build system and IDEs
But all this quite out of scope of this PR. So for now I excluded assertion on this line only for bytecode version 8 - this is at least better that current TODO-comment and we can come back to this discussion later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
excluded assertion on this line only for bytecode version 8
arr, in fact even this is fragile as one can ask generation of bytecode version 7 or 8 using JDK 9
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Experimented with this over past weeks and few times experienced fragility on
mvn clean install -Dbytecode.version=1.7
using JDK 8, so finally in favor of @bjkail suggestion to assume that execution and compilation of tests is done with same JDK.
✅
* initialized using new
|
||
* $closeResource containing |
||
* null check of primaryExc and calls to methods
|
||
* addSuppressed and close is used when numbers of
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo - numbers
instead of number
@marchof I scanned bigger subset of Maven Central - latest versions of some artifacts within group Nevertheless 9753 try-with-resources statements were filtered correctly from ≈20 Gb of JAR files with classes. And this time ECJ filter was triggered quite a good amount of times. Case that was already presented in our tests and not filtered - when body of try-with-resources is empty
was encountered in
Another funny case that was already presented in our tests and not filtered - when try-with-resources contains only
was encountered in
Not explicitly presented in out tests, but a kind of variation of previous and not filtered - when there is no non-exceptional path from a body as for example
was encountered in
An interesting case where source code contains try-with-resources, but retrolambda removes call to
An interesting case where AspectJ adds try-with-resources into bytecode was encountered in
All in all: I satisfied by results and would like to merge this PR. We can deal with cases of an empty body and absence of non-exceptional path in a separate PR. If you agree I'll squash and merge into master. |
@Godin Awesome work, Evgeny! I'm absolutly curious about the exact test setup. And yes, we can add corner cases later. I was already about to ask about the performance of the new, way more readable matching code. As there is no negative impact -> yes, please merge this! |
@Godin , when do you plan to release this ? |
@romani please see already existing answers on such question: |
https://github.com/jacoco/jacoco/wiki/filtering-JAVAC.TRYWITH