diff options
| author | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2024-03-11 14:05:06 -0300 |
|---|---|---|
| committer | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2024-03-11 14:05:06 -0300 |
| commit | 65b07dd53d7938a60112fc4473f5cad3473e3534 (patch) | |
| tree | 469c75dba3b194c494b6ad6a30ca44e7e8354ef5 | |
| parent | 7237eb3f1c480d6bc7fe2832ddd36f2137fb69d9 (diff) | |
| download | lua-65b07dd53d7938a60112fc4473f5cad3473e3534.tar.gz lua-65b07dd53d7938a60112fc4473f5cad3473e3534.tar.bz2 lua-65b07dd53d7938a60112fc4473f5cad3473e3534.zip | |
API asserts for illegal pops of to-be-closed variables
| -rw-r--r-- | lapi.c | 52 | ||||
| -rw-r--r-- | lapi.h | 14 | ||||
| -rw-r--r-- | ldebug.c | 1 | ||||
| -rw-r--r-- | ldo.c | 5 | ||||
| -rw-r--r-- | testes/api.lua | 3 |
5 files changed, 45 insertions, 30 deletions
| @@ -139,7 +139,7 @@ LUA_API void lua_xmove (lua_State *from, lua_State *to, int n) { | |||
| 139 | int i; | 139 | int i; |
| 140 | if (from == to) return; | 140 | if (from == to) return; |
| 141 | lua_lock(to); | 141 | lua_lock(to); |
| 142 | api_checknelems(from, n); | 142 | api_checkpop(from, n); |
| 143 | api_check(from, G(from) == G(to), "moving among independent states"); | 143 | api_check(from, G(from) == G(to), "moving among independent states"); |
| 144 | api_check(from, to->ci->top.p - to->top.p >= n, "stack overflow"); | 144 | api_check(from, to->ci->top.p - to->top.p >= n, "stack overflow"); |
| 145 | from->top.p -= n; | 145 | from->top.p -= n; |
| @@ -205,7 +205,6 @@ LUA_API void lua_settop (lua_State *L, int idx) { | |||
| 205 | api_check(L, -(idx+1) <= (L->top.p - (func + 1)), "invalid new top"); | 205 | api_check(L, -(idx+1) <= (L->top.p - (func + 1)), "invalid new top"); |
| 206 | diff = idx + 1; /* will "subtract" index (as it is negative) */ | 206 | diff = idx + 1; /* will "subtract" index (as it is negative) */ |
| 207 | } | 207 | } |
| 208 | api_check(L, L->tbclist.p < L->top.p, "previous pop of an unclosed slot"); | ||
| 209 | newtop = L->top.p + diff; | 208 | newtop = L->top.p + diff; |
| 210 | if (diff < 0 && L->tbclist.p >= newtop) { | 209 | if (diff < 0 && L->tbclist.p >= newtop) { |
| 211 | lua_assert(hastocloseCfunc(ci->nresults)); | 210 | lua_assert(hastocloseCfunc(ci->nresults)); |
| @@ -253,6 +252,7 @@ LUA_API void lua_rotate (lua_State *L, int idx, int n) { | |||
| 253 | lua_lock(L); | 252 | lua_lock(L); |
| 254 | t = L->top.p - 1; /* end of stack segment being rotated */ | 253 | t = L->top.p - 1; /* end of stack segment being rotated */ |
| 255 | p = index2stack(L, idx); /* start of segment */ | 254 | p = index2stack(L, idx); /* start of segment */ |
| 255 | api_check(L, L->tbclist.p < p, "moving a to-be-closed slot"); | ||
| 256 | api_check(L, (n >= 0 ? n : -n) <= (t - p + 1), "invalid 'n'"); | 256 | api_check(L, (n >= 0 ? n : -n) <= (t - p + 1), "invalid 'n'"); |
| 257 | m = (n >= 0 ? t - n : p - n - 1); /* end of prefix */ | 257 | m = (n >= 0 ? t - n : p - n - 1); /* end of prefix */ |
| 258 | reverse(L, p, m); /* reverse the prefix with length 'n' */ | 258 | reverse(L, p, m); /* reverse the prefix with length 'n' */ |
| @@ -345,9 +345,9 @@ LUA_API int lua_rawequal (lua_State *L, int index1, int index2) { | |||
| 345 | LUA_API void lua_arith (lua_State *L, int op) { | 345 | LUA_API void lua_arith (lua_State *L, int op) { |
| 346 | lua_lock(L); | 346 | lua_lock(L); |
| 347 | if (op != LUA_OPUNM && op != LUA_OPBNOT) | 347 | if (op != LUA_OPUNM && op != LUA_OPBNOT) |
| 348 | api_checknelems(L, 2); /* all other operations expect two operands */ | 348 | api_checkpop(L, 2); /* all other operations expect two operands */ |
| 349 | else { /* for unary operations, add fake 2nd operand */ | 349 | else { /* for unary operations, add fake 2nd operand */ |
| 350 | api_checknelems(L, 1); | 350 | api_checkpop(L, 1); |
| 351 | setobjs2s(L, L->top.p, L->top.p - 1); | 351 | setobjs2s(L, L->top.p, L->top.p - 1); |
| 352 | api_incr_top(L); | 352 | api_incr_top(L); |
| 353 | } | 353 | } |
| @@ -611,17 +611,18 @@ LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) { | |||
| 611 | api_incr_top(L); | 611 | api_incr_top(L); |
| 612 | } | 612 | } |
| 613 | else { | 613 | else { |
| 614 | int i; | ||
| 614 | CClosure *cl; | 615 | CClosure *cl; |
| 615 | api_checknelems(L, n); | 616 | api_checkpop(L, n); |
| 616 | api_check(L, n <= MAXUPVAL, "upvalue index too large"); | 617 | api_check(L, n <= MAXUPVAL, "upvalue index too large"); |
| 617 | cl = luaF_newCclosure(L, n); | 618 | cl = luaF_newCclosure(L, n); |
| 618 | cl->f = fn; | 619 | cl->f = fn; |
| 619 | L->top.p -= n; | 620 | for (i = 0; i < n; i++) { |
| 620 | while (n--) { | 621 | setobj2n(L, &cl->upvalue[i], s2v(L->top.p - n + i)); |
| 621 | setobj2n(L, &cl->upvalue[n], s2v(L->top.p + n)); | ||
| 622 | /* does not need barrier because closure is white */ | 622 | /* does not need barrier because closure is white */ |
| 623 | lua_assert(iswhite(cl)); | 623 | lua_assert(iswhite(cl)); |
| 624 | } | 624 | } |
| 625 | L->top.p -= n; | ||
| 625 | setclCvalue(L, s2v(L->top.p), cl); | 626 | setclCvalue(L, s2v(L->top.p), cl); |
| 626 | api_incr_top(L); | 627 | api_incr_top(L); |
| 627 | luaC_checkGC(L); | 628 | luaC_checkGC(L); |
| @@ -701,6 +702,7 @@ LUA_API int lua_gettable (lua_State *L, int idx) { | |||
| 701 | int hres; | 702 | int hres; |
| 702 | TValue *t; | 703 | TValue *t; |
| 703 | lua_lock(L); | 704 | lua_lock(L); |
| 705 | api_checkpop(L, 1); | ||
| 704 | t = index2value(L, idx); | 706 | t = index2value(L, idx); |
| 705 | luaV_fastget(t, s2v(L->top.p - 1), s2v(L->top.p - 1), luaH_get, hres); | 707 | luaV_fastget(t, s2v(L->top.p - 1), s2v(L->top.p - 1), luaH_get, hres); |
| 706 | if (hres != HOK) | 708 | if (hres != HOK) |
| @@ -751,13 +753,13 @@ l_sinline Table *gettable (lua_State *L, int idx) { | |||
| 751 | 753 | ||
| 752 | LUA_API int lua_rawget (lua_State *L, int idx) { | 754 | LUA_API int lua_rawget (lua_State *L, int idx) { |
| 753 | Table *t; | 755 | Table *t; |
| 754 | int hres; | ||
| 755 | lua_lock(L); | 756 | lua_lock(L); |
| 756 | api_checknelems(L, 1); | 757 | api_checkpop(L, 1); |
| 757 | t = gettable(L, idx); | 758 | t = gettable(L, idx); |
| 758 | hres = luaH_get(t, s2v(L->top.p - 1), s2v(L->top.p - 1)); | 759 | if (luaH_get(t, s2v(L->top.p - 1), s2v(L->top.p - 1)) != HOK) |
| 759 | L->top.p--; /* remove key */ | 760 | setnilvalue(s2v(L->top.p - 1)); |
| 760 | return finishrawget(L, hres); | 761 | lua_unlock(L); |
| 762 | return ttype(s2v(L->top.p - 1)); | ||
| 761 | } | 763 | } |
| 762 | 764 | ||
| 763 | 765 | ||
| @@ -851,7 +853,7 @@ LUA_API int lua_getiuservalue (lua_State *L, int idx, int n) { | |||
| 851 | static void auxsetstr (lua_State *L, const TValue *t, const char *k) { | 853 | static void auxsetstr (lua_State *L, const TValue *t, const char *k) { |
| 852 | int hres; | 854 | int hres; |
| 853 | TString *str = luaS_new(L, k); | 855 | TString *str = luaS_new(L, k); |
| 854 | api_checknelems(L, 1); | 856 | api_checkpop(L, 1); |
| 855 | luaV_fastset(t, str, s2v(L->top.p - 1), hres, luaH_psetstr); | 857 | luaV_fastset(t, str, s2v(L->top.p - 1), hres, luaH_psetstr); |
| 856 | if (hres == HOK) { | 858 | if (hres == HOK) { |
| 857 | luaV_finishfastset(L, t, s2v(L->top.p - 1)); | 859 | luaV_finishfastset(L, t, s2v(L->top.p - 1)); |
| @@ -879,7 +881,7 @@ LUA_API void lua_settable (lua_State *L, int idx) { | |||
| 879 | TValue *t; | 881 | TValue *t; |
| 880 | int hres; | 882 | int hres; |
| 881 | lua_lock(L); | 883 | lua_lock(L); |
| 882 | api_checknelems(L, 2); | 884 | api_checkpop(L, 2); |
| 883 | t = index2value(L, idx); | 885 | t = index2value(L, idx); |
| 884 | luaV_fastset(t, s2v(L->top.p - 2), s2v(L->top.p - 1), hres, luaH_pset); | 886 | luaV_fastset(t, s2v(L->top.p - 2), s2v(L->top.p - 1), hres, luaH_pset); |
| 885 | if (hres == HOK) { | 887 | if (hres == HOK) { |
| @@ -902,7 +904,7 @@ LUA_API void lua_seti (lua_State *L, int idx, lua_Integer n) { | |||
| 902 | TValue *t; | 904 | TValue *t; |
| 903 | int hres; | 905 | int hres; |
| 904 | lua_lock(L); | 906 | lua_lock(L); |
| 905 | api_checknelems(L, 1); | 907 | api_checkpop(L, 1); |
| 906 | t = index2value(L, idx); | 908 | t = index2value(L, idx); |
| 907 | luaV_fastseti(t, n, s2v(L->top.p - 1), hres); | 909 | luaV_fastseti(t, n, s2v(L->top.p - 1), hres); |
| 908 | if (hres == HOK) | 910 | if (hres == HOK) |
| @@ -920,7 +922,7 @@ LUA_API void lua_seti (lua_State *L, int idx, lua_Integer n) { | |||
| 920 | static void aux_rawset (lua_State *L, int idx, TValue *key, int n) { | 922 | static void aux_rawset (lua_State *L, int idx, TValue *key, int n) { |
| 921 | Table *t; | 923 | Table *t; |
| 922 | lua_lock(L); | 924 | lua_lock(L); |
| 923 | api_checknelems(L, n); | 925 | api_checkpop(L, n); |
| 924 | t = gettable(L, idx); | 926 | t = gettable(L, idx); |
| 925 | luaH_set(L, t, key, s2v(L->top.p - 1)); | 927 | luaH_set(L, t, key, s2v(L->top.p - 1)); |
| 926 | invalidateTMcache(t); | 928 | invalidateTMcache(t); |
| @@ -945,7 +947,7 @@ LUA_API void lua_rawsetp (lua_State *L, int idx, const void *p) { | |||
| 945 | LUA_API void lua_rawseti (lua_State *L, int idx, lua_Integer n) { | 947 | LUA_API void lua_rawseti (lua_State *L, int idx, lua_Integer n) { |
| 946 | Table *t; | 948 | Table *t; |
| 947 | lua_lock(L); | 949 | lua_lock(L); |
| 948 | api_checknelems(L, 1); | 950 | api_checkpop(L, 1); |
| 949 | t = gettable(L, idx); | 951 | t = gettable(L, idx); |
| 950 | luaH_setint(L, t, n, s2v(L->top.p - 1)); | 952 | luaH_setint(L, t, n, s2v(L->top.p - 1)); |
| 951 | luaC_barrierback(L, obj2gco(t), s2v(L->top.p - 1)); | 953 | luaC_barrierback(L, obj2gco(t), s2v(L->top.p - 1)); |
| @@ -958,7 +960,7 @@ LUA_API int lua_setmetatable (lua_State *L, int objindex) { | |||
| 958 | TValue *obj; | 960 | TValue *obj; |
| 959 | Table *mt; | 961 | Table *mt; |
| 960 | lua_lock(L); | 962 | lua_lock(L); |
| 961 | api_checknelems(L, 1); | 963 | api_checkpop(L, 1); |
| 962 | obj = index2value(L, objindex); | 964 | obj = index2value(L, objindex); |
| 963 | if (ttisnil(s2v(L->top.p - 1))) | 965 | if (ttisnil(s2v(L->top.p - 1))) |
| 964 | mt = NULL; | 966 | mt = NULL; |
| @@ -998,7 +1000,7 @@ LUA_API int lua_setiuservalue (lua_State *L, int idx, int n) { | |||
| 998 | TValue *o; | 1000 | TValue *o; |
| 999 | int res; | 1001 | int res; |
| 1000 | lua_lock(L); | 1002 | lua_lock(L); |
| 1001 | api_checknelems(L, 1); | 1003 | api_checkpop(L, 1); |
| 1002 | o = index2value(L, idx); | 1004 | o = index2value(L, idx); |
| 1003 | api_check(L, ttisfulluserdata(o), "full userdata expected"); | 1005 | api_check(L, ttisfulluserdata(o), "full userdata expected"); |
| 1004 | if (!(cast_uint(n) - 1u < cast_uint(uvalue(o)->nuvalue))) | 1006 | if (!(cast_uint(n) - 1u < cast_uint(uvalue(o)->nuvalue))) |
| @@ -1031,7 +1033,7 @@ LUA_API void lua_callk (lua_State *L, int nargs, int nresults, | |||
| 1031 | lua_lock(L); | 1033 | lua_lock(L); |
| 1032 | api_check(L, k == NULL || !isLua(L->ci), | 1034 | api_check(L, k == NULL || !isLua(L->ci), |
| 1033 | "cannot use continuations inside hooks"); | 1035 | "cannot use continuations inside hooks"); |
| 1034 | api_checknelems(L, nargs+1); | 1036 | api_checkpop(L, nargs + 1); |
| 1035 | api_check(L, L->status == LUA_OK, "cannot do calls on non-normal thread"); | 1037 | api_check(L, L->status == LUA_OK, "cannot do calls on non-normal thread"); |
| 1036 | checkresults(L, nargs, nresults); | 1038 | checkresults(L, nargs, nresults); |
| 1037 | func = L->top.p - (nargs+1); | 1039 | func = L->top.p - (nargs+1); |
| @@ -1072,7 +1074,7 @@ LUA_API int lua_pcallk (lua_State *L, int nargs, int nresults, int errfunc, | |||
| 1072 | lua_lock(L); | 1074 | lua_lock(L); |
| 1073 | api_check(L, k == NULL || !isLua(L->ci), | 1075 | api_check(L, k == NULL || !isLua(L->ci), |
| 1074 | "cannot use continuations inside hooks"); | 1076 | "cannot use continuations inside hooks"); |
| 1075 | api_checknelems(L, nargs+1); | 1077 | api_checkpop(L, nargs + 1); |
| 1076 | api_check(L, L->status == LUA_OK, "cannot do calls on non-normal thread"); | 1078 | api_check(L, L->status == LUA_OK, "cannot do calls on non-normal thread"); |
| 1077 | checkresults(L, nargs, nresults); | 1079 | checkresults(L, nargs, nresults); |
| 1078 | if (errfunc == 0) | 1080 | if (errfunc == 0) |
| @@ -1141,7 +1143,7 @@ LUA_API int lua_dump (lua_State *L, lua_Writer writer, void *data, int strip) { | |||
| 1141 | ptrdiff_t otop = savestack(L, L->top.p); /* original top */ | 1143 | ptrdiff_t otop = savestack(L, L->top.p); /* original top */ |
| 1142 | TValue *f = s2v(L->top.p - 1); /* function to be dumped */ | 1144 | TValue *f = s2v(L->top.p - 1); /* function to be dumped */ |
| 1143 | lua_lock(L); | 1145 | lua_lock(L); |
| 1144 | api_checknelems(L, 1); | 1146 | api_checkpop(L, 1); |
| 1145 | api_check(L, isLfunction(f), "Lua function expected"); | 1147 | api_check(L, isLfunction(f), "Lua function expected"); |
| 1146 | status = luaU_dump(L, clLvalue(f)->p, writer, data, strip); | 1148 | status = luaU_dump(L, clLvalue(f)->p, writer, data, strip); |
| 1147 | L->top.p = restorestack(L, otop); /* restore top */ | 1149 | L->top.p = restorestack(L, otop); /* restore top */ |
| @@ -1244,7 +1246,7 @@ LUA_API int lua_error (lua_State *L) { | |||
| 1244 | TValue *errobj; | 1246 | TValue *errobj; |
| 1245 | lua_lock(L); | 1247 | lua_lock(L); |
| 1246 | errobj = s2v(L->top.p - 1); | 1248 | errobj = s2v(L->top.p - 1); |
| 1247 | api_checknelems(L, 1); | 1249 | api_checkpop(L, 1); |
| 1248 | /* error object is the memory error message? */ | 1250 | /* error object is the memory error message? */ |
| 1249 | if (ttisshrstring(errobj) && eqshrstr(tsvalue(errobj), G(L)->memerrmsg)) | 1251 | if (ttisshrstring(errobj) && eqshrstr(tsvalue(errobj), G(L)->memerrmsg)) |
| 1250 | luaM_error(L); /* raise a memory error */ | 1252 | luaM_error(L); /* raise a memory error */ |
| @@ -1259,7 +1261,7 @@ LUA_API int lua_next (lua_State *L, int idx) { | |||
| 1259 | Table *t; | 1261 | Table *t; |
| 1260 | int more; | 1262 | int more; |
| 1261 | lua_lock(L); | 1263 | lua_lock(L); |
| 1262 | api_checknelems(L, 1); | 1264 | api_checkpop(L, 1); |
| 1263 | t = gettable(L, idx); | 1265 | t = gettable(L, idx); |
| 1264 | more = luaH_next(L, t, L->top.p - 1); | 1266 | more = luaH_next(L, t, L->top.p - 1); |
| 1265 | if (more) | 1267 | if (more) |
| @@ -29,8 +29,18 @@ | |||
| 29 | 29 | ||
| 30 | /* Ensure the stack has at least 'n' elements */ | 30 | /* Ensure the stack has at least 'n' elements */ |
| 31 | #define api_checknelems(L,n) \ | 31 | #define api_checknelems(L,n) \ |
| 32 | api_check(L, (n) < (L->top.p - L->ci->func.p), \ | 32 | api_check(L, (n) < (L->top.p - L->ci->func.p), \ |
| 33 | "not enough elements in the stack") | 33 | "not enough elements in the stack") |
| 34 | |||
| 35 | |||
| 36 | /* Ensure the stack has at least 'n' elements to be popped. (Some | ||
| 37 | ** functions only update a slot after checking it for popping, but that | ||
| 38 | ** is only an optimization for a pop followed by a push.) | ||
| 39 | */ | ||
| 40 | #define api_checkpop(L,n) \ | ||
| 41 | api_check(L, (n) < L->top.p - L->ci->func.p && \ | ||
| 42 | L->tbclist.p < L->top.p - (n), \ | ||
| 43 | "not enough free elements in the stack") | ||
| 34 | 44 | ||
| 35 | 45 | ||
| 36 | /* | 46 | /* |
| @@ -245,6 +245,7 @@ LUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n) { | |||
| 245 | lua_lock(L); | 245 | lua_lock(L); |
| 246 | name = luaG_findlocal(L, ar->i_ci, n, &pos); | 246 | name = luaG_findlocal(L, ar->i_ci, n, &pos); |
| 247 | if (name) { | 247 | if (name) { |
| 248 | api_checkpop(L, 1); | ||
| 248 | setobjs2s(L, pos, L->top.p - 1); | 249 | setobjs2s(L, pos, L->top.p - 1); |
| 249 | L->top.p--; /* pop value */ | 250 | L->top.p--; /* pop value */ |
| 250 | } | 251 | } |
| @@ -767,6 +767,7 @@ static CallInfo *findpcall (lua_State *L) { | |||
| 767 | ** coroutine error handler and should not kill the coroutine.) | 767 | ** coroutine error handler and should not kill the coroutine.) |
| 768 | */ | 768 | */ |
| 769 | static int resume_error (lua_State *L, const char *msg, int narg) { | 769 | static int resume_error (lua_State *L, const char *msg, int narg) { |
| 770 | api_checkpop(L, narg); | ||
| 770 | L->top.p -= narg; /* remove args from the stack */ | 771 | L->top.p -= narg; /* remove args from the stack */ |
| 771 | setsvalue2s(L, L->top.p, luaS_new(L, msg)); /* push error message */ | 772 | setsvalue2s(L, L->top.p, luaS_new(L, msg)); /* push error message */ |
| 772 | api_incr_top(L); | 773 | api_incr_top(L); |
| @@ -849,7 +850,7 @@ LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs, | |||
| 849 | return resume_error(L, "C stack overflow", nargs); | 850 | return resume_error(L, "C stack overflow", nargs); |
| 850 | L->nCcalls++; | 851 | L->nCcalls++; |
| 851 | luai_userstateresume(L, nargs); | 852 | luai_userstateresume(L, nargs); |
| 852 | api_checknelems(L, (L->status == LUA_OK) ? nargs + 1 : nargs); | 853 | api_checkpop(L, (L->status == LUA_OK) ? nargs + 1 : nargs); |
| 853 | status = luaD_rawrunprotected(L, resume, &nargs); | 854 | status = luaD_rawrunprotected(L, resume, &nargs); |
| 854 | /* continue running after recoverable errors */ | 855 | /* continue running after recoverable errors */ |
| 855 | status = precover(L, status); | 856 | status = precover(L, status); |
| @@ -878,7 +879,7 @@ LUA_API int lua_yieldk (lua_State *L, int nresults, lua_KContext ctx, | |||
| 878 | luai_userstateyield(L, nresults); | 879 | luai_userstateyield(L, nresults); |
| 879 | lua_lock(L); | 880 | lua_lock(L); |
| 880 | ci = L->ci; | 881 | ci = L->ci; |
| 881 | api_checknelems(L, nresults); | 882 | api_checkpop(L, nresults); |
| 882 | if (l_unlikely(!yieldable(L))) { | 883 | if (l_unlikely(!yieldable(L))) { |
| 883 | if (L != G(L)->mainthread) | 884 | if (L != G(L)->mainthread) |
| 884 | luaG_runerror(L, "attempt to yield across a C-call boundary"); | 885 | luaG_runerror(L, "attempt to yield across a C-call boundary"); |
diff --git a/testes/api.lua b/testes/api.lua index eec9c0ab..dc485240 100644 --- a/testes/api.lua +++ b/testes/api.lua | |||
| @@ -1193,7 +1193,8 @@ do | |||
| 1193 | local a, b = pcall(T.makeCfunc[[ | 1193 | local a, b = pcall(T.makeCfunc[[ |
| 1194 | call 0 1 # create resource | 1194 | call 0 1 # create resource |
| 1195 | toclose -1 # mark it to be closed | 1195 | toclose -1 # mark it to be closed |
| 1196 | error # resource is the error object | 1196 | pushvalue -1 # replicate it as error object |
| 1197 | error # resource right after error object | ||
| 1197 | ]], newresource) | 1198 | ]], newresource) |
| 1198 | assert(a == false and b[1] == 11) | 1199 | assert(a == false and b[1] == 11) |
| 1199 | assert(#openresource == 0) -- was closed | 1200 | assert(#openresource == 0) -- was closed |
