diff --git a/src/java.base/share/classes/java/lang/classfile/CodeBuilder.java b/src/java.base/share/classes/java/lang/classfile/CodeBuilder.java index b4d3e427e91..1903eb2aef4 100644 --- a/src/java.base/share/classes/java/lang/classfile/CodeBuilder.java +++ b/src/java.base/share/classes/java/lang/classfile/CodeBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, 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 @@ -3429,6 +3429,9 @@ public sealed interface CodeBuilder * @param defaultTarget the default jump target * @param cases the switch cases * @return this builder + * @throws IllegalArgumentException if the low value is greater than the + * high value, or if there are too many targets between the low + * and high values * @see Opcode#TABLESWITCH * @see TableSwitchInstruction */ @@ -3443,6 +3446,7 @@ public sealed interface CodeBuilder * @param defaultTarget the default jump target * @param cases the switch cases * @return this builder + * @throws IllegalArgumentException if {@code cases} is empty * @see Opcode#TABLESWITCH * @see #tableswitch(int, int, Label, List) * @see TableSwitchInstruction diff --git a/src/java.base/share/classes/java/lang/classfile/instruction/TableSwitchInstruction.java b/src/java.base/share/classes/java/lang/classfile/instruction/TableSwitchInstruction.java index 3f7245fad49..f726652e641 100644 --- a/src/java.base/share/classes/java/lang/classfile/instruction/TableSwitchInstruction.java +++ b/src/java.base/share/classes/java/lang/classfile/instruction/TableSwitchInstruction.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, 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 @@ -95,6 +95,9 @@ public sealed interface TableSwitchInstruction extends Instruction * @param defaultTarget the default target of the switch * @param cases the cases of the switch; duplicate or out of bound case * handling is not specified + * @throws IllegalArgumentException if the low value is greater than the + * high value, or if there are too many targets between the low + * and high values */ static TableSwitchInstruction of(int lowValue, int highValue, Label defaultTarget, List cases) { return new AbstractInstruction.UnboundTableSwitchInstruction(lowValue, highValue, defaultTarget, cases); diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractInstruction.java index 177103917de..e1796561d1b 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractInstruction.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractInstruction.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, 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 @@ -289,9 +289,7 @@ public abstract sealed class AbstractInstruction afterPad = code.codeStart + RawBytecodeHelper.align(pos + 1 - code.codeStart); low = code.classReader.readInt(afterPad + 4); high = code.classReader.readInt(afterPad + 8); - if (high < low || (long)high - low > code.codeLength >> 2) { - throw new IllegalArgumentException("Invalid tableswitch values low: " + low + " high: " + high); - } + BytecodeHelpers.validateTableSwitchValues(low, high, code.codeLength); int cnt = high - low + 1; size = afterPad + 12 + cnt * 4 - pos; } @@ -926,6 +924,7 @@ public abstract sealed class AbstractInstruction public UnboundTableSwitchInstruction(int lowValue, int highValue, Label defaultTarget, List cases) { super(Opcode.TABLESWITCH); + BytecodeHelpers.validateTableSwitchValues(lowValue, highValue); this.lowValue = lowValue; this.highValue = highValue; this.defaultTarget = requireNonNull(defaultTarget); diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/BytecodeHelpers.java b/src/java.base/share/classes/jdk/internal/classfile/impl/BytecodeHelpers.java index 50c6f8b131c..f370ac26d54 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/BytecodeHelpers.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/BytecodeHelpers.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2024, Alibaba Group Holding Limited. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -484,6 +484,16 @@ public class BytecodeHelpers { .concat(Long.toString(value))); } + public static void validateTableSwitchValues(int low, int high) { + validateTableSwitchValues(low, high, 0xFFFF); + } + + public static void validateTableSwitchValues(int low, int high, int codeLength) { + if (high < low || 1L + high - low > codeLength >> 2) { + throw new IllegalArgumentException("Invalid tableswitch values low: " + low + " high: " + high); + } + } + public static MethodHandleEntry handleDescToHandleInfo(ConstantPoolBuilder constantPool, DirectMethodHandleDesc bootstrapMethod) { ClassEntry bsOwner = constantPool.classEntry(bootstrapMethod.owner()); NameAndTypeEntry bsNameAndType = constantPool.nameAndTypeEntry(constantPool.utf8Entry(bootstrapMethod.methodName()), diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/DirectCodeBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/DirectCodeBuilder.java index 5bdbb571b68..22a5024578b 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/DirectCodeBuilder.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/DirectCodeBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2024, Alibaba Group Holding Limited. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -1859,6 +1859,7 @@ public final class DirectCodeBuilder @Override public CodeBuilder tableswitch(int low, int high, Label defaultTarget, List cases) { + BytecodeHelpers.validateTableSwitchValues(low, high); Objects.requireNonNull(defaultTarget); // check cases when we write them writeTableSwitch(low, high, defaultTarget, cases); diff --git a/test/jdk/jdk/classfile/InstructionValidationTest.java b/test/jdk/jdk/classfile/InstructionValidationTest.java index 190cbffca55..45f69c36786 100644 --- a/test/jdk/jdk/classfile/InstructionValidationTest.java +++ b/test/jdk/jdk/classfile/InstructionValidationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, 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 @@ -23,7 +23,7 @@ /* * @test - * @bug 8341277 8361102 8361182 8361614 + * @bug 8341277 8361102 8361182 8361614 8385114 * @summary Testing ClassFile (pseudo-)instruction argument validation. * @run junit InstructionValidationTest */ @@ -94,15 +94,44 @@ class InstructionValidationTest { @Test void testSwitch() { TestUtil.runCodeHandler(cob -> { + // Null labels/cases + assertThrows(NullPointerException.class, () -> cob.tableswitch(null, List.of(SwitchCase.of(1, cob.startLabel())))); + assertThrows(NullPointerException.class, () -> cob.tableswitch(cob.startLabel(), null)); + assertThrows(NullPointerException.class, () -> cob.tableswitch(cob.startLabel(), Collections.singletonList(null))); assertThrows(NullPointerException.class, () -> cob.tableswitch(-1, 1, cob.startLabel(), null)); assertThrows(NullPointerException.class, () -> cob.lookupswitch(cob.startLabel(), null)); assertThrows(NullPointerException.class, () -> cob.tableswitch(-1, 1, cob.startLabel(), Collections.singletonList(null))); assertThrows(NullPointerException.class, () -> cob.lookupswitch(cob.startLabel(), Collections.singletonList(null))); assertThrows(NullPointerException.class, () -> cob.tableswitch(-1, 1, null, List.of())); assertThrows(NullPointerException.class, () -> cob.lookupswitch(null, List.of())); + // Illegal start/end + assertThrows(IllegalArgumentException.class, () -> cob.tableswitch(cob.startLabel(), List.of())); + assertThrows(IllegalArgumentException.class, () -> cob.tableswitch(1, -1, cob.startLabel(), List.of())); + assertThrows(IllegalArgumentException.class, () -> cob.tableswitch(Integer.MAX_VALUE, Integer.MIN_VALUE, cob.startLabel(), List.of())); + assertThrows(IllegalArgumentException.class, () -> cob.tableswitch(1, 16384, cob.startLabel(), List.of())); // Ensures nothing redundant is written in case of failure cob.return_(); }); + + Label[] capture = new Label[1]; + ClassFile.of().build(CD_Object, clb -> clb.withMethodBody("test", MTD_void, 0, cob -> { + capture[0] = cob.startLabel(); + cob.return_(); + })); + Label dummyLabel = capture[0]; + assertNotNull(dummyLabel); + + TableSwitchInstruction.of(0, 0, dummyLabel, List.of()); + LookupSwitchInstruction.of(dummyLabel, List.of()); + assertThrows(NullPointerException.class, () -> TableSwitchInstruction.of(0, 0, dummyLabel, null)); + assertThrows(NullPointerException.class, () -> LookupSwitchInstruction.of(dummyLabel, null)); + assertThrows(NullPointerException.class, () -> TableSwitchInstruction.of(0, 0, dummyLabel, Collections.singletonList(null))); + assertThrows(NullPointerException.class, () -> LookupSwitchInstruction.of(dummyLabel, Collections.singletonList(null))); + assertThrows(NullPointerException.class, () -> TableSwitchInstruction.of(0, 0, null, List.of())); + assertThrows(NullPointerException.class, () -> LookupSwitchInstruction.of(null, List.of())); + assertThrows(IllegalArgumentException.class, () -> TableSwitchInstruction.of(1, -1, dummyLabel, List.of())); + assertThrows(IllegalArgumentException.class, () -> TableSwitchInstruction.of(Integer.MAX_VALUE, Integer.MIN_VALUE, dummyLabel, List.of())); + assertThrows(IllegalArgumentException.class, () -> TableSwitchInstruction.of(1, 16384, dummyLabel, List.of())); } @Test