/* * Copyright (c) 2010, 2015, 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 8041648 * @summary Verify that end positions are sane if semicolons are missing. * @modules jdk.compiler/com.sun.tools.javac.api * jdk.compiler/com.sun.tools.javac.file * jdk.compiler/com.sun.tools.javac.parser * jdk.compiler/com.sun.tools.javac.tree * jdk.compiler/com.sun.tools.javac.util * @run main MissingSemicolonTest MissingSemicolonTest.java */ import java.io.File; import java.io.IOException; import java.net.URI; import java.nio.file.Files; import java.util.*; import javax.tools.*; import com.sun.source.tree.*; import com.sun.source.tree.Tree.Kind; import com.sun.source.util.*; import com.sun.tools.javac.api.JavacTool; import com.sun.tools.javac.parser.Scanner; import com.sun.tools.javac.parser.ScannerFactory; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.util.Context; public class MissingSemicolonTest { public static void main(String... args) throws IOException { String testSrc = System.getProperty("test.src"); File baseDir = new File(testSrc); boolean ok = new MissingSemicolonTest().run(baseDir, args); if (!ok) { throw new Error("failed"); } } boolean run(File baseDir, String... args) throws IOException { try { if (args.length == 0) { throw new IllegalStateException("Needs input files."); } for (String arg : args) { File file = new File(baseDir, arg); if (file.exists()) test(file); else error("File not found: " + file); } System.err.println(fileCount + " files read"); if (errors > 0) System.err.println(errors + " errors"); return errors == 0; } finally { fm.close(); } } void test(File file) { if (file.isFile() && file.getName().endsWith(".java")) { try { fileCount++; String content = new String(Files.readAllBytes(file.toPath())); List<int[]> spans = gatherTreeSpans(file, content); int nextSemicolon = -1; //remove semicolons, one at a time, and verify the positions are still meaningful: while ((nextSemicolon = content.indexOf(';', nextSemicolon + 1)) != (-1)) { String updatedContent = content.substring(0, nextSemicolon) + " " + content.substring(nextSemicolon + 1); verifyTreeSpans(file, spans, updatedContent, nextSemicolon); } } catch (IOException e) { error("Error reading " + file + ": " + e); } } } public List<int[]> gatherTreeSpans(File file, String content) throws IOException { JCCompilationUnit unit = read(file.toURI(), content); List<int[]> spans = new ArrayList<>(); new TreePathScanner<Void, Void>() { @Override public Void scan(Tree tree, Void p) { if (tree != null) { int start = ((JCTree) tree).getStartPosition(); int end = ((JCTree) tree).getEndPosition(unit.endPositions); spans.add(new int[] {start, end}); } return super.scan(tree, p); } }.scan(unit, null); return spans; } public void verifyTreeSpans(File file, List<int[]> spans, String updatedContent, int semicolon) throws IOException { JCCompilationUnit updated = read(file.toURI(), updatedContent); Iterator<int[]> nextSpan = spans.iterator(); new TreePathScanner<Void, Void>() { @Override public Void scan(Tree tree, Void p) { if (tree != null) { int start = ((JCTree) tree).getStartPosition(); int end = ((JCTree) tree).getEndPosition(updated.endPositions); if (tree.getKind() != Kind.ERRONEOUS) { int[] expected = nextSpan.next(); int expectedEnd = expected[1]; if (expectedEnd == semicolon + 1) { Scanner scanner = scannerFactory.newScanner(updatedContent, true); scanner.nextToken(); while (scanner.token().pos < expectedEnd) scanner.nextToken(); expectedEnd = scanner.token().pos; } if (expected[0] != start || expectedEnd != end) { error(updatedContent + "; semicolon: " + semicolon + "; expected: " + expected[0] + "-" + expectedEnd + "; found=" + start + "-" + end + ";" + tree); } } } return super.scan(tree, p); } }.scan(updated, null); } DiagnosticListener<JavaFileObject> devNull = (d) -> {}; JavacTool tool = JavacTool.create(); StandardJavaFileManager fm = tool.getStandardFileManager(devNull, null, null); ScannerFactory scannerFactory = ScannerFactory.instance(new Context()); /** * Read a file. * @param file the file to be read * @return the tree for the content of the file * @throws IOException if any IO errors occur * @throws MissingSemicolonTest.ParseException if any errors occur while parsing the file */ JCCompilationUnit read(URI uri, String content) throws IOException { JavacTool tool = JavacTool.create(); JavacTask task = tool.getTask(null, fm, devNull, Collections.<String>emptyList(), null, Arrays.<JavaFileObject>asList(new JavaSource(uri, content))); Iterable<? extends CompilationUnitTree> trees = task.parse(); Iterator<? extends CompilationUnitTree> iter = trees.iterator(); if (!iter.hasNext()) throw new Error("no trees found"); JCCompilationUnit t = (JCCompilationUnit) iter.next(); if (iter.hasNext()) throw new Error("too many trees found"); return t; } class JavaSource extends SimpleJavaFileObject { private final String content; public JavaSource(URI uri, String content) { super(uri, JavaFileObject.Kind.SOURCE); this.content = content; } @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) { return content; } } /** * Report an error. When the program is complete, the program will either * exit or throw an Error if any errors have been reported. * @param msg the error message */ void error(String msg) { System.err.println(msg); errors++; } /** Number of files that have been analyzed. */ int fileCount; /** Number of errors reported. */ int errors; } class TestCase { String str1; String str2; public TestCase() { super(); super.hashCode(); } public TestCase(String str1, String str2) { super(); this.str1 = str1; this.str2 = str2; assert true; } void newClass() { new String(); new String(); } void localVars() { String str1 = ""; String str2; String str3; final String str4; } void throwsException() { throw new IllegalStateException(); } int returnWithExpression() { return 1; } void returnWithoutExpression() { return ; } void doWhileBreakContinue() { do { if (true) break; if (false) continue; } while(true); } void labelled() { LABEL: doWhileBreakContinue(); } }