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
+490 -319
View File
@@ -60,6 +60,11 @@ Checker :: struct {
global_types: []types.Type,
constants: []Constant,
template_diagnostics: []int,
constant_stack: [dynamic]Constant_Frame,
expr_stack: [dynamic]int,
infer_stack: [dynamic]Infer_Frame,
build_stack: [dynamic]Build_Expr_Frame,
cycle_stack: [dynamic]Cycle_Frame,
main_symbol: symbol.Id,
sink_symbol: symbol.Id,
allocator: mem.Allocator,
@@ -69,38 +74,75 @@ symbol_text :: proc(checker: ^Checker, id: symbol.Id) -> string {
return symbol.resolve(checker.symbols, id)
}
Constant_Frame :: struct {
expr: int,
stage: u8,
}
eval_constant :: proc(checker: ^Checker, expr_id: int) -> Constant {
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
return Constant{kind = .Not_Constant}
}
if checker.constants[expr_id].kind != .Unknown {
return checker.constants[expr_id]
stack := checker.constant_stack
clear_dynamic_array(&stack)
defer {
clear_dynamic_array(&stack)
checker.constant_stack = stack
}
expr := checker.ast_module.exprs[expr_id]
result: Constant
switch expr.kind {
case .Integer:
result = Constant{kind = .Value, value = i128(expr.integer)}
case .Add:
left := eval_constant(checker, expr.left)
right := eval_constant(checker, expr.right)
append(&stack, Constant_Frame{expr=expr_id})
for len(stack) > 0 {
frame_index := len(stack)-1
frame := stack[frame_index]
if checker.constants[frame.expr].kind != .Unknown {
_ = pop(&stack)
continue
}
expr := checker.ast_module.exprs[frame.expr]
if expr.kind != .Add {
result := Constant{kind = .Not_Constant}
if expr.kind == .Integer {
result = Constant{kind = .Value, value = i128(expr.integer)}
}
checker.constants[frame.expr] = result
_ = pop(&stack)
continue
}
if frame.stage == 0 {
stack[frame_index].stage = 1
if expr.left >= 0 && expr.left < len(checker.ast_module.exprs) &&
checker.constants[expr.left].kind == .Unknown {
append(&stack, Constant_Frame{expr=expr.left})
}
continue
}
if frame.stage == 1 {
stack[frame_index].stage = 2
if expr.right >= 0 && expr.right < len(checker.ast_module.exprs) &&
checker.constants[expr.right].kind == .Unknown {
append(&stack, Constant_Frame{expr=expr.right})
}
continue
}
left := Constant{kind = .Not_Constant}
right := Constant{kind = .Not_Constant}
if expr.left >= 0 && expr.left < len(checker.constants) {
left = checker.constants[expr.left]
}
if expr.right >= 0 && expr.right < len(checker.constants) {
right = checker.constants[expr.right]
}
result := Constant{kind = .Not_Constant}
if left.kind == .Overflow || right.kind == .Overflow {
result = Constant{kind = .Overflow}
} else if left.kind != .Value || right.kind != .Value {
result = Constant{kind = .Not_Constant}
} else {
} else if left.kind == .Value && right.kind == .Value {
value, overflow := intrinsics.overflow_add(left.value, right.value)
if overflow {
result = Constant{kind = .Overflow}
} else {
result = Constant{kind = .Value, value = value}
}
result = Constant{kind = .Overflow} if overflow else Constant{kind = .Value, value = value}
}
case .Invalid, .Name, .Call:
result = Constant{kind = .Not_Constant}
checker.constants[frame.expr] = result
_ = pop(&stack)
}
checker.constants[expr_id] = result
return result
return checker.constants[expr_id]
}
fits_signed_type :: proc(value: i128, target: types.Type) -> bool {
@@ -264,26 +306,29 @@ contains_name :: proc(names: []symbol.Id, name: symbol.Id) -> bool {
}
mark_expr_imports_used :: proc(checker: ^Checker, expr_id, file: int) {
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
return
stack := checker.expr_stack
clear_dynamic_array(&stack)
defer {
clear_dynamic_array(&stack)
checker.expr_stack = stack
}
expr := checker.ast_module.exprs[expr_id]
switch expr.kind {
case .Name:
if symbol.is_valid(expr.qualifier) {
append(&stack, expr_id)
for len(stack) > 0 {
id := pop(&stack)
if id < 0 || id >= len(checker.ast_module.exprs) {
continue
}
expr := checker.ast_module.exprs[id]
if (expr.kind == .Name || expr.kind == .Call) && symbol.is_valid(expr.qualifier) {
_ = find_import(checker, file, expr.qualifier, true)
}
case .Call:
if symbol.is_valid(expr.qualifier) {
_ = find_import(checker, file, expr.qualifier, true)
switch expr.kind {
case .Call:
append(&stack, ..expr.args)
case .Add:
append(&stack, expr.left, expr.right)
case .Invalid, .Integer, .Name:
}
for arg in expr.args {
mark_expr_imports_used(checker, arg, file)
}
case .Add:
mark_expr_imports_used(checker, expr.left, file)
mark_expr_imports_used(checker, expr.right, file)
case .Invalid, .Integer:
}
}
@@ -445,80 +490,138 @@ ensure_spec :: proc(checker: ^Checker, template: int, actual_args: []types.Type)
return index
}
Infer_Frame :: struct {
expr: int,
stage: u8,
left: types.Type,
arg_index: int,
args: []types.Type,
template: int,
}
infer_expr :: proc(checker: ^Checker, expr_id: int, locals: []Infer_Local, pkg := 0, file := 0) -> types.Type {
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
return types.INVALID
stack := checker.infer_stack
clear_dynamic_array(&stack)
defer {
for frame in stack {
delete(frame.args, checker.allocator)
}
clear_dynamic_array(&stack)
checker.infer_stack = stack
}
constant := eval_constant(checker, expr_id)
if constant.kind == .Overflow || (constant.kind == .Value && !fits_i64(constant.value)) {
return types.I64
append(&stack, Infer_Frame{expr=expr_id, template=-1})
last := types.INVALID
for len(stack) > 0 {
frame_index := len(stack)-1
frame := stack[frame_index]
if frame.expr < 0 || frame.expr >= len(checker.ast_module.exprs) {
last = types.INVALID
_ = pop(&stack)
continue
}
expr := checker.ast_module.exprs[frame.expr]
if frame.stage == 0 {
constant := eval_constant(checker, frame.expr)
if constant.kind == .Overflow || (constant.kind == .Value && !fits_i64(constant.value)) {
last = types.I64
_ = pop(&stack)
continue
}
if constant.kind == .Value {
last = types.smallest_signed_for_literal(i64(constant.value))
_ = pop(&stack)
continue
}
switch expr.kind {
case .Invalid:
last = types.INVALID
_ = pop(&stack)
case .Integer:
last = types.smallest_signed_for_literal(expr.integer)
_ = pop(&stack)
case .Name:
last = types.INVALID
if !symbol.is_valid(expr.qualifier) {
last = find_infer_local(locals, expr.name)
}
if !types.is_valid(last) {
target_pkg, available := expr_package(checker, expr, pkg, file)
if available {
global := find_global(checker, expr.name, target_pkg)
if global >= 0 {
last = checker.global_types[global]
}
}
}
_ = pop(&stack)
case .Add:
stack[frame_index].stage = 1
append(&stack, Infer_Frame{expr=expr.left, template=-1})
case .Call:
target_pkg, available := expr_package(checker, expr, pkg, file)
template := -1
if available {
template = find_template(checker, expr.name, target_pkg)
}
if template < 0 {
last = types.INVALID
_ = pop(&stack)
continue
}
if checker.template_diagnostics[template] >= 0 {
declared := type_from_syntax(checker.ast_module.functions[template].result)
last = declared if declared.kind == .Concrete || declared.kind == .Void else types.INVALID
_ = pop(&stack)
continue
}
stack[frame_index].template = template
stack[frame_index].args = make([]types.Type, len(expr.args), checker.allocator)
stack[frame_index].stage = 3
if len(expr.args) > 0 {
append(&stack, Infer_Frame{expr=expr.args[0], template=-1})
}
}
continue
}
if frame.stage == 1 {
stack[frame_index].left = last
stack[frame_index].stage = 2
append(&stack, Infer_Frame{expr=expr.right, template=-1})
continue
}
if frame.stage == 2 {
last = types.widest(frame.left, last)
_ = pop(&stack)
continue
}
if frame.stage == 3 {
if frame.arg_index < len(expr.args) {
stack[frame_index].args[frame.arg_index] = last
stack[frame_index].arg_index += 1
if frame.arg_index+1 < len(expr.args) {
append(&stack, Infer_Frame{expr=expr.args[frame.arg_index+1], template=-1})
continue
}
}
function := checker.ast_module.functions[frame.template]
if can_specialize(function, stack[frame_index].args) {
spec := ensure_spec(checker, frame.template, stack[frame_index].args)
last = checker.specs[spec].result
} else {
declared := type_from_syntax(function.result)
if function.pkg == 0 && function.name == checker.main_symbol && function.result == .Int {
last = types.I32
} else {
last = declared if declared.kind == .Concrete || declared.kind == .Void else types.INVALID
}
}
delete(stack[frame_index].args, checker.allocator)
stack[frame_index].args = nil
_ = pop(&stack)
}
}
if constant.kind == .Value {
return types.smallest_signed_for_literal(i64(constant.value))
}
expr := checker.ast_module.exprs[expr_id]
switch expr.kind {
case .Invalid:
return types.INVALID
case .Integer:
return types.smallest_signed_for_literal(expr.integer)
case .Name:
if !symbol.is_valid(expr.qualifier) {
local_type := find_infer_local(locals, expr.name)
if types.is_valid(local_type) {
return local_type
}
}
target_pkg, available := expr_package(checker, expr, pkg, file)
if !available {
return types.INVALID
}
global := find_global(checker, expr.name, target_pkg)
if global >= 0 {
return checker.global_types[global]
}
return types.INVALID
case .Add:
left := infer_expr(checker, expr.left, locals, pkg, file)
right := infer_expr(checker, expr.right, locals, pkg, file)
return types.widest(left, right)
case .Call:
target_pkg, available := expr_package(checker, expr, pkg, file)
if !available {
return types.INVALID
}
template := find_template(checker, expr.name, target_pkg)
if template < 0 {
return types.INVALID
}
if checker.template_diagnostics[template] >= 0 {
declared := type_from_syntax(checker.ast_module.functions[template].result)
if declared.kind == .Concrete || declared.kind == .Void {
return declared
}
return types.INVALID
}
args := make([]types.Type, len(expr.args), checker.allocator)
for arg, index in expr.args {
args[index] = infer_expr(checker, arg, locals, pkg, file)
}
function := checker.ast_module.functions[template]
if !can_specialize(function, args) {
delete(args, checker.allocator)
declared := type_from_syntax(function.result)
if function.pkg == 0 && function.name == checker.main_symbol && function.result == .Int {
return types.I32
}
if declared.kind == .Concrete || declared.kind == .Void {
return declared
}
return types.INVALID
}
spec := ensure_spec(checker, template, args)
delete(args, checker.allocator)
return checker.specs[spec].result
}
return types.INVALID
return last
}
infer_spec_result :: proc(checker: ^Checker, spec_id: int) -> types.Type {
@@ -752,6 +855,17 @@ build_constant_expr :: proc(
)
}
Build_Expr_Frame :: struct {
expr: int,
expected: types.Type,
stage: u8,
left: int,
arg_index: int,
built_args: []int,
arg_types: []types.Type,
template: int,
}
build_expr :: proc(
checker: ^Checker,
expr_id: int,
@@ -762,173 +876,190 @@ build_expr :: proc(
pkg := 0,
file := 0,
) -> int {
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
id := source.add(checker.diagnostics, source.Span{}, "missing expression")
return invalid_hir_expr(checker, source.Span{}, id)
stack := checker.build_stack
clear_dynamic_array(&stack)
defer {
for frame in stack {
delete(frame.built_args, checker.allocator)
delete(frame.arg_types, checker.allocator)
}
clear_dynamic_array(&stack)
checker.build_stack = stack
}
expr := checker.ast_module.exprs[expr_id]
constant := eval_constant(checker, expr_id)
if constant.kind == .Value || constant.kind == .Overflow {
return build_constant_expr(checker, expr, constant, expected)
}
switch expr.kind {
case .Invalid:
return invalid_hir_expr(checker, expr.span, expr.diagnostic)
case .Integer:
unreachable()
case .Name:
if !symbol.is_valid(expr.qualifier) {
if local, ok := find_build_local(locals, expr.name); ok {
return add_hir_expr(
append(&stack, Build_Expr_Frame{expr=expr_id, expected=expected, template=-1})
last := -1
for len(stack) > 0 {
frame_index := len(stack)-1
frame := stack[frame_index]
if frame.expr < 0 || frame.expr >= len(checker.ast_module.exprs) {
id := source.add(checker.diagnostics, source.Span{}, "missing expression")
last = invalid_hir_expr(checker, source.Span{}, id)
_ = pop(&stack)
continue
}
expr := checker.ast_module.exprs[frame.expr]
if frame.stage == 0 {
constant := eval_constant(checker, frame.expr)
if constant.kind == .Value || constant.kind == .Overflow {
last = build_constant_expr(checker, expr, constant, frame.expected)
_ = pop(&stack)
continue
}
switch expr.kind {
case .Invalid, .Integer:
last = invalid_hir_expr(checker, expr.span, expr.diagnostic)
_ = pop(&stack)
case .Name:
last = -1
if !symbol.is_valid(expr.qualifier) {
if local, ok := find_build_local(locals, expr.name); ok {
last = add_hir_expr(checker, hir.Expr{
kind=.Local, span=expr.span, type=local.type, target=local.id,
left=-1, right=-1, diagnostic=-1,
})
}
}
if last < 0 {
target_pkg, available := expr_package(checker, expr, pkg, file, true)
if !available {
id := add_package_resolution_diagnostic(checker, expr, file)
last = invalid_hir_expr(checker, expr.span, id)
} else if global := find_global(checker, expr.name, target_pkg); global >= 0 {
add_unique(global_reads, global)
last = add_hir_expr(checker, hir.Expr{
kind=.Global, span=expr.span, type=checker.global_types[global],
target=global, left=-1, right=-1, diagnostic=-1,
})
} else {
id := add_name_resolution_diagnostic(checker, expr, target_pkg)
last = invalid_hir_expr(checker, expr.span, id)
}
}
_ = pop(&stack)
case .Add:
stack[frame_index].stage = 1
append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=-1})
case .Call:
target_pkg, available := expr_package(checker, expr, pkg, file, true)
if !available {
id := add_package_resolution_diagnostic(checker, expr, file)
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
template := find_template(checker, expr.name, target_pkg)
if template < 0 {
id := add_call_resolution_diagnostic(checker, expr, target_pkg)
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
if checker.template_diagnostics[template] >= 0 {
last = invalid_hir_expr(checker, expr.span, checker.template_diagnostics[template])
_ = pop(&stack)
continue
}
if len(expr.args) != len(checker.ast_module.functions[template].params) {
id := source.addf(
checker.diagnostics,
expr.span,
"function '%s' expects %d arguments, got %d",
symbol_text(checker, expr.name),
len(checker.ast_module.functions[template].params),
len(expr.args),
)
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
stack[frame_index].template = template
stack[frame_index].built_args = make([]int, len(expr.args), checker.allocator)
stack[frame_index].arg_types = make([]types.Type, len(expr.args), checker.allocator)
stack[frame_index].stage = 3
if len(expr.args) > 0 {
arg_expected := type_from_syntax(checker.ast_module.functions[template].params[0].type)
if arg_expected.kind != .Concrete {
arg_expected = types.INVALID
}
append(&stack, Build_Expr_Frame{expr=expr.args[0], expected=arg_expected, template=-1})
}
}
continue
}
if frame.stage == 1 {
stack[frame_index].left = last
stack[frame_index].stage = 2
append(&stack, Build_Expr_Frame{expr=expr.right, expected=types.INVALID, template=-1})
continue
}
if frame.stage == 2 {
left := frame.left
right := last
result := types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
if !types.is_signed(result) {
id := source.add(checker.diagnostics, expr.span, "addition requires compatible signed integers")
last = invalid_hir_expr(checker, expr.span, id)
} else {
left = coerce_expr(checker, left, result, checker.module.exprs[left].span)
right = coerce_expr(checker, right, result, checker.module.exprs[right].span)
last = add_hir_expr(checker, hir.Expr{
kind=.Add, span=expr.span, type=result, left=left, right=right,
target=-1, diagnostic=-1,
})
}
_ = pop(&stack)
continue
}
if frame.stage == 3 {
if frame.arg_index < len(expr.args) {
stack[frame_index].built_args[frame.arg_index] = last
stack[frame_index].arg_types[frame.arg_index] = checker.module.exprs[last].type
stack[frame_index].arg_index += 1
if frame.arg_index+1 < len(expr.args) {
next := frame.arg_index+1
arg_expected := type_from_syntax(checker.ast_module.functions[frame.template].params[next].type)
if arg_expected.kind != .Concrete {
arg_expected = types.INVALID
}
append(&stack, Build_Expr_Frame{expr=expr.args[next], expected=arg_expected, template=-1})
continue
}
}
spec := ensure_spec(checker, frame.template, stack[frame_index].arg_types)
delete(stack[frame_index].arg_types, checker.allocator)
stack[frame_index].arg_types = nil
for _, index in stack[frame_index].built_args {
stack[frame_index].built_args[index] = coerce_expr(
checker,
hir.Expr {
kind = .Local,
span = expr.span,
type = local.type,
target = local.id,
left = -1,
right = -1,
diagnostic = -1,
},
stack[frame_index].built_args[index],
checker.specs[spec].args[index],
checker.module.exprs[stack[frame_index].built_args[index]].span,
)
}
}
target_pkg, available := expr_package(checker, expr, pkg, file, true)
if !available {
id := add_package_resolution_diagnostic(checker, expr, file)
return invalid_hir_expr(checker, expr.span, id)
}
global := find_global(checker, expr.name, target_pkg)
if global >= 0 {
add_unique(global_reads, global)
return add_hir_expr(
checker,
hir.Expr {
kind = .Global,
span = expr.span,
type = checker.global_types[global],
target = global,
left = -1,
right = -1,
diagnostic = -1,
},
)
}
id := add_name_resolution_diagnostic(checker, expr, target_pkg)
return invalid_hir_expr(checker, expr.span, id)
case .Add:
left := build_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
right := build_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
left_type := checker.module.exprs[left].type
right_type := checker.module.exprs[right].type
result := types.widest(left_type, right_type)
if !types.is_signed(result) {
id := source.add(
checker.diagnostics,
expr.span,
"addition requires compatible signed integers",
)
return invalid_hir_expr(checker, expr.span, id)
}
left = coerce_expr(checker, left, result, checker.module.exprs[left].span)
right = coerce_expr(checker, right, result, checker.module.exprs[right].span)
return add_hir_expr(
checker,
hir.Expr {
kind = .Add,
span = expr.span,
type = result,
left = left,
right = right,
target = -1,
diagnostic = -1,
},
)
case .Call:
target_pkg, available := expr_package(checker, expr, pkg, file, true)
if !available {
id := add_package_resolution_diagnostic(checker, expr, file)
return invalid_hir_expr(checker, expr.span, id)
}
template := find_template(checker, expr.name, target_pkg)
if template < 0 {
id := add_call_resolution_diagnostic(checker, expr, target_pkg)
return invalid_hir_expr(checker, expr.span, id)
}
if checker.template_diagnostics[template] >= 0 {
return invalid_hir_expr(checker, expr.span, checker.template_diagnostics[template])
}
if len(expr.args) != len(checker.ast_module.functions[template].params) {
id := source.addf(
checker.diagnostics,
expr.span,
"function '%s' expects %d arguments, got %d",
symbol_text(checker, expr.name),
len(checker.ast_module.functions[template].params),
len(expr.args),
)
return invalid_hir_expr(checker, expr.span, id)
}
built_args := make([]int, len(expr.args), checker.allocator)
arg_types := make([]types.Type, len(expr.args), checker.allocator)
for arg, index in expr.args {
arg_expected := type_from_syntax(checker.ast_module.functions[template].params[index].type)
if arg_expected.kind != .Concrete {
arg_expected = types.INVALID
add_unique(calls, spec)
result := checker.specs[spec].result
if !types.is_valid(result) {
id := source.addf(
checker.diagnostics,
expr.span,
"could not resolve result type for specialization of '%s'",
symbol_text(checker, expr.name),
)
delete(stack[frame_index].built_args, checker.allocator)
stack[frame_index].built_args = nil
last = invalid_hir_expr(checker, expr.span, id)
} else {
last = add_hir_expr(checker, hir.Expr{
kind=.Call, span=expr.span, type=result, target=spec,
left=-1, right=-1, args=stack[frame_index].built_args, diagnostic=-1,
})
stack[frame_index].built_args = nil
}
built_args[index] = build_expr(
checker,
arg,
locals,
global_reads,
calls,
arg_expected,
pkg,
file,
)
arg_types[index] = checker.module.exprs[built_args[index]].type
_ = pop(&stack)
}
spec := ensure_spec(checker, template, arg_types)
delete(arg_types, checker.allocator)
for _, index in built_args {
built_args[index] = coerce_expr(
checker,
built_args[index],
checker.specs[spec].args[index],
checker.module.exprs[built_args[index]].span,
)
}
add_unique(calls, spec)
result := checker.specs[spec].result
if !types.is_valid(result) {
id := source.addf(
checker.diagnostics,
expr.span,
"could not resolve result type for specialization of '%s'",
symbol_text(checker, expr.name),
)
delete(built_args, checker.allocator)
return invalid_hir_expr(checker, expr.span, id)
}
return add_hir_expr(
checker,
hir.Expr {
kind = .Call,
span = expr.span,
type = result,
target = spec,
left = -1,
right = -1,
args = built_args,
diagnostic = -1,
},
)
}
return invalid_hir_expr(
checker,
expr.span,
source.add(checker.diagnostics, expr.span, "invalid expression"),
)
return last
}
make_link_name :: proc(checker: ^Checker, spec_id: int) -> string {
@@ -1022,7 +1153,7 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
result = spec.result,
locals = hir_locals[:],
body = body[:],
direct_global_reads = global_reads[:],
direct_global_reads = global_reads,
calls = calls[:],
problematic = problematic,
diagnostic = checker.template_diagnostics[spec.template],
@@ -1387,7 +1518,7 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
result = spec.result,
locals = hir_locals[:],
body = body[:],
direct_global_reads = global_reads[:],
direct_global_reads = global_reads,
calls = calls[:],
problematic = problematic,
diagnostic = -1,
@@ -1396,24 +1527,31 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
delete(locals)
}
expr_problematic :: proc(module: ^hir.Module, expr_id: int) -> bool {
if expr_id < 0 || expr_id >= len(module.exprs) {
return true
expr_problematic :: proc(checker: ^Checker, expr_id: int) -> bool {
module := &checker.module
stack := checker.expr_stack
clear_dynamic_array(&stack)
defer {
clear_dynamic_array(&stack)
checker.expr_stack = stack
}
expr := module.exprs[expr_id]
if expr.kind == .Invalid {
return true
}
if expr.left >= 0 && expr_problematic(module, expr.left) {
return true
}
if expr.right >= 0 && expr_problematic(module, expr.right) {
return true
}
for arg in expr.args {
if expr_problematic(module, arg) {
append(&stack, expr_id)
for len(stack) > 0 {
id := pop(&stack)
if id < 0 || id >= len(module.exprs) {
return true
}
expr := module.exprs[id]
if expr.kind == .Invalid {
return true
}
if expr.left >= 0 {
append(&stack, expr.left)
}
if expr.right >= 0 {
append(&stack, expr.right)
}
append(&stack, ..expr.args)
}
return false
}
@@ -1477,10 +1615,10 @@ build_globals :: proc(checker: ^Checker) {
expr = expr,
static_value = static_value,
is_static = is_static,
dependencies = dependencies[:],
dependencies = dependencies,
calls = calls[:],
direct_problem = expr_problematic(&checker.module, expr),
problematic = expr_problematic(&checker.module, expr),
direct_problem = expr_problematic(checker, expr),
problematic = expr_problematic(checker, expr),
diagnostic = diagnostic,
},
)
@@ -1544,17 +1682,13 @@ resolve_call_targets :: proc(checker: ^Checker) {
}
}
append_unique_slice :: proc(values: ^[]int, value: int, allocator: mem.Allocator) -> bool {
append_unique_slice :: proc(values: ^[dynamic]int, value: int) -> bool {
for existing in values^ {
if existing == value {
return false
}
}
replacement := make([]int, len(values^) + 1, allocator)
copy(replacement, values^)
replacement[len(values^)] = value
delete(values^, allocator)
values^ = replacement
append(values, value)
return true
}
@@ -1572,11 +1706,7 @@ propagate_global_reads :: proc(checker: ^Checker) {
continue
}
for global_id in checker.module.functions[callee].direct_global_reads {
if append_unique_slice(
&function.direct_global_reads,
global_id,
checker.allocator,
) {
if append_unique_slice(&function.direct_global_reads, global_id) {
changed = true
}
}
@@ -1593,37 +1723,68 @@ propagate_global_reads :: proc(checker: ^Checker) {
continue
}
for dependency in checker.module.functions[function_id].direct_global_reads {
_ = append_unique_slice(&global.dependencies, dependency, checker.allocator)
_ = append_unique_slice(&global.dependencies, dependency)
}
}
}
}
Cycle_Frame :: struct {
global: int,
next_dependency: int,
}
detect_global_cycles_visit :: proc(checker: ^Checker, global_id: int, states: []u8) {
if states[global_id] == 2 {
return
}
if states[global_id] == 1 {
id := source.addf(
checker.diagnostics,
checker.ast_module.globals[global_id].span,
"global initialization cycle involving '%s'",
symbol_text(checker, checker.module.globals[global_id].name),
)
checker.module.globals[global_id].diagnostic = id
checker.module.globals[global_id].problematic = true
return
stack := checker.cycle_stack
clear_dynamic_array(&stack)
defer {
clear_dynamic_array(&stack)
checker.cycle_stack = stack
}
states[global_id] = 1
for dependency in checker.module.globals[global_id].dependencies {
if dependency >= 0 && dependency < len(states) {
detect_global_cycles_visit(checker, dependency, states)
if checker.module.globals[dependency].problematic {
checker.module.globals[global_id].problematic = true
}
append(&stack, Cycle_Frame{global=global_id})
for len(stack) > 0 {
frame_index := len(stack)-1
frame := &stack[frame_index]
if states[frame.global] == 0 {
states[frame.global] = 1
}
dependencies := checker.module.globals[frame.global].dependencies
if frame.next_dependency >= len(dependencies) {
states[frame.global] = 2
if checker.module.globals[frame.global].problematic && frame_index > 0 {
checker.module.globals[stack[frame_index-1].global].problematic = true
}
_ = pop(&stack)
continue
}
dependency := dependencies[frame.next_dependency]
frame.next_dependency += 1
if dependency < 0 || dependency >= len(states) {
continue
}
if states[dependency] == 1 {
id := source.addf(
checker.diagnostics,
checker.ast_module.globals[dependency].span,
"global initialization cycle involving '%s'",
symbol_text(checker, checker.module.globals[dependency].name),
)
checker.module.globals[dependency].diagnostic = id
checker.module.globals[dependency].problematic = true
checker.module.globals[frame.global].problematic = true
continue
}
if states[dependency] == 2 {
if checker.module.globals[dependency].problematic {
checker.module.globals[frame.global].problematic = true
}
continue
}
append(&stack, Cycle_Frame{global=dependency})
}
states[global_id] = 2
}
synthesize_trap_main :: proc(checker: ^Checker) {
@@ -1700,6 +1861,11 @@ check :: proc(
allocator = allocator,
}
checker.specs.allocator = allocator
checker.constant_stack.allocator = allocator
checker.expr_stack.allocator = allocator
checker.infer_stack.allocator = allocator
checker.build_stack.allocator = allocator
checker.cycle_stack.allocator = allocator
build_symbol_indexes(&checker)
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
checker.constants = make([]Constant, len(ast_module.exprs), allocator)
@@ -1718,6 +1884,11 @@ check :: proc(
delete(checker.global_types, allocator)
delete(checker.constants, allocator)
delete(checker.template_diagnostics, allocator)
delete(checker.constant_stack)
delete(checker.expr_stack)
delete(checker.infer_stack)
delete(checker.build_stack)
delete(checker.cycle_stack)
}
for function, index in ast_module.functions {