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

Specialization is sensitive to names of type parameters #11845

Open
mbuzdalov opened this issue Jan 5, 2020 · 4 comments
Open

Specialization is sensitive to names of type parameters #11845

mbuzdalov opened this issue Jan 5, 2020 · 4 comments
Milestone

Comments

@mbuzdalov
Copy link

@mbuzdalov mbuzdalov commented Jan 5, 2020

The following code snippet compiles fine, as intended, but fails at runtime with a class cast exception.

object Main {
  trait Two[@specialized C, @specialized F] {
    def trouble(p: Int): C
  }

  class Instance(val p: Int) extends Two[Long, Int] {
    override def trouble(p: Int): Long = 0
  }

  // in this trait, and only here, "C" is actually a cyrillic "С"
  trait Entry {
    def call[@specialized С, @specialized F](a: Two[С, F]): Unit
  }

  object SimpleEntry extends Entry {
    def call[@specialized C, @specialized F](a: Two[C, F]): Unit =
      a.trouble(0)
  }

  def main(args: Array[String]): Unit = {
    SimpleEntry.call(new Instance(0))

    val algorithm: Entry = SimpleEntry
    algorithm.call(new Instance(0))
  }
}

More precisely, it runs well when the optimize method is called on SimpleOptimizer directly, but when it is called on a trait, it fails with the following stack trace:

(scala 2.12.4)

java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.Integer
        at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:101)
        at Main$Two.trouble$mcI$sp(Main.scala:3)
        at Main$Two.trouble$mcI$sp$(Main.scala:3)
        at Main$Instance.trouble$mcI$sp(Main.scala:6)
        at Main$SimpleEntry$.call$mIJc$sp(Main.scala:17)
        at Main$.main(Main.scala:24)
        at Main.main(Main.scala)
        ...

(scala 2.13.1)

Exception in thread "main" java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.Integer
	at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:99)
	at Main$Two.trouble$mcI$sp(Main.scala:3)
	at Main$Two.trouble$mcI$sp$(Main.scala:3)
	at Main$Instance.trouble$mcI$sp(Main.scala:6)
	at Main$SimpleEntry$.call$mIJc$sp(Main.scala:17)
	at Main$.main(Main.scala:24)
	at Main.main(Main.scala)

From this stack trace, one can see that in this particular piece:

	at Main$Instance.trouble$mcI$sp(Main.scala:6)

the type of the type argument, mcI is wrong, as it shall be mcJ for Long. What is more, the immediate caller:

	at Main$SimpleEntry$.call$mIJc$sp(Main.scala:17)

seems to be wrong as well, as the investigation of the stack trace of the successful direct call in the debugger shows the mJIc in the signature.

Replacing the Cyrillic type parameter С with a Latin C gets everything to the place.

Scala version: 2.12.4, 2.13.1
Java version: openjdk version "1.8.0_232"

mbuzdalov added a commit to mbuzdalov/generic-onell that referenced this issue Jan 5, 2020
@mbuzdalov mbuzdalov changed the title Specialization messes with the order of specialized type parameters Strange specialization behavior caused by Cyrillic names of type parameters Jan 5, 2020
@mbuzdalov mbuzdalov changed the title Strange specialization behavior caused by Cyrillic names of type parameters Specialization can break with Unicode names of type parameters Jan 6, 2020
@som-snytt

This comment has been minimized.

Copy link

@som-snytt som-snytt commented Jan 6, 2020

"compiles fine":

scala> :javap sandbox/Test$Entry.class#call$mJIc$sp
  public void call$mJIc$sp(Test$Two<java.lang.Object, java.lang.Object>);
    descriptor: (LTest$Two;)V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=3, locals=2, args_size=2
         0: new           #26                 // class java/lang/RuntimeException
         3: dup
         4: ldc           #28                 // String Fatal error in code generation: this should never be called.
         6: invokespecial #32                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
         9: athrow
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  this   LTest$Entry;
            0      10     1     a   LTest$Two;
    Signature: #24                          // (LTest$Two<Ljava/lang/Object;Ljava/lang/Object;>;)V
    MethodParameters:
      Name                           Flags
      a                              final
