Bug 460993 - [compiler] Incremental build not always reports the same errors (type cannot be resolved - indirectly referenced)
Summary: [compiler] Incremental build not always reports the same errors (type cannot ...
Status: VERIFIED FIXED
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 4.5   Edit
Hardware: All All
: P3 normal (vote)
Target Milestone: 4.5 M6   Edit
Assignee: Markus Keller CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2015-02-26 15:25 EST by Markus Keller CLA
Modified: 2015-03-18 04:27 EDT (History)
5 users (show)

See Also:
jarthana: review? (stephan.herrmann)


Attachments
2 test projects (4.29 KB, application/x-zip)
2015-02-26 15:25 EST, Markus Keller CLA
no flags Details
2 test projects v2 (5.52 KB, application/x-zip)
2015-02-27 07:03 EST, Markus Keller CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Markus Keller CLA 2015-02-26 15:25:14 EST
Created attachment 251154 [details]
2 test projects

We're seeing a strange ping-pong effect when trying to compile master of org.eclipse.releng.tools and org.eclipse.jgit.

On every second incremental compilation, we see this error in org.eclipse.releng.tools.git.GitCopyrightAdapter:
"The type java.lang.AutoCloseable cannot be resolved. It is indirectly referenced from required .class files"

The attached self-contained projects reproduce the problem (needs a 1.5 and a 1.7 Installed JRE). Just open GitCopyrightAdapter, add a space, and save. Hint: It's easier to debug without a reconciler thread: Use Open With > Text Editor.


In this code path, we get the IProblem#IsClassPathCorrect as expected:
	at org.eclipse.jdt.internal.compiler.lookup.UnresolvedReferenceBinding.resolve(UnresolvedReferenceBinding.java:104)
	at org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.resolveType(BinaryTypeBinding.java:187)
	at org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.superInterfaces(BinaryTypeBinding.java:1805)
	at org.eclipse.jdt.internal.compiler.lookup.Scope.findMethodInSuperInterfaces(Scope.java:1834)
	at org.eclipse.jdt.internal.compiler.lookup.Scope.findDefaultAbstractMethod(Scope.java:1113)
	at org.eclipse.jdt.internal.compiler.lookup.Scope.findMethod0(Scope.java:1630)
	at org.eclipse.jdt.internal.compiler.lookup.Scope.findMethod(Scope.java:1525)
	at org.eclipse.jdt.internal.compiler.lookup.Scope.getMethod(Scope.java:2800)
	at org.eclipse.jdt.internal.compiler.ast.MessageSend.findMethodBinding(MessageSend.java:890)
	at org.eclipse.jdt.internal.compiler.ast.MessageSend.resolveType(MessageSend.java:704)
	at org.eclipse.jdt.internal.compiler.ast.Expression.resolve(Expression.java:1020)
	at org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration.resolveStatements(AbstractMethodDeclaration.java:638)
	at org.eclipse.jdt.internal.compiler.ast.MethodDeclaration.resolveStatements(MethodDeclaration.java:307)
	at org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration.resolve(AbstractMethodDeclaration.java:548)
	at org.eclipse.jdt.internal.compiler.ast.TypeDeclaration.resolve(TypeDeclaration.java:1188)
	at org.eclipse.jdt.internal.compiler.ast.TypeDeclaration.resolve(TypeDeclaration.java:1301)
	at org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration.resolve(CompilationUnitDeclaration.java:590)
	at org.eclipse.jdt.internal.compiler.Compiler.process(Compiler.java:803)
	at org.eclipse.jdt.internal.compiler.ProcessTaskManager.run(ProcessTaskManager.java:141)
	at java.lang.Thread.run(Thread.java:745)


In the next incremental compile pass, the builder decides to build the Messages class as well. That already looks wrong to me. But as a side effect, this computes the supertypes of type String.

In MethodScope(Scope).findExactMethod(ReferenceBinding, char[], TypeBinding[], InvocationSite) line: 1215, the code reads:
	
	if (isPossibleSubtypeOfRawType(argumentTypes[i]))
		return null;

In the case where the Messages class has not been compiled before, the isPossibleSubtypeOfRawType(..) call returns true for argumentType=String (because the supertypes of String have not been resolved yet). As a consequence, Scope#findExactMethod(..) return null, and the findMethod0(..) shown above eventually resolves the type Repository and reports the missing supertype AutoCloseable.

In the other case, where the compilation of class Messages already resolved the supertypes of String, the isPossibleSubtypeOfRawType(..) call returns false, and the findExactMethod(..) call returns the "resolve" method binding without ever checking the supertypes of type Repository.
=> Error is not reported in this case.

Two questions to investigate:

1. Why does incremental compilation compile class Messages again? Is it because the previous compilation aborted due to an incomplete build path, so the next build will be a full build?

2. Why does findMethod0(..) resolve the receiver type and report errors, but findExactMethod(..) sometimes returns without ever having resolved the receiver type? If the error is worth reporting at all, then it should be reported all the time.
Comment 1 Jay Arthanareeswaran CLA 2015-02-27 02:52:16 EST
(In reply to Markus Keller from comment #0)
> Two questions to investigate:

Thanks for the detailed report, Markus!

> 1. Why does incremental compilation compile class Messages again? Is it
> because the previous compilation aborted due to an incomplete build path, so
> the next build will be a full build?

Yep, This line in IncrementalImageBuilder (line: 84) adds all source files in the affected project when a build error was previously reported:

		if (this.javaBuilder.hasBuildpathErrors()) {
                  ....

> 2. Why does findMethod0(..) resolve the receiver type and report errors, but
> findExactMethod(..) sometimes returns without ever having resolved the
> receiver type? If the error is worth reporting at all, then it should be
> reported all the time.

This looks like a problem and needs closer look.
Comment 2 Markus Keller CLA 2015-02-27 07:03:08 EST
Created attachment 251165 [details]
2 test projects v2

I was a bit too eager stripping out unnecessary files, and I accidentally removed the .settings that set the compiler compliance.

Here's a version that is even easier to debug, since it avoids the MessageSend in Messages.java, and it includes the launch config I used to verify that javac doesn't have this problem.
Comment 3 Eclipse Genie CLA 2015-02-27 07:04:54 EST
New Gerrit change created: https://git.eclipse.org/r/42893
Comment 4 Markus Keller CLA 2015-02-27 07:11:56 EST
This could be easier to fix than I initially thought. Bug 360164 laid all the ground work to fix such problems. I think this is just an oversight where the "this.environment.mayTolerateMissingType = true;" range was chosen a bit too narrow.

BinaryTypeBinding#superclass() probably has the same problem -- it also calls "resolveType(..)" outside of the "mayTolerate" block.
Comment 5 Jay Arthanareeswaran CLA 2015-02-27 07:17:50 EST
Thanks Markus, for taking this up!
Comment 6 Markus Keller CLA 2015-02-27 09:14:38 EST
Looked too easy... Gerrit reports two failures for tests that have been added for bug 164622:
MultiProjectTests.test102_missing_required_binaries
MultiProjectTests.test103_missing_required_binaries

I think I investigated into the wrong direction. For javac 1.7, the interface AutoCloseable seems to be a very special beast that is also supported in cross-compilation mode [1], although it really shouldn't be.

When I change class org.eclipse.jgit.lib.Repository in the example projects to implement java.nio.file.CopyOption or java.nio.file.WatchKey, and then compile the 1.5 project org.eclipse.releng.tools with:

javac -source 1.5 -target 1.5 -bootclasspath C:\java\jdk5\jre\lib\rt.jar -sourcepath src -d classes -classpath ..\org.eclipse.jgit\bin src\org\eclipse\releng\tools\git\*

.. then I get a compile error form javac as well:

javac 1.7.0_76
src\org\eclipse\releng\tools\git\GitCopyrightAdapter.java:8: error: cannot access CopyOption
		repo.resolve("Head"); 
		    ^
  class file for java.nio.file.CopyOption not found
1 error

=> I now think the compile error is actually correct, and we have to make sure that findExactMethod(..) doesn't take the shortcut that avoids resolving the supertypes of the receiver.
Comment 7 Markus Keller CLA 2015-02-27 10:40:06 EST
Some other bugs that always crop up when I look at this code: Bug 164622, bug 275471, bug 206930, bug 269883, bug 360164. It looks like we don't have crystal clear rules as to when supertypes of a binary type need to be resolved and when problems in there can be tolerated.

This bug goes away when I revert bug 275471, i.e. when I comment out this line in Scope#isPossibleSubtypeOfRawType(TypeBinding), which effectively removes the "Possible" in the method's name:

	if (!currentType.isHierarchyConnected()) return true; // do not fault in super types right now, so assume one is a raw type

Then, the "repo.resolve("Head");" call consistently compiles without error. Which makes sense, since the supertypes of type Repository can't influence the validity of the method call at hand.

I'm inclined to just revert the fix for bug 275471. That looks like a hack and the bug's comments don't justify why this should be correct. I think the fix for bug 360164 actually fixes the problem from bug 275471. I'll prepare another Gerrit review for that.
Comment 8 Stephan Herrmann CLA 2015-02-28 10:19:18 EST
(In reply to Markus Keller from comment #6)
> I think I investigated into the wrong direction. For javac 1.7, the
> interface AutoCloseable seems to be a very special beast

Right. You probably saw that the fix in bug 360164 specifically protects the code newly introduced via bug 349326, which eagerly searches for AutoCloseable or Closeable in the ancestry.
Later BitUninternedType joined the party.

> that is also
> supported in cross-compilation mode [1], although it really shouldn't be.

If you refer to the mix of 1.5 and 1.7 projects: your observation is true only for AutoCloseable, the other two should be detected also at 1.5.
Comment 9 Markus Keller CLA 2015-03-02 13:34:19 EST
OK, I've written a test that fails against master and passes when I remove the hack for bug 275471: https://git.eclipse.org/r/#/c/42893/2

I'll release this if Gerrit reports green tests. With this change, ECJ behaves the same as javac for the very specific example from comment 0 (with AutoCloseable as supertype of Repository). For other supertypes, javac reports an error, but ECJ won't (to keep cases like bug 360164 working).
Comment 10 Markus Keller CLA 2015-03-02 15:34:11 EST
For patch set 2, Gerrit reports that MethodVerifyTest#test124() fails for 1.5 and 1.6, but NOT for 1.7 and 1.8.

This looked fishy again, and indeed I can construct an example where the compiler is definitely wrong even without my change. Just add another file "A.java" to the test, so that the hierarchy of "String" already gets "connected" when A.java is compiled:

public void test124b() {
	this.runConformTest(
		new String[] {
			"A.java",
			"class A {\n" +
			"  public Object o = \"\";\n" +
			"}\n",
			"X.java",
			...

In 1.5 and 1.6 compliance mode, this makes the test fail because it produces compile errors. (In 1.7 and 1.8 the test passes compilation and fails in the execution phase because it tries to run the wrong CU -- but that doesn't invalidate my argument about the wrong compilation.)

I'm a bit lost here. I still think that the "isHierarchyConnected()" call is wrong, but OTOH, I don't know how to fix this new problem.
Comment 11 Markus Keller CLA 2015-03-04 14:14:25 EST
There's no way around removing the wrong isHierarchyConnected() call.

But I think a good way to keep the hacks in Scope#computeCompatibleMethod(..) for bug 207935 happy is to make sure Scope#getMethod(TypeBinding, char[], TypeBinding[], InvocationSite) doesn't return a ProblemMethodBinding from findExactMethod(..), but instead tries harder via #findMethod(..). See patch set 3.
Comment 13 Markus Keller CLA 2015-03-04 15:00:38 EST
.
Comment 14 Sasikanth Bharadwaj CLA 2015-03-18 04:27:23 EDT
Verified for 4.5 M6 using I20150316-2000 build