harden compiler parsing, recovery, and deep-expression handling

This commit is contained in:
2026-06-12 00:28:45 +02:00
parent a5ceb727c1
commit 99ba907f59
13 changed files with 1159 additions and 503 deletions
+109 -19
View File
@@ -40,9 +40,37 @@ function_result_type :: proc(function: ir.Function) -> string {
return llvm_type(function.result)
}
write_operand :: proc(builder: ^strings.Builder, instructions: []ir.Instruction, value_id: int) {
if value_id < 0 || value_id >= len(instructions) {
fmt.sbprintf(builder, "-6148914691236517206")
sentinel :: proc(value_type: types.Type) -> i64 {
switch value_type.bits {
case 8: return -86
case 16: return -21846
case 32: return -1431655766
case: return -6148914691236517206
}
}
valid_instruction :: proc(instructions: []ir.Instruction, instruction_id: int) -> bool {
return instruction_id >= 0 && instruction_id < len(instructions)
}
valid_value :: proc(instructions: []ir.Instruction, value_id: int, expected: types.Type) -> bool {
if !valid_instruction(instructions, value_id) ||
!types.is_concrete_integer(expected) ||
!types.equal(instructions[value_id].type, expected) {
return false
}
switch instructions[value_id].op {
case .Param, .Const, .Load_Global, .Load, .Widen, .Add_Checked, .Call:
return true
case .Alloca, .Store, .Trap, .Return, .Return_Void:
return false
}
return false
}
write_operand :: proc(builder: ^strings.Builder, instructions: []ir.Instruction, value_id: int, expected: types.Type) {
if !valid_value(instructions, value_id, expected) {
fmt.sbprintf(builder, "%d", sentinel(expected))
return
}
value := instructions[value_id]
@@ -95,13 +123,27 @@ emit_trap_call :: proc(emitter: ^Emitter, message_id: int) {
)
}
emit_call_args :: proc(builder: ^strings.Builder, instructions: []ir.Instruction, args: []int) {
emit_recovery_value :: proc(emitter: ^Emitter, instruction_id: int, instruction: ir.Instruction, fallback: string) {
message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, fallback)
emit_trap_call(emitter, message)
if types.is_concrete_integer(instruction.type) {
fmt.sbprintf(
&emitter.builder,
" %%v%d = add %s 0, %d\n",
instruction_id,
llvm_type(instruction.type),
sentinel(instruction.type),
)
}
}
emit_call_args :: proc(builder: ^strings.Builder, instructions: []ir.Instruction, args: []int, param_types: []types.Type) {
for arg, index in args {
if index > 0 {
strings.write_string(builder, ", ")
}
fmt.sbprintf(builder, "%s ", llvm_type(instructions[arg].type))
write_operand(builder, instructions, arg)
fmt.sbprintf(builder, "%s ", llvm_type(param_types[index]))
write_operand(builder, instructions, arg, param_types[index])
}
}
@@ -122,11 +164,14 @@ emit_instruction_stream :: proc(
case .Param, .Const:
case .Load_Global:
if instruction.target < 0 || instruction.target >= len(emitter.module.globals) {
message := diagnostic_message(emitter, -1, instruction.span, "invalid global reference")
emit_trap_call(emitter, message)
emit_recovery_value(emitter, instruction_id, instruction, "invalid global reference")
continue
}
global := emitter.module.globals[instruction.target]
if !types.equal(instruction.type, global.type) {
emit_recovery_value(emitter, instruction_id, instruction, "invalid global reference type")
continue
}
if global.is_static {
fmt.sbprintf(
&emitter.builder,
@@ -145,8 +190,18 @@ emit_instruction_stream :: proc(
)
}
case .Alloca:
if !types.is_concrete_integer(instruction.type) {
emit_recovery_value(emitter, instruction_id, instruction, "invalid allocation type")
continue
}
fmt.sbprintf(&emitter.builder, " %%v%d = alloca %s\n", instruction_id, llvm_type(instruction.type))
case .Load:
if !valid_instruction(instructions, instruction.a) ||
instructions[instruction.a].op != .Alloca ||
!types.equal(instructions[instruction.a].type, instruction.type) {
emit_recovery_value(emitter, instruction_id, instruction, "invalid load slot")
continue
}
fmt.sbprintf(
&emitter.builder,
" %%v%d = load %s, ptr %%v%d\n",
@@ -155,22 +210,41 @@ emit_instruction_stream :: proc(
instruction.a,
)
case .Store:
if !valid_instruction(instructions, instruction.a) ||
instructions[instruction.a].op != .Alloca ||
!types.equal(instructions[instruction.a].type, instruction.type) ||
!valid_value(instructions, instruction.b, instruction.type) {
emit_recovery_value(emitter, instruction_id, instruction, "invalid store operand")
continue
}
fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(instruction.type))
write_operand(&emitter.builder, instructions, instruction.b)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type)
fmt.sbprintf(&emitter.builder, ", ptr %%v%d\n", instruction.a)
case .Widen:
if !valid_instruction(instructions, instruction.a) ||
!types.is_concrete_integer(instructions[instruction.a].type) ||
!types.is_concrete_integer(instruction.type) ||
instructions[instruction.a].type.bits >= instruction.type.bits {
emit_recovery_value(emitter, instruction_id, instruction, "invalid widening operand")
continue
}
from_type := instructions[instruction.a].type
fmt.sbprintf(&emitter.builder, " %%v%d = sext %s ", instruction_id, llvm_type(from_type))
write_operand(&emitter.builder, instructions, instruction.a)
write_operand(&emitter.builder, instructions, instruction.a, from_type)
fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type))
case .Add_Checked:
if !valid_value(instructions, instruction.a, instruction.type) ||
!valid_value(instructions, instruction.b, instruction.type) {
emit_recovery_value(emitter, instruction_id, instruction, "invalid addition operand")
continue
}
type_name := llvm_type(instruction.type)
fmt.sbprintf(&emitter.builder, " %%pair%d = call ", instruction_id)
strings.write_string(&emitter.builder, "{ ")
fmt.sbprintf(&emitter.builder, "%s, i1 } @llvm.sadd.with.overflow.%s(%s ", type_name, type_name, type_name)
write_operand(&emitter.builder, instructions, instruction.a)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type)
fmt.sbprintf(&emitter.builder, ", %s ", type_name)
write_operand(&emitter.builder, instructions, instruction.b)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type)
fmt.sbprintf(&emitter.builder, ")\n")
fmt.sbprintf(&emitter.builder, " %%v%d = extractvalue ", instruction_id)
strings.write_string(&emitter.builder, "{ ")
@@ -191,11 +265,27 @@ emit_instruction_stream :: proc(
fmt.sbprintf(&emitter.builder, " unreachable\noverflow_continue%d:\n", instruction_id)
case .Call:
if instruction.target < 0 || instruction.target >= len(emitter.module.functions) {
message := diagnostic_message(emitter, -1, instruction.span, "invalid function specialization")
emit_trap_call(emitter, message)
emit_recovery_value(emitter, instruction_id, instruction, "invalid function specialization")
continue
}
target := emitter.module.functions[instruction.target]
valid_args := len(instruction.args) == len(target.param_types)
if valid_args {
for arg, index in instruction.args {
if !valid_value(instructions, arg, target.param_types[index]) {
valid_args = false
break
}
}
}
target_result := target.result
if target.is_main {
target_result = types.I32
}
if !valid_args || !types.equal(instruction.type, target_result) {
emit_recovery_value(emitter, instruction_id, instruction, "invalid function call operands")
continue
}
if instruction.type.kind != .Void {
fmt.sbprintf(&emitter.builder, " %%v%d = ", instruction_id)
} else {
@@ -206,7 +296,7 @@ emit_instruction_stream :: proc(
strings.write_string(&emitter.builder, "fastcc ")
}
fmt.sbprintf(&emitter.builder, "%s @%s(", function_result_type(target), target.link_name)
emit_call_args(&emitter.builder, instructions, instruction.args)
emit_call_args(&emitter.builder, instructions, instruction.args, target.param_types)
strings.write_string(&emitter.builder, ")\n")
case .Trap:
message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, "invalid recovered source")
@@ -217,7 +307,7 @@ emit_instruction_stream :: proc(
continue
}
fmt.sbprintf(&emitter.builder, " ret %s ", function_result_type(function))
write_operand(&emitter.builder, instructions, instruction.a)
write_operand(&emitter.builder, instructions, instruction.a, function.result)
strings.write_string(&emitter.builder, "\n")
after_return = true
case .Return_Void:
@@ -286,11 +376,11 @@ emit_global_accessors :: proc(emitter: ^Emitter) {
placeholder_function.result = global.type
value := emit_instruction_stream(emitter, global.initializer, placeholder_function, true)
fmt.sbprintf(&emitter.builder, " store %s ", type_name)
write_operand(&emitter.builder, global.initializer, value)
write_operand(&emitter.builder, global.initializer, value, global.type)
fmt.sbprintf(&emitter.builder, ", ptr @bro.g.%d\n", global_id)
fmt.sbprintf(&emitter.builder, " store i8 2, ptr @bro.gstate.%d\n", global_id)
fmt.sbprintf(&emitter.builder, " ret %s ", type_name)
write_operand(&emitter.builder, global.initializer, value)
write_operand(&emitter.builder, global.initializer, value, global.type)
strings.write_string(&emitter.builder, "\nready:\n")
fmt.sbprintf(&emitter.builder, " %%value = load %s, ptr @bro.g.%d\n ret %s %%value\n}\n\n", type_name, global_id, type_name)
}
@@ -383,7 +473,7 @@ emit_declarations :: proc(emitter: ^Emitter) {
}
strings.write_string(
&emitter.builder,
"\ndefine internal void @bro.trap(ptr %message, i64 %length) {\nentry:\n %written = call i64 @write(i32 2, ptr %message, i64 %length)\n call void @llvm.trap()\n unreachable\n}\n\n",
"\ndefine internal void @bro.trap(ptr %message, i64 %length) noreturn {\nentry:\n %written = call i64 @write(i32 2, ptr %message, i64 %length)\n call void @llvm.trap()\n unreachable\n}\n\n",
)
}