Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.
Bug 343060 - Method.getMethods() returns different methods (compared to standard compiler) for public/non-public inheritance hierarchies
Summary: Method.getMethods() returns different methods (compared to standard compiler)...
Status: RESOLVED NOT_ECLIPSE
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 3.6.2   Edit
Hardware: PC Windows XP
: P3 major (vote)
Target Milestone: 4.14 M3   Edit
Assignee: Stephan Herrmann CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2011-04-17 06:29 EDT by Bartosz Bankowski CLA
Modified: 2019-11-09 11:53 EST (History)
6 users (show)

See Also:


Attachments
Sources to reproduce the problem (1.38 KB, application/octet-stream)
2011-04-17 06:32 EDT, Bartosz Bankowski CLA
no flags Details
zip of the project (2.21 KB, application/octet-stream)
2012-01-19 04:33 EST, Satyam Kandula CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Bartosz Bankowski CLA 2011-04-17 06:29:39 EDT
Build Identifier: 20110218-0911

Given the following interfaces and classes:

Interface.java:
interface Interface<M, X> {
	void method(X x, M m);
}

Abstract.java
abstract class Abstract<X> implements Interface<String, X> {
	@Override
	public void method(X x, String string) {
	}
}

PublicAbstract.java:
public abstract class PublicAbstract<X> implements Interface<String, X> {
	@Override
	public void method(X x, String string) {
	}
}

AbstractChild.java:
public class AbstractChild extends Abstract<Integer> {
}

PublicAbstractChild:
public class PublicAbstractChild extends PublicAbstract<Integer> {
}

And the program to test it (Main):
import java.lang.reflect.Method;

public class Main {
	
	public static void main(String[] args) {
		printMethods(PublicAbstractChild.class);
		printMethods(AbstractChild.class);
	}

	private static void printMethods(Class<?> clazz) {
		System.out.println("Methods for class: " + clazz.getName());
		for (Method m : clazz.getMethods()) {
			if (m.getName().startsWith("method")) {
				System.out.println(m);
			}
		}
	}
	
}

When I have classes compiled by Eclipse the result is:

Methods for class: PublicAbstractChild
public void PublicAbstract.method(java.lang.Object,java.lang.String)
public void PublicAbstract.method(java.lang.Object,java.lang.Object)
Methods for class: AbstractChild
public void AbstractChild.method(java.lang.Object,java.lang.String)
public void Abstract.method(java.lang.Object,java.lang.Object)

When I have classes compiled by Oracle JDK (tested on versions 1.6.0_24 and 1.6.0_20) the result is:

Methods for class: PublicAbstractChild
public void PublicAbstract.method(java.lang.Object,java.lang.String)
public void PublicAbstract.method(java.lang.Object,java.lang.Object)
Methods for class: AbstractChild
public void Abstract.method(java.lang.Object,java.lang.String)
public void Abstract.method(java.lang.Object,java.lang.Object)

Results are different which causes major problems using reflection.


Reproducible: Always

Steps to Reproduce:
1. Take the code described in the details section.
2. Compile it and run Main within Eclipse.
3. Compile it with Oracle JDK 1.6.0_2x and run Main.
4. Compare the results.
Comment 1 Bartosz Bankowski CLA 2011-04-17 06:32:25 EDT
Created attachment 193429 [details]
Sources to reproduce the problem
Comment 2 Stephan Herrmann CLA 2011-04-17 09:37:18 EDT
From a quick look at the compiled byte code:
Eclipse indeed generates an extra bridge method into AbstractChild:
   public bridge synthetic void method(Object arg1, String arg2)
This method is unnecessary, because it has the same signature as the
inherited method and it does nothing but delegate (no cast involved).

I haven't seen a paragraph in the spec that defines when bridges must
(not) be created. So maybe the bytecode is legal, but maybe we should
still try to avoid creating such unnecessary bridge methods?
Comment 3 Srikanth Sankaran CLA 2011-11-03 01:00:00 EDT
Will consider a fix for this in 3.8 M4
Comment 4 Srikanth Sankaran CLA 2011-11-15 08:03:36 EST
See eclipse bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=288658 and
related sun bugs http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6342411.

