From c9c2eb93c251ecef2d0a46550ac3290437f4b331 Mon Sep 17 00:00:00 2001 From: Simeon Andreev Date: Wed, 15 Mar 2023 19:52:24 +0200 Subject: [PATCH] Record with exception in constructor results in invalid byte code The following code results in generating instructions after an athrow byte code statement: record X(String s) { X { throw new RuntimeException(); } } This is due to CompactConstructorDeclaration.checkAndGenerateFieldAssignment() appending field assignments after the record constructor body. This change adjusts CompactConstructorDeclaration to not generate field assignments if the execution flow is unreachable or dead after adding the statements from the record constructor. This prevents generating code after an athrow statement in the byte code. Fixes: #487 Signed-off-by: Simeon Andreev --- .../ast/CompactConstructorDeclaration.java | 2 +- .../RecordsRestrictedClassTest.java | 54 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/CompactConstructorDeclaration.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/CompactConstructorDeclaration.java index 391b8609908..17e7d75f063 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/CompactConstructorDeclaration.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/CompactConstructorDeclaration.java @@ -76,7 +76,7 @@ protected void checkAndGenerateFieldAssignment(FlowContext flowContext, FlowInfo assert flowInfo.isDefinitelyAssigned(field); fieldAssignments.add(assignment); } - if (fieldAssignments.isEmpty()) + if (fieldAssignments.isEmpty() || (flowInfo.tagBits & FlowInfo.UNREACHABLE_OR_DEAD) != 0) return; Statement[] fa = fieldAssignments.toArray(new Statement[0]); diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/RecordsRestrictedClassTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/RecordsRestrictedClassTest.java index 0149a95be0f..feb7f948d9c 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/RecordsRestrictedClassTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/RecordsRestrictedClassTest.java @@ -9213,4 +9213,58 @@ public void testIssue365_001() throws Exception { RecordsRestrictedClassTest.verifyClassFile(expectedOutput, "A.class", ClassFileBytesDisassembler.SYSTEM); } + +/** + * Test that the following code doesn't result in generating byte code after the throw statement: + *
+ * record X(String s) {
+ *    X {
+ *        throw new RuntimeException();
+ *    }
+ * }
+ * 
+ */ +public void testRecordConstructorWithExceptionGh487() throws Exception { + getPossibleComplianceLevels(); + runConformTest( + // test directory preparation + true /* should flush output directory */, + new String[] { /* test files */ + "X.java", + """ + public record X(String s) { + public X { + throw new RuntimeException(); + } + public static void main(String[] args) throws Exception { + new X(""); + } + } + """, + }, + // compiler results + "" /* expected compiler log */, + // runtime results + "" /* expected output string */, + """ + java.lang.RuntimeException + at X.(X.java:3) + at X.main(X.java:6) + """ /* expected error string */, + // javac options + JavacTestOptions.forRelease("16")); + String expectedOutput = // constructor + """ + // Method descriptor #8 (Ljava/lang/String;)V + // Stack: 2, Locals: 2 + public X(java.lang.String s); + 0 aload_0 [this] + 1 invokespecial java.lang.Record() [10] + 4 new java.lang.RuntimeException [13] + 7 dup + 8 invokespecial java.lang.RuntimeException() [15] + 11 athrow + """; + RecordsRestrictedClassTest.verifyClassFile(expectedOutput, "X.class", ClassFileBytesDisassembler.SYSTEM); +} }