From e663a24ab03a54fa221c20a793812e5c5ffdf94f Mon Sep 17 00:00:00 2001
From: Roberto Ierusalimschy <roberto@inf.puc-rio.br>
Date: Fri, 8 Dec 2017 15:28:25 -0200
Subject: [PATCH] more freedom in handling memory-allocation errors (not all
 allocations automatically raise an error), which allows fixing a bug when
 resizing a table.

---
 lapi.c    |  14 ++------
 ldo.c     |  42 +++++++++++++----------
 ldo.h     |   9 ++---
 lgc.c     |  11 ++----
 lmem.c    |  30 +++++++++++-----
 lmem.h    |  23 ++++++++-----
 lstring.c |   7 ++--
 ltable.c  | 101 +++++++++++++++++++++++++++++++++++-------------------
 8 files changed, 139 insertions(+), 98 deletions(-)

diff --git a/lapi.c b/lapi.c
index 3098cb9c..d6aaf8a8 100644
--- a/lapi.c
+++ b/lapi.c
@@ -1,5 +1,5 @@
 /*
-** $Id: lapi.c,v 2.277 2017/11/23 19:29:04 roberto Exp roberto $
+** $Id: lapi.c,v 2.278 2017/12/06 18:08:03 roberto Exp roberto $
 ** Lua API
 ** See Copyright Notice in lua.h
 */
@@ -99,16 +99,6 @@ static StkId index2stack (lua_State *L, int idx) {
 }
 
 
