make strict funcs analysis smarter (#18219)
* make strict funcs analysis smarter: varParam[i] = v is different from varParam[i][] = v * added a test case * Update compiler/varpartitions.nim Co-authored-by: Clyybber <darkmine956@gmail.com>
This commit is contained in:
parent
51ab7ccec1
commit
47acc80f4e
3 changed files with 95 additions and 36 deletions
|
|
@ -175,12 +175,18 @@ proc root(v: var Partitions; start: int): int =
|
||||||
v.s[it].con = Connection(kind: dependsOn, parent: result)
|
v.s[it].con = Connection(kind: dependsOn, parent: result)
|
||||||
it = next
|
it = next
|
||||||
|
|
||||||
proc potentialMutation(v: var Partitions; s: PSym; info: TLineInfo) =
|
proc potentialMutation(v: var Partitions; s: PSym; level: int; info: TLineInfo) =
|
||||||
let id = variableId(v, s)
|
let id = variableId(v, s)
|
||||||
if id >= 0:
|
if id >= 0:
|
||||||
let r = root(v, id)
|
let r = root(v, id)
|
||||||
let flags = if s.kind == skParam and isConstParam(s):
|
let flags = if s.kind == skParam:
|
||||||
{isMutated, isMutatedDirectly}
|
if isConstParam(s):
|
||||||
|
{isMutated, isMutatedDirectly}
|
||||||
|
elif s.typ.kind == tyVar and level <= 1:
|
||||||
|
# varParam[i] = v is different from varParam[i][] = v
|
||||||
|
{}
|
||||||
|
else:
|
||||||
|
{isMutated}
|
||||||
else:
|
else:
|
||||||
{isMutated}
|
{isMutated}
|
||||||
|
|
||||||
|
|
@ -339,36 +345,44 @@ proc pathExpr(node: PNode; owner: PSym): PNode =
|
||||||
if result == nil and borrowFromConstExpr(n):
|
if result == nil and borrowFromConstExpr(n):
|
||||||
result = n
|
result = n
|
||||||
|
|
||||||
proc allRoots(n: PNode; result: var seq[PSym]; followDotExpr = true) =
|
const
|
||||||
|
RootEscapes = 1000 # in 'p(r)' we don't know what p does to our poor root.
|
||||||
|
# so we assume a high level of indirections
|
||||||
|
|
||||||
|
proc allRoots(n: PNode; result: var seq[(PSym, int)]; level: int) =
|
||||||
case n.kind
|
case n.kind
|
||||||
of nkSym:
|
of nkSym:
|
||||||
if n.sym.kind in {skParam, skVar, skTemp, skLet, skResult, skForVar}:
|
if n.sym.kind in {skParam, skVar, skTemp, skLet, skResult, skForVar}:
|
||||||
result.add(n.sym)
|
result.add((n.sym, level))
|
||||||
|
|
||||||
of nkDotExpr, nkDerefExpr, nkBracketExpr, nkHiddenDeref,
|
of nkDerefExpr, nkHiddenDeref:
|
||||||
nkCheckedFieldExpr, nkAddr, nkHiddenAddr:
|
allRoots(n[0], result, level+1)
|
||||||
if followDotExpr:
|
of nkBracketExpr, nkDotExpr, nkCheckedFieldExpr, nkAddr, nkHiddenAddr:
|
||||||
allRoots(n[0], result, followDotExpr)
|
allRoots(n[0], result, level)
|
||||||
|
|
||||||
of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv, nkConv,
|
of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv, nkConv,
|
||||||
nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr, nkCast,
|
nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr, nkCast,
|
||||||
nkObjUpConv, nkObjDownConv:
|
nkObjUpConv, nkObjDownConv:
|
||||||
if n.len > 0:
|
if n.len > 0:
|
||||||
allRoots(n.lastSon, result, followDotExpr)
|
allRoots(n.lastSon, result, level)
|
||||||
of nkCaseStmt, nkObjConstr:
|
of nkCaseStmt, nkObjConstr:
|
||||||
for i in 1..<n.len:
|
for i in 1..<n.len:
|
||||||
allRoots(n[i].lastSon, result, followDotExpr)
|
allRoots(n[i].lastSon, result, level)
|
||||||
of nkIfStmt, nkIfExpr:
|
of nkIfStmt, nkIfExpr:
|
||||||
for i in 0..<n.len:
|
for i in 0..<n.len:
|
||||||
allRoots(n[i].lastSon, result, followDotExpr)
|
allRoots(n[i].lastSon, result, level)
|
||||||
of nkBracket, nkTupleConstr, nkPar:
|
of nkBracket, nkTupleConstr, nkPar:
|
||||||
for i in 0..<n.len:
|
for i in 0..<n.len:
|
||||||
allRoots(n[i], result, followDotExpr)
|
allRoots(n[i], result, level-1)
|
||||||
|
|
||||||
of nkCallKinds:
|
of nkCallKinds:
|
||||||
if n.typ != nil and n.typ.kind in {tyVar, tyLent}:
|
if n.typ != nil and n.typ.kind in {tyVar, tyLent}:
|
||||||
if n.len > 1:
|
if n.len > 1:
|
||||||
allRoots(n[1], result, followDotExpr)
|
# XXX We really need the unwritten RFC here and distinguish between
|
||||||
|
# proc `[]`(x: var Container): var T # resizes the container
|
||||||
|
# and
|
||||||
|
# proc `[]`(x: Container): var T # only allows for slot mutation
|
||||||
|
allRoots(n[1], result, RootEscapes)
|
||||||
else:
|
else:
|
||||||
let m = getMagic(n)
|
let m = getMagic(n)
|
||||||
case m
|
case m
|
||||||
|
|
@ -387,12 +401,12 @@ proc allRoots(n: PNode; result: var seq[PSym]; followDotExpr = true) =
|
||||||
let paramType = typ.n[i].typ
|
let paramType = typ.n[i].typ
|
||||||
if not paramType.isCompileTimeOnly and not typ.sons[0].isEmptyType and
|
if not paramType.isCompileTimeOnly and not typ.sons[0].isEmptyType and
|
||||||
canAlias(paramType, typ.sons[0]):
|
canAlias(paramType, typ.sons[0]):
|
||||||
allRoots(it, result, followDotExpr)
|
allRoots(it, result, RootEscapes)
|
||||||
else:
|
else:
|
||||||
allRoots(it, result, followDotExpr)
|
allRoots(it, result, RootEscapes)
|
||||||
|
|
||||||
of mSlice:
|
of mSlice:
|
||||||
allRoots(n[1], result, followDotExpr)
|
allRoots(n[1], result, level+1)
|
||||||
else:
|
else:
|
||||||
discard "harmless operation"
|
discard "harmless operation"
|
||||||
else:
|
else:
|
||||||
|
|
@ -466,10 +480,10 @@ proc destMightOwn(c: var Partitions; dest: var VarIndex; n: PNode) =
|
||||||
dest.flags.incl ownsData
|
dest.flags.incl ownsData
|
||||||
elif n.typ.kind in {tyLent, tyVar}:
|
elif n.typ.kind in {tyLent, tyVar}:
|
||||||
# we know the result is derived from the first argument:
|
# we know the result is derived from the first argument:
|
||||||
var roots: seq[PSym]
|
var roots: seq[(PSym, int)]
|
||||||
allRoots(n[1], roots)
|
allRoots(n[1], roots, RootEscapes)
|
||||||
for r in roots:
|
for r in roots:
|
||||||
connect(c, dest.sym, r, n[1].info)
|
connect(c, dest.sym, r[0], n[1].info)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
let magic = if n[0].kind == nkSym: n[0].sym.magic else: mNone
|
let magic = if n[0].kind == nkSym: n[0].sym.magic else: mNone
|
||||||
|
|
@ -582,19 +596,19 @@ proc deps(c: var Partitions; dest, src: PNode) =
|
||||||
if borrowChecking in c.goals:
|
if borrowChecking in c.goals:
|
||||||
borrowingAsgn(c, dest, src)
|
borrowingAsgn(c, dest, src)
|
||||||
|
|
||||||
var targets, sources: seq[PSym]
|
var targets, sources: seq[(PSym, int)]
|
||||||
allRoots(dest, targets)
|
allRoots(dest, targets, 0)
|
||||||
allRoots(src, sources)
|
allRoots(src, sources, 0)
|
||||||
|
|
||||||
let destIsComplex = containsPointer(dest.typ)
|
let destIsComplex = containsPointer(dest.typ)
|
||||||
|
|
||||||
for t in targets:
|
for t in targets:
|
||||||
if dest.kind != nkSym and c.inNoSideEffectSection == 0:
|
if dest.kind != nkSym and c.inNoSideEffectSection == 0:
|
||||||
potentialMutation(c, t, dest.info)
|
potentialMutation(c, t[0], t[1], dest.info)
|
||||||
|
|
||||||
if destIsComplex:
|
if destIsComplex:
|
||||||
for s in sources:
|
for s in sources:
|
||||||
connect(c, t, s, dest.info)
|
connect(c, t[0], s[0], dest.info)
|
||||||
|
|
||||||
if cursorInference in c.goals and src.kind != nkEmpty:
|
if cursorInference in c.goals and src.kind != nkEmpty:
|
||||||
let d = pathExpr(dest, c.owner)
|
let d = pathExpr(dest, c.owner)
|
||||||
|
|
@ -602,7 +616,8 @@ proc deps(c: var Partitions; dest, src: PNode) =
|
||||||
let vid = variableId(c, d.sym)
|
let vid = variableId(c, d.sym)
|
||||||
if vid >= 0:
|
if vid >= 0:
|
||||||
destMightOwn(c, c.s[vid], src)
|
destMightOwn(c, c.s[vid], src)
|
||||||
for s in sources:
|
for source in sources:
|
||||||
|
let s = source[0]
|
||||||
if s == d.sym:
|
if s == d.sym:
|
||||||
discard "assignments like: it = it.next are fine"
|
discard "assignments like: it = it.next are fine"
|
||||||
elif {sfGlobal, sfThread} * s.flags != {} or hasDisabledAsgn(c.g, s.typ):
|
elif {sfGlobal, sfThread} * s.flags != {} or hasDisabledAsgn(c.g, s.typ):
|
||||||
|
|
@ -638,9 +653,9 @@ proc potentialMutationViaArg(c: var Partitions; n: PNode; callee: PType) =
|
||||||
if constParameters in c.goals and tfNoSideEffect in callee.flags:
|
if constParameters in c.goals and tfNoSideEffect in callee.flags:
|
||||||
discard "we know there are no hidden mutations through an immutable parameter"
|
discard "we know there are no hidden mutations through an immutable parameter"
|
||||||
elif c.inNoSideEffectSection == 0 and containsPointer(n.typ):
|
elif c.inNoSideEffectSection == 0 and containsPointer(n.typ):
|
||||||
var roots: seq[PSym]
|
var roots: seq[(PSym, int)]
|
||||||
allRoots(n, roots)
|
allRoots(n, roots, RootEscapes)
|
||||||
for r in roots: potentialMutation(c, r, n.info)
|
for r in roots: potentialMutation(c, r[0], r[1], n.info)
|
||||||
|
|
||||||
proc traverse(c: var Partitions; n: PNode) =
|
proc traverse(c: var Partitions; n: PNode) =
|
||||||
inc c.abstractTime
|
inc c.abstractTime
|
||||||
|
|
@ -682,12 +697,12 @@ proc traverse(c: var Partitions; n: PNode) =
|
||||||
if i < L:
|
if i < L:
|
||||||
let paramType = parameters[i].skipTypes({tyGenericInst, tyAlias})
|
let paramType = parameters[i].skipTypes({tyGenericInst, tyAlias})
|
||||||
if not paramType.isCompileTimeOnly and paramType.kind in {tyVar, tySink, tyOwned}:
|
if not paramType.isCompileTimeOnly and paramType.kind in {tyVar, tySink, tyOwned}:
|
||||||
var roots: seq[PSym]
|
var roots: seq[(PSym, int)]
|
||||||
allRoots(it, roots)
|
allRoots(it, roots, RootEscapes)
|
||||||
if paramType.kind == tyVar:
|
if paramType.kind == tyVar:
|
||||||
if c.inNoSideEffectSection == 0:
|
if c.inNoSideEffectSection == 0:
|
||||||
for r in roots: potentialMutation(c, r, it.info)
|
for r in roots: potentialMutation(c, r[0], r[1], it.info)
|
||||||
for r in roots: noCursor(c, r)
|
for r in roots: noCursor(c, r[0])
|
||||||
|
|
||||||
if borrowChecking in c.goals:
|
if borrowChecking in c.goals:
|
||||||
# a call like 'result.add toOpenArray()' can also be a borrow
|
# a call like 'result.add toOpenArray()' can also be a borrow
|
||||||
|
|
@ -703,10 +718,10 @@ proc traverse(c: var Partitions; n: PNode) =
|
||||||
when false:
|
when false:
|
||||||
# XXX investigate if this is required, it doesn't look
|
# XXX investigate if this is required, it doesn't look
|
||||||
# like it is!
|
# like it is!
|
||||||
var roots: seq[PSym]
|
var roots: seq[(PSym, int)]
|
||||||
allRoots(n[0], roots)
|
allRoots(n[0], roots, RootEscapes)
|
||||||
for r in roots:
|
for r in roots:
|
||||||
potentialMutation(c, r, it.info)
|
potentialMutation(c, r[0], r[1], it.info)
|
||||||
|
|
||||||
of nkTupleConstr, nkBracket:
|
of nkTupleConstr, nkBracket:
|
||||||
for child in n: traverse(c, child)
|
for child in n: traverse(c, child)
|
||||||
|
|
|
||||||
27
tests/effects/tfuncs_cannot_mutate2.nim
Normal file
27
tests/effects/tfuncs_cannot_mutate2.nim
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
discard """
|
||||||
|
errormsg: "'copy' can have side effects"
|
||||||
|
nimout: '''an object reachable from 'y' is potentially mutated
|
||||||
|
tfuncs_cannot_mutate2.nim(15, 7) the mutation is here
|
||||||
|
tfuncs_cannot_mutate2.nim(13, 10) is the statement that connected the mutation to the parameter
|
||||||
|
'''
|
||||||
|
"""
|
||||||
|
|
||||||
|
{.experimental: "strictFuncs".}
|
||||||
|
|
||||||
|
func copy[T](x: var openArray[T]; y: openArray[T]) =
|
||||||
|
for i in 0..high(x):
|
||||||
|
x[i] = y[i]
|
||||||
|
|
||||||
|
x[0].a = nil
|
||||||
|
|
||||||
|
type
|
||||||
|
R = ref object
|
||||||
|
a, b: R
|
||||||
|
data: string
|
||||||
|
|
||||||
|
proc main =
|
||||||
|
var a, b: array[3, R]
|
||||||
|
b = [R(data: "a"), R(data: "b"), R(data: "c")]
|
||||||
|
copy a, b
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
@ -27,3 +27,20 @@ block:
|
||||||
|
|
||||||
var x = @[0, 1]
|
var x = @[0, 1]
|
||||||
let z = x &&& 2
|
let z = x &&& 2
|
||||||
|
|
||||||
|
|
||||||
|
func copy[T](x: var openArray[T]; y: openArray[T]) =
|
||||||
|
for i in 0..high(x):
|
||||||
|
x[i] = y[i]
|
||||||
|
|
||||||
|
type
|
||||||
|
R = ref object
|
||||||
|
a, b: R
|
||||||
|
data: string
|
||||||
|
|
||||||
|
proc main =
|
||||||
|
var a, b: array[3, R]
|
||||||
|
b = [R(data: "a"), R(data: "b"), R(data: "c")]
|
||||||
|
copy a, b
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue