new feature: --staticBoundChecks:on to enforce static array index checking (#10965)

This commit is contained in:
Andreas Rumpf
2020-03-18 14:25:10 +01:00
committed by GitHub
parent ed263e174e
commit 3f29911a94
16 changed files with 152 additions and 50 deletions

View File

@@ -182,6 +182,7 @@ proc getModule*(s: PSym): PSym =
assert((result.kind == skModule) or (result.owner != result))
while result != nil and result.kind != skModule: result = result.owner
proc fromSystem*(op: PSym): bool {.inline.} = sfSystemModule in getModule(op).flags
proc getSymFromList*(list: PNode, ident: PIdent, start: int = 0): PSym =
for i in start..<list.len:
if list[i].kind == nkSym:

View File

@@ -297,6 +297,7 @@ proc testCompileOption*(conf: ConfigRef; switch: string, info: TLineInfo): bool
of "boundchecks": result = contains(conf.options, optBoundsCheck)
of "refchecks": result = contains(conf.options, optRefCheck)
of "overflowchecks": result = contains(conf.options, optOverflowCheck)
of "staticboundchecks": result = contains(conf.options, optStaticBoundsCheck)
of "stylechecks": result = contains(conf.options, optStyleCheck)
of "linedir": result = contains(conf.options, optLineDir)
of "assertions", "a": result = contains(conf.options, optAssert)
@@ -591,6 +592,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
of "boundchecks": processOnOffSwitch(conf, {optBoundsCheck}, arg, pass, info)
of "refchecks": processOnOffSwitch(conf, {optRefCheck}, arg, pass, info)
of "overflowchecks": processOnOffSwitch(conf, {optOverflowCheck}, arg, pass, info)
of "staticboundchecks": processOnOffSwitch(conf, {optStaticBoundsCheck}, arg, pass, info)
of "stylechecks": processOnOffSwitch(conf, {optStyleCheck}, arg, pass, info)
of "linedir": processOnOffSwitch(conf, {optLineDir}, arg, pass, info)
of "assertions", "a": processOnOffSwitch(conf, {optAssert}, arg, pass, info)

View File

@@ -250,7 +250,6 @@ proc pred(n: PNode): PNode =
result = n
proc canon*(n: PNode; o: Operators): PNode =
# XXX for now only the new code in 'semparallel' uses this
if n.safeLen >= 1:
result = shallowCopy(n)
for i in 0..<n.len:
@@ -275,7 +274,7 @@ proc canon*(n: PNode; o: Operators): PNode =
result = negate(result[1], result[2], result, o)
of someLen:
result[0] = o.opLen.newSymNode
of someLt:
of someLt - {mLtF64}:
# x < y same as x <= y-1:
let y = n[2].canon(o)
let p = pred(y)
@@ -315,12 +314,12 @@ proc canon*(n: PNode; o: Operators): PNode =
if plus != nil and not isLetLocation(x, true):
result = buildCall(result[0].sym, plus, y[1])
else: discard
elif x.isValue and y.getMagic in someAdd and y[2].isValue:
elif x.isValue and y.getMagic in someAdd and y[2].kind == x.kind:
# 0 <= a.len + 3
# -3 <= a.len
result[1] = x |-| y[2]
result[2] = y[1]
elif x.isValue and y.getMagic in someSub and y[2].isValue:
elif x.isValue and y.getMagic in someSub and y[2].kind == x.kind:
# 0 <= a.len - 3
# 3 <= a.len
result[1] = x |+| y[2]
@@ -340,6 +339,8 @@ proc usefulFact(n: PNode; o: Operators): PNode =
if isLetLocation(n[1], true) or isLetLocation(n[2], true):
# XXX algebraic simplifications! 'i-1 < a.len' --> 'i < a.len+1'
result = n
elif n[1].getMagic in someLen or n[2].getMagic in someLen:
result = n
of someLe+someLt:
if isLetLocation(n[1], true) or isLetLocation(n[2], true):
# XXX algebraic simplifications! 'i-1 < a.len' --> 'i < a.len+1'
@@ -401,10 +402,20 @@ type
TModel* = object
s*: seq[PNode] # the "knowledge base"
o*: Operators
beSmart*: bool
proc addFact*(m: var TModel, nn: PNode) =
let n = usefulFact(nn, m.o)
if n != nil: m.s.add n
if n != nil:
if not m.beSmart:
m.s.add n
else:
let c = canon(n, m.o)
if c.getMagic == mAnd:
addFact(m, c[1])
addFact(m, c[2])
else:
m.s.add c
proc addFactNeg*(m: var TModel, n: PNode) =
let n = n.neg(m.o)
@@ -614,6 +625,9 @@ proc impliesGe(fact, x, c: PNode): TImplication =
proc impliesLe(fact, x, c: PNode): TImplication =
if not isLocation(x):
if c.isValue:
if leValue(x, x): return impYes
else: return impNo
return impliesGe(fact, c, x)
case fact[0].sym.magic
of someEq:
@@ -799,7 +813,7 @@ proc ple(m: TModel; a, b: PNode): TImplication =
if lastOrd(nil, a.typ) <= b.intVal: return impYes
# 3 <= x iff low(x) <= 3
if a.isValue and b.typ != nil and b.typ.isOrdinalType:
if firstOrd(nil, b.typ) <= a.intVal: return impYes
if a.intVal <= firstOrd(nil, b.typ): return impYes
# x <= x
if sameTree(a, b): return impYes
@@ -810,7 +824,11 @@ proc ple(m: TModel; a, b: PNode): TImplication =
# x <= y+c if 0 <= c and x <= y
# x <= y+(-c) if c <= 0 and y >= x
if b.getMagic in someAdd and zero() <=? b[2] and a <=? b[1]: return impYes
if b.getMagic in someAdd:
if zero() <=? b[2] and a <=? b[1]: return impYes
# x <= y-c if x+c <= y
if b[2] <=? zero() and (canon(m.o.opSub.buildCall(a, b[2]), m.o) <=? b[1]):
return impYes
# x+c <= y if c <= 0 and x <= y
if a.getMagic in someAdd and a[2] <=? zero() and a[1] <=? b: return impYes
@@ -899,10 +917,13 @@ proc pleViaModelRec(m: var TModel; a, b: PNode): TImplication =
# --> true if (len-100) <= (len-1)
let x = fact[1]
let y = fact[2]
if sameTree(x, a) and y.getMagic in someAdd and b.getMagic in someAdd and
sameTree(y[1], b[1]):
if ple(m, b[2], y[2]) == impYes:
return impYes
# x <= y.
# Question: x <= b? True iff y <= b.
if sameTree(x, a):
if ple(m, y, b) == impYes: return impYes
if y.getMagic in someAdd and b.getMagic in someAdd and sameTree(y[1], b[1]):
if ple(m, b[2], y[2]) == impYes:
return impYes
# x <= y implies a <= b if a <= x and y <= b
if ple(m, a, x) == impYes:
@@ -961,6 +982,10 @@ proc proveLe*(m: TModel; a, b: PNode): TImplication =
proc addFactLe*(m: var TModel; a, b: PNode) =
m.s.add canon(m.o.opLe.buildCall(a, b), m.o)
proc addFactLt*(m: var TModel; a, b: PNode) =
let bb = m.o.opAdd.buildCall(b, minusOne())
addFactLe(m, a, bb)
proc settype(n: PNode): PType =
result = newType(tySet, n.typ.owner)
addSonSkipIntLit(result, n.typ)

View File

@@ -36,7 +36,8 @@ type
warnUnusedImportX,
warnInheritFromException,
warnEachIdentIsTuple,
warnProveInit, warnProveField, warnProveIndex, warnGcUnsafe, warnGcUnsafe2,
warnProveInit, warnProveField, warnProveIndex,
warnStaticIndexCheck, warnGcUnsafe, warnGcUnsafe2,
warnUninit, warnGcMem, warnDestructor, warnLockLevel, warnResultShadowed,
warnInconsistentSpacing, warnCaseTransition, warnCycleCreated, warnUser,
hintSuccess, hintSuccessX, hintCC,
@@ -87,6 +88,7 @@ const
warnProveInit: "Cannot prove that '$1' is initialized. This will become a compile time error in the future.",
warnProveField: "cannot prove that field '$1' is accessible",
warnProveIndex: "cannot prove index '$1' is valid",
warnStaticIndexCheck: "$1",
warnGcUnsafe: "not GC-safe: '$1'",
warnGcUnsafe2: "$1",
warnUninit: "'$1' might not have been initialized",
@@ -142,7 +144,8 @@ const
"TypelessParam", "UseBase", "WriteToForeignHeap",
"UnsafeCode", "UnusedImport", "InheritFromException",
"EachIdentIsTuple",
"ProveInit", "ProveField", "ProveIndex", "GcUnsafe", "GcUnsafe2", "Uninit",
"ProveInit", "ProveField", "ProveIndex",
"IndexCheck", "GcUnsafe", "GcUnsafe2", "Uninit",
"GcMem", "Destructor", "LockLevel", "ResultShadowed",
"Spacing", "CaseTransition", "CycleCreated", "User"]

