# HG changeset patch # User jlahoda # Date 1567679988 -7200 # Thu Sep 05 12:39:48 2019 +0200 # Node ID 1ffdb3d86c0dea08e3302b5f956fe043e4d5d01d # Parent 3c184f05cdf1df68ae2aa94f7b9ce706d2ed2dc7 8177068: incomplete classpath causes NPE in Flow Summary: Undo completions that failed during speculative attribution, so that the appropriate CompletionFailures are thrown again and properly reported. Reviewed-by: vromero diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/DeferredCompletionFailureHandler.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/DeferredCompletionFailureHandler.java --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/DeferredCompletionFailureHandler.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/DeferredCompletionFailureHandler.java @@ -86,6 +86,26 @@ } }; + public final Handler speculativeCodeHandler = new Handler() { + private final Map class2Flip = new HashMap<>(); + + public void install() { + } + public void handleAPICompletionFailure(CompletionFailure cf) { + throw cf; + } + public void classSymbolCompleteFailed(ClassSymbol sym, Completer origCompleter) { + class2Flip.put(sym, new FlipSymbolDescription(sym, new DeferredCompleter(origCompleter))); + } + public void classSymbolRemoved(ClassSymbol sym) { + class2Flip.remove(sym); + } + public void uninstall() { + class2Flip.values().forEach(f -> f.flip()); + class2Flip.clear(); + } + }; + public final Handler javacCodeHandler = new Handler() { public void install() { } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java @@ -2626,6 +2626,8 @@ resultInfo.checkContext.report(that, cause); result = that.type = types.createErrorType(pt()); return; + } catch (CompletionFailure cf) { + chk.completionError(that.pos(), cf); } catch (Throwable t) { //when an unexpected exception happens, avoid attempts to attribute the same tree again //as that would likely cause the same exception again. diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/DeferredAttr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/DeferredAttr.java --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/DeferredAttr.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/DeferredAttr.java @@ -94,6 +94,7 @@ final Flow flow; final Names names; final TypeEnvs typeEnvs; + final DeferredCompletionFailureHandler dcfh; public static DeferredAttr instance(Context context) { DeferredAttr instance = context.get(deferredAttrKey); @@ -119,6 +120,7 @@ names = Names.instance(context); stuckTree = make.Ident(names.empty).setType(Type.stuckType); typeEnvs = TypeEnvs.instance(context); + dcfh = DeferredCompletionFailureHandler.instance(context); emptyDeferredAttrContext = new DeferredAttrContext(AttrMode.CHECK, null, MethodResolutionPhase.BOX, infer.emptyContext, null, null) { @Override @@ -479,12 +481,12 @@ */ JCTree attribSpeculative(JCTree tree, Env env, ResultInfo resultInfo) { return attribSpeculative(tree, env, resultInfo, treeCopier, - (newTree)->new DeferredAttrDiagHandler(log, newTree), null); + newTree->new DeferredDiagnosticHandler(log), null); } JCTree attribSpeculative(JCTree tree, Env env, ResultInfo resultInfo, LocalCacheContext localCache) { return attribSpeculative(tree, env, resultInfo, treeCopier, - (newTree)->new DeferredAttrDiagHandler(log, newTree), localCache); + newTree->new DeferredDiagnosticHandler(log), localCache); } JCTree attribSpeculative(JCTree tree, Env env, ResultInfo resultInfo, TreeCopier deferredCopier, @@ -494,10 +496,12 @@ Env speculativeEnv = env.dup(newTree, env.info.dup(env.info.scope.dupUnshared(env.info.scope.owner))); speculativeEnv.info.isSpeculative = true; Log.DeferredDiagnosticHandler deferredDiagnosticHandler = diagHandlerCreator.apply(newTree); + DeferredCompletionFailureHandler.Handler prevCFHandler = dcfh.setHandler(dcfh.speculativeCodeHandler); try { attr.attribTree(newTree, speculativeEnv, resultInfo); return newTree; } finally { + dcfh.setHandler(prevCFHandler); new UnenterScanner(env.toplevel.modle).scan(newTree); log.popDiagnosticHandler(deferredDiagnosticHandler); if (localCache != null) { @@ -529,35 +533,6 @@ } } - static class DeferredAttrDiagHandler extends Log.DeferredDiagnosticHandler { - - static class PosScanner extends TreeScanner { - DiagnosticPosition pos; - boolean found = false; - - PosScanner(DiagnosticPosition pos) { - this.pos = pos; - } - - @Override - public void scan(JCTree tree) { - if (tree != null && - tree.pos() == pos) { - found = true; - } - super.scan(tree); - } - } - - DeferredAttrDiagHandler(Log log, JCTree newTree) { - super(log, d -> { - PosScanner posScanner = new PosScanner(d.getDiagnosticPosition()); - posScanner.scan(newTree); - return posScanner.found; - }); - } - } - /** * A deferred context is created on each method check. A deferred context is * used to keep track of information associated with the method check, such as diff --git a/test/langtools/tools/javac/T8177068/NoCompletionFailureSkipOnSpeculativeAttribution.java b/test/langtools/tools/javac/T8177068/NoCompletionFailureSkipOnSpeculativeAttribution.java new file mode 100644 --- /dev/null +++ b/test/langtools/tools/javac/T8177068/NoCompletionFailureSkipOnSpeculativeAttribution.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8177068 + * @summary CompletionFailures occurring during speculative attribution should + * not be lost forever. + * @library /tools/lib + * @modules jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * jdk.compiler/com.sun.tools.javac.util + * @build toolbox.ToolBox + * @run main NoCompletionFailureSkipOnSpeculativeAttribution + */ + +import java.io.File; +import java.nio.file.Paths; +import java.util.List; + +import com.sun.tools.javac.util.Assert; + +import toolbox.JavacTask; +import toolbox.Task; +import toolbox.ToolBox; + +public class NoCompletionFailureSkipOnSpeculativeAttribution { + + private static final String TSrc = + "import one.A;\n" + + "class T {\n" + + " {\n" + + " System.err.println(two.C.D.g());\n" + + " }\n" + + "}"; + + private static final String CSrc = + "package two;\n" + + "public class C {\n" + + " public static class D {\n" + + " public static int g() {\n" + + " return 1;\n" + + " }\n" + + " }\n" + + "}"; + + private static final String ASrc = + "package one;\n" + + "public class A {\n" + + " public A(two.C.D x) {}\n" + + "}"; + + public static void main(String[] args) throws Exception { + new NoCompletionFailureSkipOnSpeculativeAttribution().test(); + } + + public void test() throws Exception { + ToolBox tb = new ToolBox(); + tb.writeJavaFiles(Paths.get("."), ASrc, CSrc, TSrc); + + new JavacTask(tb) + .classpath(".") + .files("T.java") + .run(); + + tb.deleteFiles("two/C.class", "two/C$D.class"); + + List output = new JavacTask(tb) + .sourcepath(File.pathSeparator) + .options("-XDrawDiagnostics", "-XDshould-stop.ifError=FLOW") + .classpath(".") + .files("T.java") + .run(Task.Expect.FAIL) + .writeAll() + .getOutputLines(Task.OutputKind.DIRECT); + + List expectedOutput = List.of( + "T.java:4:29: compiler.err.cant.access: two.C.D, (compiler.misc.class.file.not.found: two.C$D)", + "1 error" + ); + + Assert.check(output.equals(expectedOutput)); + } +}