Skip to content
This repository has been archived by the owner on Aug 27, 2022. It is now read-only.

Commit

Permalink
8243047: javac may crash when processing exits in class initializers
Browse files Browse the repository at this point in the history
Flow should handle invalid exits from class initializers gracefully

Reviewed-by: vromero
  • Loading branch information
lahodaj committed Apr 20, 2020
1 parent df82d9a commit 4b6e75d
Show file tree
Hide file tree
Showing 4 changed files with 228 additions and 37 deletions.
88 changes: 51 additions & 37 deletions src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java
Expand Up @@ -525,6 +525,7 @@ public void visitClassDef(JCClassDecl tree) {
if (!l.head.hasTag(METHODDEF) &&
(TreeInfo.flags(l.head) & STATIC) != 0) {
scanDef(l.head);
clearPendingExits(false);
}
}

Expand All @@ -533,6 +534,7 @@ public void visitClassDef(JCClassDecl tree) {
if (!l.head.hasTag(METHODDEF) &&
(TreeInfo.flags(l.head) & STATIC) == 0) {
scanDef(l.head);
clearPendingExits(false);
}
}

Expand Down Expand Up @@ -565,19 +567,23 @@ public void visitMethodDef(JCMethodDecl tree) {
if (alive == Liveness.ALIVE && !tree.sym.type.getReturnType().hasTag(VOID))
log.error(TreeInfo.diagEndPos(tree.body), Errors.MissingRetStmt);

List<PendingExit> exits = pendingExits.toList();
pendingExits = new ListBuffer<>();
while (exits.nonEmpty()) {
PendingExit exit = exits.head;
exits = exits.tail;
Assert.check(exit.tree.hasTag(RETURN) ||
log.hasErrorOn(exit.tree.pos()));
}
clearPendingExits(true);
} finally {
lint = lintPrev;
}
}

private void clearPendingExits(boolean inMethod) {
List<PendingExit> exits = pendingExits.toList();
pendingExits = new ListBuffer<>();
while (exits.nonEmpty()) {
PendingExit exit = exits.head;
exits = exits.tail;
Assert.check((inMethod && exit.tree.hasTag(RETURN)) ||
log.hasErrorOn(exit.tree.pos()));
}
}

public void visitVarDef(JCVariableDecl tree) {
if (tree.init != null) {
Lint lintPrev = lint;
Expand Down Expand Up @@ -932,20 +938,23 @@ void errorUncaught() {
for (PendingExit exit = pendingExits.next();
exit != null;
exit = pendingExits.next()) {
Assert.check(exit instanceof ThrownPendingExit);
ThrownPendingExit thrownExit = (ThrownPendingExit) exit;
if (classDef != null &&
classDef.pos == exit.tree.pos) {
log.error(exit.tree.pos(),
Errors.UnreportedExceptionDefaultConstructor(thrownExit.thrown));
} else if (exit.tree.hasTag(VARDEF) &&
((JCVariableDecl)exit.tree).sym.isResourceVariable()) {
log.error(exit.tree.pos(),
Errors.UnreportedExceptionImplicitClose(thrownExit.thrown,
((JCVariableDecl)exit.tree).sym.name));
if (exit instanceof ThrownPendingExit) {
ThrownPendingExit thrownExit = (ThrownPendingExit) exit;
if (classDef != null &&
classDef.pos == exit.tree.pos) {
log.error(exit.tree.pos(),
Errors.UnreportedExceptionDefaultConstructor(thrownExit.thrown));
} else if (exit.tree.hasTag(VARDEF) &&
((JCVariableDecl)exit.tree).sym.isResourceVariable()) {
log.error(exit.tree.pos(),
Errors.UnreportedExceptionImplicitClose(thrownExit.thrown,
((JCVariableDecl)exit.tree).sym.name));
} else {
log.error(exit.tree.pos(),
Errors.UnreportedExceptionNeedToCatchOrThrow(thrownExit.thrown));
}
} else {
log.error(exit.tree.pos(),
Errors.UnreportedExceptionNeedToCatchOrThrow(thrownExit.thrown));
Assert.check(log.hasErrorOn(exit.tree.pos()));
}
}
}
Expand Down Expand Up @@ -1992,6 +2001,7 @@ public void visitClassDef(JCClassDecl tree) {
if (!l.head.hasTag(METHODDEF) &&
(TreeInfo.flags(l.head) & STATIC) != 0) {
scan(l.head);
clearPendingExits(false);
}
}

Expand All @@ -2013,6 +2023,7 @@ public void visitClassDef(JCClassDecl tree) {
if (!l.head.hasTag(METHODDEF) &&
(TreeInfo.flags(l.head) & STATIC) == 0) {
scan(l.head);
clearPendingExits(false);
}
}

Expand Down Expand Up @@ -2120,22 +2131,7 @@ public void visitMethodDef(JCMethodDecl tree) {
}
}
}
List<PendingExit> exits = pendingExits.toList();
pendingExits = new ListBuffer<>();
while (exits.nonEmpty()) {
PendingExit exit = exits.head;
exits = exits.tail;
Assert.check(exit.tree.hasTag(RETURN) ||
log.hasErrorOn(exit.tree.pos()),
exit.tree);
if (isInitialConstructor) {
Assert.check(exit instanceof AssignPendingExit);
inits.assign(((AssignPendingExit) exit).exit_inits);
for (int i = firstadr; i < nextadr; i++) {
checkInit(exit.tree.pos(), vardecls[i].sym);
}
}
}
clearPendingExits(true);
} finally {
inits.assign(initsPrev);
uninits.assign(uninitsPrev);
Expand All @@ -2149,6 +2145,24 @@ public void visitMethodDef(JCMethodDecl tree) {
}
}

