Skip to content
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

Methods on List compile but throw NoSuchMethodError when called from Java #7374

Closed
scabug opened this issue Apr 15, 2013 · 14 comments
Closed

Comments

@scabug
Copy link

scabug commented Apr 15, 2013

There are several methods on List that will compile when called from Java, but fail at runtime with a NoSuchMethodError. For example:

// src/SomeScala.scala
object SomeScala {
  def list = List(1, 2, 3) 
}
// src-java/Main.java
public class Main {
    public static void main(String[] args) {
        System.out.println(SomeScala.list().tail());
        System.out.println(SomeScala.list().par());
        System.out.println(SomeScala.list().init());
    }
}

All 3 of those methods will fail at runtime:
list().tail():

Exception in thread "main" java.lang.NoSuchMethodError: scala.collection.immutable.List.tail()Lscala/collection/Traversable;
	at Main.main(Main.java:3)

list().par()

Exception in thread "main" java.lang.NoSuchMethodError: scala.collection.immutable.List.par()Lscala/collection/parallel/ParIterable;
	at Main.main(Main.java:4)

list().init()

Exception in thread "main" java.lang.NoSuchMethodError: scala.collection.immutable.List.init()Lscala/collection/Traversable;
	at Main.main(Main.java:5)

The runtime type of the list is scala.collection.immutable.$colon$colon. If you were to cast it to that type, then tail() will compile and run:

// src-java/Main.java
public class Main {
    public static void main(String[] args) {
        scala.collection.immutable.$colon$colon colon = (scala.collection.immutable.$colon$colon) (Object) SomeScala.list();
        System.out.println(colon.tail()); //successfully prints List(2, 3)
    }
}

I found this when unit tests failed when calling tail() and tried out a few other methods to find par() and init(). I assume there are other methods that would fail like this, but I didn't see any pattern. For example, both init() and inits() show up as methods on TraversableLike, but inits() works.

@scabug
Copy link
Author

scabug commented Apr 15, 2013

Imported From: https://issues.scala-lang.org/browse/SI-7374?orig=1
Reporter: Donny Nadolny (dnadolny)
Affected Versions: 2.10.1, 2.10.2-RC1, 2.10.2-RC2, 2.10.2, 2.11.0-M4

@scabug
Copy link
Author

scabug commented Apr 15, 2013

@vigdorchik said:
How do you compile?

@scabug
Copy link
Author

scabug commented Apr 15, 2013

Donny Nadolny (dnadolny) said:
For most of this I used the Scala IDE (v3.0.0rc3-2_10-2013-03-131031-dac2f1c, which has scala 2.10.1). I had also replicated the problem with list.tail() on the command line, again using 2.10.1 (SomeScala.scala in src/scala, Main.java in src/java):

