Skip to content

Commit 044a5a5

Browse files
cushonCompile-Testing Team
authored andcommitted
Improve parse error handling in compile-testing
RELNOTES=Improve parse error handling PiperOrigin-RevId: 592691296
1 parent e794c75 commit 044a5a5

File tree

3 files changed

+62
-98
lines changed

3 files changed

+62
-98
lines changed

pom.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,9 @@
251251
--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
252252
--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
253253
--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
254+
--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
255+
--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
256+
--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
254257
</test.jvm.flags>
255258
</properties>
256259
</profile>

src/main/java/com/google/testing/compile/Parser.java

Lines changed: 32 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -15,37 +15,33 @@
1515
*/
1616
package com.google.testing.compile;
1717

18-
import static com.google.common.base.MoreObjects.firstNonNull;
19-
import static java.lang.Boolean.TRUE;
2018
import static java.nio.charset.StandardCharsets.UTF_8;
21-
import static java.util.function.Predicate.isEqual;
2219
import static javax.tools.Diagnostic.Kind.ERROR;
2320

2421
import com.google.common.base.Joiner;
2522
import com.google.common.collect.ImmutableList;
2623
import com.google.common.collect.ImmutableListMultimap;
27-
import com.google.common.collect.ImmutableSet;
28-
import com.google.common.collect.Iterables;
2924
import com.google.common.collect.Multimaps;
3025
import com.sun.source.tree.CompilationUnitTree;
31-
import com.sun.source.tree.ErroneousTree;
32-
import com.sun.source.tree.Tree;
3326
import com.sun.source.util.JavacTask;
34-
import com.sun.source.util.TreeScanner;
3527
import com.sun.source.util.Trees;
36-
import com.sun.tools.javac.api.JavacTool;
28+
import com.sun.tools.javac.api.JavacTrees;
29+
import com.sun.tools.javac.file.JavacFileManager;
30+
import com.sun.tools.javac.parser.JavacParser;
31+
import com.sun.tools.javac.parser.ParserFactory;
32+
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
3733
import com.sun.tools.javac.util.Context;
34+
import com.sun.tools.javac.util.Log;
3835
import java.io.IOException;
36+
import java.util.ArrayList;
3937
import java.util.List;
40-
import java.util.Locale;
4138
import javax.tools.Diagnostic;
4239
import javax.tools.DiagnosticCollector;
43-
import javax.tools.JavaCompiler;
40+
import javax.tools.DiagnosticListener;
4441
import javax.tools.JavaFileObject;
45-
import javax.tools.ToolProvider;
4642

