implements PR-O2.2
This commit is contained in:
parent
5f78df750d
commit
804fc27959
@ -11,4 +11,5 @@ public enum BytecodeMarshalingErrorCode {
|
||||
MARSHAL_VERIFY_PRECHECK_EXPORT_FUNC_INDEX_INVALID,
|
||||
MARSHAL_VERIFY_PRECHECK_HOSTCALL_INDEX_INVALID,
|
||||
MARSHAL_VERIFY_PRECHECK_TRUNCATED_INSTRUCTION,
|
||||
MARSHAL_VERIFY_PRECHECK_UNTERMINATED_FUNCTION,
|
||||
}
|
||||
|
||||
@ -0,0 +1,67 @@
|
||||
package p.studio.compiler.backend.bytecode;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Set;
|
||||
|
||||
public class BytecodePreloadVerifierService {
|
||||
private static final Set<Integer> TERMINATORS = Set.of(0x01, 0x02, 0x51);
|
||||
|
||||
public void verify(final BytecodeModule module) {
|
||||
final var input = module == null ? BytecodeModule.empty() : module;
|
||||
for (final var function : input.functions()) {
|
||||
verifyFunction(input.code(), function);
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyFunction(final byte[] code, final BytecodeModule.FunctionMeta function) {
|
||||
final var start = function.codeOffset();
|
||||
final var end = start + function.codeLen();
|
||||
if (end <= start) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pc = start;
|
||||
var lastOpcode = -1;
|
||||
while (pc < end) {
|
||||
if (pc + 2 > end) {
|
||||
throw new BytecodeMarshalingException(
|
||||
BytecodeMarshalingErrorCode.MARSHAL_VERIFY_PRECHECK_TRUNCATED_INSTRUCTION,
|
||||
"truncated opcode in function at pc=" + pc);
|
||||
}
|
||||
final var opcode = readU16(code, pc);
|
||||
if (opcode == 0x70) {
|
||||
throw new BytecodeMarshalingException(
|
||||
BytecodeMarshalingErrorCode.MARSHAL_LINKAGE_RAW_SYSCALL_IN_PRELOAD,
|
||||
"raw syscall is forbidden in pre-load artifact");
|
||||
}
|
||||
final var size = sizeForOpcode(opcode);
|
||||
if (pc + size > end) {
|
||||
throw new BytecodeMarshalingException(
|
||||
BytecodeMarshalingErrorCode.MARSHAL_VERIFY_PRECHECK_TRUNCATED_INSTRUCTION,
|
||||
"truncated instruction in function at pc=" + pc);
|
||||
}
|
||||
lastOpcode = opcode;
|
||||
pc += size;
|
||||
}
|
||||
|
||||
if (!TERMINATORS.contains(lastOpcode)) {
|
||||
throw new BytecodeMarshalingException(
|
||||
BytecodeMarshalingErrorCode.MARSHAL_VERIFY_PRECHECK_UNTERMINATED_FUNCTION,
|
||||
"function ends without terminator opcode");
|
||||
}
|
||||
}
|
||||
|
||||
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 readU16(final byte[] code, final int offset) {
|
||||
return ByteBuffer.wrap(code, offset, 2).order(ByteOrder.LITTLE_ENDIAN).getShort() & 0xFFFF;
|
||||
}
|
||||
}
|
||||
@ -22,7 +22,8 @@ public class BuilderPipelineService {
|
||||
new LowerToIRVMPipelineStage(),
|
||||
new OptimizeIRVMPipelineStage(),
|
||||
new EmitBytecodePipelineStage(),
|
||||
new LinkBytecodePipelineStage()
|
||||
new LinkBytecodePipelineStage(),
|
||||
new VerifyBytecodePipelineStage()
|
||||
);
|
||||
INSTANCE = new BuilderPipelineService(stages);
|
||||
}
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
package p.studio.compiler.workspaces.stages;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import p.studio.compiler.backend.bytecode.BytecodeMarshalingException;
|
||||
import p.studio.compiler.backend.bytecode.BytecodePreloadVerifierService;
|
||||
import p.studio.compiler.messages.BuildingIssueSink;
|
||||
import p.studio.compiler.models.BuilderPipelineContext;
|
||||
import p.studio.compiler.workspaces.PipelineStage;
|
||||
import p.studio.utilities.logs.LogAggregator;
|
||||
|
||||
@Slf4j
|
||||
public class VerifyBytecodePipelineStage implements PipelineStage {
|
||||
private final BytecodePreloadVerifierService verifierService;
|
||||
|
||||
public VerifyBytecodePipelineStage() {
|
||||
this(new BytecodePreloadVerifierService());
|
||||
}
|
||||
|
||||
VerifyBytecodePipelineStage(final BytecodePreloadVerifierService verifierService) {
|
||||
this.verifierService = verifierService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BuildingIssueSink run(BuilderPipelineContext ctx, LogAggregator logs) {
|
||||
if (ctx.bytecodeModule == null) {
|
||||
return BuildingIssueSink.empty()
|
||||
.report(builder -> builder
|
||||
.error(true)
|
||||
.phase("BACKEND_VERIFY_PRELOAD")
|
||||
.code("MARSHAL_FORMAT_STAGE_INPUT_MISSING")
|
||||
.message("[BUILD]: bytecode module is missing before VerifyBytecode stage"));
|
||||
}
|
||||
|
||||
try {
|
||||
verifierService.verify(ctx.bytecodeModule);
|
||||
} catch (BytecodeMarshalingException e) {
|
||||
return BuildingIssueSink.empty()
|
||||
.report(builder -> builder
|
||||
.error(true)
|
||||
.phase("BACKEND_VERIFY_PRELOAD")
|
||||
.code(e.code().name())
|
||||
.message("[BUILD]: preload verifier failed: " + e.getMessage())
|
||||
.exception(e));
|
||||
}
|
||||
|
||||
return BuildingIssueSink.empty();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,80 @@
|
||||
package p.studio.compiler.backend.bytecode;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import p.studio.utilities.structures.ReadOnlyList;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class BytecodePreloadVerifierServiceTest {
|
||||
|
||||
private final BytecodePreloadVerifierService service = new BytecodePreloadVerifierService();
|
||||
|
||||
@Test
|
||||
void verifyMustAcceptTerminatedFunctionWithoutRawSyscall() {
|
||||
final var module = new BytecodeModule(
|
||||
0,
|
||||
ReadOnlyList.empty(),
|
||||
ReadOnlyList.from(new BytecodeModule.FunctionMeta(0, 2, 0, 0, 0, 1)),
|
||||
codeRet(),
|
||||
null,
|
||||
ReadOnlyList.empty(),
|
||||
ReadOnlyList.empty());
|
||||
|
||||
assertDoesNotThrow(() -> service.verify(module));
|
||||
}
|
||||
|
||||
@Test
|
||||
void verifyMustRejectRawSyscallInPreload() {
|
||||
final var module = new BytecodeModule(
|
||||
0,
|
||||
ReadOnlyList.empty(),
|
||||
ReadOnlyList.from(new BytecodeModule.FunctionMeta(0, 6, 0, 0, 0, 1)),
|
||||
codeRawSyscall(0x1001),
|
||||
null,
|
||||
ReadOnlyList.empty(),
|
||||
ReadOnlyList.empty());
|
||||
|
||||
final var thrown = assertThrows(BytecodeMarshalingException.class, () -> service.verify(module));
|
||||
assertEquals(BytecodeMarshalingErrorCode.MARSHAL_LINKAGE_RAW_SYSCALL_IN_PRELOAD, thrown.code());
|
||||
}
|
||||
|
||||
@Test
|
||||
void verifyMustRejectFunctionWithoutTerminator() {
|
||||
final var module = new BytecodeModule(
|
||||
0,
|
||||
ReadOnlyList.empty(),
|
||||
ReadOnlyList.from(new BytecodeModule.FunctionMeta(0, 6, 0, 0, 0, 1)),
|
||||
codeHostcall(0),
|
||||
null,
|
||||
ReadOnlyList.empty(),
|
||||
ReadOnlyList.from(new BytecodeModule.SyscallDecl("gfx", "draw", 1, 0, 0)));
|
||||
|
||||
final var thrown = assertThrows(BytecodeMarshalingException.class, () -> service.verify(module));
|
||||
assertEquals(BytecodeMarshalingErrorCode.MARSHAL_VERIFY_PRECHECK_UNTERMINATED_FUNCTION, thrown.code());
|
||||
}
|
||||
|
||||
private byte[] codeHostcall(final int index) {
|
||||
final var out = ByteBuffer.allocate(6).order(ByteOrder.LITTLE_ENDIAN);
|
||||
out.putShort((short) 0x71);
|
||||
out.putInt(index);
|
||||
return out.array();
|
||||
}
|
||||
|
||||
private byte[] codeRawSyscall(final int index) {
|
||||
final var out = ByteBuffer.allocate(6).order(ByteOrder.LITTLE_ENDIAN);
|
||||
out.putShort((short) 0x70);
|
||||
out.putInt(index);
|
||||
return out.array();
|
||||
}
|
||||
|
||||
private byte[] codeRet() {
|
||||
final var out = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN);
|
||||
out.putShort((short) 0x51);
|
||||
return out.array();
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,7 @@ import p.studio.compiler.workspaces.stages.LoadSourcesPipelineStage;
|
||||
import p.studio.compiler.workspaces.stages.LowerToIRVMPipelineStage;
|
||||
import p.studio.compiler.workspaces.stages.OptimizeIRVMPipelineStage;
|
||||
import p.studio.compiler.workspaces.stages.ResolveDepsPipelineStage;
|
||||
import p.studio.compiler.workspaces.stages.VerifyBytecodePipelineStage;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
@ -33,7 +34,8 @@ class BuilderPipelineServiceOrderTest {
|
||||
LowerToIRVMPipelineStage.class,
|
||||
OptimizeIRVMPipelineStage.class,
|
||||
EmitBytecodePipelineStage.class,
|
||||
LinkBytecodePipelineStage.class),
|
||||
LinkBytecodePipelineStage.class,
|
||||
VerifyBytecodePipelineStage.class),
|
||||
stageTypes);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
package p.studio.compiler.workspaces.stages;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import p.studio.compiler.backend.bytecode.BytecodeModule;
|
||||
import p.studio.compiler.messages.BuilderPipelineConfig;
|
||||
import p.studio.compiler.models.BuilderPipelineContext;
|
||||
import p.studio.utilities.logs.LogAggregator;
|
||||
import p.studio.utilities.structures.ReadOnlyList;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class VerifyBytecodePipelineStageTest {
|
||||
|
||||
@Test
|
||||
void runMustFailWhenBytecodeModuleIsMissing() {
|
||||
final var ctx = BuilderPipelineContext.compilerContext(new BuilderPipelineConfig(false, "."));
|
||||
final var stage = new VerifyBytecodePipelineStage();
|
||||
|
||||
final var issues = stage.run(ctx, LogAggregator.empty());
|
||||
final var firstIssue = issues.asCollection().iterator().next();
|
||||
|
||||
assertTrue(issues.hasErrors());
|
||||
assertEquals("BACKEND_VERIFY_PRELOAD", firstIssue.getPhase());
|
||||
assertEquals("MARSHAL_FORMAT_STAGE_INPUT_MISSING", firstIssue.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void runMustRejectRawSyscallInPreload() {
|
||||
final var ctx = BuilderPipelineContext.compilerContext(new BuilderPipelineConfig(false, "."));
|
||||
ctx.bytecodeModule = new BytecodeModule(
|
||||
0,
|
||||
ReadOnlyList.empty(),
|
||||
ReadOnlyList.from(new BytecodeModule.FunctionMeta(0, 6, 0, 0, 0, 1)),
|
||||
codeRawSyscall(0x1001),
|
||||
null,
|
||||
ReadOnlyList.empty(),
|
||||
ReadOnlyList.empty());
|
||||
|
||||
final var issues = new VerifyBytecodePipelineStage().run(ctx, LogAggregator.empty());
|
||||
final var firstIssue = issues.asCollection().iterator().next();
|
||||
|
||||
assertTrue(issues.hasErrors());
|
||||
assertEquals("MARSHAL_LINKAGE_RAW_SYSCALL_IN_PRELOAD", firstIssue.getCode());
|
||||
}
|
||||
|
||||
private byte[] codeRawSyscall(final int index) {
|
||||
final var out = ByteBuffer.allocate(6).order(ByteOrder.LITTLE_ENDIAN);
|
||||
out.putShort((short) 0x70);
|
||||
out.putInt(index);
|
||||
return out.array();
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user