Bug 174588 - [compiler] Code in abstract class calls wrong overloaded method. Correct method is defined in the implemented interface.
Summary: [compiler] Code in abstract class calls wrong overloaded method. Correct met...
Status: VERIFIED FIXED
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 3.2.1   Edit
Hardware: PC All
: P3 normal (vote)
Target Milestone: 3.3 M6   Edit
Assignee: Maxime Daniel CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2007-02-18 23:40 EST by Devin Barnhart CLA
Modified: 2007-03-20 11:47 EDT (History)
1 user (show)

See Also:


Attachments
Suggested fix + test cases (3.42 KB, patch)
2007-02-21 08:09 EST, Maxime Daniel CLA
no flags Details | Diff
Fix + test cases (4.91 KB, patch)
2007-02-22 03:39 EST, Maxime Daniel CLA
no flags Details | Diff
Tests cases (augmented) (4.73 KB, patch)
2007-02-23 04:07 EST, Maxime Daniel CLA
no flags Details | Diff
Alternate fix (using findDefaultAbstractMethod) (1.88 KB, patch)
2007-02-23 07:29 EST, Maxime Daniel CLA
no flags Details | Diff
Suggested change (5.05 KB, patch)
2007-02-28 16:55 EST, Kent Johnson CLA
no flags Details | Diff
Suggested fix: Kent's + changes suggested above (8.32 KB, patch)
2007-03-02 05:52 EST, Maxime Daniel CLA
no flags Details | Diff
Single walk (8.57 KB, patch)
2007-03-02 15:39 EST, Kent Johnson CLA
no flags Details | Diff
Working single walk (9.59 KB, patch)
2007-03-02 16:58 EST, Kent Johnson CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Devin Barnhart CLA 2007-02-18 23:40:26 EST
Build ID: M20070212-1330

Steps To Reproduce:
1. Compile the following code with the Eclipse compiler and Sun’s compiler (1.4.2_11).
2. Run Test in both environments.  The Eclipse env will produce a stack overflow while the Sun environment will produce “set(1)”.
3. In the eclipse env, put a breakpoint in Int.set(long) to see what it’s doing.

NOTE: I found this in 3.2.2, but I can't choose this as the version found in.

public interface Num {
    void set(int value);
    void set(long value);
    void set(double value);
}
public abstract class Int implements Num {

    // Uncomment out the next line as a work-around
    //public abstract void set(int value);

    public void set(long value) {
        set((int)value);
    }

    public void set(double value) {
        set((int)value);
    }

}
public class Test extends Int {

    public void set(int value) {
        System.out.println("set(" + value + ")");
    }

    public static void main(String[] args) {
        Test test = new Test();
        test.set(1L);
    }
}


More information:
"void set(double)" must be defined to reproduce the bug.  This can also be "void set(float)", but not "void set(short)".

Workaround is to define "void set(int)" as an abstract method in Int
Comment 1 Maxime Daniel CLA 2007-02-19 09:25:46 EST
Reproduced with I20070205-1824.
Comment 2 Maxime Daniel CLA 2007-02-21 04:26:32 EST
I believe that ClassScope#findMethod should, for abstract classes, collect methods for implemented interfaces as well before narrowing the candidates list.
Comment 3 Maxime Daniel CLA 2007-02-21 08:09:59 EST
Created attachment 59468 [details]
Suggested fix + test cases

Kent, could you please review the suggested changes?
Comment 4 Kent Johnson CLA 2007-02-21 14:20:52 EST
I think we should duplicate the code at lines 1126-1127:

if (isCompliant14 && (receiverType.isAbstract()...
  return findDefaultAbstractMethod(receiverType, selector, argumentTypes...

and do it AFTER we have decided which method is the mostSpecificMethod.

We only have a problem when there is more than 1 valid match. When there is a single match in the abstract class Int, we find the interface method correctly.
Comment 5 Maxime Daniel CLA 2007-02-22 01:08:06 EST
Kent, I believe that if we wait until a most specific method is found, then set(double) will get elected and we won't find set(int). Hence my approach that adds a few methods at the end of the candidates in the only cases where the receiver is abstract.
My fix is amendable anyway since I miss the case when the interface is implemented by another class is in the receiver hierarchy. I'll make a new attempt asap.
Comment 6 Maxime Daniel CLA 2007-02-22 03:39:33 EST
Created attachment 59545 [details]
Fix + test cases

Covers deeper hierarchy cases.
Comment 7 Kent Johnson CLA 2007-02-22 10:52:29 EST
I don't buy it.

When there is only 1 choice, we still walk the interfaces and find set(int).

So why would it be any different if we picked the mostSpecific and then searched the interfaces.

Look at the places we call findDefaultAbstractMethod() from findMethod().
Comment 8 Maxime Daniel CLA 2007-02-22 15:05:39 EST
(In reply to comment #7)
> I don't buy it.
> 
> When there is only 1 choice, we still walk the interfaces and find set(int).

Looking at the latest call to findDefaultAbstractMethod (not the one guarded by 1.4 compatibility), I can see what you mean: it checks if it gets an exact match, then if and only if it doesn't it calls findDefaultAbstractMethod. So we could do that.
Could you please elaborate on the advantages of doing so?
Comment 9 Kent Johnson CLA 2007-02-22 16:22:17 EST
We want to avoid searching interfaces if we can.

Prior to 1.4, default abstract methods were added to any class that implemented an interface & didn't provide its own implementation of each method.

With this change, do we need any calls to findDefaultAbstractMethod ?

If every branch thru the method requires searching the interfaces, then can we removed all calls to findDefaultAbstractMethod ?
Comment 10 Maxime Daniel CLA 2007-02-23 04:05:07 EST
(In reply to comment #9)
> We want to avoid searching interfaces if we can.
I would only search interfaces for abstract classes.

> With this change, do we need any calls to findDefaultAbstractMethod ?
My change only targets abstract classes, hence the other cases when we currently call findDefaultAbstractMethod would continue to do so.

One concern I have with the separate call on findDefaultAbstractMethod for the case at hand is that the condition under which we need to call it is quite complex. We cannot limit ourselves to the case where there is a most specific method that is not an exact match. There are also cases where we have an invalid method returned by mostSpecificMethod that should still trigger findDefaultAbstractMethod (see test084 in the next attachment). This may trigger a few calls to findDefaultAbstractMethod that we would rather avoid.

I also feared that separating the methods coming from inherited classes and from interfaces would hurt, but thinking more about it, I could not come up with any case that would support that.

I'll craft a patch using findDefaultAbstractMethod and we'll discuss the respective merits of both approaches then.
Comment 11 Maxime Daniel CLA 2007-02-23 04:07:06 EST
Created attachment 59636 [details]
Tests cases (augmented)

Adds test084.
Comment 12 Maxime Daniel CLA 2007-02-23 07:29:24 EST
Created attachment 59643 [details]
Alternate fix (using findDefaultAbstractMethod)

Note that we cannot rely on the match test to avoid the call, since it won't reject set(double).
Comment 13 Maxime Daniel CLA 2007-02-28 01:44:05 EST
My preference goes to the patch of comment #6, which I'll release if there is no objection.
Comment 14 Kent Johnson CLA 2007-02-28 10:08:15 EST
I'll get back to you today
Comment 15 Kent Johnson CLA 2007-02-28 16:55:45 EST
Created attachment 60024 [details]
Suggested change
Comment 16 Maxime Daniel CLA 2007-03-02 05:16:09 EST
Assuming that deferring the interfaces walk as late as possible is always desireable, we would go with your patch. I still consider that since in the case of abstract classes we will walk their interfaces anyway, we have the choice to walk them upfront or later (my patch made no effort to avoid double walk hence it would have to be reworked anyway). Making an informed decision here would involve sorting out if a single pass on the complete collection is faster than two separate passes on partial collections, but I am unsure the question deserves so much more time.

Having said that, I have three further suggestions:
- receiverType.isInterface() is called multiple times throughout the method,
  including as an initialization value for mustBePublic; I would initialize
  receiverTypeIsInterface once and for all and use it everywhere;
- at the very beginning of the method, I would emphasize the role of the
  (unchanged) receiverType over currentType and replace the 'current*' pattern
  by a 'receiver*' pattern in the first if statement;
- the comment for the 'if (candidatesCount == 0)' statement is inaccurate, since
  we will grab an exact match for some abstract classes.

I will attach a patch that reflects those. Please let me know what you think.
Comment 17 Maxime Daniel CLA 2007-03-02 05:52:54 EST
Created attachment 60148 [details]
Suggested fix: Kent's + changes suggested above 

Note: must use patch 59636 for the test cases.
Comment 18 Kent Johnson CLA 2007-03-02 11:53:18 EST
looks good

as for the single walk vs. 2 walks - findMethod() is called all the time. We can afford to look at changes if they'll have a performance benefit.

How many calls to findDefaultAbstractMethod() can be removed if we add interface methods upfront? Do we slow down the hierarchy walk if more methods are in the found collection? Or do we not add interface methods because we found an implementation for them already?
Comment 19 Kent Johnson CLA 2007-03-02 15:39:13 EST
Created attachment 60207 [details]
Single walk

We could try this except that we need to find out why we're failing 2 tests in GenericTypeTest (#137 & #316).
Comment 20 Kent Johnson CLA 2007-03-02 16:58:40 EST
Created attachment 60210 [details]
Working single walk

Fixes 2 failures in GenericTypeTests
Comment 21 Maxime Daniel CLA 2007-03-05 10:03:18 EST
I ran performance tests today that gave a slight advantage to https://bugs.eclipse.org/bugs/attachment.cgi?id=60148&action=view over https://bugs.eclipse.org/bugs/attachment.cgi?id=60210&action=view, so I will release the former if Kent is OK with that.
(Methodology was: magnify findMethod by repeating calls to its implementation, use  FullSourceWorkspaceBuildTests#testFullBuildDefault, repeat ten times and collect results.)
Comment 22 Kent Johnson CLA 2007-03-05 11:29:23 EST
its fine with me
Comment 23 Maxime Daniel CLA 2007-03-06 04:21:19 EST
Released for 3.3 M6.
Comment 24 Eric Jodet CLA 2007-03-20 11:23:52 EDT
Verified for 3.3 M6 using build I20070320-0010