mirror of
https://github.com/openjdk/jdk.git
synced 2026-02-06 16:38:36 +00:00
8041648: do while loop that misses ending semicolon has wrong end position
Ensure the end positions are meaningful even if statement's semicolon is missing. Co-authored-by: Dusan Balek <dusan.balek@oracle.com> Reviewed-by: jjg
This commit is contained in:
parent
5b199f3cb8
commit
9d852a54ef
@ -412,9 +412,16 @@ public class JavacParser implements Parser {
|
||||
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 @@ public class JavacParser implements Parser {
|
||||
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 @@ public class JavacParser implements Parser {
|
||||
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 @@ public class JavacParser implements Parser {
|
||||
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 @@ public class JavacParser implements Parser {
|
||||
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 @@ public class JavacParser implements Parser {
|
||||
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 @@ public class JavacParser implements Parser {
|
||||
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 @@ public class JavacParser implements Parser {
|
||||
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;
|
||||
|
||||
262
langtools/test/tools/javac/tree/MissingSemicolonTest.java
Normal file
262
langtools/test/tools/javac/tree/MissingSemicolonTest.java
Normal file
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user