private void clearPendingExits(boolean inMethod) {
List<PendingExit> exits = pendingExits.toList();
pendingExits = new ListBuffer<>();
while (exits.nonEmpty()) {
PendingExit exit = exits.head;
exits = exits.tail;
Assert.check((inMethod && exit.tree.hasTag(RETURN)) ||
log.hasErrorOn(exit.tree.pos()),
exit.tree);
if (inMethod && isInitialConstructor) {
Assert.check(exit instanceof AssignPendingExit);
inits.assign(((AssignPendingExit) exit).exit_inits);
for (int i = firstadr; i < nextadr; i++) {
checkInit(exit.tree.pos(), vardecls[i].sym);
}
}
}
}
protected void initParam(JCVariableDecl def) {
inits.incl(def.sym.adr);
uninits.excl(def.sym.adr);
Expand Down
141 changes: 141 additions & 0 deletions test/langtools/tools/javac/recovery/ClassBlockExits.java
@@ -0,0 +1,141 @@
/*
* Copyright (c) 2020, 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 8243047
* @summary javac should not crash while processing exits in class initializers in Flow
* @library /tools/lib /tools/javac/lib
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.file
* jdk.compiler/com.sun.tools.javac.main
* jdk.compiler/com.sun.tools.javac.util
* @build toolbox.ToolBox toolbox.JavacTask
* @build combo.ComboTestHelper
* @compile --enable-preview -source ${jdk.version} ClassBlockExits.java
* @run main/othervm --enable-preview ClassBlockExits
*/

import combo.ComboInstance;
import combo.ComboParameter;
import combo.ComboTask;
import combo.ComboTestHelper;
import java.io.StringWriter;
import toolbox.ToolBox;

public class ClassBlockExits extends ComboInstance<ClassBlockExits> {
protected ToolBox tb;

ClassBlockExits() {
super();
tb = new ToolBox();
}

public static void main(String... args) throws Exception {
new ComboTestHelper<ClassBlockExits>()
.withDimension("BLOCK", (x, block) -> x.block = block, Block.values())
.withDimension("EXIT", (x, exit) -> x.exit = exit, Exit.values())
.run(ClassBlockExits::new);
}

private Block block;
private Exit exit;

private static final String MAIN_TEMPLATE =
"""
public class Test {
#{BLOCK}
void t() {}
}
""";

@Override
protected void doWork() throws Throwable {
StringWriter out = new StringWriter();

ComboTask task = newCompilationTask()
.withSourceFromTemplate(MAIN_TEMPLATE, pname -> switch (pname) {
case "BLOCK" -> block;
case "BODY" -> exit;
default -> throw new UnsupportedOperationException(pname);
})
.withOption("-XDshould-stop.at=FLOW")
.withOption("-XDdev")
.withWriter(out);

task.analyze(result -> {
if (out.toString().length() > 0) {
throw new AssertionError("No output expected, but got" + out + "\n\n" + result.compilationInfo());
}
});
}

public enum Block implements ComboParameter {
STATIC("""
static {
#{BODY}
}
"""),
INSTANCE("""
{
#{BODY}
}
"""),
STATIC_INITIALIZER("""
private static int i = switch (0) { default: #{BODY} case 0: yield 0; };
"""),
INITIALIZER("""
private int i = switch (0) { default: #{BODY} case 0: yield 0; };
""");
private final String block;

private Block(String block) {
this.block = block;
}

@Override
public String expand(String optParameter) {
return block;
}
}

public enum Exit implements ComboParameter {
RETURN("return;"),
RETURN_VALUE("return null;"),
BREAK("break;"),
BREAK_LABEL("break LABEL;"),
CONTINUE("continue;"),
CONTINUE_LABEL("continue LABEL;"),
YIELD("yield 0;");
private final String code;

private Exit(String code) {
this.code = code;
}

@Override
public String expand(String optParameter) {
return code;
}
}
}
29 changes: 29 additions & 0 deletions test/langtools/tools/javac/recovery/ClassBlockExitsErrors.java
@@ -0,0 +1,29 @@
/*
* @test /nodynamiccopyright/
* @bug 8243047
* @summary javac should not crash while processing exits in class initializers in Flow
* @compile/fail/ref=ClassBlockExitsErrors.out -XDrawDiagnostics -XDshould-stop.at=FLOW ClassBlockExitsErrors.java
*/

public class ClassBlockExitsErrors {
class I1 {
{
return;
}
void test() {}
}

class I2 {
{
break;
}
void test() {}
}

class I3 {
{
continue;
}
void test() {}
}
}
7 changes: 7 additions & 0 deletions test/langtools/tools/javac/recovery/ClassBlockExitsErrors.out
@@ -0,0 +1,7 @@
ClassBlockExitsErrors.java:11:13: compiler.err.ret.outside.meth
ClassBlockExitsErrors.java:18:13: compiler.err.break.outside.switch.loop
ClassBlockExitsErrors.java:25:13: compiler.err.cont.outside.loop
ClassBlockExitsErrors.java:10:9: compiler.err.initializer.must.be.able.to.complete.normally
ClassBlockExitsErrors.java:17:9: compiler.err.initializer.must.be.able.to.complete.normally
ClassBlockExitsErrors.java:24:9: compiler.err.initializer.must.be.able.to.complete.normally
6 errors

0 comments on commit 4b6e75d

Please sign in to comment.