8041648: do while loop that misses ending semicolon has wrong end position
authorjlahoda
Thu, 19 Jun 2014 22:06:29 +0200
changeset 25274 a06a738fc6be
parent 25273 87443756f3ac
child 25275 e5e9bf4b8c49
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
langtools/src/share/classes/com/sun/tools/javac/parser/JavacParser.java
langtools/test/tools/javac/tree/MissingSemicolonTest.java
--- 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();
+    }
+
+}