8194978: Javac produces dead code for try-with-resource
For try-with-resources, using simplified distinct close code for try body and catch clause, to avoid creating multiple copies of the full finally code. Reviewed-by: mcimadamore
This commit is contained in:
parent
0db5f435ad
commit
01509e5b5e
src/jdk.compiler/share/classes/com/sun/tools/javac
test/langtools/tools/javac
TryWithResources
annotations/typeAnnotations/referenceinfos
flow/tests
@ -313,6 +313,12 @@ public class Flags {
|
||||
*/
|
||||
public static final long ANONCONSTR_BASED = 1L<<57;
|
||||
|
||||
/**
|
||||
* Flag that marks finalize block as body-only, should not be copied into catch clauses.
|
||||
* Used to implement try-with-resources.
|
||||
*/
|
||||
public static final long BODY_ONLY_FINALIZE = 1L<<17; //blocks only
|
||||
|
||||
/** Modifier masks.
|
||||
*/
|
||||
public static final int
|
||||
|
@ -1519,26 +1519,22 @@ public class Lower extends TreeTranslator {
|
||||
*
|
||||
* {
|
||||
* final VariableModifiers_minus_final R #resource = Expression;
|
||||
* Throwable #primaryException = null;
|
||||
*
|
||||
* try ResourceSpecificationtail
|
||||
* Block
|
||||
* catch (Throwable #t) {
|
||||
* #primaryException = t;
|
||||
* throw #t;
|
||||
* } finally {
|
||||
* if (#resource != null) {
|
||||
* if (#primaryException != null) {
|
||||
* try {
|
||||
* #resource.close();
|
||||
* } catch(Throwable #suppressedException) {
|
||||
* #primaryException.addSuppressed(#suppressedException);
|
||||
* }
|
||||
* } else {
|
||||
* } body-only-finally {
|
||||
* if (#resource != null) //nullcheck skipped if Expression is provably non-null
|
||||
* #resource.close();
|
||||
* }
|
||||
* }
|
||||
* } catch (Throwable #primaryException) {
|
||||
* if (#resource != null) //nullcheck skipped if Expression is provably non-null
|
||||
* try {
|
||||
* #resource.close();
|
||||
* } catch (Throwable #suppressedException) {
|
||||
* #primaryException.addSuppressed(#suppressedException);
|
||||
* }
|
||||
* throw #primaryException;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @param tree The try statement to inspect.
|
||||
* @return A a desugared try-with-resources tree, or the original
|
||||
@ -1547,8 +1543,7 @@ public class Lower extends TreeTranslator {
|
||||
JCTree makeTwrTry(JCTry tree) {
|
||||
make_at(tree.pos());
|
||||
twrVars = twrVars.dup();
|
||||
JCBlock twrBlock = makeTwrBlock(tree.resources, tree.body,
|
||||
tree.finallyCanCompleteNormally, 0);
|
||||
JCBlock twrBlock = makeTwrBlock(tree.resources, tree.body, 0);
|
||||
if (tree.catchers.isEmpty() && tree.finalizer == null)
|
||||
result = translate(twrBlock);
|
||||
else
|
||||
@ -1557,19 +1552,18 @@ public class Lower extends TreeTranslator {
|
||||
return result;
|
||||
}
|
||||
|
||||
private JCBlock makeTwrBlock(List<JCTree> resources, JCBlock block,
|
||||
boolean finallyCanCompleteNormally, int depth) {
|
||||
private JCBlock makeTwrBlock(List<JCTree> resources, JCBlock block, int depth) {
|
||||
if (resources.isEmpty())
|
||||
return block;
|
||||
|
||||
// Add resource declaration or expression to block statements
|
||||
ListBuffer<JCStatement> stats = new ListBuffer<>();
|
||||
JCTree resource = resources.head;
|
||||
JCExpression expr = null;
|
||||
JCExpression resourceUse;
|
||||
boolean resourceNonNull;
|
||||
if (resource instanceof JCVariableDecl) {
|
||||
JCVariableDecl var = (JCVariableDecl) resource;
|
||||
expr = make.Ident(var.sym).setType(resource.type);
|
||||
resourceUse = make.Ident(var.sym).setType(resource.type);
|
||||
resourceNonNull = var.init != null && TreeInfo.skipParens(var.init).hasTag(NEWCLASS);
|
||||
stats.add(var);
|
||||
} else {
|
||||
@ -1584,164 +1578,82 @@ public class Lower extends TreeTranslator {
|
||||
twrVars.enter(syntheticTwrVar);
|
||||
JCVariableDecl syntheticTwrVarDecl =
|
||||
make.VarDef(syntheticTwrVar, (JCExpression)resource);
|
||||
expr = (JCExpression)make.Ident(syntheticTwrVar);
|
||||
resourceNonNull = TreeInfo.skipParens(resource).hasTag(NEWCLASS);
|
||||
resourceUse = (JCExpression)make.Ident(syntheticTwrVar);
|
||||
resourceNonNull = false;
|
||||
stats.add(syntheticTwrVarDecl);
|
||||
}
|
||||
|
||||
// Add primaryException declaration
|
||||
VarSymbol primaryException =
|
||||
new VarSymbol(SYNTHETIC,
|
||||
makeSyntheticName(names.fromString("primaryException" +
|
||||
depth), twrVars),
|
||||
syms.throwableType,
|
||||
currentMethodSym);
|
||||
twrVars.enter(primaryException);
|
||||
JCVariableDecl primaryExceptionTreeDecl = make.VarDef(primaryException, makeNull());
|
||||
stats.add(primaryExceptionTreeDecl);
|
||||
//create (semi-) finally block that will be copied into the main try body:
|
||||
int oldPos = make.pos;
|
||||
make.at(TreeInfo.endPos(block));
|
||||
|
||||
// Create catch clause that saves exception and then rethrows it
|
||||
VarSymbol param =
|
||||
// if (#resource != null) { #resource.close(); }
|
||||
JCStatement bodyCloseStatement = makeResourceCloseInvocation(resourceUse);
|
||||
|
||||
if (!resourceNonNull) {
|
||||
bodyCloseStatement = make.If(makeNonNullCheck(resourceUse),
|
||||
bodyCloseStatement,
|
||||
null);
|
||||
}
|
||||
|
||||
JCBlock finallyClause = make.Block(BODY_ONLY_FINALIZE, List.of(bodyCloseStatement));
|
||||
make.at(oldPos);
|
||||
|
||||
// Create catch clause that saves exception, closes the resource and then rethrows the exception:
|
||||
VarSymbol primaryException =
|
||||
new VarSymbol(FINAL|SYNTHETIC,
|
||||
names.fromString("t" +
|
||||
target.syntheticNameChar()),
|
||||
syms.throwableType,
|
||||
currentMethodSym);
|
||||
JCVariableDecl paramTree = make.VarDef(param, null);
|
||||
JCStatement assign = make.Assignment(primaryException, make.Ident(param));
|
||||
JCStatement rethrowStat = make.Throw(make.Ident(param));
|
||||
JCBlock catchBlock = make.Block(0L, List.of(assign, rethrowStat));
|
||||
JCCatch catchClause = make.Catch(paramTree, catchBlock);
|
||||
JCVariableDecl primaryExceptionDecl = make.VarDef(primaryException, null);
|
||||
|
||||
int oldPos = make.pos;
|
||||
make.at(TreeInfo.endPos(block));
|
||||
JCBlock finallyClause = makeTwrFinallyClause(primaryException, expr, resourceNonNull);
|
||||
make.at(oldPos);
|
||||
JCTry outerTry = make.Try(makeTwrBlock(resources.tail, block,
|
||||
finallyCanCompleteNormally, depth + 1),
|
||||
List.of(catchClause),
|
||||
finallyClause);
|
||||
outerTry.finallyCanCompleteNormally = finallyCanCompleteNormally;
|
||||
stats.add(outerTry);
|
||||
JCBlock newBlock = make.Block(0L, stats.toList());
|
||||
return newBlock;
|
||||
}
|
||||
|
||||
/**If the estimated number of copies the close resource code in a single class is above this
|
||||
* threshold, generate and use a method for the close resource code, leading to smaller code.
|
||||
* As generating a method has overhead on its own, generating the method for cases below the
|
||||
* threshold could lead to an increase in code size.
|
||||
*/
|
||||
public static final int USE_CLOSE_RESOURCE_METHOD_THRESHOLD = 4;
|
||||
|
||||
private JCBlock makeTwrFinallyClause(Symbol primaryException, JCExpression resource,
|
||||
boolean resourceNonNull) {
|
||||
MethodSymbol closeResource = (MethodSymbol)lookupSynthetic(dollarCloseResource,
|
||||
currentClass.members());
|
||||
|
||||
if (closeResource == null && shouldUseCloseResourceMethod()) {
|
||||
closeResource = new MethodSymbol(
|
||||
PRIVATE | STATIC | SYNTHETIC,
|
||||
dollarCloseResource,
|
||||
new MethodType(
|
||||
List.of(syms.throwableType, syms.autoCloseableType),
|
||||
syms.voidType,
|
||||
List.nil(),
|
||||
syms.methodClass),
|
||||
currentClass);
|
||||
enterSynthetic(resource.pos(), closeResource, currentClass.members());
|
||||
|
||||
JCMethodDecl md = make.MethodDef(closeResource, null);
|
||||
List<JCVariableDecl> params = md.getParameters();
|
||||
md.body = make.Block(0, List.of(makeTwrCloseStatement(params.get(0).sym,
|
||||
make.Ident(params.get(1)))));
|
||||
|
||||
JCClassDecl currentClassDecl = classDef(currentClass);
|
||||
currentClassDecl.defs = currentClassDecl.defs.prepend(md);
|
||||
}
|
||||
|
||||
JCStatement closeStatement;
|
||||
|
||||
if (closeResource != null) {
|
||||
//$closeResource(#primaryException, #resource)
|
||||
closeStatement = make.Exec(make.Apply(List.nil(),
|
||||
make.Ident(closeResource),
|
||||
List.of(make.Ident(primaryException),
|
||||
resource)
|
||||
).setType(syms.voidType));
|
||||
} else {
|
||||
closeStatement = makeTwrCloseStatement(primaryException, resource);
|
||||
}
|
||||
|
||||
JCStatement finallyStatement;
|
||||
|
||||
if (resourceNonNull) {
|
||||
finallyStatement = closeStatement;
|
||||
} else {
|
||||
// if (#resource != null) { $closeResource(...); }
|
||||
finallyStatement = make.If(makeNonNullCheck(resource),
|
||||
closeStatement,
|
||||
null);
|
||||
}
|
||||
|
||||
return make.Block(0L,
|
||||
List.of(finallyStatement));
|
||||
}
|
||||
//where:
|
||||
private boolean shouldUseCloseResourceMethod() {
|
||||
class TryFinder extends TreeScanner {
|
||||
int closeCount;
|
||||
@Override
|
||||
public void visitTry(JCTry tree) {
|
||||
boolean empty = tree.body.stats.isEmpty();
|
||||
|
||||
for (JCTree r : tree.resources) {
|
||||
closeCount += empty ? 1 : 2;
|
||||
empty = false; //with multiple resources, only the innermost try can be empty.
|
||||
}
|
||||
super.visitTry(tree);
|
||||
}
|
||||
@Override
|
||||
public void scan(JCTree tree) {
|
||||
if (useCloseResourceMethod())
|
||||
return;
|
||||
super.scan(tree);
|
||||
}
|
||||
boolean useCloseResourceMethod() {
|
||||
return closeCount >= USE_CLOSE_RESOURCE_METHOD_THRESHOLD;
|
||||
}
|
||||
}
|
||||
TryFinder tryFinder = new TryFinder();
|
||||
tryFinder.scan(classDef(currentClass));
|
||||
return tryFinder.useCloseResourceMethod();
|
||||
}
|
||||
|
||||
private JCStatement makeTwrCloseStatement(Symbol primaryException, JCExpression resource) {
|
||||
// primaryException.addSuppressed(catchException);
|
||||
VarSymbol catchException =
|
||||
// close resource:
|
||||
// try {
|
||||
// #resource.close();
|
||||
// } catch (Throwable #suppressedException) {
|
||||
// #primaryException.addSuppressed(#suppressedException);
|
||||
// }
|
||||
VarSymbol suppressedException =
|
||||
new VarSymbol(SYNTHETIC, make.paramName(2),
|
||||
syms.throwableType,
|
||||
currentMethodSym);
|
||||
JCStatement addSuppressionStatement =
|
||||
JCStatement addSuppressedStatement =
|
||||
make.Exec(makeCall(make.Ident(primaryException),
|
||||
names.addSuppressed,
|
||||
List.of(make.Ident(catchException))));
|
||||
List.of(make.Ident(suppressedException))));
|
||||
JCBlock closeResourceTryBlock =
|
||||
make.Block(0L, List.of(makeResourceCloseInvocation(resourceUse)));
|
||||
JCVariableDecl catchSuppressedDecl = make.VarDef(suppressedException, null);
|
||||
JCBlock catchSuppressedBlock = make.Block(0L, List.of(addSuppressedStatement));
|
||||
List<JCCatch> catchSuppressedClauses =
|
||||
List.of(make.Catch(catchSuppressedDecl, catchSuppressedBlock));
|
||||
JCTry closeResourceTry = make.Try(closeResourceTryBlock, catchSuppressedClauses, null);
|
||||
closeResourceTry.finallyCanCompleteNormally = true;
|
||||
|
||||
// try { resource.close(); } catch (e) { primaryException.addSuppressed(e); }
|
||||
JCBlock tryBlock =
|
||||
make.Block(0L, List.of(makeResourceCloseInvocation(resource)));
|
||||
JCVariableDecl catchExceptionDecl = make.VarDef(catchException, null);
|
||||
JCBlock catchBlock = make.Block(0L, List.of(addSuppressionStatement));
|
||||
List<JCCatch> catchClauses = List.of(make.Catch(catchExceptionDecl, catchBlock));
|
||||
JCTry tryTree = make.Try(tryBlock, catchClauses, null);
|
||||
tryTree.finallyCanCompleteNormally = true;
|
||||
JCStatement exceptionalCloseStatement = closeResourceTry;
|
||||
|
||||
// if (primaryException != null) {try...} else resourceClose;
|
||||
JCIf closeIfStatement = make.If(makeNonNullCheck(make.Ident(primaryException)),
|
||||
tryTree,
|
||||
makeResourceCloseInvocation(resource));
|
||||
if (!resourceNonNull) {
|
||||
// if (#resource != null) { }
|
||||
exceptionalCloseStatement = make.If(makeNonNullCheck(resourceUse),
|
||||
exceptionalCloseStatement,
|
||||
null);
|
||||
}
|
||||
|
||||
return closeIfStatement;
|
||||
JCStatement exceptionalRethrow = make.Throw(make.Ident(primaryException));
|
||||
JCBlock exceptionalCloseBlock = make.Block(0L, List.of(exceptionalCloseStatement, exceptionalRethrow));
|
||||
JCCatch exceptionalCatchClause = make.Catch(primaryExceptionDecl, exceptionalCloseBlock);
|
||||
|
||||
//create the main try statement with the close:
|
||||
JCTry outerTry = make.Try(makeTwrBlock(resources.tail, block, depth + 1),
|
||||
List.of(exceptionalCatchClause),
|
||||
finallyClause);
|
||||
|
||||
outerTry.finallyCanCompleteNormally = true;
|
||||
stats.add(outerTry);
|
||||
|
||||
JCBlock newBlock = make.Block(0L, stats.toList());
|
||||
return newBlock;
|
||||
}
|
||||
|
||||
private JCStatement makeResourceCloseInvocation(JCExpression resource) {
|
||||
|
@ -1342,6 +1342,16 @@ public class Gen extends JCTree.Visitor {
|
||||
boolean hasFinalizer() {
|
||||
return tree.finalizer != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
void afterBody() {
|
||||
if (tree.finalizer != null && (tree.finalizer.flags & BODY_ONLY_FINALIZE) != 0) {
|
||||
//for body-only finally, remove the GenFinalizer after try body
|
||||
//so that the finally is not generated to catch bodies:
|
||||
tryEnv.info.finalize = null;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
tryEnv.info.gaps = new ListBuffer<>();
|
||||
genTry(tree.body, tree.catchers, tryEnv);
|
||||
@ -1358,15 +1368,16 @@ public class Gen extends JCTree.Visitor {
|
||||
Code.State stateTry = code.state.dup();
|
||||
genStat(body, env, CRT_BLOCK);
|
||||
int endpc = code.curCP();
|
||||
boolean hasFinalizer =
|
||||
env.info.finalize != null &&
|
||||
env.info.finalize.hasFinalizer();
|
||||
List<Integer> gaps = env.info.gaps.toList();
|
||||
code.statBegin(TreeInfo.endPos(body));
|
||||
genFinalizer(env);
|
||||
code.statBegin(TreeInfo.endPos(env.tree));
|
||||
Chain exitChain = code.branch(goto_);
|
||||
endFinalizerGap(env);
|
||||
env.info.finalize.afterBody();
|
||||
boolean hasFinalizer =
|
||||
env.info.finalize != null &&
|
||||
env.info.finalize.hasFinalizer();
|
||||
if (startpc != endpc) for (List<JCCatch> l = catchers; l.nonEmpty(); l = l.tail) {
|
||||
// start off with exception on stack
|
||||
code.entryPoint(stateTry, l.head.param.sym.type);
|
||||
@ -2219,6 +2230,9 @@ public class Gen extends JCTree.Visitor {
|
||||
|
||||
/** Does this finalizer have some nontrivial cleanup to perform? */
|
||||
boolean hasFinalizer() { return true; }
|
||||
|
||||
/** Should be invoked after the try's body has been visited. */
|
||||
void afterBody() {}
|
||||
}
|
||||
|
||||
/** code generation contexts,
|
||||
|
@ -27,7 +27,6 @@
|
||||
* @summary Verify that the close resource code works properly in all cases
|
||||
* @library /tools/lib
|
||||
* @modules jdk.compiler/com.sun.tools.javac.api
|
||||
* jdk.compiler/com.sun.tools.javac.comp
|
||||
* jdk.compiler/com.sun.tools.javac.main
|
||||
* @build toolbox.ToolBox TwrClose
|
||||
* @run main TwrClose
|
||||
@ -37,15 +36,14 @@ import javax.tools.JavaFileManager;
|
||||
import javax.tools.StandardLocation;
|
||||
import javax.tools.ToolProvider;
|
||||
|
||||
import com.sun.tools.javac.comp.Lower;
|
||||
|
||||
import toolbox.JavacTask;
|
||||
import toolbox.ToolBox;
|
||||
|
||||
public class TwrClose {
|
||||
|
||||
private static final int MAX_RESOURCES = 5;
|
||||
public static void main(String... args) throws Exception {
|
||||
for (int i = 1; i < Lower.USE_CLOSE_RESOURCE_METHOD_THRESHOLD * 2; i++) {
|
||||
for (int i = 1; i < MAX_RESOURCES * 2; i++) {
|
||||
new TwrClose().compile(i);
|
||||
}
|
||||
}
|
||||
|
@ -1,158 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, 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 7020499
|
||||
* @summary Verify that the code that closes the resources is shared by among try-with-resources
|
||||
* @library /tools/lib
|
||||
* @modules jdk.compiler/com.sun.tools.javac.api
|
||||
* jdk.compiler/com.sun.tools.javac.code
|
||||
* jdk.compiler/com.sun.tools.javac.comp
|
||||
* jdk.compiler/com.sun.tools.javac.tree
|
||||
* jdk.compiler/com.sun.tools.javac.util
|
||||
* @build toolbox.ToolBox TwrShareCloseCode
|
||||
* @run main TwrShareCloseCode
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.sun.source.util.JavacTask;
|
||||
import com.sun.tools.javac.api.JavacTool;
|
||||
import com.sun.tools.javac.comp.AttrContext;
|
||||
import com.sun.tools.javac.comp.Env;
|
||||
import com.sun.tools.javac.comp.Lower;
|
||||
import com.sun.tools.javac.tree.JCTree;
|
||||
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
|
||||
import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
|
||||
import com.sun.tools.javac.tree.TreeInfo;
|
||||
import com.sun.tools.javac.tree.TreeMaker;
|
||||
import com.sun.tools.javac.tree.TreeScanner;
|
||||
import com.sun.tools.javac.util.Context;
|
||||
import com.sun.tools.javac.util.Context.Factory;
|
||||
import com.sun.tools.javac.util.List;
|
||||
|
||||
import toolbox.ToolBox;
|
||||
|
||||
public class TwrShareCloseCode {
|
||||
public static void main(String... args) throws IOException {
|
||||
new TwrShareCloseCode().run();
|
||||
}
|
||||
|
||||
void run() throws IOException {
|
||||
run("try (Test t1 = new Test()) { }", true);
|
||||
run("try (Test t1 = new Test()) { }\n" +
|
||||
"try (Test t2 = new Test()) { }", true);
|
||||
run("try (Test t1 = new Test();\n" +
|
||||
" Test t2 = new Test()) { }", true);
|
||||
run("try (Test t1 = new Test()) { }\n" +
|
||||
"try (Test t2 = new Test()) { }\n" +
|
||||
"try (Test t3 = new Test()) { }", true);
|
||||
run("try (Test t1 = new Test();\n" +
|
||||
" Test t2 = new Test();\n" +
|
||||
" Test t3 = new Test()) { }", false);
|
||||
run("try (Test t1 = new Test()) { }\n" +
|
||||
"try (Test t2 = new Test()) { }\n" +
|
||||
"try (Test t3 = new Test()) { }\n" +
|
||||
"try (Test t4 = new Test()) { }", false);
|
||||
|
||||
run("try (Test t1 = new Test()) { i++; }", true);
|
||||
run("try (Test t1 = new Test()) { i++; }\n" +
|
||||
"try (Test t2 = new Test()) { i++; }", false);
|
||||
|
||||
run("try (Test t1 = new Test(); Test t2 = new Test()) { i++; }", false);
|
||||
|
||||
run("try (Test t1 = new Test()) { i++; }\n" +
|
||||
"try (Test t2 = new Test()) { }", true);
|
||||
|
||||
run("try (Test t1 = new Test()) { i++; }\n" +
|
||||
"try (Test t2 = new Test()) { }\n" +
|
||||
"try (Test t3 = new Test()) { }", false);
|
||||
|
||||
run("try (Test t1 = new Test()) { i++; }\n" +
|
||||
"try (Test t2 = new Test()) { i++; }\n" +
|
||||
"try (Test t3 = new Test()) { }", false);
|
||||
}
|
||||
void run(String trySpec, boolean expected) throws IOException {
|
||||
String template = "public class Test implements AutoCloseable {\n" +
|
||||
" void t(int i) {\n" +
|
||||
" TRY\n" +
|
||||
" }\n" +
|
||||
" public void close() { }\n" +
|
||||
"}\n";
|
||||
String code = template.replace("TRY", trySpec);
|
||||
Context ctx = new Context();
|
||||
DumpLower.preRegister(ctx);
|
||||
Iterable<ToolBox.JavaSource> files = Arrays.asList(new ToolBox.JavaSource(code));
|
||||
JavacTask task = JavacTool.create().getTask(null, null, null, null, null, files, ctx);
|
||||
task.call();
|
||||
boolean actual = ((DumpLower) DumpLower.instance(ctx)).closeSeen;
|
||||
|
||||
if (expected != actual) {
|
||||
throw new IllegalStateException("expected: " + expected + "; actual: " + actual + "; code:\n" + code);
|
||||
}
|
||||
}
|
||||
|
||||
static class DumpLower extends Lower {
|
||||
|
||||
public static void preRegister(Context ctx) {
|
||||
ctx.put(lowerKey, new Factory<Lower>() {
|
||||
@Override
|
||||
public Lower make(Context c) {
|
||||
return new DumpLower(c);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public DumpLower(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
boolean closeSeen;
|
||||
|
||||
@Override
|
||||
public List<JCTree> translateTopLevelClass(Env<AttrContext> env, JCTree cdef, TreeMaker make) {
|
||||
List<JCTree> result = super.translateTopLevelClass(env, cdef, make);
|
||||
|
||||
new TreeScanner() {
|
||||
@Override
|
||||
public void visitMethodDef(JCMethodDecl tree) {
|
||||
if (!tree.name.contentEquals("t"))
|
||||
return;
|
||||
|
||||
super.visitMethodDef(tree);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitApply(JCMethodInvocation tree) {
|
||||
closeSeen |= TreeInfo.symbol(tree.meth).name.contentEquals("close");
|
||||
super.visitApply(tree);
|
||||
}
|
||||
}.scan(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
157
test/langtools/tools/javac/TryWithResources/TwrSimpleClose.java
Normal file
157
test/langtools/tools/javac/TryWithResources/TwrSimpleClose.java
Normal file
@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 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 8194978
|
||||
* @summary Verify than an appropriate number of close method invocations is generated.
|
||||
* @library /tools/lib
|
||||
* @modules jdk.jdeps/com.sun.tools.classfile
|
||||
* @build toolbox.ToolBox TwrSimpleClose
|
||||
* @run main TwrSimpleClose
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.tools.FileObject;
|
||||
import javax.tools.ForwardingJavaFileManager;
|
||||
import javax.tools.JavaCompiler;
|
||||
import javax.tools.JavaFileManager;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.JavaFileObject.Kind;
|
||||
import javax.tools.SimpleJavaFileObject;
|
||||
import javax.tools.StandardJavaFileManager;
|
||||
import javax.tools.ToolProvider;
|
||||
|
||||
import com.sun.source.util.JavacTask;
|
||||
import com.sun.tools.classfile.Attribute;
|
||||
import com.sun.tools.classfile.ClassFile;
|
||||
import com.sun.tools.classfile.Code_attribute;
|
||||
import com.sun.tools.classfile.ConstantPool.CONSTANT_Methodref_info;
|
||||
import com.sun.tools.classfile.ConstantPool.CONSTANT_NameAndType_info;
|
||||
import com.sun.tools.classfile.Instruction;
|
||||
import com.sun.tools.classfile.Method;
|
||||
import com.sun.tools.classfile.Opcode;
|
||||
|
||||
import toolbox.ToolBox;
|
||||
|
||||
public class TwrSimpleClose {
|
||||
|
||||
private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
|
||||
|
||||
public static void main(String... args) throws Exception {
|
||||
new TwrSimpleClose().run();
|
||||
}
|
||||
|
||||
void run() throws Exception {
|
||||
run("try (Test t = new Test()) { System.err.println(0); }", 2);
|
||||
run("try (Test t = new Test()) {\n" +
|
||||
" if (t.hashCode() == 42)\n" +
|
||||
" return;\n" +
|
||||
" System.err.println(0);\n" +
|
||||
"}\n", 3);
|
||||
}
|
||||
|
||||
void run(String trySpec, int expectedCloseCount) throws Exception {
|
||||
String template = "public class Test implements AutoCloseable {\n" +
|
||||
" void t(int i) {\n" +
|
||||
" TRY\n" +
|
||||
" }\n" +
|
||||
" public void close() { }\n" +
|
||||
"}\n";
|
||||
String code = template.replace("TRY", trySpec);
|
||||
int closeCount = 0;
|
||||
|
||||
try (StandardJavaFileManager sfm = compiler.getStandardFileManager(null, null, null);
|
||||
JFMImpl fm = new JFMImpl(sfm)) {
|
||||
Iterable<ToolBox.JavaSource> files = Arrays.asList(new ToolBox.JavaSource(code));
|
||||
JavacTask task = (JavacTask) compiler.getTask(null, fm, null, null, null, files);
|
||||
task.call();
|
||||
|
||||
if (fm.classBytes.size() != 1) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
byte[] data = fm.classBytes.values().iterator().next();
|
||||
ClassFile cf = ClassFile.read(new ByteArrayInputStream(data));
|
||||
|
||||
for (Method m : cf.methods) {
|
||||
Code_attribute codeAttr = (Code_attribute) m.attributes.map.get(Attribute.Code);
|
||||
for (Instruction i : codeAttr.getInstructions()) {
|
||||
if (i.getOpcode() == Opcode.INVOKEVIRTUAL) {
|
||||
CONSTANT_Methodref_info method =
|
||||
(CONSTANT_Methodref_info) cf.constant_pool.get(i.getShort(1));
|
||||
CONSTANT_NameAndType_info nameAndType =
|
||||
cf.constant_pool.getNameAndTypeInfo(method.name_and_type_index);
|
||||
if ("close".equals(nameAndType.getName())) {
|
||||
closeCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (expectedCloseCount != closeCount) {
|
||||
throw new IllegalStateException("expected close count: " + expectedCloseCount +
|
||||
"; actual: " + closeCount + "; code:\n" + code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class JFMImpl extends ForwardingJavaFileManager<JavaFileManager> {
|
||||
|
||||
private final Map<String, byte[]> classBytes = new HashMap<>();
|
||||
|
||||
public JFMImpl(JavaFileManager fileManager) {
|
||||
super(fileManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind,
|
||||
FileObject sibling) throws IOException {
|
||||
try {
|
||||
return new SimpleJavaFileObject(new URI("mem://" + className + ".class"), kind) {
|
||||
@Override
|
||||
public OutputStream openOutputStream() throws IOException {
|
||||
return new ByteArrayOutputStream() {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
classBytes.put(className, toByteArray());
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
} catch (URISyntaxException ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -36,9 +36,9 @@ import static java.lang.System.lineSeparator;
|
||||
public class ResourceVariable {
|
||||
|
||||
@TADescription(annotation = "TA", type = RESOURCE_VARIABLE,
|
||||
lvarOffset = {10}, lvarLength = {106}, lvarIndex = {1})
|
||||
lvarOffset = {10}, lvarLength = {37}, lvarIndex = {1})
|
||||
@TADescription(annotation = "TB", type = RESOURCE_VARIABLE,
|
||||
lvarOffset = {22}, lvarLength = {31}, lvarIndex = {3})
|
||||
lvarOffset = {20}, lvarLength = {4}, lvarIndex = {2})
|
||||
public String testResourceVariable() {
|
||||
return
|
||||
"public void f() throws IOException {" + lineSeparator() +
|
||||
@ -49,7 +49,7 @@ public class ResourceVariable {
|
||||
}
|
||||
|
||||
@TADescription(annotation = "RTAs", type = RESOURCE_VARIABLE,
|
||||
lvarOffset = {10}, lvarLength = {26}, lvarIndex = {1})
|
||||
lvarOffset = {10}, lvarLength = {4}, lvarIndex = {1})
|
||||
public String testRepeatedAnnotation1() {
|
||||
return
|
||||
"public void f() throws IOException {" + lineSeparator() +
|
||||
@ -58,7 +58,7 @@ public class ResourceVariable {
|
||||
}
|
||||
|
||||
@TADescription(annotation = "RTAs", type = RESOURCE_VARIABLE,
|
||||
lvarOffset = {10}, lvarLength = {26}, lvarIndex = {1})
|
||||
lvarOffset = {10}, lvarLength = {4}, lvarIndex = {1})
|
||||
public String testRepeatedAnnotation2() {
|
||||
return
|
||||
"public void f() throws IOException {" + lineSeparator() +
|
||||
@ -67,9 +67,9 @@ public class ResourceVariable {
|
||||
}
|
||||
|
||||
@TADescription(annotation = "TA", type = RESOURCE_VARIABLE,
|
||||
lvarOffset = {10}, lvarLength = {106}, lvarIndex = {1})
|
||||
lvarOffset = {10}, lvarLength = {37}, lvarIndex = {1})
|
||||
@TADescription(annotation = "TB", type = RESOURCE_VARIABLE,
|
||||
lvarOffset = {22}, lvarLength = {31}, lvarIndex = {3})
|
||||
lvarOffset = {20}, lvarLength = {4}, lvarIndex = {2})
|
||||
public String testSeveralVariablesInTryWithResources() {
|
||||
return
|
||||
"public void f() throws IOException {" + lineSeparator() +
|
||||
|
@ -52,9 +52,9 @@ public class TestCaseTry {
|
||||
o = "";
|
||||
}
|
||||
|
||||
@AliveRange(varName="o", bytecodeStart=22, bytecodeLength=13)
|
||||
@AliveRange(varName="o", bytecodeStart=53, bytecodeLength=3)
|
||||
@AliveRange(varName="o", bytecodeStart=60, bytecodeLength=1)
|
||||
@AliveRange(varName="o", bytecodeStart=20, bytecodeLength=12)
|
||||
@AliveRange(varName="o", bytecodeStart=50, bytecodeLength=3)
|
||||
@AliveRange(varName="o", bytecodeStart=57, bytecodeLength=1)
|
||||
void m3() {
|
||||
Object o;
|
||||
try (BufferedReader br =
|
||||
@ -65,8 +65,8 @@ public class TestCaseTry {
|
||||
o = "";
|
||||
}
|
||||
|
||||
@AliveRange(varName="o", bytecodeStart=12, bytecodeLength=46)
|
||||
@AliveRange(varName="o", bytecodeStart=62, bytecodeLength=1)
|
||||
@AliveRange(varName="o", bytecodeStart=12, bytecodeLength=43)
|
||||
@AliveRange(varName="o", bytecodeStart=59, bytecodeLength=1)
|
||||
void m4() {
|
||||
String o;
|
||||
try (BufferedReader br =
|
||||
|
Loading…
x
Reference in New Issue
Block a user