lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Many thanks to all the help finding and chasing this bug! I still have
to find some time to look at that issue, but maybe the attached function
may be helpful (lua_checkmemory). It traverses all memory allocated
by Lua and checks all kinds of consistencies (such as black->white
pointers).

(OBS: the attached code was adapted from the original, in "ltests.c".)

For instance, we can add a call 'lua_checkmemory(L)' at the "end" of
'singlestep', to check whether any step of the GC is breaking some
invariant. (Beware that, if the program is using too much memory, this
change can make it prohibitively slow.)

Because 'singlestep' has many returns, it may be easier to rename
it to something else and define a new 'singlestep' that calls the
original one and then calls lua_checkmemory:

  static lu_mem singlestep (lua_State *L) {
    lu_mem res = singlestep1(L);
    lua_checkmemory(L);
    return res;
  }

Checking that fragment I already found a fail in our own tests!

-- Roberto

#define ltests_c
#define LUA_CORE

#include "lprefix.h"



/* turn on assertions */
#undef NDEBUG
#include <assert.h>
#define lua_assert(c)           assert(c)

#include <limits.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "lua.h"

#include "lapi.h"
#include "lauxlib.h"
#include "lcode.h"
#include "lctype.h"
#include "ldebug.h"
#include "ldo.h"
#include "lfunc.h"
#include "lmem.h"
#include "lopcodes.h"
#include "lopnames.h"
#include "lstate.h"
#include "lstring.h"
#include "ltable.h"
#include "lualib.h"



/*
** {======================================================
** Functions to check memory consistency
** =======================================================
*/


/*
** Check GC invariants. For incremental mode, a black object cannot
** point to a white one. For generational mode, really old objects
** cannot point to young objects. Both old1 and touched2 objects
** cannot point to new objects (but can point to survivals).
** (Threads and open upvalues, despite being marked "really old",
** continue to be visited in all collections, and therefore can point to
** new objects. They, and only they, are old but gray.)
*/
static int testobjref1 (global_State *g, GCObject *f, GCObject *t) {
  if (isdead(g,t)) return 0;
  if (issweepphase(g))
    return 1;  /* no invariants */
  else if (g->gckind == KGC_INC)
    return !(isblack(f) && iswhite(t));  /* basic incremental invariant */
  else {  /* generational mode */
    if ((getage(f) == G_OLD && isblack(f)) && !isold(t))
      return 0;
    if (((getage(f) == G_OLD1 || getage(f) == G_TOUCHED2) && isblack(f)) &&
          getage(t) == G_NEW)
      return 0;
    return 1;
  }
}


static void printobj (global_State *g, GCObject *o) {
  printf("||%s(%p)-%c%c(%02X)||",
           ttypename(novariant(o->tt)), (void *)o,
           isdead(g,o) ? 'd' : isblack(o) ? 'b' : iswhite(o) ? 'w' : 'g',
           "ns01oTt"[getage(o)], o->marked);
  if (o->tt == LUA_VSHRSTR || o->tt == LUA_VLNGSTR)
    printf(" '%s'", getstr(gco2ts(o)));
}


static int testobjref (global_State *g, GCObject *f, GCObject *t) {
  int r1 = testobjref1(g, f, t);
  if (!r1) {
    printf("%d(%02X) - ", g->gcstate, g->currentwhite);
    printobj(g, f);
    printf("  ->  ");
    printobj(g, t);
    printf("\n");
  }
  return r1;
}

#define checkobjref(g,f,t)  \
	{ if (t) lua_longassert(testobjref(g,f,obj2gco(t))); }


static void checkvalref (global_State *g, GCObject *f, const TValue *t) {
  lua_assert(!iscollectable(t) ||
    (righttt(t) && testobjref(g, f, gcvalue(t))));
}


static void checktable (global_State *g, Table *h) {
  unsigned int i;
  unsigned int asize = luaH_realasize(h);
  Node *n, *limit = gnode(h, sizenode(h));
  GCObject *hgc = obj2gco(h);
  checkobjref(g, hgc, h->metatable);
  for (i = 0; i < asize; i++)
    checkvalref(g, hgc, &h->array[i]);
  for (n = gnode(h, 0); n < limit; n++) {
    if (!isempty(gval(n))) {
      TValue k;
      getnodekey(g->mainthread, &k, n);
      lua_assert(!keyisnil(n));
      checkvalref(g, hgc, &k);
      checkvalref(g, hgc, gval(n));
    }
  }
}


