diff options
Diffstat (limited to 'tests/destructor')
84 files changed, 4905 insertions, 71 deletions
diff --git a/tests/destructor/const_smart_ptr.nim b/tests/destructor/const_smart_ptr.nim new file mode 100644 index 000000000..25dd46500 --- /dev/null +++ b/tests/destructor/const_smart_ptr.nim @@ -0,0 +1,75 @@ +type + ConstPtr*[T] = object + val: ptr T + +proc `=destroy`*[T](p: ConstPtr[T]) = + if p.val != nil: + `=destroy`(p.val[]) + dealloc(p.val) + +proc `=copy`*[T](dest: var ConstPtr[T], src: ConstPtr[T]) {.error.} + +proc `=sink`*[T](dest: var ConstPtr[T], src: ConstPtr[T]) {.inline.} = + if dest.val != nil and dest.val != src.val: + `=destroy`(dest) + dest.val = src.val + +proc newConstPtr*[T](val: sink T): ConstPtr[T] {.inline.} = + result.val = cast[type(result.val)](alloc(sizeof(result.val[]))) + reset(result.val[]) + result.val[] = val + +converter convertConstPtrToObj*[T](p: ConstPtr[T]): lent T = + result = p.val[] + + +#------------------------------------------------------------- + +type + MySeqNonCopyable* = object + len: int + data: ptr UncheckedArray[float] + +proc `=destroy`*(m: MySeqNonCopyable) {.inline.} = + if m.data != nil: + deallocShared(m.data) + +proc `=copy`*(m: var MySeqNonCopyable, m2: MySeqNonCopyable) {.error.} + +proc `=sink`*(m: var MySeqNonCopyable, m2: MySeqNonCopyable) {.inline.} = + if m.data != m2.data: + if m.data != nil: + `=destroy`(m) + m.len = m2.len + m.data = m2.data + +proc len*(m: MySeqNonCopyable): int {.inline.} = m.len + +proc `[]`*(m: MySeqNonCopyable; i: int): float {.inline.} = + m.data[i.int] + +proc `[]=`*(m: var MySeqNonCopyable; i: int, val: float) {.inline.} = + m.data[i.int] = val + +proc setTo(s: var MySeqNonCopyable, val: float) = + for i in 0..<s.len.int: + s.data[i] = val + +proc newMySeq*(size: int, initial_value = 0.0): MySeqNonCopyable = + result.len = size + if size > 0: + result.data = cast[ptr UncheckedArray[float]](createShared(float, size)) + result.setTo(initial_value) + +#---------------------------------------------------------------------- + + +proc test*(x1: int): ConstPtr[MySeqNonCopyable] {.inline.} = # remove inline here to make it work as expected + if x1 == 0: + let x = newMySeq(1, 0.0) + result = newConstPtr(x) + else: + let y = newMySeq(x1, 0.0) + result = newConstPtr(y) + +discard test(10) diff --git a/tests/destructor/helper.nim b/tests/destructor/helper.nim new file mode 100644 index 000000000..466065747 --- /dev/null +++ b/tests/destructor/helper.nim @@ -0,0 +1,3 @@ +type + MyTestObject*[T] = object + p: ptr T diff --git a/tests/destructor/nim.cfg b/tests/destructor/nim.cfg new file mode 100644 index 000000000..7c148b797 --- /dev/null +++ b/tests/destructor/nim.cfg @@ -0,0 +1 @@ +--sinkInference:on diff --git a/tests/destructor/objFile.nim b/tests/destructor/objFile.nim new file mode 100644 index 000000000..436c090d1 --- /dev/null +++ b/tests/destructor/objFile.nim @@ -0,0 +1,8 @@ +type Obj* = object + v*: int + +proc `=destroy`(this: var Obj) = + echo "igotdestroyed" + this.v = -1 + +var test* = Obj(v: 42) diff --git a/tests/destructor/smart_ptr.nim b/tests/destructor/smart_ptr.nim new file mode 100644 index 000000000..5079dc9db --- /dev/null +++ b/tests/destructor/smart_ptr.nim @@ -0,0 +1,43 @@ + +type + SharedPtr*[T] = object + val: ptr tuple[atomicCounter: int, value: T] + +proc `=destroy`*[T](p: var SharedPtr[T]) = + mixin `=destroy` + if p.val != nil: + let c = atomicDec(p.val[].atomicCounter) + if c == 0: + `=destroy`(p.val.value) + freeShared(p.val) + p.val = nil + +proc `=`*[T](dest: var SharedPtr[T], src: SharedPtr[T]) {.inline.} = + if dest.val != src.val: + if dest.val != nil: + `=destroy`(dest) + if src.val != nil: + discard atomicInc(src.val[].atomicCounter) + dest.val = src.val + +proc newSharedPtr*[T](val: sink T): SharedPtr[T] = + result.val = cast[type(result.val)](allocShared0(sizeof(result.val[]))) + result.val.atomicCounter = 1 + result.val.value = val + +func get*[T](p: SharedPtr[T]): var T {.inline.} = + p.val.value + +func isNil*[T](p: SharedPtr[T]): bool {.inline.} = + p.val == nil + +proc cas*[T](p, old_val: var SharedPtr[T], new_val: SharedPtr[T]): bool {.inline.} = + if old_val.val == new_val.val: + result = true + else: + result = cas(p.val.addr, old_val.val, new_val.val) + if result: + `=destroy`(old_val) + if new_val.val != nil: + discard atomicInc(new_val.val[].atomicCounter) + diff --git a/tests/destructor/t12037.nim b/tests/destructor/t12037.nim new file mode 100644 index 000000000..30266690f --- /dev/null +++ b/tests/destructor/t12037.nim @@ -0,0 +1,34 @@ +discard """ + cmd: '''nim c --gc:arc $file''' + output: ''' +showing original type, length, and contents seq[int] 1 @[42] +copy length and contents 1 @[42] +''' +""" + +proc test() = + var sq1 = @[42] + echo "showing original type, length, and contents ", sq1.typeof, " ", sq1.len, " ", sq1 + doAssert cast[int](sq1[0].addr) != 0 + var sq2 = sq1 # copy of original + echo "copy length and contents ", sq2.len, " ", sq2 + doAssert cast[int](sq2[0].addr) != 0 + doAssert cast[int](sq1[0].addr) != 0 + +test() + + +############################################# +### bug 12820 +import tables +var t = initTable[string, seq[ptr int]]() +discard t.hasKeyOrPut("f1", @[]) + + +############################################# +### bug #12989 +proc bug(start: (seq[int], int)) = + let (s, i) = start + +let input = @[0] +bug((input, 0)) diff --git a/tests/destructor/t16607.nim b/tests/destructor/t16607.nim new file mode 100644 index 000000000..f98a6d517 --- /dev/null +++ b/tests/destructor/t16607.nim @@ -0,0 +1,23 @@ +discard """ + matrix: "--gc:refc; --gc:arc" +""" + +# bug #16607 + +type + O {.requiresInit.} = object + initialized: bool + +proc `=destroy`(o: var O) = + doAssert o.initialized, "O was destroyed before initialization!" + +proc initO(): O = + O(initialized: true) + +proc pair(): tuple[a, b: O] = + result = (a: initO(), b: initO()) + +proc main() = + discard pair() + +main() diff --git a/tests/destructor/t17198.nim b/tests/destructor/t17198.nim new file mode 100644 index 000000000..098db8245 --- /dev/null +++ b/tests/destructor/t17198.nim @@ -0,0 +1,32 @@ +discard """ + cmd: '''nim c --gc:arc $file''' + output: ''' +other +''' +""" + +import std/macros + +macro bigCaseStmt(arg: untyped): untyped = + result = nnkCaseStmt.newTree(arg) + + # try to change 2000 to a bigger value if it doesn't crash + for x in 0 ..< 2000: + result.add nnkOfBranch.newTree(newStrLitNode($x), newStrLitNode($x)) + + result.add nnkElse.newTree(newStrLitNode("other")) + +macro bigIfElseExpr(): untyped = + result = nnkIfExpr.newTree() + + for x in 0 ..< 1000: + result.add nnkElifExpr.newTree(newLit(false), newStrLitNode($x)) + + result.add nnkElseExpr.newTree(newStrLitNode("other")) + +proc test(arg: string): string = + echo bigIfElseExpr() + + result = bigCaseStmt(arg) + +discard test("test") diff --git a/tests/destructor/t23748.nim b/tests/destructor/t23748.nim new file mode 100644 index 000000000..a3738733e --- /dev/null +++ b/tests/destructor/t23748.nim @@ -0,0 +1,31 @@ +discard """ + matrix: "--gc:refc; --gc:arc" + output: ''' +hello 42 +hello 42 +len = 2 +''' +""" + +# bug #23748 + +type + O = ref object + s: string + cb: seq[proc()] + +proc push1(o: O, i: int) = + let o = o + echo o.s, " ", i + o.cb.add(proc() = echo o.s, " ", i) + +proc push2(o: O, i: int) = + let o = o + echo o.s, " ", i + proc p() = echo o.s, " ", i + o.cb.add(p) + +let o = O(s: "hello", cb: @[]) +o.push1(42) +o.push2(42) +echo "len = ", o.cb.len diff --git a/tests/destructor/t23837.nim b/tests/destructor/t23837.nim new file mode 100644 index 000000000..e219dd6b5 --- /dev/null +++ b/tests/destructor/t23837.nim @@ -0,0 +1,51 @@ +discard """ + output: ''' +Deallocating OwnedString +HelloWorld +''' + matrix: "--cursorinference:on; --cursorinference:off" + target: "c" +""" + +# bug #23837 +{. + emit: [ + """ +#include <stdlib.h> +#include <string.h> +char *allocCString() { + char *result = (char *) malloc(10 + 1); + strcpy(result, "HelloWorld"); + return result; +} + +""" + ] +.} + +proc rawWrapper(): cstring {.importc: "allocCString", cdecl.} +proc free(p: pointer) {.importc: "free", cdecl.} + +# ------------------------- + +type OwnedString = distinct cstring + +proc `=destroy`(s: OwnedString) = + free(cstring s) + echo "Deallocating OwnedString" + +func `$`(s: OwnedString): string {.borrow.} + +proc leakyWrapper(): string = + let ostring = rawWrapper().OwnedString + $ostring + +# ------------------------- + +proc main() = + # destructor not called - definitely lost: 11 bytes in 1 blocks + # doesn't leak with --cursorInference:off + let s = leakyWrapper() + echo s + +main() \ No newline at end of file diff --git a/tests/destructor/t5342.nim b/tests/destructor/t5342.nim new file mode 100644 index 000000000..0acd5ef9d --- /dev/null +++ b/tests/destructor/t5342.nim @@ -0,0 +1,24 @@ +discard """ + matrix: "--mm:refc; --mm:arc" + targets: "c js" + output: ''' +1 +2 +here +2 +1 +''' +""" + + +type + A = object + id: int + B = object + a: A +proc `=destroy`(a: var A) = echo a.id +var x = A(id: 1) +var y = B(a: A(id: 2)) +`=destroy`(x) +`=destroy`(y) +echo "here" \ No newline at end of file diff --git a/tests/destructor/t7346.nim b/tests/destructor/t7346.nim new file mode 100644 index 000000000..3834d39ff --- /dev/null +++ b/tests/destructor/t7346.nim @@ -0,0 +1,14 @@ +discard """ +joinable: false +""" + +# This bug could only be reproduced with --newruntime + +type + Obj = object + a: int + +proc `=`(a: var Obj, b: Obj) = discard + +let a: seq[Obj] = @[] # bug #7346 +let b = newSeq[Obj]() # bug #7345 diff --git a/tests/destructor/t9440.nim b/tests/destructor/t9440.nim new file mode 100644 index 000000000..153bb303d --- /dev/null +++ b/tests/destructor/t9440.nim @@ -0,0 +1,52 @@ +discard """ + matrix: "--gc:refc; --gc:orc; --gc:arc" + output: ''' +() +Destroyed +() +Destroyed +() +Destroyed +end +------------------------- +() +Destroyed +end +''' + +""" + +# bug #9440 +block: + type + X = object + + proc `=destroy`(x: var X) = + echo "Destroyed" + + proc main() = + for x in 0 .. 2: + var obj = X() + echo obj + # The destructor call is invoked after "end" is printed + echo "end" + + main() + +echo "-------------------------" + +block: + type + X = object + + proc `=destroy`(x: var X) = + echo "Destroyed" + + proc main() = + block: + var obj = X() + echo obj + # The destructor is not called when obj goes out of scope + echo "end" + + main() diff --git a/tests/destructor/tarc.nim b/tests/destructor/tarc.nim new file mode 100644 index 000000000..54d75a410 --- /dev/null +++ b/tests/destructor/tarc.nim @@ -0,0 +1,184 @@ +discard """ + output: ''' +@[1, 2, 3] +Success +@["a", "b", "c"] +Hello +1 +2 +0 +List +@["4", "5", "6", "", "", "a", ""] +@["", "", "a", ""] +''' + cmd: '''nim c --gc:arc $file''' +""" + +import os +import math +import lists +import strutils + +proc mkleak() = + # allocate 1 MB via linked lists + let numberOfLists = 100 + for i in countUp(1, numberOfLists): + var leakList = initDoublyLinkedList[string]() + let numberOfLeaks = 5000 + for j in countUp(1, numberOfLeaks): + leakList.append(newString(200)) + +proc mkManyLeaks() = + for i in 0..0: + mkleak() + echo "Success" + +iterator foobar(c: string): seq[string] {.closure.} = + yield @["a", "b", c] + +proc tsimpleClosureIterator = + var myc = "c" + for it in foobar(myc): + echo it + +type + LazyList = ref object + c: proc() {.closure.} + +proc tlazyList = + let dep = @[1, 2, 3] + var x = LazyList(c: proc () = echo(dep)) + x.c() + +type + Foo = ref object + +proc tleakingNewStmt = + var x: Foo + for i in 0..10: + new(x) + +iterator infinite(): int {.closure.} = + var i = 0 + while true: + yield i + inc i + +iterator take(it: iterator (): int, numToTake: int): int {.closure.} = + var i = 0 + for x in it(): + if i >= numToTake: + break + yield x + inc i + +proc take3 = + for x in infinite.take(3): + discard + + +type + A = ref object of RootObj + x: int + + B = ref object of A + more: string + +proc inheritanceBug(param: string) = + var s: (A, A) + s[0] = B(more: "a" & param) + s[1] = B(more: "a" & param) + + +type + PAsyncHttpServer = ref object + value: string + +proc serve(server: PAsyncHttpServer) = discard + +proc leakObjConstr = + serve(PAsyncHttpServer(value: "asdas")) + +let startMem = getOccupiedMem() +take3() +tlazyList() +inheritanceBug("whatever") +mkManyLeaks() +tsimpleClosureIterator() +tleakingNewStmt() +leakObjConstr() + +# bug #12964 + +type + Token* = ref object of RootObj + Li* = ref object of Token + +proc bug12964*() = + var token = Li() + var tokens = @[Token()] + tokens.add token + +bug12964() + +# bug #13119 +import streams + +proc bug13119 = + var m = newStringStream("Hello world") + let buffer = m.readStr(5) + echo buffer + m.close + +bug13119() + +# bug #13105 + +type + Result[T, E] = object + a: T + b: E + D = ref object + x: int + R = Result[D, int] + +proc bug13105 = + for n in [R(b: 1), R(b: 2)]: + echo n.b + +bug13105() + +echo getOccupiedMem() - startMem + + +#------------------------------------------------------------------------------ +# issue #14294 + +import tables + +type + TagKind = enum + List = 0, Compound + + Tag = object + case kind: TagKind + of List: + values: seq[Tag] + of Compound: + compound: Table[string, Tag] + +var a = Tag(kind: List) +var b = a +echo a.kind +var c = a + +proc testAdd(i: int; yyy: openArray[string]) = + var x: seq[string] + x.add [$i, $(i+1), $(i+2)] + x.add yyy + echo x + +var y = newSeq[string](4) +y[2] = "a" +testAdd(4, y) +echo y diff --git a/tests/destructor/tarc2.nim b/tests/destructor/tarc2.nim new file mode 100644 index 000000000..a7d7b4945 --- /dev/null +++ b/tests/destructor/tarc2.nim @@ -0,0 +1,31 @@ +discard """ + output: '''leak: false''' + cmd: '''nim c --gc:orc $file''' +""" + +type + T = ref object + s: seq[T] + data: string + +proc create(): T = T(s: @[], data: "abc") + +proc addX(x: T; data: string) = + x.data = data + +{.push sinkInference: off.} + +proc addX(x: T; child: T) = + x.s.add child + +{.pop.} + +proc main(rootName: string) = + var root = create() + root.data = rootName + root.addX root + +let mem = getOccupiedMem() +main("yeah") +GC_fullCollect() +echo "leak: ", getOccupiedMem() - mem > 0 diff --git a/tests/destructor/tarc3.nim b/tests/destructor/tarc3.nim new file mode 100644 index 000000000..55d0ea42d --- /dev/null +++ b/tests/destructor/tarc3.nim @@ -0,0 +1,101 @@ + +discard """ + cmd: '''nim c --gc:arc $file''' +""" + +when defined(cpp): + {.passC: "-std=gnu++2a".} + +type + TokenKind* = enum + tkColon + tkComma + tkString + tkNumber + tkInt64 + tkIdent + + Token* = object + case kind*: TokenKind + of tkString: strVal*: string + of tkNumber: numVal*: float + of tkInt64: int64Val*: int64 + of tkIdent: ident*: string + else: discard + pos*: Natural + + + Token2* = object + case kind*: TokenKind + of tkString: strVal*: string + of tkNumber: numVal*: float + of tkInt64, tkColon..tkComma: + str1*: array[2, string] + float: float + else: discard + pos*: Natural + + Token3* = object + case kind*: TokenKind + of tkNumber: numVal*: float + of tkInt64, tkComma..tkString: ff: seq[float] + else: str1*: string + + Token4* = object + case kind*: TokenKind + of tkNumber: numVal*: float + of tkInt64, tkComma..tkString: ff: seq[float] + else: str1*: string + case kind2*: TokenKind + of tkNumber: + numVal2*: float + intSeqVal3*: seq[int] + of tkInt64, tkComma..tkString: + case kind3*: TokenKind + of tkNumber: numVal3*: float + of tkInt64, tkComma..tkString: + ff3: seq[float] + ff5: string + else: + str3*: string + mysrq: seq[int] + else: + case kind4*: TokenKind + of tkNumber: numVal4*: float + of tkInt64, tkComma..tkString: ff4: seq[float] + else: str4*: string + + BaseLexer* = object of RootObj + input*: string + pos*: Natural + + Json5Lexer* = object of BaseLexer + + JsonLexer* = object of BaseLexer + allowComments*: bool + allowSpecialFloats*: bool + + Lexer* = Json5Lexer | JsonLexer + + Parser[T: Lexer] = object + l: T + tok: Token + tok2: Token2 + tok3: Token3 + tok4: Token4 + allowTrailingComma: bool + allowIdentifierObjectKey: bool + +proc initJson5Lexer*(input: string): Json5Lexer = + result.input = input + +proc parseJson5*(input: string) = + var p = Parser[Json5Lexer]( + l: initJson5Lexer(input), + allowTrailingComma: true, + allowIdentifierObjectKey: true + ) + + +let x = "string" +parseJson5(x) \ No newline at end of file diff --git a/tests/destructor/tarctypesections.nim b/tests/destructor/tarctypesections.nim new file mode 100644 index 000000000..da81f1884 --- /dev/null +++ b/tests/destructor/tarctypesections.nim @@ -0,0 +1,70 @@ +discard """ + output: "MEM 0" + cmd: "nim c --gc:arc $file" +""" + +type + RefNode = ref object + le, ri: RefNode + name: char + +proc edge0(a, b: RefNode) = + if a.le == nil: a.le = b + else: a.ri = b + +proc createNode0(name: char): RefNode = + new result + result.name = name + +proc main0 = + let r = createNode0('R') + let c = createNode0('C') + c.edge0 r + + +type + NodeDesc = object + le, ri: Node + name: char + Node = ref NodeDesc + +proc edge(a, b: Node) = + if a.le == nil: a.le = b + else: a.ri = b + +proc createNode(name: char): Node = + new result + result.name = name + +proc main = + let r = createNode('R') + let c = createNode('C') + c.edge r + + +type + NodeB = ref NodeBo + NodeBo = object + le, ri: NodeB + name: char + +proc edge(a, b: NodeB) = + if a.le == nil: a.le = b + else: a.ri = b + +proc createNodeB(name: char): NodeB = + new result + result.name = name + + +proc mainB = + let r = createNodeB('R') + let c = createNodeB('C') + c.edge r + + +let memB = getOccupiedMem() +main0() +main() +mainB() +echo "MEM ", getOccupiedMem() - memB diff --git a/tests/destructor/tarray_indexing.nim b/tests/destructor/tarray_indexing.nim new file mode 100644 index 000000000..a9dfdf4ed --- /dev/null +++ b/tests/destructor/tarray_indexing.nim @@ -0,0 +1,75 @@ +discard """ + output: '''allocating 1048576 65536 +filling page from 1048576 len 65536''' + cmd: '''nim c --gc:arc $file''' +""" + +# bug #12669 + +type + MemState* = enum + memPrivate + + MemPermisison* = enum + memperm_Read + + MemInfo* = ref object + base*, size*: uint32 + state*: MemState + perm*: set[MemPermisison] + + MemBlock = ref object + info: MemInfo + data: seq[byte] + + UserProcessMemory* = ref object + pageAccess: array[0x40000, ptr UncheckedArray[byte]] + pages: array[0x40000, MemInfo] + blocks: seq[owned MemBlock] + +proc allocMemory*(mem: UserProcessMemory, base, size: uint32) = + let + roundedBase = base and not(0xFFF'u32) + roundedSize = (size + 0xFFF) and not(0xFFF'u32) + + echo "allocating ", base, " ", size + for i in (roundedBase shr 12)..<((roundedBase + roundedSize) shr 12): + #echo "span ", i + doAssert mem.pages[i] == nil + # TODO: beserer fehler + + let memBlock = MemBlock( + info: MemInfo( + base: roundedBase, + size: roundedSize, + state: memPrivate, + perm: {memperm_Read} + ), + data: newSeq[byte](roundedSize)) + for i in 0..<(roundedSize shr 12): + mem.pages[i + (roundedBase shr 12)] = memBlock.info + #echo cast[uint64](addr mem.pageAccess[i + (roundedBase shr 12)]) + mem.pageAccess[i + (roundedBase shr 12)] = cast[ptr UncheckedArray[byte]](addr memBlock.data[i * 0x1000]) + mem.blocks.add memBlock + + #for i in (roundedBase shr 12)..<((roundedBase + roundedSize) shr 12): + # assert mem.pageAccess[i] != nil + +proc fillPages*(mem: UserProcessMemory, start: uint32, data: seq[byte]) = + echo "filling page from ", start, " len ", data.len + assert (start and not(0xFFF'u32)) == start + assert (uint32(data.len) and not(0xFFF'u32)) == uint32(data.len) + for i in (start shr 12)..<((start + uint32(data.len)) shr 12): + #echo cast[uint64](addr mem.pageAccess[i]) + let page = mem.pageAccess[i] + assert page != nil + #copyMem(page, addr data[i * 0x1000 - start], 0x1000) + +const base = 0x00100000 + +proc a(): owned UserProcessMemory = + result = UserProcessMemory() + result.allocMemory(base, 0x1000 * 16) + result.fillPages(base, newSeq[byte](0x1000 * 16)) + +discard a() diff --git a/tests/destructor/tasync_prototype.nim b/tests/destructor/tasync_prototype.nim new file mode 100644 index 000000000..81fd824e9 --- /dev/null +++ b/tests/destructor/tasync_prototype.nim @@ -0,0 +1,59 @@ +discard """ + output: '''asdas +processClient end +false +MEMORY 0 +''' + cmd: '''nim c --gc:arc $file''' +""" + +type + PAsyncHttpServer = ref object + value: string + PFutureBase {.acyclic.} = ref object + callback: proc () {.closure.} + value: string + failed: bool + +proc accept(server: PAsyncHttpServer): PFutureBase = + new(result) + result.callback = proc () = + discard + server.value = "hahaha" + +proc processClient(): PFutureBase = + new(result) + +proc serve(server: PAsyncHttpServer): PFutureBase = + iterator serveIter(): PFutureBase {.closure.} = + echo server.value + while true: + var acceptAddrFut = server.accept() + yield acceptAddrFut + var fut = acceptAddrFut.value + + # with the new scope based destruction, this cannot + # possibly work: + var f {.cursor.} = processClient() + # It also seems to be the wrong way how to avoid the + # cycle. The cycle is caused by capturing the 'env' + # part from 'env.f'. + when true: + f.callback = + proc () = + echo("processClient end") + echo(f.failed) + yield f + var x = serveIter + for i in 0 .. 1: + result = x() + if result.callback != nil: + result.callback() + +let mem = getOccupiedMem() + +proc main = + discard serve(PAsyncHttpServer(value: "asdas")) + +main() +echo "MEMORY ", getOccupiedMem() - mem diff --git a/tests/destructor/tasync_prototype_cyclic.nim b/tests/destructor/tasync_prototype_cyclic.nim new file mode 100644 index 000000000..8ba73a8fc --- /dev/null +++ b/tests/destructor/tasync_prototype_cyclic.nim @@ -0,0 +1,55 @@ +discard """ + output: '''asdas +processClient end +false +MEMORY 0 +''' + cmd: '''nim c --gc:orc $file''' +""" + +type + PAsyncHttpServer = ref object + value: string + PFutureBase = ref object + callback: proc () {.closure.} + value: string + failed: bool + +proc accept(server: PAsyncHttpServer): PFutureBase = + new(result) + result.callback = proc () = + discard + server.value = "hahaha" + +proc processClient(): PFutureBase = + new(result) + +proc serve(server: PAsyncHttpServer): PFutureBase = + iterator serveIter(): PFutureBase {.closure.} = + echo server.value + while true: + var acceptAddrFut = server.accept() + yield acceptAddrFut + var fut = acceptAddrFut.value + + var f = processClient() + when true: + f.callback = + proc () = + echo("processClient end") + echo(f.failed) + yield f + var x = serveIter + for i in 0 .. 1: + result = x() + if result.callback != nil: + result.callback() + +let mem = getOccupiedMem() + +proc main = + discard serve(PAsyncHttpServer(value: "asdas")) + +main() +GC_fullCollect() +echo "MEMORY ", getOccupiedMem() - mem diff --git a/tests/destructor/tatomicptrs.nim b/tests/destructor/tatomicptrs.nim index d20596415..82870ac82 100644 --- a/tests/destructor/tatomicptrs.nim +++ b/tests/destructor/tatomicptrs.nim @@ -8,8 +8,10 @@ allocating deallocating deallocating deallocating +allocating +deallocating ''' - cmd: '''nim c --newruntime $file''' +joinable: false """ type @@ -23,10 +25,9 @@ template incRef(x) = template decRef(x): untyped = atomicDec(x.refcount) -proc makeShared*[T](x: T): SharedPtr[T] = - # XXX could benefit from 'sink' parameter. +proc makeShared*[T](x: sink T): SharedPtr[T] = # XXX could benefit from a macro that generates it. - result = cast[SharedPtr[T]](allocShared(sizeof(x))) + result = cast[SharedPtr[T]](allocShared0(sizeof(x))) result.x[] = x echo "allocating" @@ -38,7 +39,7 @@ proc `=destroy`*[T](dest: var SharedPtr[T]) = echo "deallocating" dest.x = nil -proc `=`*[T](dest: var SharedPtr[T]; src: SharedPtr[T]) = +proc `=copy`*[T](dest: var SharedPtr[T]; src: SharedPtr[T]) = var s = src.x if s != nil: incRef(s) #atomicSwap(dest, s) @@ -49,6 +50,9 @@ proc `=`*[T](dest: var SharedPtr[T]; src: SharedPtr[T]) = deallocShared(s) echo "deallocating" +proc `=dup`*[T](src: SharedPtr[T]): SharedPtr[T] = + `=copy`(result, src) + proc `=sink`*[T](dest: var SharedPtr[T]; src: SharedPtr[T]) = ## XXX make this an atomic store: if dest.x != src.x: @@ -59,6 +63,9 @@ proc `=sink`*[T](dest: var SharedPtr[T]; src: SharedPtr[T]) = echo "deallocating" dest.x = src.x +proc get*[T](s: SharedPtr[T]): lent T = + s.x[] + template `.`*[T](s: SharedPtr[T]; field: untyped): untyped = s.x.field @@ -68,6 +75,7 @@ template `.=`*[T](s: SharedPtr[T]; field, value: untyped) = from macros import unpackVarargs template `.()`*[T](s: SharedPtr[T]; field: untyped, args: varargs[untyped]): untyped = + # xxx this isn't used, the test should be improved unpackVarargs(s.x.field, args) @@ -99,3 +107,70 @@ proc main = main() + + +#------------------------------------------------------- +#bug #9781 + +type + MySeq* [T] = object + refcount: int + len: int + data: ptr UncheckedArray[T] + +proc `=destroy`*[T](m: var MySeq[T]) {.inline.} = + if m.data != nil: + deallocShared(m.data) + m.data = nil + +proc `=copy`*[T](m: var MySeq[T], m2: MySeq[T]) = + if m.data == m2.data: return + if m.data != nil: + `=destroy`(m) + + m.len = m2.len + let bytes = m.len.int * sizeof(float) + if bytes > 0: + m.data = cast[ptr UncheckedArray[T]](allocShared(bytes)) + copyMem(m.data, m2.data, bytes) + +proc `=dup`*[T](m: MySeq[T]): MySeq[T] = + `=copy`[T](result, m) + +proc `=sink`*[T](m: var MySeq[T], m2: MySeq[T]) {.inline.} = + if m.data != m2.data: + if m.data != nil: + `=destroy`(m) + m.len = m2.len + m.data = m2.data + m.refcount = m2.refcount + +proc len*[T](m: MySeq[T]): int {.inline.} = m.len + +proc newMySeq*[T](size: int, initial_value: T): MySeq[T] = + result.len = size + result.refcount = 1 + if size > 0: + result.data = cast[ptr UncheckedArray[T]](allocShared(sizeof(T) * size)) + + +let x = makeShared(newMySeq(10, 1.0)) +doAssert: x.get().len == 10 + + + +#------------------------------------------------------- +#bug #12882 + +type + ValueObject = object + v: MySeq[int] + name: string + + TopObject = object + internal: seq[ValueObject] + +var zz = new(TopObject) + + + diff --git a/tests/destructor/tbintree2.nim b/tests/destructor/tbintree2.nim new file mode 100644 index 000000000..d56c2850b --- /dev/null +++ b/tests/destructor/tbintree2.nim @@ -0,0 +1,101 @@ +discard """ + cmd: '''nim c -d:nimAllocStats --newruntime $file''' + output: '''0 +(allocCount: 5, deallocCount: 5)''' +""" + +import system / ansi_c + +import random + +type Node = ref object + x, y: int32 + left, right: owned Node + +proc newNode(x: int32): owned Node = + result = Node(x: x, y: rand(high int32).int32) + +proc merge(lower, greater: owned Node): owned Node = + if lower.isNil: + result = greater + elif greater.isNil: + result = lower + elif lower.y < greater.y: + lower.right = merge(move lower.right, greater) + result = lower + else: + greater.left = merge(lower, move greater.left) + result = greater + +proc splitBinary(orig: owned Node, value: int32): (owned Node, owned Node) = + if orig.isNil: + result = (nil, nil) + elif orig.x < value: + let splitPair = splitBinary(move orig.right, value) + orig.right = splitPair[0] + result = (orig, splitPair[1]) + else: + let splitPair = splitBinary(move orig.left, value) + orig.left = splitPair[1] + result = (splitPair[0], orig) + +proc merge3(lower, equal, greater: owned Node): owned Node = + merge(merge(lower, equal), greater) + +proc split(orig: owned Node, value: int32): tuple[lower, equal, greater: owned Node] = + let + (lower, equalGreater) = splitBinary(orig, value) + (equal, greater) = splitBinary(equalGreater, value + 1) + result = (lower, equal, greater) + +type Tree = object + root: owned Node + +proc `=destroy`(t: var Tree) {.nodestroy.} = + var s: seq[owned Node] = @[t.root] + while s.len > 0: + let x = s.pop + if x.left != nil: s.add(x.left) + if x.right != nil: s.add(x.right) + `=dispose`(x) + `=destroy`(s) + +proc hasValue(self: var Tree, x: int32): bool = + let splited = split(move self.root, x) + result = not splited.equal.isNil + self.root = merge3(splited.lower, splited.equal, splited.greater) + +proc insert(self: var Tree, x: int32) = + var splited = split(move self.root, x) + if splited.equal.isNil: + splited.equal = newNode(x) + self.root = merge3(splited.lower, splited.equal, splited.greater) + +proc erase(self: var Tree, x: int32) = + let splited = split(move self.root, x) + self.root = merge(splited.lower, splited.greater) + +proc main() = + var + tree = Tree() + cur = 5'i32 + res = 0 + + for i in 1 ..< 10: + let a = i mod 3 + cur = (cur * 57 + 43) mod 10007 + case a: + of 0: + tree.insert(cur) + of 1: + tree.erase(cur) + of 2: + if tree.hasValue(cur): + res += 1 + else: + discard + echo res + +dumpAllocStats: + main() + diff --git a/tests/destructor/tcaseobj_transitions.nim b/tests/destructor/tcaseobj_transitions.nim new file mode 100644 index 000000000..61464101f --- /dev/null +++ b/tests/destructor/tcaseobj_transitions.nim @@ -0,0 +1,49 @@ +discard """ + cmd: '''nim c --gc:arc $file''' + output: '''no crash''' +""" + +# bug #11205 + +type + MyEnum = enum + A, B, C + MyCaseObject = object + case kind: MyEnum + of A: iseq: seq[int] + of B: fseq: seq[float] + of C: str: string + + + MyCaseObjectB = object # carefully constructed to use the same enum, + # but a different object type! + case kind: MyEnum + of A, C: x: int + of B: fseq: seq[float] + + +var x = MyCaseObject(kind: A) +x.iseq.add 1 +#x.kind = B +#x.fseq.add -3.0 + +var y = MyCaseObjectB(kind: A) +y.x = 1 +y.kind = C +echo "no crash" + + +################# +## bug #12821 + +type + RefBaseObject* = ref object of RootObj + case kind: bool + of true: a: int + of false: b: float + + MyRefObject = ref object of RefBaseObject + x: float + +let z = new(MyRefObject) +z.kind = false diff --git a/tests/destructor/tcast.nim b/tests/destructor/tcast.nim new file mode 100644 index 000000000..35a7d874b --- /dev/null +++ b/tests/destructor/tcast.nim @@ -0,0 +1,14 @@ +# Make sure we don't walk cast[T] type section while injecting sinks/destructors +block: + type + XY[T] = object + discard + + proc `=`[T](x: var XY[T]; v: XY[T]) {.error.} + proc `=sink`[T](x: var XY[T]; v: XY[T]) {.error.} + + proc main[T]() = + var m = cast[ptr XY[T]](alloc0(sizeof(XY[T]))) + doAssert(m != nil) + + main[int]() diff --git a/tests/destructor/tcomplexobjconstr.nim b/tests/destructor/tcomplexobjconstr.nim new file mode 100644 index 000000000..aea0ad1fe --- /dev/null +++ b/tests/destructor/tcomplexobjconstr.nim @@ -0,0 +1,56 @@ +discard """ + output: '''true +OK''' + cmd: "nim c --gc:arc $file" +""" + +# bug #12826 + +type + MyObject1* = object of RootObj + z*: string + + MyObject2* = object of RootObj + x*: float + name*: string + subobj: MyObject1 + case flag*: bool + of false: + more: array[3, MyObject1] + of true: y*: float + +var x = new(MyObject2) +doAssert x of MyObject2 +doAssert x.subobj of MyObject1 +doAssert x.more[2] of MyObject1 +doAssert x.more[2] of RootObj + +var y: MyObject2 +doAssert y of MyObject2 +doAssert y.subobj of MyObject1 +doAssert y.more[2] of MyObject1 +doAssert y.more[2] of RootObj + +echo "true" + +# bug #12978 +type + Vector2* = object of RootObj + x*, y*: float + +type + Vertex* = ref object + point*: Vector2 + +proc newVertex*(p: Vector2): Vertex = + return Vertex(point: p) + +proc createVertex*(p: Vector2): Vertex = + result = newVertex(p) + +proc p = + var x = Vector2(x: 1, y: 2) + let other = createVertex(x) + echo "OK" + +p() diff --git a/tests/destructor/tconst_smart_ptr.nim b/tests/destructor/tconst_smart_ptr.nim new file mode 100644 index 000000000..39fe12612 --- /dev/null +++ b/tests/destructor/tconst_smart_ptr.nim @@ -0,0 +1,7 @@ +discard """ + action: "compile" +""" + +import const_smart_ptr + +discard test(0) diff --git a/tests/destructor/tconsume_twice.nim b/tests/destructor/tconsume_twice.nim new file mode 100644 index 000000000..b0a039e9b --- /dev/null +++ b/tests/destructor/tconsume_twice.nim @@ -0,0 +1,15 @@ +discard """ + cmd: "nim c --newruntime $file" + errormsg: "'=copy' is not available for type <owned Foo>; requires a copy because it's not the last read of 'a'; another read is done here: tconsume_twice.nim(13, 10); routine: consumeTwice" + line: 11 +""" +type + Foo = ref object + +proc use(a: owned Foo): bool = discard +proc consumeTwice(a: owned Foo): owned Foo = + if use(a): + return + return a + +assert consumeTwice(Foo()) != nil diff --git a/tests/destructor/tcustomseqs.nim b/tests/destructor/tcustomseqs.nim index 97d7c07b6..17a19f871 100644 --- a/tests/destructor/tcustomseqs.nim +++ b/tests/destructor/tcustomseqs.nim @@ -16,7 +16,7 @@ discard """ 1 2 6 1 3 7 after 6 6''' - cmd: '''nim c --newruntime $file''' +joinable: false """ import typetraits @@ -43,9 +43,10 @@ proc `=destroy`*[T](x: var myseq[T]) = proc `=`*[T](a: var myseq[T]; b: myseq[T]) = if a.data == b.data: return if a.data != nil: - dealloc(a.data) - inc deallocCount - a.data = nil + `=destroy`(a) + #dealloc(a.data) + #inc deallocCount + #a.data = nil a.len = b.len a.cap = b.cap if b.data != nil: @@ -120,7 +121,7 @@ proc createSeq*[T](elems: varargs[T]): myseq[T] = result.data = cast[type(result.data)](alloc(result.cap * sizeof(T))) inc allocCount when supportsCopyMem(T): - copyMem(result.data, unsafeAddr(elems[0]), result.cap * sizeof(T)) + copyMem(result.data, addr(elems[0]), result.cap * sizeof(T)) else: for i in 0..<result.len: result.data[i] = elems[i] diff --git a/tests/destructor/tcustomstrings.nim b/tests/destructor/tcustomstrings.nim index 1a78df20b..31891856b 100644 --- a/tests/destructor/tcustomstrings.nim +++ b/tests/destructor/tcustomstrings.nim @@ -5,11 +5,9 @@ foo bar to appendmore here foo bar to appendmore here foo bar to appendmore here after 20 20''' - cmd: '''nim c --newruntime $file''' +joinable: false """ -{.this: self.} - type mystring = object len, cap: int @@ -51,7 +49,7 @@ proc resize(self: var mystring) = if self.cap == 0: self.cap = 8 else: self.cap = (self.cap * 3) shr 1 if self.data == nil: inc allocCount - self.data = cast[type(data)](realloc(self.data, self.cap + 1)) + self.data = cast[type(self.data)](realloc(self.data, self.cap + 1)) proc add*(self: var mystring; c: char) = if self.len >= self.cap: resize(self) @@ -60,22 +58,22 @@ proc add*(self: var mystring; c: char) = inc self.len proc ensure(self: var mystring; newLen: int) = - if newLen >= cap: - cap = max((cap * 3) shr 1, newLen) - if cap > 0: - if data == nil: inc allocCount - data = cast[type(data)](realloc(data, cap + 1)) + if newLen >= self.cap: + self.cap = max((self.cap * 3) shr 1, newLen) + if self.cap > 0: + if self.data == nil: inc allocCount + self.data = cast[type(self.data)](realloc(self.data, self.cap + 1)) proc add*(self: var mystring; y: mystring) = - let newLen = len + y.len + let newLen = self.len + y.len ensure(self, newLen) - copyMem(addr data[len], y.data, y.data.len + 1) - len = newLen + copyMem(addr self.data[self.len], y.data, y.data.len + 1) + self.len = newLen proc create*(lit: string): mystring = let newLen = lit.len ensure(result, newLen) - copyMem(addr result.data[result.len], unsafeAddr lit[0], newLen + 1) + copyMem(addr result.data[result.len], addr lit[0], newLen + 1) result.len = newLen proc `&`*(a, b: mystring): mystring = diff --git a/tests/destructor/tcycle1.nim b/tests/destructor/tcycle1.nim new file mode 100644 index 000000000..8dc552294 --- /dev/null +++ b/tests/destructor/tcycle1.nim @@ -0,0 +1,54 @@ +discard """ + output: "MEM 0" + cmd: "nim c --gc:orc $file" +""" + +type + Node = ref object of RootObj + le, ri: Node + name: char + +proc edge(a, b: Node) = + if a.le == nil: a.le = b + else: a.ri = b + +proc createNode(name: char): Node = + new result + result.name = name + +#[ + ++---------+ +------+ +| | | | +| A +----->+ <------+-------------+ ++--+------+ | | | | + | | | | C | + | | R | | | ++--v------+ | | +-------------+ +| | | | ^ +| B <------+ | | +| | | +--------+ ++---------+ | | + +------+ + +]# + +proc main = + let a = createNode('A') + let b = createNode('B') + let r = createNode('R') + let c = createNode('C') + + a.edge b + a.edge r + + r.edge b + r.edge c + + c.edge r + + +let mem = getOccupiedMem() +main() +GC_fullCollect() +echo "MEM ", getOccupiedMem() - mem diff --git a/tests/destructor/tcycle2.nim b/tests/destructor/tcycle2.nim new file mode 100644 index 000000000..7b03101fe --- /dev/null +++ b/tests/destructor/tcycle2.nim @@ -0,0 +1,36 @@ +discard """ + output: "MEM 0" + cmd: "nim c --gc:orc $file" +""" + +type + Node = ref object + kids: seq[Node] + data: string + +proc main(x: int) = + var n = Node(kids: @[], data: "3" & $x) + let m = n + n.kids.add m + +type + NodeA = ref object + s: char + a: array[3, NodeA] + +proc m: NodeA = + result = NodeA(s: 'a') + result.a[0] = result + result.a[1] = result + result.a[2] = result + +proc mainA = + for i in 0..10: + discard m() + +let mem = getOccupiedMem() +main(90) +mainA() +GC_fullCollect() + +echo "MEM ", getOccupiedMem() - mem diff --git a/tests/destructor/tcycle3.nim b/tests/destructor/tcycle3.nim new file mode 100644 index 000000000..8662136e7 --- /dev/null +++ b/tests/destructor/tcycle3.nim @@ -0,0 +1,98 @@ +discard """ + output: '''BEGIN +END +END 2 +cpu.nes false +cpu step nes is nil? - false +0''' + cmd: '''nim c --gc:orc $file''' +""" + +# extracted from thavlak.nim + +type + BasicBlock = ref object + inEdges: seq[BasicBlock] + outEdges: seq[BasicBlock] + name: int + +proc newBasicBlock(name: int): BasicBlock = + result = BasicBlock( + inEdges: newSeq[BasicBlock](), + outEdges: newSeq[BasicBlock](), + name: name + ) + +type + Cfg = object + basicBlockMap: seq[BasicBlock] + startNode: BasicBlock + +proc newCfg(): Cfg = + result = Cfg( + basicBlockMap: newSeq[BasicBlock](), + startNode: nil) + +proc createNode(cfg: var Cfg, name: int): BasicBlock = + if name < cfg.basicBlockMap.len: + result = cfg.basicBlockMap[name] + else: + result = newBasicBlock(name) + cfg.basicBlockMap.setLen name+1 + cfg.basicBlockMap[name] = result + +proc newBasicBlockEdge(cfg: var Cfg, fromName, toName: int) = + echo "BEGIN" + let fr = cfg.createNode(fromName) + let to = cfg.createNode(toName) + + fr.outEdges.add(to) + to.inEdges.add(fr) + +proc run(cfg: var Cfg) = + cfg.startNode = cfg.createNode(0) # RC = 2 + newBasicBlockEdge(cfg, 0, 1) # + echo "END" + + discard cfg.createNode(1) + +proc main = + var c = newCfg() + c.run + echo "END 2" + +# bug #14159 +type + NES = ref object + cpu: CPU + apu: APU + + CPU = ref object + nes: NES + + APU = object + nes: NES + cpu: CPU + +proc initAPU(nes: sink NES): APU {.nosinks.} = + result.nes = nes + result.cpu = nes.cpu + +proc step(cpu: CPU): int = + echo "cpu.nes ", cpu.isNil + echo "cpu step nes is nil? - ", cpu.nes.isNil() + +proc newNES(): NES = + new result + result.cpu = CPU(nes: result) + result.apu = initAPU(result) + +proc bug14159 = + var nesConsole = newNES() + discard nesConsole.cpu.step() + +let mem = getOccupiedMem() +main() +bug14159() +GC_fullCollect() +echo getOccupiedMem() - mem diff --git a/tests/destructor/tdangingref_simple.nim b/tests/destructor/tdangingref_simple.nim new file mode 100644 index 000000000..279581b0f --- /dev/null +++ b/tests/destructor/tdangingref_simple.nim @@ -0,0 +1,32 @@ +discard """ + output: '''a +[FATAL] dangling references exist +''' + exitCode: 1 + cmd: "nim c --newruntime $file" +""" + +# bug #11350 + +type + Node = ref object + data: int + +proc use(x: Node) = discard + +proc main = + var x = Node(data: 3) # inferred to be an ``owned ref`` + var dangling = unown x + assert dangling.data == 3 + #use x + #dangling = nil + # reassignment causes the memory of what ``x`` points to to be freed: + echo "a" + x = Node(data: 4) + echo "b" + # accessing 'dangling' here is invalid as it is nil. + # at scope exit the memory of what ``x`` points to is freed + if dangling != nil: + echo dangling.data + +main() diff --git a/tests/destructor/tdestructor.nim b/tests/destructor/tdestructor.nim index c9f1caf2d..e081eb251 100644 --- a/tests/destructor/tdestructor.nim +++ b/tests/destructor/tdestructor.nim @@ -1,27 +1,30 @@ discard """ - output: '''---- + output: '''----1 myobj constructed myobj destroyed ----- +----2 mygeneric1 constructed mygeneric1 destroyed ----- +----3 mygeneric2 constructed mygeneric2 destroyed myobj destroyed ----- +----4 mygeneric3 constructed mygeneric1 destroyed ----- +----5 +mydistinctObj constructed +myobj destroyed +mygeneric2 destroyed +------------------8 mygeneric1 destroyed ----- +----6 +myobj destroyed +----7 +---9 myobj destroyed ----- ----- myobj destroyed ''' - cmd: '''nim c --newruntime $file''' - disabled: "true" """ type @@ -29,6 +32,13 @@ type x, y: int p: pointer +proc `=destroy`(o: var TMyObj) = + if o.p != nil: + dealloc o.p + o.p = nil + echo "myobj destroyed" + +type TMyGeneric1[T] = object x: T @@ -36,37 +46,40 @@ type x: A y: B +proc `=destroy`(o: var TMyGeneric1[int]) = + echo "mygeneric1 destroyed" + +proc `=destroy`[A, B](o: var TMyGeneric2[A, B]) = + echo "mygeneric2 destroyed" + +type TMyGeneric3[A, B, C] = object x: A y: B z: C - TObjKind = enum A, B, C, D + TDistinctObjX = distinct TMyGeneric3[TMyObj, TMyGeneric2[int, int], int] + TDistinctObj = TDistinctObjX + + TObjKind = enum Z, A, B, C, D TCaseObj = object + z: TMyGeneric3[TMyObj, float, int] case kind: TObjKind + of Z: discard of A: x: TMyGeneric1[int] of B, C: y: TMyObj else: case innerKind: TObjKind + of Z: discard of A, B, C: p: TMyGeneric3[int, float, string] of D: q: TMyGeneric3[TMyObj, int, int] r: string -proc `=destroy`(o: var TMyObj) = - if o.p != nil: dealloc o.p - echo "myobj destroyed" - -proc `=destroy`(o: var TMyGeneric1[int]) = - echo "mygeneric1 destroyed" - -proc `=destroy`[A, B](o: var TMyGeneric2[A, B]) = - echo "mygeneric2 destroyed" - proc open: TMyObj = # allow for superfluous () result = (TMyObj(x: 1, y: 2, p: alloc(3))) @@ -95,36 +108,60 @@ proc mygeneric3 = echo "mygeneric3 constructed" -echo "----" +proc mydistinctObj = + var x = TMyGeneric3[TMyObj, TMyGeneric2[int, int], int]( + x: open(), y: TMyGeneric2[int, int](x: 5, y: 15), z: 20) + + echo "mydistinctObj constructed" + +echo "----1" myobj() -echo "----" +echo "----2" mygeneric1() -echo "----" +echo "----3" mygeneric2[int](10) -echo "----" +echo "----4" mygeneric3() +echo "----5" +mydistinctObj() + proc caseobj = block: - echo "----" var o1 = TCaseObj(kind: A, x: TMyGeneric1[int](x: 10)) block: - echo "----" + echo "----6" var o2 = TCaseObj(kind: B, y: open()) block: - echo "----" + echo "----7" var o3 = TCaseObj(kind: D, innerKind: B, r: "test", p: TMyGeneric3[int, float, string](x: 10, y: 1.0, z: "test")) - block: - echo "----" - var o4 = TCaseObj(kind: D, innerKind: D, r: "test", - q: TMyGeneric3[TMyObj, int, int](x: open(), y: 1, z: 0)) +echo "------------------8" caseobj() +proc caseobj_test_sink: TCaseObj = + # check that lifted sink can destroy case val correctly + result = TCaseObj(kind: D, innerKind: D, r: "test", + q: TMyGeneric3[TMyObj, int, int](x: open(), y: 1, z: 0)) + result = TCaseObj(kind: B, y: open()) + + +echo "---9" +discard caseobj_test_sink() + +# issue #14315 + +type Vector*[T] = object + x1: int + # x2: T # uncomment will remove error + +# proc `=destroy`*(x: var Vector[int]) = discard # this will remove error +proc `=destroy`*[T](x: var Vector[T]) = discard +var a: Vector[int] # Error: unresolved generic parameter diff --git a/tests/destructor/tdestructor3.nim b/tests/destructor/tdestructor3.nim index 3e177d3cd..3f5eb2cc1 100644 --- a/tests/destructor/tdestructor3.nim +++ b/tests/destructor/tdestructor3.nim @@ -1,12 +1,21 @@ discard """ - output: '''assign + output: ''' +assign destroy destroy 5 123 +destroy Foo: 123 destroy Foo: 5 -destroy Foo: 123''' - cmd: '''nim c --newruntime $file''' +(x1: (val: ...)) +destroy +--------------- +app begin +(val: ...) +destroy +app end +''' +joinable: false """ # bug #2821 @@ -14,14 +23,18 @@ destroy Foo: 123''' type T = object proc `=`(lhs: var T, rhs: T) = - echo "assign" + echo "assign" proc `=destroy`(v: var T) = - echo "destroy" + echo "destroy" + +proc use(x: T) = discard proc usedToBeBlock = - var v1 : T - var v2 : T = v1 + var v1 = T() + var v2: T = v1 + discard addr(v2) # prevent cursorfication + use v1 usedToBeBlock() @@ -46,3 +59,127 @@ proc main = test(toFooPtr(123)) main() + +# bug #11517 +type + UniquePtr*[T] = object + val: ptr T + +proc `=destroy`*[T](p: var UniquePtr[T]) = + mixin `=destroy` + echo "destroy" + if p.val != nil: + `=destroy`(p.val[]) + dealloc(p.val) + p.val = nil + +proc `=`*[T](dest: var UniquePtr[T], src: UniquePtr[T]) {.error.} + +proc `=sink`*[T](dest: var UniquePtr[T], src: UniquePtr[T]) {.inline.} = + if dest.val != src.val: + if dest.val != nil: + `=destroy`(dest) + dest.val = src.val + +proc newUniquePtr*[T](val: sink T): UniquePtr[T] = + result.val = create(T) + result.val[] = val + +#------------------------------------------------------------- + +type + MyObject = object of RootObj + x1: UniquePtr[int] + + MyObject2 = object of MyObject + +proc newObj2(x:int, y: float): MyObject2 = + MyObject2(x1: newUniquePtr(x)) + +proc test = + let obj2 = newObj2(1, 1.0) + echo obj2 + +test() + + +#------------------------------------------------------------ +# Issue #12883 + +type + TopObject = object + internal: UniquePtr[int] + +proc deleteTop(p: ptr TopObject) = + if p != nil: + `=destroy`(p[]) # !!! this operation used to leak the integer + deallocshared(p) + +proc createTop(): ptr TopObject = + result = cast[ptr TopObject](allocShared0(sizeof(TopObject))) + result.internal = newUniquePtr(1) + +proc test2() = + let x = createTop() + echo $x.internal + deleteTop(x) + +echo "---------------" +echo "app begin" +test2() +echo "app end" + +# bug #14601 + +when true: # D20200607T202043 + type Foo2 = object + x: int + x2: array[10, int] + + type Vec = object + vals: seq[Foo2] + + proc `=destroy`*(a: var Foo2) {.inline.} = + discard + + proc initFoo2(x: int): Foo2 = Foo2(x: x) + + proc add2(v: var Vec, a: Foo2) = # ditto with `a: sink Foo2` + v.vals.add a + + proc add3(v: var Vec, a: Foo2) = # ditto with `a: sink Foo2` + v.vals = @[a] + + proc add4(v: var Vec, a: sink Foo2) = # ditto with `a: sink Foo2` + v.vals.add a + + proc add5(v: var Vec, a: sink Foo2) = # ditto with `a: sink Foo2` + v.vals = @[a] + + proc main2()= + var a: Vec + var b = Foo2(x: 10) + a.add2 b # ok + a.vals.add Foo2(x: 10) # ok + a.add2 initFoo2(x = 10) # ok + a.add2 Foo2(x: 10) # bug + a.add3 initFoo2(x = 10) # ok + a.add3 Foo2(x: 10) # bug + a.add4 initFoo2(x = 10) # ok + a.add4 Foo2(x: 10) # bug + a.add5 initFoo2(x = 10) # ok + a.add5 Foo2(x: 10) # bug + main2() + + + +#------------------------------------------------------------ +# Issue #15825 + +type + Union = string | int | char + +proc run(a: sink Union) = + discard + +run("123") diff --git a/tests/destructor/tdestructor_too_late.nim b/tests/destructor/tdestructor_too_late.nim new file mode 100644 index 000000000..76d1dde84 --- /dev/null +++ b/tests/destructor/tdestructor_too_late.nim @@ -0,0 +1,14 @@ +discard """ + errormsg: "cannot bind another '=destroy' to: Obj; previous declaration was constructed here implicitly: tdestructor_too_late.nim(7, 16)" +""" +type Obj* = object + v*: int + +proc something(this: sink Obj) = + discard + +proc `=destroy`(this: var Obj) = + echo "igotdestroyed" + this.v = -1 + +var test* = Obj(v: 42) \ No newline at end of file diff --git a/tests/destructor/tdistinctseq.nim b/tests/destructor/tdistinctseq.nim new file mode 100644 index 000000000..5a2ac5ead --- /dev/null +++ b/tests/destructor/tdistinctseq.nim @@ -0,0 +1,8 @@ +discard """ + matrix: "-u:nimPreviewNonVarDestructor;" +""" +type DistinctSeq* = distinct seq[int] + +# `=destroy`(cast[ptr DistinctSeq](0)[]) +var x = @[].DistinctSeq +`=destroy`(x) diff --git a/tests/destructor/tdont_return_unowned_from_owned.nim b/tests/destructor/tdont_return_unowned_from_owned.nim new file mode 100644 index 000000000..ffe87cd76 --- /dev/null +++ b/tests/destructor/tdont_return_unowned_from_owned.nim @@ -0,0 +1,56 @@ +discard """ + cmd: "nim check --newruntime --hints:off $file" + nimout: ''' +tdont_return_unowned_from_owned.nim(26, 13) Error: assignment produces a dangling ref: the unowned ref lives longer than the owned ref +tdont_return_unowned_from_owned.nim(27, 13) Error: assignment produces a dangling ref: the unowned ref lives longer than the owned ref +tdont_return_unowned_from_owned.nim(31, 10) Error: cannot return an owned pointer as an unowned pointer; use 'owned(RootRef)' as the return type +tdont_return_unowned_from_owned.nim(43, 10) Error: cannot return an owned pointer as an unowned pointer; use 'owned(Obj)' as the return type +tdont_return_unowned_from_owned.nim(46, 10) Error: cannot return an owned pointer as an unowned pointer; use 'owned(Obj)' as the return type +tdont_return_unowned_from_owned.nim(49, 6) Error: type mismatch: got <Obj> +but expected one of: +proc new[T](a: var ref T; finalizer: proc (x: ref T) {.nimcall.}) + first type mismatch at position: 2 + missing parameter: finalizer +2 other mismatching symbols have been suppressed; compile with --showAllMismatches:on to see them + +expression: new(result) +tdont_return_unowned_from_owned.nim(49, 6) Error: illformed AST: +''' + errormsg: "illformed AST:" +""" + + + +proc testA(result: var (RootRef, RootRef)) = + let r: owned RootRef = RootRef() + result[0] = r + result[1] = RootRef() + +proc testB(): RootRef = + let r: owned RootRef = RootRef() + result = r + + + + + +## line 30 +# bug #11073 +type + Obj = ref object + +proc newObjA(): Obj = + result = new Obj + +proc newObjB(): Obj = + result = Obj() + +proc newObjC(): Obj = + new(result) # illFormedAst raises GlobalError, + # without pipeline parsing, it needs to placed at the end + # in case that it disturbs other errors + +let a = newObjA() +let b = newObjB() +let c = newObjC() + diff --git a/tests/destructor/terror_module.nim b/tests/destructor/terror_module.nim new file mode 100644 index 000000000..f3d7c9b26 --- /dev/null +++ b/tests/destructor/terror_module.nim @@ -0,0 +1,20 @@ +discard """ +joinable: false +cmd: "nim check $file" +errormsg: "type bound operation `=deepcopy` can be defined only in the same module with its type (MyTestObject)" +nimout: ''' +terror_module.nim(14, 1) Error: type bound operation `=destroy` can be defined only in the same module with its type (MyTestObject) +terror_module.nim(16, 1) Error: type bound operation `=sink` can be defined only in the same module with its type (MyTestObject) +terror_module.nim(18, 1) Error: type bound operation `=` can be defined only in the same module with its type (MyTestObject) +terror_module.nim(20, 1) Error: type bound operation `=deepcopy` can be defined only in the same module with its type (MyTestObject) +''' +""" +import helper + +proc `=destroy`[T](x: var MyTestObject[T]) = discard + +proc `=sink`[T](x: var MyTestObject[T], y:MyTestObject[T]) = discard + +proc `=`[T](x: var MyTestObject[T], y: MyTestObject[T]) = discard + +proc `=deepcopy`[T](x: ptr MyTestObject[T]): ptr MyTestObject[T] = discard diff --git a/tests/destructor/texceptions.nim b/tests/destructor/texceptions.nim new file mode 100644 index 000000000..335ca23be --- /dev/null +++ b/tests/destructor/texceptions.nim @@ -0,0 +1,29 @@ +discard """ + cmd: '''nim c --gc:arc $file''' + output: '''0''' +""" + +proc other = + raise newException(ValueError, "stuff happening") + +proc indirectViaProcCall = + var correct = 0 + for i in 1 .. 20: + try: + other() + except: + let x = getCurrentException() + correct += ord(x of ValueError) + doAssert correct == 20 + +proc direct = + for i in 1 .. 20: + try: + raise newException(ValueError, "stuff happening") + except ValueError: + discard + +let mem = getOccupiedMem() +indirectViaProcCall() +direct() +echo getOccupiedMem() - mem diff --git a/tests/destructor/texplicit_move.nim b/tests/destructor/texplicit_move.nim new file mode 100644 index 000000000..93795af42 --- /dev/null +++ b/tests/destructor/texplicit_move.nim @@ -0,0 +1,30 @@ + +discard """ + output: '''3 +0 +0 +10 +destroyed! +''' +joinable: false +""" + +type + myseq* = object + f: int + +proc `=destroy`*(x: var myseq) = + echo "destroyed!" + +var + x: myseq +x.f = 3 +echo move(x.f) +echo x.f + +# bug #9743 +let a = create int +a[] = 10 +var b = move a[] +echo a[] +echo b diff --git a/tests/destructor/tfinalizer.nim b/tests/destructor/tfinalizer.nim new file mode 100644 index 000000000..eb2cd09af --- /dev/null +++ b/tests/destructor/tfinalizer.nim @@ -0,0 +1,31 @@ +discard """ + cmd: "nim c --gc:arc $file" + output: '''Foo(field: "Dick Laurent", k: ka, x: 0.0) +Nobody is dead +Dick Laurent is dead''' +""" + +type + Kind = enum + ka, kb + Foo = ref object + field: string + case k: Kind + of ka: x: float + of kb: discard + +#var x = Foo(field: "lovely") +proc finalizer(x: Foo) = + echo x.field, " is dead" + +var x: Foo +new(x, finalizer) +x.field = "Dick Laurent" +# reference to a great movie. If you haven't seen it, highly recommended. + +echo repr x + +# bug #13112: bind the same finalizer multiple times: +var xx: Foo +new(xx, finalizer) +xx.field = "Nobody" diff --git a/tests/destructor/tgcdestructors.nim b/tests/destructor/tgcdestructors.nim new file mode 100644 index 000000000..07a3731a0 --- /dev/null +++ b/tests/destructor/tgcdestructors.nim @@ -0,0 +1,203 @@ +discard """ + cmd: '''nim c -d:nimAllocStats --gc:arc $file''' + output: '''hi +ho +ha +@["arg", "asdfklasdfkl", "asdkfj", "dfasj", "klfjl"] +@[1, 2, 3] +@["red", "yellow", "orange", "rtrt1", "pink"] +a: @[4, 2, 3] +0 +30 +true +(allocCount: 27, deallocCount: 27)''' +""" + +include system / ansi_c + +proc main = + var s: seq[string] = @[] + for i in 0..<80: s.add "foo" + +main() + +const + test = @["hi", "ho", "ha"] + +for t in test: + echo t + +type + InterpolatedKind* = enum + ikStr, ## ``str`` part of the interpolated string + ikDollar, ## escaped ``$`` part of the interpolated string + ikVar, ## ``var`` part of the interpolated string + ikExpr ## ``expr`` part of the interpolated string + +iterator interpolatedFragments*(s: string): tuple[kind: InterpolatedKind, + value: string] = + var i = 0 + var kind: InterpolatedKind + while true: + var j = i + if j < s.len and s[j] == '$': + if j+1 < s.len and s[j+1] == '{': + inc j, 2 + var nesting = 0 + block curlies: + while j < s.len: + case s[j] + of '{': inc nesting + of '}': + if nesting == 0: + inc j + break curlies + dec nesting + else: discard + inc j + raise newException(ValueError, + "Expected closing '}': " & substr(s, i, s.high)) + inc i, 2 # skip ${ + kind = ikExpr + elif j+1 < s.len and s[j+1] in {'A'..'Z', 'a'..'z', '_'}: + inc j, 2 + while j < s.len and s[j] in {'A'..'Z', 'a'..'z', '0'..'9', '_'}: inc(j) + inc i # skip $ + kind = ikVar + elif j+1 < s.len and s[j+1] == '$': + inc j, 2 + inc i # skip $ + kind = ikDollar + else: + raise newException(ValueError, + "Unable to parse a varible name at " & substr(s, i, s.high)) + else: + while j < s.len and s[j] != '$': inc j + kind = ikStr + if j > i: + # do not copy the trailing } for ikExpr: + yield (kind, substr(s, i, j-1-ord(kind == ikExpr))) + else: + break + i = j + +proc parseCmdLine(c: string): seq[string] = + result = @[] + var i = 0 + var a = "" + while true: + setLen(a, 0) + while i < c.len and c[i] in {' ', '\t', '\l', '\r'}: inc(i) + if i >= c.len: break + var inQuote = false + while i < c.len: + case c[i] + of '\\': + var j = i + while j < c.len and c[j] == '\\': inc(j) + if j < c.len and c[j] == '"': + for k in 1..(j-i) div 2: a.add('\\') + if (j-i) mod 2 == 0: + i = j + else: + a.add('"') + i = j+1 + else: + a.add(c[i]) + inc(i) + of '"': + inc(i) + if not inQuote: inQuote = true + elif i < c.len and c[i] == '"': + a.add(c[i]) + inc(i) + else: + inQuote = false + break + of ' ', '\t': + if not inQuote: break + a.add(c[i]) + inc(i) + else: + a.add(c[i]) + inc(i) + add(result, a) + + +proc other = + let input = "$test{} $this is ${an{ example}} " + let expected = @[(ikVar, "test"), (ikStr, "{} "), (ikVar, "this"), + (ikStr, " is "), (ikExpr, "an{ example}"), (ikStr, " ")] + var i = 0 + for s in interpolatedFragments(input): + doAssert s == expected[i] + inc i + + echo parseCmdLine("arg asdfklasdfkl asdkfj dfasj klfjl") + +other() + +# bug #11050 + +type + Obj* = object + f*: seq[int] + +method main(o: Obj) {.base.} = + for newb in o.f: + discard + +# test that o.f was not moved! +proc testforNoMove = + var o = Obj(f: @[1, 2, 3]) + main(o) + echo o.f + +testforNoMove() + +# bug #11065 +type + Warm = seq[string] + +proc testWarm = + var w: Warm + w = @["red", "yellow", "orange"] + + var x = "rt" + var y = "rt1" + w.add(x & y) + + w.add("pink") + echo w + +testWarm() + +proc mutConstSeq() = + # bug #11524 + var a = @[1,2,3] + a[0] = 4 + echo "a: ", a + +mutConstSeq() + +proc mainSeqOfCap = + # bug #11098 + var s = newSeqOfCap[int](10) + echo s.len + + var s2 = newSeqUninitialized[int](30) + echo s2.len + +mainSeqOfCap() + +# bug #11614 + +let ga = "foo" + +proc takeAinArray = + let b = [ga] + +takeAinArray() +echo ga == "foo" + +echo getAllocStats() diff --git a/tests/destructor/tgcleak4.nim b/tests/destructor/tgcleak4.nim new file mode 100644 index 000000000..4299c8841 --- /dev/null +++ b/tests/destructor/tgcleak4.nim @@ -0,0 +1,47 @@ +discard """ + outputsub: "no leak: " + cmd: "nim c --gc:arc $file" +""" +# bug #12758 +type + TExpr {.inheritable.} = object ## abstract base class for an expression + PLiteral = ref TLiteral + TLiteral = object of TExpr + x: int + op1: string + TPlusExpr = object of TExpr + a, b: ref TExpr + op2: string + +method eval(e: ref TExpr): int {.base.} = + # override this base method + quit "to override!" + +method eval(e: ref TLiteral): int = return e.x + +method eval(e: ref TPlusExpr): int = + # watch out: relies on dynamic binding + return eval(e.a) + eval(e.b) + +proc newLit(x: int): ref TLiteral = + new(result) + result.x = x + result.op1 = $getOccupiedMem() + +proc newPlus(a, b: ref TExpr): ref TPlusExpr = + new(result) + result.a = a + result.b = b + result.op2 = $getOccupiedMem() + +const Limit = when compileOption("gc", "markAndSweep") or compileOption("gc", "boehm"): 5*1024*1024 else: 500_000 + +for i in 0..100_000: + var s: array[0..11, ref TExpr] + for j in 0..high(s): + s[j] = newPlus(newPlus(newLit(j), newLit(2)), newLit(4)) + if eval(s[j]) != j+6: + quit "error: wrong result" + if getOccupiedMem() > Limit: quit("still a leak!") + +echo "no leak: ", getOccupiedMem() diff --git a/tests/destructor/tglobaldestructor.nim b/tests/destructor/tglobaldestructor.nim new file mode 100644 index 000000000..4d002a092 --- /dev/null +++ b/tests/destructor/tglobaldestructor.nim @@ -0,0 +1,9 @@ +discard """ + cmd: '''nim c --gc:arc $file''' + output: '''(v: 42) +igotdestroyed''' +""" + +import objFile + +echo test diff --git a/tests/destructor/tgotoexc_leak.nim b/tests/destructor/tgotoexc_leak.nim new file mode 100644 index 000000000..c8a234085 --- /dev/null +++ b/tests/destructor/tgotoexc_leak.nim @@ -0,0 +1,19 @@ +discard """ + output: '''0 +true''' + cmd: "nim c --gc:arc $file" +""" + +# bug #22398 + +for i in 0 ..< 10_000: + try: + try: + raise newException(ValueError, "") + except CatchableError: + discard + raise newException(ValueError, "") # or raise getCurrentException(), just raise works ok + except ValueError: + discard +echo getOccupiedMem() +echo getCurrentException() == nil diff --git a/tests/destructor/tgotoexceptions.nim b/tests/destructor/tgotoexceptions.nim new file mode 100755 index 000000000..f76592270 --- /dev/null +++ b/tests/destructor/tgotoexceptions.nim @@ -0,0 +1,117 @@ +discard """ + output: ''' +msg1 +msg2 +finally2 +finally1 +begin +one iteration! +caught! +except1 +finally1 +caught! 2 +BEFORE +FINALLY +BEFORE +EXCEPT +FINALLY +RECOVER +BEFORE +EXCEPT: IOError: hi +FINALLY +''' + cmd: "nim c --gc:arc --exceptions:goto $file" +""" + +#bug 7204 +proc nested_finally = + try: + raise newException(KeyError, "msg1") + except KeyError as ex: + echo ex.msg + try: + raise newException(ValueError, "msg2") + except: + echo getCurrentExceptionMsg() + finally: + echo "finally2" + finally: + echo "finally1" + +nested_finally() + +proc doraise = + raise newException(ValueError, "gah") + +proc main = + while true: + try: + echo "begin" + doraise() + finally: + echo "one ", "iteration!" + +try: + main() +except: + echo "caught!" + +when true: + proc p = + try: + raise newException(Exception, "Hello") + except: + echo "except1" + raise + finally: + echo "finally1" + + try: + p() + except: + echo "caught! 2" + + +proc noException = + try: + echo "BEFORE" + + except: + echo "EXCEPT" + raise + + finally: + echo "FINALLY" + +try: noException() +except: echo "RECOVER" + +proc reraise_in_except = + try: + echo "BEFORE" + raise newException(IOError, "") + + except IOError: + echo "EXCEPT" + raise + + finally: + echo "FINALLY" + +try: reraise_in_except() +except: echo "RECOVER" + +proc return_in_except = + try: + echo "BEFORE" + raise newException(IOError, "hi") + + except: + echo "EXCEPT: ", getCurrentException().name, ": ", getCurrentExceptionMsg() + return + + finally: + echo "FINALLY" + +try: return_in_except() +except: echo "RECOVER" diff --git a/tests/destructor/tgotoexceptions2.nim b/tests/destructor/tgotoexceptions2.nim new file mode 100644 index 000000000..057caf7b7 --- /dev/null +++ b/tests/destructor/tgotoexceptions2.nim @@ -0,0 +1,104 @@ +discard """ + cmd: "nim c --gc:arc --exceptions:goto $file" + output: ''' +B1 +B2 +catch +A1 +1 +B1 +B2 +catch +A1 +A2 +0 +B1 +B2 +A1 +1 +B1 +B2 +A1 +A2 +3 +A +B +C +''' +""" + +# More thorough test of return-in-finaly + +var raiseEx = true +var returnA = true +var returnB = false + +proc main: int = + try: #A + try: #B + if raiseEx: + raise newException(OSError, "") + return 3 + finally: #B + echo "B1" + if returnB: + return 2 + echo "B2" + except OSError: #A + echo "catch" + finally: #A + echo "A1" + if returnA: + return 1 + echo "A2" + +for x in [true, false]: + for y in [true, false]: + # echo "raiseEx: " & $x + # echo "returnA: " & $y + # echo "returnB: " & $z + # in the original test returnB was set to true too and + # this leads to swallowing the OSError exception. This is + # somewhat compatible with Python but it's non-sense, 'finally' + # should not be allowed to swallow exceptions. The goto based + # implementation does something sane so we don't "correct" its + # behavior just to be compatible with v1. + raiseEx = x + returnA = y + echo main() + +# Various tests of return nested in double try/except statements + +proc test1() = + + defer: echo "A" + + try: + raise newException(OSError, "Problem") + except OSError: + return + +test1() + + +proc test2() = + + defer: echo "B" + + try: + return + except OSError: + discard + +test2() + +proc test3() = + try: + try: + raise newException(OSError, "Problem") + except OSError: + return + finally: + echo "C" + +test3() diff --git a/tests/destructor/tgotoexceptions3.nim b/tests/destructor/tgotoexceptions3.nim new file mode 100644 index 000000000..308d288b2 --- /dev/null +++ b/tests/destructor/tgotoexceptions3.nim @@ -0,0 +1,7 @@ +discard """ + cmd: "nim c --gc:arc --exceptions:goto $file" + outputsub: "Error: unhandled exception: Problem [OSError]" + exitcode: "1" +""" + +raise newException(OSError, "Problem") diff --git a/tests/destructor/tgotoexceptions4.nim b/tests/destructor/tgotoexceptions4.nim new file mode 100644 index 000000000..b2b481256 --- /dev/null +++ b/tests/destructor/tgotoexceptions4.nim @@ -0,0 +1,60 @@ +discard """ + cmd: "nim c --gc:arc --exceptions:goto $file" + output: '''caught in gun +caught in fun +caughtsome msgMyExcept +in finally +caught1 +123 +123''' +""" + +when true: + # bug #13070 + type MyExcept = object of CatchableError + proc gun() = + try: + raise newException(MyExcept, "some msg") + except Exception as eab: + echo "caught in gun" + raise eab + + proc fun() = + try: + gun() + except Exception as e: + echo "caught in fun" + echo("caught", e.msg, e.name) + finally: + echo "in finally" + fun() + +when true: + # bug #13072 + type MyExceptB = object of CatchableError + proc gunB() = + raise newException(MyExceptB, "some msg") + proc funB() = + try: + gunB() + except CatchableError: + echo "caught1" + funB() + +# bug #13782 + +import strutils +var n = 123 + +try: n = parseInt("xxx") +except: discard + +echo n + +proc sameTestButForLocalVar = + var n = 123 + try: n = parseInt("xxx") + except: discard + echo n + +sameTestButForLocalVar() diff --git a/tests/destructor/tgotoexceptions5.nim b/tests/destructor/tgotoexceptions5.nim new file mode 100644 index 000000000..695aab0a4 --- /dev/null +++ b/tests/destructor/tgotoexceptions5.nim @@ -0,0 +1,45 @@ +discard """ + output: ''' +before +swallowed +before +swallowed B +''' + cmd: "nim c --gc:arc --exceptions:goto -d:ssl $file" +""" + +# bug #13599 +proc main() = + try: + echo "before" + raise newException(CatchableError, "foo") + except AssertionDefect: + echo "caught" + echo "after" + +try: + main() +except: + echo "swallowed" + +proc mainB() = + try: + echo "before" + raise newException(CatchableError, "foo") + # except CatchableError: # would work + except AssertionDefect: + echo "caught" + except: + raise + echo "after" + +try: + mainB() +except: + echo "swallowed B" + +# bug #14647 +import httpclient + +newAsyncHttpClient().close() + diff --git a/tests/destructor/tgotoexceptions6.nim b/tests/destructor/tgotoexceptions6.nim new file mode 100644 index 000000000..7c01f6a52 --- /dev/null +++ b/tests/destructor/tgotoexceptions6.nim @@ -0,0 +1,10 @@ +discard """ + cmd: "nim c --gc:arc --exceptions:goto $file" + outputsub: "Error: unhandled exception: virus detected [ValueError]" + exitcode: "1" +""" + +# bug #13436 +proc foo = + raise newException(ValueError, "virus detected") +foo() diff --git a/tests/destructor/tgotoexceptions7.nim b/tests/destructor/tgotoexceptions7.nim new file mode 100644 index 000000000..c04bd6ba0 --- /dev/null +++ b/tests/destructor/tgotoexceptions7.nim @@ -0,0 +1,49 @@ +discard """ + cmd: "nim c --gc:arc --exceptions:goto --panics:off $file" + output: '''prevented! +caught +AssertionDefect +900''' +""" + +type + E = enum + kindA, kindB + Obj = object + case kind: E + of kindA: s: string + of kindB: i: int + + ObjA = ref object of RootObj + ObjB = ref object of ObjA + +proc takeRange(x: range[0..4]) = discard + +proc bplease(x: ObjB) = discard + +proc helper = doAssert(false) + +proc main(i: int) = + var obj = Obj(kind: kindA, s: "abc") + {.cast(uncheckedAssign).}: + obj.kind = kindB + obj.i = 2 + try: + var objA = ObjA() + bplease(ObjB(objA)) + except ObjectConversionDefect: + echo "prevented!" + + try: + takeRange(i) + except RangeDefect: + echo "caught" + + try: + helper() + except AssertionDefect: + echo "AssertionDefect" + + echo i * i + +main(30) diff --git a/tests/destructor/tgotoexceptions8.nim b/tests/destructor/tgotoexceptions8.nim new file mode 100644 index 000000000..8ed2ed0ba --- /dev/null +++ b/tests/destructor/tgotoexceptions8.nim @@ -0,0 +1,76 @@ +discard """ + output: '''A +B +X +inner finally +Y +outer finally +msg1 +msg2 +finally2 +finally1 +true''' + cmd: "nim c --gc:arc $file" +""" + +# bug #13668 + +proc main = + try: + try: + raise newException(IOError, "IOError") + + except: + echo "A" + raise newException(CatchableError, "CatchableError") + + except: + echo "B" + #discard + +proc mainB = + try: + try: + raise newException(IOError, "IOError") + + except: + echo "X" + raise newException(CatchableError, "CatchableError") + finally: + echo "inner finally" + + except: + echo "Y" + #discard + finally: + echo "outer finally" + +main() +mainB() + +when true: + #bug 7204 + proc nested_finally = + try: + raise newException(KeyError, "msg1") + except KeyError as ex: + echo ex.msg + try: + # pop exception + raise newException(ValueError, "msg2") # push: exception stack (1 entry) + except: + echo getCurrentExceptionMsg() + # pop exception (except) + finally: + echo "finally2" + # pop exception (except KeyError as ex) + finally: + echo "finally1" + + nested_finally() + +# bug #14925 +proc test(b: bool) = + echo b + +test(try: true except: false) diff --git a/tests/destructor/tinvalid_rebind.nim b/tests/destructor/tinvalid_rebind.nim new file mode 100644 index 000000000..0f15c8f9e --- /dev/null +++ b/tests/destructor/tinvalid_rebind.nim @@ -0,0 +1,15 @@ +discard """ +joinable: false +cmd: "nim check $file" +errormsg: "cannot bind another '=destroy' to: Foo; previous declaration was constructed here implicitly: tinvalid_rebind.nim(12, 7)" +line: 14 +""" + +type + Foo[T] = object + +proc main = + var f: Foo[int] + +proc `=destroy`[T](f: var Foo[T]) = + discard diff --git a/tests/destructor/tmatrix.nim b/tests/destructor/tmatrix.nim new file mode 100644 index 000000000..2fd5af789 --- /dev/null +++ b/tests/destructor/tmatrix.nim @@ -0,0 +1,135 @@ +discard """ + output: '''after 2 2 +after 2 2 +after 2 2 +after 2 2''' +""" +# bug #9263 +type + Matrix* = object + # Array for internal storage of elements. + data: ptr UncheckedArray[float] + # Row and column dimensions. + m*, n*: int + +var + allocCount, deallocCount: int + +proc `=destroy`*(m: var Matrix) = + if m.data != nil: + dealloc(m.data) + deallocCount.inc + m.data = nil + m.m = 0 + m.n = 0 + +proc `=sink`*(a: var Matrix; b: Matrix) = + if a.data != nil and a.data != b.data: + dealloc(a.data) + deallocCount.inc + a.data = b.data + a.m = b.m + a.n = b.n + +proc `=copy`*(a: var Matrix; b: Matrix) = + if a.data != nil and a.data != b.data: + dealloc(a.data) + deallocCount.inc + a.data = nil + a.m = b.m + a.n = b.n + if b.data != nil: + a.data = cast[type(a.data)](alloc(a.m * a.n * sizeof(float))) + allocCount.inc + copyMem(a.data, b.data, b.m * b.n * sizeof(float)) + +proc `=dup`*(a: Matrix): Matrix = + `=copy`(result, a) + +proc matrix*(m, n: int, s: float): Matrix = + ## Construct an m-by-n constant matrix. + result.m = m + result.n = n + result.data = cast[type(result.data)](alloc(m * n * sizeof(float))) + allocCount.inc + for i in 0 ..< m * n: + result.data[i] = s + +proc len(m: Matrix): int = m.n * m.m + +proc `[]`*(m: Matrix, i, j: int): float {.inline.} = + ## Get a single element. + m.data[i * m.n + j] + +proc `[]`*(m: var Matrix, i, j: int): var float {.inline.} = + ## Get a single element. + m.data[i * m.n + j] + +proc `[]=`*(m: var Matrix, i, j: int, s: float) = + ## Set a single element. + m.data[i * m.n + j] = s + +proc `-`*(m: sink Matrix): Matrix = + ## Unary minus + result = m + for i in 0 ..< result.m: + for j in 0 ..< result.n: + result[i, j] = -result[i, j] + +proc `+`*(a: sink Matrix; b: Matrix): Matrix = + ## ``C = A + B`` + doAssert(b.m == a.m and b.n == a.n, "Matrix dimensions must agree.") + doAssert(a.len == b.len) # non destructive use before sink is ok + result = a + for i in 0 ..< result.m: + for j in 0 ..< result.n: + result[i, j] = result[i, j] + b[i, j] + +proc `-`*(a: sink Matrix; b: Matrix): Matrix = + ## ``C = A - B`` + assert(b.m == a.m and b.n == a.n, "Matrix dimensions must agree.") + doAssert(a.len == b.len) # non destructive use before sink is ok + result = a + for i in 0 ..< result.m: + for j in 0 ..< result.n: + result[i, j] = a[i, j] - b[i, j] + +proc info = + echo "after ", allocCount, " ", deallocCount + allocCount = 0 + deallocCount = 0 + +proc copy(a: Matrix): Matrix = a + +proc test1 = + var a = matrix(5, 5, 1.0) + var b = copy a + var c = a + b + +proc test2 = + var a = matrix(5, 5, 1.0) + var b = copy a + var c = -a + +proc test3 = + var a = matrix(5, 5, 1.0) + var b = matrix(5, 5, 2.0) + # a = a - b + b = -b + a + +proc test4 = + # bug #9294 + var a = matrix(5, 5, 1.0) + a = -a + a + +test1() +info() + +test2() +info() + +test3() +info() + +test4() +info() diff --git a/tests/destructor/tmisc_destructors.nim b/tests/destructor/tmisc_destructors.nim new file mode 100644 index 000000000..082cb0f78 --- /dev/null +++ b/tests/destructor/tmisc_destructors.nim @@ -0,0 +1,43 @@ +discard """ + output: '''@[0] +@[1] +@[2] +@[3]''' + joinable: false +""" + +# bug #6434 + +type + Foo* = object + boo: int + +var sink_counter = 0 +var assign_counter = 0 + +proc `=sink`(dest: var Foo, src: Foo) = + sink_counter.inc + +proc `=`(dest: var Foo, src: Foo) = + assign_counter.inc + +proc createFoo(): Foo = Foo(boo: 0) + +proc test(): auto = + var a, b = createFoo() + return (a, b, Foo(boo: 5)) + +var (ag, bg, _) = test() + +doAssert assign_counter == 0 +doAssert sink_counter == 0 + +# bug #11510 +proc main = + for i in 0 ..< 4: + var buffer: seq[int] # = @[] # uncomment to make it work + # var buffer: string # also this is broken + buffer.add i + echo buffer + +main() diff --git a/tests/destructor/tmove.nim b/tests/destructor/tmove.nim new file mode 100644 index 000000000..2762aff90 --- /dev/null +++ b/tests/destructor/tmove.nim @@ -0,0 +1,18 @@ +discard """ + targets: "c cpp" +""" + +block: + var called = 0 + + proc bar(a: var int): var int = + inc called + result = a + + proc foo = + var a = 2 + var s = move bar(a) + doAssert called == 1 + doAssert s == 2 + + foo() diff --git a/tests/destructor/tmove_objconstr.nim b/tests/destructor/tmove_objconstr.nim index 8aa12ed05..cdc1eb1c0 100644 --- a/tests/destructor/tmove_objconstr.nim +++ b/tests/destructor/tmove_objconstr.nim @@ -7,7 +7,8 @@ test destroyed 0 3 4 Pony is dying!''' - cmd: '''nim c --newruntime $file''' +joinable: false +targets: "c" """ # bug #4214 @@ -36,24 +37,158 @@ proc pointlessWrapper(s: string): Data = proc main = var x = pointlessWrapper"test" -when isMainModule: +when true: main() # bug #985 type - Pony = object - name: string + Pony = object + name: string proc `=destroy`(o: var Pony) = echo "Pony is dying!" proc getPony: Pony = - result.name = "Sparkles" + result = Pony(name: "Sparkles") iterator items(p: Pony): int = - for i in 1..4: - yield i + for i in 1..4: + yield i for x in getPony(): - echo x + echo x + + + + + +#------------------------------------------------------------ +#-- Move into tuple constructor and move on tuple unpacking +#------------------------------------------------------------ + +type + MySeqNonCopyable* = object + len: int + data: ptr UncheckedArray[float] + +proc `=destroy`*(m: var MySeqNonCopyable) {.inline.} = + if m.data != nil: + deallocShared(m.data) + m.data = nil + +proc `=`*(m: var MySeqNonCopyable, m2: MySeqNonCopyable) {.error.} + +proc `=sink`*(m: var MySeqNonCopyable, m2: MySeqNonCopyable) {.inline.} = + if m.data != m2.data: + if m.data != nil: + `=destroy`(m) + m.len = m2.len + m.data = m2.data + +proc len*(m: MySeqNonCopyable): int {.inline.} = m.len + +proc `[]`*(m: MySeqNonCopyable; i: int): float {.inline.} = + m.data[i.int] + +proc `[]=`*(m: var MySeqNonCopyable; i, val: float) {.inline.} = + m.data[i.int] = val + +proc setTo(s: var MySeqNonCopyable, val: float) = + for i in 0..<s.len.int: + s.data[i] = val + +proc newMySeq*(size: int, initial_value = 0.0): MySeqNonCopyable =# + result.len = size + if size > 0: + result.data = cast[ptr UncheckedArray[float]](createShared(float, size)) + + result.setTo(initial_value) + +proc myfunc(x, y: int): (MySeqNonCopyable, MySeqNonCopyable) = + result = (newMySeq(x, 1.0), newMySeq(y, 5.0)) + +proc myfunc2(x, y: int): tuple[a: MySeqNonCopyable, b:int, c:MySeqNonCopyable] = + var cc = newMySeq(y, 5.0) + (a: case x: + of 1: + let (z1, z2) = myfunc(x, y) + z2 + elif x > 5: raise newException(ValueError, "new error") + else: newMySeq(x, 1.0), + b: 0, + c: block: + var tmp = if y > 0: move(cc) else: newMySeq(1, 3.0) + tmp[0] = 5 + tmp + ) + + +let (seq1, seq2) = myfunc(2, 3) +doAssert seq1.len == 2 +doAssert seq1[0] == 1.0 +doAssert seq2.len == 3 +doAssert seq2[0] == 5.0 + +var (seq3, i, _) = myfunc2(2, 3) +doAssert seq3.len == 2 +doAssert seq3[0] == 1.0 + +var seq4, seq5: MySeqNonCopyable +(seq4, i, seq5) = myfunc2(2, 3) + +proc foo = + seq4 = block: + var tmp = newMySeq(4, 1.0) + tmp[0] = 3.0 + tmp + + doAssert seq4[0] == 3.0 + + + seq4 = + if i > 0: newMySeq(2, 5.0) + elif i < -100: raise newException(ValueError, "Parse Error") + else: newMySeq(2, 3.0) + + seq4 = + case (char) i: + of 'A', {'W'..'Z'}: newMySeq(2, 5.0) + of 'B': quit(-1) + else: + let (x1, x2, x3) = myfunc2(2, 3) + x3 + +foo() + +#------------------------------------------------------------ +#-- Move into array constructor +#------------------------------------------------------------ + +var ii = 1 +let arr2 = [newMySeq(2, 5.0), if i > 1: newMySeq(3, 1.0) else: newMySeq(0, 0.0)] +var seqOfSeq2 = @[newMySeq(2, 5.0), newMySeq(3, 1.0)] + + +## issue #10462 +proc myfuncLoop(x: int): MySeqNonCopyable = + for i in 0..<x: + var cc = newMySeq(i, 5.0) + result = cc + +discard myfuncLoop(3) + +#------------------------------------------------------------ +# Move into table via openArray +#------------------------------------------------------------ + +type + TableNonCopyable = object + x: seq[(string, MySeqNonCopyable)] + +proc toTable(pairs: sink openArray[(string, MySeqNonCopyable)]): TableNonCopyable = + discard + + +let mytable = {"a": newMySeq(2, 5.0)}.toTable + diff --git a/tests/destructor/tnewruntime_misc.nim b/tests/destructor/tnewruntime_misc.nim new file mode 100644 index 000000000..21c70557d --- /dev/null +++ b/tests/destructor/tnewruntime_misc.nim @@ -0,0 +1,155 @@ +discard """ + cmd: '''nim cpp -d:nimAllocStats --newruntime --threads:on $file''' + output: '''(field: "value") +Indeed +axc +(v: 10) +... +destroying GenericObj[T] GenericObj[system.int] +test +(allocCount: 12, deallocCount: 10) +3''' +""" + +import system / ansi_c + +import tables + +type + Node = ref object + field: string + +# bug #11807 +import os +putEnv("HEAPTRASHING", "Indeed") + +let s1 = getAllocStats() + + +proc newTableOwned[A, B](initialSize = defaultInitialSize): owned(TableRef[A, B]) = + new(result) + result[] = initTable[A, B](initialSize) + +proc main = + var w = newTableOwned[string, owned Node]() + w["key"] = Node(field: "value") + echo w["key"][] + echo getEnv("HEAPTRASHING") + + # bug #11891 + var x = "abc" + x[1] = 'x' + echo x + +main() + +# bug #11745 + +type + Foo = object + bar: seq[int] + +var x = [Foo()] + +# bug #11563 +type + MyTypeType = enum + Zero, One + MyType = object + case kind: MyTypeType + of Zero: + s*: seq[MyType] + of One: + x*: int +var t: MyType + +# bug #11254 +proc test(p: owned proc()) = + let x = (proc())p + +test(proc() = discard) + +# bug #10689 + +type + O = object + v: int + +proc `=sink`(d: var O, s: O) = + d.v = s.v + +proc selfAssign = + var o = O(v: 10) + o = o + echo o + +selfAssign() + +# bug #11833 +type FooAt = object + +proc testWrongAt() = + var x = @[@[FooAt()]] + +testWrongAt() + +#------------------------------------------------- +type + Table[A, B] = object + x: seq[(A, B)] + + +proc toTable[A,B](p: sink openArray[(A, B)]): Table[A, B] = + for zz in mitems(p): + result.x.add move(zz) + + +let table = {"a": new(int)}.toTable() + +# bug # #12051 + +type + GenericObj[T] = object + val: T + Generic[T] = owned ref GenericObj[T] + +proc `=destroy`[T](x: var GenericObj[T]) = + echo "destroying GenericObj[T] ", x.typeof # to know when its being destroyed + +proc main12() = + let gnrc = Generic[int](val: 42) + echo "..." + +main12() + +##################################################################### +## bug #12827 +type + MyObject = object + x: string + y: seq[string] + needs_ref: ref int + +proc xx(xml: string): MyObject = + let stream = xml + result.x = xml + defer: echo stream + + +discard xx("test") + +# Windows has 1 extra allocation in `getEnv` - there it allocates parameter to +# `_wgetenv` (WideCString). Therefore subtract by 1 to match other OSes' +# allocation. +when defined(windows): + import std/importutils + privateAccess(AllocStats) + echo getAllocStats() - s1 - AllocStats(allocCount: 1, deallocCount: 1) +else: + echo getAllocStats() - s1 + +# bug #13457 +var s = "abcde" +s.setLen(3) + +echo s.cstring.len diff --git a/tests/destructor/tnewruntime_strutils.nim b/tests/destructor/tnewruntime_strutils.nim new file mode 100644 index 000000000..9c8d41973 --- /dev/null +++ b/tests/destructor/tnewruntime_strutils.nim @@ -0,0 +1,254 @@ +discard """ + valgrind: true + cmd: '''nim c -d:nimAllocStats --gc:arc -d:useMalloc $file''' + output: ''' +@[(input: @["KXSC", "BGMC"]), (input: @["PXFX"]), (input: @["WXRQ", "ZSCZD"])] +14 +First tasks completed. +Second tasks completed. +test1''' +""" + +import strutils, os, std / wordwrap + +import system / ansi_c + +# bug #11004 +proc retTuple(): (seq[int], int) = + return (@[1], 1) + +# bug #12899 + +import sequtils, strmisc + +const input = ["KXSC, BGMC => 7 PTHL", "PXFX => LBZJ", "WXRQ, ZSCZD => HLQM"] + +type + Reaction = object + input: seq[string] + +proc bug12899 = + var reactions: seq[Reaction] = @[] + for l in input: + let x = l.partition(" => ") + reactions.add Reaction(input: @(x[0].split(", "))) + + let x = $reactions + echo x + +bug12899() + + +proc nonStaticTests = + doAssert formatBiggestFloat(1234.567, ffDecimal, -1) == "1234.567000" + doAssert formatBiggestFloat(1234.567, ffDecimal, 0) == "1235." # bugs 8242, 12586 + doAssert formatBiggestFloat(1234.567, ffDecimal, 1) == "1234.6" + doAssert formatBiggestFloat(0.00000000001, ffDecimal, 11) == "0.00000000001" + doAssert formatBiggestFloat(0.00000000001, ffScientific, 1, ',') in + ["1,0e-11", "1,0e-011"] + + doAssert "$# $3 $# $#" % ["a", "b", "c"] == "a c b c" + doAssert "${1}12 ${-1}$2" % ["a", "b"] == "a12 bb" + + block: # formatSize tests + when not defined(js): + doAssert formatSize((1'i64 shl 31) + (300'i64 shl 20)) == "2.293GiB" # <=== bug #8231 + doAssert formatSize((2.234*1024*1024).int) == "2.234MiB" + doAssert formatSize(4096) == "4KiB" + doAssert formatSize(4096, prefix=bpColloquial, includeSpace=true) == "4 kB" + doAssert formatSize(4096, includeSpace=true) == "4 KiB" + doAssert formatSize(5_378_934, prefix=bpColloquial, decimalSep=',') == "5,13MB" + + block: # formatEng tests + doAssert formatEng(0, 2, trim=false) == "0.00" + doAssert formatEng(0, 2) == "0" + doAssert formatEng(53, 2, trim=false) == "53.00" + doAssert formatEng(0.053, 2, trim=false) == "53.00e-3" + doAssert formatEng(0.053, 4, trim=false) == "53.0000e-3" + doAssert formatEng(0.053, 4, trim=true) == "53e-3" + doAssert formatEng(0.053, 0) == "53e-3" + doAssert formatEng(52731234) == "52.731234e6" + doAssert formatEng(-52731234) == "-52.731234e6" + doAssert formatEng(52731234, 1) == "52.7e6" + doAssert formatEng(-52731234, 1) == "-52.7e6" + doAssert formatEng(52731234, 1, decimalSep=',') == "52,7e6" + doAssert formatEng(-52731234, 1, decimalSep=',') == "-52,7e6" + + doAssert formatEng(4100, siPrefix=true, unit="V") == "4.1 kV" + doAssert formatEng(4.1, siPrefix=true, unit="V", useUnitSpace=true) == "4.1 V" + doAssert formatEng(4.1, siPrefix=true) == "4.1" # Note lack of space + doAssert formatEng(4100, siPrefix=true) == "4.1 k" + doAssert formatEng(4.1, siPrefix=true, unit="", useUnitSpace=true) == "4.1 " # Includes space + doAssert formatEng(4100, siPrefix=true, unit="") == "4.1 k" + doAssert formatEng(4100) == "4.1e3" + doAssert formatEng(4100, unit="V", useUnitSpace=true) == "4.1e3 V" + doAssert formatEng(4100, unit="", useUnitSpace=true) == "4.1e3 " + # Don't use SI prefix as number is too big + doAssert formatEng(3.1e22, siPrefix=true, unit="a", useUnitSpace=true) == "31e21 a" + # Don't use SI prefix as number is too small + doAssert formatEng(3.1e-25, siPrefix=true, unit="A", useUnitSpace=true) == "310e-27 A" + +proc staticTests = + doAssert align("abc", 4) == " abc" + doAssert align("a", 0) == "a" + doAssert align("1232", 6) == " 1232" + doAssert align("1232", 6, '#') == "##1232" + + doAssert alignLeft("abc", 4) == "abc " + doAssert alignLeft("a", 0) == "a" + doAssert alignLeft("1232", 6) == "1232 " + doAssert alignLeft("1232", 6, '#') == "1232##" + + let + inp = """ this is a long text -- muchlongerthan10chars and here + it goes""" + outp = " this is a\nlong text\n--\nmuchlongerthan10chars\nand here\nit goes" + doAssert wrapWords(inp, 10, false) == outp + + let + longInp = """ThisIsOneVeryLongStringWhichWeWillSplitIntoEightSeparatePartsNow""" + longOutp = "ThisIsOn\neVeryLon\ngStringW\nhichWeWi\nllSplitI\nntoEight\nSeparate\nPartsNow" + doAssert wrapWords(longInp, 8, true) == longOutp + + doAssert "$animal eats $food." % ["animal", "The cat", "food", "fish"] == + "The cat eats fish." + + doAssert "-ld a-ldz -ld".replaceWord("-ld") == " a-ldz " + doAssert "-lda-ldz -ld abc".replaceWord("-ld") == "-lda-ldz abc" + + doAssert "-lda-ldz -ld abc".replaceWord("") == "-lda-ldz -ld abc" + doAssert "oo".replace("", "abc") == "oo" + + type MyEnum = enum enA, enB, enC, enuD, enE + doAssert parseEnum[MyEnum]("enu_D") == enuD + + doAssert parseEnum("invalid enum value", enC) == enC + + doAssert center("foo", 13) == " foo " + doAssert center("foo", 0) == "foo" + doAssert center("foo", 3, fillChar = 'a') == "foo" + doAssert center("foo", 10, fillChar = '\t') == "\t\t\tfoo\t\t\t\t" + + doAssert count("foofoofoo", "foofoo") == 1 + doAssert count("foofoofoo", "foofoo", overlapping = true) == 2 + doAssert count("foofoofoo", 'f') == 3 + doAssert count("foofoofoobar", {'f','b'}) == 4 + + doAssert strip(" foofoofoo ") == "foofoofoo" + doAssert strip("sfoofoofoos", chars = {'s'}) == "foofoofoo" + doAssert strip("barfoofoofoobar", chars = {'b', 'a', 'r'}) == "foofoofoo" + doAssert strip("stripme but don't strip this stripme", + chars = {'s', 't', 'r', 'i', 'p', 'm', 'e'}) == + " but don't strip this " + doAssert strip("sfoofoofoos", leading = false, chars = {'s'}) == "sfoofoofoo" + doAssert strip("sfoofoofoos", trailing = false, chars = {'s'}) == "foofoofoos" + + doAssert " foo\n bar".indent(4, "Q") == "QQQQ foo\nQQQQ bar" + + doAssert "abba".multiReplace(("a", "b"), ("b", "a")) == "baab" + doAssert "Hello World.".multiReplace(("ello", "ELLO"), ("World.", "PEOPLE!")) == "HELLO PEOPLE!" + doAssert "aaaa".multiReplace(("a", "aa"), ("aa", "bb")) == "aaaaaaaa" + + doAssert rsplit("foo bar", seps=Whitespace) == @["foo", "bar"] + doAssert rsplit(" foo bar", seps=Whitespace, maxsplit=1) == @[" foo", "bar"] + doAssert rsplit(" foo bar ", seps=Whitespace, maxsplit=1) == @[" foo bar", ""] + doAssert rsplit(":foo:bar", sep=':') == @["", "foo", "bar"] + doAssert rsplit(":foo:bar", sep=':', maxsplit=2) == @["", "foo", "bar"] + doAssert rsplit(":foo:bar", sep=':', maxsplit=3) == @["", "foo", "bar"] + doAssert rsplit("foothebar", sep="the") == @["foo", "bar"] + + doAssert(unescape(r"\x013", "", "") == "\x013") + + doAssert join(["foo", "bar", "baz"]) == "foobarbaz" + doAssert join(@["foo", "bar", "baz"], ", ") == "foo, bar, baz" + doAssert join([1, 2, 3]) == "123" + doAssert join(@[1, 2, 3], ", ") == "1, 2, 3" + + doAssert """~~!!foo +~~!!bar +~~!!baz""".unindent(2, "~~!!") == "foo\nbar\nbaz" + + doAssert """~~!!foo +~~!!bar +~~!!baz""".unindent(2, "~~!!aa") == "~~!!foo\n~~!!bar\n~~!!baz" + doAssert """~~foo +~~ bar +~~ baz""".unindent(4, "~") == "foo\n bar\n baz" + doAssert """foo + bar + baz + """.unindent(4) == "foo\nbar\nbaz\n" + doAssert """foo + bar + baz + """.unindent(2) == "foo\n bar\n baz\n" + doAssert """foo + bar + baz + """.unindent(100) == "foo\nbar\nbaz\n" + + doAssert """foo + foo + bar + """.unindent() == "foo\nfoo\nbar\n" + + let s = " this is an example " + let s2 = ":this;is;an:example;;" + + doAssert s.split() == @["", "this", "is", "an", "example", "", ""] + doAssert s2.split(seps={':', ';'}) == @["", "this", "is", "an", "example", "", ""] + doAssert s.split(maxsplit=4) == @["", "this", "is", "an", "example "] + doAssert s.split(' ', maxsplit=1) == @["", "this is an example "] + doAssert s.split(" ", maxsplit=4) == @["", "this", "is", "an", "example "] + + doAssert s.splitWhitespace() == @["this", "is", "an", "example"] + doAssert s.splitWhitespace(maxsplit=1) == @["this", "is an example "] + doAssert s.splitWhitespace(maxsplit=2) == @["this", "is", "an example "] + doAssert s.splitWhitespace(maxsplit=3) == @["this", "is", "an", "example "] + doAssert s.splitWhitespace(maxsplit=4) == @["this", "is", "an", "example"] + + discard retTuple() + +nonStaticTests() +staticTests() + +# bug #12965 +let xaa = @[""].join() +let xbb = @["", ""].join() + +# bug #16365 + +# Task 1: +when true: + # Task 1_a: + var test_string_a = "name_something" + echo test_string_a.len() + let new_len_a = test_string_a.len - "_something".len() + test_string_a.setLen new_len_a + + echo "First tasks completed." + +# Task 2: +when true: + # Task 2_a + var test_string: string + let some_string = "something" + for i in some_string.items: + test_string.add $i + + # Task 2_b + var test_string_b = "name_something" + let new_len_b = test_string_b.len - "_something".len() + test_string_b.setLen new_len_b + + echo "Second tasks completed." + +# bug #17450 +proc main = + var i = 1 + echo: + block: + "test" & $i + +main() + diff --git a/tests/destructor/tnonvardestructor.nim b/tests/destructor/tnonvardestructor.nim new file mode 100644 index 000000000..1b4413790 --- /dev/null +++ b/tests/destructor/tnonvardestructor.nim @@ -0,0 +1,247 @@ +discard """ + targets: "c cpp" + matrix: "--mm:arc; --mm:orc" +""" + +block: + type + PublicKey = array[32, uint8] + PrivateKey = array[64, uint8] + + proc ed25519_create_keypair(publicKey: ptr PublicKey; privateKey: ptr PrivateKey) = + publicKey[][0] = uint8(88) + + type + KeyPair = object + public: PublicKey + private: PrivateKey + + proc initKeyPair(): KeyPair = + ed25519_create_keypair(result.public.addr, result.private.addr) + + let keys = initKeyPair() + doAssert keys.public[0] == 88 + + +template minIndexByIt: untyped = + var other = 3 + other + +proc bug20303() = + var hlibs = @["hello", "world", "how", "are", "you"] + let res = hlibs[minIndexByIt()] + doAssert res == "are" + +bug20303() + +proc main() = # todo bug with templates + block: # bug #11267 + var a: seq[char] = block: @[] + doAssert a == @[] + # 2 + proc b: seq[string] = + discard + @[] + doAssert b() == @[] +static: main() +main() + + +type Obj = tuple + value: int + arr: seq[int] + +proc bug(): seq[Obj] = + result.add (value: 0, arr: @[]) + result[^1].value = 1 + result[^1].arr.add 1 + +# bug #19990 +let s = bug() +doAssert s[0] == (value: 1, arr: @[1]) + +block: # bug #21974 + type Test[T] = ref object + values : seq[T] + counter: int + + proc newTest[T](): Test[T] = + result = new(Test[T]) + result.values = newSeq[T](16) + result.counter = 0 + + proc push[T](self: Test[T], value: T) = + self.counter += 1 + if self.counter >= self.values.len: + self.values.setLen(self.values.len * 2) + self.values[self.counter - 1] = value + + proc pop[T](self: Test[T]): T = + result = self.values[0] + self.values[0] = self.values[self.counter - 1] # <--- This line + self.counter -= 1 + + + type X = tuple + priority: int + value : string + + var a = newTest[X]() + a.push((1, "One")) + doAssert a.pop.value == "One" + +# bug #21987 + +type + EmbeddedImage* = distinct Image + Image = object + len: int + +proc imageCopy*(image: Image): Image {.nodestroy.} + +proc `=destroy`*(x: Image) = + discard + +proc `=sink`*(dest: var Image; source: Image) = + `=destroy`(dest) + wasMoved(dest) + +proc `=dup`*(source: Image): Image {.nodestroy.} = + result = imageCopy(source) + +proc `=copy`*(dest: var Image; source: Image) = + dest = imageCopy(source) # calls =sink implicitly + +proc `=destroy`*(x: EmbeddedImage) = discard + +proc `=dup`*(source: EmbeddedImage): EmbeddedImage {.nodestroy.} = source + +proc `=copy`*(dest: var EmbeddedImage; source: EmbeddedImage) {.nodestroy.} = + dest = source + +proc imageCopy*(image: Image): Image = + result = image + +proc main2 = + block: + var a = Image(len: 2).EmbeddedImage + var b = Image(len: 1).EmbeddedImage + b = a + doAssert Image(a).len == 2 + doAssert Image(b).len == 2 + + block: + var a = Image(len: 2) + var b = Image(len: 1) + b = a + doAssert a.len == 2 + doAssert b.len == 0 + +main2() + +type + Edge = object + neighbor {.cursor.}: Node + + NodeObj = object + neighbors: seq[Edge] + label: string + visited: bool + Node = ref NodeObj + + Graph = object + nodes: seq[Node] + +proc `=destroy`(x: NodeObj) = + `=destroy`(x.neighbors) + `=destroy`(x.label) + +proc addNode(self: var Graph; label: string): Node = + self.nodes.add(Node(label: label)) + result = self.nodes[^1] + +proc addEdge(self: Graph; source, neighbor: Node) = + source.neighbors.add(Edge(neighbor: neighbor)) + +block: + proc main = + var graph: Graph + let nodeA = graph.addNode("a") + let nodeB = graph.addNode("b") + let nodeC = graph.addNode("c") + + graph.addEdge(nodeA, neighbor = nodeB) + graph.addEdge(nodeA, neighbor = nodeC) + + main() + +block: + type RefObj = ref object + + proc `[]`(val: static[int]) = # works with different name/overload or without static arg + discard + + template noRef(T: typedesc): typedesc = # works without template indirection + typeof(default(T)[]) + + proc `=destroy`(x: noRef(RefObj)) = + discard + + proc foo = + var x = new RefObj + doAssert $(x[]) == "()" + + # bug #11705 + foo() + +block: # bug #22197 + type + H5IdObj = object + H5Id = ref H5IdObj + + FileID = distinct H5Id + + H5GroupObj = object + file_id: FileID + H5Group = ref H5GroupObj + + ## This would make it work! + #proc `=destroy`*(x: FileID) = `=destroy`(cast[H5Id](x)) + ## If this does not exist, it also works! + proc newFileID(): FileID = FileID(H5Id()) + + proc `=destroy`(grp: H5GroupObj) = + ## Closes the group and resets all references to nil. + if cast[pointer](grp.fileId) != nil: + `=destroy`(grp.file_id) + + var grp = H5Group() + reset(grp.file_id) + reset(grp) + +import std/tables + +block: # bug #22286 + type + A = object + B = object + a: A + C = object + b: B + + proc `=destroy`(self: A) = + echo "destroyed" + + proc `=destroy`(self: C) = + `=destroy`(self.b) + + var c = C() + +block: # https://forum.nim-lang.org/t/10642 + type AObj = object + name: string + tableField: Table[string, string] + + proc `=destroy`(x: AObj) = + `=destroy`(x.name) + `=destroy`(x.tableField) diff --git a/tests/destructor/tobjfield_analysis.nim b/tests/destructor/tobjfield_analysis.nim new file mode 100644 index 000000000..83f394c3b --- /dev/null +++ b/tests/destructor/tobjfield_analysis.nim @@ -0,0 +1,51 @@ +discard """ + output: '''works''' +""" + +# bug #11095 + +type + MyVal[T] = object + f: ptr T + +proc `=destroy`[T](x: var MyVal[T]) = + if x.f != nil: + dealloc(x.f) + +proc `=sink`[T](x1: var MyVal[T], x2: MyVal[T]) = + if x1.f != x2.f: + `=destroy`(x1) + x1.f = x2.f + +proc `=`[T](x1: var MyVal[T], x2: MyVal[T]) {.error.} + +proc newVal[T](x: sink T): MyVal[T] = + result.f = create(T) + result.f[] = x + +proc set[T](x: var MyVal[T], val: T) = + x.f[] = val + +proc sinkMe[T](x: sink MyVal[T]) = + discard + +var flag = false + +proc main = + var y = case flag + of true: + var x1 = newVal[float](1.0) + var x2 = newVal[float](2.0) + (newVal(x1), newVal(x2)) + + of false: + var x1 = newVal[float](1.0) + var x2 = newVal[float](2.0) + (newVal(x1), newVal(x2)) + + sinkMe y[0] + sinkMe y[1] + echo "works" + +main() + diff --git a/tests/destructor/topt.nim b/tests/destructor/topt.nim new file mode 100644 index 000000000..4adda1914 --- /dev/null +++ b/tests/destructor/topt.nim @@ -0,0 +1,61 @@ + +discard """ + output: '''5 +vseq destroy +''' +joinable: false +""" +type + opt*[T] = object + case exists: bool + of true: val: T + of false: discard + +proc some*[T](val: sink T): opt[T] {.inline.} = + ## Returns an ``opt`` that has the value. + ## nil is considered as none for reference types + result = opt[T](exists: true, val: val) + +proc none*(T: typedesc): opt[T] {.inline.} = + ## Returns an ``opt`` for this type that has no value. + # the default is the none type + discard + +proc none*[T]: opt[T] {.inline.} = + ## Alias for ``none(T)``. + none(T) + +proc unsafeGet*[T](self: opt[T]): lent T {.inline.} = + ## Returns the value of a ``some``. Behavior is undefined for ``none``. + self.val + +type + VSeq*[T] = object + len: int + data: ptr UncheckedArray[T] + +proc `=destroy`*[T](m: var VSeq[T]) {.inline.} = + if m.data != nil: + echo "vseq destroy" + dealloc(m.data) + m.data = nil + +proc `=`*[T](m: var VSeq[T], m2: VSeq[T]) {.error.} + +proc `=sink`*[T](m: var VSeq[T], m2: VSeq[T]) {.inline.} = + if m.data != m2.data: + `=destroy`(m) + m.len = m2.len + m.data = m2.data + +proc newVSeq*[T](len: int): VSeq[T] = + ## Only support sequence creation from scalar size because creation from + ## vetorized size can't reproduce the original scalar size + result.len = len + if len > 0: + result.data = cast[ptr UncheckedArray[T]](alloc(sizeof(T) * len)) + +let x = some newVSeq[float](5) +echo x.unsafeGet.len +let y = none(VSeq[float]) + diff --git a/tests/destructor/topttree.nim b/tests/destructor/topttree.nim index 924644392..8cf757e8b 100644 --- a/tests/destructor/topttree.nim +++ b/tests/destructor/topttree.nim @@ -1,4 +1,5 @@ discard """ + disabled: i386 output: '''10.0 60.0 90.0 @@ -8,7 +9,7 @@ discard """ 90.0 120.0 8 8''' - cmd: '''nim c --newruntime $file''' +joinable: false """ import typetraits @@ -22,6 +23,7 @@ var proc `=destroy`*[T](x: var opt[T]) = if x.data != nil: + mixin `=destroy` when not supportsCopyMem(T): `=destroy`(x.data[]) dealloc(x.data) @@ -90,6 +92,8 @@ proc write(t: opt[Tree]) = write stdout, it.data, "\n" write(it.ri) +proc use(t: opt[Tree]) = discard + proc main = var t: opt[Tree] insert t, 60.0 @@ -99,6 +103,7 @@ proc main = write t let copy = t write copy + use t main() echo allocCount, " ", deallocCount diff --git a/tests/destructor/towned_binary_tree.nim b/tests/destructor/towned_binary_tree.nim new file mode 100644 index 000000000..fb635e7c6 --- /dev/null +++ b/tests/destructor/towned_binary_tree.nim @@ -0,0 +1,91 @@ +discard """ + cmd: '''nim c -d:nimAllocStats --gc:arc $file''' + output: '''31665 +(allocCount: 33334, deallocCount: 33334)''' +""" + +# bug #11053 + +import random + +type Node = ref object + x, y: int32 + left, right: owned Node + +proc newNode(x: int32): owned Node = + result = Node(x: x, y: rand(high int32).int32) + +proc merge(lower, greater: owned Node): owned Node = + if lower.isNil: + result = greater + elif greater.isNil: + result = lower + elif lower.y < greater.y: + lower.right = merge(lower.right, greater) + result = lower + else: + greater.left = merge(lower, greater.left) + result = greater + +proc splitBinary(orig: owned Node, value: int32): (owned Node, owned Node) = + if orig.isNil: + result = (nil, nil) + elif orig.x < value: + let splitPair = splitBinary(orig.right, value) + orig.right = splitPair[0] + result = (orig, splitPair[1]) + else: + let splitPair = splitBinary(orig.left, value) + orig.left = splitPair[1] + result = (splitPair[0], orig) + +proc merge3(lower, equal, greater: owned Node): owned Node = + merge(merge(lower, equal), greater) + +proc split(orig: owned Node, value: int32): tuple[lower, equal, greater: owned Node] = + let + (lower, equalGreater) = splitBinary(orig, value) + (equal, greater) = splitBinary(equalGreater, value + 1) + result = (lower, equal, greater) + +type Tree = object + root: owned Node + +proc hasValue(self: var Tree, x: int32): bool = + let splited = split(move self.root, x) + result = not splited.equal.isNil + self.root = merge3(splited.lower, splited.equal, splited.greater) + +proc insert(self: var Tree, x: int32) = + var splited = split(move self.root, x) + if splited.equal.isNil: + splited.equal = newNode(x) + self.root = merge3(splited.lower, splited.equal, splited.greater) + +proc erase(self: var Tree, x: int32) = + let splited = split(move self.root, x) + self.root = merge(splited.lower, splited.greater) + +proc main() = + var + tree = Tree() + cur = 5'i32 + res = 0 + + for i in 1 ..< 100000: + let a = i mod 3 + cur = (cur * 57 + 43) mod 10007 + case a: + of 0: + tree.insert(cur) + of 1: + tree.erase(cur) + of 2: + if tree.hasValue(cur): + res += 1 + else: + discard + echo res + +dumpAllocStats: + main() diff --git a/tests/destructor/tprevent_assign.nim b/tests/destructor/tprevent_assign.nim new file mode 100644 index 000000000..4c484ebc1 --- /dev/null +++ b/tests/destructor/tprevent_assign.nim @@ -0,0 +1,33 @@ +discard """ + errormsg: "'=copy' is not available for type <Foo>; requires a copy because it's not the last read of 'otherTree'" + line: 29 +""" + +type + Foo = object + x: int + +proc `=destroy`(f: var Foo) = f.x = 0 +proc `=`(a: var Foo; b: Foo) {.error.} # = a.x = b.x +proc `=sink`(a: var Foo; b: Foo) = a.x = b.x + +proc createTree(x: int): Foo = + Foo(x: x) + +proc take2(a, b: sink Foo) = + echo a.x, " ", b.x + +proc allowThis() = + # all these temporary lets are harmless: + let otherTree = createTree(44) + let b = otherTree + let c = b + take2(createTree(34), c) + +proc preventThis() = + let otherTree = createTree(44) + let b = otherTree + take2(createTree(34), otherTree) + +allowThis() +preventThis() diff --git a/tests/destructor/tprevent_assign2.nim b/tests/destructor/tprevent_assign2.nim new file mode 100644 index 000000000..eb5588b1a --- /dev/null +++ b/tests/destructor/tprevent_assign2.nim @@ -0,0 +1,56 @@ +discard """ + errormsg: "'=dup' is not available for type <Foo>, which is inferred from unavailable '=copy'; requires a copy because it's not the last read of 'otherTree'; another read is done here: tprevent_assign2.nim(51, 31); routine: preventThis" + file: "tprevent_assign2.nim" + line: 49 +""" + +type + Foo = object + x: int + +proc `=destroy`(f: var Foo) = f.x = 0 +proc `=copy`(a: var Foo; b: Foo) {.error.} # = a.x = b.x + +proc `=sink`(a: var Foo; b: Foo) = a.x = b.x + +proc createTree(x: int): Foo = + Foo(x: x) + +proc take2(a, b: sink Foo) = + echo a.x, " ", b.x + +when false: + var otherTree: Foo + try: + for i in 0..3: + while true: + #if i == 0: + otherTree = createTree(44) + case i + of 0: + echo otherTree + take2(createTree(34), otherTree) + of 1: + take2(createTree(34), otherTree) + else: + discard + finally: + discard + +proc preventThis() = + var otherTree: Foo + for i in 0..3: + while true: + if i == 0: + otherTree = createTree(44) + case i + of 0: + echo otherTree + take2(createTree(34), otherTree) + of 1: + take2(createTree(34), otherTree) + else: + discard + +#allowThis() +preventThis() diff --git a/tests/destructor/tprevent_assign3.nim b/tests/destructor/tprevent_assign3.nim new file mode 100644 index 000000000..aa834a66c --- /dev/null +++ b/tests/destructor/tprevent_assign3.nim @@ -0,0 +1,54 @@ +discard """ + errormsg: "'=dup' is not available for type <Foo>; requires a copy because it's not the last read of 'otherTree'" + file: "tprevent_assign3.nim" + line: 47 +""" + +type + Foo = object + x: int + +proc `=destroy`(f: var Foo) = f.x = 0 +proc `=copy`(a: var Foo; b: Foo) {.error.} # = a.x = b.x +proc `=dup`(a: Foo): Foo {.error.} +proc `=sink`(a: var Foo; b: Foo) = a.x = b.x + +proc createTree(x: int): Foo = + Foo(x: x) + +proc take2(a, b: sink Foo) = + echo a.x, " ", b.x + +when false: + var otherTree: Foo + try: + for i in 0..3: + while true: + #if i == 0: + otherTree = createTree(44) + case i + of 0: + echo otherTree + take2(createTree(34), otherTree) + of 1: + take2(createTree(34), otherTree) + else: + discard + finally: + discard + +proc preventThis2() = + var otherTree: Foo + try: + try: + otherTree = createTree(44) + echo otherTree + finally: + take2(createTree(34), otherTree) + finally: + echo otherTree + +#allowThis() +preventThis2() + + diff --git a/tests/destructor/trecursive.nim b/tests/destructor/trecursive.nim new file mode 100644 index 000000000..e7afa6ba9 --- /dev/null +++ b/tests/destructor/trecursive.nim @@ -0,0 +1,60 @@ + +discard """ + output: ''' +test1 OK +''' +""" + +import smart_ptr + +type + Node[T] = object + value: T + next: SharedPtr[Node[T]] + + ForwardList[T] = object + first: SharedPtr[Node[T]] + len: Natural + +proc pushFront*[T] (list: var ForwardList[T], val: sink T) = + var newNode = newSharedPtr(Node[T](value: val)) + var result = false + while not result: + var head = list.first + newNode.get.next = head + result = list.first.cas(head, newNode) + list.len.atomicInc() + +proc test1() = + var list: ForwardList[int] + list.pushFront(1) + doAssert list.len == 1 + echo "test1 OK" + +test1() + +#------------------------------------------------------------------------------ +# issue #14217 + +type + MyObject = object + p: ptr int + +proc `=destroy`(x: var MyObject) = + if x.p != nil: + deallocShared(x.p) + +proc `=`(x: var MyObject, y: MyObject) {.error.} + +proc newMyObject(i: int): MyObject = + result.p = createShared(int) + result.p[] = i + +proc test: seq[MyObject] = + for i in 0..3: + let x = newMyObject(i) + result.add x + +var x = test() +for i in 0..3: + doAssert(x[i].p[] == i) diff --git a/tests/destructor/tselect.nim b/tests/destructor/tselect.nim new file mode 100644 index 000000000..c22bf7203 --- /dev/null +++ b/tests/destructor/tselect.nim @@ -0,0 +1,50 @@ +discard """ + output: '''abcsuffix +xyzsuffix +destroy foo 2 +destroy foo 1 +''' + cmd: '''nim c --gc:arc $file''' +""" + +proc select(cond: bool; a, b: sink string): string = + if cond: + result = a # moves a into result + else: + result = b # moves b into result + +proc test(param: string; cond: bool) = + var x = "abc" & param + var y = "xyz" & param + + # possible self-assignment: + x = select(cond, x, y) + + echo x + # 'select' must communicate what parameter has been + # consumed. We cannot simply generate: + # (select(...); wasMoved(x); wasMoved(y)) + +test("suffix", true) +test("suffix", false) + + + +#-------------------------------------------------------------------- +# issue #13659 + +type + Foo = ref object + data: int + parent: Foo + +proc `=destroy`(self: var type(Foo()[])) = + echo "destroy foo ", self.data + for i in self.fields: i.reset + +proc getParent(self: Foo): Foo = self.parent + +var foo1 = Foo(data: 1) +var foo2 = Foo(data: 2, parent: foo1) + +foo2.getParent.data = 1 \ No newline at end of file diff --git a/tests/destructor/tsetjmp_raise.nim b/tests/destructor/tsetjmp_raise.nim new file mode 100644 index 000000000..3a9803f39 --- /dev/null +++ b/tests/destructor/tsetjmp_raise.nim @@ -0,0 +1,11 @@ +discard """ + outputsub: "index 2 not in 0 .. 0 [IndexDefect]" + exitcode: 1 + cmd: "nim c --gc:arc --exceptions:setjmp $file" +""" + +# bug #12961 +# --gc:arc --exceptions:setjmp +let a = @[1] +echo a[2] + diff --git a/tests/destructor/tsimpleclosure.nim b/tests/destructor/tsimpleclosure.nim new file mode 100644 index 000000000..9626dd6f8 --- /dev/null +++ b/tests/destructor/tsimpleclosure.nim @@ -0,0 +1,62 @@ +discard """ + cmd: '''nim c -d:nimAllocStats --gc:arc $file''' + output: '''a b +70 +hello +hello +hello +(allocCount: 3, deallocCount: 3)''' +""" + +import system / ansi_c + +proc main(): owned(proc()) = + var a = "a" + var b = "b" + result = proc() = + echo a, " ", b + + +proc foo(f: (iterator(): int)) = + for i in f(): echo i + +proc wrap = + let p = main() + p() + + let fIt = iterator(): int = yield 70 + foo fIt + +wrap() + +# bug #11533 +proc say = echo "hello" + +# Error: internal error: genAssignment: tyNil +var err0: proc() = say +err0() + +var ok0: proc() +ok0 = say +ok0() + +var ok1 = say +ok1() + +when false: + # bug #12443 + func newStringIterator(s: string): owned(iterator(): char) = + result = iterator(): char = + var pos = 0 + while pos < s.len: + yield s[pos] + inc pos + + proc stringIter() = + let si = newStringIterator("foo") + for i in si(): + echo i + + stringIter() + +echo getAllocStats() diff --git a/tests/destructor/tsink.nim b/tests/destructor/tsink.nim new file mode 100644 index 000000000..e8750ad7c --- /dev/null +++ b/tests/destructor/tsink.nim @@ -0,0 +1,70 @@ +discard """ + matrix: "--mm:arc" +""" + +type AnObject = object of RootObj + value*: int + +proc mutate(shit: sink AnObject) = + shit.value = 1 + +proc foo = # bug #23359 + var bar = AnObject(value: 42) + mutate(bar) + doAssert bar.value == 42 + +foo() + +block: # bug #23902 + proc foo(a: sink string): auto = (a, a) + + proc bar(a: sink int): auto = return a + + proc foo(a: sink string) = + var x = (a, a) + +block: # bug #24175 + block: + func mutate(o: sink string): string = + o[1] = '1' + result = o + + static: + let s = "999" + let m = mutate(s) + doAssert s == "999" + doAssert m == "919" + + func foo() = + let s = "999" + let m = mutate(s) + doAssert s == "999" + doAssert m == "919" + + static: + foo() + foo() + + block: + type O = object + a: int + + func mutate(o: sink O): O = + o.a += 1 + o + + static: + let x = O(a: 1) + let y = mutate(x) + doAssert x.a == 1 + doAssert y.a == 2 + + proc foo() = + let x = O(a: 1) + let y = mutate(x) + doAssert x.a == 1 + doAssert y.a == 2 + + static: + foo() + foo() diff --git a/tests/destructor/ttuple.nim b/tests/destructor/ttuple.nim new file mode 100644 index 000000000..d0ea72c60 --- /dev/null +++ b/tests/destructor/ttuple.nim @@ -0,0 +1,130 @@ + +discard """ + output: '''5.0 10.0 +=destroy +=destroy +''' +""" + +type + MyOpt[T] = object + case has: bool: + of true: val: T + of false: nil + + MyVal = object + f: ptr float + +proc `=destroy`(x: var MyVal) = + if x.f != nil: + dealloc(x.f) + +proc `=sink`(x1: var MyVal, x2: Myval) = + if x1.f != x2.f: + `=destroy`(x1) + x1.f = x2.f + +proc `=`(x1: var MyVal, x2: Myval) = + if x1.f != x2.f: + `=destroy`(x1) + x1.f = create(float) + x1.f[] = x2.f[] + +proc newVal(x: float): MyVal = + result.f = create(float) + result.f[] = x + +template getIt[T, R](self: MyOpt[T], body: untyped, default: R): R = + if self.has: + template it: untyped {.inject.} = self.val + body + else: + default + +proc myproc(h: MyOpt[float]) = + let (a, b) = h.getIt((newVal(it), newVal(it * 2)), (newVal(1.0), newVal(1.0))) + echo a.f[], " ", b.f[] + +let h = MyOpt[float](has: true, val: 5.0) +myproc(h) + + +#------------------------------------------------------------- +type + MyObject* = object + len*: int + amount: UncheckedArray[float] + + MyObjPtr* = ptr MyObject + + MyObjContainer* {.byref.} = object + size1: int + size2: int + data: ptr UncheckedArray[MyObjPtr] + + +proc size1*(m: MyObjContainer): int {.inline.} = m.size1 +proc size2*(m: MyObjContainer): int {.inline.} = m.size2 + +proc allocateMyObjPtr(size2: int): MyObjPtr = + cast[MyObjPtr](allocShared(sizeof(MyObject) + sizeof(float) * size2.int)) + +proc `=destroy`*(m: var MyObjContainer) {.inline.} = + if m.data != nil: + for i in 0..<m.size1: + if m.data[i] != nil: + deallocShared(m.data[i]) + m.data[i] = nil + deallocShared(m.data) + echo "=destroy" + m.data = nil + +proc `=sink`*(m: var MyObjContainer, m2: MyObjContainer) {.inline.} = + if m.data != m2.data: + `=destroy`(m) + m.size1 = m2.size1 + m.size2 = m2.size2 + m.data = m2.data + + +proc `=`*(m: var MyObjContainer, m2: MyObjContainer) {.error.} + ## non copyable + +func newMyObjContainer*(size2: Natural): MyObjContainer = + result.size2 = size2 + +proc push(m: var MyObjContainer, cf: MyObjPtr) = + ## Add MyObjPtr to MyObjContainer, shallow copy + m.size1.inc + m.data = cast[ptr UncheckedArray[MyObjPtr]](reallocShared(m.data, m.size1 * sizeof(MyObjPtr))) + m.data[m.size1 - 1] = cf + + +proc add*(m: var MyObjContainer, amount: float) = + assert m.size2 > 0, "MyObjContainer is not initialized, use newMyObjContainer() to initialize object before use" + let cf = allocateMyObjPtr(m.size2) + for i in 0..<m.size2: + cf.amount[i.int] = amount + + m.push(cf) + +proc add*(dest: var MyObjContainer, src: sink MyObjContainer) = + # merge containers + + for i in 0..<src.size1: + dest.push src.data[i] + src.data[i] = nil + + +proc test = + var cf1 = newMyObjContainer(100) + cf1.add(1) + cf1.add(2) + + var cf3 = newMyObjContainer(100) + cf3.add(2) + cf3.add(3) + + cf1.add(cf3) + +test() diff --git a/tests/destructor/turn_destroy_into_finalizer.nim b/tests/destructor/turn_destroy_into_finalizer.nim new file mode 100644 index 000000000..1409c1c57 --- /dev/null +++ b/tests/destructor/turn_destroy_into_finalizer.nim @@ -0,0 +1,26 @@ +discard """ + output: "turn_destroy_into_finalizer works" + joinable: false +""" + +type + Foo = object + id: int + +var destroyed: int + +proc `=destroy`(x: var Foo) = + #echo "finally ", x.id + inc destroyed + +proc main = + var r: ref Foo + for i in 1..50_000: + new(r) + r.id = i + if destroyed > 30_000: + echo "turn_destroy_into_finalizer works" + else: + echo "turn_destroy_into_finalizer failed: ", destroyed + +main() diff --git a/tests/destructor/tuse_ownedref_after_move.nim b/tests/destructor/tuse_ownedref_after_move.nim new file mode 100644 index 000000000..69348d530 --- /dev/null +++ b/tests/destructor/tuse_ownedref_after_move.nim @@ -0,0 +1,57 @@ +discard """ + cmd: '''nim c --newruntime $file''' + errormsg: "'=copy' is not available for type <owned Button>; requires a copy because it's not the last read of ':envAlt.b1'; routine: main" + line: 48 +""" + +import system / ansi_c + +type + Widget* = ref object of RootObj + drawImpl: owned(proc (self: Widget)) + + Button* = ref object of Widget + caption: string + onclick: owned(proc()) + + Window* = ref object of Widget + elements: seq[owned Widget] + + +proc newButton(caption: string; onclick: owned(proc())): owned Button = + proc draw(self: Widget) = + let b = Button(self) + echo b.caption + + result = Button(drawImpl: draw, caption: caption, onclick: onclick) + +proc newWindow(): owned Window = + proc draw(self: Widget) = + let w = Window(self) + for e in w.elements: + if not e.drawImpl.isNil: e.drawImpl(e) + + result = Window(drawImpl: draw, elements: @[]) + +proc draw(w: Widget) = + if not w.drawImpl.isNil: w.drawImpl(w) + +proc add*(w: Window; elem: owned Widget) = + w.elements.add elem + +proc main = + var w = newWindow() + + var b = newButton("button", nil) + b.onclick = proc () = + b.caption = "clicked!" + w.add b + + w.draw() + # simulate button click: + b.onclick() + + w.draw() + +main() + diff --git a/tests/destructor/tuse_result_prevents_sinks.nim b/tests/destructor/tuse_result_prevents_sinks.nim new file mode 100644 index 000000000..e74c16da3 --- /dev/null +++ b/tests/destructor/tuse_result_prevents_sinks.nim @@ -0,0 +1,37 @@ +discard """ + output: "" + targets: "c" +""" + +# bug #9594 + +type + Foo = object + i: int + +proc `=`(self: var Foo; other: Foo) = + self.i = other.i + 1 + +proc `=sink`(self: var Foo; other: Foo) = + self.i = other.i + +proc `=destroy`(self: var Foo) = discard + +template preventCursorInference(x) = + let p = addr(x) + +proc test(): Foo = + result = Foo() + let temp = result + preventCursorInference temp + doAssert temp.i > 0 + return result + +proc testB(): Foo = + result = Foo() + let temp = result + preventCursorInference temp + doAssert temp.i > 0 + +discard test() +discard testB() diff --git a/tests/destructor/tv2_cast.nim b/tests/destructor/tv2_cast.nim new file mode 100644 index 000000000..48bdf67dd --- /dev/null +++ b/tests/destructor/tv2_cast.nim @@ -0,0 +1,116 @@ +discard """ + output: '''@[1] +@[116, 101, 115, 116] +@[1953719668, 875770417] +destroying O1''' + cmd: '''nim c --mm:arc --expandArc:main --expandArc:main1 --expandArc:main2 --expandArc:main3 --hints:off --assertions:off $file''' + nimout: ''' +--expandArc: main + +var + data + :tmpD +data = cast[string](encode(cast[seq[byte]]( + :tmpD = newString(100) + :tmpD))) +`=destroy`(:tmpD) +`=destroy`(data) +-- end of expandArc ------------------------ +--expandArc: main1 + +var + s + data +s = newString(100) +data = cast[string](encode(toOpenArrayByte(s, 0, len(s) - 1))) +`=destroy`(data) +`=destroy`(s) +-- end of expandArc ------------------------ +--expandArc: main2 + +var + s + data +s = newSeq(100) +data = cast[string](encode(s)) +`=destroy`(data) +`=destroy_1`(s) +-- end of expandArc ------------------------ +--expandArc: main3 + +var + data + :tmpD +data = cast[string](encode do: + :tmpD = newSeq(100) + :tmpD) +`=destroy`(:tmpD) +`=destroy_1`(data) +-- end of expandArc ------------------------ +''' +""" + +func encode*(src: openArray[byte]): seq[byte] = + result = newSeq[byte](src.len) + +template compress*(src: string): string = + cast[string](encode(cast[seq[byte]](src))) + +proc main = + let data = compress(newString(100)) +main() + +proc main1 = + var + s = newString(100) + let data = cast[string](encode(s.toOpenArrayByte(0, s.len-1))) +main1() + +proc main2 = + var + s = newSeq[byte](100) + let data = cast[string](encode(s)) +main2() + +proc main3 = + let data = cast[string](encode(newSeq[byte](100))) +main3() + +# bug #11018 +discard cast[seq[uint8]](@[1]) +discard cast[seq[uint8]]("test") +echo cast[seq[uint8]](@[1]) +echo cast[seq[uint8]]("test") + +discard cast[string](@[116'u8, 101, 115, 116]) +#echo cast[string](@[116'u8, 101, 115, 116, 0]) +var a = cast[seq[uint32]]("test1234") +a.setLen(2) +echo a + + +#issue 11204 +var ac {.compileTime.} = @["a", "b"] +const bc = ac.len + + +type + O = object of RootRef + i: int + + O1 = object of O + O2 = object of O + +proc `=destroy`(o: var O) = + echo "destroying O" + +proc `=destroy`(o: var O1) = + echo "destroying O1" + +proc `=destroy`(o: var O2) = + echo "destroying O2" + +proc test = + let o3 = cast[ref O2]((ref O1)()) + +test() diff --git a/tests/destructor/tv2_raise.nim b/tests/destructor/tv2_raise.nim new file mode 100644 index 000000000..66b0aec30 --- /dev/null +++ b/tests/destructor/tv2_raise.nim @@ -0,0 +1,53 @@ +discard """ + valgrind: true + cmd: '''nim c -d:nimAllocStats --newruntime $file''' + output: '''OK 3 +(allocCount: 7, deallocCount: 4)''' +""" + +import strutils, math +import system / ansi_c + +proc mainA = + try: + var e: owned(ref ValueError) + new(e) + e.msg = "message" + raise e + except Exception as e: + raise + + +proc main = + raise newException(ValueError, "argh") + +var ok = 0 +try: + mainA() +except ValueError: + inc ok +except: + discard + +try: + main() +except ValueError: + inc ok +except: + discard + +# bug #11577 + +proc newError*: owned(ref Exception) {.noinline.} = + new(result) + +proc mainC = + raise newError() + +try: + mainC() +except: + inc ok + +echo "OK ", ok +echo getAllocStats() diff --git a/tests/destructor/twasmoved.nim b/tests/destructor/twasmoved.nim new file mode 100644 index 000000000..566322702 --- /dev/null +++ b/tests/destructor/twasmoved.nim @@ -0,0 +1,14 @@ +type + Foo = object + id: int + +proc `=wasMoved`(x: var Foo) = + x.id = -1 + +proc foo = + var s = Foo(id: 999) + var m = move s + doAssert s.id == -1 + doAssert m.id == 999 + +foo() diff --git a/tests/destructor/twasmoved_error.nim b/tests/destructor/twasmoved_error.nim new file mode 100644 index 000000000..1cd57e3df --- /dev/null +++ b/tests/destructor/twasmoved_error.nim @@ -0,0 +1,37 @@ +discard """ + cmd: '''nim c --mm:arc $file''' + errormsg: "'=wasMoved' is not available for type <Game>; routine: main" +""" + +# bug #19291 + +const + screenWidth = 800 + screenHeight = 450 + +var + ready = false +type + Game = object + +proc `=destroy`(x: var Game) = + assert ready, "Window is already opened" + ready = false + +proc `=sink`(x: var Game; y: Game) {.error.} +proc `=copy`(x: var Game; y: Game) {.error.} +proc `=wasMoved`(x: var Game) {.error.} + +proc initGame(width, height: int32, title: string): Game = + assert not ready, "Window is already closed" + ready = true + +proc update(x: Game) = discard + +proc main = + var g = initGame(screenWidth, screenHeight, "Tetris raylib") + g.update() + var g2 = g + echo "hello" + +main() diff --git a/tests/destructor/twidgets.nim b/tests/destructor/twidgets.nim new file mode 100644 index 000000000..f13868110 --- /dev/null +++ b/tests/destructor/twidgets.nim @@ -0,0 +1,76 @@ +discard """ + cmd: '''nim c -d:nimAllocStats --newruntime $file''' + output: '''button +clicked! +(allocCount: 4, deallocCount: 4)''' +""" + +import system / ansi_c + +type + Widget* = ref object of RootObj + drawImpl: owned(proc (self: Widget)) + + Button* = ref object of Widget + caption: string + onclick: owned(proc()) + + Window* = ref object of Widget + elements: seq[owned Widget] + + +proc newButton(caption: string; onclick: owned(proc())): owned Button = + proc draw(self: Widget) = + let b = Button(self) + echo b.caption + + #result = Button(drawImpl: draw, caption: caption, onclick: onclick) + new(result) + result.drawImpl = draw + result.caption = caption + result.onclick = onclick + +iterator unitems*[T](a: seq[owned T]): T {.inline.} = + ## Iterates over each item of `a`. + var i = 0 + let L = len(a) + while i < L: + yield a[i] + inc(i) + assert(len(a) == L, "the length of the seq changed while iterating over it") + +proc newWindow(): owned Window = + proc windraw(self: Widget) = + let w = Window(self) + for i in 0..<len(w.elements): + let e = Widget(w.elements[i]) + let d = (proc(self: Widget))e.drawImpl + if not d.isNil: d(e) + + result = Window(drawImpl: windraw, elements: @[]) + +proc draw(w: Widget) = + let d = (proc(self: Widget))w.drawImpl + if not d.isNil: d(w) + +proc add*(w: Window; elem: owned Widget) = + w.elements.add elem + +proc main = + var w = newWindow() + + var b = newButton("button", nil) + let u: Button = b + b.onclick = proc () = + u.caption = "clicked!" + w.add b + + w.draw() + # simulate button click: + u.onclick() + + w.draw() + +dumpAllocstats: + main() + diff --git a/tests/destructor/twidgets_unown.nim b/tests/destructor/twidgets_unown.nim new file mode 100644 index 000000000..8653d5c28 --- /dev/null +++ b/tests/destructor/twidgets_unown.nim @@ -0,0 +1,72 @@ +discard """ + cmd: '''nim c -d:nimAllocStats --newruntime $file''' + output: '''button +clicked! +(allocCount: 6, deallocCount: 6)''' +""" + +import system / ansi_c + +type + Widget* = ref object of RootObj + drawImpl: owned(proc (self: Widget)) + + Button* = ref object of Widget + caption: string + onclick: owned(proc()) + + Window* = ref object of Widget + elements: seq[owned Widget] + + +proc newButton(caption: string; onclick: owned(proc())): owned Button = + proc draw(self: Widget) = + let b = Button(self) + echo b.caption + + #result = Button(drawImpl: draw, caption: caption, onclick: onclick) + new(result) + result.drawImpl = draw + result.caption = caption + result.onclick = onclick + +proc newWindow(): owned Window = + proc windraw(self: Widget) = + let w = Window(self) + for e in unown(w.elements): + let d = unown e.drawImpl + if not d.isNil: d(e) + + result = Window(drawImpl: windraw, elements: @[]) + +proc draw(w: Widget) = + let d = unown w.drawImpl + if not d.isNil: d(w) + +proc add*(w: Window; elem: owned Widget) = + w.elements.add elem + +proc main = + var w = newWindow() + + var b = newButton("button", nil) + let u = unown b + var clicked = "clicked" + b.onclick = proc () = + clicked.add "!" + u.caption = clicked + w.add b + + w.draw() + # simulate button click: + u.onclick() + + w.draw() + + # bug #11257 + var a: owned proc() + if a != nil: + a() + +dumpAllocStats: + main() |