Skip to content

Mishandled captured parameters in secondary class constructors. #9536

Open
@scabug

Description

@scabug

There are some conditions where parameter captured by secondary constructors generate ambiguous bytecode. Such as in the following example where the default constructor receives an Int parameter and the secondary captures an Int.

object Bar extends App {
  val 
  def foo(x: Int) = {		
    class Foo(i: Int) {
      def this() = this(x)
    }
    new Foo
  }
  println(foo(6))
}

Will generate the following bytecode where the two constructors have the same signature.

public class Bar$Foo$1 {
  public Bar$Foo$1(int);
    descriptor: (I)V
    Code:
       0: aload_0
       1: invokespecial #15                 // Method java/lang/Object."<init>":()V
       4: return

  public Bar$Foo$1(int);
    descriptor: (I)V
    Code:
       0: aload_0
       1: iload_1
       2: invokespecial #21                 // Method "<init>":(I)V
       5: return
}

The problem does not show itself at compilation time, it only fails at runtime with:

[info] Running Bar 
[error] (run-main-b) java.lang.ClassFormatError: Duplicate method name&signature in class file Bar$Foo$1
java.lang.ClassFormatError: Duplicate method name&signature in class file Bar$Foo$1
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
	at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
	at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at Bar$.foo(A.scala:7)
	at Bar$.delayedEndpoint$App$1(Bar.scala:10)
	at Bar$delayedInit$body.apply(Bar.scala:1)
	at scala.Function0$class.apply$mcV$sp(Function0.scala:34)
	at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
	at scala.Bar$$anonfun$main$1.apply(App.scala:76)
	at scala.Bar$$anonfun$main$1.apply(App.scala:76)
	at scala.collection.immutable.List.foreach(List.scala:381)
	at scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:35)
	at scala.App$class.main(App.scala:76)
	at Bar$.main(Bar.scala:1)
	at Bar.main(Bar.scala)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
[trace] Stack trace suppressed: run last compile:run for the full output.
java.lang.RuntimeException: Nonzero exit code: 1
	at scala.sys.package$.error(package.scala:27)
[trace] Stack trace suppressed: run last compile:run for the full output.
[error] (compile:run) Nonzero exit code: 1
[error] Total time: 1 s, completed Oct 26, 2015 4:42:21 PM

The compiler should handle these cases in a different way or should disallow them by failing the compilation.

Activity

scabug

scabug commented on Oct 26, 2015

@scabug
Author

Imported From: https://issues.scala-lang.org/browse/SI-9536?orig=1
Reporter: Nicolas Stucki (nicolas.stucki)
Affected Versions: 2.10.5, 2.11.7, 2.12.0-M3

scabug

scabug commented on Oct 27, 2015

@scabug
Author

@retronym said:
Great test case!

Lambdalift basically assumes a world without overloading, a restriction which is enforced for local methods:

def foo { def bar(a: Int) = 0; def bar(a: String) = 0 } //  error: method bar is defined twice

However, this is not enforced for constructors of local classes, which are handled by the same code in lambdalift (see addFreeParams).

One possible resolution of this bug would be to enforce such a restriction (although it would need to be phased with a deprecation warning)

An analagous condundrum exists in the Java compiler when it needs to add constructors to a class to allow inner classes to invoke a private enclosing constructor. Java adds dummy parameter to the method to avoid ambiguity with existing constructors. See scala/scala#2750 for some more details. This approach might also work here, but I don't think the cost/benefit stacks up.

added this to the Backlog milestone on Apr 7, 2017
som-snytt

som-snytt commented on Sep 18, 2019

@som-snytt

The last reference on Jan 16 was a typo, but editing the comment did not cause this "notification of reference" to disappear.

som-snytt

som-snytt commented on Mar 16, 2022

@som-snytt

Not yet fixed on Scala 3. It probably would have been fixed by now if they hadn't removed the quickfix label.

som-snytt

som-snytt commented on Jan 2, 2024

@som-snytt

Fixed in 3.3.1. The nested class gets an outer pointer, where the sigs are (outer, int) and (int, outer). I couldn't find the change with just a dart and a blindfold.

added
fixed in Scala 3This issue does not exist in the Scala 3 compiler (https://github.com/lampepfl/dotty/)
on Jan 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bytecodefixed in Scala 3This issue does not exist in the Scala 3 compiler (https://github.com/lampepfl/dotty/)

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @retronym@SethTisue@som-snytt@scabug

        Issue actions

          Mishandled captured parameters in secondary class constructors. · Issue #9536 · scala/bug