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,
|
column: uint32,
|
||||||
isPublic: bool,
|
isPublic: bool,
|
||||||
isAsync: bool,
|
isAsync: bool,
|
||||||
|
isChecked: int, // @[Checked] attribute (0/1)
|
||||||
// Names
|
// Names
|
||||||
strValue: String, // decl name
|
strValue: String, // decl name
|
||||||
strValue2: String, // interface name, dll name, module path
|
strValue2: String, // interface name, dll name, module path
|
||||||
|
|||||||
+30
-2
@@ -1361,14 +1361,42 @@ func parserParseDecl(p: *Parser) -> *Decl {
|
|||||||
discard parserAdvance(p);
|
discard parserAdvance(p);
|
||||||
}
|
}
|
||||||
let isPublic: bool = parserMatch(p, tkPub);
|
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);
|
let kind: int = parserPeek(p, 0);
|
||||||
|
|
||||||
if kind == tkAsync && parserPeek(p, 1) == tkFunc {
|
if kind == tkAsync && parserPeek(p, 1) == tkFunc {
|
||||||
discard parserAdvance(p); // async
|
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 {
|
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 == tkStruct { return parserParseStructDecl(p, isPublic); }
|
||||||
if kind == tkEnum { return parserParseEnumDecl(p, isPublic); }
|
if kind == tkEnum { return parserParseEnumDecl(p, isPublic); }
|
||||||
|
|||||||
+24
-1
@@ -14,6 +14,9 @@ struct Sema {
|
|||||||
diags: *SemaDiag;
|
diags: *SemaDiag;
|
||||||
hasError: bool;
|
hasError: bool;
|
||||||
currentRetType: int; // return type of the function being checked
|
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 {
|
struct SemaDiag {
|
||||||
@@ -325,6 +328,14 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
|
|||||||
let right: int = Sema_CheckExpr(sema, expr.child2);
|
let right: int = Sema_CheckExpr(sema, expr.child2);
|
||||||
let op: int = expr.intValue;
|
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
|
// Assignment operators return target type
|
||||||
if op == tkAssign || (op >= tkPlusAssign && op <= tkShrAssign) {
|
if op == tkAssign || (op >= tkPlusAssign && op <= tkShrAssign) {
|
||||||
return left;
|
return left;
|
||||||
@@ -360,7 +371,11 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
|
|||||||
if kind == ekAssign {
|
if kind == ekAssign {
|
||||||
let target: int = Sema_CheckExpr(sema, expr.child1);
|
let target: int = Sema_CheckExpr(sema, expr.child1);
|
||||||
let value: int = Sema_CheckExpr(sema, expr.child2);
|
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;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -896,8 +911,15 @@ func Sema_Analyze(mod: *Module) -> *Sema {
|
|||||||
s.currentRetType = Sema_ResolveType(s, decl.retType);
|
s.currentRetType = Sema_ResolveType(s, decl.retType);
|
||||||
} else {
|
} else {
|
||||||
s.currentRetType = tyVoid;
|
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
|
// Check body statements
|
||||||
var stmt: *Stmt = decl.refBody.firstStmt;
|
var stmt: *Stmt = decl.refBody.firstStmt;
|
||||||
while stmt != null as *Stmt {
|
while stmt != null as *Stmt {
|
||||||
@@ -905,6 +927,7 @@ func Sema_Analyze(mod: *Module) -> *Sema {
|
|||||||
stmt = stmt.nextStmt;
|
stmt = stmt.nextStmt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.checkedFunc = wasChecked;
|
||||||
s.scope = prevScope;
|
s.scope = prevScope;
|
||||||
}
|
}
|
||||||
decl = decl.childDecl2;
|
decl = decl.childDecl2;
|
||||||
|
|||||||
Reference in New Issue
Block a user