mirror of
https://github.com/lua/lua
synced 2025-04-10 06:52:56 +03:00
finalizers (__gc) for tables
This commit is contained in:
parent
5b33e39855
commit
9b7a12c46d
5
lapi.c
5
lapi.c
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
** $Id: lapi.c,v 2.139 2010/10/25 20:31:11 roberto Exp roberto $
|
** $Id: lapi.c,v 2.141 2010/11/18 19:15:00 roberto Exp roberto $
|
||||||
** Lua API
|
** Lua API
|
||||||
** See Copyright Notice in lua.h
|
** See Copyright Notice in lua.h
|
||||||
*/
|
*/
|
||||||
@ -760,13 +760,14 @@ LUA_API int lua_setmetatable (lua_State *L, int objindex) {
|
|||||||
hvalue(obj)->metatable = mt;
|
hvalue(obj)->metatable = mt;
|
||||||
if (mt)
|
if (mt)
|
||||||
luaC_objbarrierback(L, gcvalue(obj), mt);
|
luaC_objbarrierback(L, gcvalue(obj), mt);
|
||||||
|
luaC_checkfinalizer(L, gcvalue(obj), mt);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LUA_TUSERDATA: {
|
case LUA_TUSERDATA: {
|
||||||
uvalue(obj)->metatable = mt;
|
uvalue(obj)->metatable = mt;
|
||||||
if (mt) {
|
if (mt) {
|
||||||
luaC_objbarrier(L, rawuvalue(obj), mt);
|
luaC_objbarrier(L, rawuvalue(obj), mt);
|
||||||
luaC_checkfinalizer(L, rawuvalue(obj));
|
luaC_checkfinalizer(L, gcvalue(obj), mt);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
72
lgc.c
72
lgc.c
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
** $Id: lgc.c,v 2.102 2010/09/03 14:14:01 roberto Exp roberto $
|
** $Id: lgc.c,v 2.104 2010/11/18 19:15:00 roberto Exp roberto $
|
||||||
** Garbage Collector
|
** Garbage Collector
|
||||||
** See Copyright Notice in lua.h
|
** See Copyright Notice in lua.h
|
||||||
*/
|
*/
|
||||||
@ -68,7 +68,7 @@
|
|||||||
#define stringmark(s) ((void)((s) && resetbits((s)->tsv.marked, WHITEBITS)))
|
#define stringmark(s) ((void)((s) && resetbits((s)->tsv.marked, WHITEBITS)))
|
||||||
|
|
||||||
|
|
||||||
#define isfinalized(u) testbit((u)->marked, FINALIZEDBIT)
|
#define isfinalized(x) testbit(gch(x)->marked, FINALIZEDBIT)
|
||||||
|
|
||||||
#define checkdeadkey(n) lua_assert(!ttisdeadkey(gkey(n)) || ttisnil(gval(n)))
|
#define checkdeadkey(n) lua_assert(!ttisdeadkey(gkey(n)) || ttisnil(gval(n)))
|
||||||
|
|
||||||
@ -114,12 +114,11 @@ static void removeentry (Node *n) {
|
|||||||
*/
|
*/
|
||||||
static int iscleared (const TValue *o, int iskey) {
|
static int iscleared (const TValue *o, int iskey) {
|
||||||
if (!iscollectable(o)) return 0;
|
if (!iscollectable(o)) return 0;
|
||||||
if (ttisstring(o)) {
|
else if (ttisstring(o)) {
|
||||||
stringmark(rawtsvalue(o)); /* strings are `values', so are never weak */
|
stringmark(rawtsvalue(o)); /* strings are `values', so are never weak */
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return iswhite(gcvalue(o)) ||
|
else return iswhite(gcvalue(o)) || (!iskey && isfinalized(gcvalue(o)));
|
||||||
(ttisuserdata(o) && (!iskey && isfinalized(uvalue(o))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -687,19 +686,18 @@ static void checkSizes (lua_State *L) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static Udata *udata2finalize (global_State *g) {
|
static GCObject *udata2finalize (global_State *g) {
|
||||||
GCObject *o = g->tobefnz; /* get first element */
|
GCObject *o = g->tobefnz; /* get first element */
|
||||||
Udata *u = rawgco2u(o);
|
lua_assert(isfinalized(o));
|
||||||
lua_assert(isfinalized(&u->uv));
|
|
||||||
lua_assert(!isold(o));
|
lua_assert(!isold(o));
|
||||||
g->tobefnz = u->uv.next; /* remove it from 'tobefnz' list */
|
g->tobefnz = gch(o)->next; /* remove it from 'tobefnz' list */
|
||||||
u->uv.next = g->allgc; /* return it to 'allgc' list */
|
gch(o)->next = g->allgc; /* return it to 'allgc' list */
|
||||||
g->allgc = o;
|
g->allgc = o;
|
||||||
resetbit(u->uv.marked, SEPARATED); /* mark that it is not in 'tobefnz' */
|
resetbit(gch(o)->marked, SEPARATED); /* mark that it is not in 'tobefnz' */
|
||||||
resetoldbit(o); /* see MOVE OLD rule */
|
resetoldbit(o); /* see MOVE OLD rule */
|
||||||
if (!keepinvariant(g)) /* not keeping invariant? */
|
if (!keepinvariant(g)) /* not keeping invariant? */
|
||||||
makewhite(g, o); /* "sweep" object */
|
makewhite(g, o); /* "sweep" object */
|
||||||
return u;
|
return o;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -711,8 +709,10 @@ static void dothecall (lua_State *L, void *ud) {
|
|||||||
|
|
||||||
static void GCTM (lua_State *L, int propagateerrors) {
|
static void GCTM (lua_State *L, int propagateerrors) {
|
||||||
global_State *g = G(L);
|
global_State *g = G(L);
|
||||||
Udata *udata = udata2finalize(g);
|
const TValue *tm;
|
||||||
const TValue *tm = gfasttm(g, udata->uv.metatable, TM_GC);
|
TValue v;
|
||||||
|
setgcovalue(L, &v, udata2finalize(g));
|
||||||
|
tm = luaT_gettmbyobj(L, &v, TM_GC);
|
||||||
if (tm != NULL && ttisfunction(tm)) { /* is there a finalizer? */
|
if (tm != NULL && ttisfunction(tm)) { /* is there a finalizer? */
|
||||||
int status;
|
int status;
|
||||||
lu_byte oldah = L->allowhook;
|
lu_byte oldah = L->allowhook;
|
||||||
@ -720,7 +720,7 @@ static void GCTM (lua_State *L, int propagateerrors) {
|
|||||||
L->allowhook = 0; /* stop debug hooks during GC tag method */
|
L->allowhook = 0; /* stop debug hooks during GC tag method */
|
||||||
stopgc(g); /* avoid GC steps */
|
stopgc(g); /* avoid GC steps */
|
||||||
setobj2s(L, L->top, tm); /* push finalizer... */
|
setobj2s(L, L->top, tm); /* push finalizer... */
|
||||||
setuvalue(L, L->top+1, udata); /* ... and its argument */
|
setobj2s(L, L->top + 1, &v); /* ... and its argument */
|
||||||
L->top += 2; /* and (next line) call the finalizer */
|
L->top += 2; /* and (next line) call the finalizer */
|
||||||
status = luaD_pcall(L, dothecall, NULL, savestack(L, L->top - 2), 0);
|
status = luaD_pcall(L, dothecall, NULL, savestack(L, L->top - 2), 0);
|
||||||
L->allowhook = oldah; /* restore hooks */
|
L->allowhook = oldah; /* restore hooks */
|
||||||
@ -738,25 +738,25 @@ static void GCTM (lua_State *L, int propagateerrors) {
|
|||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** move all unreachable udata that need finalization from list 'udgc' to
|
** move all unreachable objects that need finalization from list 'finobj'
|
||||||
** list 'tobefnz'
|
** to list 'tobefnz'
|
||||||
*/
|
*/
|
||||||
void luaC_separateudata (lua_State *L, int all) {
|
void luaC_separateudata (lua_State *L, int all) {
|
||||||
global_State *g = G(L);
|
global_State *g = G(L);
|
||||||
GCObject **p = &g->udgc;
|
GCObject **p = &g->finobj;
|
||||||
GCObject *curr;
|
GCObject *curr;
|
||||||
GCObject **lastnext = &g->tobefnz;
|
GCObject **lastnext = &g->tobefnz;
|
||||||
/* find last 'next' field in 'tobefnz' list (to add elements in its end) */
|
/* find last 'next' field in 'tobefnz' list (to add elements in its end) */
|
||||||
while (*lastnext != NULL)
|
while (*lastnext != NULL)
|
||||||
lastnext = &gch(*lastnext)->next;
|
lastnext = &gch(*lastnext)->next;
|
||||||
while ((curr = *p) != NULL) { /* traverse all finalizable objects */
|
while ((curr = *p) != NULL) { /* traverse all finalizable objects */
|
||||||
lua_assert(gch(curr)->tt == LUA_TUSERDATA && !isfinalized(gco2u(curr)));
|
lua_assert(!isfinalized(curr));
|
||||||
lua_assert(testbit(gch(curr)->marked, SEPARATED));
|
lua_assert(testbit(gch(curr)->marked, SEPARATED));
|
||||||
if (!(all || iswhite(curr))) /* not being collected? */
|
if (!(all || iswhite(curr))) /* not being collected? */
|
||||||
p = &gch(curr)->next; /* don't bother with it */
|
p = &gch(curr)->next; /* don't bother with it */
|
||||||
else {
|
else {
|
||||||
l_setbit(gch(curr)->marked, FINALIZEDBIT); /* won't be finalized again */
|
l_setbit(gch(curr)->marked, FINALIZEDBIT); /* won't be finalized again */
|
||||||
*p = gch(curr)->next; /* remove 'curr' from 'udgc' list */
|
*p = gch(curr)->next; /* remove 'curr' from 'finobj' list */
|
||||||
gch(curr)->next = *lastnext; /* link at the end of 'tobefnz' list */
|
gch(curr)->next = *lastnext; /* link at the end of 'tobefnz' list */
|
||||||
*lastnext = curr;
|
*lastnext = curr;
|
||||||
lastnext = &gch(curr)->next;
|
lastnext = &gch(curr)->next;
|
||||||
@ -766,23 +766,23 @@ void luaC_separateudata (lua_State *L, int all) {
|
|||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** if userdata 'u' has a finalizer, remove it from 'allgc' list (must
|
** if object 'o' has a finalizer, remove it from 'allgc' list (must
|
||||||
** search the list to find it) and link it in 'udgc' list.
|
** search the list to find it) and link it in 'finobj' list.
|
||||||
*/
|
*/
|
||||||
void luaC_checkfinalizer (lua_State *L, Udata *u) {
|
void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt) {
|
||||||
global_State *g = G(L);
|
global_State *g = G(L);
|
||||||
if (testbit(u->uv.marked, SEPARATED) || /* udata is already separated... */
|
if (testbit(gch(o)->marked, SEPARATED) || /* obj. is already separated... */
|
||||||
isfinalized(&u->uv) || /* ... or is finalized... */
|
isfinalized(o) || /* ... or is finalized... */
|
||||||
gfasttm(g, u->uv.metatable, TM_GC) == NULL) /* or has no finalizer? */
|
gfasttm(g, mt, TM_GC) == NULL) /* or has no finalizer? */
|
||||||
return; /* nothing to be done */
|
return; /* nothing to be done */
|
||||||
else { /* move 'u' to 'udgc' list */
|
else { /* move 'o' to 'finobj' list */
|
||||||
GCObject **p;
|
GCObject **p;
|
||||||
for (p = &g->allgc; *p != obj2gco(u); p = &gch(*p)->next) ;
|
for (p = &g->allgc; *p != o; p = &gch(*p)->next) ;
|
||||||
*p = u->uv.next; /* remove 'u' from root list */
|
*p = gch(o)->next; /* remove 'o' from root list */
|
||||||
u->uv.next = g->udgc; /* link it in list 'udgc' */
|
gch(o)->next = g->finobj; /* link it in list 'finobj' */
|
||||||
g->udgc = obj2gco(u);
|
g->finobj = o;
|
||||||
l_setbit(u->uv.marked, SEPARATED); /* mark it as such */
|
l_setbit(gch(o)->marked, SEPARATED); /* mark it as such */
|
||||||
resetoldbit(obj2gco(u)); /* see MOVE OLD rule */
|
resetoldbit(o); /* see MOVE OLD rule */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -837,8 +837,8 @@ void luaC_freeallobjects (lua_State *L) {
|
|||||||
/* following "white" makes all objects look dead */
|
/* following "white" makes all objects look dead */
|
||||||
g->currentwhite = WHITEBITS;
|
g->currentwhite = WHITEBITS;
|
||||||
g->gckind = KGC_NORMAL;
|
g->gckind = KGC_NORMAL;
|
||||||
sweepwholelist(L, &g->udgc);
|
sweepwholelist(L, &g->finobj);
|
||||||
lua_assert(g->udgc == NULL);
|
lua_assert(g->finobj == NULL);
|
||||||
sweepwholelist(L, &g->allgc);
|
sweepwholelist(L, &g->allgc);
|
||||||
lua_assert(g->allgc == NULL);
|
lua_assert(g->allgc == NULL);
|
||||||
for (i = 0; i < g->strt.size; i++) /* free all string lists */
|
for (i = 0; i < g->strt.size; i++) /* free all string lists */
|
||||||
@ -905,7 +905,7 @@ static l_mem singlestep (lua_State *L) {
|
|||||||
return GCSWEEPCOST;
|
return GCSWEEPCOST;
|
||||||
}
|
}
|
||||||
else { /* no more strings to sweep */
|
else { /* no more strings to sweep */
|
||||||
g->sweepgc = &g->udgc; /* prepare to sweep userdata */
|
g->sweepgc = &g->finobj; /* prepare to sweep finalizable objects */
|
||||||
g->gcstate = GCSsweepudata;
|
g->gcstate = GCSsweepudata;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
4
lgc.h
4
lgc.h
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
** $Id: lgc.h,v 2.43 2010/06/07 16:55:34 roberto Exp roberto $
|
** $Id: lgc.h,v 2.45 2010/11/18 19:15:00 roberto Exp $
|
||||||
** Garbage Collector
|
** Garbage Collector
|
||||||
** See Copyright Notice in lua.h
|
** See Copyright Notice in lua.h
|
||||||
*/
|
*/
|
||||||
@ -137,7 +137,7 @@ LUAI_FUNC GCObject *luaC_newobj (lua_State *L, int tt, size_t sz,
|
|||||||
LUAI_FUNC void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v);
|
LUAI_FUNC void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v);
|
||||||
LUAI_FUNC void luaC_barrierback_ (lua_State *L, GCObject *o);
|
LUAI_FUNC void luaC_barrierback_ (lua_State *L, GCObject *o);
|
||||||
LUAI_FUNC void luaC_barrierproto_ (lua_State *L, Proto *p, Closure *c);
|
LUAI_FUNC void luaC_barrierproto_ (lua_State *L, Proto *p, Closure *c);
|
||||||
LUAI_FUNC void luaC_checkfinalizer (lua_State *L, Udata *u);
|
LUAI_FUNC void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt);
|
||||||
LUAI_FUNC void luaC_checkupvalcolor (global_State *g, UpVal *uv);
|
LUAI_FUNC void luaC_checkupvalcolor (global_State *g, UpVal *uv);
|
||||||
LUAI_FUNC void luaC_changemode (lua_State *L, int mode);
|
LUAI_FUNC void luaC_changemode (lua_State *L, int mode);
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
** $Id: lobject.h,v 2.41 2010/06/04 13:25:10 roberto Exp roberto $
|
** $Id: lobject.h,v 2.42 2010/07/26 15:53:23 roberto Exp roberto $
|
||||||
** Type definitions for Lua objects
|
** Type definitions for Lua objects
|
||||||
** See Copyright Notice in lua.h
|
** See Copyright Notice in lua.h
|
||||||
*/
|
*/
|
||||||
@ -153,6 +153,10 @@ typedef struct lua_TValue {
|
|||||||
#define setbvalue(obj,x) \
|
#define setbvalue(obj,x) \
|
||||||
{ TValue *i_o=(obj); i_o->value_.b=(x); i_o->tt_=LUA_TBOOLEAN; }
|
{ TValue *i_o=(obj); i_o->value_.b=(x); i_o->tt_=LUA_TBOOLEAN; }
|
||||||
|
|
||||||
|
#define setgcovalue(L,obj,x) \
|
||||||
|
{ TValue *i_o=(obj); GCObject *i_g=(x); \
|
||||||
|
i_o->value_.gc=i_g; i_o->tt_=gch(i_g)->tt; }
|
||||||
|
|
||||||
#define setsvalue(L,obj,x) \
|
#define setsvalue(L,obj,x) \
|
||||||
{ TValue *i_o=(obj); \
|
{ TValue *i_o=(obj); \
|
||||||
i_o->value_.gc=cast(GCObject *, (x)); i_o->tt_=LUA_TSTRING; \
|
i_o->value_.gc=cast(GCObject *, (x)); i_o->tt_=LUA_TSTRING; \
|
||||||
|
4
lstate.c
4
lstate.c
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
** $Id: lstate.c,v 2.85 2010/04/30 18:36:22 roberto Exp roberto $
|
** $Id: lstate.c,v 2.86 2010/09/03 14:14:01 roberto Exp roberto $
|
||||||
** Global State
|
** Global State
|
||||||
** See Copyright Notice in lua.h
|
** See Copyright Notice in lua.h
|
||||||
*/
|
*/
|
||||||
@ -252,7 +252,7 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
|
|||||||
g->version = lua_version(NULL);
|
g->version = lua_version(NULL);
|
||||||
g->gcstate = GCSpause;
|
g->gcstate = GCSpause;
|
||||||
g->allgc = NULL;
|
g->allgc = NULL;
|
||||||
g->udgc = NULL;
|
g->finobj = NULL;
|
||||||
g->tobefnz = NULL;
|
g->tobefnz = NULL;
|
||||||
g->gray = g->grayagain = NULL;
|
g->gray = g->grayagain = NULL;
|
||||||
g->weak = g->ephemeron = g->allweak = NULL;
|
g->weak = g->ephemeron = g->allweak = NULL;
|
||||||
|
8
lstate.h
8
lstate.h
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
** $Id: lstate.h,v 2.67 2010/09/30 17:21:31 roberto Exp roberto $
|
** $Id: lstate.h,v 2.68 2010/10/29 17:52:46 roberto Exp roberto $
|
||||||
** Global State
|
** Global State
|
||||||
** See Copyright Notice in lua.h
|
** See Copyright Notice in lua.h
|
||||||
*/
|
*/
|
||||||
@ -32,9 +32,9 @@
|
|||||||
** when traversing the respective threads, but the thread may already be
|
** when traversing the respective threads, but the thread may already be
|
||||||
** dead, while the upvalue is still accessible through closures.)
|
** dead, while the upvalue is still accessible through closures.)
|
||||||
**
|
**
|
||||||
** Userdata with finalizers are kept in the list g->udgc.
|
** Objects with finalizers are kept in the list g->finobj.
|
||||||
**
|
**
|
||||||
** The list g->tobefnz links all userdata being finalized.
|
** The list g->tobefnz links all objects being finalized.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ typedef struct global_State {
|
|||||||
lu_byte gckind; /* kind of GC running */
|
lu_byte gckind; /* kind of GC running */
|
||||||
int sweepstrgc; /* position of sweep in `strt' */
|
int sweepstrgc; /* position of sweep in `strt' */
|
||||||
GCObject *allgc; /* list of all collectable objects */
|
GCObject *allgc; /* list of all collectable objects */
|
||||||
GCObject *udgc; /* list of collectable userdata with finalizers */
|
GCObject *finobj; /* list of collectable objects with finalizers */
|
||||||
GCObject **sweepgc; /* current position of sweep */
|
GCObject **sweepgc; /* current position of sweep */
|
||||||
GCObject *gray; /* list of gray objects */
|
GCObject *gray; /* list of gray objects */
|
||||||
GCObject *grayagain; /* list of objects to be traversed atomically */
|
GCObject *grayagain; /* list of objects to be traversed atomically */
|
||||||
|
21
ltests.c
21
ltests.c
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
** $Id: ltests.c,v 2.112 2010/07/28 15:51:59 roberto Exp roberto $
|
** $Id: ltests.c,v 2.113 2010/11/16 17:43:29 roberto Exp roberto $
|
||||||
** Internal Module for Debugging of the Lua Implementation
|
** Internal Module for Debugging of the Lua Implementation
|
||||||
** See Copyright Notice in lua.h
|
** See Copyright Notice in lua.h
|
||||||
*/
|
*/
|
||||||
@ -429,20 +429,21 @@ int lua_checkmemory (lua_State *L) {
|
|||||||
checkobject(g, o);
|
checkobject(g, o);
|
||||||
lua_assert(!testbit(o->gch.marked, SEPARATED));
|
lua_assert(!testbit(o->gch.marked, SEPARATED));
|
||||||
}
|
}
|
||||||
/* check 'udgc' list */
|
/* check 'finobj' list */
|
||||||
checkold(g, g->udgc);
|
checkold(g, g->finobj);
|
||||||
for (o = g->udgc; o != NULL; o = gch(o)->next) {
|
for (o = g->finobj; o != NULL; o = gch(o)->next) {
|
||||||
lua_assert(gch(o)->tt == LUA_TUSERDATA &&
|
lua_assert(!isdead(g, o) && testbit(o->gch.marked, SEPARATED));
|
||||||
!isdead(g, o) &&
|
lua_assert(gch(o)->tt == LUA_TUSERDATA ||
|
||||||
testbit(o->gch.marked, SEPARATED));
|
gch(o)->tt == LUA_TTABLE);
|
||||||
checkobject(g, o);
|
checkobject(g, o);
|
||||||
}
|
}
|
||||||
/* check 'tobefnz' list */
|
/* check 'tobefnz' list */
|
||||||
checkold(g, g->tobefnz);
|
checkold(g, g->tobefnz);
|
||||||
for (o = g->tobefnz; o != NULL; o = gch(o)->next) {
|
for (o = g->tobefnz; o != NULL; o = gch(o)->next) {
|
||||||
lua_assert(gch(o)->tt == LUA_TUSERDATA);
|
lua_assert(!iswhite(o));
|
||||||
lua_assert(isblack(o));
|
lua_assert(!isdead(g, o) && testbit(o->gch.marked, SEPARATED));
|
||||||
lua_assert(testbit(o->gch.marked, SEPARATED));
|
lua_assert(gch(o)->tt == LUA_TUSERDATA ||
|
||||||
|
gch(o)->tt == LUA_TTABLE);
|
||||||
}
|
}
|
||||||
/* check 'uvhead' list */
|
/* check 'uvhead' list */
|
||||||
for (uv = g->uvhead.u.l.next; uv != &g->uvhead; uv = uv->u.l.next) {
|
for (uv = g->uvhead.u.l.next; uv != &g->uvhead; uv = uv->u.l.next) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user