diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodeLinkPrecheckService.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodeLinkPrecheckService.java index 6caaab7a..83d336c0 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodeLinkPrecheckService.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodeLinkPrecheckService.java @@ -54,7 +54,7 @@ public class BytecodeLinkPrecheckService { "truncated opcode at pc=" + pc); } final var opcode = readU16(module.code(), pc); - final var size = sizeForOpcode(opcode); + final var size = sizeForOpcode(opcode, pc); if (pc + size > module.code().length) { throw new BytecodeMarshalingException( BytecodeMarshalingErrorCode.MARSHAL_VERIFY_PRECHECK_TRUNCATED_INSTRUCTION, @@ -72,13 +72,16 @@ public class BytecodeLinkPrecheckService { } } - private int sizeForOpcode(final int opcode) { - return switch (opcode) { - case 0x50, 0x71, 0x72, 0x02, 0x03, 0x04, 0x10, 0x18, 0x40, 0x41, 0x42, 0x43, 0x56 -> 6; - case 0x14, 0x15, 0x52, 0x54 -> 10; - case 0x16 -> 3; - default -> 2; - }; + private int sizeForOpcode( + final int opcode, + final int pc) { + try { + return BytecodeOpcodeLayout.sizeForOpcode(opcode); + } catch (BytecodeMarshalingException ignored) { + throw new BytecodeMarshalingException( + BytecodeMarshalingErrorCode.MARSHAL_VERIFY_PRECHECK_UNKNOWN_OPCODE, + "unknown opcode 0x%04X at pc=%d".formatted(opcode, pc)); + } } private int readU16(final byte[] code, final int offset) { diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodeMarshalingErrorCode.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodeMarshalingErrorCode.java index 7053f2aa..5b155dad 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodeMarshalingErrorCode.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodeMarshalingErrorCode.java @@ -10,6 +10,7 @@ public enum BytecodeMarshalingErrorCode { MARSHAL_VERIFY_PRECHECK_FUNCTION_BOUNDS_INVALID, MARSHAL_VERIFY_PRECHECK_EXPORT_FUNC_INDEX_INVALID, MARSHAL_VERIFY_PRECHECK_HOSTCALL_INDEX_INVALID, + MARSHAL_VERIFY_PRECHECK_UNKNOWN_OPCODE, MARSHAL_VERIFY_PRECHECK_TRUNCATED_INSTRUCTION, MARSHAL_VERIFY_PRECHECK_UNTERMINATED_FUNCTION, } diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodeOpcodeLayout.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodeOpcodeLayout.java new file mode 100644 index 00000000..4cf19b75 --- /dev/null +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodeOpcodeLayout.java @@ -0,0 +1,42 @@ +package p.studio.compiler.backend.bytecode; + +import java.util.Map; + +final class BytecodeOpcodeLayout { + private static final Map SIZE_BY_OPCODE = Map.ofEntries( + Map.entry(0x01, 2), + Map.entry(0x02, 6), + Map.entry(0x03, 6), + Map.entry(0x04, 6), + Map.entry(0x10, 6), + Map.entry(0x14, 10), + Map.entry(0x15, 10), + Map.entry(0x16, 3), + Map.entry(0x17, 6), + Map.entry(0x18, 6), + Map.entry(0x40, 6), + Map.entry(0x41, 6), + Map.entry(0x42, 6), + Map.entry(0x43, 6), + Map.entry(0x50, 6), + Map.entry(0x51, 2), + Map.entry(0x52, 10), + Map.entry(0x54, 10), + Map.entry(0x56, 6), + Map.entry(0x70, 6), + Map.entry(0x71, 6), + Map.entry(0x72, 6)); + + private BytecodeOpcodeLayout() { + } + + static int sizeForOpcode(final int opcode) { + final var size = SIZE_BY_OPCODE.get(opcode); + if (size == null) { + throw new BytecodeMarshalingException( + BytecodeMarshalingErrorCode.MARSHAL_VERIFY_PRECHECK_UNKNOWN_OPCODE, + "unknown opcode 0x%04X".formatted(opcode)); + } + return size; + } +} diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodePreloadVerifierService.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodePreloadVerifierService.java index 9c5f7ed2..97180080 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodePreloadVerifierService.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodePreloadVerifierService.java @@ -35,7 +35,7 @@ public class BytecodePreloadVerifierService { BytecodeMarshalingErrorCode.MARSHAL_LINKAGE_RAW_SYSCALL_IN_PRELOAD, "raw syscall is forbidden in pre-load artifact"); } - final var size = sizeForOpcode(opcode); + final var size = sizeForOpcode(opcode, pc); if (pc + size > end) { throw new BytecodeMarshalingException( BytecodeMarshalingErrorCode.MARSHAL_VERIFY_PRECHECK_TRUNCATED_INSTRUCTION, @@ -52,13 +52,16 @@ public class BytecodePreloadVerifierService { } } - private int sizeForOpcode(final int opcode) { - return switch (opcode) { - case 0x50, 0x70, 0x71, 0x72, 0x02, 0x03, 0x04, 0x10, 0x18, 0x40, 0x41, 0x42, 0x43, 0x56 -> 6; - case 0x14, 0x15, 0x52, 0x54 -> 10; - case 0x16 -> 3; - default -> 2; - }; + private int sizeForOpcode( + final int opcode, + final int pc) { + try { + return BytecodeOpcodeLayout.sizeForOpcode(opcode); + } catch (BytecodeMarshalingException ignored) { + throw new BytecodeMarshalingException( + BytecodeMarshalingErrorCode.MARSHAL_VERIFY_PRECHECK_UNKNOWN_OPCODE, + "unknown opcode 0x%04X at pc=%d".formatted(opcode, pc)); + } } private int readU16(final byte[] code, final int offset) { diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/bytecode/BytecodeLinkPrecheckServiceTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/bytecode/BytecodeLinkPrecheckServiceTest.java index 44246265..17a84324 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/bytecode/BytecodeLinkPrecheckServiceTest.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/bytecode/BytecodeLinkPrecheckServiceTest.java @@ -61,6 +61,21 @@ class BytecodeLinkPrecheckServiceTest { assertEquals(BytecodeMarshalingErrorCode.MARSHAL_VERIFY_PRECHECK_HOSTCALL_INDEX_INVALID, thrown.code()); } + @Test + void validateMustRejectUnknownOpcode() { + final var module = new BytecodeModule( + 0, + ReadOnlyList.empty(), + ReadOnlyList.from(new BytecodeModule.FunctionMeta(0, 4, 0, 0, 0, 1)), + codeUnknownThenRet(), + null, + ReadOnlyList.empty(), + ReadOnlyList.empty()); + + final var thrown = assertThrows(BytecodeMarshalingException.class, () -> service.validate(module)); + assertEquals(BytecodeMarshalingErrorCode.MARSHAL_VERIFY_PRECHECK_UNKNOWN_OPCODE, thrown.code()); + } + private byte[] codeHostcall(final int index) { final var out = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN); out.putShort((short) 0x71); @@ -74,4 +89,11 @@ class BytecodeLinkPrecheckServiceTest { out.putShort((short) 0x51); return out.array(); } + + private byte[] codeUnknownThenRet() { + final var out = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); + out.putShort((short) 0x7FFF); + out.putShort((short) 0x51); + return out.array(); + } } diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/bytecode/BytecodePreloadVerifierServiceTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/bytecode/BytecodePreloadVerifierServiceTest.java index cb77488b..3987d2d3 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/bytecode/BytecodePreloadVerifierServiceTest.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/bytecode/BytecodePreloadVerifierServiceTest.java @@ -58,6 +58,21 @@ class BytecodePreloadVerifierServiceTest { assertEquals(BytecodeMarshalingErrorCode.MARSHAL_VERIFY_PRECHECK_UNTERMINATED_FUNCTION, thrown.code()); } + @Test + void verifyMustRejectUnknownOpcode() { + final var module = new BytecodeModule( + 0, + ReadOnlyList.empty(), + ReadOnlyList.from(new BytecodeModule.FunctionMeta(0, 4, 0, 0, 0, 1)), + codeUnknownThenRet(), + null, + ReadOnlyList.empty(), + ReadOnlyList.empty()); + + final var thrown = assertThrows(BytecodeMarshalingException.class, () -> service.verify(module)); + assertEquals(BytecodeMarshalingErrorCode.MARSHAL_VERIFY_PRECHECK_UNKNOWN_OPCODE, thrown.code()); + } + private byte[] codeHostcall(final int index) { final var out = ByteBuffer.allocate(6).order(ByteOrder.LITTLE_ENDIAN); out.putShort((short) 0x71); @@ -77,4 +92,11 @@ class BytecodePreloadVerifierServiceTest { out.putShort((short) 0x51); return out.array(); } + + private byte[] codeUnknownThenRet() { + final var out = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); + out.putShort((short) 0x7FFF); + out.putShort((short) 0x51); + return out.array(); + } }