# HG changeset patch # User jlahoda # Date 1562656804 -7200 # Node ID 0b470386f5f7d68415a8cd7ee526d2cf12bb1b77 # Parent ea3b1a8fd4bb9af12cd057dba5071fedcaa4ae83 8223443: Calling Trees.getScope early changes names of local/anonymous classes Summary: Ensure Trees.getScope does not affect the rest of the compilation. Reviewed-by: mcimadamore diff -r ea3b1a8fd4bb -r 0b470386f5f7 src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java Mon Jul 08 16:20:40 2019 -0700 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java Tue Jul 09 09:20:04 2019 +0200 @@ -28,13 +28,12 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.text.BreakIterator; -import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.WeakHashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; @@ -60,9 +59,8 @@ import com.sun.source.doctree.DocCommentTree; import com.sun.source.doctree.DocTree; -import com.sun.source.doctree.EndElementTree; -import com.sun.source.doctree.StartElementTree; import com.sun.source.tree.CatchTree; +import com.sun.source.tree.ClassTree; import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.Scope; import com.sun.source.tree.Tree; @@ -71,13 +69,11 @@ import com.sun.source.util.DocTreeScanner; import com.sun.source.util.DocTrees; import com.sun.source.util.JavacTask; -import com.sun.source.util.SimpleDocTreeVisitor; import com.sun.source.util.TreePath; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Scope.NamedImportScope; import com.sun.tools.javac.code.Scope.StarImportScope; import com.sun.tools.javac.code.Scope.WriteableScope; -import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.code.Symbol.ModuleSymbol; @@ -94,11 +90,13 @@ import com.sun.tools.javac.code.Types.TypeRelation; import com.sun.tools.javac.comp.Attr; import com.sun.tools.javac.comp.AttrContext; +import com.sun.tools.javac.comp.Check; import com.sun.tools.javac.comp.Enter; import com.sun.tools.javac.comp.Env; import com.sun.tools.javac.comp.MemberEnter; import com.sun.tools.javac.comp.Modules; import com.sun.tools.javac.comp.Resolve; +import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.file.BaseFileManager; import com.sun.tools.javac.model.JavacElements; import com.sun.tools.javac.parser.DocCommentParser; @@ -133,6 +131,7 @@ import com.sun.tools.javac.tree.TreeCopier; import com.sun.tools.javac.tree.TreeInfo; import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.tree.TreeScanner; import com.sun.tools.javac.util.Abort; import com.sun.tools.javac.util.Assert; import com.sun.tools.javac.util.Context; @@ -171,6 +170,7 @@ private Log log; private MemberEnter memberEnter; private Attr attr; + private Check chk; private TreeMaker treeMaker; private JavacElements elements; private JavacTaskImpl javacTaskImpl; @@ -218,6 +218,7 @@ private void init(Context context) { modules = Modules.instance(context); attr = Attr.instance(context); + chk = Check.instance(context); enter = Enter.instance(context); elements = JavacElements.instance(context); log = Log.instance(context); @@ -915,13 +916,13 @@ try { Assert.check(method.body == tree); method.body = copier.copy((JCBlock)tree, (JCTree) path.getLeaf()); - env = attribStatToTree(method.body, env, copier.leafCopy); + env = attribStatToTree(method.body, env, copier.leafCopy, copier.copiedClasses); } finally { method.body = (JCBlock) tree; } } else { JCBlock body = copier.copy((JCBlock)tree, (JCTree) path.getLeaf()); - env = attribStatToTree(body, env, copier.leafCopy); + env = attribStatToTree(body, env, copier.leafCopy, copier.copiedClasses); } return env; } @@ -930,7 +931,7 @@ if (field != null && field.getInitializer() == tree) { env = memberEnter.getInitEnv(field, env); JCExpression expr = copier.copy((JCExpression)tree, (JCTree) path.getLeaf()); - env = attribExprToTree(expr, env, copier.leafCopy); + env = attribExprToTree(expr, env, copier.leafCopy, copier.copiedClasses); return env; } } @@ -938,24 +939,137 @@ return (field != null) ? memberEnter.getInitEnv(field, env) : env; } - private Env attribStatToTree(JCTree stat, Envenv, JCTree tree) { + private Env attribStatToTree(JCTree stat, Envenv, + JCTree tree, Map copiedClasses) { JavaFileObject prev = log.useSource(env.toplevel.sourcefile); + Log.DiagnosticHandler diagHandler = new Log.DiscardDiagnosticHandler(log); try { - return attr.attribStatToTree(stat, env, tree); + Env result = attr.attribStatToTree(stat, env, tree); + + enter.unenter(env.toplevel, stat); + fixLocalClassNames(copiedClasses, env); + return result; } finally { + log.popDiagnosticHandler(diagHandler); + log.useSource(prev); + } + } + + private Env attribExprToTree(JCExpression expr, Envenv, + JCTree tree, Map copiedClasses) { + JavaFileObject prev = log.useSource(env.toplevel.sourcefile); + Log.DiagnosticHandler diagHandler = new Log.DiscardDiagnosticHandler(log); + try { + Env result = attr.attribExprToTree(expr, env, tree); + + enter.unenter(env.toplevel, expr); + fixLocalClassNames(copiedClasses, env); + return result; + } finally { + log.popDiagnosticHandler(diagHandler); log.useSource(prev); } } - private Env attribExprToTree(JCExpression expr, Envenv, JCTree tree) { - JavaFileObject prev = log.useSource(env.toplevel.sourcefile); - try { - return attr.attribExprToTree(expr, env, tree); - } finally { - log.useSource(prev); + /* Change the flatnames of the local and anonymous classes in the Scope to + * the names they would have if the whole file was attributed normally. + */ + private void fixLocalClassNames(Map copiedClasses, + Env lastEnv) { + Map flatnameForClass = null; + + for (Entry e : copiedClasses.entrySet()) { + if (e.getKey().sym != null) { + Name origName; + if (e.getValue().sym != null) { + //if the source tree was already attributed, use the flatname + //from the source tree's Symbol: + origName = e.getValue().sym.flatname; + } else { + //otherwise, compute the flatnames (for source trees) as + //if the full source code would be attributed: + if (flatnameForClass == null) { + flatnameForClass = prepareFlatnameForClass(lastEnv); + } + origName = flatnameForClass.get(e.getValue()); + } + if (origName != null) { + e.getKey().sym.flatname = origName; + } + } } } + /* This method computes and assigns flatnames to trees, as if they would be + * normally assigned during attribution of the full source code. + */ + private Map prepareFlatnameForClass(Env env) { + Map flatNameForClass = new HashMap<>(); + Symbol enclClass = env.enclClass.sym; + + if (enclClass != null && (enclClass.flags_field & Flags.UNATTRIBUTED) != 0) { + ListBuffer toClear = new ListBuffer<>(); + new TreeScanner() { + Symbol owner; + boolean localContext; + @Override + public void visitClassDef(JCClassDecl tree) { + //compute the name (and ClassSymbol) which would be used + //for this class for full attribution + Symbol prevOwner = owner; + try { + ClassSymbol c; + if (tree.sym != null) { + //already entered: + c = tree.sym; + } else { + c = syms.defineClass(tree.name, owner); + if (owner.kind != TYP) { + //for local classes, assign the flatname + c.flatname = chk.localClassName(c); + chk.putCompiled(c); + toClear.add(c); + } + flatNameForClass.put(tree, c.flatname); + } + owner = c; + super.visitClassDef(tree); + } finally { + owner = prevOwner; + } + } + + @Override + public void visitBlock(JCBlock tree) { + Symbol prevOwner = owner; + try { + owner = new MethodSymbol(0, names.empty, Type.noType, owner); + super.visitBlock(tree); + } finally { + owner = prevOwner; + } + } + @Override + public void visitVarDef(JCVariableDecl tree) { + Symbol prevOwner = owner; + try { + owner = new MethodSymbol(0, names.empty, Type.noType, owner); + super.visitVarDef(tree); + } finally { + owner = prevOwner; + } + } + }.scan(env.enclClass); + //revert changes done by the visitor: + toClear.stream().forEach(c -> { + chk.clearLocalClassNameIndexes(c); + chk.removeCompiled(c); + }); + } + + return flatNameForClass; + } + static JavaFileObject asJavaFileObject(FileObject fileObject) { JavaFileObject jfo = null; @@ -1065,6 +1179,7 @@ **/ protected static class Copier extends TreeCopier { JCTree leafCopy = null; + private Map copiedClasses = new HashMap<>(); protected Copier(TreeMaker M) { super(M); @@ -1077,6 +1192,14 @@ leafCopy = t2; return t2; } + + @Override + public JCTree visitClass(ClassTree node, JCTree p) { + JCTree nue = super.visitClass(node, p); + copiedClasses.put((JCClassDecl) nue, (JCClassDecl) node); + return nue; + } + } protected Copier createCopier(TreeMaker maker) { diff -r ea3b1a8fd4bb -r 0b470386f5f7 src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java Mon Jul 08 16:20:40 2019 -0700 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java Tue Jul 09 09:20:04 2019 +0200 @@ -407,7 +407,7 @@ * enclClass is the flat name of the enclosing class, * classname is the simple name of the local class */ - Name localClassName(ClassSymbol c) { + public Name localClassName(ClassSymbol c) { Name enclFlatname = c.owner.enclClass().flatname; String enclFlatnameStr = enclFlatname.toString(); Pair key = new Pair<>(enclFlatname, c.name); @@ -422,7 +422,7 @@ } } - void clearLocalClassNameIndexes(ClassSymbol c) { + public void clearLocalClassNameIndexes(ClassSymbol c) { if (c.owner != null && c.owner.kind != NIL) { localClassNameIndexes.remove(new Pair<>( c.owner.enclClass().flatname, c.name)); diff -r ea3b1a8fd4bb -r 0b470386f5f7 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 Mon Jul 08 16:20:40 2019 -0700 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/DeferredAttr.java Tue Jul 09 09:20:04 2019 +0200 @@ -41,7 +41,6 @@ import com.sun.tools.javac.util.DefinedBy.Api; import com.sun.tools.javac.util.GraphUtils.DependencyKind; import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; -import com.sun.tools.javac.code.Symbol.*; import com.sun.tools.javac.comp.Attr.ResultInfo; import com.sun.tools.javac.comp.Resolve.MethodResolutionPhase; import com.sun.tools.javac.resources.CompilerProperties.Errors; @@ -501,7 +500,7 @@ attr.attribTree(newTree, speculativeEnv, resultInfo); return newTree; } finally { - new UnenterScanner(env.toplevel.modle).scan(newTree); + enter.unenter(env.toplevel, newTree); log.popDiagnosticHandler(deferredDiagnosticHandler); if (localCache != null) { localCache.leave(); @@ -509,29 +508,6 @@ } } //where - - class UnenterScanner extends TreeScanner { - private final ModuleSymbol msym; - - public UnenterScanner(ModuleSymbol msym) { - this.msym = msym; - } - - @Override - public void visitClassDef(JCClassDecl tree) { - ClassSymbol csym = tree.sym; - //if something went wrong during method applicability check - //it is possible that nested expressions inside argument expression - //are left unchecked - in such cases there's nothing to clean up. - if (csym == null) return; - typeEnvs.remove(csym); - chk.removeCompiled(csym); - chk.clearLocalClassNameIndexes(csym); - syms.removeClass(msym, csym.flatname); - super.visitClassDef(tree); - } - } - static class DeferredAttrDiagHandler extends Log.DeferredDiagnosticHandler { static class PosScanner extends TreeScanner { diff -r ea3b1a8fd4bb -r 0b470386f5f7 src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Enter.java --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Enter.java Mon Jul 08 16:20:40 2019 -0700 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Enter.java Tue Jul 09 09:20:04 2019 +0200 @@ -610,4 +610,29 @@ public void newRound() { typeEnvs.clear(); } + + public void unenter(JCCompilationUnit topLevel, JCTree tree) { + new UnenterScanner(topLevel.modle).scan(tree); + } + class UnenterScanner extends TreeScanner { + private final ModuleSymbol msym; + + public UnenterScanner(ModuleSymbol msym) { + this.msym = msym; + } + + @Override + public void visitClassDef(JCClassDecl tree) { + ClassSymbol csym = tree.sym; + //if something went wrong during method applicability check + //it is possible that nested expressions inside argument expression + //are left unchecked - in such cases there's nothing to clean up. + if (csym == null) return; + typeEnvs.remove(csym); + chk.removeCompiled(csym); + chk.clearLocalClassNameIndexes(csym); + syms.removeClass(msym, csym.flatname); + super.visitClassDef(tree); + } + } } diff -r ea3b1a8fd4bb -r 0b470386f5f7 test/langtools/tools/javac/api/TestGetScopeBinaryNames.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/langtools/tools/javac/api/TestGetScopeBinaryNames.java Tue Jul 09 09:20:04 2019 +0200 @@ -0,0 +1,291 @@ +/* + * 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 8223443 + * @summary Verify binary names are not changed and are correct + * when using Trees.getScope + * @modules jdk.compiler + */ + +import com.sun.source.tree.ClassTree; +import java.io.IOException; +import java.net.URI; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.lang.model.element.Element; +import javax.lang.model.element.NestingKind; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.Elements; +import javax.tools.JavaCompiler; +import javax.tools.SimpleJavaFileObject; +import javax.tools.ToolProvider; + +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.Scope; +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; +import com.sun.source.util.JavacTask; +import com.sun.source.util.TaskEvent; +import com.sun.source.util.TaskListener; +import com.sun.source.util.TreePath; +import com.sun.source.util.TreePathScanner; +import com.sun.source.util.Trees; + +import static javax.tools.JavaFileObject.Kind.SOURCE; + +public class TestGetScopeBinaryNames { + public static void main(String... args) throws IOException { + new TestGetScopeBinaryNames().run(); + } + + public void run() throws IOException { + class EnclosingDesc { + final String code; + final boolean supportsLocal; + public EnclosingDesc(String code, boolean supportsLocal) { + this.code = code; + this.supportsLocal = supportsLocal; + } + } + List enclosingEnvs = List.of( + new EnclosingDesc("class Test {" + + " void test() {" + + " $" + + " }" + + "}", + true), + new EnclosingDesc("class Test {" + + " {" + + " $" + + " }" + + "}", + true), + new EnclosingDesc("class Test {" + + " static {" + + " $" + + " }" + + "}", + true), + new EnclosingDesc("class Test {" + + " Object I = $" + + "}", + true) + ); + class LocalDesc { + final String localCode; + final boolean isLocalClass; + public LocalDesc(String localCode, boolean isLocalClass) { + this.localCode = localCode; + this.isLocalClass = isLocalClass; + } + } + List locals = List.of( + new LocalDesc("new A() {" + + " class AI extends B {" + + " class AII extends C {" + + " private void t() {" + + " new D() { class DI extends E {} };" + + " }" + + " }" + + " private void t() { new F() {}; }" + + " }" + + " private void t() { new G() {}; }" + + "};", + false), + new LocalDesc("class AA extends A {" + + " class AI extends B {" + + " class AII extends C {" + + " private void t() {" + + " new D() { class DI extends E {} };" + + " }" + + " }" + + " private void t() { new F() {}; }" + + " }" + + " private void t() { new G() {}; }" + + "}", + false) + ); + String markerClasses = "class A {} class B {} class C {}" + + "class D {} class E {} class F {}" + + "class G {}"; + for (EnclosingDesc enclosing : enclosingEnvs) { + for (LocalDesc local : locals) { + if (!local.isLocalClass || enclosing.supportsLocal) { + doTest(enclosing.code.replace("$", local.localCode) + + markerClasses); + } + } + } + } + + void doTest(String code, String... expected) throws IOException { + Map name2BinaryName = new HashMap<>(); + Map name2QualifiedName = new HashMap<>(); + + computeNames(code, name2BinaryName, name2QualifiedName); + + JavaCompiler c = ToolProvider.getSystemJavaCompiler(); + JavacTask t = (JavacTask) c.getTask(null, null, null, null, null, + List.of(new MyFileObject(code))); + CompilationUnitTree cut = t.parse().iterator().next(); + Trees trees = Trees.instance(t); + + t.addTaskListener(new TaskListener() { + @Override + public void finished(TaskEvent e) { + if (e.getKind() == TaskEvent.Kind.ENTER) { + new TreePathScanner() { + @Override + public Void scan(Tree tree, Void p) { + if (tree != null && + !isInExtendsClause(getCurrentPath(), tree)) { + TreePath path = + new TreePath(getCurrentPath(), tree); + Scope scope = trees.getScope(path); + checkScope(t.getElements(), scope, + name2BinaryName, name2QualifiedName); + } + return super.scan(tree, p); + } + }.scan(cut, null); + } + } + }); + + t.analyze(); + + new TreePathScanner() { + @Override + public Void visitClass(ClassTree node, Void p) { + TypeElement type = + (TypeElement) trees.getElement(getCurrentPath()); + checkClass(t.getElements(), type, + name2BinaryName, name2QualifiedName); + return super.visitClass(node, p); + } + }.scan(cut, null); + + new TreePathScanner() { + @Override + public Void scan(Tree tree, Void p) { + if (tree != null && + !isInExtendsClause(getCurrentPath(), tree)) { + TreePath path = + new TreePath(getCurrentPath(), tree); + Scope scope = trees.getScope(path); + checkScope(t.getElements(), scope, + name2BinaryName, name2QualifiedName); + } + return super.scan(tree, p); + } + }.scan(cut, null); + } + + void computeNames(String code, + Map name2BinaryName, + Map name2QualifiedName) throws IOException { + JavaCompiler c = ToolProvider.getSystemJavaCompiler(); + JavacTask t = (JavacTask) c.getTask(null, null, null, null, null, + List.of(new MyFileObject(code))); + CompilationUnitTree cut = t.parse().iterator().next(); + + t.analyze(); + + new TreePathScanner() { + Trees trees = Trees.instance(t); + Elements els = t.getElements(); + @Override + public Void visitClass(ClassTree node, Void p) { + TypeElement type = + (TypeElement) trees.getElement(getCurrentPath()); + String key = type.getSuperclass().toString(); + + name2BinaryName.put(key, els.getBinaryName(type).toString()); + name2QualifiedName.put(key, type.getQualifiedName().toString()); + return super.visitClass(node, p); + } + }.scan(cut, null); + } + + boolean isInExtendsClause(TreePath clazz, Tree toCheck) { + return clazz != null && + clazz.getLeaf().getKind() == Kind.CLASS && + ((ClassTree) clazz.getLeaf()).getExtendsClause() == toCheck; + } + + void checkClass(Elements els, TypeElement type, + Map name2BinaryName, + Map name2QualifiedName) { + if (type.getNestingKind() == NestingKind.TOP_LEVEL || + type.getNestingKind() == NestingKind.MEMBER) { + return ; + } + + String binaryName = name2BinaryName.get(type.getSuperclass().toString()); + + if (!els.getBinaryName(type).contentEquals(binaryName)) { + throw new AssertionError("Unexpected: " + els.getBinaryName(type)); + } + + String qualifiedName = name2QualifiedName.get(type.getSuperclass().toString()); + + if (qualifiedName != null) { + if (!type.getQualifiedName().contentEquals(qualifiedName)) { + throw new AssertionError("Unexpected: " + type.getQualifiedName() + + ", expected: " + qualifiedName); + } + } + } + + void checkScope(Elements els, Scope scope, + Map name2BinaryName, + Map name2QualifiedName) { + while (scope != null) { + for (Element el : scope.getLocalElements()) { + if (el.getKind().isClass()) { + checkClass(els, (TypeElement) el, + name2BinaryName, name2QualifiedName); + } + } + scope = scope.getEnclosingScope(); + } + } + + class MyFileObject extends SimpleJavaFileObject { + private final String code; + + MyFileObject(String code) { + super(URI.create("myfo:///Test.java"), SOURCE); + this.code = code; + } + @Override + public String getCharContent(boolean ignoreEncodingErrors) { + return code; + } + } +} + diff -r ea3b1a8fd4bb -r 0b470386f5f7 test/langtools/tools/javac/api/TestGetScopeErrors.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/langtools/tools/javac/api/TestGetScopeErrors.java Tue Jul 09 09:20:04 2019 +0200 @@ -0,0 +1,131 @@ +/* + * 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 8223443 + * @summary Verify errors are not reported when computing Scopes. + * @modules jdk.compiler + */ + +import java.io.IOException; +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.tools.Diagnostic; +import javax.tools.DiagnosticListener; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; +import javax.tools.ToolProvider; + +import static javax.tools.JavaFileObject.Kind.SOURCE; + +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.Tree; +import com.sun.source.util.JavacTask; +import com.sun.source.util.TaskEvent; +import com.sun.source.util.TaskListener; +import com.sun.source.util.TreePath; +import com.sun.source.util.TreePathScanner; +import com.sun.source.util.Trees; + +public class TestGetScopeErrors { + public static void main(String... args) throws IOException { + new TestGetScopeErrors().run(); + } + + void run() throws IOException { + JavaCompiler c = ToolProvider.getSystemJavaCompiler(); + String code = + "public class Test {" + + " private Object obj = new Object() {" + + " private Unresolvable u;" + + " };" + + " void test() {" + + " new Object() {" + + " private Unresolvable u;" + + " };" + + " }" + + "}"; + class MyFileObject extends SimpleJavaFileObject { + MyFileObject() { + super(URI.create("myfo:///Test.java"), SOURCE); + } + @Override + public String getCharContent(boolean ignoreEncodingErrors) { + return code; + } + } + AtomicBoolean enterDone = new AtomicBoolean(); + List errors = new ArrayList<>(); + DiagnosticListener noErrors = d -> { + if (!enterDone.get() && d.getKind() == Diagnostic.Kind.ERROR) { + throw new AssertionError(d.toString()); + } + errors.add(d.getSource().getName() + ":" + + d.getPosition() + ":" + + d.getCode()); + }; + JavacTask t = + (JavacTask) c.getTask(null, null, noErrors, + Arrays.asList("-XDrawDiagnostics"), + null, List.of(new MyFileObject())); + CompilationUnitTree cut = t.parse().iterator().next(); + Trees trees = Trees.instance(t); + t.addTaskListener(new TaskListener() { + @Override + public void finished(TaskEvent e) { + if (e.getKind() == TaskEvent.Kind.ENTER) { + new TreePathScanner() { + @Override + public Void scan(Tree tree, Void p) { + if (tree != null) { + TreePath path = + new TreePath(getCurrentPath(), tree); + trees.getScope(path); + } + return super.scan(tree, p); + } + }.scan(cut, null); + enterDone.set(true); + } + } + }); + + t.analyze(); + + List expectedErrors = List.of( + "/Test.java:74:compiler.err.cant.resolve", + "/Test.java:154:compiler.err.cant.resolve" + ); + + if (!expectedErrors.equals(errors)) { + throw new IllegalStateException("Unexpected errors: " + errors); + } + } + +}