feat(selfhost): @[Checked] borrow checker — basic write-through-ptr detection
- Add isChecked field to Decl struct (ast.bux) - Parse @[Checked] attribute in parserParseDecl (parser.bux) - Add checkedFunc flag and movedVars to Sema struct (sema.bux) - Enable borrow checking per-function in Sema_Analyze - Reject assignment through raw pointer (*T) in @[Checked] functions - Skip newlines between @[Checked] attribute and the declaration keyword to avoid parser seeing the attribute and function as separate decls
This commit is contained in:
@@ -250,6 +250,7 @@ struct Decl {
|
||||
column: uint32,
|
||||
isPublic: bool,
|
||||
isAsync: bool,
|
||||
isChecked: int, // @[Checked] attribute (0/1)
|
||||
// Names
|
||||
strValue: String, // decl name
|
||||
strValue2: String, // interface name, dll name, module path
|
||||
|
||||
+30
-2
@@ -1361,14 +1361,42 @@ func parserParseDecl(p: *Parser) -> *Decl {
|
||||
discard parserAdvance(p);
|
||||
}
|
||||
let isPublic: bool = parserMatch(p, tkPub);
|
||||
|
||||
// Parse @[Checked] attribute
|
||||
var isChecked: int = 0;
|
||||
if parserCheck(p, tkAt) {
|
||||
discard parserAdvance(p); // @
|
||||
if parserCheck(p, tkLBracket) {
|
||||
discard parserAdvance(p); // [
|
||||
if parserCheck(p, tkIdent) {
|
||||
let attrName: LexToken = parserCurToken(p);
|
||||
if String_Eq(attrName.text, "Checked") {
|
||||
isChecked = 1;
|
||||
}
|
||||
discard parserAdvance(p); // attribute name
|
||||
}
|
||||
if parserCheck(p, tkRBracket) {
|
||||
discard parserAdvance(p); // ]
|
||||
}
|
||||
}
|
||||
// Skip newlines after attribute before the declaration
|
||||
while parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
|
||||
discard parserAdvance(p);
|
||||
}
|
||||
}
|
||||
|
||||
let kind: int = parserPeek(p, 0);
|
||||
|
||||
if kind == tkAsync && parserPeek(p, 1) == tkFunc {
|
||||
discard parserAdvance(p); // async
|
||||
return parserParseFuncDecl(p, isPublic, false, true);
|
||||
let d: *Decl = parserParseFuncDecl(p, isPublic, false, true);
|
||||
d.isChecked = isChecked;
|
||||
return d;
|
||||
}
|
||||
if kind == tkFunc {
|
||||
return parserParseFuncDecl(p, isPublic, false, false);
|
||||
let d: *Decl = parserParseFuncDecl(p, isPublic, false, false);
|
||||
d.isChecked = isChecked;
|
||||
return d;
|
||||
}
|
||||
if kind == tkStruct { return parserParseStructDecl(p, isPublic); }
|
||||
if kind == tkEnum { return parserParseEnumDecl(p, isPublic); }
|
||||
|
||||
+24
-1
@@ -14,6 +14,9 @@ struct Sema {
|
||||
diags: *SemaDiag;
|
||||
hasError: bool;
|
||||
currentRetType: int; // return type of the function being checked
|
||||
checkedFunc: bool; // true inside @[Checked] function
|
||||
movedCount: int; // number of moved variables
|
||||
movedNames: *String; // array of moved variable names
|
||||
}
|
||||
|
||||
struct SemaDiag {
|
||||
@@ -325,6 +328,14 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
|
||||
let right: int = Sema_CheckExpr(sema, expr.child2);
|
||||
let op: int = expr.intValue;
|
||||
|
||||
// Borrow check: reject assignment through deref in @[Checked] functions
|
||||
if sema.checkedFunc && op == tkAssign {
|
||||
if expr.child1 != null as *Expr && expr.child1.kind == ekUnary && expr.child1.intValue == tkStar {
|
||||
Sema_EmitError(sema, expr.line, expr.column,
|
||||
"cannot assign through raw pointer in checked function — use '&mut T' instead");
|
||||
}
|
||||
}
|
||||
|
||||
// Assignment operators return target type
|
||||
if op == tkAssign || (op >= tkPlusAssign && op <= tkShrAssign) {
|
||||
return left;
|
||||
@@ -360,7 +371,11 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
|
||||
if kind == ekAssign {
|
||||
let target: int = Sema_CheckExpr(sema, expr.child1);
|
||||
let value: int = Sema_CheckExpr(sema, expr.child2);
|
||||
// Basic type compatibility (permissive for now)
|
||||
// Borrow check: reject assignment through deref in @[Checked] functions
|
||||
if sema.checkedFunc && expr.child1 != null as *Expr && expr.child1.kind == ekUnary && expr.child1.intValue == tkStar {
|
||||
Sema_EmitError(sema, expr.line, expr.column,
|
||||
"cannot assign through raw pointer in checked function — use '&mut T' instead");
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
@@ -896,8 +911,15 @@ func Sema_Analyze(mod: *Module) -> *Sema {
|
||||
s.currentRetType = Sema_ResolveType(s, decl.retType);
|
||||
} else {
|
||||
s.currentRetType = tyVoid;
|
||||
s.checkedFunc = false;
|
||||
s.movedCount = 0;
|
||||
s.movedNames = bux_alloc(64 as uint * 256) as *String; // up to 64 moved var names
|
||||
}
|
||||
|
||||
// Enable borrow checking for @[Checked] functions
|
||||
let wasChecked: bool = s.checkedFunc;
|
||||
s.checkedFunc = decl.isChecked != 0;
|
||||
|
||||
// Check body statements
|
||||
var stmt: *Stmt = decl.refBody.firstStmt;
|
||||
while stmt != null as *Stmt {
|
||||
@@ -905,6 +927,7 @@ func Sema_Analyze(mod: *Module) -> *Sema {
|
||||
stmt = stmt.nextStmt;
|
||||
}
|
||||
|
||||
s.checkedFunc = wasChecked;
|
||||
s.scope = prevScope;
|
||||
}
|
||||
decl = decl.childDecl2;
|
||||
|
||||
Reference in New Issue
Block a user