feat(C3.1): #foreign refs are validated — must name a #library or a named #import c unit
validateForeignRefs walks the merged tree (libraries + named c units, nested namespaces included) and diagnoses any #foreign whose ref names neither — a typo'd ref previously compiled and resolved silently through whatever image carried the symbol. Decls synthesized from #include headers carry no ref and are exempt. Flips the C0.2b pin; zero collateral across the 608 other examples.
This commit is contained in:
@@ -1,9 +1,8 @@
|
|||||||
// Pins the C3 gap (PLAN-C C0.2b): the `#foreign` library/unit ref is
|
// A `#foreign` ref must name something real (PLAN-C C3.1): `nosuchunit`
|
||||||
// DECORATIVE today — `nosuchunit` names nothing anywhere, yet this
|
// names neither a #library constant nor a named `#import c` unit, so
|
||||||
// compiles and the symbol resolves globally (the unit's objects are in
|
// this is a compile-time diagnostic — a typo'd ref previously compiled
|
||||||
// the program; the ref is never consulted). After C3 this is a
|
// and resolved silently through whatever image carried the symbol.
|
||||||
// compile-time diagnostic: a #foreign ref must name a #library or a
|
// Regression (PLAN-C C0.2b xfail, flipped by C3.1).
|
||||||
// named `#import c` unit.
|
|
||||||
#import "modules/std.sx";
|
#import "modules/std.sx";
|
||||||
|
|
||||||
refs :: #import c {
|
refs :: #import c {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
0
|
1
|
||||||
|
|||||||
@@ -1 +1,5 @@
|
|||||||
|
error: #foreign library 'nosuchunit' is not declared; expected a #library constant or a named '#import c' unit
|
||||||
|
--> examples/1620-cimport-foreign-ref-unvalidated.sx:12:1
|
||||||
|
|
|
||||||
|
12 | ref_answer :: () -> i32 #foreign nosuchunit "ref_answer";
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
ref = 7
|
|
||||||
|
|||||||
@@ -235,6 +235,58 @@ pub fn processCImport(
|
|||||||
// Native C compilation (compile to .o, not LLVM module)
|
// Native C compilation (compile to .o, not LLVM module)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// ── #foreign ref validation ───────────────────────────────────────────
|
||||||
|
|
||||||
|
fn collectForeignRefTargets(valid: *std.StringHashMap(void), decls: []const *Node) !void {
|
||||||
|
for (decls) |d| {
|
||||||
|
switch (d.data) {
|
||||||
|
.library_decl => |ld| try valid.put(ld.name, {}),
|
||||||
|
.namespace_decl => |ns| {
|
||||||
|
// A NAMED `#import c` unit lowers to a namespace wrapping
|
||||||
|
// its c_import_decl — the namespace name is the unit ref.
|
||||||
|
for (ns.decls) |nd| {
|
||||||
|
if (nd.data == .c_import_decl) {
|
||||||
|
try valid.put(ns.name, {});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try collectForeignRefTargets(valid, ns.decls);
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checkForeignRefs(valid: *const std.StringHashMap(void), decls: []const *Node, diags: *@import("errors.zig").DiagnosticList) void {
|
||||||
|
for (decls) |d| {
|
||||||
|
switch (d.data) {
|
||||||
|
.fn_decl => |fd| {
|
||||||
|
if (fd.body.data != .foreign_expr) continue;
|
||||||
|
const ref = fd.body.data.foreign_expr.library_ref orelse continue;
|
||||||
|
if (!valid.contains(ref)) {
|
||||||
|
diags.addFmt(.err, d.span, "#foreign library '{s}' is not declared; expected a #library constant or a named '#import c' unit", .{ref});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.namespace_decl => |ns| checkForeignRefs(valid, ns.decls, diags),
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validate every `#foreign <ref>` in the merged program: the ref must
|
||||||
|
/// name a `#library` constant or a NAMED `#import c` unit. Refs are
|
||||||
|
/// author-side module-local names, but modules merge flat or
|
||||||
|
/// namespaced into one tree, so existence is checked program-wide.
|
||||||
|
/// Decls synthesized from `#include` headers carry no ref and are
|
||||||
|
/// exempt.
|
||||||
|
pub fn validateForeignRefs(allocator: std.mem.Allocator, root: *const Node, diags: *@import("errors.zig").DiagnosticList) !void {
|
||||||
|
if (root.data != .root) return;
|
||||||
|
var valid = std.StringHashMap(void).init(allocator);
|
||||||
|
defer valid.deinit();
|
||||||
|
try collectForeignRefTargets(&valid, root.data.root.decls);
|
||||||
|
checkForeignRefs(&valid, root.data.root.decls, diags);
|
||||||
|
}
|
||||||
|
|
||||||
/// A cached entry must at least LOOK like an object file (Mach-O or
|
/// A cached entry must at least LOOK like an object file (Mach-O or
|
||||||
/// ELF magic) — a truncated or garbage entry falls back to a fresh
|
/// ELF magic) — a truncated or garbage entry falls back to a fresh
|
||||||
/// compile instead of poisoning the link with an opaque error.
|
/// compile instead of poisoning the link with an opaque error.
|
||||||
|
|||||||
@@ -298,6 +298,13 @@ pub const Compilation = struct {
|
|||||||
/// Lower the parsed AST to the sx IR module (shadow pipeline).
|
/// Lower the parsed AST to the sx IR module (shadow pipeline).
|
||||||
pub fn lowerToIR(self: *Compilation) !ir.Module {
|
pub fn lowerToIR(self: *Compilation) !ir.Module {
|
||||||
const root = self.resolved_root orelse self.root orelse return ir.Module.init(self.allocator);
|
const root = self.resolved_root orelse self.root orelse return ir.Module.init(self.allocator);
|
||||||
|
|
||||||
|
// Every `#foreign <ref>` must name a #library constant or a named
|
||||||
|
// `#import c` unit — a typo'd ref otherwise resolves silently
|
||||||
|
// through whatever image happens to carry the symbol.
|
||||||
|
try c_import.validateForeignRefs(self.allocator, root, &self.diagnostics);
|
||||||
|
if (self.diagnostics.hasErrors()) return error.CompileError;
|
||||||
|
|
||||||
var module = ir.Module.init(self.allocator);
|
var module = ir.Module.init(self.allocator);
|
||||||
//TODO: find a better place for this
|
//TODO: find a better place for this
|
||||||
if (self.target_config.isWasm32()) {
|
if (self.target_config.isWasm32()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user