1-- $Id: testes/locals.lua $
2-- See Copyright Notice in file all.lua
3
4print('testing local variables and environments')
5
6local debug = require"debug"
7
8local tracegc = require"tracegc"
9
10
11-- bug in 5.1:
12
13local function f(x) x = nil; return x end
14assert(f(10) == nil)
15
16local function f() local x; return x end
17assert(f(10) == nil)
18
19local function f(x) x = nil; local y; return x, y end
20assert(f(10) == nil and select(2, f(20)) == nil)
21
22do
23 local i = 10
24 do local i = 100; assert(i==100) end
25 do local i = 1000; assert(i==1000) end
26 assert(i == 10)
27 if i ~= 10 then
28 local i = 20
29 else
30 local i = 30
31 assert(i == 30)
32 end
33end
34
35
36
37f = nil
38
39local f
40local x = 1
41
42a = nil
43load('local a = {}')()
44assert(a == nil)
45
46function f (a)
47 local _1, _2, _3, _4, _5
48 local _6, _7, _8, _9, _10
49 local x = 3
50 local b = a
51 local c,d = a,b
52 if (d == b) then
53 local x = 'q'
54 x = b
55 assert(x == 2)
56 else
57 assert(nil)
58 end
59 assert(x == 3)
60 local f = 10
61end
62
63local b=10
64local a; repeat local b; a,b=1,2; assert(a+1==b); until a+b==3
65
66
67assert(x == 1)
68
69f(2)
70assert(type(f) == 'function')
71
72
73local function getenv (f)
74 local a,b = debug.getupvalue(f, 1)
75 assert(a == '_ENV')
76 return b
77end
78
79-- test for global table of loaded chunks
80assert(getenv(load"a=3") == _G)
81local c = {}; local f = load("a = 3", nil, nil, c)
82assert(getenv(f) == c)
83assert(c.a == nil)
84f()
85assert(c.a == 3)
86
87-- old test for limits for special instructions
88do
89 local i = 2
90 local p = 4 -- p == 2^i
91 repeat
92 for j=-3,3 do
93 assert(load(string.format([[local a=%s;
94 a=a+%s;
95 assert(a ==2^%s)]], j, p-j, i), '')) ()
96 assert(load(string.format([[local a=%s;
97 a=a-%s;
98 assert(a==-2^%s)]], -j, p-j, i), '')) ()
99 assert(load(string.format([[local a,b=0,%s;
100 a=b-%s;
101 assert(a==-2^%s)]], -j, p-j, i), '')) ()
102 end
103 p = 2 * p; i = i + 1
104 until p <= 0
105end
106
107print'+'
108
109
110if rawget(_G, "T") then
111 -- testing clearing of dead elements from tables
112 collectgarbage("stop") -- stop GC
113 local a = {[{}] = 4, [3] = 0, alo = 1,
114 a1234567890123456789012345678901234567890 = 10}
115
116 local t = T.querytab(a)
117
118 for k,_ in pairs(a) do a[k] = undef end
119 collectgarbage() -- restore GC and collect dead fields in 'a'
120 for i=0,t-1 do
121 local k = querytab(a, i)
122 assert(k == nil or type(k) == 'number' or k == 'alo')
123 end
124
125 -- testing allocation errors during table insertions
126 local a = {}
127 local function additems ()
128 a.x = true; a.y = true; a.z = true
129 a[1] = true
130 a[2] = true
131 end
132 for i = 1, math.huge do
133 T.alloccount(i)
134 local st, msg = pcall(additems)
135 T.alloccount()
136 local count = 0
137 for k, v in pairs(a) do
138 assert(a[k] == v)
139 count = count + 1
140 end
141 if st then assert(count == 5); break end
142 end
143end
144
145
146-- testing lexical environments
147
148assert(_ENV == _G)
149
150do
151local dummy
152local _ENV = (function (...) return ... end)(_G, dummy) -- {
153
154do local _ENV = {assert=assert}; assert(true) end
155local mt = {_G = _G}
156local foo,x
157A = false -- "declare" A
158do local _ENV = mt
159 function foo (x)
160 A = x
161 do local _ENV = _G; A = 1000 end
162 return function (x) return A .. x end
163 end
164end
165assert(getenv(foo) == mt)
166x = foo('hi'); assert(mt.A == 'hi' and A == 1000)
167assert(x('*') == mt.A .. '*')
168
169do local _ENV = {assert=assert, A=10};
170 do local _ENV = {assert=assert, A=20};
171 assert(A==20);x=A
172 end
173 assert(A==10 and x==20)
174end
175assert(x==20)
176
177A = nil
178
179
180do -- constants
181 local a<const>, b, c<const> = 10, 20, 30
182 b = a + c + b -- 'b' is not constant
183 assert(a == 10 and b == 60 and c == 30)
184 local function checkro (name, code)
185 local st, msg = load(code)
186 local gab = string.format("attempt to assign to const variable '%s'", name)
187 assert(not st and string.find(msg, gab))
188 end
189 checkro("y", "local x, y <const>, z = 10, 20, 30; x = 11; y = 12")
190 checkro("x", "local x <const>, y, z <const> = 10, 20, 30; x = 11")
191 checkro("z", "local x <const>, y, z <const> = 10, 20, 30; y = 10; z = 11")
192 checkro("foo", "local foo <const> = 10; function foo() end")
193 checkro("foo", "local foo <const> = {}; function foo() end")
194
195 checkro("z", [[
196 local a, z <const>, b = 10;
197 function foo() a = 20; z = 32; end
198 ]])
199
200 checkro("var1", [[
201 local a, var1 <const> = 10;
202 function foo() a = 20; z = function () var1 = 12; end end
203 ]])
204end
205
206
207print"testing to-be-closed variables"
208
209local function stack(n) n = ((n == 0) or stack(n - 1)) end
210
211local function func2close (f, x, y)
212 local obj = setmetatable({}, {__close = f})
213 if x then
214 return x, obj, y
215 else
216 return obj
217 end
218end
219
220
221do
222 local a = {}
223 do
224 local b <close> = false -- not to be closed
225 local x <close> = setmetatable({"x"}, {__close = function (self)
226 a[#a + 1] = self[1] end})
227 local w, y <close>, z = func2close(function (self, err)
228 assert(err == nil); a[#a + 1] = "y"
229 end, 10, 20)
230 local c <close> = nil -- not to be closed
231 a[#a + 1] = "in"
232 assert(w == 10 and z == 20)
233 end
234 a[#a + 1] = "out"
235 assert(a[1] == "in" and a[2] == "y" and a[3] == "x" and a[4] == "out")
236end
237
238do
239 local X = false
240
241 local x, closescope = func2close(function (_, msg)
242 stack(10);
243 assert(msg == nil)
244 X = true
245 end, 100)
246 assert(x == 100); x = 101; -- 'x' is not read-only
247
248 -- closing functions do not corrupt returning values
249 local function foo (x)
250 local _ <close> = closescope
251 return x, X, 23
252 end
253
254 local a, b, c = foo(1.5)
255 assert(a == 1.5 and b == false and c == 23 and X == true)
256
257 X = false
258 foo = function (x)
259 local _<close> = func2close(function (_, msg)
260 -- without errors, enclosing function should be still active when
261 -- __close is called
262 assert(debug.getinfo(2).name == "foo")
263 assert(msg == nil)
264 end)
265 local _<close> = closescope
266 local y = 15
267 return y
268 end
269
270 assert(foo() == 15 and X == true)
271
272 X = false
273 foo = function ()
274 local x <close> = closescope
275 return x
276 end
277
278 assert(foo() == closescope and X == true)
279
280end
281
282
283-- testing to-be-closed x compile-time constants
284-- (there were some bugs here in Lua 5.4-rc3, due to a confusion
285-- between compile levels and stack levels of variables)
286do
287 local flag = false
288 local x = setmetatable({},
289 {__close = function() assert(flag == false); flag = true end})
290 local y <const> = nil
291 local z <const> = nil
292 do
293 local a <close> = x
294 end
295 assert(flag) -- 'x' must be closed here
296end
297
298do
299 -- similar problem, but with implicit close in for loops
300 local flag = false
301 local x = setmetatable({},
302 {__close = function () assert(flag == false); flag = true end})
303 -- return an empty iterator, nil, nil, and 'x' to be closed
304 local function a ()
305 return (function () return nil end), nil, nil, x
306 end
307 local v <const> = 1
308 local w <const> = 1
309 local x <const> = 1
310 local y <const> = 1
311 local z <const> = 1
312 for k in a() do
313 a = k
314 end -- ending the loop must close 'x'
315 assert(flag) -- 'x' must be closed here
316end
317
318
319
320do
321 -- calls cannot be tail in the scope of to-be-closed variables
322 local X, Y
323 local function foo ()
324 local _ <close> = func2close(function () Y = 10 end)
325 assert(X == true and Y == nil) -- 'X' not closed yet
326 return 1,2,3
327 end
328
329 local function bar ()
330 local _ <close> = func2close(function () X = false end)
331 X = true
332 do
333 return foo() -- not a tail call!
334 end
335 end
336
337 local a, b, c, d = bar()
338 assert(a == 1 and b == 2 and c == 3 and X == false and Y == 10 and d == nil)
339end
340
341
342do
343 -- bug in 5.4.3: previous condition (calls cannot be tail in the
344 -- scope of to-be-closed variables) must be valid for tbc variables
345 -- created by 'for' loops.
346
347 local closed = false
348
349 local function foo ()
350 return function () return true end, 0, 0,
351 func2close(function () closed = true end)
352 end
353
354 local function tail() return closed end
355
356 local function foo1 ()
357 for k in foo() do return tail() end
358 end
359
360 assert(foo1() == false)
361 assert(closed == true)
362end
363
364
365do
366 -- bug in 5.4.4: 'break' may generate wrong 'close' instruction when
367 -- leaving a loop block.
368
369 local closed = false
370
371 local o1 = setmetatable({}, {__close=function() closed = true end})
372
373 local function test()
374 for k, v in next, {}, nil, o1 do
375 local function f() return k end -- create an upvalue
376 break
377 end
378 assert(closed)
379 end
380
381 test()
382end
383
384
385do print("testing errors in __close")
386
387 -- original error is in __close
388 local function foo ()
389
390 local x <close> =
391 func2close(function (self, msg)
392 assert(string.find(msg, "@y"))
393 error("@x")
394 end)
395
396 local x1 <close> =
397 func2close(function (self, msg)
398 assert(string.find(msg, "@y"))
399 end)
400
401 local gc <close> = func2close(function () collectgarbage() end)
402
403 local y <close> =
404 func2close(function (self, msg)
405 assert(string.find(msg, "@z")) -- error in 'z'
406 error("@y")
407 end)
408
409 local z <close> =
410 func2close(function (self, msg)
411 assert(msg == nil)
412 error("@z")
413 end)
414
415 return 200
416 end
417
418 local stat, msg = pcall(foo, false)
419 assert(string.find(msg, "@x"))
420
421
422 -- original error not in __close
423 local function foo ()
424
425 local x <close> =
426 func2close(function (self, msg)
427 -- after error, 'foo' was discarded, so caller now
428 -- must be 'pcall'
429 assert(debug.getinfo(2).name == "pcall")
430 assert(string.find(msg, "@x1"))
431 end)
432
433 local x1 <close> =
434 func2close(function (self, msg)
435 assert(debug.getinfo(2).name == "pcall")
436 assert(string.find(msg, "@y"))
437 error("@x1")
438 end)
439
440 local gc <close> = func2close(function () collectgarbage() end)
441
442 local y <close> =
443 func2close(function (self, msg)
444 assert(debug.getinfo(2).name == "pcall")
445 assert(string.find(msg, "@z"))
446 error("@y")
447 end)
448
449 local first = true
450 local z <close> =
451 func2close(function (self, msg)
452 assert(debug.getinfo(2).name == "pcall")
453 -- 'z' close is called once
454 assert(first and msg == 4)
455 first = false
456 error("@z")
457 end)
458
459 error(4) -- original error
460 end
461
462 local stat, msg = pcall(foo, true)
463 assert(string.find(msg, "@x1"))
464
465 -- error leaving a block
466 local function foo (...)
467 do
468 local x1 <close> =
469 func2close(function (self, msg)
470 assert(string.find(msg, "@X"))
471 error("@Y")
472 end)
473
474 local x123 <close> =
475 func2close(function (_, msg)
476 assert(msg == nil)
477 error("@X")
478 end)
479 end
480 os.exit(false) -- should not run
481 end
482
483 local st, msg = xpcall(foo, debug.traceback)
484 assert(string.match(msg, "^[^ ]* @Y"))
485
486 -- error in toclose in vararg function
487 local function foo (...)
488 local x123 <close> = func2close(function () error("@x123") end)
489 end
490
491 local st, msg = xpcall(foo, debug.traceback)
492 assert(string.match(msg, "^[^ ]* @x123"))
493 assert(string.find(msg, "in metamethod 'close'"))
494end
495
496
497do -- errors due to non-closable values
498 local function foo ()
499 local x <close> = {}
500 os.exit(false) -- should not run
501 end
502 local stat, msg = pcall(foo)
503 assert(not stat and
504 string.find(msg, "variable 'x' got a non%-closable value"))
505
506 local function foo ()
507 local xyz <close> = setmetatable({}, {__close = print})
508 getmetatable(xyz).__close = nil -- remove metamethod
509 end
510 local stat, msg = pcall(foo)
511 assert(not stat and string.find(msg, "metamethod 'close'"))
512
513 local function foo ()
514 local a1 <close> = func2close(function (_, msg)
515 assert(string.find(msg, "number value"))
516 error(12)
517 end)
518 local a2 <close> = setmetatable({}, {__close = print})
519 local a3 <close> = func2close(function (_, msg)
520 assert(msg == nil)
521 error(123)
522 end)
523 getmetatable(a2).__close = 4 -- invalidate metamethod
524 end
525 local stat, msg = pcall(foo)
526 assert(not stat and msg == 12)
527end
528
529
530do -- tbc inside close methods
531 local track = {}
532 local function foo ()
533 local x <close> = func2close(function ()
534 local xx <close> = func2close(function (_, msg)
535 assert(msg == nil)
536 track[#track + 1] = "xx"
537 end)
538 track[#track + 1] = "x"
539 end)
540 track[#track + 1] = "foo"
541 return 20, 30, 40
542 end
543 local a, b, c, d = foo()
544 assert(a == 20 and b == 30 and c == 40 and d == nil)
545 assert(track[1] == "foo" and track[2] == "x" and track[3] == "xx")
546
547 -- again, with errors
548 local track = {}
549 local function foo ()
550 local x0 <close> = func2close(function (_, msg)
551 assert(msg == 202)
552 track[#track + 1] = "x0"
553 end)
554 local x <close> = func2close(function ()
555 local xx <close> = func2close(function (_, msg)
556 assert(msg == 101)
557 track[#track + 1] = "xx"
558 error(202)
559 end)
560 track[#track + 1] = "x"
561 error(101)
562 end)
563 track[#track + 1] = "foo"
564 return 20, 30, 40
565 end
566 local st, msg = pcall(foo)
567 assert(not st and msg == 202)
568 assert(track[1] == "foo" and track[2] == "x" and track[3] == "xx" and
569 track[4] == "x0")
570end
571
572
573local function checktable (t1, t2)
574 assert(#t1 == #t2)
575 for i = 1, #t1 do
576 assert(t1[i] == t2[i])
577 end
578end
579
580
581do -- test for tbc variable high in the stack
582
583 -- function to force a stack overflow
584 local function overflow (n)
585 overflow(n + 1)
586 end
587
588 -- error handler will create tbc variable handling a stack overflow,
589 -- high in the stack
590 local function errorh (m)
591 assert(string.find(m, "stack overflow"))
592 local x <close> = func2close(function (o) o[1] = 10 end)
593 return x
594 end
595
596 local flag
597 local st, obj
598 -- run test in a coroutine so as not to swell the main stack
599 local co = coroutine.wrap(function ()
600 -- tbc variable down the stack
601 local y <close> = func2close(function (obj, msg)
602 assert(msg == nil)
603 obj[1] = 100
604 flag = obj
605 end)
606 tracegc.stop()
607 st, obj = xpcall(overflow, errorh, 0)
608 tracegc.start()
609 end)
610 co()
611 assert(not st and obj[1] == 10 and flag[1] == 100)
612end
613
614
615if rawget(_G, "T") then
616
617 do
618 -- bug in 5.4.3
619 -- 'lua_settop' may use a pointer to stack invalidated by 'luaF_close'
620
621 -- reduce stack size
622 collectgarbage(); collectgarbage(); collectgarbage()
623
624 -- force a stack reallocation
625 local function loop (n)
626 if n < 400 then loop(n + 1) end
627 end
628
629 -- close metamethod will reallocate the stack
630 local o = setmetatable({}, {__close = function () loop(0) end})
631
632 local script = [[toclose 2; settop 1; return 1]]
633
634 assert(T.testC(script, o) == script)
635
636 end
637
638
639 -- memory error inside closing function
640 local function foo ()
641 local y <close> = func2close(function () T.alloccount() end)
642 local x <close> = setmetatable({}, {__close = function ()
643 T.alloccount(0); local x = {} -- force a memory error
644 end})
645 error(1000) -- common error inside the function's body
646 end
647
648 stack(5) -- ensure a minimal number of CI structures
649
650 -- despite memory error, 'y' will be executed and
651 -- memory limit will be lifted
652 local _, msg = pcall(foo)
653 assert(msg == "not enough memory")
654
655 local closemsg
656 local close = func2close(function (self, msg)
657 T.alloccount()
658 closemsg = msg
659 end)
660
661 -- set a memory limit and return a closing object to remove the limit
662 local function enter (count)
663 stack(10) -- reserve some stack space
664 T.alloccount(count)
665 closemsg = nil
666 return close
667 end
668
669 local function test ()
670 local x <close> = enter(0) -- set a memory limit
671 local y = {} -- raise a memory error
672 end
673
674 local _, msg = pcall(test)
675 assert(msg == "not enough memory" and closemsg == "not enough memory")
676
677
678 -- repeat test with extra closing upvalues
679 local function test ()
680 local xxx <close> = func2close(function (self, msg)
681 assert(msg == "not enough memory");
682 error(1000) -- raise another error
683 end)
684 local xx <close> = func2close(function (self, msg)
685 assert(msg == "not enough memory");
686 end)
687 local x <close> = enter(0) -- set a memory limit
688 local y = {} -- raise a memory error
689 end
690
691 local _, msg = pcall(test)
692 assert(msg == 1000 and closemsg == "not enough memory")
693
694 do -- testing 'toclose' in C string buffer
695 collectgarbage()
696 local s = string.rep('a', 10000) -- large string
697 local m = T.totalmem()
698 collectgarbage("stop")
699 s = string.upper(s) -- allocate buffer + new string (10K each)
700 -- ensure buffer was deallocated
701 assert(T.totalmem() - m <= 11000)
702 collectgarbage("restart")
703 end
704
705 do -- now some tests for freeing buffer in case of errors
706 local lim = 10000 -- some size larger than the static buffer
707 local extra = 2000 -- some extra memory (for callinfo, etc.)
708
709 local s = string.rep("a", lim)
710
711 -- concat this table needs two buffer resizes (one for each 's')
712 local a = {s, s}
713
714 collectgarbage(); collectgarbage()
715
716 local m = T.totalmem()
717 collectgarbage("stop")
718
719 -- error in the first buffer allocation
720 T. totalmem(m + extra)
721 assert(not pcall(table.concat, a))
722 -- first buffer was not even allocated
723 assert(T.totalmem() - m <= extra)
724
725 -- error in the second buffer allocation
726 T. totalmem(m + lim + extra)
727 assert(not pcall(table.concat, a))
728 -- first buffer was released by 'toclose'
729 assert(T.totalmem() - m <= extra)
730
731 -- error in creation of final string
732 T.totalmem(m + 2 * lim + extra)
733 assert(not pcall(table.concat, a))
734 -- second buffer was released by 'toclose'
735 assert(T.totalmem() - m <= extra)
736
737 -- userdata, buffer, buffer, final string
738 T.totalmem(m + 4*lim + extra)
739 assert(#table.concat(a) == 2*lim)
740
741 T.totalmem(0) -- remove memory limit
742 collectgarbage("restart")
743
744 print'+'
745 end
746
747
748 do
749 -- '__close' vs. return hooks in C functions
750 local trace = {}
751
752 local function hook (event)
753 trace[#trace + 1] = event .. " " .. (debug.getinfo(2).name or "?")
754 end
755
756 -- create tbc variables to be used by C function
757 local x = func2close(function (_,msg)
758 trace[#trace + 1] = "x"
759 end)
760
761 local y = func2close(function (_,msg)
762 trace[#trace + 1] = "y"
763 end)
764
765 debug.sethook(hook, "r")
766 local t = {T.testC([[
767 toclose 2 # x
768 pushnum 10
769 pushint 20
770 toclose 3 # y
771 return 2
772 ]], x, y)}
773 debug.sethook()
774
775 -- hooks ran before return hook from 'testC'
776 checktable(trace,
777 {"return sethook", "y", "return ?", "x", "return ?", "return testC"})
778 -- results are correct
779 checktable(t, {10, 20})
780 end
781end
782
783
784do -- '__close' vs. return hooks in Lua functions
785 local trace = {}
786
787 local function hook (event)
788 trace[#trace + 1] = event .. " " .. debug.getinfo(2).name
789 end
790
791 local function foo (...)
792 local x <close> = func2close(function (_,msg)
793 trace[#trace + 1] = "x"
794 end)
795
796 local y <close> = func2close(function (_,msg)
797 debug.sethook(hook, "r")
798 end)
799
800 return ...
801 end
802
803 local t = {foo(10,20,30)}
804 debug.sethook()
805 checktable(t, {10, 20, 30})
806 checktable(trace,
807 {"return sethook", "return close", "x", "return close", "return foo"})
808end
809
810
811print "to-be-closed variables in coroutines"
812
813do
814 -- yielding inside closing metamethods
815
816 local trace = {}
817 local co = coroutine.wrap(function ()
818
819 trace[#trace + 1] = "nowX"
820
821 -- will be closed after 'y'
822 local x <close> = func2close(function (_, msg)
823 assert(msg == nil)
824 trace[#trace + 1] = "x1"
825 coroutine.yield("x")
826 trace[#trace + 1] = "x2"
827 end)
828
829 return pcall(function ()
830 do -- 'z' will be closed first
831 local z <close> = func2close(function (_, msg)
832 assert(msg == nil)
833 trace[#trace + 1] = "z1"
834 coroutine.yield("z")
835 trace[#trace + 1] = "z2"
836 end)
837 end
838
839 trace[#trace + 1] = "nowY"
840
841 -- will be closed after 'z'
842 local y <close> = func2close(function(_, msg)
843 assert(msg == nil)
844 trace[#trace + 1] = "y1"
845 coroutine.yield("y")
846 trace[#trace + 1] = "y2"
847 end)
848
849 return 10, 20, 30
850 end)
851 end)
852
853 assert(co() == "z")
854 assert(co() == "y")
855 assert(co() == "x")
856 checktable({co()}, {true, 10, 20, 30})
857 checktable(trace, {"nowX", "z1", "z2", "nowY", "y1", "y2", "x1", "x2"})
858
859end
860
861
862do
863 -- yielding inside closing metamethods while returning
864 -- (bug in 5.4.3)
865
866 local extrares -- result from extra yield (if any)
867
868 local function check (body, extra, ...)
869 local t = table.pack(...) -- expected returns
870 local co = coroutine.wrap(body)
871 if extra then
872 extrares = co() -- runs until first (extra) yield
873 end
874 local res = table.pack(co()) -- runs until yield inside '__close'
875 assert(res.n == 2 and res[2] == nil)
876 local res2 = table.pack(co()) -- runs until end of function
877 assert(res2.n == t.n)
878 for i = 1, #t do
879 if t[i] == "x" then
880 assert(res2[i] == res[1]) -- value that was closed
881 else
882 assert(res2[i] == t[i])
883 end
884 end
885 end
886
887 local function foo ()
888 local x <close> = func2close(coroutine.yield)
889 local extra <close> = func2close(function (self)
890 assert(self == extrares)
891 coroutine.yield(100)
892 end)
893 extrares = extra
894 return table.unpack{10, x, 30}
895 end
896 check(foo, true, 10, "x", 30)
897 assert(extrares == 100)
898
899 local function foo ()
900 local x <close> = func2close(coroutine.yield)
901 return
902 end
903 check(foo, false)
904
905 local function foo ()
906 local x <close> = func2close(coroutine.yield)
907 local y, z = 20, 30
908 return x
909 end
910 check(foo, false, "x")
911
912 local function foo ()
913 local x <close> = func2close(coroutine.yield)
914 local extra <close> = func2close(coroutine.yield)
915 return table.unpack({}, 1, 100) -- 100 nils
916 end
917 check(foo, true, table.unpack({}, 1, 100))
918
919end
920
921do
922 -- yielding inside closing metamethods after an error
923
924 local co = coroutine.wrap(function ()
925
926 local function foo (err)
927
928 local z <close> = func2close(function(_, msg)
929 assert(msg == nil or msg == err + 20)
930 coroutine.yield("z")
931 return 100, 200
932 end)
933
934 local y <close> = func2close(function(_, msg)
935 -- still gets the original error (if any)
936 assert(msg == err or (msg == nil and err == 1))
937 coroutine.yield("y")
938 if err then error(err + 20) end -- creates or changes the error
939 end)
940
941 local x <close> = func2close(function(_, msg)
942 assert(msg == err or (msg == nil and err == 1))
943 coroutine.yield("x")
944 return 100, 200
945 end)
946
947 if err == 10 then error(err) else return 10, 20 end
948 end
949
950 coroutine.yield(pcall(foo, nil)) -- no error
951 coroutine.yield(pcall(foo, 1)) -- error in __close
952 return pcall(foo, 10) -- 'foo' will raise an error
953 end)
954
955 local a, b = co() -- first foo: no error
956 assert(a == "x" and b == nil) -- yields inside 'x'; Ok
957 a, b = co()
958 assert(a == "y" and b == nil) -- yields inside 'y'; Ok
959 a, b = co()
960 assert(a == "z" and b == nil) -- yields inside 'z'; Ok
961 local a, b, c = co()
962 assert(a and b == 10 and c == 20) -- returns from 'pcall(foo, nil)'
963
964 local a, b = co() -- second foo: error in __close
965 assert(a == "x" and b == nil) -- yields inside 'x'; Ok
966 a, b = co()
967 assert(a == "y" and b == nil) -- yields inside 'y'; Ok
968 a, b = co()
969 assert(a == "z" and b == nil) -- yields inside 'z'; Ok
970 local st, msg = co() -- reports the error in 'y'
971 assert(not st and msg == 21)
972
973 local a, b = co() -- third foo: error in function body
974 assert(a == "x" and b == nil) -- yields inside 'x'; Ok
975 a, b = co()
976 assert(a == "y" and b == nil) -- yields inside 'y'; Ok
977 a, b = co()
978 assert(a == "z" and b == nil) -- yields inside 'z'; Ok
979 local st, msg = co() -- gets final error
980 assert(not st and msg == 10 + 20)
981
982end
983
984
985do
986 -- an error in a wrapped coroutine closes variables
987 local x = false
988 local y = false
989 local co = coroutine.wrap(function ()
990 local xv <close> = func2close(function () x = true end)
991 do
992 local yv <close> = func2close(function () y = true end)
993 coroutine.yield(100) -- yield doesn't close variable
994 end
995 coroutine.yield(200) -- yield doesn't close variable
996 error(23) -- error does
997 end)
998
999 local b = co()
1000 assert(b == 100 and not x and not y)
1001 b = co()
1002 assert(b == 200 and not x and y)
1003 local a, b = pcall(co)
1004 assert(not a and b == 23 and x and y)
1005end
1006
1007
1008do
1009
1010 -- error in a wrapped coroutine raising errors when closing a variable
1011 local x = 0
1012 local co = coroutine.wrap(function ()
1013 local xx <close> = func2close(function (_, msg)
1014 x = x + 1;
1015 assert(string.find(msg, "@XXX"))
1016 error("@YYY")
1017 end)
1018 local xv <close> = func2close(function () x = x + 1; error("@XXX") end)
1019 coroutine.yield(100)
1020 error(200)
1021 end)
1022 assert(co() == 100); assert(x == 0)
1023 local st, msg = pcall(co); assert(x == 2)
1024 assert(not st and string.find(msg, "@YYY")) -- should get error raised
1025
1026 local x = 0
1027 local y = 0
1028 co = coroutine.wrap(function ()
1029 local xx <close> = func2close(function (_, err)
1030 y = y + 1;
1031 assert(string.find(err, "XXX"))
1032 error("YYY")
1033 end)
1034 local xv <close> = func2close(function ()
1035 x = x + 1; error("XXX")
1036 end)
1037 coroutine.yield(100)
1038 return 200
1039 end)
1040 assert(co() == 100); assert(x == 0)
1041 local st, msg = pcall(co)
1042 assert(x == 1 and y == 1)
1043 -- should get first error raised
1044 assert(not st and string.find(msg, "%w+%.%w+:%d+: YYY"))
1045
1046end
1047
1048
1049-- a suspended coroutine should not close its variables when collected
1050local co
1051co = coroutine.wrap(function()
1052 -- should not run
1053 local x <close> = func2close(function () os.exit(false) end)
1054 co = nil
1055 coroutine.yield()
1056end)
1057co() -- start coroutine
1058assert(co == nil) -- eventually it will be collected
1059collectgarbage()
1060
1061
1062if rawget(_G, "T") then
1063 print("to-be-closed variables x coroutines in C")
1064 do
1065 local token = 0
1066 local count = 0
1067 local f = T.makeCfunc[[
1068 toclose 1
1069 toclose 2
1070 return .
1071 ]]
1072
1073 local obj = func2close(function (_, msg)
1074 count = count + 1
1075 token = coroutine.yield(count, token)
1076 end)
1077
1078 local co = coroutine.wrap(f)
1079 local ct, res = co(obj, obj, 10, 20, 30, 3) -- will return 10, 20, 30
1080 -- initial token value, after closing 2nd obj
1081 assert(ct == 1 and res == 0)
1082 -- run until yield when closing 1st obj
1083 ct, res = co(100)
1084 assert(ct == 2 and res == 100)
1085 res = {co(200)} -- run until end
1086 assert(res[1] == 10 and res[2] == 20 and res[3] == 30 and res[4] == nil)
1087 assert(token == 200)
1088 end
1089
1090 do
1091 local f = T.makeCfunc[[
1092 toclose 1
1093 return .
1094 ]]
1095
1096 local obj = func2close(function ()
1097 local temp
1098 local x <close> = func2close(function ()
1099 coroutine.yield(temp)
1100 return 1,2,3 -- to be ignored
1101 end)
1102 temp = coroutine.yield("closing obj")
1103 return 1,2,3 -- to be ignored
1104 end)
1105
1106 local co = coroutine.wrap(f)
1107 local res = co(obj, 10, 30, 1) -- will return only 30
1108 assert(res == "closing obj")
1109 res = co("closing x")
1110 assert(res == "closing x")
1111 res = {co()}
1112 assert(res[1] == 30 and res[2] == nil)
1113 end
1114
1115 do
1116 -- still cannot yield inside 'closeslot'
1117 local f = T.makeCfunc[[
1118 toclose 1
1119 closeslot 1
1120 ]]
1121 local obj = func2close(coroutine.yield)
1122 local co = coroutine.create(f)
1123 local st, msg = coroutine.resume(co, obj)
1124 assert(not st and string.find(msg, "attempt to yield across"))
1125
1126 -- nor outside a coroutine
1127 local f = T.makeCfunc[[
1128 toclose 1
1129 ]]
1130 local st, msg = pcall(f, obj)
1131 assert(not st and string.find(msg, "attempt to yield from outside"))
1132 end
1133end
1134
1135
1136
1137-- to-be-closed variables in generic for loops
1138do
1139 local numopen = 0
1140 local function open (x)
1141 numopen = numopen + 1
1142 return
1143 function () -- iteraction function
1144 x = x - 1
1145 if x > 0 then return x end
1146 end,
1147 nil, -- state
1148 nil, -- control variable
1149 func2close(function () numopen = numopen - 1 end) -- closing function
1150 end
1151
1152 local s = 0
1153 for i in open(10) do
1154 s = s + i
1155 end
1156 assert(s == 45 and numopen == 0)
1157
1158 local s = 0
1159 for i in open(10) do
1160 if i < 5 then break end
1161 s = s + i
1162 end
1163 assert(s == 35 and numopen == 0)
1164
1165 local s = 0
1166 for i in open(10) do
1167 for j in open(10) do
1168 if i + j < 5 then goto endloop end
1169 s = s + i
1170 end
1171 end
1172 ::endloop::
1173 assert(s == 375 and numopen == 0)
1174end
1175
1176print('OK')
1177
1178return 5,f
1179
1180end -- }
1181