Need to see why those considerations don't kick in for javac in this test
case. In particular, need to construct a test case similar to the one
in http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6342411 using comment#0
test case to see if the IllegalAccessException shows up.
Comment 5 Srikanth Sankaran CLA 2011-11-15 18:59:52 EST
(In reply to comment #4)
> See eclipse bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=288658 and
> related sun bugs http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6342411.
> 
> Need to see why those considerations don't kick in for javac in this test
> case. 

Duh. Eclipse compiler misses the nuance that such a bridge would need to be
introduced only when inheriting public methods introduced by non public classes 
from a named package. Patch will follow shortly.
Comment 6 Srikanth Sankaran CLA 2011-11-15 23:29:36 EST
Simpler test case that can be cut & pasted into package explorer:

import java.lang.reflect.Method;

interface Interface<M, T> {
    void method(T x, M m);
}

abstract class Abstract<T> implements Interface<String, T> {
    @Override
    public void method(T x, String string) {
    }
}

public class X extends Abstract<Integer> {

    public static void main(String[] args) {
        printMethods(X.class);
    }

    private static void printMethods(Class<?> clazz) {
        System.out.println("Methods for class: " + clazz.getName());
        for (Method m : clazz.getMethods()) {
            if (m.getName().startsWith("method")) {
                System.out.println(m);
            }
        }
    }

}
Comment 7 Srikanth Sankaran CLA 2011-11-16 00:20:49 EST
Turns out the regression tests added for bug 288658 are actually
testing the current scenario (i.e default package) I need to write
junits for that bug now :)
Comment 8 Srikanth Sankaran CLA 2011-11-16 03:38:56 EST
Released fix and tests in 3.8 stream via commit id : f966b547afe1ce6e5e600680e6f3625db1aa756f.

