From 57ff0b8555f8c56425e87ca5de1a96a209631b2e Mon Sep 17 00:00:00 2001 From: andri lim Date: Mon, 12 Feb 2024 12:39:19 +0700 Subject: [PATCH] Add push raises (#101) * Add push raises * Fix gcsafe violation * remove debug code --- confutils.nim | 11 +++++++-- confutils/cli_parser.nim | 3 +++ confutils/cli_parsing_fuzzer.nim | 3 +++ confutils/config_file.nim | 25 ++++++++++++-------- confutils/defs.nim | 4 ++++ confutils/shell_completion.nim | 28 +++++++++++++++-------- confutils/std/net.nim | 8 +++++-- confutils/toml/defs.nim | 4 ++++ confutils/toml/std/net.nim | 14 ++++++++---- confutils/toml/std/uri.nim | 7 ++++-- confutils/winreg/reader.nim | 11 ++++++--- confutils/winreg/types.nim | 4 ++++ confutils/winreg/utils.nim | 14 ++++++++++-- confutils/winreg/winreg_serialization.nim | 4 ++++ confutils/winreg/writer.nim | 4 ++++ tests/{ => private}/specialint.nim | 0 tests/test_config_file.nim | 3 --- tests/test_envvar.nim | 4 ---- tests/test_qualified_ident.nim | 2 +- 19 files changed, 111 insertions(+), 42 deletions(-) rename tests/{ => private}/specialint.nim (100%) diff --git a/confutils.nim b/confutils.nim index 2262f51..414d9f4 100644 --- a/confutils.nim +++ b/confutils.nim @@ -26,7 +26,7 @@ const when not defined(nimscript): import - os, terminal, + terminal, confutils/shell_completion type @@ -70,6 +70,8 @@ const confutils_description_width {.intdefine.} = 80 confutils_narrow_terminal_width {.intdefine.} = 36 +{.push gcsafe, raises: [].} + func getFieldName(caseField: NimNode): NimNode = result = caseField if result.kind == nnkIdentDefs: result = result[0] @@ -565,7 +567,10 @@ proc parseCmdArgAux(T: type, s: string): T {.raises: [ValueError].} = # If you have provided your own specializations, please handle # all other exception types. mixin parseCmdArg - parseCmdArg(T, s) + try: + parseCmdArg(T, s) + except CatchableError as exc: + raise newException(ValueError, exc.msg) func completeCmdArg*(T: type enum, val: string): seq[string] = for e in low(T)..high(T): @@ -1291,3 +1296,5 @@ func load*(f: TypedInputFile): f.ContentType = else: mixin loadFile loadFile(f.Format, f.string, f.ContentType) + +{.pop.} diff --git a/confutils/cli_parser.nim b/confutils/cli_parser.nim index d9ed5af..54d0954 100644 --- a/confutils/cli_parser.nim +++ b/confutils/cli_parser.nim @@ -29,6 +29,8 @@ type ## or the argument, and the value is not "" if ## the option was given a value +{.push gcsafe, raises: [].} + func parseWord(s: string, i: int, w: var string, delim: set[char] = {'\t', ' '}): int = result = i @@ -161,3 +163,4 @@ iterator getopt*(cmds: seq[string], if p.kind == cmdEnd: break yield (p.kind, p.key, p.val) +{.pop.} diff --git a/confutils/cli_parsing_fuzzer.nim b/confutils/cli_parsing_fuzzer.nim index 48033af..5277a4a 100644 --- a/confutils/cli_parsing_fuzzer.nim +++ b/confutils/cli_parsing_fuzzer.nim @@ -12,6 +12,8 @@ import stew/byteutils, testutils/fuzzing, ../confutils +{.push gcsafe, raises: [].} + template fuzzCliParsing*(Conf: type) = test: block: @@ -22,3 +24,4 @@ template fuzzCliParsing*(Conf: type) = except ConfigurationError as err: discard +{.pop.} diff --git a/confutils/config_file.nim b/confutils/config_file.nim index e2c05e7..f4a5f0d 100644 --- a/confutils/config_file.nim +++ b/confutils/config_file.nim @@ -45,6 +45,8 @@ type OriginalToGeneratedFields = OrderedTable[string, GeneratedFieldInfo] +{.push gcsafe, raises: [].} + func isOption(n: NimNode): bool = if n.kind != nnkBracketExpr: return false eqIdent(n[0], "Option") @@ -248,19 +250,20 @@ proc generateTypes(root: ConfFileSection): seq[NimNode] = recList.add generateOptionalField(child.getRenamedName.ident, child.typ) result[index].putRecList(recList) -proc generateSettersPaths(node: ConfFileSection, result: var OriginalToGeneratedFields) = - var path {.global.}: seq[string] - path.add node.getRenamedName +proc generateSettersPaths(node: ConfFileSection, + result: var OriginalToGeneratedFields, + pathsCache: var seq[string]) = + pathsCache.add node.getRenamedName if node.children.len == 0: - result[node.fieldName] = (node.isCommandOrArgument, path) + result[node.fieldName] = (node.isCommandOrArgument, pathsCache) else: for child in node.children: - generateSettersPaths(child, result) - path.del path.len - 1 + generateSettersPaths(child, result, pathsCache) + pathsCache.del pathsCache.len - 1 -proc generateSettersPaths(root: ConfFileSection): OriginalToGeneratedFields = +proc generateSettersPaths(root: ConfFileSection, pathsCache: var seq[string]): OriginalToGeneratedFields = for child in root.children: - generateSettersPaths(child, result) + generateSettersPaths(child, result, pathsCache) template cfSetter(a, b: untyped): untyped = when a is Option: @@ -346,9 +349,13 @@ macro generateSecondarySources*(ConfType: type): untyped = let model = generateConfigFileModel(ConfType) modelType = generateTypes(model) + var + pathsCache: seq[string] result = newTree(nnkStmtList) result.add newTree(nnkTypeSection, modelType) - let settersPaths = model.generateSettersPaths + let settersPaths = model.generateSettersPaths(pathsCache) result.add generateConfigFileSetters(ConfType, result[^1], settersPaths) + +{.pop.} diff --git a/confutils/defs.nim b/confutils/defs.nim index 19b50be..e85c3e2 100644 --- a/confutils/defs.nim +++ b/confutils/defs.nim @@ -45,6 +45,8 @@ type SomeDistinctString = InputFile|InputDir|OutPath|OutDir|OutFile +{.push gcsafe, raises: [].} + template `/`*(dir: InputDir|OutDir, path: string): auto = string(dir) / path @@ -70,3 +72,5 @@ template implicitlySelectable* {.pragma.} ## to allow the value of the discriminator to be determined ## implicitly when the user specifies any of the sub-options ## that depend on the disciminator value. + +{.pop.} diff --git a/confutils/shell_completion.nim b/confutils/shell_completion.nim index 962d5b0..d23a059 100644 --- a/confutils/shell_completion.nim +++ b/confutils/shell_completion.nim @@ -24,13 +24,21 @@ const WORDBREAKS = "\"'@><=;|&(:" SAFE_CHARS = {'a'..'z', 'A'..'Z', '0'..'9', '@', '%', '+', '=', ':', ',', '.', '/', '-'} -proc open(l: var ShellLexer, input: Stream, wordBreakChars: string = WORDBREAKS, preserveTrailingWs = true) = +{.push gcsafe, raises: [].} + +proc open(l: var ShellLexer, + input: Stream, + wordBreakChars: string = WORDBREAKS, + preserveTrailingWs = true) {.gcsafe, raises: [IOError, OSError].} = lexbase.open(l, input) l.preserveTrailingWs = preserveTrailingWs l.mergeWordBreaks = false l.wordBreakChars = wordBreakChars -proc parseQuoted(l: var ShellLexer, pos: int, isSingle: bool, output: var string): int = +proc parseQuoted(l: var ShellLexer, + pos: int, + isSingle: bool, + output: var string): int {.gcsafe, raises: [IOError, OSError].} = var pos = pos while true: case l.buf[pos]: @@ -62,7 +70,7 @@ proc parseQuoted(l: var ShellLexer, pos: int, isSingle: bool, output: var string inc(pos) return pos -proc getTok(l: var ShellLexer): Option[string] = +proc getTok(l: var ShellLexer): Option[string] {.gcsafe, raises: [IOError, OSError].} = var pos = l.bufpos # Skip the initial whitespace @@ -179,6 +187,8 @@ proc shellPathEscape*(path: string): string = result.add('\\') result.add(ch) +{.pop.} + when isMainModule: # Test data lifted from python's shlex unit-tests const data = """ @@ -265,9 +275,9 @@ foo\ bar|foo bar| echo "expected ", expected doAssert(false) - doAssert(quoteWord("") == "''") - doAssert(quoteWord("\\\"") == "'\\\"'") - doAssert(quoteWord("foobar") == "foobar") - doAssert(quoteWord("foo$bar") == "'foo$bar'") - doAssert(quoteWord("foo bar") == "'foo bar'") - doAssert(quoteWord("foo'bar") == "'foo\\'bar'") + doAssert(shellQuote("") == "''") + doAssert(shellQuote("\\\"") == "'\\\"'") + doAssert(shellQuote("foobar") == "foobar") + doAssert(shellQuote("foo$bar") == "'foo$bar'") + doAssert(shellQuote("foo bar") == "'foo bar'") + doAssert(shellQuote("foo'bar") == "'foo\\'bar'") diff --git a/confutils/std/net.nim b/confutils/std/net.nim index 18033bc..206016f 100644 --- a/confutils/std/net.nim +++ b/confutils/std/net.nim @@ -11,14 +11,16 @@ import std/net from std/parseutils import parseInt export net -func parseCmdArg*(T: type IpAddress, s: string): T = +{.push gcsafe, raises: [].} + +func parseCmdArg*(T: type IpAddress, s: string): T {.gcsafe, raises: [ValueError].} = parseIpAddress(s) func completeCmdArg*(T: type IpAddress, val: string): seq[string] = # TODO: Maybe complete the local IP address? @[] -func parseCmdArg*(T: type Port, s: string): T = +func parseCmdArg*(T: type Port, s: string): T {.gcsafe, raises: [ValueError].} = template fail = raise newException(ValueError, "The supplied port must be an integer value in the range 1-65535") @@ -34,3 +36,5 @@ func parseCmdArg*(T: type Port, s: string): T = func completeCmdArg*(T: type Port, val: string): seq[string] = @[] + +{.pop.} diff --git a/confutils/toml/defs.nim b/confutils/toml/defs.nim index c1ea5b8..b93d72e 100644 --- a/confutils/toml/defs.nim +++ b/confutils/toml/defs.nim @@ -13,6 +13,8 @@ import export toml_serialization, confutilsDefs +{.push gcsafe, raises: [].} + template readConfutilsType(T: type) = template readValue*(r: var TomlReader, val: var T) = val = T r.readValue(string) @@ -22,3 +24,5 @@ readConfutilsType InputDir readConfutilsType OutPath readConfutilsType OutDir readConfutilsType OutFile + +{.pop.} diff --git a/confutils/toml/std/net.nim b/confutils/toml/std/net.nim index 5ee8dd0..f9b434c 100644 --- a/confutils/toml/std/net.nim +++ b/confutils/toml/std/net.nim @@ -14,16 +14,20 @@ import export net, toml_serialization +{.push gcsafe, raises: [].} + proc readValue*(r: var TomlReader, val: var IpAddress) - {.raises: [SerializationError, IOError, Defect].} = + {.raises: [SerializationError, IOError].} = val = try: parseIpAddress(r.readValue(string)) except ValueError as err: - r.lex.raiseUnexpectedValue("IP address") + r.lex.raiseUnexpectedValue("IP address " & err.msg) proc readValue*(r: var TomlReader, val: var Port) - {.raises: [SerializationError, IOError, Defect].} = + {.raises: [SerializationError, IOError].} = let port = try: r.readValue(uint16) - except ValueError: - r.lex.raiseUnexpectedValue("Port") + except ValueError as exc: + r.lex.raiseUnexpectedValue("Port " & exc.msg) val = Port port + +{.pop.} diff --git a/confutils/toml/std/uri.nim b/confutils/toml/std/uri.nim index 6f18f90..c538682 100644 --- a/confutils/toml/std/uri.nim +++ b/confutils/toml/std/uri.nim @@ -14,9 +14,12 @@ import export uri, toml_serialization +{.push gcsafe, raises: [].} + proc readValue*(r: var TomlReader, val: var Uri) - {.raises: [SerializationError, IOError, Defect].} = + {.raises: [SerializationError, IOError].} = val = try: parseUri(r.readValue(string)) except ValueError as err: - r.lex.raiseUnexpectedValue("URI") + r.lex.raiseUnexpectedValue("URI " & err.msg) +{.pop.} diff --git a/confutils/winreg/reader.nim b/confutils/winreg/reader.nim index c421599..6d7918f 100644 --- a/confutils/winreg/reader.nim +++ b/confutils/winreg/reader.nim @@ -9,7 +9,7 @@ import tables, typetraits, options, - serialization/[object_serialization], + serialization/[object_serialization, errors], ./utils, ./types type @@ -24,11 +24,13 @@ type deserializedField*: string innerException*: ref CatchableError +{.push gcsafe, raises: [].} + proc handleReadException*(r: WinregReader, Record: type, fieldName: string, field: auto, - err: ref CatchableError) = + err: ref CatchableError) {.gcsafe, raises: [WinregError].} = var ex = new GenericWinregReaderError ex.deserializedField = fieldName ex.innerException = err @@ -39,7 +41,8 @@ proc init*(T: type WinregReader, result.hKey = hKey result.path = path -proc readValue*[T](r: var WinregReader, value: var T) = +proc readValue*[T](r: var WinregReader, value: var T) + {.gcsafe, raises: [SerializationError, IOError].} = mixin readValue # TODO: reduce allocation @@ -88,3 +91,5 @@ proc readValue*[T](r: var WinregReader, value: var T) = else: const typeName = typetraits.name(T) {.fatal: "Failed to convert from Winreg an unsupported type: " & typeName.} + +{.pop.} diff --git a/confutils/winreg/types.nim b/confutils/winreg/types.nim index 6e33d25..b4e6489 100644 --- a/confutils/winreg/types.nim +++ b/confutils/winreg/types.nim @@ -26,5 +26,9 @@ const HKCR* = HKEY_CLASSES_ROOT HKU* = HKEY_USERS +{.push gcsafe, raises: [].} + proc `==`*(a, b: HKEY): bool {.borrow.} proc `==`*(a, b: RegType): bool {.borrow.} + +{.pop.} diff --git a/confutils/winreg/utils.nim b/confutils/winreg/utils.nim index 43f855d..b873fda 100644 --- a/confutils/winreg/utils.nim +++ b/confutils/winreg/utils.nim @@ -26,6 +26,8 @@ const RT_QWORD* = 0x00000040 RT_ANY* = 0x0000ffff +{.push gcsafe, raises: [].} + proc regGetValue(hKey: HKEY, lpSubKey, lpValue: cstring, dwFlags: int32, pdwType: ptr RegType, pvData: pointer, pcbData: ptr int32): int32 {. @@ -39,12 +41,18 @@ template call(f) = if f != 0: return false +template safeCast(destType: type, src: typed): auto = + when sizeof(src) < sizeof(destType): + destType(src) + else: + cast[destType](src) + proc setValue*(hKey: HKEY, path, key: string, val: SomePrimitives): bool = when sizeof(val) < 8: - var dw = cast[int32](val) + var dw = int32.safeCast(val) call regSetValue(hKey, path, key, REG_DWORD, dw.addr, sizeof(dw).int32) else: - var dw = cast[int64](val) + var dw = int64.safeCast(val) call regSetValue(hKey, path, key, REG_QWORD, dw.addr, sizeof(dw).int32) result = true @@ -160,3 +168,5 @@ func constructPath*(root: string, keys: openArray[string]): string = result.add keys[i] if i < keys.len-2: result. add '\\' + +{.pop.} diff --git a/confutils/winreg/winreg_serialization.nim b/confutils/winreg/winreg_serialization.nim index 69a2588..324f7fb 100644 --- a/confutils/winreg/winreg_serialization.nim +++ b/confutils/winreg/winreg_serialization.nim @@ -14,6 +14,8 @@ import export serialization, reader, writer, types +{.push gcsafe, raises: [].} + serializationFormat Winreg Winreg.setReader WinregReader @@ -65,3 +67,5 @@ template saveFile*(_: type Winreg, filename: string, value: auto, params: vararg let (hKey, path) = parseWinregPath(filename) var writer = unpackArgs(init, [WinregWriter, hKey, path, params]) writer.writeValue(value) + +{.pop.} diff --git a/confutils/winreg/writer.nim b/confutils/winreg/writer.nim index c971430..dabfbc6 100644 --- a/confutils/winreg/writer.nim +++ b/confutils/winreg/writer.nim @@ -18,6 +18,8 @@ type path: string key: seq[string] +{.push gcsafe, raises: [].} + proc init*(T: type WinregWriter, hKey: HKEY, path: string): T = result.hKey = hKey @@ -55,3 +57,5 @@ proc writeValue*(w: var WinregWriter, value: auto) {.raises: [IOError].} = else: const typeName = typetraits.name(value.type) {.fatal: "Failed to convert to Winreg an unsupported type: " & typeName.} + +{.pop.} diff --git a/tests/specialint.nim b/tests/private/specialint.nim similarity index 100% rename from tests/specialint.nim rename to tests/private/specialint.nim diff --git a/tests/test_config_file.nim b/tests/test_config_file.nim index efedad3..75f1ab6 100644 --- a/tests/test_config_file.nim +++ b/tests/test_config_file.nim @@ -20,10 +20,7 @@ type field_a: int field_b: string - CheckPoint = int - RuntimePreset = int GraffitiBytes = array[16, byte] - WalletName = string VCStartUpCmd = enum VCNoCommand diff --git a/tests/test_envvar.nim b/tests/test_envvar.nim index 8f0eae1..a02d1bf 100644 --- a/tests/test_envvar.nim +++ b/tests/test_envvar.nim @@ -34,10 +34,6 @@ type abbr: "d" name: "data-dir" }: OutDir -func defaultObject(conf: TestConf): SomeObject = - discard - - func completeCmdArg(T: type SomeObject, val: string): seq[string] = @[] diff --git a/tests/test_qualified_ident.nim b/tests/test_qualified_ident.nim index 56d973a..6fb4b78 100644 --- a/tests/test_qualified_ident.nim +++ b/tests/test_qualified_ident.nim @@ -2,7 +2,7 @@ import std/[strutils], unittest2, ../confutils, - ./specialint + ./private/specialint type TestConf* = object