4743
/** Methods to parse Java source files. */
48-
public final class Parser {
44+
final class Parser {
4945

5046
/**
5147
* Parses {@code sources} into {@linkplain CompilationUnitTree compilation units}. This method
@@ -55,89 +51,42 @@ public final class Parser {
5551
* @throws IllegalStateException if any parsing errors occur.
5652
*/
5753
static ParseResult parse(Iterable<? extends JavaFileObject> sources, String sourcesDescription) {
58-
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
5954
DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
60-
InMemoryJavaFileManager fileManager =
61-
new InMemoryJavaFileManager(
62-
compiler.getStandardFileManager(diagnosticCollector, Locale.getDefault(), UTF_8));
6355
Context context = new Context();
64-
JavacTask task =
65-
((JavacTool) compiler)
66-
.getTask(
67-
null, // explicitly use the default because old javac logs some output on stderr
68-
fileManager,
69-
diagnosticCollector,
70-
ImmutableSet.<String>of(),
71-
ImmutableSet.<String>of(),
72-
sources,
73-
context);
56+
context.put(DiagnosticListener.class, diagnosticCollector);
57+
Log log = Log.instance(context);
58+
// The constructor registers the instance in the Context
59+
JavacFileManager unused = new JavacFileManager(context, true, UTF_8);
60+
ParserFactory parserFactory = ParserFactory.instance(context);
7461
try {
75-
Iterable<? extends CompilationUnitTree> parsedCompilationUnits = task.parse();
62+
List<CompilationUnitTree> parsedCompilationUnits = new ArrayList<>();
63+
for (JavaFileObject source : sources) {
64+
log.useSource(source);
65+
JavacParser parser =
66+
parserFactory.newParser(
67+
source.getCharContent(false),
68+
/* keepDocComments= */ true,
69+
/* keepEndPos= */ true,
70+
/* keepLineMap= */ true);
71+
JCCompilationUnit unit = parser.parseCompilationUnit();
72+
unit.sourcefile = source;
73+
parsedCompilationUnits.add(unit);
74+
}
7675
List<Diagnostic<? extends JavaFileObject>> diagnostics = diagnosticCollector.getDiagnostics();
77-
if (foundParseErrors(parsedCompilationUnits, diagnostics)) {
76+
if (foundParseErrors(diagnostics)) {
7877
String msgPrefix = String.format("Error while parsing %s:\n", sourcesDescription);
7978
throw new IllegalStateException(msgPrefix + Joiner.on('\n').join(diagnostics));
8079
}
8180
return new ParseResult(
82-
sortDiagnosticsByKind(diagnostics), parsedCompilationUnits, Trees.instance(task));
81+
sortDiagnosticsByKind(diagnostics), parsedCompilationUnits, JavacTrees.instance(context));
8382
} catch (IOException e) {
8483
throw new RuntimeException(e);
85-
} finally {
86-
DummyJavaCompilerSubclass.closeCompiler(context);
8784
}
8885
}
8986

90-
/**
91-
* Returns {@code true} if errors were found while parsing source files.
92-
*
93-
* <p>Normally, the parser reports error diagnostics, but in some cases there are no diagnostics;
94-
* instead the parse tree contains {@linkplain ErroneousTree "erroneous"} nodes.
95-
*/
96-
private static boolean foundParseErrors(
97-
Iterable<? extends CompilationUnitTree> parsedCompilationUnits,
98-
List<Diagnostic<? extends JavaFileObject>> diagnostics) {
99-
return diagnostics.stream().map(Diagnostic::getKind).anyMatch(isEqual(ERROR))
100-
|| Iterables.any(parsedCompilationUnits, Parser::hasErrorNode);
101-
}
102-
103-
/**
104-
* Returns {@code true} if the tree contains at least one {@linkplain ErroneousTree "erroneous"}
105-
* node.
106-
*/
107-
private static boolean hasErrorNode(Tree tree) {
108-
return isTrue(HAS_ERRONEOUS_NODE.scan(tree, false));
109-
}
110-
111-
private static final TreeScanner<Boolean, Boolean> HAS_ERRONEOUS_NODE =
112-
new TreeScanner<Boolean, Boolean>() {
113-
@Override
114-
public Boolean visitErroneous(ErroneousTree node, Boolean p) {
115-
return true;
116-
}
117-
118-
@Override
119-
public Boolean scan(Iterable<? extends Tree> nodes, Boolean p) {
120-
for (Tree node : firstNonNull(nodes, ImmutableList.<Tree>of())) {
121-
if (isTrue(scan(node, p))) {
122-
return true;
123-
}
124-
}
125-
return p;
126-
}
127-
128-
@Override
129-
public Boolean scan(Tree tree, Boolean p) {
130-
return isTrue(p) ? p : super.scan(tree, p);
131-
}
132-
133-
@Override
134-
public Boolean reduce(Boolean r1, Boolean r2) {
135-
return isTrue(r1) || isTrue(r2);
136-
}
137-
};
138-
139-
private static boolean isTrue(Boolean p) {
140-
return TRUE.equals(p);
87+
/** Returns {@code true} if errors were found while parsing source files. */
88+
private static boolean foundParseErrors(List<Diagnostic<? extends JavaFileObject>> diagnostics) {
89+
return diagnostics.stream().anyMatch(d -> d.getKind().equals(ERROR));
14190
}
14291

14392
private static ImmutableListMultimap<Diagnostic.Kind, Diagnostic<? extends JavaFileObject>>
@@ -184,20 +133,5 @@ Trees trees() {
184133
}
185134
}
186135

187-
// JavaCompiler.compilerKey has protected access until Java 9, so this is a workaround.
188-
private static final class DummyJavaCompilerSubclass extends com.sun.tools.javac.main.JavaCompiler {
189-
private static void closeCompiler(Context context) {
190-
com.sun.tools.javac.main.JavaCompiler compiler = context.get(compilerKey);
191-
if (compiler != null) {
192-
compiler.close();
193-
}
194-
}
195-
196-
private DummyJavaCompilerSubclass() {
197-
// not instantiable
198-
super(null);
199-
}
200-
}
201-
202136
private Parser() {}
203137
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.google.testing.compile;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
import static org.junit.Assert.assertThrows;
5+
6+
import com.google.common.collect.ImmutableList;
7+
import javax.tools.JavaFileObject;
8+
import org.junit.Test;
9+
import org.junit.runner.RunWith;
10+
import org.junit.runners.JUnit4;
11+
12+
@RunWith(JUnit4.class)
13+
public final class ParserTest {
14+
15+
private static final JavaFileObject HELLO_WORLD_BROKEN =
16+
JavaFileObjects.forSourceLines(
17+
"test.HelloWorld", "package test;", "", "public class HelloWorld {", "}}");
18+
19+
@Test
20+
public void failsToParse() {
21+
IllegalStateException expected =
22+
assertThrows(
23+
IllegalStateException.class,
24+
() -> Parser.parse(ImmutableList.of(HELLO_WORLD_BROKEN), "hello world"));
25+
assertThat(expected).hasMessageThat().contains("HelloWorld.java:4: error");
26+
}
27+
}

0 commit comments

Comments
 (0)