JDK-6798988 : NullPointerException when compiling a simple generic method
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 6u10
  • Priority: P4
  • Status: Closed
  • Resolution: Future Project
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2009-01-29
  • Updated: 2011-09-17
  • Resolved: 2009-07-30
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.6.0_11"
Java(TM) SE Runtime Environment (build 1.6.0_11-b03)
Java HotSpot(TM) Client VM (build 11.0-b16, mixed mode, sharing)

ADDITIONAL OS VERSION INFORMATION :
Windows XP Professional Version 2002 Service Pack 2

A DESCRIPTION OF THE PROBLEM :
When trying to compile java code with a simple generic method javac throws NullPointerException and produces no class files.

The code and the stacktrace are attached to the report.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
just run javac against the given source code file

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Get working class files

ERROR MESSAGES/STACK TRACES THAT OCCUR :
java.lang.NullPointerException
        at com.sun.tools.javac.comp.Flow.visitApply(Flow.java:1117)
        at com.sun.tools.javac.tree.JCTree$JCMethodInvocation.accept(JCTree.java:1210)
        at com.sun.tools.javac.tree.TreeScanner.scan(TreeScanner.java:35)
        at com.sun.tools.javac.tree.TreeScanner.visitExec(TreeScanner.java:157)
        at com.sun.tools.javac.tree.JCTree$JCExpressionStatement.accept(JCTree.java:1074)
        at com.sun.tools.javac.tree.TreeScanner.scan(TreeScanner.java:35)
        at com.sun.tools.javac.comp.Flow.scanStat(Flow.java:484)
        at com.sun.tools.javac.comp.Flow.scanStats(Flow.java:492)
        at com.sun.tools.javac.comp.Flow.visitBlock(Flow.java:747)
        at com.sun.tools.javac.tree.JCTree$JCBlock.accept(JCTree.java:739)
        at com.sun.tools.javac.tree.TreeScanner.scan(TreeScanner.java:35)
        at com.sun.tools.javac.comp.Flow.scanStat(Flow.java:484)
        at com.sun.tools.javac.comp.Flow.visitIf(Flow.java:1073)
        at com.sun.tools.javac.tree.JCTree$JCIf.accept(JCTree.java:1050)
        at com.sun.tools.javac.tree.TreeScanner.scan(TreeScanner.java:35)
        at com.sun.tools.javac.comp.Flow.scanStat(Flow.java:484)
        at com.sun.tools.javac.comp.Flow.scanStats(Flow.java:492)
        at com.sun.tools.javac.comp.Flow.visitBlock(Flow.java:747)
        at com.sun.tools.javac.tree.JCTree$JCBlock.accept(JCTree.java:739)
        at com.sun.tools.javac.tree.TreeScanner.scan(TreeScanner.java:35)
        at com.sun.tools.javac.comp.Flow.scanStat(Flow.java:484)
        at com.sun.tools.javac.comp.Flow.visitForeachLoop(Flow.java:864)
        at com.sun.tools.javac.tree.JCTree$JCEnhancedForLoop.accept(JCTree.java:849)
        at com.sun.tools.javac.tree.TreeScanner.scan(TreeScanner.java:35)
        at com.sun.tools.javac.comp.Flow.scanStat(Flow.java:484)
        at com.sun.tools.javac.comp.Flow.scanStats(Flow.java:492)
        at com.sun.tools.javac.comp.Flow.visitBlock(Flow.java:747)
        at com.sun.tools.javac.tree.JCTree$JCBlock.accept(JCTree.java:739)
        at com.sun.tools.javac.tree.TreeScanner.scan(TreeScanner.java:35)
        at com.sun.tools.javac.comp.Flow.scanStat(Flow.java:484)
        at com.sun.tools.javac.comp.Flow.visitMethodDef(Flow.java:693)
        at com.sun.tools.javac.tree.JCTree$JCMethodDecl.accept(JCTree.java:639)
        at com.sun.tools.javac.tree.TreeScanner.scan(TreeScanner.java:35)
        at com.sun.tools.javac.comp.Flow.visitClassDef(Flow.java:641)
        at com.sun.tools.javac.tree.JCTree$JCClassDecl.accept(JCTree.java:575)
        at com.sun.tools.javac.tree.TreeScanner.scan(TreeScanner.java:35)
        at com.sun.tools.javac.comp.Flow.analyzeTree(Flow.java:1256)
        at com.sun.tools.javac.main.JavaCompiler.flow(JavaCompiler.java:1090)
        at com.sun.tools.javac.main.JavaCompiler.flow(JavaCompiler.java:1064)
        at com.sun.tools.javac.main.JavaCompiler.compile2(JavaCompiler.java:765)
        at com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:730)
        at com.sun.tools.javac.main.Main.compile(Main.java:353)
        at com.sun.tools.javac.main.Main.compile(Main.java:279)
        at com.sun.tools.javac.main.Main.compile(Main.java:270)
        at com.sun.tools.javac.Main.compile(Main.java:69)
        at com.sun.tools.javac.Main.main(Main.java:54)


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/*
 * JavacBug.java
 *
 */