@mbuzdalov

This comment has been minimized.

Copy link
Author

@mbuzdalov mbuzdalov commented Jan 6, 2020

Well, the compiler does not print anything to an end user about that.

And it is at least internally consistent, as, just as it promises, it never calls that method, but instead goes for call$mIJc$sp:

scala> :javap Main$.class#main
  public void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=3, args_size=2
         0: getstatic     #32                 // Field Main$SimpleEntry$.MODULE$:LMain$SimpleEntry$;
         3: new           #12                 // class Main$Instance
         6: dup
         7: iconst_0
         8: invokespecial #35                 // Method Main$Instance."<init>":(I)V
        11: invokevirtual #39                 // Method Main$SimpleEntry$.call$mJIc$sp:(LMain$Two;)V
        14: getstatic     #32                 // Field Main$SimpleEntry$.MODULE$:LMain$SimpleEntry$;
        17: astore_2
        18: aload_2
        19: new           #12                 // class Main$Instance
        22: dup
        23: iconst_0
        24: invokespecial #35                 // Method Main$Instance."<init>":(I)V
        27: invokeinterface #42,  2           // InterfaceMethod Main$Entry.call$mIJc$sp:(LMain$Two;)V
        32: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           17      15     2 algorithm   LMain$Entry;
            0      33     0  this   LMain$;
            0      33     1  args   [Ljava/lang/String;
      LineNumberTable:
        line 21: 0
        line 23: 14
        line 24: 18
    MethodParameters:
      Name                           Flags
      args                           final

On the other hand, the first target in main has the right order of specialized type arguments and goes just fine:

scala> :javap Main$SimpleEntry$.class#call$mJIc$sp
  public void call$mJIc$sp(Main$Two<java.lang.Object, java.lang.Object>);
    descriptor: (LMain$Two;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_1
         1: iconst_0
         2: invokeinterface #113,  2          // InterfaceMethod Main$Two.trouble$mcJ$sp:(I)J
         7: pop2
         8: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   LMain$SimpleEntry$;
            0       9     1     a   LMain$Two;
      LineNumberTable:
        line 17: 0
    Signature: #157                         // (LMain$Two<Ljava/lang/Object;Ljava/lang/Object;>;)V
    MethodParameters:
      Name                           Flags
      a                              final

Another (legit) difference is that the first call is invokevirtual and the second is invokeinterface.

By the way, if SimpleEntry also has a cyrillic "С" at the same location, everything works as intended, with the wrong IJ order adopted globally.

P.S.: and am I right that Main$Entry.class#call$mJIc$sp is just a default interface method stub?

@SethTisue SethTisue added this to the Backlog milestone Jan 10, 2020
@hrhino

This comment has been minimized.

Copy link
Member

@hrhino hrhino commented Jan 16, 2020

@retronym was prescient as always:

  /** Return the specialized name of 'sym' in the given environment. It
   *  guarantees the same result regardless of the map order by sorting
   *  type variables alphabetically.
   *
   *  !!! Is this safe in the face of the following?
   *    scala> trait T { def foo[A] = 0}; object O extends T { override def foo[B] = 0 }
   */
  private def specializedName(sym: Symbol, env: TypeEnv): TermName = {
    val tvars = (
      if (sym.isClass) env.keySet
      else specializedTypeVars(sym).intersect(env.keySet)
    )
    specializedName(sym.name, tvars, env)
  }

Sadly I think this means this can't be fixed until 2.14 Dotty doti, where it doesn't exist, because doing so would change the names of specialized methods and break bincompat.

@hrhino hrhino changed the title Specialization can break with Unicode names of type parameters Specialization is sensitive to names of type parameters Jan 16, 2020
@hrhino

This comment has been minimized.

Copy link
Member

@hrhino hrhino commented Jan 16, 2020

Description updated, because you can break it by replacing the Cyrillic letter with B, which precedes C (both of them). I resisted the temptation to add "... and somehow no one has noticed".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants
You can’t perform that action at this time.