View File

@@ -26,7 +26,7 @@ type # please make sure we have under 32 options
TOption* = enum # **keep binary compatible**
optNone, optObjCheck, optFieldCheck, optRangeCheck, optBoundsCheck,
optOverflowCheck, optNilCheck, optRefCheck,
optNaNCheck, optInfCheck, optStyleCheck,
optNaNCheck, optInfCheck, optStaticBoundsCheck, optStyleCheck,
optAssert, optLineDir, optWarns, optHints,
optOptimizeSpeed, optOptimizeSize, optStackTrace, # stack tracing support
optLineTrace, # line tracing support (includes stack tracing)
@@ -320,7 +320,7 @@ const oldExperimentalFeatures* = {implicitDeref, dotOperators, callOperator, par
const
ChecksOptions* = {optObjCheck, optFieldCheck, optRangeCheck, optNilCheck,
optOverflowCheck, optBoundsCheck, optAssert, optNaNCheck, optInfCheck,
optStyleCheck, optRefCheck}
optStyleCheck}
DefaultOptions* = {optObjCheck, optFieldCheck, optRangeCheck,
optBoundsCheck, optOverflowCheck, optAssert, optWarns, optRefCheck,

View File

@@ -42,7 +42,8 @@ const
wTags, wLocks, wGcSafe}
exprPragmas* = {wLine, wLocks, wNoRewrite, wGcSafe, wNoSideEffect}
stmtPragmas* = {wChecks, wObjChecks, wFieldChecks, wRangeChecks,
wBoundChecks, wOverflowChecks, wNilChecks, wStyleChecks, wAssertions,
wBoundChecks, wOverflowChecks, wNilChecks, wStaticBoundchecks,
wStyleChecks, wAssertions,
wWarnings, wHints,
wLineDir, wStackTrace, wLineTrace, wOptimization, wHint, wWarning, wError,
wFatal, wDefine, wUndef, wCompile, wLink, wLinksys, wPure, wPush, wPop,
@@ -344,6 +345,7 @@ proc pragmaToOptions(w: TSpecialWord): TOptions {.inline.} =
of wFloatChecks: {optNaNCheck, optInfCheck}
of wNanChecks: {optNaNCheck}
of wInfChecks: {optInfCheck}
of wStaticBoundchecks: {optStaticBoundsCheck}
of wStyleChecks: {optStyleCheck}
of wAssertions: {optAssert}
of wWarnings: {optWarns}
@@ -1027,7 +1029,8 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
of wCodegenDecl: processCodegenDecl(c, it, sym)
of wChecks, wObjChecks, wFieldChecks, wRangeChecks, wBoundChecks,
wOverflowChecks, wNilChecks, wAssertions, wWarnings, wHints,
wLineDir, wOptimization, wStyleChecks, wCallconv, wDebugger, wProfiler,
wLineDir, wOptimization, wStaticBoundchecks, wStyleChecks,
wCallconv, wDebugger, wProfiler,
wFloatChecks, wNanChecks, wInfChecks, wPatterns, wTrMacros:
processOption(c, it, c.config.options)
of wStackTrace, wLineTrace:

View File

@@ -23,7 +23,7 @@
import
ast, astalgo, idents, lowerings, magicsys, guards, sempass2, msgs,
renderer, types, modulegraphs, options, spawn
renderer, types, modulegraphs, options, spawn, lineinfos
from trees import getMagic
from strutils import `%`
@@ -129,10 +129,12 @@ template `?`(x): untyped = x.renderTree
proc checkLe(c: AnalysisCtx; a, b: PNode) =
case proveLe(c.guards, a, b)
of impUnknown:
localError(c.graph.config, a.info, "cannot prove: " & ?a & " <= " & ?b & " (bounds check)")
message(c.graph.config, a.info, warnStaticIndexCheck,
"cannot prove: " & ?a & " <= " & ?b)
of impYes: discard
of impNo:
localError(c.graph.config, a.info, "can prove: " & ?a & " > " & ?b & " (bounds check)")
message(c.graph.config, a.info, warnStaticIndexCheck,
"can prove: " & ?a & " > " & ?b)
proc checkBounds(c: AnalysisCtx; arr, idx: PNode) =
checkLe(c, lowBound(c.graph.config, arr), idx)
@@ -162,17 +164,17 @@ proc overlap(m: TModel; conf: ConfigRef; x,y,c,d: PNode) =
case proveLe(m, x, d)
of impNo: discard
of impUnknown, impYes:
localError(conf, x.info,
message(conf, x.info, warnStaticIndexCheck,
"cannot prove: $# > $#; required for ($#)..($#) disjoint from ($#)..($#)" %
[?c, ?y, ?x, ?y, ?c, ?d])
of impYes:
case proveLe(m, x, d)
of impUnknown:
localError(conf, x.info,
message(conf, x.info, warnStaticIndexCheck,
"cannot prove: $# > $#; required for ($#)..($#) disjoint from ($#)..($#)" %
[?x, ?d, ?x, ?y, ?c, ?d])
of impYes:
localError(conf, x.info, "($#)..($#) not disjoint from ($#)..($#)" %
message(conf, x.info, warnStaticIndexCheck, "($#)..($#) not disjoint from ($#)..($#)" %
[?c, ?y, ?x, ?y, ?c, ?d])
of impNo: discard
of impNo: discard
@@ -261,8 +263,6 @@ proc min(a, b: PNode): PNode =
elif a.intVal < b.intVal: result = a
else: result = b
proc fromSystem(op: PSym): bool = sfSystemModule in getModule(op).flags
template pushSpawnId(c, body) {.dirty.} =
inc c.spawns
let oldSpawnId = c.currentSpawnId

View File

@@ -75,6 +75,7 @@ type
gcUnsafe, isRecursive, isTopLevel, hasSideEffect, inEnforcedGcSafe: bool
inEnforcedNoSideEffects: bool
maxLockLevel, currLockLevel: TLockLevel
currOptions: TOptions
config: ConfigRef
graph: ModuleGraph
c: PContext
@@ -668,6 +669,32 @@ proc cstringCheck(tracked: PEffects; n: PNode) =
a.typ.kind == tyString and a.kind notin {nkStrLit..nkTripleStrLit}):
message(tracked.config, n.info, warnUnsafeCode, renderTree(n))
proc checkLe(c: PEffects; a, b: PNode) =
case proveLe(c.guards, a, b)
of impUnknown:
#for g in c.guards.s:
# if g != nil: echo "I Know ", g
message(c.config, a.info, warnStaticIndexCheck,
"cannot prove: " & $a & " <= " & $b)
of impYes:
discard
of impNo:
message(c.config, a.info, warnStaticIndexCheck,
"can prove: " & $a & " > " & $b)
proc checkBounds(c: PEffects; arr, idx: PNode) =
checkLe(c, lowBound(c.config, arr), idx)
checkLe(c, idx, highBound(c.config, arr, c.guards.o))
proc checkRange(c: PEffects; value: PNode; typ: PType) =
if typ.skipTypes(abstractInst - {tyRange}).kind == tyRange:
let lowBound = nkIntLit.newIntNode(firstOrd(c.config, typ))
lowBound.info = value.info
let highBound = nkIntLit.newIntNode(lastOrd(c.config, typ))
highBound.info = value.info
checkLe(c, lowBound, value)
checkLe(c, value, highBound)
proc createTypeBoundOps(tracked: PEffects, typ: PType; info: TLineInfo) =
createTypeBoundOps(tracked.graph, tracked.c, typ, info)
if (typ != nil and tfHasAsgn in typ.flags) or
@@ -755,12 +782,17 @@ proc track(tracked: PEffects, n: PNode) =
discard
else:
message(tracked.config, arg.info, warnProveInit, $arg)
# check required for 'nim check':
if n[1].typ.len > 0:
createTypeBoundOps(tracked, n[1].typ.lastSon, n.info)
createTypeBoundOps(tracked, n[1].typ, n.info)
# new(x, finalizer): Problem: how to move finalizer into 'createTypeBoundOps'?
elif a.kind == nkSym and a.sym.magic in {mArrGet, mArrPut} and
optStaticBoundsCheck in tracked.currOptions:
checkBounds(tracked, n[1], n[2])
if a.kind == nkSym and a.sym.name.s.len > 0 and a.sym.name.s[0] == '=' and
tracked.owner.kind != skMacro:
let opKind = find(AttachedOpToStr, a.sym.name.s.normalize)
@@ -849,6 +881,28 @@ proc track(tracked: PEffects, n: PNode) =
of nkForStmt, nkParForStmt:
# we are very conservative here and assume the loop is never executed:
let oldState = tracked.init.len
let oldFacts = tracked.guards.s.len
let iterCall = n[n.len-2]
if optStaticBoundsCheck in tracked.currOptions and iterCall.kind in nkCallKinds:
let op = iterCall[0]
if op.kind == nkSym and fromSystem(op.sym):
let iterVar = n[0]
case op.sym.name.s
of "..", "countup", "countdown":
let lower = iterCall[1]
let upper = iterCall[2]
# for i in 0..n means 0 <= i and i <= n. Countdown is
# the same since only the iteration direction changes.
addFactLe(tracked.guards, lower, iterVar)
addFactLe(tracked.guards, iterVar, upper)
of "..<":
let lower = iterCall[1]
let upper = iterCall[2]
addFactLe(tracked.guards, lower, iterVar)
addFactLt(tracked.guards, iterVar, upper)
else: discard
for i in 0..<n.len-2:
let it = n[i]
track(tracked, it)
@@ -858,7 +912,6 @@ proc track(tracked: PEffects, n: PNode) =
createTypeBoundOps(tracked, x.typ, x.info)
else:
createTypeBoundOps(tracked, it.typ, it.info)
let iterCall = n[^2]
let loopBody = n[^1]
if tracked.owner.kind != skMacro and iterCall.safeLen > 1:
# XXX this is a bit hacky:
@@ -867,6 +920,7 @@ proc track(tracked: PEffects, n: PNode) =
track(tracked, iterCall)
track(tracked, loopBody)
setLen(tracked.init, oldState)
setLen(tracked.guards.s, oldFacts)
of nkObjConstr:
when false: track(tracked, n[0])
let oldFacts = tracked.guards.s.len
@@ -932,18 +986,27 @@ proc track(tracked: PEffects, n: PNode) =
# a better solution will come up eventually.
if n[1].typ.kind != tyString:
createTypeBoundOps(tracked, n[1].typ, n[1].info)
if optStaticBoundsCheck in tracked.currOptions:
checkRange(tracked, n[1], n.typ)
of nkObjUpConv, nkObjDownConv, nkChckRange, nkChckRangeF, nkChckRange64:
if n.len == 1:
track(tracked, n[0])
if tracked.owner.kind != skMacro:
createTypeBoundOps(tracked, n.typ, n.info)
createTypeBoundOps(tracked, n[0].typ, n[0].info)
if optStaticBoundsCheck in tracked.currOptions:
checkRange(tracked, n[0], n.typ)
of nkBracket:
for i in 0..<n.safeLen:
track(tracked, n[i])
checkForSink(tracked.config, tracked.owner, n[i])
if tracked.owner.kind != skMacro:
createTypeBoundOps(tracked, n.typ, n.info)
of nkBracketExpr:
if optStaticBoundsCheck in tracked.currOptions and n.len == 2:
if n[0].typ != nil and skipTypes(n[0].typ, abstractVar).kind != tyTuple:
checkBounds(tracked, n[0], n[1])
for i in 0 ..< n.len: track(tracked, n[i])
else:
for i in 0..<n.safeLen: track(tracked, n[i])
@@ -1028,6 +1091,8 @@ proc initEffects(g: ModuleGraph; effects: PNode; s: PSym; t: var TEffects; c: PC
t.init = @[]
t.guards.s = @[]
t.guards.o = initOperators(g)
t.currOptions = g.config.options + s.options
t.guards.beSmart = optStaticBoundsCheck in t.currOptions
t.locked = @[]
t.graph = g
t.config = g.config

View File

@@ -74,6 +74,7 @@ proc sameTree*(a, b: PNode): bool =
result = true
proc getMagic*(op: PNode): TMagic =
if op == nil: return mNone
case op.kind
of nkCallKinds:
case op[0].kind

View File

@@ -716,7 +716,7 @@ proc firstOrd*(conf: ConfigRef; t: PType): Int128 =
of tyOrdinal:
if t.len > 0: result = firstOrd(conf, lastSon(t))
else: internalError(conf, "invalid kind for firstOrd(" & $t.kind & ')')
of tyUncheckedArray:
of tyUncheckedArray, tyCString:
result = Zero
else:
internalError(conf, "invalid kind for firstOrd(" & $t.kind & ')')

View File

@@ -50,7 +50,7 @@ type
wNimcall, wStdcall, wCdecl, wSafecall, wSyscall, wInline, wNoInline,
wFastcall, wClosure, wNoconv, wOn, wOff, wChecks, wRangeChecks,
wBoundChecks, wOverflowChecks, wNilChecks,
wFloatChecks, wNanChecks, wInfChecks, wStyleChecks,
wFloatChecks, wNanChecks, wInfChecks, wStyleChecks, wStaticBoundchecks,
wNonReloadable, wExecuteOnReload,
wAssertions, wPatterns, wTrMacros, wSinkInference, wWarnings,
wHints, wOptimization, wRaises, wWrites, wReads, wSize, wEffects, wTags,
@@ -137,7 +137,7 @@ const
"cdecl", "safecall", "syscall", "inline", "noinline", "fastcall", "closure",
"noconv", "on", "off", "checks", "rangechecks", "boundchecks",
"overflowchecks", "nilchecks",
"floatchecks", "nanchecks", "infchecks", "stylechecks",
"floatchecks", "nanchecks", "infchecks", "stylechecks", "staticboundchecks",
"nonreloadable", "executeonreload",
"assertions", "patterns", "trmacros", "sinkinference", "warnings", "hints",

View File

@@ -1,14 +1,14 @@
discard """
cmd: "nim c --gc:arc $file"
action: "compile"
"""
# bug #13269
import posix
proc foo*() =
var last = newSeq[Stat]()
var next = last
for i in 0..3:
last = next
foo()
discard """
cmd: "nim c --gc:arc $file"
action: "compile"
"""
# bug #13269
import posix
proc foo*() =
var last = newSeq[Stat]()
var next = last
for i in 0..3:
last = next
foo()

View File

@@ -10,8 +10,9 @@ proc check(a: TObj not nil) =
proc doit() =
var x : array[0..1, TObj]
if x[0] != nil:
check(x[0])
let y = x[0]
if y != nil:
check(y)
doit()

View File

@@ -1,6 +1,6 @@
discard """
errormsg: "can prove: i + 1 > 30"
line: 21
errormsg: "cannot prove (i)..(i) disjoint from (i + 1)..(i + 1)"
line: 20
"""
import threadpool

View File

@@ -1,6 +1,7 @@
discard """
errormsg: "(k)..(k) not disjoint from (k)..(k)"
line: 23
action: compile
"""
# bug #1597

View File

@@ -1,3 +1,3 @@
import tables
let dataEx* = {1: 2, 3: 4}.toTable
import tables
let dataEx* = {1: 2, 3: 4}.toTable