./scalac -sourcepath src/java/* src/scala/*
javac -classpath /home/dnadolny/tools/scala-2.10.1/lib/scala-library.jar:. -d . src/java/Main.java
./scala Main

I have also reproduced it with 2.10.0, however it works correctly under 2.9.2.

@scabug
Copy link
Author

scabug commented Apr 18, 2013

@SethTisue said:
here's a complete transcript of a reproduction:

% cat S.scala   
object CreateCollection {
  def someList = List(1, 2, 3)
}
% cat Main.java
class Main {
    public static void main(String[] args) {
        System.out.println(CreateCollection.someList().tail());
    }
}
% /usr/local/scala-2.10.1/bin/scalac S.scala                                
% javac -classpath .:/usr/local/scala-2.10.1/lib/scala-library.jar Main.java
% java -classpath .:/usr/local/scala-2.10.1/lib/scala-library.jar Main      
Exception in thread "main" java.lang.NoSuchMethodError: scala.collection.immutable.List.tail()Lscala/collection/Traversable;
	at Main.main(Main.java:3)

@scabug
Copy link
Author

scabug commented Sep 19, 2013

@retronym said:
Paul's analysis from the mailing list:

The method descriptor is not the erasure of the generic signature. Surely a scala bug. Notice how the third tail's return type is Traversable<A> in the generic signature but Object in the descriptor. javac issues an ill-fated call to the method it expects to find based on generic return type Traversable<A>.

Exception in thread "main" java.lang.NoSuchMethodError: scala.collection.immutable.List.tail()Lscala/collection/Traversable;
	at Main.main(Main.java:3)

scala> val tails = classOf[scala.collection.immutable.List[_]].getMethods filter (_.getName == "tail")
tails: Array[java.lang.reflect.Method] = Array(public scala.collection.LinearSeqOptimized scala.collection.immutable.List.tail(), public scala.collection.immutable.List scala.collection.immutable.List.tail(), public java.lang.Object scala.collection.AbstractTraversable.tail())

scala> tails foreach println
public scala.collection.LinearSeqOptimized scala.collection.immutable.List.tail()
public scala.collection.immutable.List scala.collection.immutable.List.tail()
public java.lang.Object scala.collection.AbstractTraversable.tail()

scala> tails map (_.toGenericString) foreach println
public scala.collection.LinearSeqOptimized scala.collection.immutable.List.tail()
public scala.collection.immutable.List scala.collection.immutable.List.tail()
public scala.collection.Traversable<A> scala.collection.AbstractTraversable.tail()

But as you may have heard, my scala days are numbered so all I can offer is this diagnosis.

I bisected that (the old fashioned way, as the regression pre dates the archive of builds available to scalac-hash), and landed scala/scala@a0a045f5c0. That was just a library change (introduction of Abstract{Seq, Iterable, ...}) so the root problem is a latent bug in the backend (either with erasure or with generic signatures.), most probably #3452.

I've been poking around in this space recently to fix some problems with generic sigs and erasure of value classes, so I'll take a look at this one.

@scabug
Copy link
Author

scabug commented Sep 20, 2013

@Blaisorblade said:
If it's #3452, very likely it's workarounded by -Xverify (with a backend respecting it -- currently it's GenASM, it used to be GenJVM): -Xverify detected and discarded invalid generic signatures, avoiding the problem (while reducing precision of type signatures, of course).
I even had a pull request enabling that check by default.

@scabug
Copy link
Author

scabug commented Sep 22, 2013

@retronym said:
Seen in isolation:

trait TraversableLike[Repr] {
  def tail: Repr = ???
}
trait MyTraversable[A] extends TraversableLike[Option[A]]
abstract class MyAbstractTraversable extends MyTraversable[String]

scala> classOf[MyAbstractTraversable].getMethod("tail")
res16: java.lang.reflect.Method = public java.lang.Object MyAbstractTraversable.tail()

scala> classOf[MyAbstractTraversable].getMethod("tail").toGenericString
res17: String = public scala.Option<java.lang.String> MyAbstractTraversable.tail()

@scabug
Copy link
Author

scabug commented Sep 22, 2013

@retronym said (edited on Sep 22, 2013 10:44:53 AM UTC):
Which is just #3452, and is fixed by the almost-there patch of Paul's paulp/scala@c007b7d

scala> classOf[MyAbstractTraversable].getMethod("tail")
res0: java.lang.reflect.Method = public java.lang.Object MyAbstractTraversable.tail()

scala> classOf[MyAbstractTraversable].getMethod("tail").toGenericString
res1: String = public java.lang.Object MyAbstractTraversable.tail()

I think I finally understand Paul's assertion that "we need more bridges". Rather than making the weakening generic signature to meet the erasure to Object we could (should?) sharpen the erasure of tail to Traversable, in the process installing a bridge for the covariant override. Currently, bridges are only added in the erasure phase, so later phases (importantly: mixin) are restricted to generating signatures that exactly match the overriden signature.

@scabug
Copy link
Author

scabug commented Feb 3, 2014

@paulp said:
Jason: yes, exactly. I was also thinking about either running mixin before erasure or bridge creation after mixin. It's not clear to me why bridges should be generated before the last moment - I would not base them on the extremely fallible scala ideas as held halfway through the pipeline, but on the actual bytecode being generated.

@scabug
Copy link
Author

scabug commented Feb 3, 2014

@retronym said:
I'm pretty sure that bridges have to be based on the Scala typesystem. Brian's JVMLS talk on the matter was really helpful to me to understand why its non-trivial to just do them in the VM (and I think the same arguments prevent us from making it a back-end only affair).

I actually got part of the way there to installing additional bridges in mixin: https://github.com/retronym/scala/compare/ticket/3452-rebased (please excuse the expasperated messaage-free commits at the end of the sequence!) but was lucky enough that a test (or the library) alerted me to a corner case that resulted from spreading responsibility for bridges across two phases. The root complexity is, as you point out, running mixin after erasure. Martin told me he's going to try it way earlier next time around, all the way back in extension methods.

@scabug
Copy link
Author

scabug commented Feb 3, 2014

@retronym said:
Maybe I misintepreted your comment; I guess we could have a bridges phase right at the end, even with mixin where it stands. There are still the more fundamental issues that result from Any (should List[Int] project onto List or List). I revisited those tickets last week to remind myself of history.

@scabug
Copy link
Author

scabug commented Feb 3, 2014

@paulp said:
Yes, I don't mean we have to ignore scala's type system, but that the bridge analysis should be performed with exact knowledge of the bytecode we are about to generate. This would eliminate a huge class of bridge bugs - the "obviously wrong" bytecode which is still generated in many cases.

@scabug
Copy link
Author

scabug commented Feb 9, 2014

@retronym said:
scala/scala#3493

@scabug
Copy link
Author

scabug commented Feb 16, 2014

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants