Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/VM/src/lgcdebug.cpp
2725 views
1
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
2
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
3
#include "lgc.h"
4
5
#include "lfunc.h"
6
#include "lmem.h"
7
#include "lobject.h"
8
#include "lstate.h"
9
#include "lstring.h"
10
#include "ltable.h"
11
#include "ludata.h"
12
#include "lbuffer.h"
13
14
#include <string.h>
15
#include <stdio.h>
16
17
static void validateobjref(global_State* g, GCObject* f, GCObject* t)
18
{
19
LUAU_ASSERT(!isdead(g, t));
20
21
if (keepinvariant(g))
22
{
23
// basic incremental invariant: black can't point to white
24
LUAU_ASSERT(!(isblack(f) && iswhite(t)));
25
}
26
}
27
28
static void validateref(global_State* g, GCObject* f, TValue* v)
29
{
30
if (iscollectable(v))
31
{
32
LUAU_ASSERT(ttype(v) == gcvalue(v)->gch.tt);
33
validateobjref(g, f, gcvalue(v));
34
}
35
}
36
37
static void validatetable(global_State* g, LuaTable* h)
38
{
39
int sizenode = 1 << h->lsizenode;
40
41
LUAU_ASSERT(h->lastfree <= sizenode);
42
43
if (h->metatable)
44
validateobjref(g, obj2gco(h), obj2gco(h->metatable));
45
46
for (int i = 0; i < h->sizearray; ++i)
47
validateref(g, obj2gco(h), &h->array[i]);
48
49
for (int i = 0; i < sizenode; ++i)
50
{
51
LuaNode* n = &h->node[i];
52
53
LUAU_ASSERT(ttype(gkey(n)) != LUA_TDEADKEY || ttisnil(gval(n)));
54
LUAU_ASSERT(i + gnext(n) >= 0 && i + gnext(n) < sizenode);
55
56
if (!ttisnil(gval(n)))
57
{
58
TValue k = {};
59
k.tt = gkey(n)->tt;
60
k.value = gkey(n)->value;
61
62
validateref(g, obj2gco(h), &k);
63
validateref(g, obj2gco(h), gval(n));
64
}
65
}
66
}
67
68
static void validateclosure(global_State* g, Closure* cl)
69
{
70
validateobjref(g, obj2gco(cl), obj2gco(cl->env));
71
72
if (cl->isC)
73
{
74
for (int i = 0; i < cl->nupvalues; ++i)
75
validateref(g, obj2gco(cl), &cl->c.upvals[i]);
76
}
77
else
78
{
79
LUAU_ASSERT(cl->nupvalues == cl->l.p->nups);
80
81
validateobjref(g, obj2gco(cl), obj2gco(cl->l.p));
82
83
for (int i = 0; i < cl->nupvalues; ++i)
84
validateref(g, obj2gco(cl), &cl->l.uprefs[i]);
85
}
86
}
87
88
static void validatestack(global_State* g, lua_State* l)
89
{
90
validateobjref(g, obj2gco(l), obj2gco(l->gt));
91
92
for (CallInfo* ci = l->base_ci; ci <= l->ci; ++ci)
93
{
94
LUAU_ASSERT(l->stack <= ci->base);
95
LUAU_ASSERT(ci->func <= ci->base && ci->base <= ci->top);
96
LUAU_ASSERT(ci->top <= l->stack_last);
97
}
98
99
// note: stack refs can violate gc invariant so we only check for liveness
100
for (StkId o = l->stack; o < l->top; ++o)
101
checkliveness(g, o);
102
103
if (l->namecall)
104
validateobjref(g, obj2gco(l), obj2gco(l->namecall));
105
106
for (UpVal* uv = l->openupval; uv; uv = uv->u.open.threadnext)
107
{
108
LUAU_ASSERT(uv->tt == LUA_TUPVAL);
109
LUAU_ASSERT(upisopen(uv));
110
LUAU_ASSERT(uv->u.open.next->u.open.prev == uv && uv->u.open.prev->u.open.next == uv);
111
LUAU_ASSERT(!isblack(obj2gco(uv))); // open upvalues are never black
112
}
113
}
114
115
static void validateproto(global_State* g, Proto* f)
116
{
117
if (f->source)
118
validateobjref(g, obj2gco(f), obj2gco(f->source));
119
120
if (f->debugname)
121
validateobjref(g, obj2gco(f), obj2gco(f->debugname));
122
123
for (int i = 0; i < f->sizek; ++i)
124
validateref(g, obj2gco(f), &f->k[i]);
125
126
for (int i = 0; i < f->sizeupvalues; ++i)
127
if (f->upvalues[i])
128
validateobjref(g, obj2gco(f), obj2gco(f->upvalues[i]));
129
130
for (int i = 0; i < f->sizep; ++i)
131
if (f->p[i])
132
validateobjref(g, obj2gco(f), obj2gco(f->p[i]));
133
134
for (int i = 0; i < f->sizelocvars; i++)
135
if (f->locvars[i].varname)
136
validateobjref(g, obj2gco(f), obj2gco(f->locvars[i].varname));
137
}
138
139
static void validateobj(global_State* g, GCObject* o)
140
{
141
// dead objects can only occur during sweep
142
if (isdead(g, o))
143
{
144
LUAU_ASSERT(g->gcstate == GCSsweep);
145
return;
146
}
147
148
switch (o->gch.tt)
149
{
150
case LUA_TSTRING:
151
break;
152
153
case LUA_TTABLE:
154
validatetable(g, gco2h(o));
155
break;
156
157
case LUA_TFUNCTION:
158
validateclosure(g, gco2cl(o));
159
break;
160
161
case LUA_TUSERDATA:
162
if (gco2u(o)->metatable)
163
validateobjref(g, o, obj2gco(gco2u(o)->metatable));
164
break;
165
166
case LUA_TTHREAD:
167
validatestack(g, gco2th(o));
168
break;
169
170
case LUA_TBUFFER:
171
break;
172
173
case LUA_TPROTO:
174
validateproto(g, gco2p(o));
175
break;
176
177
case LUA_TUPVAL:
178
validateref(g, o, gco2uv(o)->v);
179
break;
180
181
default:
182
LUAU_ASSERT(!"unexpected object type");
183
}
184
}
185
186
static void validategraylist(global_State* g, GCObject* o)
187
{
188
if (!keepinvariant(g))
189
return;
190
191
while (o)
192
{
193
LUAU_ASSERT(isgray(o));
194
195
switch (o->gch.tt)
196
{
197
case LUA_TTABLE:
198
o = gco2h(o)->gclist;
199
break;
200
case LUA_TFUNCTION:
201
o = gco2cl(o)->gclist;
202
break;
203
case LUA_TTHREAD:
204
o = gco2th(o)->gclist;
205
break;
206
case LUA_TPROTO:
207
o = gco2p(o)->gclist;
208
break;
209
default:
210
LUAU_ASSERT(!"unknown object in gray list");
211
return;
212
}
213
}
214
}
215
216
static bool validategco(void* context, lua_Page* page, GCObject* gco)
217
{
218
lua_State* L = (lua_State*)context;
219
global_State* g = L->global;
220
221
validateobj(g, gco);
222
return false;
223
}
224
225
void luaC_validate(lua_State* L)
226
{
227
global_State* g = L->global;
228
229
LUAU_ASSERT(!isdead(g, obj2gco(g->mainthread)));
230
checkliveness(g, &g->registry);
231
232
for (int i = 0; i < LUA_T_COUNT; ++i)
233
if (g->mt[i])
234
LUAU_ASSERT(!isdead(g, obj2gco(g->mt[i])));
235
236
validategraylist(g, g->weak);
237
validategraylist(g, g->gray);
238
validategraylist(g, g->grayagain);
239
240
validategco(L, NULL, obj2gco(g->mainthread));
241
242
luaM_visitgco(L, L, validategco);
243
244
for (UpVal* uv = g->uvhead.u.open.next; uv != &g->uvhead; uv = uv->u.open.next)
245
{
246
LUAU_ASSERT(uv->tt == LUA_TUPVAL);
247
LUAU_ASSERT(upisopen(uv));
248
LUAU_ASSERT(uv->u.open.next->u.open.prev == uv && uv->u.open.prev->u.open.next == uv);
249
LUAU_ASSERT(!isblack(obj2gco(uv))); // open upvalues are never black
250
}
251
}
252
253
inline bool safejson(char ch)
254
{
255
return unsigned(ch) < 128 && ch >= 32 && ch != '\\' && ch != '\"';
256
}
257
258
static void dumpref(FILE* f, GCObject* o)
259
{
260
fprintf(f, "\"%p\"", o);
261
}
262
263
static void dumprefs(FILE* f, TValue* data, size_t size)
264
{
265
bool first = true;
266
267
for (size_t i = 0; i < size; ++i)
268
{
269
if (iscollectable(&data[i]))
270
{
271
if (!first)
272
fputc(',', f);
273
first = false;
274
275
dumpref(f, gcvalue(&data[i]));
276
}
277
}
278
}
279
280
static void dumpstringdata(FILE* f, const char* data, size_t len)
281
{
282
for (size_t i = 0; i < len; ++i)
283
fputc(safejson(data[i]) ? data[i] : '?', f);
284
}
285
286
static void dumpstring(FILE* f, TString* ts)
287
{
288
fprintf(f, "{\"type\":\"string\",\"cat\":%d,\"size\":%d,\"data\":\"", ts->memcat, int(sizestring(ts->len)));
289
dumpstringdata(f, ts->data, ts->len);
290
fprintf(f, "\"}");
291
}
292
293
static void dumptable(FILE* f, LuaTable* h)
294
{
295
size_t size = sizeof(LuaTable) + (h->node == &luaH_dummynode ? 0 : sizenode(h) * sizeof(LuaNode)) + h->sizearray * sizeof(TValue);
296
297
fprintf(f, "{\"type\":\"table\",\"cat\":%d,\"size\":%d", h->memcat, int(size));
298
299
if (h->node != &luaH_dummynode)
300
{
301
fprintf(f, ",\"pairs\":[");
302
303
bool first = true;
304
305
for (int i = 0; i < sizenode(h); ++i)
306
{
307
const LuaNode& n = h->node[i];
308
309
if (!ttisnil(&n.val) && (iscollectable(&n.key) || iscollectable(&n.val)))
310
{
311
if (!first)
312
fputc(',', f);
313
first = false;
314
315
if (iscollectable(&n.key))
316
dumpref(f, gcvalue(&n.key));
317
else
318
fprintf(f, "null");
319
320
fputc(',', f);
321
322
if (iscollectable(&n.val))
323
dumpref(f, gcvalue(&n.val));
324
else
325
fprintf(f, "null");
326
}
327
}
328
329
fprintf(f, "]");
330
}
331
if (h->sizearray)
332
{
333
fprintf(f, ",\"array\":[");
334
dumprefs(f, h->array, h->sizearray);
335
fprintf(f, "]");
336
}
337
if (h->metatable)
338
{
339
fprintf(f, ",\"metatable\":");
340
dumpref(f, obj2gco(h->metatable));
341
}
342
fprintf(f, "}");
343
}
344
345
static void dumpclosure(FILE* f, Closure* cl)
346
{
347
fprintf(
348
f, "{\"type\":\"function\",\"cat\":%d,\"size\":%d", cl->memcat, cl->isC ? int(sizeCclosure(cl->nupvalues)) : int(sizeLclosure(cl->nupvalues))
349
);
350
351
fprintf(f, ",\"env\":");
352
dumpref(f, obj2gco(cl->env));
353
354
if (cl->isC)
355
{
356
if (cl->c.debugname)
357
fprintf(f, ",\"name\":\"%s\"", cl->c.debugname + 0);
358
359
if (cl->nupvalues)
360
{
361
fprintf(f, ",\"upvalues\":[");
362
dumprefs(f, cl->c.upvals, cl->nupvalues);
363
fprintf(f, "]");
364
}
365
}
366
else
367
{
368
if (cl->l.p->debugname)
369
fprintf(f, ",\"name\":\"%s\"", getstr(cl->l.p->debugname));
370
371
fprintf(f, ",\"proto\":");
372
dumpref(f, obj2gco(cl->l.p));
373
if (cl->nupvalues)
374
{
375
fprintf(f, ",\"upvalues\":[");
376
dumprefs(f, cl->l.uprefs, cl->nupvalues);
377
fprintf(f, "]");
378
}
379
}
380
fprintf(f, "}");
381
}
382
383
static void dumpudata(FILE* f, Udata* u)
384
{
385
fprintf(f, "{\"type\":\"userdata\",\"cat\":%d,\"size\":%d,\"tag\":%d", u->memcat, int(sizeudata(u->len)), u->tag);
386
387
if (u->metatable)
388
{
389
fprintf(f, ",\"metatable\":");
390
dumpref(f, obj2gco(u->metatable));
391
}
392
fprintf(f, "}");
393
}
394
395
static void dumpthread(FILE* f, lua_State* th)
396
{
397
size_t size = sizeof(lua_State) + sizeof(TValue) * th->stacksize + sizeof(CallInfo) * th->size_ci;
398
399
fprintf(f, "{\"type\":\"thread\",\"cat\":%d,\"size\":%d", th->memcat, int(size));
400
401
fprintf(f, ",\"env\":");
402
dumpref(f, obj2gco(th->gt));
403
404
Closure* tcl = 0;
405
for (CallInfo* ci = th->base_ci; ci <= th->ci; ++ci)
406
{
407
if (ttisfunction(ci->func))
408
{
409
tcl = clvalue(ci->func);
410
break;
411
}
412
}
413
414
if (tcl && !tcl->isC && tcl->l.p->source)
415
{
416
Proto* p = tcl->l.p;
417
418
fprintf(f, ",\"source\":\"");
419
dumpstringdata(f, p->source->data, p->source->len);
420
fprintf(f, "\",\"line\":%d", p->linedefined);
421
}
422
423
if (th->top > th->stack)
424
{
425
fprintf(f, ",\"stack\":[");
426
dumprefs(f, th->stack, th->top - th->stack);
427
fprintf(f, "]");
428
429
CallInfo* ci = th->base_ci;
430
bool first = true;
431
432
fprintf(f, ",\"stacknames\":[");
433
for (StkId v = th->stack; v < th->top; ++v)
434
{
435
if (!iscollectable(v))
436
continue;
437
438
while (ci < th->ci && v >= (ci + 1)->func)
439
ci++;
440
441
if (!first)
442
fputc(',', f);
443
first = false;
444
445
if (v == ci->func)
446
{
447
Closure* cl = ci_func(ci);
448
449
if (cl->isC)
450
{
451
fprintf(f, "\"frame:%s\"", cl->c.debugname ? cl->c.debugname : "[C]");
452
}
453
else
454
{
455
Proto* p = cl->l.p;
456
fprintf(f, "\"frame:");
457
if (p->source)
458
dumpstringdata(f, p->source->data, p->source->len);
459
fprintf(f, ":%d:%s\"", p->linedefined, p->debugname ? getstr(p->debugname) : "");
460
}
461
}
462
else if (isLua(ci))
463
{
464
Proto* p = ci_func(ci)->l.p;
465
int pc = pcRel(ci->savedpc, p);
466
const LocVar* var = luaF_findlocal(p, int(v - ci->base), pc);
467
468
if (var && var->varname)
469
fprintf(f, "\"%s\"", getstr(var->varname));
470
else
471
fprintf(f, "null");
472
}
473
else
474
fprintf(f, "null");
475
}
476
fprintf(f, "]");
477
}
478
fprintf(f, "}");
479
}
480
481
static void dumpbuffer(FILE* f, Buffer* b)
482
{
483
fprintf(f, "{\"type\":\"buffer\",\"cat\":%d,\"size\":%d}", b->memcat, int(sizebuffer(b->len)));
484
}
485
486
static void dumpproto(FILE* f, Proto* p)
487
{
488
size_t size = sizeof(Proto) + sizeof(Instruction) * p->sizecode + sizeof(Proto*) * p->sizep + sizeof(TValue) * p->sizek + p->sizelineinfo +
489
sizeof(LocVar) * p->sizelocvars + sizeof(TString*) * p->sizeupvalues;
490
491
fprintf(f, "{\"type\":\"proto\",\"cat\":%d,\"size\":%d", p->memcat, int(size));
492
493
if (p->source)
494
{
495
fprintf(f, ",\"source\":\"");
496
dumpstringdata(f, p->source->data, p->source->len);
497
fprintf(f, "\",\"line\":%d", p->abslineinfo ? p->abslineinfo[0] : 0);
498
}
499
500
if (p->sizek)
501
{
502
fprintf(f, ",\"constants\":[");
503
dumprefs(f, p->k, p->sizek);
504
fprintf(f, "]");
505
}
506
507
if (p->sizep)
508
{
509
fprintf(f, ",\"protos\":[");
510
for (int i = 0; i < p->sizep; ++i)
511
{
512
if (i != 0)
513
fputc(',', f);
514
dumpref(f, obj2gco(p->p[i]));
515
}
516
fprintf(f, "]");
517
}
518
519
fprintf(f, "}");
520
}
521
522
static void dumpupval(FILE* f, UpVal* uv)
523
{
524
fprintf(f, "{\"type\":\"upvalue\",\"cat\":%d,\"size\":%d,\"open\":%s", uv->memcat, int(sizeof(UpVal)), upisopen(uv) ? "true" : "false");
525
526
if (iscollectable(uv->v))
527
{
528
fprintf(f, ",\"object\":");
529
dumpref(f, gcvalue(uv->v));
530
}
531
532
fprintf(f, "}");
533
}
534
535
static void dumpobj(FILE* f, GCObject* o)
536
{
537
switch (o->gch.tt)
538
{
539
case LUA_TSTRING:
540
return dumpstring(f, gco2ts(o));
541
542
case LUA_TTABLE:
543
return dumptable(f, gco2h(o));
544
545
case LUA_TFUNCTION:
546
return dumpclosure(f, gco2cl(o));
547
548
case LUA_TUSERDATA:
549
return dumpudata(f, gco2u(o));
550
551
case LUA_TTHREAD:
552
return dumpthread(f, gco2th(o));
553
554
case LUA_TBUFFER:
555
return dumpbuffer(f, gco2buf(o));
556
557
case LUA_TPROTO:
558
return dumpproto(f, gco2p(o));
559
560
case LUA_TUPVAL:
561
return dumpupval(f, gco2uv(o));
562
563
default:
564
LUAU_ASSERT(0);
565
}
566
}
567
568
static bool dumpgco(void* context, lua_Page* page, GCObject* gco)
569
{
570
FILE* f = (FILE*)context;
571
572
dumpref(f, gco);
573
fputc(':', f);
574
dumpobj(f, gco);
575
fputc(',', f);
576
fputc('\n', f);
577
578
return false;
579
}
580
581
void luaC_dump(lua_State* L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat))
582
{
583
global_State* g = L->global;
584
FILE* f = static_cast<FILE*>(file);
585
586
fprintf(f, "{\"objects\":{\n");
587
588
dumpgco(f, NULL, obj2gco(g->mainthread));
589
590
luaM_visitgco(L, f, dumpgco);
591
592
fprintf(f, "\"0\":{\"type\":\"userdata\",\"cat\":0,\"size\":0}\n"); // to avoid issues with trailing ,
593
fprintf(f, "},\"roots\":{\n");
594
fprintf(f, "\"mainthread\":");
595
dumpref(f, obj2gco(g->mainthread));
596
fprintf(f, ",\"registry\":");
597
dumpref(f, gcvalue(&g->registry));
598
599
fprintf(f, "},\"stats\":{\n");
600
601
fprintf(f, "\"size\":%d,\n", int(g->totalbytes));
602
603
fprintf(f, "\"categories\":{\n");
604
for (int i = 0; i < LUA_MEMORY_CATEGORIES; i++)
605
{
606
if (size_t bytes = g->memcatbytes[i])
607
{
608
if (categoryName)
609
fprintf(f, "\"%d\":{\"name\":\"%s\", \"size\":%d},\n", i, categoryName(L, i), int(bytes));
610
else
611
fprintf(f, "\"%d\":{\"size\":%d},\n", i, int(bytes));
612
}
613
}
614
fprintf(f, "\"none\":{}\n"); // to avoid issues with trailing ,
615
fprintf(f, "}\n");
616
fprintf(f, "}}\n");
617
}
618
619
struct EnumContext
620
{
621
lua_State* L;
622
void* context;
623
void (*node)(void* context, void* ptr, uint8_t tt, uint8_t memcat, size_t size, const char* name);
624
void (*edge)(void* context, void* from, void* to, const char* name);
625
};
626
627
static void* enumtopointer(GCObject* gco)
628
{
629
// To match lua_topointer, userdata pointer is represented as a pointer to internal data
630
return gco->gch.tt == LUA_TUSERDATA ? (void*)gco2u(gco)->data : (void*)gco;
631
}
632
633
static void enumnode(EnumContext* ctx, GCObject* gco, size_t size, const char* objname)
634
{
635
ctx->node(ctx->context, enumtopointer(gco), gco->gch.tt, gco->gch.memcat, size, objname);
636
}
637
638
static void enumedge(EnumContext* ctx, GCObject* from, GCObject* to, const char* edgename)
639
{
640
ctx->edge(ctx->context, enumtopointer(from), enumtopointer(to), edgename);
641
}
642
643
static void enumedges(EnumContext* ctx, GCObject* from, TValue* data, size_t size, const char* edgename)
644
{
645
for (size_t i = 0; i < size; ++i)
646
{
647
if (iscollectable(&data[i]))
648
enumedge(ctx, from, gcvalue(&data[i]), edgename);
649
}
650
}
651
652
static void enumstring(EnumContext* ctx, TString* ts)
653
{
654
enumnode(ctx, obj2gco(ts), sizestring(ts->len), NULL);
655
}
656
657
static void enumtable(EnumContext* ctx, LuaTable* h)
658
{
659
size_t size = sizeof(LuaTable) + (h->node == &luaH_dummynode ? 0 : sizenode(h) * sizeof(LuaNode)) + h->sizearray * sizeof(TValue);
660
661
// Provide a name for a special registry table
662
enumnode(ctx, obj2gco(h), size, h == hvalue(registry(ctx->L)) ? "registry" : NULL);
663
664
if (h->node != &luaH_dummynode)
665
{
666
bool weakkey = false;
667
bool weakvalue = false;
668
669
if (const TValue* mode = gfasttm(ctx->L->global, h->metatable, TM_MODE))
670
{
671
if (ttisstring(mode))
672
{
673
weakkey = strchr(svalue(mode), 'k') != NULL;
674
weakvalue = strchr(svalue(mode), 'v') != NULL;
675
}
676
}
677
678
for (int i = 0; i < sizenode(h); ++i)
679
{
680
const LuaNode& n = h->node[i];
681
682
if (!ttisnil(&n.val) && (iscollectable(&n.key) || iscollectable(&n.val)))
683
{
684
if (!weakkey && iscollectable(&n.key))
685
enumedge(ctx, obj2gco(h), gcvalue(&n.key), "[key]");
686
687
if (!weakvalue && iscollectable(&n.val))
688
{
689
if (ttisstring(&n.key))
690
{
691
enumedge(ctx, obj2gco(h), gcvalue(&n.val), svalue(&n.key));
692
}
693
else if (ttisnumber(&n.key))
694
{
695
char buf[32];
696
snprintf(buf, sizeof(buf), "%.14g", nvalue(&n.key));
697
enumedge(ctx, obj2gco(h), gcvalue(&n.val), buf);
698
}
699
else
700
{
701
char buf[32];
702
snprintf(buf, sizeof(buf), "[%s]", getstr(ctx->L->global->ttname[n.key.tt]));
703
enumedge(ctx, obj2gco(h), gcvalue(&n.val), buf);
704
}
705
}
706
}
707
}
708
}
709
710
if (h->sizearray)
711
enumedges(ctx, obj2gco(h), h->array, h->sizearray, "array");
712
713
if (h->metatable)
714
enumedge(ctx, obj2gco(h), obj2gco(h->metatable), "metatable");
715
}
716
717
static void enumclosure(EnumContext* ctx, Closure* cl)
718
{
719
if (cl->isC)
720
{
721
enumnode(ctx, obj2gco(cl), sizeCclosure(cl->nupvalues), cl->c.debugname);
722
}
723
else
724
{
725
Proto* p = cl->l.p;
726
727
char buf[LUA_IDSIZE];
728
729
if (p->source)
730
snprintf(buf, sizeof(buf), "%s:%d %s", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined, getstr(p->source));
731
else
732
snprintf(buf, sizeof(buf), "%s:%d", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined);
733
734
enumnode(ctx, obj2gco(cl), sizeLclosure(cl->nupvalues), buf);
735
}
736
737
enumedge(ctx, obj2gco(cl), obj2gco(cl->env), "env");
738
739
if (cl->isC)
740
{
741
if (cl->nupvalues)
742
enumedges(ctx, obj2gco(cl), cl->c.upvals, cl->nupvalues, "upvalue");
743
}
744
else
745
{
746
enumedge(ctx, obj2gco(cl), obj2gco(cl->l.p), "proto");
747
748
if (cl->nupvalues)
749
enumedges(ctx, obj2gco(cl), cl->l.uprefs, cl->nupvalues, "upvalue");
750
}
751
}
752
753
static void enumudata(EnumContext* ctx, Udata* u)
754
{
755
const char* name = NULL;
756
757
if (LuaTable* h = u->metatable)
758
{
759
if (h->node != &luaH_dummynode)
760
{
761
for (int i = 0; i < sizenode(h); ++i)
762
{
763
const LuaNode& n = h->node[i];
764
765
if (ttisstring(&n.key) && ttisstring(&n.val) && strcmp(svalue(&n.key), "__type") == 0)
766
{
767
name = svalue(&n.val);
768
break;
769
}
770
}
771
}
772
}
773
774
enumnode(ctx, obj2gco(u), sizeudata(u->len), name);
775
776
if (u->metatable)
777
enumedge(ctx, obj2gco(u), obj2gco(u->metatable), "metatable");
778
}
779
780
static void enumthread(EnumContext* ctx, lua_State* th)
781
{
782
size_t size = sizeof(lua_State) + sizeof(TValue) * th->stacksize + sizeof(CallInfo) * th->size_ci;
783
784
Closure* tcl = NULL;
785
for (CallInfo* ci = th->base_ci; ci <= th->ci; ++ci)
786
{
787
if (ttisfunction(ci->func))
788
{
789
tcl = clvalue(ci->func);
790
break;
791
}
792
}
793
794
if (tcl && !tcl->isC && tcl->l.p->source)
795
{
796
Proto* p = tcl->l.p;
797
798
char buf[LUA_IDSIZE];
799
800
if (p->source)
801
snprintf(buf, sizeof(buf), "thread at %s:%d %s", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined, getstr(p->source));
802
else
803
snprintf(buf, sizeof(buf), "thread at %s:%d", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined);
804
805
enumnode(ctx, obj2gco(th), size, buf);
806
}
807
else
808
{
809
enumnode(ctx, obj2gco(th), size, NULL);
810
}
811
812
enumedge(ctx, obj2gco(th), obj2gco(th->gt), "globals");
813
814
if (th->top > th->stack)
815
enumedges(ctx, obj2gco(th), th->stack, th->top - th->stack, "stack");
816
}
817
818
static void enumbuffer(EnumContext* ctx, Buffer* b)
819
{
820
enumnode(ctx, obj2gco(b), sizebuffer(b->len), NULL);
821
}
822
823
static void enumproto(EnumContext* ctx, Proto* p)
824
{
825
size_t size = sizeof(Proto) + sizeof(Instruction) * p->sizecode + sizeof(Proto*) * p->sizep + sizeof(TValue) * p->sizek + p->sizelineinfo +
826
sizeof(LocVar) * p->sizelocvars + sizeof(TString*) * p->sizeupvalues;
827
828
if (p->execdata && ctx->L->global->ecb.getmemorysize)
829
{
830
size_t nativesize = ctx->L->global->ecb.getmemorysize(ctx->L, p);
831
832
ctx->node(ctx->context, p->execdata, uint8_t(LUA_TNONE), p->memcat, nativesize, NULL);
833
ctx->edge(ctx->context, enumtopointer(obj2gco(p)), p->execdata, "[native]");
834
}
835
836
char buf[LUA_IDSIZE];
837
838
if (p->source)
839
snprintf(buf, sizeof(buf), "proto %s:%d %s", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined, getstr(p->source));
840
else
841
snprintf(buf, sizeof(buf), "proto %s:%d", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined);
842
843
enumnode(ctx, obj2gco(p), size, buf);
844
845
if (p->sizek)
846
enumedges(ctx, obj2gco(p), p->k, p->sizek, "constants");
847
848
for (int i = 0; i < p->sizep; ++i)
849
enumedge(ctx, obj2gco(p), obj2gco(p->p[i]), "protos");
850
}
851
852
static void enumupval(EnumContext* ctx, UpVal* uv)
853
{
854
enumnode(ctx, obj2gco(uv), sizeof(UpVal), NULL);
855
856
if (iscollectable(uv->v))
857
enumedge(ctx, obj2gco(uv), gcvalue(uv->v), "value");
858
}
859
860
static void enumobj(EnumContext* ctx, GCObject* o)
861
{
862
switch (o->gch.tt)
863
{
864
case LUA_TSTRING:
865
return enumstring(ctx, gco2ts(o));
866
867
case LUA_TTABLE:
868
return enumtable(ctx, gco2h(o));
869
870
case LUA_TFUNCTION:
871
return enumclosure(ctx, gco2cl(o));
872
873
case LUA_TUSERDATA:
874
return enumudata(ctx, gco2u(o));
875
876
case LUA_TTHREAD:
877
return enumthread(ctx, gco2th(o));
878
879
case LUA_TBUFFER:
880
return enumbuffer(ctx, gco2buf(o));
881
882
case LUA_TPROTO:
883
return enumproto(ctx, gco2p(o));
884
885
case LUA_TUPVAL:
886
return enumupval(ctx, gco2uv(o));
887
888
default:
889
LUAU_ASSERT(!"Unknown object tag");
890
}
891
}
892
893
static bool enumgco(void* context, lua_Page* page, GCObject* gco)
894
{
895
enumobj((EnumContext*)context, gco);
896
return false;
897
}
898
899
void luaC_enumheap(
900
lua_State* L,
901
void* context,
902
void (*node)(void* context, void* ptr, uint8_t tt, uint8_t memcat, size_t size, const char* name),
903
void (*edge)(void* context, void* from, void* to, const char* name)
904
)
905
{
906
global_State* g = L->global;
907
908
EnumContext ctx;
909
ctx.L = L;
910
ctx.context = context;
911
ctx.node = node;
912
ctx.edge = edge;
913
914
enumgco(&ctx, NULL, obj2gco(g->mainthread));
915
916
luaM_visitgco(L, &ctx, enumgco);
917
}
918
919