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:
parent
fda51b6ca2
commit
44e7a7b6c2
8 changed files with 83 additions and 24 deletions
|
|
@ -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"
|
||||||
|
|
||||||
|
proc genFeatureDesc[T: enum](t: typedesc[T]): string {.compileTime.} =
|
||||||
|
var x = ""
|
||||||
|
for f in low(T)..high(T):
|
||||||
|
if x.len > 0: x.add "|"
|
||||||
|
x.add $f
|
||||||
|
x
|
||||||
|
|
||||||
const
|
const
|
||||||
Usage = slurp"../doc/basicopt.txt".replace(" //", " ")
|
Usage = slurp"../doc/basicopt.txt".replace(" //", " ")
|
||||||
FeatureDesc = block:
|
AdvancedUsage = slurp"../doc/advopt.txt".replace(" //", " ") % [genFeatureDesc(Feature), genFeatureDesc(LegacyFeature)]
|
||||||
var x = ""
|
|
||||||
for f in low(Feature)..high(Feature):
|
|
||||||
if x.len > 0: x.add "|"
|
|
||||||
x.add $f
|
|
||||||
x
|
|
||||||
AdvancedUsage = slurp"../doc/advopt.txt".replace(" //", " ") % FeatureDesc
|
|
||||||
|
|
||||||
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)
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
12
tests/macros/tlocktypednode1.nim
Normal file
12
tests/macros/tlocktypednode1.nim
Normal 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)))
|
||||||
12
tests/macros/tlocktypednode2.nim
Normal file
12
tests/macros/tlocktypednode2.nim
Normal 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)))
|
||||||
15
tests/macros/tlocktypednode3.nim
Normal file
15
tests/macros/tlocktypednode3.nim
Normal 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)))
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue