Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.
Bug 395231 - [introduce indirection] ClassCastException when introducing indirection on method in generic class
Summary: [introduce indirection] ClassCastException when introducing indirection on me...
Status: RESOLVED FIXED
Alias: None
Product: JDT
Classification: Eclipse Project
Component: UI (show other bugs)
Version: 4.2.1   Edit
Hardware: All All
: P3 normal (vote)
Target Milestone: 4.4 M4   Edit
Assignee: Nikolay Metchev CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2012-11-27 19:58 EST by Milos Gligoric CLA
Modified: 2013-11-28 03:55 EST (History)
6 users (show)

See Also:
noopur_gupta: review+
noopur_gupta: review+


Attachments
proposed patch (5.44 KB, patch)
2013-10-04 05:09 EDT, Nikolay Metchev CLA
no flags Details | Diff
2nd attempt (7.61 KB, patch)
2013-10-07 11:58 EDT, Nikolay Metchev CLA
no flags Details | Diff
2nd attempt (4.59 KB, patch)
2013-10-07 12:04 EDT, Nikolay Metchev CLA
no flags Details | Diff
3rd time's a charm (5.76 KB, patch)
2013-10-09 08:17 EDT, Nikolay Metchev CLA
no flags Details | Diff
Lets try this (7.22 KB, patch)
2013-10-15 08:11 EDT, Nikolay Metchev CLA
no flags Details | Diff
unit test (2.17 KB, patch)
2013-10-23 11:55 EDT, Nikolay Metchev CLA
no flags Details | Diff
more unit tests + more generic fix (7.58 KB, patch)
2013-10-24 10:44 EDT, Nikolay Metchev CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Milos Gligoric CLA 2012-11-27 19:58:08 EST
Steps to reproduce:
1. Invoke "Introduce Indirection" on 'g' method in code below
2. ClassCastException is throws (see part of the log below)

class IntroduceIndirectionBug3<T extends IntroduceIndirectionBug3<T>> {
    // Invoke "Introduce Indirection" on 'g'
    void g(T t) {
    }

    void f(T t) {
        t.g(null);
    }
}

(Thanks to Yilong Li for helping with the bug report.)

java.lang.ClassCastException: org.eclipse.jdt.internal.core.TypeParameter cannot be cast to org.eclipse.jdt.core.IType
	at org.eclipse.jdt.internal.corext.refactoring.code.IntroduceIndirectionRefactoring.checkFinalConditions(IntroduceIndirectionRefactoring.java:545)
	at org.eclipse.ltk.core.refactoring.CheckConditionsOperation.run(CheckConditionsOperation.java:85)
	at org.eclipse.ltk.core.refactoring.CreateChangeOperation.run(CreateChangeOperation.java:121)
	at org.eclipse.ltk.core.refactoring.PerformChangeOperation.run(PerformChangeOperation.java:209)
	at org.eclipse.core.internal.resources.Workspace.run(Workspace.java:2344)
	at org.eclipse.ltk.internal.ui.refactoring.WorkbenchRunnableAdapter.run(WorkbenchRunnableAdapter.java:87)
	at org.eclipse.jface.operation.ModalContext$ModalContextThread.run(ModalContext.java:121)
Comment 1 Martin Mathew CLA 2013-01-24 06:19:52 EST
Issue is reproducible using Build id: I20130115-1300. The refactoring throws ClassCastException.
Comment 2 Nikolay Metchev CLA 2013-10-04 05:09:44 EDT
Created attachment 236095 [details]
proposed patch

This contribution complies with http://www.eclipse.org/legal/CoO.php
Comment 3 Dani Megert CLA 2013-10-04 05:11:37 EDT
Noopur, please review whenever you have some spare time during your Java 8 work.
Comment 4 Noopur Gupta CLA 2013-10-07 08:52:47 EDT
Comment on attachment 236095 [details]
proposed patch

With the patch, the refactoring results in compilation errors if "Redirect all method invocations" is checked.
Please note that in the given example, different methods are created depending on the checked state of the above check box, which is wrong.
Comment 5 Nikolay Metchev CLA 2013-10-07 10:48:23 EDT
Hello Noopur,
I am a little over my head with this one. Is there an easy way to convert a 
org.eclipse.jdt.core.dom.TypeBinding into a org.eclipse.jdt.core.dom.TypeParameter.

In the example in the bug report the TypeBinding is <T extends Test<T>>

I found some code that attempted to do that in IntroduceFactoryRefactoring.copyTypeParameters()
but it failed for the example above.
Comment 6 Nikolay Metchev CLA 2013-10-07 11:58:49 EDT
Created attachment 236184 [details]
2nd attempt

This contribution complies with http://www.eclipse.org/legal/CoO.php

I figured out a different way to fix this bug that doesn't create code that doesn't compile. Please take a look and let me know if it is correct.
Comment 7 Nikolay Metchev CLA 2013-10-07 12:04:14 EDT
Created attachment 236185 [details]
2nd attempt

This contribution complies with http://www.eclipse.org/legal/CoO.php

for some reason .gitignore files leaked into the patch.
Comment 8 Noopur Gupta CLA 2013-10-08 08:07:46 EDT
Comment on attachment 236185 [details]
2nd attempt

With this patch:

- Refactoring:
public class Test<T extends Test<T>> {
    void foo(T t) {
    }

    void f(T t) {
        t.foo(null); // Invoke "Introduce Indirection" on 'foo'
    }
}

Adds the new method:
public static <T extends Test<T>> void foo(Test<T> test, T t) {
	test.foo(t);
}

The type of the first parameter is 'Test<T>', which is fine as #foo is present in 'Test<T>'.
But in the original code, the invocation expression has type 'T' (which extends Test<T>).

Markus, is the result correct or should it be:
public static <T extends Test<T>> void foo(T test, T t) {
	test.foo(t);
}

- In the above example, select "foo" in "t.foo(null);" and invoke Introduce Indirection.
Uncheck "Redirect all method invocations" and click OK => We get CCE.

- Nikolay, you can also try some examples with checked and unchecked state of "Replace all..", selecting method declaration and method invocation, with the selected method as static and non-static, with different target types chosen in refactoring wizard, navigating in the refactoring wizard with 'Preview' and 'Back' buttons etc.
Comment 9 Nikolay Metchev CLA 2013-10-09 08:17:44 EDT
Created attachment 236271 [details]
3rd time's a charm

This contribution complies with http://www.eclipse.org/legal/CoO.php

Oops... I am not doing too well am I? Sorry about that. Lets try this patch.
Comment 10 Noopur Gupta CLA 2013-10-15 05:26:07 EDT
Comment on attachment 236271 [details]
3rd time's a charm

With this patch, select "foo" in "t.foo(null);" and invoke Introduce Indirection.
Uncheck "Redirect all method invocations" and click Preview. 
Click Back and then click OK.
We get the exception:
java.lang.IllegalArgumentException: Node is not inside the AST
	at org.eclipse.jdt.core.dom.rewrite.ASTRewrite.validateIsCorrectAST(ASTRewrite.java:582)
	at org.eclipse.jdt.core.dom.rewrite.ASTRewrite.createTargetNode(ASTRewrite.java:698)
	at org.eclipse.jdt.core.dom.rewrite.ASTRewrite.createMoveTarget(ASTRewrite.java:741)
	at org.eclipse.jdt.internal.corext.refactoring.code.IntroduceIndirectionRefactoring.updateMethodInvocation(IntroduceIndirectionRefactoring.java:989)
...

Before the next patch, please make sure you have tested various scenarios (some are already mentioned in comment #8).
Comment 11 Nikolay Metchev CLA 2013-10-15 08:11:13 EDT
Created attachment 236477 [details]
Lets try this

This contribution complies with http://www.eclipse.org/legal/CoO.php

Hello Noopur,
This latest problem you found is technically unrelated to the CCE in the original bug. I don't know if it should be fixed as a separate patch. 
Anyway. I have submitted a patch which addresses both.
Comment 12 Noopur Gupta CLA 2013-10-17 07:17:59 EDT
(In reply to Nikolay Metchev from comment #11)
> This latest problem you found is technically unrelated to the CCE in the
> original bug. I don't know if it should be fixed as a separate patch. 
> Anyway. I have submitted a patch which addresses both.
Thanks for the patch Nikolay. The IAE problem is not related to CCE. But since we found it while fixing CCE and there is no other bug for this, let us fix it along with this bug itself.

(In reply to Noopur Gupta from comment #10)
> select "foo" in "t.foo(null);" and invoke Introduce Indirection.
> Uncheck "Redirect all method invocations" and click Preview. 
> Click Back and then click OK.
> We get the exception:
> java.lang.IllegalArgumentException: Node is not inside the AST

When we click Preview, we remove the cached CU rewrite from the cache in #createChangeAndDiscardRewrite(ICompilationUnit).
But fSelectionMethodInvocation still belongs to the discarded CU rewrite's AST.
Hence on clicking Back and OK/Preview again, when we need the CU rewrite for fSelectionMethodInvocation, #getCachedCURewrite(ICompilationUnit) creates a new CU rewrite with a new CUNode/AST. And now, the existing AST of fSelectionMethodInvocation is different from the new CU rewrite's AST -> IAE.

Either we should not discard the CU rewrite from the cache or, #getCachedCURewrite should use the existing CUNode/AST of fSelectionMethodInvocation while creating the new CU rewrite.

Nikolay, added a null check for typeBinding in #getExpressionType(MethodInvocation) and released your patch for CCE with:
http://git.eclipse.org/c/jdt/eclipse.jdt.ui.git/commit/?id=b74c4b80110ff14b76d1482504cba63a96d3d309

Released fix for IAE with #getCachedCURewrite using existing AST of fSelectionMethodInvocation as:
http://git.eclipse.org/c/jdt/eclipse.jdt.ui.git/commit/?id=ace0a97be4318ccbde62328ddc0b8374bf710d7c
Comment 13 Nikolay Metchev CLA 2013-10-17 07:56:04 EDT
Thanks Noopur,
I am still learning... Sorry about the multiple incorrect attempts.
Comment 14 Markus Keller CLA 2013-10-23 10:29:10 EDT
(In reply to Noopur Gupta from comment #12)
> Nikolay, added a null check for typeBinding in
> #getExpressionType(MethodInvocation) and released your patch for CCE with:
> http://git.eclipse.org/c/jdt/eclipse.jdt.ui.git/commit/
> ?id=b74c4b80110ff14b76d1482504cba63a96d3d309

This only works for the original example, where T extends IntroduceIndirectionBug3, but it's not correct in the general case:

package snippet;

class IntroduceIndirectionBug4<T extends M> {
	// Invoke "Introduce Indirection" on 'm2.m()'
    void h(M2 m2) {
    	m2.m();
    }

    void f(T t) {
        t.m();
    }
}

class M {
	void m() { }
}

class M2 extends M {
	void m() { }
}

Fixed this with http://git.eclipse.org/c/jdt/eclipse.jdt.ui.git/commit/?id=d6ed4ae23d3c8539db8731b3658c9bf520c513d2

Nikolay, could you please attach another test for this?
Comment 15 Nikolay Metchev CLA 2013-10-23 11:55:23 EDT
Created attachment 236811 [details]
unit test

This contribution complies with http://www.eclipse.org/legal/CoO.php

Here is a unit test for the test case you have presented. Looking at your code it made me come up with more test cases. This one for example now complains that the refactoring is not possible. However if you swap Runnable and M it does work.:

package p;

class Test<T extends Runnable & M> {
	// Invoke "Introduce Indirection" on 'm2.m()'
    void h(M2 m2) {
    	m2.m();
    }

    void f(T t) {
        t.m();
    }
}

interface M {
	void m();
}

interface M2 extends M {
	void m();
}
Comment 16 Nikolay Metchev CLA 2013-10-24 10:44:13 EDT
Created attachment 236846 [details]
more unit tests + more generic fix

This contribution complies with http://www.eclipse.org/legal/CoO.php

I figured out a more generic fix.
Comment 17 Markus Keller CLA 2013-10-28 09:12:53 EDT
(In reply to Nikolay Metchev from comment #15)
> class Test<T extends Runnable & M> {

Yeah, that's the point where we usually give up. To resolve this, we would have to re-write too much of the implementation. And we would complicate the code for a use case that is not expected to occur frequently enough to justify the cost. And luckily, we even detect the problem before we produce compile errors.
Comment 18 Noopur Gupta CLA 2013-11-28 03:55:17 EST
(In reply to Nikolay Metchev from comment #16)
> Created attachment 236846 [details] [diff]
> more unit tests + more generic fix

The patch looks fine though it traverses the complete hierarchy of each type bound until it finds the method, but that is required and would happen for very rare use cases. Released the fix and tests with:
http://git.eclipse.org/c/jdt/eclipse.jdt.ui.git/commit/?id=36efc3dc84f50d4a0a35ccc6e62305532fd82c78