fix(obs): reject frame/own plain-pointer arguments to reference returns
`return id(p)` where the callee takes a reference could hand out a pointer that dies with the frame: `_BorrowSourceReturnOk` returned -1 (nothing to check) for a plain name that is not a reference binding. It now classifies that name with the same owner rule used elsewhere: a reference parameter, `self`, a module global, or a non-owned heap pointer is allowed; an `own`/slife local, a by-value parameter, or a frame pointer is rejected, and unknown cases are rejected conservatively. Reference bindings follow their registered owner, and field/element sources are classified by their root. The LSP flags the same plain-name case when the argument is `own` or initialized from `&local`.
This commit is contained in:
1 parent
2f397eb301
commit
c3c2e55fb4
2 files changed
+42
-6
No files matched your search
@@ -1857,10 +1857,22 @@ novirtual class AssignGenMixin:
|
||||
if expr.kind() == ast.ASTKind.Name:
|
||||
nm: ast.Name* = ast.Name*(expr)
|
||||
if nm.id is None: return -1
|
||||
if self.Gen.VarGen.var_ref_kind(trans.SymTab, nm.id) == 0: return -1
|
||||
own_r: str = self.Gen.StmtGen._BorrowOwnerOf(trans, nm.id)
|
||||
if own_r is None: return -1
|
||||
return self._OwnerOutlivesFrame(trans, fd, own_r)
|
||||
if string.strcmp(nm.id, "self") == 0: return 1
|
||||
# A reference parameter borrows the caller's value; any other parameter is a frame
|
||||
# copy, so handing it out as a reference dangles.
|
||||
pi: int = self._ParamIndexByName(fd, nm.id)
|
||||
if pi >= 0:
|
||||
par: ast.Arg* = self._ParamAt(fd, pi)
|
||||
if par is not None and self.Gen.TypeGenResolve.ref_kind(par.annotation) != 0: return 1
|
||||
return 0
|
||||
# A reference binding: follow the object it borrows.
|
||||
if self.Gen.VarGen.var_ref_kind(trans.SymTab, nm.id) != 0:
|
||||
own_r: str = self.Gen.StmtGen._BorrowOwnerOf(trans, nm.id)
|
||||
if own_r is None: return 0
|
||||
return self._OwnerOutlivesFrame(trans, fd, own_r)
|
||||
# A plain name passed where the callee takes a reference is itself the borrowed
|
||||
# object: it must outlive this frame (own/slife and frame pointers do not).
|
||||
return self._OwnerOutlivesFrame(trans, fd, nm.id)
|
||||
if expr.kind() != ast.ASTKind.UnaryOp: return -1
|
||||
uo: ast.UnaryOp* = ast.UnaryOp*(expr)
|
||||
if uo.op != ast.OpKind.AddrOf and uo.op != ast.OpKind.AddrOfMut: return -1
|
||||
@@ -1870,7 +1882,7 @@ novirtual class AssignGenMixin:
|
||||
nm2: ast.Name* = ast.Name*(tgt)
|
||||
if nm2.id is None: return -1
|
||||
return self._OwnerOutlivesFrame(trans, fd, nm2.id)
|
||||
if tgt.kind() == ast.ASTKind.Attribute:
|
||||
if tgt.kind() == ast.ASTKind.Attribute or tgt.kind() == ast.ASTKind.Subscript:
|
||||
root: str = self.Gen.StmtGen._BorrowPathName(trans.Pool, tgt)
|
||||
if root is None: return -1
|
||||
dot: str = string.strchr(root, '.')
|
||||
|
||||
@@ -2000,6 +2000,24 @@ def _CallReturnsReference(pool: memhub.MemManager*, doc: LspDocuments.LspDocumen
|
||||
return 1 if s[0] == '&' else 0
|
||||
|
||||
|
||||
def _ArgLeavesFrame(pool: memhub.MemManager*, a: LspSymbols.LspAnalysis*, node: ast.AST*) -> int:
|
||||
"""1 when a plain pointer argument cannot outlive the frame: `own` binding or `&local`."""
|
||||
if node is None or node.kind() != ast.ASTKind.Name: return 0
|
||||
det: str = _SymDetailAt(pool, a, node)
|
||||
if _DetailIsOwn(det) != 0: return 1
|
||||
occ: LspSymbols.LspOccurrence* = _OccAtExpr(a, node)
|
||||
if occ is None or occ.sym is None or occ.sym.decl is None: return 0
|
||||
d: ast.AST* = occ.sym.decl
|
||||
if d.kind() != ast.ASTKind.AnnAssign: return 0
|
||||
v: ast.AST* = (ast.AnnAssign*)(d).value
|
||||
if v is None: return 0
|
||||
if v.kind() == ast.ASTKind.New: return 0
|
||||
if v.kind() == ast.ASTKind.UnaryOp:
|
||||
op: int = (ast.UnaryOp*)(v).op
|
||||
if op == ast.OpKind.AddrOf or op == ast.OpKind.AddrOfMut: return 1
|
||||
return 0
|
||||
|
||||
|
||||
def _CheckRefReturn(pool: memhub.MemManager*, doc: LspDocuments.LspDocument*,
|
||||
a: LspSymbols.LspAnalysis*, ws: LspWorkspace.LspWorkspace*,
|
||||
table_pool: memhub.MemManager*, ret_ann: str, value: ast.AST*,
|
||||
@@ -2019,7 +2037,13 @@ def _CheckRefReturn(pool: memhub.MemManager*, doc: LspDocuments.LspDocument*,
|
||||
while ai < int(len(args)):
|
||||
arg: ast.AST* = args.get(sizet(ai))
|
||||
ai += 1
|
||||
if arg is None or arg.kind() != ast.ASTKind.UnaryOp: continue
|
||||
if arg is None: continue
|
||||
if arg.kind() == ast.ASTKind.Name:
|
||||
if _ArgLeavesFrame(pool, a, arg) != 0:
|
||||
_EmitTypeError(pool, doc, arg,
|
||||
f"cannot return this call: argument {ai} is released with this frame; pass a reference parameter, `self`, or a non-owned heap value", errors)
|
||||
continue
|
||||
if arg.kind() != ast.ASTKind.UnaryOp: continue
|
||||
uo: ast.UnaryOp* = ast.UnaryOp*(arg)
|
||||
if uo.op != ast.OpKind.AddrOf and uo.op != ast.OpKind.AddrOfMut: continue
|
||||
if uo.operand is None or uo.operand.kind() != ast.ASTKind.Name: continue
|
||||
|
||||
Reference in new issue
Block a user