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

java.lang.ClassFormatError: Duplicate method name&signature in class file #7120

Closed
scabug opened this issue Feb 12, 2013 · 9 comments
Closed
Assignees
Milestone

Comments

@scabug
Copy link

scabug commented Feb 12, 2013

After migrating from 2.9.2 to 2.10 my team has encountered a runtime ClassFormatError, which I've managed to reduce to a simple single-file reproduction case which involves a type parameter, type alias and a partial function. The sample is reproduced here (full code as a gist):

case class Container( v: String )
 
trait Base[ T <: AnyRef ] {
  type UserType = T
	protected def defect: PartialFunction[ UserType, String ]
}
 
trait Derived extends Base[ Container ] {
  protected def defect = { case c: Container => c.v.toString }
}
 
object Test extends Derived with App {
	println( defect( Container( "8" ) ) )
}

The generated class file for Derived.defect clearly has duplicate method signatures:

wanamingo:tmp tomer$ scalap -cp . 'com.tomergabel.examples.Derived$$anonfun$defect$1'
package com.tomergabel.examples;
final class Derived$$anonfun$defect$1 extends scala.runtime.AbstractPartialFunction with scala.Serializable {
  def this(com.tomergabel.examples.Derived): scala.Unit;
  def applyOrElse(scala.Any, scala.Function1): scala.Any;
  def isDefinedAt(scala.Any): scala.Boolean;
  def isDefinedAt(com.tomergabel.examples.Container): scala.Boolean;
  def applyOrElse(scala.Any, scala.Function1): scala.Any;
}
object Derived$$anonfun$defect$1 {
  final val serialVersionUID: scala.Long;
}

Fortunately this can be worked around by qualifying the derived method with a full return type:

  protected def defect: PartialFunction[ Container, String ] = { case c: Container => c.v.toString }
@scabug
Copy link
Author

scabug commented Feb 12, 2013

Imported From: https://issues.scala-lang.org/browse/SI-7120?orig=1
Reporter: Tomer Gabel (holograph)
Affected Versions: 2.10.0

@scabug
Copy link
Author

scabug commented Feb 13, 2013

@retronym said:
aka "a bridge too far".

[log erasure] computing bridges for anonymous class $anonfun
[log erasure(->explicitouter)] overriding-pairs? method applyOrElse matches method applyOrElse in trait PartialFunction ([A1 <: <empty>.this.Container, B1 >: lang.this.String](<param> <synthetic> <triedcooking> x1: A1, <param> <synthetic> default: scala.this.Function1[A1,B1])B1/class scala.reflect.internal.Types$PolyType vs. [A1 <: <empty>.this.Container, B1 >: lang.this.String](<param> x: A1, <param> default: scala.this.Function1[A1,B1])B1/class scala.reflect.internal.Types$PolyType) == true
[log erasure] generating bridge from method applyOrElse (<method> final override <bridge>): (<param> x: Object, <param> default: Function1)Object in trait PartialFunction to method applyOrElse: (<param> <synthetic> <triedcooking> x1: Container, <param> <synthetic> default: Function1)Object
[log erasure] overriding-pairs? method isDefinedAt matches method isDefinedAt in trait PartialFunction ((<param> <synthetic> <triedcooking> x1: <empty>.this.Container)scala.this.Boolean/class scala.reflect.internal.Types$MethodType vs. (<param> x: <empty>.this.Container)scala.this.Boolean/class scala.reflect.internal.Types$MethodType) == true

@scabug
Copy link
Author

scabug commented Feb 13, 2013

@retronym said:
Regressed with the new encoding of PartialFunctions: 383d28daa16

But it's possible that this just lifted the lid on some existing problem in erasure; I'm trying to track down why x1 in the non-bridge signature below erases to Object, rather than Container.

        final override def applyOrElse(x1: Object, default: Function1): Object = {
        final override <bridge> def applyOrElse(x: Object, default: Function1): Object = $anonfun.this.applyOrElse(x.$asInstanceOf[Container](), default)

@scabug
Copy link
Author

scabug commented Feb 13, 2013

@retronym said (edited on Feb 13, 2013 4:08:12 PM UTC):
Yep, it was a problem in erasure.

I think this will fix it:

https://github.com/retronym/scala/compare/ticket/7120

Checking for collateral damage:

https://scala-webapps.epfl.ch/jenkins/job/scala-checkin-manual/799/console

We can't fix this for 2.10.x as changing the erased types risks binary compatibility breakages for ourselves, and downstream libraries that are themselves trying to offer binary compatibility guarantees.

If that build goes well, I'll submit a PR against master.

@scabug
Copy link
Author

scabug commented Feb 14, 2013

@retronym said:
scala/scala#2125

@scabug scabug closed this as completed Feb 23, 2013
@scabug
Copy link
Author

scabug commented Jul 9, 2013

Ben Hutchison (ben_hutchison) said:
I tripped over this issue in a different case. Worked around OK. Including if it helps anyone to workaround until 2.11 is out.

object AmbiguousImplicitExample extends App {

  trait ApproxEq[T] extends (T => ApproxEqOps[T]) {

    //*** triggers the bug, remove to workaround ***
    def apply(v: T): ApproxEqOps[T]

    def approxEq(a: T, b: T): Boolean
  }
  implicit object doubleApprox extends ApproxEq[Double] {
    def approxEq(a: Double, b: Double): Boolean = ???

    def apply(v1: Double): ApproxEqOps[Double] = new ApproxEqOps[Double](v1)
  }
  class ApproxEqOps[T](val value: T) extends AnyVal {
    def ===(other: T)(implicit eq: ApproxEq[T]) = ???
  }
  print(doubleApprox)
}

@scabug
Copy link
Author

scabug commented Jan 3, 2014

@adriaanm said:
Here's another real-world use case, distilled from a Slick 2.0.0-RC1 app:

// derived from a real-world Slick app (support #3035)
// running Test should not throw a java.lang.ClassFormatError: Duplicate method name&signature in class file Test$$anonfun$1 
// workaround: uncomment type arg to list below

class AbstractTable[T] { type TableElementType }
class Table[T] extends AbstractTable[T] { type TableElementType = T }

class Query[E, U]
class TableQuery[E <: AbstractTable[_]] extends Query[E, E#TableElementType]

object Test extends App {
  object MyTable extends TableQuery[Table[Long]]

  def list[R](q: Query[_, R]): List[R] = Nil
  list/*[Long]*/(MyTable) collect { case x => x }
}

@scabug
Copy link
Author

scabug commented Jan 3, 2014

@adriaanm said:
Hmm, looks like that last example is a bug in uncurry where signatures are dealiased, but not the type params/value params that constitute them. Found diffing -Yshow-syms.

@scabug
Copy link
Author

scabug commented Jan 3, 2014

@adriaanm said:
Opened #8114 to track this one.

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

No branches or pull requests

2 participants