Ayush, this also needs to be backported to 3.7.2
after a bit more testing as this is a regression
in 3.7 relative to 3.6.
Comment 9 Ayushman Jain CLA 2011-11-23 01:53:57 EST
Released in 3.7 maintenance via commit cc817186788a86e60b58427a9b7e989e5093af0a
Comment 10 Ayushman Jain CLA 2011-12-06 09:58:53 EST
Verified for 3.8M4 using build I20111202-0800.
Comment 11 Ayushman Jain CLA 2011-12-08 05:01:25 EST
Released in 362+java7 branch via commit b317a1f8cb626a10310b9e4d19e0361f15b998da
Comment 12 Satyam Kandula CLA 2012-01-19 01:21:55 EST
javac is behaving the same if the package is default or not. With this patch, Eclipse is behaving similar to javac if the package is default and not otherwise.  I believe there could be more to the observations in comment 5.
Comment 13 Srikanth Sankaran CLA 2012-01-19 01:25:13 EST
(In reply to comment #12)
> javac is behaving the same if the package is default or not. With this patch,
> Eclipse is behaving similar to javac if the package is default and not
> otherwise.  I believe there could be more to the observations in comment 5.

Not sure I understand, can you post a test case that shows material
difference ? Thanks.
Comment 14 Satyam Kandula CLA 2012-01-19 01:36:50 EST
Run the following test with both javac and Eclipse. They differ in the output.
This is the simpler test case in comment 6, put in a package. 
######
package pkg1;
import java.lang.reflect.Method;

interface Interface<M, T> {
    void method(T x, M m);
}

abstract class Abstract<T> implements Interface<String, T> {
    @Override
    public void method(T x, String string) {
    }
}

public class X extends Abstract<Integer> {

    public static void main(String[] args) {
        printMethods(X.class);
    }

    private static void printMethods(Class<?> clazz) {
        System.out.println("Methods for class: " + clazz.getName());
        for (Method m : clazz.getMethods()) {
            if (m.getName().startsWith("method")) {
                System.out.println(m);
            }
        }
    }

}

########
Comment 15 Satyam Kandula CLA 2012-01-19 02:15:35 EST
Some more update:
I don't think javac differs depends on the defaultness of the package. I think there is an issue with javac itself whenever type parameters are used.
For eg: Use this testcase in a default package
######

class AbstractY {
	public void method() {}
}

public class Y extends AbstractY {
	 public static void main(String[] args) {
	        printMethods(Y.class);
	    }

	    private static void printMethods(Class<?> clazz) {
	        System.out.println("Methods for class: " + clazz.getName());
	        for (Method m : clazz.getMethods()) {
	            if (m.getName().startsWith("method")) {
	                System.out.println(m);
	            }
	        }
	    }
}
########
Run with javac and ecj. ecj prints AbstractY.method(), but javac prints Y.method(). 
I will do some investigations and update.
Comment 16 Srikanth Sankaran CLA 2012-01-19 02:47:56 EST
(In reply to comment #15)

> I will do some investigations and update.

Thanks, if this isn't a regression relative to 3.7.1, please open
a follow up defect.

I verified that the scenario specified in comment#14 is not
a regression. i.e for the default package case, we now conform
with java7. For the named package case we differ from javac7,
but not from 3.7.1 (i.e there is no regression).

(In reply to comment #15)
> Some more update:
> I don't think javac differs depends on the defaultness of the package. I think
> there is an issue with javac itself whenever type parameters are used.

I agree there could be more here. Bridge method generation is not specified
in JLS and so there are some differences historically that we are trying
to bridge ;-)

The defaultness came into picture from following the reasoning
for java.lang.IllegalAccessException discussed in https://bugs.eclipse.org/bugs/show_bug.cgi?id=288658

If the concerned public method belongs to a package default
type but that package happens to be the default package, 
the visibility barrier goes away and so there is no need for 
a bridge.

So I think the current fix is actually correct, but your testing
has uncovered some more nuances where we differ.
Comment 17 Srikanth Sankaran CLA 2012-01-19 02:48:43 EST
(In reply to comment #15)
> Some more update:
> I don't think javac differs depends on the defaultness of the package. I think
> there is an issue with javac itself whenever type parameters are used.

You may also want to see if the abstractness or not of the superclass has
any bearing.
Comment 18 Satyam Kandula CLA 2012-01-19 03:07:34 EST
(In reply to comment #16)
> So I think the current fix is actually correct, but your testing
> has uncovered some more nuances where we differ.
I am not sure about this. Please look at the test case in comment 15. javac and Eclipse 3.7.1 are similar for this test case but there is a change in 3.7.2.
Comment 19 Satyam Kandula CLA 2012-01-19 03:17:12 EST
(In reply to comment #17)
> You may also want to see if the abstractness or not of the superclass has
> any bearing.
This doesn't have any bearing.
Comment 20 Srikanth Sankaran CLA 2012-01-19 03:25:19 EST
(In reply to comment #18)
> (In reply to comment #16)
> > So I think the current fix is actually correct, but your testing
> > has uncovered some more nuances where we differ.
> I am not sure about this. Please look at the test case in comment 15. javac and
> Eclipse 3.7.1 are similar for this test case but there is a change in 3.7.2.

There _is_ a change in behavior for this test case, but I don't think it is 
a regression, it is a progression.

I think what it is showing is that there is many inconsistencies in javac's
bridge generation algorithm and it is nowhere nearly as straightforward as
as the statement 

"Specifically, we would generate a bridge method when a public method
is inherited from a nonpublic class into a public class." in 
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6342411
Comment 21 Srikanth Sankaran CLA 2012-01-19 03:30:19 EST
(In reply to comment #16)
> (In reply to comment #15)
> 
> > I will do some investigations and update.
> 
> Thanks, if this isn't a regression relative to 3.7.1, please open
> a follow up defect.

I meant to say if this isn't a regression relative to 3.7 and/or 
3.7.1.
Comment 22 Satyam Kandula CLA 2012-01-19 03:41:54 EST
I think the problem is with substitution of the type parameter that the method
takes causes the problem.
###
abstract class AbstractY<T> {
    public void method(T a) {}
}

public class Y extends AbstractY<String> {
}
####
javac breaks or differs in this condition. Note that the parameter that the
method 'method' takes is substituted by it's subclass. I still don't understand
completely but in my opinion ECJ3.7.1 behaviour is correct.
Comment 23 Satyam Kandula CLA 2012-01-19 03:53:12 EST
(In reply to comment #20)
> "Specifically, we would generate a bridge method when a public method
> is inherited from a nonpublic class into a public class." in 
> http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6342411
I agree that this is the right thing to do, but with this bug fix, there is a change in behaviour for classes in default package. I don't think that is right. As I understand from 6342411, a bridge method is being added so that things work fine with reflection. So, the defaultness of the package should not cause change in behaviour. 

>There _is_ a change in behavior for this test case, but I don't think it is 
>a regression, it is a progression.
It could be either way depending upon what is the expected behaviour. What should be the behaviour of the testcase in comment 22? Should the bridge method be generated or not? 
BTW, this bug fix breaks (I believe it breaks and not differs) the test in comment 15. Hence, this may be a regression.
Comment 24 Srikanth Sankaran CLA 2012-01-19 04:29:07 EST
(In reply to comment #16)

> The defaultness came into picture from following the reasoning
> for java.lang.IllegalAccessException discussed in
> https://bugs.eclipse.org/bugs/show_bug.cgi?id=288658
> 
> If the concerned public method belongs to a package default
> type but that package happens to be the default package, 
> the visibility barrier goes away and so there is no need for 
> a bridge.
> 
> So I think the current fix is actually correct, but your testing
> has uncovered some more nuances where we differ.

On thinking more about this, I think I understand what Satyam
is trying to say: I have been guilty of muddled thinking in
treating default package as though it were a globally accessible
package and claiming per earlier that the visibility barrier goes
away and  so there is no need for a bridge...

Satyam, thanks for persisting and for putting together a test case
that will show that this package barrier does exist between default
and named packages that can be used a test case when we work on
this further.

I think we should back put this fix for 3.7.2 and restore status quo
as of 3.7.1 and continue future work only for 3.8.x streams.
Comment 25 Satyam Kandula CLA 2012-01-19 04:33:10 EST
Created attachment 209730 [details]
zip of the project

Project to show that the current behavior is wrong. Run <default>.Y in this project.
Comment 26 Dani Megert CLA 2012-01-19 07:50:16 EST
> I think we should back put this fix for 3.7.2 and restore status quo
> as of 3.7.1 and continue future work only for 3.8.x streams.

Talked to Srikanth. I agree that this is the best thing to do at this point.
Comment 27 Srikanth Sankaran CLA 2012-01-19 09:07:02 EST
(In reply to comment #26)
> > I think we should back put this fix for 3.7.2 and restore status quo
> > as of 3.7.1 and continue future work only for 3.8.x streams.
> 
> Talked to Srikanth. I agree that this is the best thing to do at this point.

We will shortly restore the behavior to what it was at 3.8M3/3.7.1/3.6.2
time for the respective streams by backing out the "fix", which fortunately
is very small (only two lines of code change not counting tests.)

Apologies for the confusion. Satyam, once again thanks for insistence/
persistence in the face of my inclination to dismiss it rather readily.
Comment 28 Ayushman Jain CLA 2012-01-19 09:16:40 EST
Reverted from 3.7 maintenance branch via commit 52ffa6a622336b1154708c0ba10ecff9e5941c7f
Comment 29 Ayushman Jain CLA 2012-01-19 09:31:36 EST
Pushing revert into 3.6.2+java7 branch fails with Can't connect to any repository: ssh://ajain@git.eclipse.org/gitroot/jdt/eclipse.jdt.core.git (An internal Exception occurred during push: ssh://ajain@git.eclipse.org/gitroot/jdt/eclipse.jdt.core.git: Short read of block.)
Weird. :\
Comment 30 Ayushman Jain CLA 2012-01-19 09:58:48 EST
Reverted in 3.6.2+java7 branch via commit 6ddf29924a61a973ae1bd8add4af0942f5b5d8fb
Comment 31 Ayushman Jain CLA 2012-01-19 10:47:51 EST
Reverted in master via commit 99e06cdf0656ed0f6c3eb604300a8baae7ee3beb
Comment 32 Eclipse Genie CLA 2019-11-08 02:11:23 EST
This bug hasn't had any activity in quite some time. Maybe the problem got resolved, was a duplicate of something else, or became less pressing for some reason - or maybe it's still relevant but just hasn't been looked at yet.

If you have further information on the current state of the bug, please add it. The information can be, for example, that the problem still occurs, that you still want the feature, that more information is needed, or that the bug is (for whatever reason) no longer relevant.

--
The automated Eclipse Genie.
Comment 33 Stephan Herrmann CLA 2019-11-09 11:53:58 EST
Trying to take stock of this store:

Example from comment 15 showed an interim regression in ecj, has been reverted to the state where we agree with javac.

Example from comment 14 used to show a difference between ecj and javac, but starting with version 11 javac produces the same result as ecj. The same is true for example 0.

=> Apparently this wasn't a bug in ecj to begin with.