Bug 99260 - [1.5][compiler] Bad bytecode generated with varargs + generics
Summary: [1.5][compiler] Bad bytecode generated with varargs + generics
Status: CLOSED FIXED
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 3.1   Edit
Hardware: PC Windows 2000
: P3 major (vote)
Target Milestone: 3.1 RC2   Edit
Assignee: Philipe Mulet CLA
QA Contact:
URL:
Whiteboard:
Keywords:
: 108497 (view as bug list)
Depends on:
Blocks:
 
Reported: 2005-06-09 16:31 EDT by Osvaldo Pinali Doederlein CLA
Modified: 2005-08-31 14:51 EDT (History)
1 user (show)

See Also:


Attachments
Apply on HEAD (1.10 KB, patch)
2005-06-09 19:53 EDT, Olivier Thomann CLA
no flags Details | Diff
Apply on HEAD (2.19 KB, patch)
2005-06-09 19:54 EDT, Olivier Thomann CLA
no flags Details | Diff
Apply on HEAD (1.02 KB, patch)
2005-06-09 20:00 EDT, Olivier Thomann CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Osvaldo Pinali Doederlein CLA 2005-06-09 16:31:33 EDT
Hi.  Found another bug in generics, this time combined with varargs:

public class Test {
 public static void main (String[] args) {
  audit("osvaldo", "localhost", "logged", "X", new Integer(0));
 }
 public static <A extends Object> void audit (
  String login, String address, String event, A... args) {}
}

If yo build this code with 3.1RC1, it compiles the code, but the resulting
.class does not run in JVMs because it's invalid.  Decompiling with javap shows:

---------------------------------------------
javap -c -s -verbose Test

Compiled from "Test.java"
public class Test extends java.lang.Object
  SourceFile: "Test.java"
  minor version: 0
  major version: 49
  Constant pool:
const #1 = Asciz        Test;
const #2 = class        #1;     //  Test
const #3 = Asciz        java/lang/Object;
const #4 = class        #3;     //  java/lang/Object
const #5 = Asciz        <init>;
const #6 = Asciz        ()V;
const #7 = Asciz        Code;
const #8 = NameAndType  #5:#6;//  "<init>":()V
const #9 = Method       #4.#8;  //  java/lang/Object."<init>":()V
const #10 = Asciz       LineNumberTable;
const #11 = Asciz       LocalVariableTable;
const #12 = Asciz       this;
const #13 = Asciz       LTest;;
const #14 = Asciz       main;
const #15 = Asciz       ([Ljava/lang/String;)V;
const #16 = Asciz       osvaldo;
const #17 = String      #16;    //  osvaldo
const #18 = Asciz       localhost;
const #19 = String      #18;    //  localhost
const #20 = Asciz       logged;
const #21 = String      #20;    //  logged
const #22 = Asciz       ;
const #23 = class       #22;    //  ""
const #24 = Asciz       X;
const #25 = String      #24;    //  X
const #26 = Asciz       java/lang/Integer;
const #27 = class       #26;    //  java/lang/Integer
const #28 = Asciz       (I)V;
const #29 = NameAndType #5:#28;//  "<init>":(I)V
const #30 = Method      #27.#29;        //  java/lang/Integer."<init>":(I)V
const #31 = Asciz       audit;
const #32 = Asciz      
(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V;
const #33 = NameAndType #31:#32;// 
audit:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V
const #34 = Method      #2.#33; // 
Test.audit:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V
const #35 = Asciz       args;
const #36 = Asciz       [Ljava/lang/String;;
const #37 = Asciz       Signature;
const #38 = Asciz      
<A:Ljava/lang/Object;>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[TA;)V;
const #39 = Asciz       login;
const #40 = Asciz       Ljava/lang/String;;
const #41 = Asciz       address;
const #42 = Asciz       event;
const #43 = Asciz       [Ljava/lang/Object;;
const #44 = Asciz       SourceFile;
const #45 = Asciz       Test.java;

{
public Test();
  Signature: ()V
  Code:
   Stack=1, Locals=1, Args_size=1
   0:   aload_0
   1:   invokespecial   #9; //Method java/lang/Object."<init>":()V
   4:   return
  LineNumberTable:
   line 1: 0
  LocalVariableTable:
   Start  Length  Slot  Name   Signature
   0      5      0    this       LTest;

public static void main(java.lang.String[]);
  Signature: ([Ljava/lang/String;)V
  Code:
   Stack=9, Locals=1, Args_size=1
   0:   ldc     #17; //String osvaldo
   2:   ldc     #19; //String localhost
   4:   ldc     #21; //String logged
   6:   iconst_2
   7:   anewarray       #23; //class ""
   10:  dup
   11:  iconst_0
   12:  ldc     #25; //String X
   14:  aastore
   15:  dup
   16:  iconst_1
   17:  new     #27; //class java/lang/Integer
   20:  dup
   21:  iconst_0
   22:  invokespecial   #30; //Method java/lang/Integer."<init>":(I)V
   25:  aastore
   26:  invokestatic    #34; //Method
audit:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V
   29:  return
  LineNumberTable:
   line 5: 0
   line 6: 29
  LocalVariableTable:
   Start  Length  Slot  Name   Signature
   0      30      0    args       [Ljava/lang/String;

public static void audit(java.lang.String, java.lang.String, java.lang.String,
java.lang.Object[]);
  Signature:
(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V
  Signature: length = 0x2
   00 26
  Code:
   Stack=0, Locals=4, Args_size=4
   0:   return
  LineNumberTable:
   line 11: 0
  LocalVariableTable:
   Start  Length  Slot  Name   Signature
   0      1      0    login       Ljava/lang/String;
   0      1      1    address       Ljava/lang/String;
   0      1      2    event       Ljava/lang/String;
   0      1      3    args       [Ljava/lang/Object;

}
---------------------------------------------

Notice the problem here:

 7:   anewarray       #23; //class ""

The array that the compiler generates to wrap the variable arguments has an
invalid class "".  It seems that the compiler got confused by my declaration
of varargs of type <T extends Object>.  In this case, Sun JDK 5.0_03's javac
does the right thing, producing a Serializable[] since this is the best possible
T that is compatible with both String and Integer.
Comment 1 Osvaldo Pinali Doederlein CLA 2005-06-09 17:05:20 EDT
FYI, I just re-tested with I20050609-1228, bug is still there.

Thre is another important detail: the bug depends on the types of all
variable arguments in some odd way.  For example,

 audit("osvaldo", "localhost", "logged", "X", "Y");
 audit("osvaldo", "localhost", "logged", new Float(0), new java.awt.Point(0));

will both compile correctly.  The first call is OK, maybe just because all
variable arguments are the same type.  But in the second call, there are args
of different types, and the compiler works too.  The suspect cause is that
the compilers is confused only when the vararg list begins with an argument
that has the same type as the last non-variable argument.  This is the case
of all places in my app code where this bug happened, and it surely explains
why the bug is very rare, and may hint at the root cause: perhaps some problem
in disambiguation of fixed vs. variable arguments, plus the generics factor...
Comment 2 Olivier Thomann CLA 2005-06-09 19:53:49 EDT
Created attachment 22762 [details]
Apply on HEAD

Proposed fix.
Philippe, please verify the part with Scope.greaterLowerBound(bounds)[0].
I get a code gen similar to what javac is doing.
And it runs fine.
Comment 3 Olivier Thomann CLA 2005-06-09 19:54:56 EDT
Created attachment 22763 [details]
Apply on HEAD

Regression test
Comment 4 Olivier Thomann CLA 2005-06-09 20:00:24 EDT
Created attachment 22764 [details]
Apply on HEAD

New patch that is using locals to acces bounds.
Comment 5 Philipe Mulet CLA 2005-06-10 06:34:35 EDT
I suspect this is not the proper fix. The wildcard represents here an
intersection type, and the glb there is ignoring the first bound which could
carry useful information.

I believe, it should rather simply use the erasure when generating arguments. We
would result in constructing an Object[] here, which is fine as it is all the
method is expecting anyway.
Comment 6 Philipe Mulet CLA 2005-06-10 06:44:06 EDT
My fix would rather be to use the expected type from codegen binding varargs
type, i.e.

in Statement#generateArguments, introduce 'codeGenVarArgsType' and used it for
#newArray(...) invocations.

ArrayBinding varArgsType = (ArrayBinding) params[varArgIndex]; // parameterType
has to be an array type
ArrayBinding codeGenVarArgsType = (ArrayBinding)
binding.original().parameters[varArgIndex].erasure();
Comment 7 Philipe Mulet CLA 2005-06-10 06:47:15 EDT
Released VarargsTest#test30 (from patch).
Fixed in latest.
Comment 8 Osvaldo Pinali Doederlein CLA 2005-06-10 10:13:50 EDT
Just wondering... there is one subtle aspect of ths bug that you may want to
consider as a separate bug: the fact that the compiler's classfile assembling
phase agreed to write a .class with an invalid entry in the constant pool -

const #22 = Asciz       ;
const #23 = class       #22;    //  ""

AFAIK a class named "" is not possible in any circumstances, not even for
synthetic or obfuscated code.  If the code that writes the classfile would
validate this, it would flag the problem instead of covering bugs from the
front-end compiler and silently producing a invalid, unverifiable classfile.
Comment 9 Philipe Mulet CLA 2005-06-10 10:28:42 EDT
Our infrastructure relies on the compiler to behave. Checking for this rare
situation to occur would penalize normal compilation. 

Though I agree it would help finding bugs in general.
Comment 10 Osvaldo Pinali Doederlein CLA 2005-06-10 10:55:10 EDT
I see.  Perhaps a good case for asserts, since Eclipse 3.0+ requires J2SE 1.4+?
When reporting bugs like this one and also bug 97440, it takes a reasonable effort
to produce a good bug report including test code that's simple, easy to reproduce,
and doesn't carry much code that cannot be disclosed publicly... so it'd be nice
if I could run Eclipse with -vmargs:ea, even if 10X slower, and get some clues.
Comment 11 Olivier Thomann CLA 2005-06-10 13:29:54 EDT
Verify in  I20050610-1200.
Comment 12 Olivier Thomann CLA 2005-06-10 13:34:07 EDT
Closing.
Comment 13 Tim Hanson CLA 2005-08-31 14:51:10 EDT
*** Bug 108497 has been marked as a duplicate of this bug. ***