static void checkudata (global_State *g, Udata *u) {
  int i;
  GCObject *hgc = obj2gco(u);
  checkobjref(g, hgc, u->metatable);
  for (i = 0; i < u->nuvalue; i++)
    checkvalref(g, hgc, &u->uv[i].uv);
}


/*
** All marks are conditional because a GC may happen while the
** prototype is still being created
*/
static void checkproto (global_State *g, Proto *f) {
  int i;
  GCObject *fgc = obj2gco(f);
  checkobjref(g, fgc, f->source);
  for (i=0; i<f->sizek; i++) {
    if (ttisstring(f->k + i))
      checkobjref(g, fgc, tsvalue(f->k + i));
  }
  for (i=0; i<f->sizeupvalues; i++)
    checkobjref(g, fgc, f->upvalues[i].name);
  for (i=0; i<f->sizep; i++)
    checkobjref(g, fgc, f->p[i]);
  for (i=0; i<f->sizelocvars; i++)
    checkobjref(g, fgc, f->locvars[i].varname);
}


static void checkCclosure (global_State *g, CClosure *cl) {
  GCObject *clgc = obj2gco(cl);
  int i;
  for (i = 0; i < cl->nupvalues; i++)
    checkvalref(g, clgc, &cl->upvalue[i]);
}


static void checkLclosure (global_State *g, LClosure *cl) {
  GCObject *clgc = obj2gco(cl);
  int i;
  checkobjref(g, clgc, cl->p);
  for (i=0; i<cl->nupvalues; i++) {
    UpVal *uv = cl->upvals[i];
    if (uv) {
      checkobjref(g, clgc, uv);
      if (!upisopen(uv))
        checkvalref(g, obj2gco(uv), uv->v);
    }
  }
}


static int lua_checkpc (CallInfo *ci) {
  if (!isLua(ci)) return 1;
  else {
    StkId f = ci->func;
    Proto *p = clLvalue(s2v(f))->p;
    return p->code <= ci->u.l.savedpc &&
           ci->u.l.savedpc <= p->code + p->sizecode;
  }
}


static void checkstack (global_State *g, lua_State *L1) {
  StkId o;
  CallInfo *ci;
  UpVal *uv;
  lua_assert(!isdead(g, L1));
  if (L1->stack == NULL) {  /* incomplete thread? */
    lua_assert(L1->stacksize == 0 && L1->openupval == NULL &&
               L1->ci == NULL);
    return;
  }
  for (uv = L1->openupval; uv != NULL; uv = uv->u.open.next)
    lua_assert(upisopen(uv));  /* must be open */
  for (ci = L1->ci; ci != NULL; ci = ci->previous) {
    lua_assert(ci->top <= L1->stack_last);
    lua_assert(lua_checkpc(ci));
  }
  for (o = L1->stack; o < L1->stack_last + EXTRA_STACK; o++)
    checkliveness(L1, s2v(o));  /* entire stack must have valid values */
}


static void checkrefs (global_State *g, GCObject *o) {
  switch (o->tt) {
    case LUA_VUSERDATA: {
      checkudata(g, gco2u(o));
      break;
    }
    case LUA_VUPVAL: {
      checkvalref(g, o, gco2upv(o)->v);
      break;
    }
    case LUA_VTABLE: {
      checktable(g, gco2t(o));
      break;
    }
    case LUA_VTHREAD: {
      checkstack(g, gco2th(o));
      break;
    }
    case LUA_VLCL: {
      checkLclosure(g, gco2lcl(o));
      break;
    }
    case LUA_VCCL: {
      checkCclosure(g, gco2ccl(o));
      break;
    }
    case LUA_VPROTO: {
      checkproto(g, gco2p(o));
      break;
    }
    case LUA_VSHRSTR:
    case LUA_VLNGSTR: {
      lua_assert(!isgray(o));  /* strings are never gray */
      break;
    }
    default: lua_assert(0);
  }
}


