Lock semchecked ast for macros (#11883) [bugfix]

* reject to modify type checked AST
* add flag to back out
* Introduce legacy feature set.
This commit is contained in:
Arne Döring 2019-08-08 16:57:06 +02:00 • committed by Andreas Rumpf
commit 44e7a7b6c2
8 changed files with 83 additions and 24 deletions

View file

@ -50,15 +50,16 @@ const
"Compiled at $4\n" & "Compiled at $4\n" &
"Copyright (c) 2006-" & copyrightYear & " by Andreas Rumpf\n" "Copyright (c) 2006-" & copyrightYear & " by Andreas Rumpf\n"
const proc genFeatureDesc[T: enum](t: typedesc[T]): string {.compileTime.} =
Usage = slurp"../doc/basicopt.txt".replace(" //", " ")
FeatureDesc = block:
var x = "" var x = ""
for f in low(Feature)..high(Feature): for f in low(T)..high(T):
if x.len > 0: x.add "|" if x.len > 0: x.add "|"
x.add $f x.add $f
x x
AdvancedUsage = slurp"../doc/advopt.txt".replace(" //", " ") % FeatureDesc
const
Usage = slurp"../doc/basicopt.txt".replace(" //", " ")
AdvancedUsage = slurp"../doc/advopt.txt".replace(" //", " ") % [genFeatureDesc(Feature), genFeatureDesc(LegacyFeature)]
proc getCommandLineDesc(conf: ConfigRef): string = proc getCommandLineDesc(conf: ConfigRef): string =
result = (HelpMessage % [VersionAsString, platform.OS[conf.target.hostOS].name, result = (HelpMessage % [VersionAsString, platform.OS[conf.target.hostOS].name,
@ -744,6 +745,11 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
conf.features.incl parseEnum[Feature](arg) conf.features.incl parseEnum[Feature](arg)
except ValueError: except ValueError:
localError(conf, info, "unknown experimental feature") localError(conf, info, "unknown experimental feature")
of "legacy":
try:
conf.legacyFeatures.incl parseEnum[LegacyFeature](arg)
except ValueError:
localError(conf, info, "unknown obsolete feature")
of "nocppexceptions": of "nocppexceptions":
expectNoArg(conf, switch, arg, pass, info) expectNoArg(conf, switch, arg, pass, info)
incl(conf.globalOptions, optNoCppExceptions) incl(conf.globalOptions, optNoCppExceptions)

View file

@ -135,6 +135,12 @@ type
## which itself requires `nimble install libffi`, see #10150 ## which itself requires `nimble install libffi`, see #10150
## Note: this feature can't be localized with {.push.} ## Note: this feature can't be localized with {.push.}
LegacyFeature* = enum
allowSemcheckedAstModification,
## Allows to modify a NimNode where the type has already been
## flaged with nfSem. If you actually do this, it will cause
## bugs.
SymbolFilesOption* = enum SymbolFilesOption* = enum
disabledSf, writeOnlySf, readOnlySf, v2Sf disabledSf, writeOnlySf, readOnlySf, v2Sf
@ -196,6 +202,7 @@ type
cppDefines*: HashSet[string] # (*) cppDefines*: HashSet[string] # (*)
headerFile*: string headerFile*: string
features*: set[Feature] features*: set[Feature]
legacyFeatures*: set[LegacyFeature]
arguments*: string ## the arguments to be passed to the program that arguments*: string ## the arguments to be passed to the program that
## should be run ## should be run
ideCmd*: IdeCmd ideCmd*: IdeCmd
@ -315,7 +322,7 @@ proc newConfigRef*(): ConfigRef =
m: initMsgConfig(), m: initMsgConfig(),
evalExpr: "", evalExpr: "",
cppDefines: initHashSet[string](), cppDefines: initHashSet[string](),
headerFile: "", features: {}, foreignPackageNotes: {hintProcessing, warnUnknownMagic, headerFile: "", features: {}, legacyFeatures: {}, foreignPackageNotes: {hintProcessing, warnUnknownMagic,
hintQuitCalled, hintExecuting}, hintQuitCalled, hintExecuting},
notes: NotesVerbosity[1], mainPackageNotes: NotesVerbosity[1], notes: NotesVerbosity[1], mainPackageNotes: NotesVerbosity[1],
configVars: newStringTable(modeStyleInsensitive), configVars: newStringTable(modeStyleInsensitive),

View file

@ -1392,27 +1392,32 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
decodeBC(rkNode) decodeBC(rkNode)
let idx = regs[rb].intVal.int let idx = regs[rb].intVal.int
var dest = regs[ra].node var dest = regs[ra].node
if dest.kind notin {nkEmpty..nkNilLit} and idx <% dest.len: if nfSem in dest.flags and allowSemcheckedAstModification notin c.config.legacyFeatures:
dest.sons[idx] = regs[rc].node stackTrace(c, tos, pc, "typechecked nodes may not be modified")
else: elif dest.kind in {nkEmpty..nkNilLit} or idx >=% dest.len:
stackTrace(c, tos, pc, formatErrorIndexBound(idx, dest.len-1)) stackTrace(c, tos, pc, formatErrorIndexBound(idx, dest.len-1))
else:
dest.sons[idx] = regs[rc].node
of opcNAdd: of opcNAdd:
decodeBC(rkNode) decodeBC(rkNode)
var u = regs[rb].node var u = regs[rb].node
if u.kind notin {nkEmpty..nkNilLit}: if nfSem in u.flags and allowSemcheckedAstModification notin c.config.legacyFeatures:
u.add(regs[rc].node) stackTrace(c, tos, pc, "typechecked nodes may not be modified")
else: elif u.kind in {nkEmpty..nkNilLit}:
stackTrace(c, tos, pc, "cannot add to node kind: " & $u.kind) stackTrace(c, tos, pc, "cannot add to node kind: " & $u.kind)
else:
u.add(regs[rc].node)
regs[ra].node = u regs[ra].node = u
of opcNAddMultiple: of opcNAddMultiple:
decodeBC(rkNode) decodeBC(rkNode)
let x = regs[rc].node let x = regs[rc].node
var u = regs[rb].node var u = regs[rb].node
if u.kind notin {nkEmpty..nkNilLit}: if nfSem in u.flags and allowSemcheckedAstModification notin c.config.legacyFeatures:
# XXX can be optimized: stackTrace(c, tos, pc, "typechecked nodes may not be modified")
for i in 0..<x.len: u.add(x.sons[i]) elif u.kind in {nkEmpty..nkNilLit}:
else:
stackTrace(c, tos, pc, "cannot add to node kind: " & $u.kind) stackTrace(c, tos, pc, "cannot add to node kind: " & $u.kind)
else:
for i in 0 ..< x.len: u.add(x.sons[i])
regs[ra].node = u regs[ra].node = u
of opcNKind: of opcNKind:
decodeB(rkInt) decodeB(rkInt)

View file

@ -120,6 +120,9 @@ Advanced options:
--errorMax:N stop compilation after N errors; 0 means unlimited --errorMax:N stop compilation after N errors; 0 means unlimited
--experimental:$1 --experimental:$1
enable experimental language feature enable experimental language feature
--legacy:$2
enable obsolete/legacy language feature
legacy code.
--newruntime use an alternative runtime that uses destructors --newruntime use an alternative runtime that uses destructors
and that uses a shared heap via -d:useMalloc and that uses a shared heap via -d:useMalloc
--profiler:on|off enable profiling; requires `import nimprof`, and --profiler:on|off enable profiling; requires `import nimprof`, and

View file

@ -0,0 +1,12 @@
discard """
errormsg: "typechecked nodes may not be modified"
"""
import macros
macro doSomething(arg: typed): untyped =
echo arg.treeREpr
result = arg
result.add newCall(bindSym"echo", newLit(1))
doSomething((echo(1); echo(2)))

View file

@ -0,0 +1,12 @@
discard """
errormsg: "typechecked nodes may not be modified"
"""
import macros
macro doSomething(arg: typed): untyped =
echo arg.treeREpr
result = arg
result[0] = newCall(bindSym"echo", newLit(1))
doSomething((echo(1); echo(2)))

View file

@ -0,0 +1,15 @@
discard """
errormsg: "typechecked nodes may not be modified"
"""
import macros
macro doSomething(arg: typed): untyped =
echo arg.treeREpr
result = arg
result.add(
newCall(bindSym"echo", newLit(3)),
newCall(bindSym"echo", newLit(1))
)
doSomething((echo(1); echo(2)))

View file

@ -1,19 +1,20 @@
discard """ discard """
output: '''calling!stuff output: '''
calling!stuff
calling!stuff calling!stuff
''' '''
joinable: false disabled: true
""" """
# this test modifies an already semchecked ast (bad things happen)
# this test relies on the bug #4547
# issue #7792 # issue #7792
import macros import macros
proc callProc(str: string) = proc callProc(str: string) =
echo "calling!" & str echo "calling!" & str
macro testMacro(code: typed): untyped = macro testMacro(code: typed): untyped =
let stmtList = newNimNode(nnkStmtList) let stmtList = newNimNode(nnkStmtList)
@ -27,11 +28,9 @@ macro testMacro(code: typed): untyped =
result = newEmptyNode() result = newEmptyNode()
proc main() {.testMacro.} = proc main() {.testMacro.} =
echo "test" echo "test"
echo "test2" echo "test2"
when isMainModule: when isMainModule:
main() main()