/**
 *
 * @author Daniil Elovkov
 */
public class JavacBug {
    
    // function to transform a list of things augmented with key to a map of lists

    // THIS METHOD IS THE PROBLEM

    private <T> Map<String,List<T>> listToListMap(List<Keyed<? extends T>> list ) {
        
        Map<String,List<T>> map = new TreeMap<String,List<T>>();
        
        for (Keyed<? extends T> pair: list) {
            
            List<T> l = map.get(pair.key);
            
            if (l == null) {
                l = new ArrayList<T>( );
                l.add( pair.val );
                map.put( pair.key, l );
            } else {
                l.add( pair.val );
            }
            
        }
        
        return map;
    }
    
}

class Keyed<T extends MyClass> {
    String key;
    T val;
}

class MyClass {
}

---------- END SOURCE ----------

Comments
EVALUATION Type variables are more like interfaces. The intent of 5.1.10 is that capture-conversion should lead to well-formed intersection types, up to the limits of known type information. X is not known within the body of <X>test(..), so the intersection type X&Integer is legal. As an aside, 5.1.10 is dumb to ignore the bound of B in '? extends B'. The bound is ignored whether B is a formal type parameter of a class or of a generic method. For example, if we had: private void test(Test<? extends T> test) {..} then the capture of <? extends T> would be glb(T, Integer) and the question in the CR would arise. It would make sense to substitute T with Integer, or X with Object in the previous Evaluation's example. glb's operands would be classes more often, and the captured intersection type would be more precise. Is there a regression impact to this spec change?
27-03-2009

EVALUATION As a general strategy, replacing type-variables with their bound when computing glb lead to inconsistencies - consider the following program: class C<T> { public static void m(List<? extends T> l) { m(l); } } The call to m() would be rejected: in fact, capturing List<? extends T> would lead to the type List<#1> where upper(#1) = glb(Object, [T:=Object]T) = glb(Object,Object) = Object. It follows that List<#1> is not a subtype of List<? extends T>, because #1 is not contained by ? extends T. IMHO, we should allow both type-variables and interfaces when computing glb - but we should also impose a restriction on how non-class types could be added to an intersection type: if we want to compute glb(T1, T2) *) if both T1 and T2 are classes, then either T1 <: T2 or T2 <: T1, otherwise a compile-time error occurs; *) otherwise either T1 ~> T2 or T2 ~> T1 (where X ~> Y means X castable to Y), or a compile-time error occurs. This new rule would allow to accept all the example in this CR and also to fix a part of 6718388 - in that these new rules will always produce intersection types whose component types have pairwise different erasures (e.g. glb cannot generate the type List<String> & List<Integer> - in the samw way it cannot generate a type String & X where bound(X) = Integer). What about this?
27-03-2009

EVALUATION Since 7 b33 this is not reproducible because of fix of 6594284 - however now javac reports an error where the program is probably correct - here's a simplified test case: class Test<T extends Integer> { T key; void m(Object o) {} private <X> void test(Test<? extends X> test) { m(test.key); } } Javac is crashing/reporting an error because it thinks that the capture of Test<? extends X> does not exist. On the other hand, by reading JLS, 5.1.10 it seems like capture conversion should exists: capture(Test<? extends X>) == Keyed<#1>, where upper-bound(#1) = glb(X, Integer) = Integer & X This should be correct as even if neither X <: Integer nor Integer <: X, because X it's not a class type, while JLS 5.1.10 state the following: "If Ti is a wildcard type argument of the form ? extends Bi, then Si is a fresh type variable whose upper bound is glb(Bi, Ui[A1 := S1, ..., An := Sn]) and whose lower bound is the null type, where glb(V1,... ,Vm) is V1 & ... & Vm. It is a compile-time error if for any two classes (not interfaces) Vi and Vj,Vi is not a subclass of Vj or vice versa" In other words - how should we interpret that 'for any two classes' in the sentence above? Are type-variables more like classes or interfaces? The former interpretation lead to a compiler error while the latter accepts the above program.
29-01-2009