/*
** Check consistency of an object:
** - Dead objects can only happen in the 'allgc' list during a sweep
** phase (controlled by the caller through 'maybedead').
** - During pause, all objects must be white.
** - In generational mode:
**   * objects must be old enough for their lists ('listage').
**   * old objects cannot be white.
**   * old objects must be black, except for 'touched1', 'old0',
** threads, and open upvalues.
*/
static void checkobject (global_State *g, GCObject *o, int maybedead,
                         int listage) {
  if (isdead(g, o))
    lua_assert(maybedead);
  else {
    lua_assert(g->gcstate != GCSpause || iswhite(o));
    if (g->gckind == KGC_GEN) {  /* generational mode? */
      lua_assert(getage(o) >= listage);
      lua_assert(!iswhite(o) || !isold(o));
      if (isold(o)) {
        lua_assert(isblack(o) ||
        getage(o) == G_TOUCHED1 ||
        getage(o) == G_OLD0 ||
        o->tt == LUA_VTHREAD ||
        (o->tt == LUA_VUPVAL && upisopen(gco2upv(o))));
      }
    }
    checkrefs(g, o);
  }
}


static void checkgraylist (global_State *g, GCObject *o) {
  ((void)g);  /* better to keep it available if we need to print an object */
  while (o) {
    lua_assert(isgray(o) || getage(o) == G_TOUCHED2);
    switch (o->tt) {
      case LUA_VTABLE: o = gco2t(o)->gclist; break;
      case LUA_VLCL: o = gco2lcl(o)->gclist; break;
      case LUA_VCCL: o = gco2ccl(o)->gclist; break;
      case LUA_VTHREAD: o = gco2th(o)->gclist; break;
      case LUA_VPROTO: o = gco2p(o)->gclist; break;
      default: lua_assert(0);  /* other objects cannot be in a gray list */
    }
  }
}


/*
** Check objects in gray lists.
*/
static void checkgrays (global_State *g) {
  if (!keepinvariant(g)) return;
  checkgraylist(g, g->gray);
  checkgraylist(g, g->grayagain);
  checkgraylist(g, g->weak);
  checkgraylist(g, g->ephemeron);
}


static void checklist (global_State *g, int maybedead, int tof,
  GCObject *newl, GCObject *survival, GCObject *old, GCObject *reallyold) {
  GCObject *o;
  for (o = newl; o != survival; o = o->next) {
    checkobject(g, o, maybedead, G_NEW);
    lua_assert(!tof == !tofinalize(o));
  }
  for (o = survival; o != old; o = o->next) {
    checkobject(g, o, 0, G_SURVIVAL);
    lua_assert(!tof == !tofinalize(o));
  }
  for (o = old; o != reallyold; o = o->next) {
    checkobject(g, o, 0, G_OLD1);
    lua_assert(!tof == !tofinalize(o));
  }
  for (o = reallyold; o != NULL; o = o->next) {
    checkobject(g, o, 0, G_OLD);
    lua_assert(!tof == !tofinalize(o));
  }
}


int lua_checkmemory (lua_State *L) {
  global_State *g = G(L);
  GCObject *o;
  int maybedead;
  if (keepinvariant(g)) {
    lua_assert(!iswhite(g->mainthread));
    lua_assert(!iswhite(gcvalue(&g->l_registry)));
  }
  lua_assert(!isdead(g, gcvalue(&g->l_registry)));
  lua_assert(g->sweepgc == NULL || issweepphase(g));
  checkgrays(g);

  /* check 'fixedgc' list */
  for (o = g->fixedgc; o != NULL; o = o->next) {
    lua_assert(o->tt == LUA_VSHRSTR && isgray(o) && getage(o) == G_OLD);
  }

  /* check 'allgc' list */
  maybedead = (GCSatomic < g->gcstate && g->gcstate <= GCSswpallgc);
  checklist(g, maybedead, 0, g->allgc, g->survival, g->old, g->reallyold);

  /* check 'finobj' list */
  checklist(g, 0, 1, g->finobj, g->finobjsur, g->finobjold, g->finobjrold);

  /* check 'tobefnz' list */
  for (o = g->tobefnz; o != NULL; o = o->next) {
    checkobject(g, o, 0, G_NEW);
    lua_assert(tofinalize(o));
    lua_assert(o->tt == LUA_VUSERDATA || o->tt == LUA_VTABLE);
  }
  return 0;
}

/* }====================================================== */