Skip to content

Commit 6fb6f3d

Browse files
committed
8361638: java.lang.classfile.CodeBuilder.CatchBuilder should not throw IllegalArgumentException for representable exception handlers
Reviewed-by: asotona
1 parent 44b19c0 commit 6fb6f3d

File tree

4 files changed

+71
-32
lines changed

4 files changed

+71
-32
lines changed

src/java.base/share/classes/java/lang/classfile/CodeBuilder.java

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,11 @@ default CodeBuilder ifThenElse(Opcode opcode,
330330

331331
/**
332332
* A builder to add catch blocks.
333+
* <p>
334+
* The order of catch blocks is significant. When an exception is thrown
335+
* by the try block, the first catch block whose exception type is {@linkplain
336+
* Class#isAssignableFrom(Class) the same class as or a superclass of} the
337+
* class of exception thrown is branched to (JVMS {@jvms 2.10}).
333338
*
334339
* @see #trying
335340
* @see ExceptionCatch
@@ -348,13 +353,16 @@ sealed interface CatchBuilder permits CatchBuilderImpl {
348353
* If the type of exception is {@code null} then the catch block catches
349354
* all exceptions.
350355
*
356+
* @apiNote
357+
* If the type of exception to catch is already handled by previous
358+
* catch blocks, this block will never be executed.
359+
*
351360
* @param exceptionType the type of exception to catch, may be {@code null}
352361
* @param catchHandler handler that receives a {@link BlockCodeBuilder} to
353362
* generate the body of the catch block
354363
* @return this builder
355-
* @throws IllegalArgumentException if an existing catch block catches
356-
* an exception of the given type or {@code exceptionType}
357-
* represents a primitive type
364+
* @throws IllegalArgumentException if {@code exceptionType} represents
365+
* a primitive type
358366
* @see #catchingMulti
359367
* @see #catchingAll
360368
*/
@@ -372,12 +380,16 @@ sealed interface CatchBuilder permits CatchBuilderImpl {
372380
* If list of exception types is empty then the catch block catches all
373381
* exceptions.
374382
*
383+
* @apiNote
384+
* If every type of exception to catch is already handled by previous
385+
* catch blocks, this block will never be executed.
386+
*
375387
* @param exceptionTypes the types of exception to catch
376388
* @param catchHandler handler that receives a {@link BlockCodeBuilder}
377389
* to generate the body of the catch block
378390
* @return this builder
379-
* @throws IllegalArgumentException if an existing catch block catches
380-
* one or more exceptions of the given types
391+
* @throws IllegalArgumentException if any exception type represents a
392+
* primitive type
381393
* @see #catching
382394
* @see #catchingAll
383395
*/
@@ -392,10 +404,12 @@ sealed interface CatchBuilder permits CatchBuilderImpl {
392404
* The caught exception will be on top of the operand stack when the
393405
* catch block is entered.
394406
*
407+
* @apiNote
408+
* Since this block intercepts all exceptions, all subsequent catch
409+
* blocks will never be executed.
410+
*
395411
* @param catchAllHandler handler that receives a {@link BlockCodeBuilder}
396412
* to generate the body of the catch block
397-
* @throws IllegalArgumentException if an existing catch block catches
398-
* all exceptions
399413
* @see #catching
400414
* @see #catchingMulti
401415
*/

src/java.base/share/classes/java/lang/classfile/instruction/ExceptionCatch.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@
3939
* A pseudo-instruction modeling an entry in the {@code exception_table} array
4040
* of a {@link CodeAttribute Code} attribute. Catch (JVMS {@jvms 3.12}) and
4141
* finally (JVMS {@jvms 3.14}) blocks in Java source code compile to exception
42-
* table entries. Delivered as a {@link CodeElement} when traversing the
43-
* contents of a {@link CodeModel}.
42+
* table entries. The order of exception table entries is significant: when an
43+
* exception is thrown in a method, execution branches to the first matching
44+
* exception handler if such a handler exists (JVMS {@jvms 2.10}). Delivered as
45+
* a {@link CodeElement} when traversing the contents of a {@link CodeModel}.
4446
* <p>
4547
* An exception table entry is composite:
4648
* {@snippet lang=text :

src/java.base/share/classes/jdk/internal/classfile/impl/CatchBuilderImpl.java

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -27,8 +27,9 @@
2727
import java.lang.classfile.CodeBuilder;
2828
import java.lang.classfile.Label;
2929
import java.lang.classfile.Opcode;
30+
import java.lang.classfile.constantpool.ClassEntry;
3031
import java.lang.constant.ClassDesc;
31-
import java.lang.constant.ConstantDesc;
32+
import java.util.ArrayList;
3233
import java.util.HashSet;
3334
import java.util.List;
3435
import java.util.Objects;
@@ -39,14 +40,12 @@ public final class CatchBuilderImpl implements CodeBuilder.CatchBuilder {
3940
final CodeBuilder b;
4041
final BlockCodeBuilderImpl tryBlock;
4142
final Label tryCatchEnd;
42-
final Set<ConstantDesc> catchTypes;
4343
BlockCodeBuilderImpl catchBlock;
4444

4545
public CatchBuilderImpl(CodeBuilder b, BlockCodeBuilderImpl tryBlock, Label tryCatchEnd) {
4646
this.b = b;
4747
this.tryBlock = tryBlock;
4848
this.tryCatchEnd = tryCatchEnd;
49-
this.catchTypes = new HashSet<>();
5049
}
5150

5251
@Override
@@ -59,15 +58,21 @@ public CodeBuilder.CatchBuilder catchingMulti(List<ClassDesc> exceptionTypes, Co
5958
Objects.requireNonNull(exceptionTypes);
6059
Objects.requireNonNull(catchHandler);
6160

62-
if (catchBlock == null) {
63-
if (tryBlock.reachable()) {
64-
b.branch(Opcode.GOTO, tryCatchEnd);
61+
// nullable list of CP entries - null means catching all (0)
62+
List<ClassEntry> entries = new ArrayList<>(Math.max(1, exceptionTypes.size()));
63+
if (exceptionTypes.isEmpty()) {
64+
entries.add(null);
65+
} else {
66+
for (var exceptionType : exceptionTypes) {
67+
var entry = b.constantPool().classEntry(exceptionType); // throws IAE
68+
entries.add(entry);
6569
}
6670
}
71+
// End validation
6772

68-
for (var exceptionType : exceptionTypes) {
69-
if (!catchTypes.add(exceptionType)) {
70-
throw new IllegalArgumentException("Existing catch block catches exception of type: " + exceptionType);
73+
if (catchBlock == null) {
74+
if (tryBlock.reachable()) {
75+
b.branch(Opcode.GOTO, tryCatchEnd);
7176
}
7277
}
7378

@@ -82,13 +87,9 @@ public CodeBuilder.CatchBuilder catchingMulti(List<ClassDesc> exceptionTypes, Co
8287
catchBlock = new BlockCodeBuilderImpl(b, tryCatchEnd);
8388
Label tryStart = tryBlock.startLabel();
8489
Label tryEnd = tryBlock.endLabel();
85-
if (exceptionTypes.isEmpty()) {
86-
catchBlock.exceptionCatchAll(tryStart, tryEnd, catchBlock.startLabel());
87-
}
88-
else {
89-
for (var exceptionType : exceptionTypes) {
90-
catchBlock.exceptionCatch(tryStart, tryEnd, catchBlock.startLabel(), exceptionType);
91-
}
90+
for (var entry : entries) {
91+
// This accepts null for catching all
92+
catchBlock.exceptionCatch(tryStart, tryEnd, catchBlock.startLabel(), entry);
9293
}
9394
catchBlock.start();
9495
catchHandler.accept(catchBlock);

test/jdk/jdk/classfile/BuilderTryCatchTest.java

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -23,6 +23,7 @@
2323

2424
/*
2525
* @test
26+
* @bug 8361638
2627
* @summary Testing ClassFile builder blocks.
2728
* @run junit BuilderTryCatchTest
2829
*/
@@ -37,6 +38,7 @@
3738

3839
import static java.lang.classfile.ClassFile.ACC_PUBLIC;
3940
import static java.lang.classfile.ClassFile.ACC_STATIC;
41+
import static java.lang.constant.ConstantDescs.*;
4042
import static org.junit.jupiter.api.Assertions.*;
4143
import org.junit.jupiter.api.Test;
4244

@@ -47,20 +49,40 @@
4749
import java.lang.invoke.MethodType;
4850
import java.nio.file.Files;
4951
import java.nio.file.Path;
52+
import java.util.Collections;
5053
import java.util.List;
5154
import java.util.function.Consumer;
5255

53-
import static java.lang.constant.ConstantDescs.CD_Double;
54-
import static java.lang.constant.ConstantDescs.CD_Integer;
55-
import static java.lang.constant.ConstantDescs.CD_Object;
56-
import static java.lang.constant.ConstantDescs.CD_String;
57-
5856
class BuilderTryCatchTest {
5957

6058
static final ClassDesc CD_IOOBE = IndexOutOfBoundsException.class.describeConstable().get();
6159
static final ClassDesc CD_NPE = NullPointerException.class.describeConstable().get();
6260
static final MethodTypeDesc MTD_String = MethodType.methodType(String.class).describeConstable().get();
6361

62+
@Test
63+
void testExceptionalContracts() throws Throwable {
64+
generateTryCatchMethod(catchBuilder -> {
65+
Consumer<CodeBuilder.BlockCodeBuilder> handler = tb -> tb.pop().aconst_null().areturn();
66+
assertThrows(NullPointerException.class, () -> catchBuilder.catching(CD_NPE, null));
67+
assertThrows(NullPointerException.class, () -> catchBuilder.catchingMulti(null, handler));
68+
assertThrows(NullPointerException.class, () -> catchBuilder.catchingMulti(List.of(), null));
69+
assertThrows(NullPointerException.class, () -> catchBuilder.catchingMulti(Collections.singletonList(null), null));
70+
assertThrows(NullPointerException.class, () -> catchBuilder.catchingAll(null));
71+
catchBuilder.catchingMulti(List.of(CD_IOOBE, CD_NPE), tb -> {
72+
tb.invokevirtual(CD_Object, "toString", MTD_String);
73+
tb.astore(1);
74+
});
75+
catchBuilder.catchingAll(tb -> tb.pop().loadConstant("all").areturn());
76+
assertThrows(IllegalArgumentException.class, () -> catchBuilder.catching(CD_int, handler));
77+
assertDoesNotThrow(() -> catchBuilder.catching(CD_NPE, handler));
78+
assertDoesNotThrow(() -> catchBuilder.catching(null, handler));
79+
assertDoesNotThrow(() -> catchBuilder.catchingMulti(List.of(), handler));
80+
assertDoesNotThrow(() -> catchBuilder.catchingMulti(List.of(CD_Exception, CD_IOOBE), handler));
81+
assertThrows(IllegalArgumentException.class, () -> catchBuilder.catchingMulti(List.of(CD_long, CD_Throwable), handler));
82+
assertDoesNotThrow(() -> catchBuilder.catchingAll(handler));
83+
});
84+
}
85+
6486
@Test
6587
void testTryCatchCatchAll() throws Throwable {
6688
byte[] bytes = generateTryCatchMethod(catchBuilder -> {

0 commit comments

Comments
 (0)