8041648: do while loop that misses ending semicolon has wrong end position
Summary: Ensure the end positions are meaningful even if statement's semicolon is missing.
Reviewed-by: jjg
Contributed-by: dusan.balek@oracle.com, jan.lahoda@oracle.com
--- a/langtools/src/share/classes/com/sun/tools/javac/parser/JavacParser.java Thu Jun 19 15:39:37 2014 +0100
+++ b/langtools/src/share/classes/com/sun/tools/javac/parser/JavacParser.java Thu Jun 19 22:06:29 2014 +0200
@@ -412,9 +412,16 @@
case ELSE:
case FINALLY:
case CATCH:
+ case THIS:
+ case SUPER:
+ case NEW:
if (stopAtStatement)
return;
break;
+ case ASSERT:
+ if (stopAtStatement && allowAsserts)
+ return ;
+ break;
}
nextToken();
}
@@ -2374,8 +2381,8 @@
ListBuffer<JCStatement> stats =
variableDeclarators(mods, t, new ListBuffer<JCStatement>());
// A "LocalVariableDeclarationStatement" subsumes the terminating semicolon
- storeEnd(stats.last(), token.endPos);
accept(SEMI);
+ storeEnd(stats.last(), S.prevToken().endPos);
return stats.toList();
}
}
@@ -2412,13 +2419,14 @@
ListBuffer<JCStatement> stats =
variableDeclarators(mods, t, new ListBuffer<JCStatement>());
// A "LocalVariableDeclarationStatement" subsumes the terminating semicolon
- storeEnd(stats.last(), token.endPos);
accept(SEMI);
+ storeEnd(stats.last(), S.prevToken().endPos);
return stats.toList();
} else {
// This Exec is an "ExpressionStatement"; it subsumes the terminating semicolon
- JCExpressionStatement expr = to(F.at(pos).Exec(checkExprStat(t)));
+ t = checkExprStat(t);
accept(SEMI);
+ JCExpressionStatement expr = toP(F.at(pos).Exec(t));
return List.<JCStatement>of(expr);
}
}
@@ -2497,8 +2505,8 @@
JCStatement body = parseStatementAsBlock();
accept(WHILE);
JCExpression cond = parExpression();
- JCDoWhileLoop t = to(F.at(pos).DoLoop(body, cond));
accept(SEMI);
+ JCDoWhileLoop t = toP(F.at(pos).DoLoop(body, cond));
return t;
}
case TRY: {
@@ -2546,29 +2554,29 @@
case RETURN: {
nextToken();
JCExpression result = token.kind == SEMI ? null : parseExpression();
- JCReturn t = to(F.at(pos).Return(result));
accept(SEMI);
+ JCReturn t = toP(F.at(pos).Return(result));
return t;
}
case THROW: {
nextToken();
JCExpression exc = parseExpression();
- JCThrow t = to(F.at(pos).Throw(exc));
accept(SEMI);
+ JCThrow t = toP(F.at(pos).Throw(exc));
return t;
}
case BREAK: {
nextToken();
Name label = LAX_IDENTIFIER.accepts(token.kind) ? ident() : null;
- JCBreak t = to(F.at(pos).Break(label));
accept(SEMI);
+ JCBreak t = toP(F.at(pos).Break(label));
return t;
}
case CONTINUE: {
nextToken();
Name label = LAX_IDENTIFIER.accepts(token.kind) ? ident() : null;
- JCContinue t = to(F.at(pos).Continue(label));
accept(SEMI);
+ JCContinue t = toP(F.at(pos).Continue(label));
return t;
}
case SEMI:
@@ -2593,8 +2601,8 @@
nextToken();
message = parseExpression();
}
- JCAssert t = to(F.at(pos).Assert(assertion, message));
accept(SEMI);
+ JCAssert t = toP(F.at(pos).Assert(assertion, message));
return t;
}
/* else fall through to default case */
@@ -2609,8 +2617,9 @@
return F.at(pos).Labelled(prevToken.name(), stat);
} else {
// This Exec is an "ExpressionStatement"; it subsumes the terminating semicolon
- JCExpressionStatement stat = to(F.at(pos).Exec(checkExprStat(expr)));
+ expr = checkExprStat(expr);
accept(SEMI);
+ JCExpressionStatement stat = toP(F.at(pos).Exec(expr));
return stat;
}
}
@@ -3513,8 +3522,8 @@
List<JCTree> defs =
variableDeclaratorsRest(pos, mods, type, name, isInterface, dc,
new ListBuffer<JCTree>()).toList();
- storeEnd(defs.last(), token.endPos);
accept(SEMI);
+ storeEnd(defs.last(), S.prevToken().endPos);
return defs;
} else {
pos = token.pos;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/tree/MissingSemicolonTest.java Thu Jun 19 22:06:29 2014 +0200
@@ -0,0 +1,262 @@
+/*
+ * Copyright (c) 2010, 2014, 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.
+ * @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) {
+ 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) {
+ 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;
+ }
+
+ 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();
+ }
+
+}