From 1d98388f9fbccf941982a17eb12a278e8fc04915 Mon Sep 17 00:00:00 2001 From: Naveed Khan Date: Thu, 16 Jul 2026 01:14:27 +0530 Subject: [PATCH] add negative verifier tests for array load/store component type checks TestArrayAccess05 and TestArrayAccess06 only cover iaload and caload, so the component type checks for the remaining array load/store instructions in InstConstraintVisitor have no negative tests. Add a parameterized TestArrayAccess07Creator that applies a given array access instruction to an array whose component type does not match (castore on an int[], iastore on a char[], and so on) and assert that structural verification rejects each of aaload, aastore, baload, bastore, castore, daload, dastore, faload, fastore, iastore, laload, lastore, saload and sastore. Make TestCreator.getClassName protected so the creator can name the generated class per instruction instead of adding fourteen creator subclasses. Follow-up to #522. --- .../verifier/VerifierArrayAccessTest.java | 43 ++++++ .../tests/TestArrayAccess07Creator.java | 129 ++++++++++++++++++ .../bcel/verifier/tests/TestCreator.java | 2 +- 3 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 src/test/java/org/apache/bcel/verifier/tests/TestArrayAccess07Creator.java diff --git a/src/test/java/org/apache/bcel/verifier/VerifierArrayAccessTest.java b/src/test/java/org/apache/bcel/verifier/VerifierArrayAccessTest.java index 0cebe1319c..227ef29bcd 100644 --- a/src/test/java/org/apache/bcel/verifier/VerifierArrayAccessTest.java +++ b/src/test/java/org/apache/bcel/verifier/VerifierArrayAccessTest.java @@ -22,7 +22,11 @@ import static org.junit.jupiter.api.Assertions.assertThrowsExactly; import java.io.IOException; +import java.util.stream.Stream; +import org.apache.bcel.generic.ArrayInstruction; +import org.apache.bcel.generic.InstructionConst; +import org.apache.bcel.generic.Type; import org.apache.bcel.verifier.tests.TestArrayAccess02Creator; import org.apache.bcel.verifier.tests.TestArrayAccess03Creator; import org.apache.bcel.verifier.tests.TestArrayAccess04DoubleCreator; @@ -33,10 +37,49 @@ import org.apache.bcel.verifier.tests.TestArrayAccess04UnknownCreator; import org.apache.bcel.verifier.tests.TestArrayAccess05Creator; import org.apache.bcel.verifier.tests.TestArrayAccess06Creator; +import org.apache.bcel.verifier.tests.TestArrayAccess07Creator; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; class VerifierArrayAccessTest extends AbstractVerifierTest { + /** + * Array load and store instructions applied to an array whose component type does not match the instruction. + * {@code caload} and {@code iaload} have their own tests, {@link org.apache.bcel.verifier.tests.TestArrayAccess06Creator} and + * {@link org.apache.bcel.verifier.tests.TestArrayAccess05Creator}. + */ + static Stream invalidComponentTypeArrayAccess() { + return Stream.of( + // @formatter:off + Arguments.of(InstructionConst.AALOAD, Type.INT), + Arguments.of(InstructionConst.AASTORE, Type.INT), + Arguments.of(InstructionConst.BALOAD, Type.INT), + Arguments.of(InstructionConst.BASTORE, Type.INT), + Arguments.of(InstructionConst.CASTORE, Type.INT), + Arguments.of(InstructionConst.DALOAD, Type.INT), + Arguments.of(InstructionConst.DASTORE, Type.INT), + Arguments.of(InstructionConst.FALOAD, Type.INT), + Arguments.of(InstructionConst.FASTORE, Type.INT), + Arguments.of(InstructionConst.IASTORE, Type.CHAR), + Arguments.of(InstructionConst.LALOAD, Type.INT), + Arguments.of(InstructionConst.LASTORE, Type.INT), + Arguments.of(InstructionConst.SALOAD, Type.INT), + Arguments.of(InstructionConst.SASTORE, Type.INT)); + // @formatter:on + } + + @ParameterizedTest + @MethodSource("invalidComponentTypeArrayAccess") + void testInvalidComponentTypeArrayAccess(final ArrayInstruction arrayInstruction, final Type arrayElementType) + throws IOException, ClassNotFoundException { + final TestArrayAccess07Creator creator = new TestArrayAccess07Creator(arrayInstruction, arrayElementType); + creator.create(); + assertVerifyRejected(creator.getSimpleClassName(), + "Verification of " + arrayInstruction.getName() + " applied to a " + arrayElementType + "[] must fail."); + } + @Test void testInvalidArrayAccess() throws IOException, ClassNotFoundException { new TestArrayAccess03Creator().create(); diff --git a/src/test/java/org/apache/bcel/verifier/tests/TestArrayAccess07Creator.java b/src/test/java/org/apache/bcel/verifier/tests/TestArrayAccess07Creator.java new file mode 100644 index 0000000000..da845b57d2 --- /dev/null +++ b/src/test/java/org/apache/bcel/verifier/tests/TestArrayAccess07Creator.java @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.bcel.verifier.tests; + +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.bcel.Const; +import org.apache.bcel.classfile.JavaClass; +import org.apache.bcel.generic.ArrayInstruction; +import org.apache.bcel.generic.ClassGen; +import org.apache.bcel.generic.ConstantPoolGen; +import org.apache.bcel.generic.Instruction; +import org.apache.bcel.generic.InstructionConst; +import org.apache.bcel.generic.InstructionFactory; +import org.apache.bcel.generic.InstructionList; +import org.apache.bcel.generic.MethodGen; +import org.apache.bcel.generic.PUSH; +import org.apache.bcel.generic.Type; + +/** + * Emits a method that applies the given array load or store instruction to an array whose component type does not + * match the instruction, for example {@code castore} on an {@code int[]} or {@code laload} on an {@code int[]}. + * Structural verification must reject every such access. + */ +public class TestArrayAccess07Creator extends TestCreator { + private final InstructionFactory factory; + private final ConstantPoolGen cp; + private final ClassGen cg; + private final ArrayInstruction arrayInstruction; + private final Type arrayElementType; + private final String simpleClassName; + + public TestArrayAccess07Creator(final ArrayInstruction arrayInstruction, final Type arrayElementType) { + this.arrayInstruction = arrayInstruction; + this.arrayElementType = arrayElementType; + final String name = arrayInstruction.getName(); + simpleClassName = "TestArrayAccess07" + Character.toUpperCase(name.charAt(0)) + name.substring(1); + cg = new ClassGen(TEST_PACKAGE + "." + simpleClassName, "java.lang.Object", simpleClassName + ".java", Const.ACC_PUBLIC | Const.ACC_SUPER, + new String[] {}); + cp = cg.getConstantPool(); + factory = new InstructionFactory(cg, cp); + } + + @Override + public void create(final OutputStream out) throws IOException { + createMethod_0(); + createMethod_1(); + cg.getJavaClass().dump(out); + } + + private void createMethod_0() { + final InstructionList il = new InstructionList(); + final MethodGen method = new MethodGen(Const.ACC_PUBLIC, Type.VOID, Type.NO_ARGS, new String[] {}, "", TEST_PACKAGE + "." + simpleClassName, il, + cp); + il.append(InstructionFactory.createLoad(Type.OBJECT, 0)); + il.append(factory.createInvoke("java.lang.Object", "", Type.VOID, Type.NO_ARGS, Const.INVOKESPECIAL)); + il.append(InstructionFactory.createReturn(Type.VOID)); + method.setMaxStack(); + method.setMaxLocals(); + cg.addMethod(method.getMethod()); + il.dispose(); + } + + private void createMethod_1() { + final InstructionList il = new InstructionList(); + final MethodGen method = new MethodGen(Const.ACC_PUBLIC | Const.ACC_STATIC, Type.VOID, Type.NO_ARGS, new String[] {}, "test", + TEST_PACKAGE + "." + simpleClassName, il, cp); + + il.append(new PUSH(cp, 1)); + il.append(factory.createNewArray(arrayElementType, (short) 1)); + il.append(new PUSH(cp, 0)); + if (arrayInstruction.produceStack(cp) == 0) { + // store: push a value of the type the instruction stores, then access the mismatched array + il.append(storedValue()); + il.append(arrayInstruction); + } else { + // load: access the mismatched array, then pop the loaded value + il.append(arrayInstruction); + il.append(arrayInstruction.produceStack(cp) == 2 ? InstructionConst.POP2 : InstructionConst.POP); + } + il.append(InstructionFactory.createReturn(Type.VOID)); + method.setMaxStack(); + method.setMaxLocals(); + cg.addMethod(method.getMethod()); + il.dispose(); + } + + @Override + protected String getClassName() { + return simpleClassName + JavaClass.EXTENSION; + } + + public String getSimpleClassName() { + return simpleClassName; + } + + private Instruction storedValue() { + switch (arrayInstruction.getOpcode()) { + case Const.AASTORE: + return InstructionConst.ACONST_NULL; + case Const.DASTORE: + return InstructionConst.DCONST_1; + case Const.FASTORE: + return InstructionConst.FCONST_1; + case Const.LASTORE: + return InstructionConst.LCONST_1; + default: + return InstructionConst.ICONST_1; + } + } +} diff --git a/src/test/java/org/apache/bcel/verifier/tests/TestCreator.java b/src/test/java/org/apache/bcel/verifier/tests/TestCreator.java index 2edbad8266..e69a03a40c 100644 --- a/src/test/java/org/apache/bcel/verifier/tests/TestCreator.java +++ b/src/test/java/org/apache/bcel/verifier/tests/TestCreator.java @@ -49,7 +49,7 @@ private File getClassesFolder() throws IOException { } } - private String getClassName() { + protected String getClassName() { final String name = getClass().getName(); return name.substring(name.lastIndexOf('.') + 1).replace("Creator", JavaClass.EXTENSION); }