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

Class Signature attribute inconsistent with interfaces #8931

Closed
scabug opened this issue Oct 20, 2014 · 8 comments
Closed

Class Signature attribute inconsistent with interfaces #8931

scabug opened this issue Oct 20, 2014 · 8 comments

Comments

@scabug
Copy link

scabug commented Oct 20, 2014

In scala-library.jar of Scala 2.11.2, a number of classes (165 to be precise) have Signature attributes that are inconsistent with their interfaces.

For instance, scala.Tuple1 has one superclass
  java/lang/Object

and 2 direct interfaces:
  scala/Product1
  scala/Serializable

At the same time, it has this signature (adding whitespace for clarity):
  T1:Ljava/lang/Object;
  Ljava/lang/Object;
  Lscala/Product1<TT1;>;
  Lscala/Product;
  Lscala/Serializable;

Note the 3 interface types, including the spurious "Lscala/Product;". The tool javap incorrectly lists it as a direct interface.

This seems to go against The Java Virtual Machine Specification, section 4.7.9.1: "A class signature encodes type information about a (possibly generic) class declaration. It describes any type parameters of the class, and lists its (possibly parameterized) direct superclass and direct superinterfaces, if any."

In practice, the problem triggers a crash in ProGuard 5.0, with an ArrayIndexOutOfBoundsException.

My apologies if this is a known issue; I haven't found it anywhere.

Eric Lafortune -- developer of ProGuard

@scabug
Copy link
Author

scabug commented Oct 20, 2014

Imported From: https://issues.scala-lang.org/browse/SI-8931?orig=1
Reporter: Eric Lafortune (lafortune)
Affected Versions: 2.11.2
See #5278

@scabug
Copy link
Author

scabug commented Oct 20, 2014

@soc said:
Hi Eric, thanks a lot for your report!

@scabug
Copy link
Author

scabug commented Oct 21, 2014

@gourlaysama said:
Hmm, I don't get it, isn't it the same from Java? If you directly implement an interface (scala.Product here), and also indirectly implement it through something else (here scala.Product1[T] extends scala.Product), then it does appear in the resulting classfile.

As in:

==> A.java <==
import java.io.Serializable;

public class A<T> implements Product1<T>, Product, Serializable {}

==> Product.java <==
public interface Product {}

==> Product1.java <==
public interface Product1<T> extends Product {}
% javap -v A.class
[...]
#12 = Utf8               <T:Ljava/lang/Object;>Ljava/lang/Object;LProduct1<TT;>;LProduct;Ljava/io/Serializable;

Note that Tuple1 directly implements Product because it is a case class:

case class Tuple1[+T](_1: T) extends Product1[T]

is syntactic sugar for this:

class Tuple1[+T] extends AnyRef with Product1[T] with Product with Serializable { <autogenerated members> }

@scabug
Copy link
Author

scabug commented Oct 21, 2014

Eric Lafortune (lafortune) said:
Looking at the compiled bytecode, Product really isn't listed as a direct interface. I presume the scalac compiler has removed it (but not from the Signature). The tool javap confusingly lists the generic direct interfaces, not the plain direct interfaces.

With a lower-level tool, you should see the actual interfaces. E.g. with ProGuard's -dump option:

java -jar proguard5.0/lib/proguard.jar \
  -dontshrink \
  -dontoptimize \
  -dontobfuscate \
  -dontpreverify \
  -dump \
  -injars lib/scala-library.jar'(scala/Tuple1.class)' \
| less

You'll then see

- Program class: scala/Tuple1
  Superclass:    java/lang/Object
.....
Interfaces (count = 2):
  - Class [scala/Product1]
  - Class [scala/Serializable]

.....
  - Signature attribute:
    - Utf8 [<T1:Ljava/lang/Object;>Ljava/lang/Object;Lscala/Product1<TT1;>;Lscala/Product;Lscala/Serializable;]

Tools like asm or jad may provide the same information. Other tell-tale sign: scala/Product isn't present as a class constant in the bytecode.

@scabug
Copy link
Author

scabug commented Oct 21, 2014

@gourlaysama said:
bq. scala/Product isn't present as a class constant in the bytecode.
Oh, I had completely missed that; that's very wrong. And it happens for any interface:

trait Foo

trait Foo1 extends Foo

class A extends Foo1 with Foo
% javap -v A.class | grep '= Class.*Foo' # where is Foo...
#6 = Class              #5             // Foo1

This seems to have regressed in Scala 2.9.2:

% java -jar ~/Downloads/proguard5.0/lib/proguard.jar \
  -dontshrink \
  -dontoptimize \
  -dontobfuscate \
  -dontpreverify \
  -dump \
  -injars ~/scaladev/packs/scala-v2.9.1-1/pack/lib/scala-library.jar'(scala/Tuple1.class)' \
  | grep 'Interfaces' -A 4
Interfaces (count = 4):
  - Class [scala/Product1]
  - Class [scala/ScalaObject]
  - Class [scala/Product]
  - Class [scala/Serializable]
% java -jar ~/Downloads/proguard5.0/lib/proguard.jar \
  -dontshrink \
  -dontoptimize \
  -dontobfuscate \
  -dontpreverify \
  -dump \
  -injars ~/scaladev/packs/scala-v2.9.2/pack/lib/scala-library.jar'(scala/Tuple1.class)' \
  | grep 'Interfaces' -A 4
Interfaces (count = 2):
  - Class [scala/Product1]
  - Class [scala/Serializable]

Constant Pool (count = 184):

@scabug
Copy link
Author

scabug commented Oct 21, 2014

@gourlaysama said:
The jvms does say:

bq. Oracle's Java Virtual Machine implementation does not check the well-formedness of Signature attributes during class loading or linking. Instead, Signature attributes are checked by methods of the Java SE platform class libraries which expose generic signatures of classes, interfaces, constructors, methods, and fields. Examples include getGenericSuperclass in Class and toGenericString in java.lang.reflect.Executable.
(§4.7.9 in jdk8, and something similar at $4.3.4 in jdk7)

But even java reflection seems fine with it:

scala> val c = classOf[Tuple1[String]]
c: Class[(String,)] = class scala.Tuple1

scala> c.getInterfaces
res0: Array[Class[_]] = Array(interface scala.Product1, interface scala.Serializable)

scala> c.getGenericInterfaces
res1: Array[java.lang.reflect.Type] = Array(scala.Product1<T1>, interface scala.Product, interface scala.Serializable)

@scabug
Copy link
Author

scabug commented Oct 21, 2014

@gourlaysama said:
So apparently this was done on purpose, in 7a99c03 (#5278) to "ease trouble on android".

So either it shouldn't do it at all, or it should do it properly (for both the signature and the interface list).

@scabug
Copy link
Author

scabug commented Oct 28, 2014

@gourlaysama said:
scala/scala#4080

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