Scala Programming Language
  1. Scala Programming Language
  2. SI-7120

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

    Details

    • Type: Bug Bug
    • Status: Closed
    • Priority: Critical Critical
    • Resolution: Fixed
    • Affects Version/s: Scala 2.10.0
    • Fix Version/s: Scala 2.11.0-M2
    • Component/s: None
    • Labels:
      None

      Description

      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 }
      

        Activity

        Hide
        Jason Zaugg added a comment -

        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
        
        Show
        Jason Zaugg added a comment - 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
        Hide
        Jason Zaugg added a comment -

        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)
        
        Show
        Jason Zaugg added a comment - 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)
        Hide
        Jason Zaugg added a comment - - edited

        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.

        Show
        Jason Zaugg added a comment - - edited 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.
        Show
        Jason Zaugg added a comment - https://github.com/scala/scala/pull/2125
        Hide
        Ben Hutchison added a comment -

        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)
        }
        
        
        Show
        Ben Hutchison added a comment - 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) }
        Hide
        Adriaan Moors added a comment -

        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 }
        }
        
        Show
        Adriaan Moors added a comment - 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 } }
        Hide
        Adriaan Moors added a comment -

        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.

        Show
        Adriaan Moors added a comment - 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.
        Hide
        Adriaan Moors added a comment -

        Opened SI-8114 to track this one.

        Show
        Adriaan Moors added a comment - Opened SI-8114 to track this one.

          People

          • Assignee:
            Jason Zaugg
            Reporter:
            Tomer Gabel
          • Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development