restricted c header imports
This commit is contained in:
+236
-3
@@ -1,11 +1,14 @@
|
||||
package loader
|
||||
|
||||
import "../ast"
|
||||
import "../cimport"
|
||||
import "../lexer"
|
||||
import "../parser"
|
||||
import "../source"
|
||||
import "../symbol"
|
||||
import "../target"
|
||||
import "../types"
|
||||
import "core:fmt"
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
import "core:path/filepath"
|
||||
@@ -19,6 +22,10 @@ State :: struct {
|
||||
symbols: ^symbol.Table,
|
||||
token_allocator: mem.Allocator,
|
||||
allocator: mem.Allocator,
|
||||
c_options: cimport.Options,
|
||||
selected: target.Target,
|
||||
record_identities: [dynamic]string,
|
||||
record_types: [dynamic]types.Type,
|
||||
root_failed: bool,
|
||||
}
|
||||
|
||||
@@ -104,6 +111,214 @@ resolve_import_path :: proc(state: ^State, importing_path, import_path: string)
|
||||
return joined, false
|
||||
}
|
||||
|
||||
header_package_name :: proc(path: string, symbols: ^symbol.Table) -> symbol.Id {
|
||||
base := filepath.base(path)
|
||||
extension := filepath.ext(base)
|
||||
if len(extension) > 0 {
|
||||
base = base[:len(base)-len(extension)]
|
||||
}
|
||||
return symbol.intern(symbols, base)
|
||||
}
|
||||
|
||||
find_record_identity :: proc(state: ^State, identity: string) -> types.Type {
|
||||
for existing, index in state.record_identities {
|
||||
if existing == identity {
|
||||
return state.record_types[index]
|
||||
}
|
||||
}
|
||||
return types.INVALID
|
||||
}
|
||||
|
||||
translate_c_type :: proc(
|
||||
state: ^State,
|
||||
result: ^cimport.Result,
|
||||
value: cimport.Type_Id,
|
||||
pkg: ast.Package_Id,
|
||||
record_mapping: []types.Type,
|
||||
type_mapping: []types.Type,
|
||||
) -> types.Type {
|
||||
if value == cimport.INVALID_TYPE || int(value) < 0 || int(value) >= len(result.types) {
|
||||
return types.INVALID
|
||||
}
|
||||
if types.is_valid(type_mapping[value]) {
|
||||
return type_mapping[value]
|
||||
}
|
||||
item := result.types[value]
|
||||
translated := types.INVALID
|
||||
switch item.kind {
|
||||
case .Invalid: translated = types.INVALID
|
||||
case .Void: translated = types.VOID
|
||||
case .C_Char: translated = types.C_CHAR
|
||||
case .C_Schar: translated = types.C_SCHAR
|
||||
case .C_Uchar: translated = types.C_UCHAR
|
||||
case .C_Short: translated = types.C_SHORT
|
||||
case .C_Ushort: translated = types.C_USHORT
|
||||
case .C_Int: translated = types.C_INT
|
||||
case .C_Uint: translated = types.C_UINT
|
||||
case .C_Long: translated = types.C_LONG
|
||||
case .C_Ulong: translated = types.C_ULONG
|
||||
case .C_Longlong: translated = types.C_LONGLONG
|
||||
case .C_Ulonglong: translated = types.C_ULONGLONG
|
||||
case .C_Float: translated = types.C_FLOAT
|
||||
case .C_Double: translated = types.C_DOUBLE
|
||||
case .C_Longdouble: translated = types.C_LONGDOUBLE
|
||||
case .Pointer:
|
||||
child := translate_c_type(state, result, item.child, pkg, record_mapping, type_mapping)
|
||||
if types.is_valid(child) {
|
||||
pointer := types.pointer(&state.module.type_store, child, item.mutable, true)
|
||||
translated = types.optional(&state.module.type_store, pointer)
|
||||
}
|
||||
case .Record:
|
||||
if int(item.record) < len(record_mapping) {
|
||||
translated = record_mapping[item.record]
|
||||
}
|
||||
}
|
||||
type_mapping[value] = translated
|
||||
return translated
|
||||
}
|
||||
|
||||
function_signatures_equal :: proc(left: ast.Function, params: []ast.Param, result: types.Type) -> bool {
|
||||
if left.result != result || len(left.params) != len(params) {
|
||||
return false
|
||||
}
|
||||
for param, index in params {
|
||||
if left.params[index].type != param.type {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
load_header :: proc(state: ^State, path: string, import_span: source.Span) -> ast.Package_Id {
|
||||
canonical, ok := filepath.abs(path, state.allocator)
|
||||
if !ok {
|
||||
id := add_placeholder(state, path)
|
||||
source.addf(state.diagnostics, import_span, "could not resolve C header '%s'", path)
|
||||
return id
|
||||
}
|
||||
if existing := find_package(state, canonical); existing != ast.INVALID_PACKAGE {
|
||||
delete(canonical, state.allocator)
|
||||
return existing
|
||||
}
|
||||
pkg_id := ast.package_id(len(state.module.packages))
|
||||
append(&state.module.packages, ast.Package{
|
||||
path=canonical,
|
||||
name=header_package_name(canonical, state.symbols),
|
||||
available=false,
|
||||
kind=.C_Header,
|
||||
})
|
||||
result := cimport.import_header(state.c_options, canonical, state.selected, state.allocator)
|
||||
defer cimport.destroy_result(&result)
|
||||
if !result.available {
|
||||
message := result.error_message if len(result.error_message) > 0 else "C header import failed"
|
||||
source.addf(state.diagnostics, import_span, "could not import C header '%s': %s", path, message)
|
||||
if result.infrastructure {
|
||||
state.root_failed = true
|
||||
}
|
||||
return pkg_id
|
||||
}
|
||||
state.module.packages[pkg_id].available = true
|
||||
|
||||
record_mapping := make([]types.Type, len(result.records), state.allocator)
|
||||
defer delete(record_mapping, state.allocator)
|
||||
for record, index in result.records {
|
||||
record_type := find_record_identity(state, record.identity)
|
||||
if !types.is_valid(record_type) {
|
||||
name := record.name
|
||||
if len(name) == 0 {
|
||||
name = fmt.tprintf("__c_record_%d", len(state.record_types))
|
||||
}
|
||||
record_type = types.named(&state.module.type_store, u32(pkg_id), u32(symbol.intern(state.symbols, name)))
|
||||
_ = types.define_struct(&state.module.type_store, record_type, nil, true, true)
|
||||
append(&state.record_identities, strings.clone(record.identity, state.allocator))
|
||||
append(&state.record_types, record_type)
|
||||
}
|
||||
record_mapping[index] = record_type
|
||||
}
|
||||
type_mapping := make([]types.Type, len(result.types), state.allocator)
|
||||
defer delete(type_mapping, state.allocator)
|
||||
|
||||
for alias in result.aliases {
|
||||
name := symbol.intern(state.symbols, alias.name)
|
||||
id := types.named(&state.module.type_store, u32(pkg_id), u32(name))
|
||||
child := translate_c_type(state, &result, alias.type, pkg_id, record_mapping, type_mapping)
|
||||
_ = types.define_alias(&state.module.type_store, id, child)
|
||||
if len(alias.reason) > 0 {
|
||||
append(&state.module.unsupported, ast.Unsupported{
|
||||
pkg=pkg_id,
|
||||
name=name,
|
||||
reason=strings.clone(alias.reason, state.allocator),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for function in result.functions {
|
||||
params := make([]ast.Param, len(function.params), state.allocator)
|
||||
for param_type, index in function.params {
|
||||
params[index] = ast.Param{
|
||||
name=symbol.intern(state.symbols, fmt.tprintf("arg%d", index)),
|
||||
span=import_span,
|
||||
type=translate_c_type(state, &result, param_type, pkg_id, record_mapping, type_mapping),
|
||||
}
|
||||
}
|
||||
function_result := translate_c_type(state, &result, function.result, pkg_id, record_mapping, type_mapping)
|
||||
unsupported_reason := function.reason
|
||||
if len(unsupported_reason) == 0 {
|
||||
for param in params {
|
||||
if types.contains_c_struct_by_value(param.type, &state.module.type_store) {
|
||||
unsupported_reason = "C records passed by value are not supported"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(unsupported_reason) == 0 &&
|
||||
types.contains_c_struct_by_value(function_result, &state.module.type_store) {
|
||||
unsupported_reason = "C records returned by value are not supported"
|
||||
}
|
||||
name := symbol.intern(state.symbols, function.name)
|
||||
duplicate := false
|
||||
for &existing in state.module.functions {
|
||||
if existing.pkg != pkg_id || existing.name != name {
|
||||
continue
|
||||
}
|
||||
duplicate = true
|
||||
if !function_signatures_equal(existing, params, function_result) && len(existing.unsupported_reason) == 0 {
|
||||
existing.unsupported_reason = fmt.aprintf(
|
||||
"conflicting C declarations for '%s'",
|
||||
function.name,
|
||||
allocator=state.allocator,
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
if duplicate {
|
||||
delete(params, state.allocator)
|
||||
continue
|
||||
}
|
||||
append(&state.module.functions, ast.Function{
|
||||
span=import_span,
|
||||
name=name,
|
||||
pkg=pkg_id,
|
||||
file=ast.INVALID_FILE,
|
||||
c_abi=true,
|
||||
imported=true,
|
||||
has_body=false,
|
||||
params=params,
|
||||
result=function_result,
|
||||
unsupported_reason=strings.clone(unsupported_reason, state.allocator),
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
for item in result.unsupported {
|
||||
append(&state.module.unsupported, ast.Unsupported{
|
||||
pkg=pkg_id,
|
||||
name=symbol.intern(state.symbols, item.name),
|
||||
reason=strings.clone(item.reason, state.allocator),
|
||||
})
|
||||
}
|
||||
return pkg_id
|
||||
}
|
||||
|
||||
load_package :: proc(state: ^State, path: string, import_span: source.Span, is_root := false) -> ast.Package_Id {
|
||||
canonical, ok := filepath.abs(path, state.allocator)
|
||||
if !ok || !os.is_dir(path) {
|
||||
@@ -191,7 +406,7 @@ load_package :: proc(state: ^State, path: string, import_span: source.Span, is_r
|
||||
continue
|
||||
}
|
||||
target_path, target_ok := resolve_import_path(state, canonical, import_item.path)
|
||||
target := load_package(state, target_path, import_item.span)
|
||||
target := load_header(state, target_path, import_item.span) if filepath.ext(import_item.path) == ".h" else load_package(state, target_path, import_item.span)
|
||||
state.module.imports[import_id].target = target
|
||||
if !target_ok || target == ast.INVALID_PACKAGE || !state.module.packages[target].available {
|
||||
state.module.imports[import_id].valid = false
|
||||
@@ -293,14 +508,19 @@ canonical_type :: proc(
|
||||
import_item := module.imports[import_id]
|
||||
resolved := types.find_named(&module.type_store, u32(import_item.target), item.name)
|
||||
if types.is_valid(resolved) {
|
||||
mapping[index] = resolved
|
||||
return resolved
|
||||
mapping[index] = canonical_type(module, resolved, mapping, visiting)
|
||||
return mapping[index]
|
||||
}
|
||||
}
|
||||
}
|
||||
mapping[index] = value
|
||||
return value
|
||||
}
|
||||
if item.kind == .Alias {
|
||||
resolved := canonical_type(module, item.child, mapping, visiting)
|
||||
mapping[index] = value if !types.is_valid(resolved) else resolved
|
||||
return mapping[index]
|
||||
}
|
||||
if item.kind == .Struct {
|
||||
mapping[index] = value
|
||||
fields := types.fields_for(&module.type_store, value)
|
||||
@@ -347,6 +567,8 @@ load :: proc(
|
||||
symbols: ^symbol.Table,
|
||||
token_allocator := context.allocator,
|
||||
allocator := context.allocator,
|
||||
c_options := cimport.Options{},
|
||||
selected := target.DEFAULT,
|
||||
) -> (ast.Module, bool) {
|
||||
module := ast.init_module(allocator)
|
||||
state := State{
|
||||
@@ -356,6 +578,17 @@ load :: proc(
|
||||
symbols=symbols,
|
||||
token_allocator=token_allocator,
|
||||
allocator=allocator,
|
||||
c_options=c_options,
|
||||
selected=selected,
|
||||
}
|
||||
state.record_identities.allocator = allocator
|
||||
state.record_types.allocator = allocator
|
||||
defer {
|
||||
for identity in state.record_identities {
|
||||
delete(identity, allocator)
|
||||
}
|
||||
delete(state.record_identities)
|
||||
delete(state.record_types)
|
||||
}
|
||||
root := load_package(&state, root_path, source.Span{}, true)
|
||||
if root != ast.Package_Id(0) && root != ast.INVALID_PACKAGE {
|
||||
|
||||
Reference in New Issue
Block a user