-/*
-** to be called by 'lua_checkstack' in protected mode, to grow stack
-** capturing memory errors
-*/
-static void growstack (lua_State *L, void *ud) {
-  int size = *(int *)ud;
-  luaD_growstack(L, size);
-}
-
-
 LUA_API int lua_checkstack (lua_State *L, int n) {
   int res;
   CallInfo *ci = L->ci;
@@ -121,7 +111,7 @@ LUA_API int lua_checkstack (lua_State *L, int n) {
     if (inuse > LUAI_MAXSTACK - n)  /* can grow without overflow? */
       res = 0;  /* no */
     else  /* try to grow stack */
-      res = (luaD_rawrunprotected(L, &growstack, &n) == LUA_OK);
+      res = luaD_growstack(L, n, 0);
   }
   if (res && ci->top < L->top + n)
     ci->top = L->top + n;  /* adjust frame top */
diff --git a/ldo.c b/ldo.c
index fe1fcb25..136aef55 100644
--- a/ldo.c
+++ b/ldo.c
@@ -1,5 +1,5 @@
 /*
-** $Id: ldo.c,v 2.176 2017/11/29 13:02:17 roberto Exp roberto $
+** $Id: ldo.c,v 2.177 2017/12/01 15:44:51 roberto Exp roberto $
 ** Stack and Call structure of Lua
 ** See Copyright Notice in lua.h
 */
@@ -156,17 +156,17 @@ int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) {
 ** Stack reallocation
 ** ===================================================================
 */
-static void correctstack (lua_State *L, StkId oldstack) {
+static void correctstack (lua_State *L, StkId oldstack, StkId newstack) {
   CallInfo *ci;
   UpVal *up;
-  if (L->stack == oldstack)
+  if (oldstack == newstack)
     return;  /* stack address did not change */
-  L->top = (L->top - oldstack) + L->stack;
+  L->top = (L->top - oldstack) + newstack;
   for (up = L->openupval; up != NULL; up = up->u.open.next)
-    up->v = s2v((uplevel(up) - oldstack) + L->stack);
+    up->v = s2v((uplevel(up) - oldstack) + newstack);
   for (ci = L->ci; ci != NULL; ci = ci->previous) {
-    ci->top = (ci->top - oldstack) + L->stack;
-    ci->func = (ci->func - oldstack) + L->stack;
+    ci->top = (ci->top - oldstack) + newstack;
+    ci->func = (ci->func - oldstack) + newstack;
     if (isLua(ci))
       ci->u.l.trap = 1;  /* signal to update 'trap' in 'luaV_execute' */
   }
@@ -177,36 +177,40 @@ static void correctstack (lua_State *L, StkId oldstack) {
 #define ERRORSTACKSIZE	(LUAI_MAXSTACK + 200)
 
 
-void luaD_reallocstack (lua_State *L, int newsize) {
-  StkId oldstack = L->stack;
+int luaD_reallocstack (lua_State *L, int newsize, int safe) {
   int lim = L->stacksize;
+  StkId newstack = luaM_reallocvector(L, L->stack, lim, newsize, StackValue);
   lua_assert(newsize <= LUAI_MAXSTACK || newsize == ERRORSTACKSIZE);
   lua_assert(L->stack_last - L->stack == L->stacksize - EXTRA_STACK);
-  luaM_reallocvector(L, L->stack, L->stacksize, newsize, StackValue);
+  if (newstack == NULL) {  /* reallocation failed? */
+    if (safe) luaM_error(L);
+    else return 0;  /* no-safe mode: signal the error */
+  }
   for (; lim < newsize; lim++)
-    setnilvalue(s2v(L->stack + lim)); /* erase new segment */
+    setnilvalue(s2v(newstack + lim)); /* erase new segment */
+  correctstack(L, L->stack, newstack);
+  L->stack = newstack;
   L->stacksize = newsize;
   L->stack_last = L->stack + newsize - EXTRA_STACK;
-  correctstack(L, oldstack);
+  return 1;
 }
 
 
-void luaD_growstack (lua_State *L, int n) {
+int luaD_growstack (lua_State *L, int n, int safe) {
   int size = L->stacksize;
+  int newsize = 2 * size;
   if (size > LUAI_MAXSTACK)  /* error after extra size? */
     luaD_throw(L, LUA_ERRERR);
   else {
     int needed = cast_int(L->top - L->stack) + n + EXTRA_STACK;
-    int newsize = 2 * size;
     if (newsize > LUAI_MAXSTACK) newsize = LUAI_MAXSTACK;
     if (newsize < needed) newsize = needed;
     if (newsize > LUAI_MAXSTACK) {  /* stack overflow? */
-      luaD_reallocstack(L, ERRORSTACKSIZE);
+      luaD_reallocstack(L, ERRORSTACKSIZE, 1);
       luaG_runerror(L, "stack overflow");
     }
-    else
-      luaD_reallocstack(L, newsize);
-  }
+  }  /* else */
+  return luaD_reallocstack(L, newsize, safe);
 }
 
 
@@ -234,7 +238,7 @@ void luaD_shrinkstack (lua_State *L) {
      good size is smaller than current size, shrink its stack */
   if (inuse <= (LUAI_MAXSTACK - EXTRA_STACK) &&
       goodsize < L->stacksize)
-    luaD_reallocstack(L, goodsize);
+    luaD_reallocstack(L, goodsize, 0);  /* ok if that fails */
   else  /* don't change stack */
     condmovestack(L,{},{});  /* (change only for debugging) */
 }
diff --git a/ldo.h b/ldo.h
index a9b46cdf..388f6c3e 100644
--- a/ldo.h
+++ b/ldo.h
@@ -1,5 +1,5 @@
 /*
-** $Id: ldo.h,v 2.35 2017/11/23 16:35:54 roberto Exp roberto $
+** $Id: ldo.h,v 2.36 2017/11/23 18:29:41 roberto Exp roberto $
 ** Stack and Call structure of Lua
 ** See Copyright Notice in lua.h
 */
@@ -22,7 +22,8 @@
 */
 #define luaD_checkstackaux(L,n,pre,pos)  \
 	if (L->stack_last - L->top <= (n)) \
-	  { pre; luaD_growstack(L, n); pos; } else { condmovestack(L,pre,pos); }
+	  { pre; luaD_growstack(L, n, 1); pos; } \
+        else { condmovestack(L,pre,pos); }
 
 /* In general, 'pre'/'pos' are empty (nothing to save) */
 #define luaD_checkstack(L,n)	luaD_checkstackaux(L,n,(void)0,(void)0)
@@ -55,8 +56,8 @@ LUAI_FUNC int luaD_pcall (lua_State *L, Pfunc func, void *u,
                                         ptrdiff_t oldtop, ptrdiff_t ef);
 LUAI_FUNC void luaD_poscall (lua_State *L, CallInfo *ci, StkId firstResult,
                                           int nres);
-LUAI_FUNC void luaD_reallocstack (lua_State *L, int newsize);
-LUAI_FUNC void luaD_growstack (lua_State *L, int n);
+LUAI_FUNC int luaD_reallocstack (lua_State *L, int newsize, int safe);
+LUAI_FUNC int luaD_growstack (lua_State *L, int n, int safe);
 LUAI_FUNC void luaD_shrinkstack (lua_State *L);
 LUAI_FUNC void luaD_inctop (lua_State *L);
 
diff --git a/lgc.c b/lgc.c
index f893e122..8d28d3bf 100644
--- a/lgc.c
+++ b/lgc.c
@@ -1,5 +1,5 @@
 /*
-** $Id: lgc.c,v 2.240 2017/11/30 15:37:16 roberto Exp roberto $
+** $Id: lgc.c,v 2.241 2017/12/01 17:38:49 roberto Exp roberto $
 ** Garbage Collector
 ** See Copyright Notice in lua.h
 */
@@ -816,18 +816,13 @@ static GCObject **sweeptolive (lua_State *L, GCObject **p) {
 */
 
 /*
-** If possible, shrink string table (protected from memory errors).
+** If possible, shrink string table.
 */
-static void shrinkstrtable (lua_State *L, void *ud) {
-  luaS_resize(L, *cast(int*, ud) / 2);
-}
-
-
 static void checkSizes (lua_State *L, global_State *g) {
   if (!g->gcemergency) {
     l_mem olddebt = g->GCdebt;
     if (g->strt.nuse < g->strt.size / 4)  /* string table too big? */
-      luaD_rawrunprotected(L, &shrinkstrtable, &g->strt.size);
+      luaS_resize(L, g->strt.size / 2);
     g->GCestimate += g->GCdebt - olddebt;  /* correct estimate */
   }
 }
diff --git a/lmem.c b/lmem.c
index 23dc14d6..afacbb9f 100644
--- a/lmem.c
+++ b/lmem.c
@@ -1,5 +1,5 @@
 /*
-** $Id: lmem.c,v 1.92 2017/12/06 18:36:31 roberto Exp roberto $
+** $Id: lmem.c,v 1.93 2017/12/07 18:59:52 roberto Exp roberto $
 ** Interface to Memory Manager
 ** See Copyright Notice in lua.h
 */
@@ -69,9 +69,12 @@ void *luaM_growaux_ (lua_State *L, void *block, int nelems, int *psize,
     if (size < MINSIZEARRAY)
       size = MINSIZEARRAY;  /* minimum size */
   }
+  lua_assert(nelems + 1 <= size && size <= limit);
   /* 'limit' ensures that multiplication will not overflow */
-  newblock = luaM_realloc(L, block, cast(size_t, *psize) * size_elems,
-                                    cast(size_t, size) * size_elems);
+  newblock = luaM_realloc_(L, block, cast(size_t, *psize) * size_elems,
+                                     cast(size_t, size) * size_elems);
+  if (newblock == NULL)
+    luaM_error(L);
   *psize = size;  /* update only when everything else is OK */
   return newblock;
 }
@@ -115,20 +118,20 @@ void luaM_free_ (lua_State *L, void *block, size_t osize) {
 /*
 ** generic allocation routine.
 */
-void *luaM_realloc (lua_State *L, void *block, size_t osize, size_t nsize) {
+void *luaM_realloc_ (lua_State *L, void *block, size_t osize, size_t nsize) {
   void *newblock;
   global_State *g = G(L);
   lua_assert((osize == 0) == (block == NULL));
   hardtest(L, osize, nsize);
   newblock = (*g->frealloc)(g->ud, block, osize, nsize);
   if (newblock == NULL && nsize > 0) {
-    lua_assert(nsize > osize);  /* cannot fail when shrinking a block */
-    if (g->version) {  /* is state fully built? */
+    /* Is state fully built? Not shrinking a block? */
+    if (g->version && nsize > osize) {
       luaC_fullgc(L, 1);  /* try to free some memory... */
       newblock = (*g->frealloc)(g->ud, block, osize, nsize);  /* try again */
     }
     if (newblock == NULL)
-      luaD_throw(L, LUA_ERRMEM);
+      return NULL;
   }
   lua_assert((nsize == 0) == (newblock == NULL));
   g->GCdebt = (g->GCdebt + nsize) - osize;
@@ -136,7 +139,16 @@ void *luaM_realloc (lua_State *L, void *block, size_t osize, size_t nsize) {
 }
 
 
-void *luaM_malloc (lua_State *L, size_t size, int tag) {
+void *luaM_saferealloc_ (lua_State *L, void *block, size_t osize,
+                                                    size_t nsize) {
+  void *newblock = luaM_realloc_(L, block, osize, nsize);
+  if (newblock == NULL && nsize > 0)  /* allocation failed? */
+    luaM_error(L);
+  return newblock;
+}
+
+
+void *luaM_malloc_ (lua_State *L, size_t size, int tag) {
   hardtest(L, 0, size);
   if (size == 0)
     return NULL;  /* that's all */
@@ -149,7 +161,7 @@ void *luaM_malloc (lua_State *L, size_t size, int tag) {
         newblock = (*g->frealloc)(g->ud, NULL, tag, size);  /* try again */
       }
       if (newblock == NULL)
-        luaD_throw(L, LUA_ERRMEM);
+        luaM_error(L);
     }
     g->GCdebt += size;
     return newblock;
diff --git a/lmem.h b/lmem.h
index 3c828789..e98aabdb 100644
--- a/lmem.h
+++ b/lmem.h
@@ -1,5 +1,5 @@
 /*
-** $Id: lmem.h,v 1.44 2017/12/06 18:36:31 roberto Exp roberto $
+** $Id: lmem.h,v 1.45 2017/12/07 18:59:52 roberto Exp roberto $
 ** Interface to Memory Manager
 ** See Copyright Notice in lua.h
 */
@@ -14,6 +14,9 @@
 #include "lua.h"
 
 
+#define luaM_error(L)	luaD_throw(L, LUA_ERRMEM)
+
+
 /*
 ** This macro tests whether it is safe to multiply 'n' by the size of
 ** type 't' without overflows. Because 'e' is always constant, it avoids
@@ -45,26 +48,26 @@
 ** Arrays of chars do not need any test
 */
 #define luaM_reallocvchar(L,b,on,n)  \
-    cast(char *, luaM_realloc(L, (b), (on)*sizeof(char), (n)*sizeof(char)))
+  cast(char *, luaM_saferealloc_(L, (b), (on)*sizeof(char), (n)*sizeof(char)))
 
 #define luaM_freemem(L, b, s)	luaM_free_(L, (b), (s))
 #define luaM_free(L, b)		luaM_free_(L, (b), sizeof(*(b)))
 #define luaM_freearray(L, b, n)   luaM_free_(L, (b), (n)*sizeof(*(b)))
 
-#define luaM_new(L,t)		cast(t*, luaM_malloc(L, sizeof(t), 0))
-#define luaM_newvector(L,n,t)	cast(t*, luaM_malloc(L, (n)*sizeof(t), 0))
+#define luaM_new(L,t)		cast(t*, luaM_malloc_(L, sizeof(t), 0))
+#define luaM_newvector(L,n,t)	cast(t*, luaM_malloc_(L, (n)*sizeof(t), 0))
 #define luaM_newvectorchecked(L,n,t) \
   (luaM_checksize(L,n,sizeof(t)), luaM_newvector(L,n,t))
 
-#define luaM_newobject(L,tag,s)	luaM_malloc(L, (s), tag)
+#define luaM_newobject(L,tag,s)	luaM_malloc_(L, (s), tag)
 
 #define luaM_growvector(L,v,nelems,size,t,limit,e) \
 	((v)=cast(t *, luaM_growaux_(L,v,nelems,&(size),sizeof(t), \
                          luaM_limitN(limit,t),e)))
 
 #define luaM_reallocvector(L, v,oldn,n,t) \
-   ((v)=cast(t *, luaM_realloc(L, v, cast(size_t, oldn) * sizeof(t), \
-                                     cast(size_t, n) * sizeof(t))))
+   (cast(t *, luaM_realloc_(L, v, cast(size_t, oldn) * sizeof(t), \
+                                  cast(size_t, n) * sizeof(t))))
 
 #define luaM_shrinkvector(L,v,size,fs,t) \
    ((v)=cast(t *, luaM_shrinkvector_(L, v, &(size), fs, sizeof(t))))
@@ -72,15 +75,17 @@
 LUAI_FUNC l_noret luaM_toobig (lua_State *L);
 
 /* not to be called directly */
-LUAI_FUNC void *luaM_realloc (lua_State *L, void *block, size_t oldsize,
+LUAI_FUNC void *luaM_realloc_ (lua_State *L, void *block, size_t oldsize,
                                                           size_t size);
+LUAI_FUNC void *luaM_saferealloc_ (lua_State *L, void *block, size_t oldsize,
+                                                              size_t size);
 LUAI_FUNC void luaM_free_ (lua_State *L, void *block, size_t osize);
 LUAI_FUNC void *luaM_growaux_ (lua_State *L, void *block, int nelems,
                                int *size, int size_elem, int limit,
                                const char *what);
 LUAI_FUNC void *luaM_shrinkvector_ (lua_State *L, void *block, int *nelem,
                                     int final_n, int size_elem);
-LUAI_FUNC void *luaM_malloc (lua_State *L, size_t size, int tag);
+LUAI_FUNC void *luaM_malloc_ (lua_State *L, size_t size, int tag);
 
 #endif
 
diff --git a/lstring.c b/lstring.c
index 1b3f53e1..1675b87a 100644
--- a/lstring.c
+++ b/lstring.c
@@ -1,5 +1,5 @@
 /*
-** $Id: lstring.c,v 2.58 2017/12/01 16:40:29 roberto Exp roberto $
+** $Id: lstring.c,v 2.59 2017/12/07 18:59:52 roberto Exp roberto $
 ** String table (keeps all strings handled by Lua)
 ** See Copyright Notice in lua.h
 */
@@ -70,12 +70,15 @@ unsigned int luaS_hashlongstr (TString *ts) {
 
 
 /*
-** Resizes the string table.
+** Resize the string table. If allocation fails, keep the current size.
+** (This can degrade performance, but any size should work correctly.)
 */
 void luaS_resize (lua_State *L, int newsize) {
   int i;
   TString **newhash = luaM_newvector(L, newsize, TString *);
   stringtable *tb = &G(L)->strt;
+  if (newhash == NULL)  /* allocation failed? */
+    return;  /* leave hash as it is */
   for (i = 0; i < newsize; i++)  /* initialize new hash array */
     newhash[i] = NULL;
   for (i = 0; i < tb->size; i++) {  /* rehash all elements into new array */
diff --git a/ltable.c b/ltable.c
index d1345009..85101a8a 100644
--- a/ltable.c
+++ b/ltable.c
@@ -1,5 +1,5 @@
 /*
-** $Id: ltable.c,v 2.127 2017/11/23 19:29:04 roberto Exp roberto $
+** $Id: ltable.c,v 2.128 2017/12/07 18:59:52 roberto Exp roberto $
 ** Lua tables (hash)
 ** See Copyright Notice in lua.h
 */
@@ -357,15 +357,6 @@ static int numusehash (const Table *t, unsigned int *nums, unsigned int *pna) {
 }
 
 
-static void setarrayvector (lua_State *L, Table *t, unsigned int size) {
-  unsigned int i;
-  luaM_reallocvector(L, t->array, t->sizearray, size, TValue);
-  for (i=t->sizearray; i<size; i++)
-     setnilvalue(&t->array[i]);
-  t->sizearray = size;
-}
-
-
 /*
 ** Creates an array for the hash part of a table with the given
 ** size, or reuses the dummy node if size is zero.
@@ -398,39 +389,79 @@ static void setnodevector (lua_State *L, Table *t, unsigned int size) {
 }
 
 
-void luaH_resize (lua_State *L, Table *t, unsigned int nasize,
-                                          unsigned int nhsize) {
-  unsigned int i;
+/*
+** (Re)insert all elements from list 'nodes' into table 't'.
+*/
+static void reinsert(lua_State *L, Node *nodes, int nsize, Table *t) {
   int j;
-  unsigned int oldasize = t->sizearray;
-  int oldhsize = allocsizenode(t);
-  Node *nold = t->node;  /* save old hash ... */
-  if (nasize > oldasize)  /* array part must grow? */
-    setarrayvector(L, t, nasize);
-  /* create new hash part with appropriate size */
-  setnodevector(L, t, nhsize);
-  if (nasize < oldasize) {  /* array part must shrink? */
-    t->sizearray = nasize;
-    /* re-insert elements from vanishing slice */
-    for (i=nasize; i<oldasize; i++) {
-      if (!ttisnil(&t->array[i]))
-        luaH_setint(L, t, i + 1, &t->array[i]);
-    }
-    /* shrink array */
-    luaM_reallocvector(L, t->array, oldasize, nasize, TValue);
-  }
-  /* re-insert elements from hash part */
-  for (j = oldhsize - 1; j >= 0; j--) {
-    Node *old = nold + j;
+  for (j = nsize - 1; j >= 0; j--) {
+    Node *old = nodes + j;
     if (!ttisnil(gval(old))) {
       /* doesn't need barrier/invalidate cache, as entry was
          already present in the table */
-      TValue k; getnodekey(L, &k, old);
+      TValue k;
+      getnodekey(L, &k, old);
       setobjt2t(L, luaH_set(L, t, &k), gval(old));
     }
   }
+}
+
+
+/*
+** Resize table 't' for the new given sizes. Both allocations
+** (for the hash part and for the array part) can fail, which
+** creates some subtleties. If the first allocation, for the hash
+** part, fails, an error is raised and that is it. Otherwise,
+** copy the elements in the shrinking part of the array (if it
+** is shrinking) into the new hash. Then it reallocates the array part.
+** If that fails, it frees the new hash part and restores the old hash
+** part (to restore the original state of the table), and then raises
+** the allocation error. Otherwise, initialize the new part of the
+** array (if any) with nils and reinsert the elements in the old
+** hash back into the new parts of the table.
+*/
+void luaH_resize (lua_State *L, Table *t, unsigned int newasize,
+                                          unsigned int nhsize) {
+  unsigned int i;
+  Node *oldnode = t->node;  /* save old hash ... */
+  Node *oldlastfree = t->lastfree;
+  int oldlsizenode = t->lsizenode;
+  int oldhsize = allocsizenode(t);
+  unsigned int oldasize = t->sizearray;
+  TValue *newarray;
+  /* create new hash part with appropriate size */
+  setnodevector(L, t, nhsize);
+  if (newasize < oldasize) {  /* will array shrink? */
+    /* re-insert into the hash the elements from vanishing slice */
+    t->sizearray = newasize;  /* pretend array has new size */
+    for (i = newasize; i < oldasize; i++) {
+      if (!ttisnil(&t->array[i]))
+        luaH_setint(L, t, i + 1, &t->array[i]);
+    }
+    t->sizearray = oldasize;  /* restore current size */
+  }
+  /* allocate new array */
+  newarray = luaM_reallocvector(L, t->array, oldasize, newasize, TValue);
+  if (newarray == NULL && newasize > 0) {  /* allocation failed? */
+    if (nhsize > 0)  /* not the dummy node? */
+      luaM_freearray(L, t->node, allocsizenode(t)); /* release new hash part */
+    t->node = oldnode;  /* restore original hash part */
+    t->lastfree = oldlastfree;
+    t->lsizenode = oldlsizenode;
+    lua_assert(!isdummy(t) == (t->node != dummynode));
+    luaM_error(L);  /* error with array unchanged */
+  }
+  /* allocation ok; initialize new part of the array */
+  t->array = newarray;
+  t->sizearray = newasize;
+  for (i = oldasize; i < newasize; i++)
+     setnilvalue(&t->array[i]);
+  /* re-insert elements from old hash part into new parts */
+  reinsert(L, oldnode, oldhsize, t);
+  /* free old hash */
   if (oldhsize > 0)  /* not the dummy node? */
-    luaM_freearray(L, nold, cast(size_t, oldhsize)); /* free old hash */
+    luaM_freearray(L, oldnode, cast(size_t, oldhsize));
+  lua_assert(!isdummy(t) == (t->node != dummynode));
 }