diff --git a/lapi.c b/lapi.c index bbba88a3..16eb170d 100644 --- a/lapi.c +++ b/lapi.c @@ -1195,9 +1195,15 @@ LUA_API int lua_gc (lua_State *L, int what, ...) { LUA_API int lua_error (lua_State *L) { + TValue *errobj; lua_lock(L); + errobj = s2v(L->top - 1); api_checknelems(L, 1); - luaG_errormsg(L); + /* error object is the memory error message? */ + if (ttisshrstring(errobj) && eqshrstr(tsvalue(errobj), G(L)->memerrmsg)) + luaM_error(L); /* raise a memory error */ + else + luaG_errormsg(L); /* raise a regular error */ /* code unreachable; will unlock when control actually leaves the kernel */ return 0; /* to avoid warnings */ } diff --git a/lauxlib.c b/lauxlib.c index e3d9be37..cbe9ed31 100644 --- a/lauxlib.c +++ b/lauxlib.c @@ -475,8 +475,10 @@ static void *resizebox (lua_State *L, int idx, size_t newsize) { lua_Alloc allocf = lua_getallocf(L, &ud); UBox *box = (UBox *)lua_touserdata(L, idx); void *temp = allocf(ud, box->box, box->bsize, newsize); - if (temp == NULL && newsize > 0) /* allocation error? */ - luaL_error(L, "not enough memory"); + if (temp == NULL && newsize > 0) { /* allocation error? */ + lua_pushliteral(L, "not enough memory"); + lua_error(L); /* raise a memory error */ + } box->box = temp; box->bsize = newsize; return temp; diff --git a/lcorolib.c b/lcorolib.c index 7d6e585b..c165031d 100644 --- a/lcorolib.c +++ b/lcorolib.c @@ -73,11 +73,12 @@ static int luaB_coresume (lua_State *L) { static int luaB_auxwrap (lua_State *L) { lua_State *co = lua_tothread(L, lua_upvalueindex(1)); int r = auxresume(L, co, lua_gettop(L)); - if (r < 0) { + if (r < 0) { /* error? */ int stat = lua_status(co); - if (stat != LUA_OK && stat != LUA_YIELD) - lua_resetthread(co); /* close variables in case of errors */ - if (lua_type(L, -1) == LUA_TSTRING) { /* error object is a string? */ + if (stat != LUA_OK && stat != LUA_YIELD) /* error in the coroutine? */ + lua_resetthread(co); /* close its tbc variables */ + if (stat != LUA_ERRMEM && /* not a memory error and ... */ + lua_type(L, -1) == LUA_TSTRING) { /* ... error object is a string? */ luaL_where(L, 1); /* add extra info, if available */ lua_insert(L, -2); lua_concat(L, 2); diff --git a/testes/api.lua b/testes/api.lua index 9447e42a..95551481 100644 --- a/testes/api.lua +++ b/testes/api.lua @@ -11,6 +11,9 @@ local debug = require "debug" local pack = table.pack +-- standard error message for memory errors +local MEMERRMSG = "not enough memory" + function tcheck (t1, t2) assert(t1.n == (t2.n or #t2) + 1) for i = 2, t1.n do assert(t1[i] == t2[i - 1]) end @@ -408,7 +411,7 @@ do -- memory error T.totalmem(T.totalmem()+10000) -- set low memory limit (+10k) - assert(T.checkpanic("newuserdata 20000") == "not enough memory") + assert(T.checkpanic("newuserdata 20000") == MEMERRMSG) T.totalmem(0) -- restore high limit -- stack error @@ -1153,40 +1156,74 @@ do end -------------------------------------------------------------------------- --- testing memory limits -------------------------------------------------------------------------- +--[[ +** {================================================================== +** Testing memory limits +** =================================================================== +--]] + print("memory-allocation errors") checkerr("block too big", T.newuserdata, math.maxinteger) collectgarbage() local f = load"local a={}; for i=1,100000 do a[i]=i end" T.alloccount(10) -checkerr("not enough memory", f) +checkerr(MEMERRMSG, f) T.alloccount() -- remove limit + +-- test memory errors; increase limit for maximum memory by steps, +-- o that we get memory errors in all allocations of a given +-- task, until there is enough memory to complete the task without +-- errors. +function testbytes (s, f) + collectgarbage() + local M = T.totalmem() + local oldM = M + local a,b = nil + while true do + collectgarbage(); collectgarbage() + T.totalmem(M) + a, b = T.testC("pcall 0 1 0; pushstatus; return 2", f) + T.totalmem(0) -- remove limit + if a and b == "OK" then break end -- stop when no more errors + if b ~= "OK" and b ~= MEMERRMSG then -- not a memory error? + error(a, 0) -- propagate it + end + M = M + 7 -- increase memory limit + end + print(string.format("minimum memory for %s: %d bytes", s, M - oldM)) + return a +end + -- test memory errors; increase limit for number of allocations one -- by one, so that we get memory errors in all allocations of a given -- task, until there is enough allocations to complete the task without -- errors. -function testamem (s, f) - collectgarbage(); collectgarbage() +function testalloc (s, f) + collectgarbage() local M = 0 local a,b = nil while true do + collectgarbage(); collectgarbage() T.alloccount(M) - a, b = pcall(f) + a, b = T.testC("pcall 0 1 0; pushstatus; return 2", f) T.alloccount() -- remove limit - if a and b then break end -- stop when no more errors - if not a and not -- `real' error? - (string.find(b, "memory") or string.find(b, "overflow")) then - error(b, 0) -- propagate it + if a and b == "OK" then break end -- stop when no more errors + if b ~= "OK" and b ~= MEMERRMSG then -- not a memory error? + error(a, 0) -- propagate it end M = M + 1 -- increase allocation limit end - print(string.format("limit for %s: %d allocations", s, M)) - return b + print(string.format("minimum allocations for %s: %d allocations", s, M)) + return a +end + + +local function testamem (s, f) + testalloc(s, f) + return testbytes(s, f) end @@ -1196,8 +1233,11 @@ assert(b == 10) -- testing memory errors when creating a new state -b = testamem("state creation", T.newstate) -T.closestate(b); -- close new state +testamem("state creation", function () + local st = T.newstate() + if st then T.closestate(st) end -- close new state + return st +end) testamem("empty-table creation", function () return {} @@ -1345,6 +1385,9 @@ testamem("growing stack", function () return foo(100) end) +-- }================================================================== + + do -- testing failing in 'lua_checkstack' local res = T.testC([[rawcheckstack 500000; return 1]]) assert(res == false)