Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/tests/Conformance.test.cpp
2723 views
1
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
2
#include "Luau/Common.h"
3
#include "Luau/Type.h"
4
#include "lua.h"
5
#include "lualib.h"
6
#include "luacode.h"
7
#include "luacodegen.h"
8
9
#include "Luau/BuiltinDefinitions.h"
10
#include "Luau/DenseHash.h"
11
#include "Luau/ModuleResolver.h"
12
#include "Luau/TypeInfer.h"
13
#include "Luau/BytecodeBuilder.h"
14
#include "Luau/Frontend.h"
15
#include "Luau/Compiler.h"
16
#include "Luau/CodeGen.h"
17
#include "Luau/BytecodeSummary.h"
18
19
#include "doctest.h"
20
#include "ScopedFlags.h"
21
#include "ConformanceIrHooks.h"
22
23
#include <cstdlib>
24
#include <fstream>
25
#include <string>
26
#include <vector>
27
#include <math.h>
28
29
extern bool verbose;
30
extern bool codegen;
31
extern int optimizationLevel;
32
33
// internal functions, declared in lgc.h - not exposed via lua.h
34
void luaC_fullgc(lua_State* L);
35
void luaC_validate(lua_State* L);
36
37
// internal functions, declared in lvm.h - not exposed via lua.h
38
void luau_callhook(lua_State* L, lua_Hook hook, void* userdata);
39
40
LUAU_FASTFLAG(DebugLuauAbortingChecks)
41
LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
42
LUAU_FASTFLAG(LuauStacklessPcall)
43
LUAU_FASTFLAG(LuauIntegerLibrary)
44
LUAU_FASTFLAG(LuauIntegerType)
45
LUAU_FASTFLAG(DebugLuauForceOldSolver)
46
LUAU_FASTFLAG(LuauNewMathConstantsRuntime)
47
LUAU_FASTFLAG(LuauCompileStringInterpWithZero)
48
49
static lua_CompileOptions defaultOptions()
50
{
51
lua_CompileOptions copts = {};
52
copts.optimizationLevel = optimizationLevel;
53
copts.debugLevel = 1;
54
copts.typeInfoLevel = 1;
55
56
return copts;
57
}
58
59
static Luau::CodeGen::CompilationOptions defaultCodegenOptions()
60
{
61
Luau::CodeGen::CompilationOptions opts = {};
62
opts.flags = Luau::CodeGen::CodeGen_ColdFunctions;
63
return opts;
64
}
65
66
static int lua_collectgarbage(lua_State* L)
67
{
68
static const char* const opts[] = {"stop", "restart", "collect", "count", "isrunning", "step", "setgoal", "setstepmul", "setstepsize", nullptr};
69
static const int optsnum[] = {
70
LUA_GCSTOP, LUA_GCRESTART, LUA_GCCOLLECT, LUA_GCCOUNT, LUA_GCISRUNNING, LUA_GCSTEP, LUA_GCSETGOAL, LUA_GCSETSTEPMUL, LUA_GCSETSTEPSIZE
71
};
72
73
int o = luaL_checkoption(L, 1, "collect", opts);
74
int ex = luaL_optinteger(L, 2, 0);
75
int res = lua_gc(L, optsnum[o], ex);
76
switch (optsnum[o])
77
{
78
case LUA_GCSTEP:
79
case LUA_GCISRUNNING:
80
{
81
lua_pushboolean(L, res);
82
return 1;
83
}
84
default:
85
{
86
lua_pushnumber(L, res);
87
return 1;
88
}
89
}
90
}
91
92
static int lua_loadstring(lua_State* L)
93
{
94
size_t l = 0;
95
const char* s = luaL_checklstring(L, 1, &l);
96
const char* chunkname = luaL_optstring(L, 2, s);
97
98
lua_setsafeenv(L, LUA_ENVIRONINDEX, false);
99
100
size_t bytecodeSize = 0;
101
char* bytecode = luau_compile(s, l, nullptr, &bytecodeSize);
102
int result = luau_load(L, chunkname, bytecode, bytecodeSize, 0);
103
free(bytecode);
104
105
if (result == 0)
106
return 1;
107
108
lua_pushnil(L);
109
lua_insert(L, -2); // put before error message
110
return 2; // return nil plus error message
111
}
112
113
static int lua_vector_dot(lua_State* L)
114
{
115
const float* a = luaL_checkvector(L, 1);
116
const float* b = luaL_checkvector(L, 2);
117
118
lua_pushnumber(L, a[0] * b[0] + a[1] * b[1] + a[2] * b[2]);
119
return 1;
120
}
121
122
static int lua_vector_cross(lua_State* L)
123
{
124
const float* a = luaL_checkvector(L, 1);
125
const float* b = luaL_checkvector(L, 2);
126
127
#if LUA_VECTOR_SIZE == 4
128
lua_pushvector(L, a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0], 0.0f);
129
#else
130
lua_pushvector(L, a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]);
131
#endif
132
133
return 1;
134
}
135
136
static int lua_vector_index(lua_State* L)
137
{
138
const float* v = luaL_checkvector(L, 1);
139
const char* name = luaL_checkstring(L, 2);
140
141
if (strcmp(name, "Magnitude") == 0)
142
{
143
#if LUA_VECTOR_SIZE == 4
144
lua_pushnumber(L, sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2] + v[3] * v[3]));
145
#else
146
lua_pushnumber(L, sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]));
147
#endif
148
return 1;
149
}
150
151
if (strcmp(name, "Unit") == 0)
152
{
153
#if LUA_VECTOR_SIZE == 4
154
float invSqrt = 1.0f / sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2] + v[3] * v[3]);
155
156
lua_pushvector(L, v[0] * invSqrt, v[1] * invSqrt, v[2] * invSqrt, v[3] * invSqrt);
157
#else
158
float invSqrt = 1.0f / sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
159
160
lua_pushvector(L, v[0] * invSqrt, v[1] * invSqrt, v[2] * invSqrt);
161
#endif
162
return 1;
163
}
164
165
if (strcmp(name, "Dot") == 0)
166
{
167
lua_pushcfunction(L, lua_vector_dot, "Dot");
168
return 1;
169
}
170
171
luaL_error(L, "%s is not a valid member of vector", name);
172
}
173
174
static int lua_vector_namecall(lua_State* L)
175
{
176
if (const char* str = lua_namecallatom(L, nullptr))
177
{
178
if (strcmp(str, "Dot") == 0)
179
return lua_vector_dot(L);
180
181
if (strcmp(str, "Cross") == 0)
182
return lua_vector_cross(L);
183
}
184
185
luaL_error(L, "%s is not a valid method of vector", luaL_checkstring(L, 1));
186
}
187
188
int lua_silence(lua_State* L)
189
{
190
return 0;
191
}
192
193
using StateRef = std::unique_ptr<lua_State, void (*)(lua_State*)>;
194
195
static StateRef runConformance(
196
const char* name,
197
void (*setup)(lua_State* L) = nullptr,
198
bool (*yield)(lua_State* L) = nullptr,
199
lua_State* initialLuaState = nullptr,
200
lua_CompileOptions* options = nullptr,
201
bool skipCodegen = false,
202
Luau::CodeGen::CompilationOptions* codegenOptions = nullptr
203
)
204
{
205
#ifdef LUAU_CONFORMANCE_SOURCE_DIR
206
std::string path = LUAU_CONFORMANCE_SOURCE_DIR;
207
path += "/";
208
path += name;
209
#else
210
std::string path = __FILE__;
211
// __FILE__ is not guaranteed to be absolute path because of reproducible BUCK2 builds.
212
if (path.find_last_of("\\/") == std::string::npos)
213
{
214
path = "Client/Luau/tests/conformance";
215
if (const char* envDir = std::getenv("LUAU_CONFORMANCE_SOURCE_DIR"))
216
path = envDir;
217
path += "/";
218
}
219
else
220
{
221
path.erase(path.find_last_of("\\/"));
222
path += "/conformance/";
223
}
224
path += name;
225
#endif
226
227
std::fstream stream(path, std::ios::in | std::ios::binary);
228
INFO(path);
229
if (!stream)
230
{
231
std::string message = "File " + path + " is not found. " +
232
"Make sure you run tests from the root or specify custom directory using LUAU_CONFORMANCE_SOURCE_DIR env variable";
233
throw message;
234
}
235
236
std::string source(std::istreambuf_iterator<char>(stream), {});
237
238
stream.close();
239
240
if (!initialLuaState)
241
initialLuaState = luaL_newstate();
242
StateRef globalState(initialLuaState, lua_close);
243
lua_State* L = globalState.get();
244
245
if (codegen && !skipCodegen && luau_codegen_supported())
246
luau_codegen_create(L);
247
248
luaL_openlibs(L);
249
250
// Register a few global functions for conformance tests
251
std::vector<luaL_Reg> funcs = {
252
{"collectgarbage", lua_collectgarbage},
253
{"loadstring", lua_loadstring},
254
};
255
256
if (!verbose)
257
{
258
funcs.push_back({"print", lua_silence});
259
}
260
261
// "null" terminate the list of functions to register
262
funcs.push_back({nullptr, nullptr});
263
264
lua_pushvalue(L, LUA_GLOBALSINDEX);
265
luaL_register(L, nullptr, funcs.data());
266
lua_pop(L, 1);
267
268
// In some configurations we have a larger C stack consumption which trips some conformance tests
269
#if defined(LUAU_ENABLE_ASAN) || defined(_NOOPT) || defined(_DEBUG)
270
lua_pushboolean(L, true);
271
lua_setglobal(L, "limitedstack");
272
#endif
273
274
// Extra test-specific setup
275
if (setup)
276
setup(L);
277
278
// Protect core libraries and metatables from modification
279
luaL_sandbox(L);
280
281
// Create a new writable global table for current thread
282
luaL_sandboxthread(L);
283
284
// Lua conformance tests treat _G synonymously with getfenv(); for now cater to them
285
lua_pushvalue(L, LUA_GLOBALSINDEX);
286
lua_setfield(L, -1, "_G");
287
288
std::string chunkname = "=" + std::string(name);
289
290
// note: luau_compile supports nullptr options, but we need to customize our defaults to improve test coverage
291
lua_CompileOptions opts = options ? *options : defaultOptions();
292
293
size_t bytecodeSize = 0;
294
char* bytecode = luau_compile(source.data(), source.size(), &opts, &bytecodeSize);
295
int result = luau_load(L, chunkname.c_str(), bytecode, bytecodeSize, 0);
296
free(bytecode);
297
298
Luau::CodeGen::CompilationOptions nativeOpts = codegenOptions ? *codegenOptions : defaultCodegenOptions();
299
300
if (result == 0 && codegen && !skipCodegen && luau_codegen_supported())
301
{
302
Luau::CodeGen::CompilationResult result = Luau::CodeGen::compile(L, -1, nativeOpts);
303
304
CHECK(result.result == Luau::CodeGen::CodeGenCompilationResult::Success);
305
306
if (result.hasErrors())
307
{
308
for (const Luau::CodeGen::ProtoCompilationFailure& protoFailure : result.protoFailures)
309
{
310
switch (protoFailure.result)
311
{
312
case Luau::CodeGen::CodeGenCompilationResult::Success:
313
case Luau::CodeGen::CodeGenCompilationResult::NothingToCompile:
314
case Luau::CodeGen::CodeGenCompilationResult::NotNativeModule:
315
case Luau::CodeGen::CodeGenCompilationResult::CodeGenNotInitialized:
316
case Luau::CodeGen::CodeGenCompilationResult::CodeGenAssemblerFinalizationFailure:
317
case Luau::CodeGen::CodeGenCompilationResult::AllocationFailed:
318
// These cases cannot be the Proto failure reason
319
FAIL("Unexpected main code generation failure result");
320
break;
321
322
case Luau::CodeGen::CodeGenCompilationResult::CodeGenOverflowInstructionLimit:
323
MESSAGE(
324
"Function '",
325
protoFailure.debugname.empty() ? "(anonymous)" : protoFailure.debugname,
326
"':",
327
protoFailure.line,
328
" exceeded total module instruction limit"
329
);
330
break;
331
case Luau::CodeGen::CodeGenCompilationResult::CodeGenOverflowBlockLimit:
332
MESSAGE(
333
"Function '",
334
protoFailure.debugname.empty() ? "(anonymous)" : protoFailure.debugname,
335
"':",
336
protoFailure.line,
337
" exceeded function code block limit"
338
);
339
break;
340
case Luau::CodeGen::CodeGenCompilationResult::CodeGenOverflowBlockInstructionLimit:
341
MESSAGE(
342
"Function '",
343
protoFailure.debugname.empty() ? "(anonymous)" : protoFailure.debugname,
344
"':",
345
protoFailure.line,
346
" exceeded single code block instruction limit"
347
);
348
break;
349
case Luau::CodeGen::CodeGenCompilationResult::CodeGenLoweringFailure:
350
MESSAGE(
351
"Function '",
352
protoFailure.debugname.empty() ? "(anonymous)" : protoFailure.debugname,
353
"':",
354
protoFailure.line,
355
" encountered an internal lowering failure"
356
);
357
break;
358
case Luau::CodeGen::CodeGenCompilationResult::Count:
359
LUAU_ASSERT(false);
360
}
361
}
362
}
363
}
364
365
// Extra test for lowering on both platforms with assembly generation
366
if (luau_codegen_supported())
367
{
368
Luau::CodeGen::AssemblyOptions assemblyOptions;
369
assemblyOptions.compilationOptions = nativeOpts;
370
371
assemblyOptions.includeAssembly = true;
372
assemblyOptions.includeIr = true;
373
assemblyOptions.includeOutlinedCode = true;
374
assemblyOptions.includeIrTypes = true;
375
376
Luau::CodeGen::LoweringStats stats;
377
stats.functionStatsFlags = Luau::CodeGen::FunctionStatsFlags::FunctionStats_Enable;
378
379
assemblyOptions.target = Luau::CodeGen::AssemblyOptions::A64;
380
std::string a64 = Luau::CodeGen::getAssembly(L, -1, assemblyOptions, &stats);
381
CHECK(!a64.empty());
382
383
CHECK(stats.regAllocErrors == 0);
384
CHECK(stats.loweringErrors == 0);
385
386
assemblyOptions.target = Luau::CodeGen::AssemblyOptions::X64_SystemV;
387
std::string x64 = Luau::CodeGen::getAssembly(L, -1, assemblyOptions, &stats);
388
CHECK(!x64.empty());
389
390
CHECK(stats.regAllocErrors == 0);
391
CHECK(stats.loweringErrors == 0);
392
}
393
394
int status = (result == 0) ? lua_resume(L, nullptr, 0) : LUA_ERRSYNTAX;
395
396
while (yield && (status == LUA_YIELD || status == LUA_BREAK))
397
{
398
bool resumeError = yield(L);
399
400
if (resumeError)
401
status = lua_resumeerror(L, nullptr);
402
else
403
status = lua_resume(L, nullptr, 0);
404
}
405
406
luaC_validate(L);
407
408
if (status == 0)
409
{
410
REQUIRE(lua_isstring(L, -1));
411
CHECK(std::string(lua_tostring(L, -1)) == "OK");
412
lua_pop(L, 1);
413
}
414
else
415
{
416
std::string error = (status == LUA_YIELD) ? "thread yielded unexpectedly" : lua_tostring(L, -1);
417
error += "\nstacktrace:\n";
418
error += lua_debugtrace(L);
419
420
FAIL(error);
421
}
422
423
return globalState;
424
}
425
426
static void* limitedRealloc(void* ud, void* ptr, size_t osize, size_t nsize)
427
{
428
if (nsize == 0)
429
{
430
free(ptr);
431
return nullptr;
432
}
433
else if (nsize > 8 * 1024 * 1024)
434
{
435
// For testing purposes return null for large allocations so we can generate errors related to memory allocation failures
436
return nullptr;
437
}
438
else
439
{
440
return realloc(ptr, nsize);
441
}
442
}
443
444
void setupVectorHelpers(lua_State* L)
445
{
446
#if LUA_VECTOR_SIZE == 4
447
lua_pushvector(L, 0.0f, 0.0f, 0.0f, 0.0f);
448
#else
449
lua_pushvector(L, 0.0f, 0.0f, 0.0f);
450
#endif
451
luaL_newmetatable(L, "vector");
452
453
lua_pushstring(L, "__index");
454
lua_pushcfunction(L, lua_vector_index, nullptr);
455
lua_settable(L, -3);
456
457
lua_pushstring(L, "__namecall");
458
lua_pushcfunction(L, lua_vector_namecall, nullptr);
459
lua_settable(L, -3);
460
461
lua_setreadonly(L, -1, true);
462
lua_setmetatable(L, -2);
463
lua_pop(L, 1);
464
}
465
466
Vec2* lua_vec2_push(lua_State* L)
467
{
468
Vec2* data = (Vec2*)lua_newuserdatatagged(L, sizeof(Vec2), kTagVec2);
469
470
lua_getuserdatametatable(L, kTagVec2);
471
lua_setmetatable(L, -2);
472
473
return data;
474
}
475
476
Vec2* lua_vec2_get(lua_State* L, int idx)
477
{
478
Vec2* a = (Vec2*)lua_touserdatatagged(L, idx, kTagVec2);
479
480
if (a)
481
return a;
482
483
luaL_typeerror(L, idx, "vec2");
484
}
485
486
static int lua_vec2(lua_State* L)
487
{
488
double x = luaL_checknumber(L, 1);
489
double y = luaL_checknumber(L, 2);
490
491
Vec2* data = lua_vec2_push(L);
492
493
data->x = float(x);
494
data->y = float(y);
495
496
return 1;
497
}
498
499
static int lua_vec2_dot(lua_State* L)
500
{
501
Vec2* a = lua_vec2_get(L, 1);
502
Vec2* b = lua_vec2_get(L, 2);
503
504
lua_pushnumber(L, a->x * b->x + a->y * b->y);
505
return 1;
506
}
507
508
static int lua_vec2_min(lua_State* L)
509
{
510
Vec2* a = lua_vec2_get(L, 1);
511
Vec2* b = lua_vec2_get(L, 2);
512
513
Vec2* data = lua_vec2_push(L);
514
515
data->x = a->x < b->x ? a->x : b->x;
516
data->y = a->y < b->y ? a->y : b->y;
517
518
return 1;
519
}
520
521
static int lua_vec2_index(lua_State* L)
522
{
523
Vec2* v = lua_vec2_get(L, 1);
524
const char* name = luaL_checkstring(L, 2);
525
526
if (strcmp(name, "X") == 0)
527
{
528
lua_pushnumber(L, v->x);
529
return 1;
530
}
531
532
if (strcmp(name, "Y") == 0)
533
{
534
lua_pushnumber(L, v->y);
535
return 1;
536
}
537
538
if (strcmp(name, "Magnitude") == 0)
539
{
540
lua_pushnumber(L, sqrtf(v->x * v->x + v->y * v->y));
541
return 1;
542
}
543
544
if (strcmp(name, "Unit") == 0)
545
{
546
float invSqrt = 1.0f / sqrtf(v->x * v->x + v->y * v->y);
547
548
Vec2* data = lua_vec2_push(L);
549
550
data->x = v->x * invSqrt;
551
data->y = v->y * invSqrt;
552
return 1;
553
}
554
555
luaL_error(L, "%s is not a valid member of vector", name);
556
}
557
558
static int lua_vec2_namecall(lua_State* L)
559
{
560
if (const char* str = lua_namecallatom(L, nullptr))
561
{
562
if (strcmp(str, "Dot") == 0)
563
return lua_vec2_dot(L);
564
565
if (strcmp(str, "Min") == 0)
566
return lua_vec2_min(L);
567
}
568
569
luaL_error(L, "%s is not a valid method of vector", luaL_checkstring(L, 1));
570
}
571
572
Vertex* lua_vertex_push(lua_State* L)
573
{
574
Vertex* data = (Vertex*)lua_newuserdatatagged(L, sizeof(Vertex), kTagVertex);
575
576
lua_getuserdatametatable(L, kTagVertex);
577
lua_setmetatable(L, -2);
578
579
return data;
580
}
581
582
Vertex* lua_vertex_get(lua_State* L, int idx)
583
{
584
Vertex* a = (Vertex*)lua_touserdatatagged(L, idx, kTagVertex);
585
586
if (a)
587
return a;
588
589
luaL_typeerror(L, idx, "vertex");
590
}
591
592
static int lua_vertex(lua_State* L)
593
{
594
const float* pos = luaL_checkvector(L, 1);
595
const float* normal = luaL_checkvector(L, 2);
596
Vec2* uv = lua_vec2_get(L, 3);
597
598
Vertex* data = lua_vertex_push(L);
599
600
data->pos[0] = pos[0];
601
data->pos[1] = pos[1];
602
data->pos[2] = pos[2];
603
data->normal[0] = normal[0];
604
data->normal[1] = normal[1];
605
data->normal[2] = normal[2];
606
data->uv[0] = uv->x;
607
data->uv[1] = uv->y;
608
609
return 1;
610
}
611
612
static int lua_vertex_index(lua_State* L)
613
{
614
Vertex* v = lua_vertex_get(L, 1);
615
const char* name = luaL_checkstring(L, 2);
616
617
if (strcmp(name, "pos") == 0)
618
{
619
lua_pushvector(L, v->pos[0], v->pos[1], v->pos[2]);
620
return 1;
621
}
622
623
if (strcmp(name, "normal") == 0)
624
{
625
lua_pushvector(L, v->normal[0], v->normal[1], v->normal[2]);
626
return 1;
627
}
628
629
if (strcmp(name, "uv") == 0)
630
{
631
Vec2* uv = lua_vec2_push(L);
632
uv->x = v->uv[0];
633
uv->y = v->uv[1];
634
return 1;
635
}
636
637
luaL_error(L, "%s is not a valid member of vertex", name);
638
}
639
640
void setupUserdataHelpers(lua_State* L)
641
{
642
// create metatable with all the metamethods
643
luaL_newmetatable(L, "vec2");
644
lua_pushvalue(L, -1);
645
lua_setuserdatametatable(L, kTagVec2);
646
647
lua_pushcfunction(L, lua_vec2_index, nullptr);
648
lua_setfield(L, -2, "__index");
649
650
lua_pushcfunction(L, lua_vec2_namecall, nullptr);
651
lua_setfield(L, -2, "__namecall");
652
653
lua_pushcclosurek(
654
L,
655
[](lua_State* L)
656
{
657
Vec2* a = lua_vec2_get(L, 1);
658
Vec2* b = lua_vec2_get(L, 2);
659
Vec2* data = lua_vec2_push(L);
660
661
data->x = a->x + b->x;
662
data->y = a->y + b->y;
663
664
return 1;
665
},
666
nullptr,
667
0,
668
nullptr
669
);
670
lua_setfield(L, -2, "__add");
671
672
lua_pushcclosurek(
673
L,
674
[](lua_State* L)
675
{
676
Vec2* a = lua_vec2_get(L, 1);
677
Vec2* b = lua_vec2_get(L, 2);
678
Vec2* data = lua_vec2_push(L);
679
680
data->x = a->x - b->x;
681
data->y = a->y - b->y;
682
683
return 1;
684
},
685
nullptr,
686
0,
687
nullptr
688
);
689
lua_setfield(L, -2, "__sub");
690
691
lua_pushcclosurek(
692
L,
693
[](lua_State* L)
694
{
695
Vec2* a = lua_vec2_get(L, 1);
696
Vec2* b = lua_vec2_get(L, 2);
697
Vec2* data = lua_vec2_push(L);
698
699
data->x = a->x * b->x;
700
data->y = a->y * b->y;
701
702
return 1;
703
},
704
nullptr,
705
0,
706
nullptr
707
);
708
lua_setfield(L, -2, "__mul");
709
710
lua_pushcclosurek(
711
L,
712
[](lua_State* L)
713
{
714
Vec2* a = lua_vec2_get(L, 1);
715
Vec2* b = lua_vec2_get(L, 2);
716
Vec2* data = lua_vec2_push(L);
717
718
data->x = a->x / b->x;
719
data->y = a->y / b->y;
720
721
return 1;
722
},
723
nullptr,
724
0,
725
nullptr
726
);
727
lua_setfield(L, -2, "__div");
728
729
lua_pushcclosurek(
730
L,
731
[](lua_State* L)
732
{
733
Vec2* a = lua_vec2_get(L, 1);
734
Vec2* data = lua_vec2_push(L);
735
736
data->x = -a->x;
737
data->y = -a->y;
738
739
return 1;
740
},
741
nullptr,
742
0,
743
nullptr
744
);
745
lua_setfield(L, -2, "__unm");
746
747
lua_setreadonly(L, -1, true);
748
749
// ctor
750
lua_pushcfunction(L, lua_vec2, "vec2");
751
lua_setglobal(L, "vec2");
752
753
lua_pop(L, 1);
754
755
// register vertex as well
756
luaL_newmetatable(L, "vertex");
757
lua_pushvalue(L, -1);
758
lua_setuserdatametatable(L, kTagVertex);
759
760
lua_pushcfunction(L, lua_vertex_index, nullptr);
761
lua_setfield(L, -2, "__index");
762
763
lua_setreadonly(L, -1, true);
764
765
// ctor
766
lua_pushcfunction(L, lua_vertex, "vertex");
767
lua_setglobal(L, "vertex");
768
769
lua_pop(L, 1);
770
}
771
772
static void setupNativeHelpers(lua_State* L)
773
{
774
extern int luaG_isnative(lua_State * L, int level);
775
776
lua_pushcclosurek(
777
L,
778
[](lua_State* L) -> int
779
{
780
lua_pushboolean(L, luaG_isnative(L, 1));
781
return 1;
782
},
783
"is_native",
784
0,
785
nullptr
786
);
787
lua_setglobal(L, "is_native");
788
789
lua_pushcclosurek(
790
L,
791
[](lua_State* L) -> int
792
{
793
if (!codegen || !luau_codegen_supported())
794
lua_pushboolean(L, 1);
795
else
796
lua_pushboolean(L, luaG_isnative(L, 1));
797
798
return 1;
799
},
800
"is_native_if_supported",
801
0,
802
nullptr
803
);
804
lua_setglobal(L, "is_native_if_supported");
805
}
806
807
static std::vector<Luau::CodeGen::FunctionBytecodeSummary> analyzeFile(const char* source, const unsigned nestingLimit)
808
{
809
Luau::BytecodeBuilder bcb;
810
811
Luau::CompileOptions options;
812
options.optimizationLevel = optimizationLevel;
813
options.debugLevel = 1;
814
options.typeInfoLevel = 1;
815
816
compileOrThrow(bcb, source, options);
817
818
const std::string& bytecode = bcb.getBytecode();
819
820
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
821
lua_State* L = globalState.get();
822
823
int result = luau_load(L, "source", bytecode.data(), bytecode.size(), 0);
824
REQUIRE(result == 0);
825
826
return Luau::CodeGen::summarizeBytecode(L, -1, nestingLimit);
827
}
828
829
TEST_SUITE_BEGIN("Conformance");
830
831
TEST_CASE("CodegenSupported")
832
{
833
if (codegen && !luau_codegen_supported())
834
MESSAGE("Native code generation is not supported by the current configuration and will be disabled");
835
}
836
837
TEST_CASE("Assert")
838
{
839
runConformance("assert.luau");
840
}
841
842
TEST_CASE("Basic")
843
{
844
runConformance("basic.luau");
845
}
846
847
TEST_CASE("Buffers")
848
{
849
runConformance("buffers.luau");
850
}
851
852
TEST_CASE("Math")
853
{
854
ScopedFastFlag newMathConstants{FFlag::LuauNewMathConstantsRuntime, true};
855
runConformance("math.luau");
856
}
857
858
TEST_CASE("Integers")
859
{
860
if (FFlag::LuauIntegerType && FFlag::LuauIntegerLibrary)
861
runConformance("integers.luau");
862
}
863
864
TEST_CASE("Tables")
865
{
866
runConformance(
867
"tables.luau",
868
[](lua_State* L)
869
{
870
lua_pushcfunction(
871
L,
872
[](lua_State* L)
873
{
874
if (lua_type(L, 1) == LUA_TNUMBER)
875
{
876
unsigned v = luaL_checkunsigned(L, 1);
877
lua_pushlightuserdata(L, reinterpret_cast<void*>(uintptr_t(v)));
878
}
879
else
880
{
881
const void* p = lua_topointer(L, 1);
882
LUAU_ASSERT(p); // we expect the test call to only pass GC values here
883
lua_pushlightuserdata(L, const_cast<void*>(p));
884
}
885
return 1;
886
},
887
"makelud"
888
);
889
lua_setglobal(L, "makelud");
890
}
891
);
892
}
893
894
TEST_CASE("PatternMatch")
895
{
896
runConformance("pm.luau");
897
}
898
899
TEST_CASE("Sort")
900
{
901
runConformance("sort.luau");
902
}
903
904
TEST_CASE("Move")
905
{
906
runConformance("move.luau");
907
}
908
909
TEST_CASE("Clear")
910
{
911
runConformance("clear.luau");
912
}
913
914
TEST_CASE("Strings")
915
{
916
runConformance("strings.luau");
917
}
918
919
TEST_CASE("StringInterp")
920
{
921
ScopedFastFlag luauCompileStringInterpWithZero{FFlag::LuauCompileStringInterpWithZero, true};
922
923
runConformance("stringinterp.luau");
924
}
925
926
TEST_CASE("VarArg")
927
{
928
runConformance("vararg.luau");
929
}
930
931
TEST_CASE("Locals")
932
{
933
runConformance("locals.luau");
934
}
935
936
TEST_CASE("Literals")
937
{
938
runConformance("literals.luau");
939
}
940
941
TEST_CASE("Errors")
942
{
943
ScopedFastFlag luauStacklessPcall{FFlag::LuauStacklessPcall, true};
944
945
runConformance("errors.luau");
946
}
947
948
TEST_CASE("Events")
949
{
950
runConformance("events.luau");
951
}
952
953
TEST_CASE("Constructs")
954
{
955
runConformance("constructs.luau");
956
}
957
958
TEST_CASE("Closure")
959
{
960
runConformance("closure.luau");
961
}
962
963
TEST_CASE("Calls")
964
{
965
runConformance("calls.luau");
966
}
967
968
TEST_CASE("Attrib")
969
{
970
runConformance("attrib.luau");
971
}
972
973
static bool blockableReallocAllowed = true;
974
975
static void* blockableRealloc(void* ud, void* ptr, size_t osize, size_t nsize)
976
{
977
if (nsize == 0)
978
{
979
free(ptr);
980
return nullptr;
981
}
982
else
983
{
984
if (!blockableReallocAllowed)
985
return nullptr;
986
987
return realloc(ptr, nsize);
988
}
989
}
990
991
TEST_CASE("GC")
992
{
993
runConformance(
994
"gc.luau",
995
[](lua_State* L)
996
{
997
lua_pushcclosurek(
998
L,
999
[](lua_State* L)
1000
{
1001
blockableReallocAllowed = !luaL_checkboolean(L, 1);
1002
return 0;
1003
},
1004
"setblockallocations",
1005
0,
1006
nullptr
1007
);
1008
lua_setglobal(L, "setblockallocations");
1009
},
1010
nullptr,
1011
lua_newstate(blockableRealloc, nullptr)
1012
);
1013
}
1014
1015
TEST_CASE("Bitwise")
1016
{
1017
runConformance("bitwise.luau");
1018
}
1019
1020
TEST_CASE("UTF8")
1021
{
1022
runConformance("utf8.luau");
1023
}
1024
1025
TEST_CASE("Coroutine")
1026
{
1027
runConformance("coroutine.luau");
1028
}
1029
1030
static int cxxthrow(lua_State* L)
1031
{
1032
#if LUA_USE_LONGJMP
1033
luaL_error(L, "oops");
1034
#else
1035
throw std::runtime_error("oops");
1036
#endif
1037
}
1038
1039
TEST_CASE("PCall")
1040
{
1041
ScopedFastFlag luauStacklessPcall{FFlag::LuauStacklessPcall, true};
1042
1043
runConformance(
1044
"pcall.luau",
1045
[](lua_State* L)
1046
{
1047
lua_pushcfunction(L, cxxthrow, "cxxthrow");
1048
lua_setglobal(L, "cxxthrow");
1049
1050
lua_pushcfunction(
1051
L,
1052
[](lua_State* L) -> int
1053
{
1054
lua_State* co = lua_tothread(L, 1);
1055
lua_xmove(L, co, 1);
1056
lua_resumeerror(co, L);
1057
return 0;
1058
},
1059
"resumeerror"
1060
);
1061
lua_setglobal(L, "resumeerror");
1062
},
1063
nullptr,
1064
lua_newstate(limitedRealloc, nullptr)
1065
);
1066
}
1067
1068
TEST_CASE("Pack")
1069
{
1070
runConformance("tpack.luau");
1071
}
1072
1073
TEST_CASE("ExplicitTypeInstantiations")
1074
{
1075
runConformance("explicit_type_instantiations.luau");
1076
}
1077
1078
int singleYield(lua_State* L)
1079
{
1080
lua_pushnumber(L, 2);
1081
1082
return lua_yield(L, 1);
1083
}
1084
1085
int singleYieldContinuation(lua_State* L, int status)
1086
{
1087
lua_pushnumber(L, 4);
1088
return 1;
1089
}
1090
1091
int multipleYields(lua_State* L)
1092
{
1093
lua_settop(L, 1); // Only 1 argument expected
1094
int base = luaL_checkinteger(L, 1);
1095
1096
luaL_checkstack(L, 2, "cmultiyield");
1097
1098
// current state
1099
int pos = 1;
1100
lua_pushinteger(L, pos);
1101
1102
// return value
1103
lua_pushinteger(L, base + pos);
1104
return lua_yield(L, 1);
1105
}
1106
1107
int multipleYieldsContinuation(lua_State* L, int status)
1108
{
1109
// function arguments are still alive
1110
int base = luaL_checkinteger(L, 1);
1111
1112
// function state is still alive
1113
int pos = luaL_checkinteger(L, 2) + 1;
1114
luaL_checkstack(L, 1, "cmultiyieldcont");
1115
lua_pushinteger(L, pos);
1116
lua_replace(L, 2);
1117
1118
luaL_checkstack(L, 1, "cmultiyieldcont");
1119
1120
if (pos < 4)
1121
{
1122
lua_pushinteger(L, base + pos);
1123
return lua_yield(L, 1);
1124
}
1125
else
1126
{
1127
lua_pushinteger(L, base + pos);
1128
return 1;
1129
}
1130
}
1131
1132
int nestedMultipleYieldHelper(lua_State* L)
1133
{
1134
int context = luaL_checkinteger(L, lua_upvalueindex(1));
1135
1136
lua_pushinteger(L, 100 + context);
1137
return lua_yield(L, 1);
1138
}
1139
1140
int nestedMultipleYieldHelperContinuation(lua_State* L, int status)
1141
{
1142
int context = luaL_checkinteger(L, lua_upvalueindex(1));
1143
lua_pushinteger(L, 110 + context);
1144
return 1;
1145
}
1146
1147
int nestedMultipleYieldHelperNonYielding(lua_State* L)
1148
{
1149
int context = luaL_checkinteger(L, lua_upvalueindex(1));
1150
lua_pushinteger(L, 105 + context);
1151
return 1;
1152
}
1153
1154
int multipleYieldsWithNestedCall(lua_State* L)
1155
{
1156
lua_settop(L, 2); // Only 2 arguments expected
1157
bool nestedShouldYield = luaL_checkboolean(L, 2);
1158
1159
lua_pushinteger(L, 0); // state
1160
1161
lua_pushnumber(L, 5);
1162
if (nestedShouldYield)
1163
lua_pushcclosurek(L, nestedMultipleYieldHelper, nullptr, 1, nestedMultipleYieldHelperContinuation);
1164
else
1165
lua_pushcclosurek(L, nestedMultipleYieldHelperNonYielding, nullptr, 1, nullptr);
1166
1167
return luaL_callyieldable(L, 0, 1);
1168
}
1169
1170
int multipleYieldsWithNestedCallContinuation(lua_State* L, int status)
1171
{
1172
int state = luaL_checkinteger(L, 3);
1173
luaL_checkstack(L, 1, "cnestedmultiyieldcont");
1174
lua_pushinteger(L, state + 1);
1175
lua_replace(L, 3);
1176
1177
if (state == 0)
1178
{
1179
return lua_yield(L, lua_gettop(L) - 3);
1180
}
1181
else if (state == 1)
1182
{
1183
lua_pushnumber(L, luaL_checkinteger(L, 1) + 200);
1184
return lua_yield(L, 1);
1185
}
1186
else
1187
{
1188
lua_pushnumber(L, luaL_checkinteger(L, 1) + 210);
1189
return 1;
1190
}
1191
}
1192
1193
int passthroughCall(lua_State* L)
1194
{
1195
luaL_checkstack(L, 3, "cpass");
1196
lua_pushvalue(L, 1);
1197
lua_pushvalue(L, 2);
1198
lua_pushvalue(L, 3);
1199
return luaL_callyieldable(L, 2, 1);
1200
}
1201
1202
int passthroughCallContinuation(lua_State* L, int status)
1203
{
1204
LUAU_ASSERT(lua_gettop(L) == 4); // 3 original arguments and the return value
1205
LUAU_ASSERT(lua_tonumber(L, -1) == 0.5);
1206
return 1;
1207
}
1208
1209
int passthroughCallMoreResults(lua_State* L)
1210
{
1211
luaL_checkstack(L, 3, "cpass");
1212
lua_pushvalue(L, 1);
1213
lua_pushvalue(L, 2);
1214
lua_pushvalue(L, 3);
1215
return luaL_callyieldable(L, 2, 10);
1216
}
1217
1218
int passthroughCallMoreResultsContinuation(lua_State* L, int status)
1219
{
1220
LUAU_ASSERT(lua_gettop(L) == 13); // 3 original arguments and 10 requested return values
1221
1222
for (int i = 0; i < 9; i++)
1223
{
1224
LUAU_ASSERT(lua_isnil(L, -1));
1225
lua_pop(L, 1);
1226
}
1227
1228
LUAU_ASSERT(lua_tonumber(L, -1) == 0.5);
1229
return 1;
1230
}
1231
1232
int passthroughCallArgReuse(lua_State* L)
1233
{
1234
return luaL_callyieldable(L, 2, 1);
1235
}
1236
1237
int passthroughCallArgReuseContinuation(lua_State* L, int status)
1238
{
1239
LUAU_ASSERT(lua_gettop(L) == 1); // Original arguments were consumed, only return remains
1240
LUAU_ASSERT(lua_tonumber(L, -1) == 0.5);
1241
return 1;
1242
}
1243
1244
int passthroughCallVaradic(lua_State* L)
1245
{
1246
luaL_checkany(L, 1);
1247
return luaL_callyieldable(L, lua_gettop(L) - 1, LUA_MULTRET);
1248
}
1249
1250
int passthroughCallVaradicContinuation(lua_State* L, int status)
1251
{
1252
return lua_gettop(L);
1253
}
1254
1255
int passthroughCallWithState(lua_State* L)
1256
{
1257
luaL_checkany(L, 1);
1258
int args = lua_gettop(L) - 1;
1259
1260
lua_pushnumber(L, 42);
1261
lua_insert(L, 1);
1262
1263
return luaL_callyieldable(L, args, LUA_MULTRET);
1264
}
1265
1266
int passthroughCallWithStateContinuation(lua_State* L, int status)
1267
{
1268
LUAU_ASSERT(luaL_checkinteger(L, 1) == 42);
1269
1270
return lua_gettop(L) - 1;
1271
}
1272
1273
TEST_CASE("CYield")
1274
{
1275
runConformance(
1276
"cyield.luau",
1277
[](lua_State* L)
1278
{
1279
lua_pushcclosurek(L, singleYield, "singleYield", 0, singleYieldContinuation);
1280
lua_setglobal(L, "singleYield");
1281
1282
lua_pushcclosurek(L, multipleYields, "multipleYields", 0, multipleYieldsContinuation);
1283
lua_setglobal(L, "multipleYields");
1284
1285
lua_pushcclosurek(L, multipleYieldsWithNestedCall, "multipleYieldsWithNestedCall", 0, multipleYieldsWithNestedCallContinuation);
1286
lua_setglobal(L, "multipleYieldsWithNestedCall");
1287
1288
lua_pushcclosurek(L, passthroughCall, "passthroughCall", 0, passthroughCallContinuation);
1289
lua_setglobal(L, "passthroughCall");
1290
1291
lua_pushcclosurek(L, passthroughCallMoreResults, "passthroughCallMoreResults", 0, passthroughCallMoreResultsContinuation);
1292
lua_setglobal(L, "passthroughCallMoreResults");
1293
1294
lua_pushcclosurek(L, passthroughCallArgReuse, "passthroughCallArgReuse", 0, passthroughCallArgReuseContinuation);
1295
lua_setglobal(L, "passthroughCallArgReuse");
1296
1297
lua_pushcclosurek(L, passthroughCallVaradic, "passthroughCallVaradic", 0, passthroughCallVaradicContinuation);
1298
lua_setglobal(L, "passthroughCallVaradic");
1299
1300
lua_pushcclosurek(L, passthroughCallWithState, "passthroughCallWithState", 0, passthroughCallWithStateContinuation);
1301
lua_setglobal(L, "passthroughCallWithState");
1302
}
1303
);
1304
}
1305
1306
TEST_CASE("Vector")
1307
{
1308
lua_CompileOptions copts = defaultOptions();
1309
Luau::CodeGen::CompilationOptions nativeOpts = defaultCodegenOptions();
1310
1311
SUBCASE("NoIrHooks")
1312
{
1313
SUBCASE("O0")
1314
{
1315
copts.optimizationLevel = 0;
1316
}
1317
SUBCASE("O1")
1318
{
1319
copts.optimizationLevel = 1;
1320
}
1321
SUBCASE("O2")
1322
{
1323
copts.optimizationLevel = 2;
1324
}
1325
}
1326
SUBCASE("IrHooks")
1327
{
1328
nativeOpts.hooks.vectorAccessBytecodeType = vectorAccessBytecodeType;
1329
nativeOpts.hooks.vectorNamecallBytecodeType = vectorNamecallBytecodeType;
1330
nativeOpts.hooks.vectorAccess = vectorAccess;
1331
nativeOpts.hooks.vectorNamecall = vectorNamecall;
1332
1333
SUBCASE("O0")
1334
{
1335
copts.optimizationLevel = 0;
1336
}
1337
SUBCASE("O1")
1338
{
1339
copts.optimizationLevel = 1;
1340
}
1341
SUBCASE("O2")
1342
{
1343
copts.optimizationLevel = 2;
1344
}
1345
}
1346
1347
runConformance(
1348
"vector.luau",
1349
[](lua_State* L)
1350
{
1351
setupVectorHelpers(L);
1352
setupNativeHelpers(L);
1353
},
1354
nullptr,
1355
nullptr,
1356
&copts,
1357
false,
1358
&nativeOpts
1359
);
1360
}
1361
1362
TEST_CASE("VectorLibrary")
1363
{
1364
lua_CompileOptions copts = defaultOptions();
1365
1366
SUBCASE("O0")
1367
{
1368
copts.optimizationLevel = 0;
1369
}
1370
SUBCASE("O1")
1371
{
1372
copts.optimizationLevel = 1;
1373
}
1374
SUBCASE("O2")
1375
{
1376
copts.optimizationLevel = 2;
1377
}
1378
1379
runConformance(
1380
"vector_library.luau",
1381
[](lua_State* L)
1382
{
1383
setupNativeHelpers(L);
1384
},
1385
nullptr,
1386
nullptr,
1387
&copts
1388
);
1389
}
1390
1391
static void populateRTTI(lua_State* L, Luau::TypeId type)
1392
{
1393
if (auto p = Luau::get<Luau::PrimitiveType>(type))
1394
{
1395
switch (p->type)
1396
{
1397
case Luau::PrimitiveType::Boolean:
1398
lua_pushstring(L, "boolean");
1399
break;
1400
1401
case Luau::PrimitiveType::NilType:
1402
lua_pushstring(L, "nil");
1403
break;
1404
1405
case Luau::PrimitiveType::Number:
1406
lua_pushstring(L, "number");
1407
break;
1408
1409
case Luau::PrimitiveType::Integer:
1410
if (FFlag::LuauIntegerType)
1411
lua_pushstring(L, "integer");
1412
break;
1413
1414
case Luau::PrimitiveType::String:
1415
lua_pushstring(L, "string");
1416
break;
1417
1418
case Luau::PrimitiveType::Thread:
1419
lua_pushstring(L, "thread");
1420
break;
1421
1422
case Luau::PrimitiveType::Buffer:
1423
lua_pushstring(L, "buffer");
1424
break;
1425
1426
default:
1427
LUAU_ASSERT(!"Unknown primitive type");
1428
}
1429
}
1430
else if (auto t = Luau::get<Luau::TableType>(type))
1431
{
1432
lua_newtable(L);
1433
1434
for (const auto& [name, prop] : t->props)
1435
{
1436
if (prop.readTy)
1437
populateRTTI(L, *prop.readTy);
1438
else if (prop.writeTy)
1439
populateRTTI(L, *prop.writeTy);
1440
1441
lua_setfield(L, -2, name.c_str());
1442
}
1443
}
1444
else if (Luau::get<Luau::FunctionType>(type))
1445
{
1446
lua_pushstring(L, "function");
1447
}
1448
else if (Luau::get<Luau::AnyType>(type))
1449
{
1450
lua_pushstring(L, "any");
1451
}
1452
else if (auto i = Luau::get<Luau::IntersectionType>(type))
1453
{
1454
for (const auto& part : i->parts)
1455
LUAU_ASSERT(Luau::get<Luau::FunctionType>(part));
1456
1457
lua_pushstring(L, "function");
1458
}
1459
else if (auto c = Luau::get<Luau::ExternType>(type))
1460
{
1461
lua_pushstring(L, c->name.c_str());
1462
}
1463
else
1464
{
1465
LUAU_ASSERT(!"Unknown type");
1466
}
1467
}
1468
1469
TEST_CASE("Types")
1470
{
1471
runConformance(
1472
"types.luau",
1473
[](lua_State* L)
1474
{
1475
Luau::NullModuleResolver moduleResolver;
1476
Luau::NullFileResolver fileResolver;
1477
Luau::NullConfigResolver configResolver;
1478
Luau::Frontend frontend{!FFlag::DebugLuauForceOldSolver ? Luau::SolverMode::New : Luau::SolverMode::Old, &fileResolver, &configResolver};
1479
Luau::registerBuiltinGlobals(frontend, frontend.globals);
1480
Luau::freeze(frontend.globals.globalTypes);
1481
1482
lua_newtable(L);
1483
1484
for (const auto& [name, binding] : frontend.globals.globalScope->bindings)
1485
{
1486
populateRTTI(L, binding.typeId);
1487
lua_setfield(L, -2, toString(name).c_str());
1488
}
1489
1490
lua_setglobal(L, "RTTI");
1491
}
1492
);
1493
}
1494
1495
TEST_CASE("DateTime")
1496
{
1497
runConformance("datetime.luau");
1498
}
1499
1500
TEST_CASE("Debug")
1501
{
1502
runConformance("debug.luau");
1503
}
1504
1505
TEST_CASE("Debugger")
1506
{
1507
static int breakhits = 0;
1508
static lua_State* interruptedthread = nullptr;
1509
static bool singlestep = false;
1510
static int stephits = 0;
1511
1512
SUBCASE("")
1513
{
1514
singlestep = false;
1515
}
1516
SUBCASE("SingleStep")
1517
{
1518
singlestep = true;
1519
}
1520
1521
breakhits = 0;
1522
interruptedthread = nullptr;
1523
stephits = 0;
1524
1525
lua_CompileOptions copts = defaultOptions();
1526
copts.debugLevel = 2;
1527
1528
runConformance(
1529
"debugger.luau",
1530
[](lua_State* L)
1531
{
1532
lua_Callbacks* cb = lua_callbacks(L);
1533
1534
lua_singlestep(L, singlestep);
1535
1536
// this will only be called in single-step mode
1537
cb->debugstep = [](lua_State* L, lua_Debug* ar)
1538
{
1539
stephits++;
1540
};
1541
1542
// for breakpoints to work we should make sure debugbreak is installed
1543
cb->debugbreak = [](lua_State* L, lua_Debug* ar)
1544
{
1545
breakhits++;
1546
1547
// make sure we can trace the stack for every breakpoint we hit
1548
lua_debugtrace(L);
1549
1550
// for every breakpoint, we break on the first invocation and continue on second
1551
// this allows us to easily step off breakpoints
1552
// (real implementaiton may require singlestepping)
1553
if (breakhits % 2 == 1)
1554
lua_break(L);
1555
};
1556
1557
// for resuming off a breakpoint inside a coroutine we need to resume the interrupted coroutine
1558
cb->debuginterrupt = [](lua_State* L, lua_Debug* ar)
1559
{
1560
CHECK(interruptedthread == nullptr);
1561
CHECK(ar->userdata); // userdata contains the interrupted thread
1562
1563
interruptedthread = static_cast<lua_State*>(ar->userdata);
1564
};
1565
1566
// add breakpoint() function
1567
lua_pushcclosurek(
1568
L,
1569
[](lua_State* L) -> int
1570
{
1571
int line = luaL_checkinteger(L, 1);
1572
bool enabled = luaL_optboolean(L, 2, true);
1573
1574
lua_Debug ar = {};
1575
lua_getinfo(L, lua_stackdepth(L) - 1, "f", &ar);
1576
1577
lua_breakpoint(L, -1, line, enabled);
1578
return 0;
1579
},
1580
"breakpoint",
1581
0,
1582
nullptr
1583
);
1584
lua_setglobal(L, "breakpoint");
1585
},
1586
[](lua_State* L) -> bool
1587
{
1588
CHECK(breakhits % 2 == 1);
1589
1590
lua_checkstack(L, LUA_MINSTACK);
1591
1592
if (breakhits == 1)
1593
{
1594
// test lua_getargument
1595
int a = lua_getargument(L, 0, 1);
1596
REQUIRE(a);
1597
CHECK(lua_tointeger(L, -1) == 50);
1598
lua_pop(L, 1);
1599
1600
int v = lua_getargument(L, 0, 2);
1601
REQUIRE(v);
1602
CHECK(lua_tointeger(L, -1) == 42);
1603
lua_pop(L, 1);
1604
1605
// test lua_getlocal
1606
const char* l = lua_getlocal(L, 0, 1);
1607
REQUIRE(l);
1608
CHECK(strcmp(l, "b") == 0);
1609
CHECK(lua_tointeger(L, -1) == 50);
1610
lua_pop(L, 1);
1611
1612
// test lua_getupvalue
1613
lua_Debug ar = {};
1614
lua_getinfo(L, 0, "f", &ar);
1615
1616
const char* u = lua_getupvalue(L, -1, 1);
1617
REQUIRE(u);
1618
CHECK(strcmp(u, "a") == 0);
1619
CHECK(lua_tointeger(L, -1) == 5);
1620
lua_pop(L, 2);
1621
}
1622
else if (breakhits == 3)
1623
{
1624
// validate assignment via lua_getlocal
1625
const char* l = lua_getlocal(L, 0, 1);
1626
REQUIRE(l);
1627
CHECK(strcmp(l, "a") == 0);
1628
CHECK(lua_tointeger(L, -1) == 6);
1629
lua_pop(L, 1);
1630
}
1631
else if (breakhits == 5)
1632
{
1633
// validate assignment via lua_getlocal
1634
const char* l = lua_getlocal(L, 1, 1);
1635
REQUIRE(l);
1636
CHECK(strcmp(l, "a") == 0);
1637
CHECK(lua_tointeger(L, -1) == 7);
1638
lua_pop(L, 1);
1639
}
1640
else if (breakhits == 7)
1641
{
1642
// validate assignment via lua_getlocal
1643
const char* l = lua_getlocal(L, 1, 1);
1644
REQUIRE(l);
1645
CHECK(strcmp(l, "a") == 0);
1646
CHECK(lua_tointeger(L, -1) == 8);
1647
lua_pop(L, 1);
1648
}
1649
else if (breakhits == 9)
1650
{
1651
// validate assignment via lua_getlocal
1652
const char* l = lua_getlocal(L, 1, 1);
1653
REQUIRE(l);
1654
CHECK(strcmp(l, "a") == 0);
1655
CHECK(lua_tointeger(L, -1) == 9);
1656
lua_pop(L, 1);
1657
}
1658
else if (breakhits == 13)
1659
{
1660
// validate assignment via lua_getlocal
1661
const char* l = lua_getlocal(L, 0, 1);
1662
REQUIRE(l);
1663
CHECK(strcmp(l, "a") == 0);
1664
CHECK(lua_isnil(L, -1));
1665
lua_pop(L, 1);
1666
}
1667
else if (breakhits == 15)
1668
{
1669
// test lua_getlocal
1670
const char* x = lua_getlocal(L, 2, 1);
1671
REQUIRE(x);
1672
CHECK(strcmp(x, "x") == 0);
1673
lua_pop(L, 1);
1674
1675
const char* a1 = lua_getlocal(L, 2, 2);
1676
REQUIRE(!a1);
1677
}
1678
1679
if (interruptedthread)
1680
{
1681
lua_resume(interruptedthread, nullptr, 0);
1682
interruptedthread = nullptr;
1683
}
1684
1685
return false;
1686
},
1687
nullptr,
1688
&copts,
1689
/* skipCodegen */ true
1690
); // Native code doesn't support debugging yet
1691
1692
CHECK(breakhits == 16); // 2 hits per breakpoint
1693
1694
if (singlestep)
1695
CHECK(stephits > 100); // note; this will depend on number of instructions which can vary, so we just make sure the callback gets hit often
1696
}
1697
1698
TEST_CASE("InterruptInspection")
1699
{
1700
static bool skipbreak = false;
1701
1702
runConformance(
1703
"basic.luau",
1704
[](lua_State* L)
1705
{
1706
lua_Callbacks* cb = lua_callbacks(L);
1707
1708
cb->interrupt = [](lua_State* L, int gc)
1709
{
1710
if (gc >= 0)
1711
return;
1712
1713
if (!lua_isyieldable(L))
1714
return;
1715
1716
if (!skipbreak)
1717
lua_break(L);
1718
1719
skipbreak = !skipbreak;
1720
};
1721
},
1722
[](lua_State* L) -> bool
1723
{
1724
// Debug info can be retrieved from every location
1725
lua_Debug ar = {};
1726
CHECK(lua_getinfo(L, 0, "nsl", &ar));
1727
1728
// Simulating a hook being called from the original break location
1729
luau_callhook(
1730
L,
1731
[](lua_State* L, lua_Debug* ar)
1732
{
1733
CHECK(lua_getinfo(L, 0, "nsl", ar));
1734
},
1735
nullptr
1736
);
1737
1738
return false;
1739
},
1740
nullptr,
1741
nullptr,
1742
/* skipCodegen */ true
1743
);
1744
}
1745
1746
TEST_CASE("InterruptErrorInspection")
1747
{
1748
// for easy access in no-capture lambda
1749
static int target = 0;
1750
static int step = 0;
1751
1752
std::string source = R"(
1753
function fib(n)
1754
return n < 2 and 1 or fib(n - 1) + fib(n - 2)
1755
end
1756
1757
fib(5)
1758
)";
1759
1760
for (target = 0; target < 20; target++)
1761
{
1762
step = 0;
1763
1764
StateRef globalState(luaL_newstate(), lua_close);
1765
lua_State* L = globalState.get();
1766
1767
luaL_openlibs(L);
1768
luaL_sandbox(L);
1769
luaL_sandboxthread(L);
1770
1771
size_t bytecodeSize = 0;
1772
char* bytecode = luau_compile(source.data(), source.size(), nullptr, &bytecodeSize);
1773
int result = luau_load(L, "=InterruptErrorInspection", bytecode, bytecodeSize, 0);
1774
free(bytecode);
1775
1776
REQUIRE(result == LUA_OK);
1777
1778
lua_Callbacks* cb = lua_callbacks(L);
1779
1780
cb->interrupt = [](lua_State* L, int gc)
1781
{
1782
if (gc >= 0)
1783
return;
1784
1785
if (step == target)
1786
luaL_error(L, "test");
1787
1788
step++;
1789
};
1790
1791
lua_resume(L, nullptr, 0);
1792
1793
// Debug info can be retrieved from every location
1794
lua_Debug ar = {};
1795
CHECK(lua_getinfo(L, 0, "nsl", &ar));
1796
1797
// Simulating a hook being called from the original break location
1798
luau_callhook(
1799
L,
1800
[](lua_State* L, lua_Debug* ar)
1801
{
1802
CHECK(lua_getinfo(L, 0, "nsl", ar));
1803
},
1804
nullptr
1805
);
1806
}
1807
}
1808
1809
TEST_CASE("NDebugGetUpValue")
1810
{
1811
lua_CompileOptions copts = defaultOptions();
1812
copts.debugLevel = 0;
1813
// Don't optimize away any upvalues
1814
copts.optimizationLevel = 0;
1815
1816
runConformance(
1817
"ndebug_upvalues.luau",
1818
nullptr,
1819
[](lua_State* L) -> bool
1820
{
1821
lua_checkstack(L, LUA_MINSTACK);
1822
1823
// push the second frame's closure to the stack
1824
lua_Debug ar = {};
1825
REQUIRE(lua_getinfo(L, 1, "f", &ar));
1826
1827
// get the first upvalue
1828
const char* u = lua_getupvalue(L, -1, 1);
1829
REQUIRE(u);
1830
// upvalue name is unknown without debug info
1831
CHECK(strcmp(u, "") == 0);
1832
CHECK(lua_tointeger(L, -1) == 5);
1833
lua_pop(L, 2);
1834
1835
return false;
1836
},
1837
nullptr,
1838
&copts,
1839
/* skipCodegen */ false
1840
);
1841
}
1842
1843
TEST_CASE("SameHash")
1844
{
1845
extern unsigned int luaS_hash(const char* str, size_t len); // internal function, declared in lstring.h - not exposed via lua.h
1846
1847
// To keep VM and compiler separate, we duplicate the hash function definition
1848
// This test validates that the hash function in question returns the same results on basic inputs
1849
// If this is violated, some code may regress in performance due to hash slot misprediction in inline caches
1850
CHECK(luaS_hash("", 0) == Luau::BytecodeBuilder::getStringHash({"", 0}));
1851
CHECK(luaS_hash("lua", 3) == Luau::BytecodeBuilder::getStringHash({"lua", 3}));
1852
CHECK(luaS_hash("luau", 4) == Luau::BytecodeBuilder::getStringHash({"luau", 4}));
1853
CHECK(luaS_hash("luaubytecode", 12) == Luau::BytecodeBuilder::getStringHash({"luaubytecode", 12}));
1854
CHECK(luaS_hash("luaubytecodehash", 16) == Luau::BytecodeBuilder::getStringHash({"luaubytecodehash", 16}));
1855
1856
// Also hash should work on unaligned source data even when hashing long strings
1857
char buf[128] = {};
1858
CHECK(luaS_hash(buf + 1, 120) == luaS_hash(buf + 2, 120));
1859
}
1860
1861
TEST_CASE("Reference")
1862
{
1863
static int dtorhits = 0;
1864
1865
dtorhits = 0;
1866
1867
StateRef globalState(luaL_newstate(), lua_close);
1868
lua_State* L = globalState.get();
1869
1870
// note, we push two userdata objects but only pin one of them (the first one)
1871
lua_newuserdatadtor(
1872
L,
1873
0,
1874
[](void*)
1875
{
1876
dtorhits++;
1877
}
1878
);
1879
lua_newuserdatadtor(
1880
L,
1881
0,
1882
[](void*)
1883
{
1884
dtorhits++;
1885
}
1886
);
1887
1888
lua_gc(L, LUA_GCCOLLECT, 0);
1889
CHECK(dtorhits == 0);
1890
1891
int ref = lua_ref(L, -2);
1892
lua_pop(L, 2);
1893
1894
lua_gc(L, LUA_GCCOLLECT, 0);
1895
CHECK(dtorhits == 1);
1896
1897
lua_getref(L, ref);
1898
CHECK(lua_isuserdata(L, -1));
1899
lua_pop(L, 1);
1900
1901
lua_gc(L, LUA_GCCOLLECT, 0);
1902
CHECK(dtorhits == 1);
1903
1904
lua_unref(L, ref);
1905
1906
lua_gc(L, LUA_GCCOLLECT, 0);
1907
CHECK(dtorhits == 2);
1908
}
1909
1910
TEST_CASE("NewUserdataOverflow")
1911
{
1912
StateRef globalState(luaL_newstate(), lua_close);
1913
lua_State* L = globalState.get();
1914
1915
lua_pushcfunction(
1916
L,
1917
[](lua_State* L1)
1918
{
1919
// The following userdata request might cause an overflow.
1920
lua_newuserdatadtor(L1, SIZE_MAX, [](void* d) {});
1921
// The overflow might segfault in the following call.
1922
lua_getmetatable(L1, -1);
1923
return 0;
1924
},
1925
nullptr
1926
);
1927
1928
CHECK(lua_pcall(L, 0, 0, 0) == LUA_ERRRUN);
1929
CHECK(strcmp(lua_tostring(L, -1), "memory allocation error: block too big") == 0);
1930
}
1931
1932
TEST_CASE("SandboxWithoutLibs")
1933
{
1934
StateRef globalState(luaL_newstate(), lua_close);
1935
lua_State* L = globalState.get();
1936
1937
luaopen_base(L); // Load only base library
1938
luaL_sandbox(L);
1939
1940
CHECK(lua_getreadonly(L, LUA_GLOBALSINDEX));
1941
}
1942
1943
TEST_CASE("ApiTables")
1944
{
1945
StateRef globalState(luaL_newstate(), lua_close);
1946
lua_State* L = globalState.get();
1947
1948
int lu1 = 1;
1949
int lu2 = 2;
1950
1951
lua_newtable(L);
1952
lua_pushnumber(L, 123.0);
1953
lua_setfield(L, -2, "key");
1954
lua_pushnumber(L, 456.0);
1955
lua_rawsetfield(L, -2, "key2");
1956
lua_pushstring(L, "key3");
1957
lua_rawseti(L, -2, 5);
1958
lua_pushstring(L, "key4");
1959
lua_rawsetp(L, -2, &lu1);
1960
lua_pushstring(L, "key5");
1961
lua_rawsetptagged(L, -2, &lu2, 1);
1962
lua_pushstring(L, "key6");
1963
lua_rawsetptagged(L, -2, &lu2, 2);
1964
1965
// lua_gettable
1966
lua_pushstring(L, "key");
1967
CHECK(lua_gettable(L, -2) == LUA_TNUMBER);
1968
CHECK(lua_tonumber(L, -1) == 123.0);
1969
lua_pop(L, 1);
1970
1971
// lua_getfield
1972
CHECK(lua_getfield(L, -1, "key") == LUA_TNUMBER);
1973
CHECK(lua_tonumber(L, -1) == 123.0);
1974
lua_pop(L, 1);
1975
1976
// lua_rawgetfield
1977
CHECK(lua_rawgetfield(L, -1, "key2") == LUA_TNUMBER);
1978
CHECK(lua_tonumber(L, -1) == 456.0);
1979
lua_pop(L, 1);
1980
1981
// lua_rawget
1982
lua_pushstring(L, "key");
1983
CHECK(lua_rawget(L, -2) == LUA_TNUMBER);
1984
CHECK(lua_tonumber(L, -1) == 123.0);
1985
lua_pop(L, 1);
1986
1987
// lua_rawgeti
1988
CHECK(lua_rawgeti(L, -1, 5) == LUA_TSTRING);
1989
CHECK(strcmp(lua_tostring(L, -1), "key3") == 0);
1990
lua_pop(L, 1);
1991
1992
// lua_rawgetp
1993
CHECK(lua_rawgetp(L, -1, &lu1) == LUA_TSTRING);
1994
CHECK(strcmp(lua_tostring(L, -1), "key4") == 0);
1995
lua_pop(L, 1);
1996
1997
// lua_rawsetptagged
1998
CHECK(lua_rawgetptagged(L, -1, &lu2, 1) == LUA_TSTRING);
1999
CHECK(strcmp(lua_tostring(L, -1), "key5") == 0);
2000
lua_pop(L, 1);
2001
2002
CHECK(lua_rawgetptagged(L, -1, &lu2, 2) == LUA_TSTRING);
2003
CHECK(strcmp(lua_tostring(L, -1), "key6") == 0);
2004
lua_pop(L, 1);
2005
2006
CHECK(lua_rawgetptagged(L, -1, &lu2, 0) == LUA_TNIL);
2007
lua_pop(L, 1);
2008
2009
// lua_clonetable
2010
lua_clonetable(L, -1);
2011
2012
CHECK(lua_getfield(L, -1, "key") == LUA_TNUMBER);
2013
CHECK(lua_tonumber(L, -1) == 123.0);
2014
lua_pop(L, 1);
2015
2016
// modify clone
2017
lua_pushnumber(L, 456.0);
2018
lua_rawsetfield(L, -2, "key");
2019
2020
// remove clone
2021
lua_pop(L, 1);
2022
2023
// check original
2024
CHECK(lua_getfield(L, -1, "key") == LUA_TNUMBER);
2025
CHECK(lua_tonumber(L, -1) == 123.0);
2026
lua_pop(L, 1);
2027
2028
// lua_cleartable
2029
lua_cleartable(L, -1);
2030
lua_pushnil(L);
2031
CHECK(lua_next(L, -2) == 0);
2032
2033
lua_pop(L, 1);
2034
}
2035
2036
TEST_CASE("ApiIter")
2037
{
2038
StateRef globalState(luaL_newstate(), lua_close);
2039
lua_State* L = globalState.get();
2040
2041
lua_newtable(L);
2042
lua_pushnumber(L, 123.0);
2043
lua_setfield(L, -2, "key");
2044
lua_pushnumber(L, 456.0);
2045
lua_rawsetfield(L, -2, "key2");
2046
lua_pushstring(L, "test");
2047
lua_rawseti(L, -2, 1);
2048
2049
// Lua-compatible iteration interface: lua_next
2050
double sum1 = 0;
2051
lua_pushnil(L);
2052
while (lua_next(L, -2))
2053
{
2054
sum1 += lua_tonumber(L, -2); // key
2055
sum1 += lua_tonumber(L, -1); // value
2056
lua_pop(L, 1); // pop value, key is used by lua_next
2057
}
2058
CHECK(sum1 == 580);
2059
2060
// Luau iteration interface: lua_rawiter (faster and preferable to lua_next)
2061
double sum2 = 0;
2062
for (int index = 0; index = lua_rawiter(L, -1, index), index >= 0;)
2063
{
2064
sum2 += lua_tonumber(L, -2); // key
2065
sum2 += lua_tonumber(L, -1); // value
2066
lua_pop(L, 2); // pop both key and value
2067
}
2068
CHECK(sum2 == 580);
2069
2070
// check lua_rawiter compatibility with C stack limit
2071
lua_settop(L, 18);
2072
lua_pushvalue(L, 1);
2073
2074
CHECK(lua_gettop(L) == 19);
2075
CHECK(lua_checkstack(L, 2));
2076
2077
// Luau iteration interface: lua_rawiter (faster and preferable to lua_next)
2078
double sum3 = 0;
2079
for (int index = 0; index = lua_rawiter(L, -1, index), index >= 0;)
2080
{
2081
sum3 += lua_tonumber(L, -2); // key
2082
sum3 += lua_tonumber(L, -1); // value
2083
lua_pop(L, 2); // pop both key and value
2084
}
2085
CHECK(sum3 == 580);
2086
2087
// pop everything
2088
lua_pop(L, 19);
2089
}
2090
2091
static int cpcallTest(lua_State* L)
2092
{
2093
bool shouldFail = *(bool*)(lua_tolightuserdata(L, 1));
2094
2095
if (shouldFail)
2096
{
2097
luaL_error(L, "Failed");
2098
}
2099
else
2100
{
2101
lua_pushinteger(L, 123);
2102
lua_setglobal(L, "cpcallvalue");
2103
}
2104
2105
return 0;
2106
}
2107
2108
TEST_CASE("ApiCalls")
2109
{
2110
StateRef globalState = runConformance("apicalls.luau", nullptr, nullptr, lua_newstate(limitedRealloc, nullptr));
2111
lua_State* L = globalState.get();
2112
2113
// lua_call
2114
{
2115
lua_getfield(L, LUA_GLOBALSINDEX, "add");
2116
lua_pushnumber(L, 40);
2117
lua_pushnumber(L, 2);
2118
lua_call(L, 2, 1);
2119
CHECK(lua_isnumber(L, -1));
2120
CHECK(lua_tonumber(L, -1) == 42);
2121
lua_pop(L, 1);
2122
}
2123
2124
// lua_call variadic
2125
{
2126
lua_getfield(L, LUA_GLOBALSINDEX, "getnresults");
2127
lua_pushinteger(L, 200);
2128
lua_call(L, 1, LUA_MULTRET);
2129
CHECK(lua_gettop(L) == 200);
2130
lua_pop(L, 200);
2131
}
2132
2133
// lua_pcall
2134
{
2135
lua_getfield(L, LUA_GLOBALSINDEX, "add");
2136
lua_pushnumber(L, 40);
2137
lua_pushnumber(L, 2);
2138
int status = lua_pcall(L, 2, 1, 0);
2139
CHECK(status == LUA_OK);
2140
CHECK(lua_isnumber(L, -1));
2141
CHECK(lua_tonumber(L, -1) == 42);
2142
lua_pop(L, 1);
2143
}
2144
2145
// lua_pcall variadic
2146
{
2147
lua_getfield(L, LUA_GLOBALSINDEX, "getnresults");
2148
lua_pushinteger(L, 200);
2149
int status = lua_pcall(L, 1, LUA_MULTRET, 0);
2150
CHECK(status == LUA_OK);
2151
CHECK(lua_gettop(L) == 200);
2152
lua_pop(L, 200);
2153
}
2154
2155
// baselib pcall variadic
2156
{
2157
lua_getfield(L, LUA_GLOBALSINDEX, "pcall");
2158
lua_getfield(L, LUA_GLOBALSINDEX, "getnresults");
2159
lua_pushinteger(L, 200);
2160
lua_call(L, 2, LUA_MULTRET);
2161
CHECK(lua_gettop(L) == 201);
2162
lua_pop(L, 200);
2163
CHECK(lua_toboolean(L, -1) == 1);
2164
lua_pop(L, 1);
2165
}
2166
2167
// lua_cpcall success
2168
{
2169
bool shouldFail = false;
2170
CHECK(lua_cpcall(L, cpcallTest, &shouldFail) == LUA_OK);
2171
CHECK(lua_status(L) == LUA_OK);
2172
2173
lua_getglobal(L, "cpcallvalue");
2174
CHECK(luaL_checkinteger(L, -1) == 123);
2175
lua_pop(L, 1);
2176
}
2177
2178
// lua_cpcall failure
2179
{
2180
bool shouldFail = true;
2181
CHECK(lua_cpcall(L, cpcallTest, &shouldFail) == LUA_ERRRUN);
2182
REQUIRE(lua_isstring(L, -1));
2183
CHECK(std::string(lua_tostring(L, -1)) == "Failed");
2184
lua_pop(L, 1);
2185
2186
CHECK(lua_status(L) == LUA_OK);
2187
}
2188
2189
// lua_cpcall early failure
2190
{
2191
bool shouldFail = false;
2192
2193
CHECK(lua_gettop(L) == 0);
2194
2195
luaL_checkstack(L, LUAI_MAXCSTACK - 1, "must succeed");
2196
2197
for (int i = 0; i < LUAI_MAXCSTACK - 1; i++)
2198
lua_pushnumber(L, 1.0);
2199
2200
CHECK(lua_cpcall(L, cpcallTest, &shouldFail) == LUA_ERRRUN);
2201
REQUIRE(lua_isstring(L, -1));
2202
CHECK(std::string(lua_tostring(L, -1)) == "stack limit");
2203
lua_pop(L, 1);
2204
2205
CHECK(lua_status(L) == LUA_OK);
2206
2207
lua_pop(L, LUAI_MAXCSTACK - 1);
2208
}
2209
2210
// lua_resume does not make thread yieldable
2211
{
2212
lua_State* L2 = lua_newthread(L);
2213
2214
lua_pushcclosurek(
2215
L2,
2216
[](lua_State* L)
2217
{
2218
CHECK(lua_isyieldable(L) == 0);
2219
return 0;
2220
},
2221
nullptr,
2222
0,
2223
nullptr
2224
);
2225
lua_call(L2, 0, 0);
2226
2227
lua_getfield(L2, LUA_GLOBALSINDEX, "getnresults");
2228
lua_pushinteger(L2, 1);
2229
int status = lua_resume(L2, nullptr, 1);
2230
REQUIRE(status == LUA_OK);
2231
CHECK(lua_gettop(L2) == 1);
2232
lua_pop(L2, 1);
2233
2234
lua_pushcclosurek(
2235
L2,
2236
[](lua_State* L)
2237
{
2238
CHECK(lua_isyieldable(L) == 0);
2239
return 0;
2240
},
2241
nullptr,
2242
0,
2243
nullptr
2244
);
2245
lua_call(L2, 0, 0);
2246
2247
lua_pop(L, 1);
2248
}
2249
2250
// lua_equal with a sleeping thread wake up
2251
{
2252
lua_State* L2 = lua_newthread(L);
2253
2254
lua_getfield(L2, LUA_GLOBALSINDEX, "create_with_tm");
2255
lua_pushnumber(L2, 42);
2256
lua_pcall(L2, 1, 1, 0);
2257
2258
lua_getfield(L2, LUA_GLOBALSINDEX, "create_with_tm");
2259
lua_pushnumber(L2, 42);
2260
lua_pcall(L2, 1, 1, 0);
2261
2262
// Reset GC
2263
lua_gc(L2, LUA_GCCOLLECT, 0);
2264
2265
// Try to mark 'L2' as sleeping
2266
// Can't control GC precisely, even in tests
2267
lua_gc(L2, LUA_GCSTEP, 8);
2268
2269
CHECK(lua_equal(L2, -1, -2) == 1);
2270
lua_pop(L2, 2);
2271
2272
lua_pop(L, 1);
2273
}
2274
2275
// lua_clonefunction + fenv
2276
{
2277
lua_getfield(L, LUA_GLOBALSINDEX, "getpi");
2278
lua_call(L, 0, 1);
2279
CHECK(lua_tonumber(L, -1) == 3.1415926);
2280
lua_pop(L, 1);
2281
2282
lua_getfield(L, LUA_GLOBALSINDEX, "getpi");
2283
2284
// clone & override env
2285
lua_clonefunction(L, -1);
2286
lua_newtable(L);
2287
lua_pushnumber(L, 42);
2288
lua_setfield(L, -2, "pi");
2289
lua_setfenv(L, -2);
2290
2291
lua_call(L, 0, 1);
2292
CHECK(lua_tonumber(L, -1) == 42);
2293
lua_pop(L, 1);
2294
2295
// this one calls original function again
2296
lua_call(L, 0, 1);
2297
CHECK(lua_tonumber(L, -1) == 3.1415926);
2298
lua_pop(L, 1);
2299
}
2300
2301
// lua_clonefunction + upvalues
2302
{
2303
lua_getfield(L, LUA_GLOBALSINDEX, "incuv");
2304
lua_call(L, 0, 1);
2305
CHECK(lua_tonumber(L, -1) == 1);
2306
lua_pop(L, 1);
2307
2308
lua_getfield(L, LUA_GLOBALSINDEX, "incuv");
2309
// two clones
2310
lua_clonefunction(L, -1);
2311
lua_clonefunction(L, -2);
2312
2313
lua_call(L, 0, 1);
2314
CHECK(lua_tonumber(L, -1) == 2);
2315
lua_pop(L, 1);
2316
2317
lua_call(L, 0, 1);
2318
CHECK(lua_tonumber(L, -1) == 3);
2319
lua_pop(L, 1);
2320
2321
// this one calls original function again
2322
lua_call(L, 0, 1);
2323
CHECK(lua_tonumber(L, -1) == 4);
2324
lua_pop(L, 1);
2325
}
2326
2327
// lua_pcall on OOM
2328
{
2329
lua_getfield(L, LUA_GLOBALSINDEX, "largealloc");
2330
int res = lua_pcall(L, 0, 0, 0);
2331
CHECK(res == LUA_ERRMEM);
2332
lua_pop(L, 1);
2333
}
2334
2335
// lua_pcall on OOM with an error handler
2336
{
2337
lua_getfield(L, LUA_GLOBALSINDEX, "oops");
2338
lua_getfield(L, LUA_GLOBALSINDEX, "largealloc");
2339
int res = lua_pcall(L, 0, 1, -2);
2340
CHECK(res == LUA_ERRMEM);
2341
CHECK((lua_isstring(L, -1) && strcmp(lua_tostring(L, -1), "oops") == 0));
2342
lua_pop(L, 2);
2343
}
2344
2345
// lua_pcall on OOM with an error handler that errors
2346
{
2347
lua_getfield(L, LUA_GLOBALSINDEX, "error");
2348
lua_getfield(L, LUA_GLOBALSINDEX, "largealloc");
2349
int res = lua_pcall(L, 0, 1, -2);
2350
CHECK(res == LUA_ERRERR);
2351
CHECK((lua_isstring(L, -1) && strcmp(lua_tostring(L, -1), "error in error handling") == 0));
2352
lua_pop(L, 2);
2353
}
2354
2355
// lua_pcall on OOM with an error handler that OOMs
2356
{
2357
lua_getfield(L, LUA_GLOBALSINDEX, "largealloc");
2358
lua_getfield(L, LUA_GLOBALSINDEX, "largealloc");
2359
int res = lua_pcall(L, 0, 1, -2);
2360
CHECK(res == LUA_ERRMEM);
2361
CHECK((lua_isstring(L, -1) && strcmp(lua_tostring(L, -1), "not enough memory") == 0));
2362
lua_pop(L, 2);
2363
}
2364
2365
// lua_pcall on error with an error handler that OOMs
2366
{
2367
lua_getfield(L, LUA_GLOBALSINDEX, "largealloc");
2368
lua_getfield(L, LUA_GLOBALSINDEX, "error");
2369
int res = lua_pcall(L, 0, 1, -2);
2370
CHECK(res == LUA_ERRERR);
2371
CHECK((lua_isstring(L, -1) && strcmp(lua_tostring(L, -1), "error in error handling") == 0));
2372
lua_pop(L, 2);
2373
}
2374
2375
CHECK(lua_gettop(L) == 0);
2376
}
2377
2378
TEST_CASE("ApiAtoms")
2379
{
2380
StateRef globalState(luaL_newstate(), lua_close);
2381
lua_State* L = globalState.get();
2382
2383
lua_callbacks(L)->useratom = [](lua_State* L, const char* s, size_t l) -> int16_t
2384
{
2385
if (strcmp(s, "string") == 0)
2386
return 0;
2387
if (strcmp(s, "important") == 0)
2388
return 1;
2389
2390
return -1;
2391
};
2392
2393
lua_pushstring(L, "string");
2394
lua_pushstring(L, "import");
2395
lua_pushstring(L, "ant");
2396
lua_concat(L, 2);
2397
lua_pushstring(L, "unimportant");
2398
2399
int a1, a2, a3;
2400
const char* s1 = lua_tostringatom(L, -3, &a1);
2401
const char* s2 = lua_tostringatom(L, -2, &a2);
2402
const char* s3 = lua_tostringatom(L, -1, &a3);
2403
2404
CHECK(strcmp(s1, "string") == 0);
2405
CHECK(a1 == 0);
2406
2407
CHECK(strcmp(s2, "important") == 0);
2408
CHECK(a2 == 1);
2409
2410
CHECK(strcmp(s3, "unimportant") == 0);
2411
CHECK(a3 == -1);
2412
}
2413
2414
static bool endsWith(const std::string& str, const std::string& suffix)
2415
{
2416
if (suffix.length() > str.length())
2417
return false;
2418
2419
return suffix == std::string_view(str.c_str() + str.length() - suffix.length(), suffix.length());
2420
}
2421
2422
TEST_CASE("ApiType")
2423
{
2424
StateRef globalState(luaL_newstate(), lua_close);
2425
lua_State* L = globalState.get();
2426
2427
lua_pushnumber(L, 2);
2428
CHECK(strcmp(luaL_typename(L, -1), "number") == 0);
2429
CHECK(strcmp(luaL_typename(L, 1), "number") == 0);
2430
CHECK(lua_type(L, -1) == LUA_TNUMBER);
2431
CHECK(lua_type(L, 1) == LUA_TNUMBER);
2432
2433
CHECK(strcmp(luaL_typename(L, 2), "no value") == 0);
2434
CHECK(lua_type(L, 2) == LUA_TNONE);
2435
CHECK(strcmp(lua_typename(L, lua_type(L, 2)), "no value") == 0);
2436
2437
lua_newuserdata(L, 0);
2438
CHECK(strcmp(luaL_typename(L, -1), "userdata") == 0);
2439
CHECK(lua_type(L, -1) == LUA_TUSERDATA);
2440
2441
lua_newtable(L);
2442
lua_pushstring(L, "hello");
2443
lua_setfield(L, -2, "__type");
2444
lua_setmetatable(L, -2);
2445
2446
CHECK(strcmp(luaL_typename(L, -1), "hello") == 0);
2447
CHECK(lua_type(L, -1) == LUA_TUSERDATA);
2448
}
2449
2450
TEST_CASE("ApiBuffer")
2451
{
2452
StateRef globalState(luaL_newstate(), lua_close);
2453
lua_State* L = globalState.get();
2454
2455
lua_newbuffer(L, 1000);
2456
2457
REQUIRE(lua_type(L, -1) == LUA_TBUFFER);
2458
2459
CHECK(lua_isbuffer(L, -1));
2460
CHECK(lua_objlen(L, -1) == 1000);
2461
2462
CHECK(strcmp(lua_typename(L, LUA_TBUFFER), "buffer") == 0);
2463
2464
CHECK(strcmp(luaL_typename(L, -1), "buffer") == 0);
2465
2466
void* p1 = lua_tobuffer(L, -1, nullptr);
2467
2468
size_t len = 0;
2469
void* p2 = lua_tobuffer(L, -1, &len);
2470
CHECK(len == 1000);
2471
CHECK(p1 == p2);
2472
2473
void* p3 = luaL_checkbuffer(L, -1, nullptr);
2474
CHECK(p1 == p3);
2475
2476
len = 0;
2477
void* p4 = luaL_checkbuffer(L, -1, &len);
2478
CHECK(len == 1000);
2479
CHECK(p1 == p4);
2480
2481
memset(p1, 0xab, 1000);
2482
2483
CHECK(lua_topointer(L, -1) != nullptr);
2484
2485
lua_newbuffer(L, 0);
2486
2487
lua_pushvalue(L, -2);
2488
2489
CHECK(lua_equal(L, -3, -1));
2490
CHECK(!lua_equal(L, -2, -1));
2491
2492
lua_pop(L, 1);
2493
}
2494
2495
int slowlyOverflowStack(lua_State* L)
2496
{
2497
for (int i = 0; i < LUAI_MAXCSTACK * 2; i++)
2498
{
2499
luaL_checkstack(L, 1, "test");
2500
lua_pushnumber(L, 1.0);
2501
}
2502
2503
return 0;
2504
}
2505
2506
TEST_CASE("ApiStack")
2507
{
2508
StateRef globalState(lua_newstate(blockableRealloc, nullptr), lua_close);
2509
lua_State* GL = globalState.get();
2510
2511
{
2512
lua_State* L = lua_newthread(GL);
2513
2514
lua_pushcfunction(L, slowlyOverflowStack, "foo");
2515
int result = lua_pcall(L, 0, 0, 0);
2516
REQUIRE(result == LUA_ERRRUN);
2517
CHECK(strcmp(luaL_checkstring(L, -1), "stack overflow (test)") == 0);
2518
}
2519
2520
{
2521
lua_State* L = lua_newthread(GL);
2522
2523
REQUIRE(lua_checkstack(L, 100) == 1);
2524
2525
blockableReallocAllowed = false;
2526
REQUIRE(lua_checkstack(L, 1000) == 0);
2527
blockableReallocAllowed = true;
2528
2529
REQUIRE(lua_checkstack(L, 1000) == 1);
2530
2531
REQUIRE(lua_checkstack(L, LUAI_MAXCSTACK * 2) == 0);
2532
}
2533
}
2534
2535
TEST_CASE("ApiAlloc")
2536
{
2537
int ud = 0;
2538
StateRef globalState(lua_newstate(limitedRealloc, &ud), lua_close);
2539
lua_State* L = globalState.get();
2540
2541
void* udCheck = nullptr;
2542
bool allocfIsSet = lua_getallocf(L, &udCheck) == limitedRealloc;
2543
CHECK(allocfIsSet);
2544
CHECK(udCheck == &ud);
2545
}
2546
2547
#if !LUA_USE_LONGJMP
2548
TEST_CASE("ExceptionObject")
2549
{
2550
struct ExceptionResult
2551
{
2552
bool exceptionGenerated;
2553
std::string description;
2554
};
2555
2556
auto captureException = [](lua_State* L, const char* functionToRun)
2557
{
2558
try
2559
{
2560
lua_State* threadState = lua_newthread(L);
2561
lua_getfield(threadState, LUA_GLOBALSINDEX, functionToRun);
2562
CHECK(lua_isLfunction(threadState, -1));
2563
lua_call(threadState, 0, 0);
2564
}
2565
catch (std::exception& e)
2566
{
2567
CHECK(e.what() != nullptr);
2568
return ExceptionResult{true, e.what()};
2569
}
2570
return ExceptionResult{false, ""};
2571
};
2572
2573
StateRef globalState = runConformance("exceptions.luau", nullptr, nullptr, lua_newstate(limitedRealloc, nullptr));
2574
lua_State* L = globalState.get();
2575
2576
{
2577
ExceptionResult result = captureException(L, "infinite_recursion_error");
2578
CHECK(result.exceptionGenerated);
2579
}
2580
2581
{
2582
ExceptionResult result = captureException(L, "empty_function");
2583
CHECK_FALSE(result.exceptionGenerated);
2584
}
2585
2586
{
2587
ExceptionResult result = captureException(L, "pass_number_to_error");
2588
CHECK(result.exceptionGenerated);
2589
CHECK(endsWith(result.description, "42"));
2590
}
2591
2592
{
2593
ExceptionResult result = captureException(L, "pass_string_to_error");
2594
CHECK(result.exceptionGenerated);
2595
CHECK(endsWith(result.description, "string argument"));
2596
}
2597
2598
{
2599
ExceptionResult result = captureException(L, "pass_table_to_error");
2600
CHECK(result.exceptionGenerated);
2601
}
2602
2603
{
2604
ExceptionResult result = captureException(L, "large_allocation_error");
2605
CHECK(result.exceptionGenerated);
2606
}
2607
}
2608
#endif
2609
2610
TEST_CASE("IfElseExpression")
2611
{
2612
runConformance("ifelseexpr.luau");
2613
}
2614
2615
// Optionally returns debug info for the first Luau stack frame that is encountered on the callstack.
2616
static std::optional<lua_Debug> getFirstLuauFrameDebugInfo(lua_State* L)
2617
{
2618
static std::string_view kLua = "Lua";
2619
lua_Debug ar;
2620
for (int i = 0; lua_getinfo(L, i, "sl", &ar); i++)
2621
{
2622
if (kLua == ar.what)
2623
return ar;
2624
}
2625
return std::nullopt;
2626
}
2627
2628
TEST_CASE("TagMethodError")
2629
{
2630
static std::vector<int> expectedHits;
2631
2632
// Loop over two modes:
2633
// when doLuaBreak is false the test only verifies that callbacks occur on the expected lines in the Luau source
2634
// when doLuaBreak is true the test additionally calls lua_break to ensure breaking the debugger doesn't cause the VM to crash
2635
for (bool doLuaBreak : {false, true})
2636
{
2637
expectedHits = {37, 54, 73};
2638
2639
static int index;
2640
static bool luaBreak;
2641
index = 0;
2642
luaBreak = doLuaBreak;
2643
2644
// To restore from a protected error break, we return 'true' so that test runner will use lua_resumeerror
2645
auto yieldCallback = [](lua_State* L) -> bool
2646
{
2647
return true;
2648
};
2649
2650
runConformance(
2651
"tmerror.luau",
2652
[](lua_State* L)
2653
{
2654
auto* cb = lua_callbacks(L);
2655
2656
cb->debugprotectederror = [](lua_State* L)
2657
{
2658
std::optional<lua_Debug> ar = getFirstLuauFrameDebugInfo(L);
2659
2660
CHECK(lua_isyieldable(L));
2661
REQUIRE(ar.has_value());
2662
REQUIRE(index < int(std::size(expectedHits)));
2663
CHECK(ar->currentline == expectedHits[index++]);
2664
2665
if (luaBreak)
2666
{
2667
// Cause luau execution to break when 'error' is called via 'pcall'
2668
// This call to lua_break is a regression test for an issue where debugprotectederror
2669
// was called on a thread that couldn't be yielded even though lua_isyieldable was true.
2670
lua_break(L);
2671
}
2672
};
2673
},
2674
yieldCallback
2675
);
2676
2677
// Make sure the number of break points hit was the expected number
2678
CHECK(index == std::size(expectedHits));
2679
}
2680
}
2681
2682
TEST_CASE("Coverage")
2683
{
2684
lua_CompileOptions copts = defaultOptions();
2685
copts.optimizationLevel = 1; // disable inlining to get fixed expected hit results
2686
copts.coverageLevel = 2;
2687
2688
runConformance(
2689
"coverage.luau",
2690
[](lua_State* L)
2691
{
2692
lua_pushcfunction(
2693
L,
2694
[](lua_State* L) -> int
2695
{
2696
luaL_argexpected(L, lua_isLfunction(L, 1), 1, "function");
2697
2698
lua_newtable(L);
2699
lua_getcoverage(
2700
L,
2701
1,
2702
L,
2703
[](void* context, const char* function, int linedefined, int depth, const int* hits, size_t size)
2704
{
2705
lua_State* L = static_cast<lua_State*>(context);
2706
2707
lua_newtable(L);
2708
2709
lua_pushstring(L, function);
2710
lua_setfield(L, -2, "name");
2711
2712
lua_pushinteger(L, linedefined);
2713
lua_setfield(L, -2, "linedefined");
2714
2715
lua_pushinteger(L, depth);
2716
lua_setfield(L, -2, "depth");
2717
2718
for (size_t i = 0; i < size; ++i)
2719
if (hits[i] != -1)
2720
{
2721
lua_pushinteger(L, hits[i]);
2722
lua_rawseti(L, -2, int(i));
2723
}
2724
2725
lua_rawseti(L, -2, lua_objlen(L, -2) + 1);
2726
}
2727
);
2728
2729
return 1;
2730
},
2731
"getcoverage"
2732
);
2733
lua_setglobal(L, "getcoverage");
2734
},
2735
nullptr,
2736
nullptr,
2737
&copts
2738
);
2739
}
2740
2741
TEST_CASE("StringConversion")
2742
{
2743
runConformance("strconv.luau");
2744
}
2745
2746
TEST_CASE("GCDump")
2747
{
2748
// internal function, declared in lgc.h - not exposed via lua.h
2749
extern void luaC_dump(lua_State * L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat));
2750
extern void luaC_enumheap(
2751
lua_State * L,
2752
void* context,
2753
void (*node)(void* context, void* ptr, uint8_t tt, uint8_t memcat, size_t size, const char* name),
2754
void (*edge)(void* context, void* from, void* to, const char* name)
2755
);
2756
2757
StateRef globalState(luaL_newstate(), lua_close);
2758
lua_State* L = globalState.get();
2759
2760
// push various objects on stack to cover different paths
2761
lua_createtable(L, 1, 2);
2762
lua_pushstring(L, "value");
2763
lua_setfield(L, -2, "key");
2764
2765
lua_pushstring(L, "u42");
2766
lua_setfield(L, -2, "__type");
2767
2768
lua_pushinteger(L, 42);
2769
lua_rawseti(L, -2, 1000);
2770
2771
lua_pushinteger(L, 42);
2772
lua_rawseti(L, -2, 1);
2773
2774
lua_pushvalue(L, -1);
2775
lua_setmetatable(L, -2);
2776
2777
lua_newuserdata(L, 42);
2778
lua_pushvalue(L, -2);
2779
lua_setmetatable(L, -2);
2780
2781
lua_pushinteger(L, 1);
2782
lua_pushcclosure(L, lua_silence, "test", 1);
2783
2784
lua_newbuffer(L, 100);
2785
2786
lua_State* CL = lua_newthread(L);
2787
2788
lua_pushstring(CL, R"(
2789
local x
2790
x = {}
2791
local function f()
2792
x[1] = math.abs(42)
2793
end
2794
function foo()
2795
x[2] = ''
2796
for i = 1, 10000 do x[2] ..= '1234567890' end
2797
end
2798
foo()
2799
return f
2800
)");
2801
lua_pushstring(CL, "=GCDump");
2802
REQUIRE(lua_loadstring(CL) == 1);
2803
REQUIRE(lua_resume(CL, nullptr, 0) == LUA_OK);
2804
2805
#ifdef _WIN32
2806
const char* path = "NUL";
2807
#else
2808
const char* path = "/dev/null";
2809
#endif
2810
2811
FILE* f = fopen(path, "w");
2812
REQUIRE(f);
2813
2814
luaC_fullgc(L);
2815
luaC_dump(L, f, nullptr);
2816
2817
fclose(f);
2818
2819
struct Node
2820
{
2821
void* ptr;
2822
uint8_t tag;
2823
uint8_t memcat;
2824
size_t size;
2825
std::string name;
2826
};
2827
2828
struct EnumContext
2829
{
2830
Luau::DenseHashMap<void*, Node> nodes{nullptr};
2831
Luau::DenseHashMap<void*, void*> edges{nullptr};
2832
2833
bool seenTargetString = false;
2834
} ctx;
2835
2836
luaC_enumheap(
2837
L,
2838
&ctx,
2839
[](void* ctx, void* gco, uint8_t tt, uint8_t memcat, size_t size, const char* name)
2840
{
2841
EnumContext& context = *(EnumContext*)ctx;
2842
2843
if (name)
2844
{
2845
std::string_view sv{name};
2846
2847
if (tt == LUA_TUSERDATA)
2848
CHECK(sv == "u42");
2849
else if (tt == LUA_TPROTO)
2850
CHECK((sv == "proto unnamed:1 =GCDump" || sv == "proto foo:7 =GCDump" || sv == "proto f:4 =GCDump"));
2851
else if (tt == LUA_TFUNCTION)
2852
CHECK((sv == "test" || sv == "unnamed:1 =GCDump" || sv == "foo:7 =GCDump" || sv == "f:4 =GCDump"));
2853
else if (tt == LUA_TTHREAD)
2854
CHECK(sv == "thread at unnamed:1 =GCDump");
2855
}
2856
else if (tt == LUA_TSTRING && size >= 100000)
2857
{
2858
CHECK(!context.seenTargetString);
2859
context.seenTargetString = true;
2860
2861
// The only string we have in this test that is 100000 characters long should include string data overhead
2862
CHECK(size > 100000);
2863
}
2864
2865
context.nodes[gco] = {gco, tt, memcat, size, name ? name : ""};
2866
},
2867
[](void* ctx, void* s, void* t, const char*)
2868
{
2869
EnumContext& context = *(EnumContext*)ctx;
2870
context.edges[s] = t;
2871
}
2872
);
2873
2874
CHECK(!ctx.nodes.empty());
2875
CHECK(!ctx.edges.empty());
2876
CHECK(ctx.seenTargetString);
2877
}
2878
2879
TEST_CASE("Interrupt")
2880
{
2881
lua_CompileOptions copts = defaultOptions();
2882
copts.optimizationLevel = 1; // disable loop unrolling to get fixed expected hit results
2883
2884
static int index;
2885
2886
StateRef globalState = runConformance("interrupt.luau", nullptr, nullptr, nullptr, &copts);
2887
2888
lua_State* L = globalState.get();
2889
2890
// note: for simplicity here we setup the interrupt callback when the test starts
2891
// however, this carries a noticeable performance cost. in a real application,
2892
// it's advised to set interrupt callback on a timer from a different thread,
2893
// and set it back to nullptr once the interrupt triggered.
2894
2895
// define the interrupt to check the expected hits
2896
static const int expectedhits[] = {11, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 20, 15, 15, 15, 15, 18, 25, 23, 26};
2897
2898
lua_callbacks(L)->interrupt = [](lua_State* L, int gc)
2899
{
2900
if (gc >= 0)
2901
return;
2902
2903
CHECK(index < int(std::size(expectedhits)));
2904
2905
lua_Debug ar = {};
2906
lua_getinfo(L, 0, "l", &ar);
2907
2908
CHECK(ar.currentline == expectedhits[index]);
2909
2910
index++;
2911
2912
// check that we can yield inside an interrupt
2913
if (index == 4)
2914
lua_yield(L, 0);
2915
};
2916
2917
{
2918
lua_State* T = lua_newthread(L);
2919
2920
lua_getglobal(T, "test");
2921
2922
index = 0;
2923
int status = lua_resume(T, nullptr, 0);
2924
CHECK(status == LUA_YIELD);
2925
CHECK(index == 4);
2926
2927
status = lua_resume(T, nullptr, 0);
2928
CHECK(status == LUA_OK);
2929
CHECK(index == int(std::size(expectedhits)));
2930
2931
lua_pop(L, 1);
2932
}
2933
2934
// redefine the interrupt to break after 10 iterations of a loop that would otherwise be infinite
2935
// the test exposes a few global functions that we will call; the interrupt will force a yield
2936
lua_callbacks(L)->interrupt = [](lua_State* L, int gc)
2937
{
2938
if (gc >= 0)
2939
return;
2940
2941
CHECK(index < 11);
2942
if (++index == 11)
2943
lua_yield(L, 0);
2944
};
2945
2946
for (int test = 1; test <= 10; ++test)
2947
{
2948
lua_State* T = lua_newthread(L);
2949
2950
std::string name = "infloop" + std::to_string(test);
2951
lua_getglobal(T, name.c_str());
2952
2953
index = 0;
2954
int status = lua_resume(T, nullptr, 0);
2955
CHECK(status == LUA_YIELD);
2956
CHECK(index == 11);
2957
2958
// abandon the thread
2959
lua_pop(L, 1);
2960
}
2961
2962
lua_callbacks(L)->interrupt = [](lua_State* L, int gc)
2963
{
2964
if (gc >= 0)
2965
return;
2966
2967
index++;
2968
2969
if (index == 1'000)
2970
{
2971
index = 0;
2972
luaL_error(L, "timeout");
2973
}
2974
};
2975
2976
for (int test = 1; test <= 6; ++test)
2977
{
2978
lua_State* T = lua_newthread(L);
2979
2980
std::string name = "hang" + std::to_string(test);
2981
lua_getglobal(T, name.c_str());
2982
2983
index = 0;
2984
int status = lua_resume(T, nullptr, 0);
2985
CHECK(status == LUA_ERRRUN);
2986
CHECK(strstr(luaL_checkstring(T, -1), "timeout") != nullptr);
2987
2988
lua_pop(L, 1);
2989
}
2990
2991
{
2992
lua_State* T = lua_newthread(L);
2993
2994
lua_getglobal(T, "hangpcall");
2995
2996
index = 0;
2997
int status = lua_resume(T, nullptr, 0);
2998
CHECK(status == LUA_OK);
2999
3000
lua_pop(L, 1);
3001
}
3002
}
3003
3004
TEST_CASE("UserdataApi")
3005
{
3006
static int dtorhits = 0;
3007
3008
dtorhits = 0;
3009
3010
StateRef globalState(luaL_newstate(), lua_close);
3011
lua_State* L = globalState.get();
3012
3013
// setup dtor for tag 42 (created later)
3014
auto dtor = [](lua_State* l, void* data)
3015
{
3016
dtorhits += *(int*)data;
3017
};
3018
bool dtorIsNull = lua_getuserdatadtor(L, 42) == nullptr;
3019
CHECK(dtorIsNull);
3020
lua_setuserdatadtor(L, 42, dtor);
3021
bool dtorIsSet = lua_getuserdatadtor(L, 42) == dtor;
3022
CHECK(dtorIsSet);
3023
3024
// light user data
3025
int lud;
3026
lua_pushlightuserdata(L, &lud);
3027
3028
CHECK(lua_tolightuserdata(L, -1) == &lud);
3029
CHECK(lua_touserdata(L, -1) == &lud);
3030
CHECK(lua_topointer(L, -1) == &lud);
3031
3032
// regular user data
3033
int* ud1 = (int*)lua_newuserdata(L, 4);
3034
*ud1 = 42;
3035
3036
CHECK(lua_tolightuserdata(L, -1) == nullptr);
3037
CHECK(lua_touserdata(L, -1) == ud1);
3038
CHECK(lua_topointer(L, -1) == ud1);
3039
3040
// tagged user data
3041
int* ud2 = (int*)lua_newuserdatatagged(L, 4, 42);
3042
*ud2 = -4;
3043
3044
CHECK(lua_touserdatatagged(L, -1, 42) == ud2);
3045
CHECK(lua_touserdatatagged(L, -1, 41) == nullptr);
3046
CHECK(lua_userdatatag(L, -1) == 42);
3047
3048
lua_setuserdatatag(L, -1, 43);
3049
CHECK(lua_userdatatag(L, -1) == 43);
3050
lua_setuserdatatag(L, -1, 42);
3051
3052
// user data with inline dtor
3053
void* ud3 = lua_newuserdatadtor(
3054
L,
3055
4,
3056
[](void* data)
3057
{
3058
dtorhits += *(int*)data;
3059
}
3060
);
3061
3062
void* ud4 = lua_newuserdatadtor(
3063
L,
3064
1,
3065
[](void* data)
3066
{
3067
dtorhits += *(char*)data;
3068
}
3069
);
3070
3071
*(int*)ud3 = 43;
3072
*(char*)ud4 = 3;
3073
3074
// user data with named metatable
3075
luaL_newmetatable(L, "udata1");
3076
luaL_newmetatable(L, "udata2");
3077
3078
void* ud5 = lua_newuserdata(L, 0);
3079
luaL_getmetatable(L, "udata1");
3080
lua_setmetatable(L, -2);
3081
3082
void* ud6 = lua_newuserdata(L, 0);
3083
luaL_getmetatable(L, "udata2");
3084
lua_setmetatable(L, -2);
3085
3086
CHECK(luaL_checkudata(L, -2, "udata1") == ud5);
3087
CHECK(luaL_checkudata(L, -1, "udata2") == ud6);
3088
3089
// tagged user data with fast metatable access
3090
luaL_newmetatable(L, "udata3");
3091
lua_pushvalue(L, -1);
3092
lua_setuserdatametatable(L, 50);
3093
3094
luaL_newmetatable(L, "udata4");
3095
lua_pushvalue(L, -1);
3096
lua_setuserdatametatable(L, 51);
3097
3098
void* ud7 = lua_newuserdatatagged(L, 16, 50);
3099
lua_getuserdatametatable(L, 50);
3100
lua_setmetatable(L, -2);
3101
3102
void* ud8 = lua_newuserdatataggedwithmetatable(L, 16, 51);
3103
3104
CHECK(luaL_checkudata(L, -2, "udata3") == ud7);
3105
CHECK(luaL_checkudata(L, -1, "udata4") == ud8);
3106
3107
globalState.reset();
3108
3109
CHECK(dtorhits == 42);
3110
}
3111
3112
// provide alignment of 16 for userdata objects with size of 16 and up as long as the Luau allocation functions supports it
3113
TEST_CASE("UserdataAlignment")
3114
{
3115
const auto testAllocate = [](void* ud, void* ptr, size_t osize, size_t nsize) -> void*
3116
{
3117
if (nsize == 0)
3118
{
3119
::operator delete(ptr, std::align_val_t(16));
3120
return nullptr;
3121
}
3122
else if (osize == 0)
3123
{
3124
return ::operator new(nsize, std::align_val_t(16));
3125
}
3126
3127
// resize is unreachable in this test and is omitted
3128
return nullptr;
3129
};
3130
3131
StateRef globalState(lua_newstate(testAllocate, nullptr), lua_close);
3132
lua_State* L = globalState.get();
3133
3134
for (int size = 16; size <= 4096; size += 4)
3135
{
3136
for (int i = 0; i < 10; i++)
3137
{
3138
void* data = lua_newuserdata(L, size);
3139
LUAU_ASSERT(uintptr_t(data) % 16 == 0);
3140
lua_pop(L, 1);
3141
}
3142
3143
for (int i = 0; i < 10; i++)
3144
{
3145
void* data = lua_newuserdatadtor(L, size, [](void*) {});
3146
LUAU_ASSERT(uintptr_t(data) % 16 == 0);
3147
lua_pop(L, 1);
3148
}
3149
}
3150
}
3151
3152
TEST_CASE("LightuserdataApi")
3153
{
3154
StateRef globalState(luaL_newstate(), lua_close);
3155
lua_State* L = globalState.get();
3156
3157
void* value = (void*)0x12345678;
3158
3159
lua_pushlightuserdatatagged(L, value, 1);
3160
CHECK(lua_lightuserdatatag(L, -1) == 1);
3161
CHECK(lua_tolightuserdatatagged(L, -1, 0) == nullptr);
3162
CHECK(lua_tolightuserdatatagged(L, -1, 1) == value);
3163
3164
lua_setlightuserdataname(L, 1, "id");
3165
CHECK(!lua_getlightuserdataname(L, 0));
3166
CHECK(strcmp(lua_getlightuserdataname(L, 1), "id") == 0);
3167
CHECK(strcmp(luaL_typename(L, -1), "id") == 0);
3168
lua_pop(L, 1);
3169
3170
lua_pushlightuserdatatagged(L, value, 0);
3171
lua_pushlightuserdatatagged(L, value, 1);
3172
CHECK(lua_rawequal(L, -1, -2) == 0);
3173
lua_pop(L, 2);
3174
3175
// Check lightuserdata table key uniqueness
3176
lua_newtable(L);
3177
3178
lua_pushlightuserdatatagged(L, value, 2);
3179
lua_pushinteger(L, 20);
3180
lua_settable(L, -3);
3181
lua_pushlightuserdatatagged(L, value, 3);
3182
lua_pushinteger(L, 30);
3183
lua_settable(L, -3);
3184
3185
lua_pushlightuserdatatagged(L, value, 2);
3186
lua_gettable(L, -2);
3187
lua_pushinteger(L, 20);
3188
CHECK(lua_rawequal(L, -1, -2) == 1);
3189
lua_pop(L, 2);
3190
3191
lua_pushlightuserdatatagged(L, value, 3);
3192
lua_gettable(L, -2);
3193
lua_pushinteger(L, 30);
3194
CHECK(lua_rawequal(L, -1, -2) == 1);
3195
lua_pop(L, 2);
3196
3197
lua_pop(L, 1);
3198
3199
// Still possible to rename the global lightuserdata name using a metatable
3200
lua_pushlightuserdata(L, value);
3201
CHECK(strcmp(luaL_typename(L, -1), "userdata") == 0);
3202
3203
lua_createtable(L, 0, 1);
3204
lua_pushstring(L, "luserdata");
3205
lua_setfield(L, -2, "__type");
3206
lua_setmetatable(L, -2);
3207
3208
CHECK(strcmp(luaL_typename(L, -1), "luserdata") == 0);
3209
lua_pop(L, 1);
3210
3211
globalState.reset();
3212
}
3213
3214
TEST_CASE("DebugApi")
3215
{
3216
StateRef globalState(luaL_newstate(), lua_close);
3217
lua_State* L = globalState.get();
3218
3219
lua_pushnumber(L, 10);
3220
3221
lua_Debug ar;
3222
CHECK(lua_getinfo(L, -1, "f", &ar) == 0); // number is not a function
3223
CHECK(lua_getinfo(L, -10, "f", &ar) == 0); // not on stack
3224
}
3225
3226
TEST_CASE("Iter")
3227
{
3228
runConformance("iter.luau");
3229
}
3230
3231
const int kInt64Tag = 1;
3232
3233
static int64_t getInt64(lua_State* L, int idx)
3234
{
3235
if (void* p = lua_touserdatatagged(L, idx, kInt64Tag))
3236
return *static_cast<int64_t*>(p);
3237
3238
if (lua_isnumber(L, idx))
3239
return lua_tointeger(L, idx);
3240
3241
luaL_typeerror(L, 1, "int64");
3242
}
3243
3244
static void pushInt64(lua_State* L, int64_t value)
3245
{
3246
void* p = lua_newuserdatatagged(L, sizeof(int64_t), kInt64Tag);
3247
3248
luaL_getmetatable(L, "int64");
3249
lua_setmetatable(L, -2);
3250
3251
*static_cast<int64_t*>(p) = value;
3252
}
3253
3254
TEST_CASE("Userdata")
3255
{
3256
runConformance(
3257
"userdata.luau",
3258
[](lua_State* L)
3259
{
3260
// create metatable with all the metamethods
3261
luaL_newmetatable(L, "int64");
3262
3263
// __index
3264
lua_pushcfunction(
3265
L,
3266
[](lua_State* L)
3267
{
3268
void* p = lua_touserdatatagged(L, 1, kInt64Tag);
3269
if (!p)
3270
luaL_typeerror(L, 1, "int64");
3271
3272
const char* name = luaL_checkstring(L, 2);
3273
3274
if (strcmp(name, "value") == 0)
3275
{
3276
lua_pushnumber(L, double(*static_cast<int64_t*>(p)));
3277
return 1;
3278
}
3279
3280
luaL_error(L, "unknown field %s", name);
3281
},
3282
nullptr
3283
);
3284
lua_setfield(L, -2, "__index");
3285
3286
// __newindex
3287
lua_pushcfunction(
3288
L,
3289
[](lua_State* L)
3290
{
3291
void* p = lua_touserdatatagged(L, 1, kInt64Tag);
3292
if (!p)
3293
luaL_typeerror(L, 1, "int64");
3294
3295
const char* name = luaL_checkstring(L, 2);
3296
3297
if (strcmp(name, "value") == 0)
3298
{
3299
double value = luaL_checknumber(L, 3);
3300
*static_cast<int64_t*>(p) = int64_t(value);
3301
return 0;
3302
}
3303
3304
luaL_error(L, "unknown field %s", name);
3305
},
3306
nullptr
3307
);
3308
lua_setfield(L, -2, "__newindex");
3309
3310
// __eq
3311
lua_pushcfunction(
3312
L,
3313
[](lua_State* L)
3314
{
3315
lua_pushboolean(L, getInt64(L, 1) == getInt64(L, 2));
3316
return 1;
3317
},
3318
nullptr
3319
);
3320
lua_setfield(L, -2, "__eq");
3321
3322
// __lt
3323
lua_pushcfunction(
3324
L,
3325
[](lua_State* L)
3326
{
3327
lua_pushboolean(L, getInt64(L, 1) < getInt64(L, 2));
3328
return 1;
3329
},
3330
nullptr
3331
);
3332
lua_setfield(L, -2, "__lt");
3333
3334
// __le
3335
lua_pushcfunction(
3336
L,
3337
[](lua_State* L)
3338
{
3339
lua_pushboolean(L, getInt64(L, 1) <= getInt64(L, 2));
3340
return 1;
3341
},
3342
nullptr
3343
);
3344
lua_setfield(L, -2, "__le");
3345
3346
// __add
3347
lua_pushcfunction(
3348
L,
3349
[](lua_State* L)
3350
{
3351
pushInt64(L, getInt64(L, 1) + getInt64(L, 2));
3352
return 1;
3353
},
3354
nullptr
3355
);
3356
lua_setfield(L, -2, "__add");
3357
3358
// __sub
3359
lua_pushcfunction(
3360
L,
3361
[](lua_State* L)
3362
{
3363
pushInt64(L, getInt64(L, 1) - getInt64(L, 2));
3364
return 1;
3365
},
3366
nullptr
3367
);
3368
lua_setfield(L, -2, "__sub");
3369
3370
// __mul
3371
lua_pushcfunction(
3372
L,
3373
[](lua_State* L)
3374
{
3375
pushInt64(L, getInt64(L, 1) * getInt64(L, 2));
3376
return 1;
3377
},
3378
nullptr
3379
);
3380
lua_setfield(L, -2, "__mul");
3381
3382
// __div
3383
lua_pushcfunction(
3384
L,
3385
[](lua_State* L)
3386
{
3387
// ideally we'd guard against 0 but it's a test so eh
3388
pushInt64(L, getInt64(L, 1) / getInt64(L, 2));
3389
return 1;
3390
},
3391
nullptr
3392
);
3393
lua_setfield(L, -2, "__div");
3394
3395
// __idiv
3396
lua_pushcfunction(
3397
L,
3398
[](lua_State* L)
3399
{
3400
// for testing we use different semantics here compared to __div: __idiv rounds to negative inf, __div truncates (rounds to zero)
3401
// additionally, division loses precision here outside of 2^53 range
3402
// we do not necessarily recommend this behavior in production code!
3403
pushInt64(L, int64_t(floor(double(getInt64(L, 1)) / double(getInt64(L, 2)))));
3404
return 1;
3405
},
3406
nullptr
3407
);
3408
lua_setfield(L, -2, "__idiv");
3409
3410
// __mod
3411
lua_pushcfunction(
3412
L,
3413
[](lua_State* L)
3414
{
3415
// ideally we'd guard against 0 and INT64_MIN but it's a test so eh
3416
pushInt64(L, getInt64(L, 1) % getInt64(L, 2));
3417
return 1;
3418
},
3419
nullptr
3420
);
3421
lua_setfield(L, -2, "__mod");
3422
3423
// __pow
3424
lua_pushcfunction(
3425
L,
3426
[](lua_State* L)
3427
{
3428
pushInt64(L, int64_t(pow(double(getInt64(L, 1)), double(getInt64(L, 2)))));
3429
return 1;
3430
},
3431
nullptr
3432
);
3433
lua_setfield(L, -2, "__pow");
3434
3435
// __unm
3436
lua_pushcfunction(
3437
L,
3438
[](lua_State* L)
3439
{
3440
pushInt64(L, -getInt64(L, 1));
3441
return 1;
3442
},
3443
nullptr
3444
);
3445
lua_setfield(L, -2, "__unm");
3446
3447
// __tostring
3448
lua_pushcfunction(
3449
L,
3450
[](lua_State* L)
3451
{
3452
int64_t value = getInt64(L, 1);
3453
std::string str = std::to_string(value);
3454
lua_pushlstring(L, str.c_str(), str.length());
3455
return 1;
3456
},
3457
nullptr
3458
);
3459
lua_setfield(L, -2, "__tostring");
3460
3461
// ctor
3462
lua_pushcfunction(
3463
L,
3464
[](lua_State* L)
3465
{
3466
double v = luaL_checknumber(L, 1);
3467
pushInt64(L, int64_t(v));
3468
return 1;
3469
},
3470
"int64"
3471
);
3472
lua_setglobal(L, "int64");
3473
}
3474
);
3475
}
3476
3477
TEST_CASE("SafeEnv")
3478
{
3479
runConformance("safeenv.luau");
3480
}
3481
3482
TEST_CASE("Native")
3483
{
3484
// This tests requires code to run natively, otherwise all 'is_native' checks will fail
3485
if (!codegen || !luau_codegen_supported())
3486
return;
3487
3488
lua_CompileOptions copts = defaultOptions();
3489
3490
SUBCASE("Checked")
3491
{
3492
FFlag::DebugLuauAbortingChecks.value = true;
3493
3494
SUBCASE("O0")
3495
{
3496
copts.optimizationLevel = 0;
3497
}
3498
SUBCASE("O1")
3499
{
3500
copts.optimizationLevel = 1;
3501
}
3502
SUBCASE("O2")
3503
{
3504
copts.optimizationLevel = 2;
3505
}
3506
}
3507
3508
SUBCASE("Regular")
3509
{
3510
FFlag::DebugLuauAbortingChecks.value = false;
3511
3512
SUBCASE("O0")
3513
{
3514
copts.optimizationLevel = 0;
3515
}
3516
SUBCASE("O1")
3517
{
3518
copts.optimizationLevel = 1;
3519
}
3520
SUBCASE("O2")
3521
{
3522
copts.optimizationLevel = 2;
3523
}
3524
}
3525
3526
runConformance(
3527
"native.luau",
3528
[](lua_State* L)
3529
{
3530
setupNativeHelpers(L);
3531
},
3532
nullptr,
3533
nullptr,
3534
&copts
3535
);
3536
}
3537
3538
TEST_CASE("NativeIntegerSpills")
3539
{
3540
lua_CompileOptions copts = defaultOptions();
3541
3542
SUBCASE("O0")
3543
{
3544
copts.optimizationLevel = 0;
3545
}
3546
SUBCASE("O1")
3547
{
3548
copts.optimizationLevel = 1;
3549
}
3550
SUBCASE("O2")
3551
{
3552
copts.optimizationLevel = 2;
3553
}
3554
3555
runConformance("native_integer_spills.luau", nullptr, nullptr, nullptr, &copts);
3556
}
3557
3558
TEST_CASE("NativeTypeAnnotations")
3559
{
3560
// This tests requires code to run natively, otherwise all 'is_native' checks will fail
3561
if (!codegen || !luau_codegen_supported())
3562
return;
3563
3564
runConformance(
3565
"native_types.luau",
3566
[](lua_State* L)
3567
{
3568
setupNativeHelpers(L);
3569
setupVectorHelpers(L);
3570
}
3571
);
3572
}
3573
3574
TEST_CASE("NativeUserdata")
3575
{
3576
lua_CompileOptions copts = defaultOptions();
3577
Luau::CodeGen::CompilationOptions nativeOpts = defaultCodegenOptions();
3578
3579
static const char* kUserdataCompileTypes[] = {"vec2", "color", "mat3", "vertex", nullptr};
3580
copts.userdataTypes = kUserdataCompileTypes;
3581
3582
SUBCASE("NoIrHooks")
3583
{
3584
SUBCASE("O0")
3585
{
3586
copts.optimizationLevel = 0;
3587
}
3588
SUBCASE("O1")
3589
{
3590
copts.optimizationLevel = 1;
3591
}
3592
SUBCASE("O2")
3593
{
3594
copts.optimizationLevel = 2;
3595
}
3596
}
3597
SUBCASE("IrHooks")
3598
{
3599
nativeOpts.hooks.vectorAccessBytecodeType = vectorAccessBytecodeType;
3600
nativeOpts.hooks.vectorNamecallBytecodeType = vectorNamecallBytecodeType;
3601
nativeOpts.hooks.vectorAccess = vectorAccess;
3602
nativeOpts.hooks.vectorNamecall = vectorNamecall;
3603
3604
nativeOpts.hooks.userdataAccessBytecodeType = userdataAccessBytecodeType;
3605
nativeOpts.hooks.userdataMetamethodBytecodeType = userdataMetamethodBytecodeType;
3606
nativeOpts.hooks.userdataNamecallBytecodeType = userdataNamecallBytecodeType;
3607
nativeOpts.hooks.userdataAccess = userdataAccess;
3608
nativeOpts.hooks.userdataMetamethod = userdataMetamethod;
3609
nativeOpts.hooks.userdataNamecall = userdataNamecall;
3610
3611
nativeOpts.userdataTypes = kUserdataRunTypes;
3612
3613
SUBCASE("O0")
3614
{
3615
copts.optimizationLevel = 0;
3616
}
3617
SUBCASE("O1")
3618
{
3619
copts.optimizationLevel = 1;
3620
}
3621
SUBCASE("O2")
3622
{
3623
copts.optimizationLevel = 2;
3624
}
3625
}
3626
3627
runConformance(
3628
"native_userdata.luau",
3629
[](lua_State* L)
3630
{
3631
Luau::CodeGen::setUserdataRemapper(
3632
L,
3633
kUserdataRunTypes,
3634
[](void* context, const char* str, size_t len) -> uint8_t
3635
{
3636
const char** types = (const char**)context;
3637
3638
uint8_t index = 0;
3639
3640
std::string_view sv{str, len};
3641
3642
for (; *types; ++types)
3643
{
3644
if (sv == *types)
3645
return index;
3646
3647
index++;
3648
}
3649
3650
return 0xff;
3651
}
3652
);
3653
3654
setupVectorHelpers(L);
3655
setupUserdataHelpers(L);
3656
},
3657
nullptr,
3658
nullptr,
3659
&copts,
3660
false,
3661
&nativeOpts
3662
);
3663
}
3664
3665
[[nodiscard]] static std::string makeHugeFunctionSource()
3666
{
3667
std::string source;
3668
3669
// add non-executed block that requires JUMPKX and generates a lot of constants that take available short (15-bit) constant space
3670
source += "if ... then\n";
3671
source += "local _ = {\n";
3672
3673
for (int i = 0; i < 40000; ++i)
3674
{
3675
source += "0.";
3676
source += std::to_string(i);
3677
source += ",";
3678
}
3679
3680
source += "}\n";
3681
source += "end\n";
3682
3683
// use failed fast-calls with imports and constants to exercise all of the more complex fallback sequences
3684
source += "return bit32.lshift('84', -1)";
3685
3686
return source;
3687
}
3688
3689
TEST_CASE("HugeFunction")
3690
{
3691
std::string source = makeHugeFunctionSource();
3692
3693
StateRef globalState(luaL_newstate(), lua_close);
3694
lua_State* L = globalState.get();
3695
3696
if (codegen && luau_codegen_supported())
3697
luau_codegen_create(L);
3698
3699
luaL_openlibs(L);
3700
luaL_sandbox(L);
3701
luaL_sandboxthread(L);
3702
3703
size_t bytecodeSize = 0;
3704
char* bytecode = luau_compile(source.data(), source.size(), nullptr, &bytecodeSize);
3705
int result = luau_load(L, "=HugeFunction", bytecode, bytecodeSize, 0);
3706
free(bytecode);
3707
3708
REQUIRE(result == 0);
3709
3710
if (codegen && luau_codegen_supported())
3711
{
3712
Luau::CodeGen::CompilationOptions nativeOptions{Luau::CodeGen::CodeGen_ColdFunctions};
3713
Luau::CodeGen::compile(L, -1, nativeOptions);
3714
}
3715
3716
int status = lua_resume(L, nullptr, 0);
3717
REQUIRE(status == 0);
3718
3719
CHECK(lua_tonumber(L, -1) == 42);
3720
}
3721
3722
TEST_CASE("HugeFunctionLoadFailure")
3723
{
3724
// This test case verifies that if an out-of-memory error occurs inside of
3725
// luau_load, we are not left with any GC objects in inconsistent states
3726
// that would cause issues during garbage collection.
3727
//
3728
// We create a script with a huge function in it, then pass this to
3729
// luau_load. This should require two "large" allocations: One for the
3730
// code array and one for the constants array (k). We run this test twice
3731
// and fail each of these two allocations.
3732
std::string source = makeHugeFunctionSource();
3733
3734
static const size_t expectedTotalLargeAllocations = 2;
3735
3736
static size_t largeAllocationToFail = 0;
3737
static size_t largeAllocationCount = 0;
3738
3739
const auto testAllocate = [](void* ud, void* ptr, size_t osize, size_t nsize) -> void*
3740
{
3741
if (nsize == 0)
3742
{
3743
free(ptr);
3744
return nullptr;
3745
}
3746
else if (nsize > 32768)
3747
{
3748
if (largeAllocationCount == largeAllocationToFail)
3749
return nullptr;
3750
3751
++largeAllocationCount;
3752
return realloc(ptr, nsize);
3753
}
3754
else
3755
{
3756
return realloc(ptr, nsize);
3757
}
3758
};
3759
3760
size_t bytecodeSize = 0;
3761
char* const bytecode = luau_compile(source.data(), source.size(), nullptr, &bytecodeSize);
3762
3763
for (largeAllocationToFail = 0; largeAllocationToFail != expectedTotalLargeAllocations; ++largeAllocationToFail)
3764
{
3765
largeAllocationCount = 0;
3766
3767
StateRef globalState(lua_newstate(testAllocate, nullptr), lua_close);
3768
lua_State* L = globalState.get();
3769
3770
luaL_openlibs(L);
3771
luaL_sandbox(L);
3772
luaL_sandboxthread(L);
3773
3774
int status = luau_load(L, "=HugeFunction", bytecode, bytecodeSize, 0);
3775
REQUIRE(status == 1);
3776
3777
const char* error = lua_tostring(L, -1);
3778
CHECK(strcmp(error, "not enough memory") == 0);
3779
3780
luaC_fullgc(L);
3781
}
3782
3783
free(bytecode);
3784
3785
REQUIRE_EQ(largeAllocationToFail, expectedTotalLargeAllocations);
3786
}
3787
3788
TEST_CASE("HugeConstantTable")
3789
{
3790
std::string source = "function foo(...)\n";
3791
3792
source += " local args = ...\n";
3793
source += " local t = args and {\n";
3794
3795
for (int i = 0; i < 400; i++)
3796
{
3797
for (int k = 0; k < 100; k++)
3798
{
3799
source += "call(";
3800
source += std::to_string(i * 100 + k);
3801
source += ".125), ";
3802
}
3803
3804
source += "\n ";
3805
}
3806
3807
source += " }\n";
3808
source += " return { a = 1, b = 2, c = 3 }\n";
3809
source += "end\n";
3810
source += "return foo().a + foo().b\n";
3811
3812
StateRef globalState(luaL_newstate(), lua_close);
3813
lua_State* L = globalState.get();
3814
3815
if (codegen && luau_codegen_supported())
3816
luau_codegen_create(L);
3817
3818
luaL_openlibs(L);
3819
luaL_sandbox(L);
3820
luaL_sandboxthread(L);
3821
3822
size_t bytecodeSize = 0;
3823
char* bytecode = luau_compile(source.data(), source.size(), nullptr, &bytecodeSize);
3824
int result = luau_load(L, "=HugeConstantTable", bytecode, bytecodeSize, 0);
3825
free(bytecode);
3826
3827
REQUIRE(result == 0);
3828
3829
if (codegen && luau_codegen_supported())
3830
{
3831
Luau::CodeGen::CompilationOptions nativeOptions{Luau::CodeGen::CodeGen_ColdFunctions};
3832
Luau::CodeGen::compile(L, -1, nativeOptions);
3833
}
3834
3835
int status = lua_resume(L, nullptr, 0);
3836
REQUIRE(status == 0);
3837
3838
CHECK(lua_tonumber(L, -1) == 3);
3839
}
3840
3841
TEST_CASE("LargeNestedClosure")
3842
{
3843
const int kCount = 2048;
3844
std::string source;
3845
3846
source += "local function test()\n";
3847
source += "local x = 0\n";
3848
3849
for (int i = 0; i < kCount; ++i)
3850
{
3851
std::string n = std::to_string(i + 1);
3852
source += " function f" + n + "() x = x + 1; return " + n + " end\n";
3853
}
3854
3855
source += " return f" + std::to_string(kCount) + "\n";
3856
source += "end\n";
3857
source += "return test()()\n";
3858
3859
StateRef globalState(luaL_newstate(), lua_close);
3860
lua_State* L = globalState.get();
3861
3862
if (codegen && luau_codegen_supported())
3863
luau_codegen_create(L);
3864
3865
luaL_openlibs(L);
3866
luaL_sandbox(L);
3867
luaL_sandboxthread(L);
3868
3869
size_t bytecodeSize = 0;
3870
char* bytecode = luau_compile(source.data(), source.size(), nullptr, &bytecodeSize);
3871
int result = luau_load(L, "=LargeNestedClosure", bytecode, bytecodeSize, 0);
3872
free(bytecode);
3873
3874
REQUIRE(result == 0);
3875
3876
if (codegen && luau_codegen_supported())
3877
{
3878
Luau::CodeGen::CompilationOptions nativeOptions{Luau::CodeGen::CodeGen_ColdFunctions};
3879
Luau::CodeGen::compile(L, -1, nativeOptions);
3880
}
3881
3882
int status = lua_resume(L, nullptr, 0);
3883
REQUIRE(status == 0);
3884
3885
CHECK(lua_tonumber(L, -1) == kCount);
3886
}
3887
3888
TEST_CASE("IrInstructionLimit")
3889
{
3890
if (!codegen || !luau_codegen_supported())
3891
return;
3892
3893
ScopedFastInt codegenHeuristicsInstructionLimit{FInt::CodegenHeuristicsInstructionLimit, 50'000};
3894
3895
std::string source;
3896
3897
// Generate a hundred fat functions
3898
for (int fn = 0; fn < 100; fn++)
3899
{
3900
source += "local function fn" + std::to_string(fn) + "(...)\n";
3901
source += "if ... then\n";
3902
source += "local p1, p2 = ...\n";
3903
source += "local _ = {\n";
3904
3905
for (int i = 0; i < 100; ++i)
3906
{
3907
source += "p1*0." + std::to_string(i) + ",";
3908
source += "p2+0." + std::to_string(i) + ",";
3909
}
3910
3911
source += "}\n";
3912
source += "return _\n";
3913
source += "end\n";
3914
source += "end\n";
3915
}
3916
3917
StateRef globalState(luaL_newstate(), lua_close);
3918
lua_State* L = globalState.get();
3919
3920
luau_codegen_create(L);
3921
3922
luaL_openlibs(L);
3923
luaL_sandbox(L);
3924
luaL_sandboxthread(L);
3925
3926
size_t bytecodeSize = 0;
3927
char* bytecode = luau_compile(source.data(), source.size(), nullptr, &bytecodeSize);
3928
int result = luau_load(L, "=HugeFunction", bytecode, bytecodeSize, 0);
3929
free(bytecode);
3930
3931
REQUIRE(result == 0);
3932
3933
Luau::CodeGen::CompilationOptions nativeOptions{Luau::CodeGen::CodeGen_ColdFunctions};
3934
Luau::CodeGen::CompilationStats nativeStats = {};
3935
Luau::CodeGen::CompilationResult nativeResult = Luau::CodeGen::compile(L, -1, nativeOptions, &nativeStats);
3936
3937
// Limit is not hit immediately, so with some functions compiled it should be a success
3938
CHECK(nativeResult.result == Luau::CodeGen::CodeGenCompilationResult::Success);
3939
3940
// But it has some failed functions
3941
CHECK(nativeResult.hasErrors());
3942
REQUIRE(!nativeResult.protoFailures.empty());
3943
3944
CHECK(nativeResult.protoFailures.front().result == Luau::CodeGen::CodeGenCompilationResult::CodeGenOverflowInstructionLimit);
3945
CHECK(nativeResult.protoFailures.front().line != -1);
3946
CHECK(nativeResult.protoFailures.front().debugname != "");
3947
3948
// We should be able to compile at least one of our functions
3949
CHECK(nativeStats.functionsCompiled > 0);
3950
3951
// But because of the limit, not all of them (101 because there's an extra global function)
3952
CHECK(nativeStats.functionsCompiled < 101);
3953
}
3954
3955
TEST_CASE("BytecodeDistributionPerFunctionTest")
3956
{
3957
const char* source = R"(
3958
local function first(n, p)
3959
local t = {}
3960
for i=1,p do t[i] = i*10 end
3961
3962
local function inner(_,n)
3963
if n > 0 then
3964
n = n-1
3965
return n, unpack(t)
3966
end
3967
end
3968
return inner, nil, n
3969
end
3970
3971
local function second(x)
3972
return x[1]
3973
end
3974
)";
3975
3976
std::vector<Luau::CodeGen::FunctionBytecodeSummary> summaries(analyzeFile(source, 0));
3977
3978
CHECK_EQ(summaries[0].getName(), "inner");
3979
CHECK_EQ(summaries[0].getLine(), 6);
3980
CHECK_EQ(summaries[0].getCounts(0), std::vector<unsigned>({0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0,
3981
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
3982
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}));
3983
3984
CHECK_EQ(summaries[1].getName(), "first");
3985
CHECK_EQ(summaries[1].getLine(), 2);
3986
CHECK_EQ(summaries[1].getCounts(0), std::vector<unsigned>({0, 0, 1, 0, 2, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0,
3987
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
3988
1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}));
3989
3990
3991
CHECK_EQ(summaries[2].getName(), "second");
3992
CHECK_EQ(summaries[2].getLine(), 15);
3993
CHECK_EQ(summaries[2].getCounts(0), std::vector<unsigned>({0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
3994
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
3995
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}));
3996
3997
CHECK_EQ(summaries[3].getName(), "");
3998
CHECK_EQ(summaries[3].getLine(), 1);
3999
CHECK_EQ(summaries[3].getCounts(0), std::vector<unsigned>({0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
4000
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
4001
0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}));
4002
}
4003
4004
TEST_CASE("NativeAttribute")
4005
{
4006
if (!codegen || !luau_codegen_supported())
4007
return;
4008
4009
std::string source = R"R(
4010
@native
4011
local function sum(x, y)
4012
local function sumHelper(z)
4013
return (x+y+z)
4014
end
4015
return sumHelper
4016
end
4017
4018
local function sub(x, y)
4019
@native
4020
local function subHelper(z)
4021
return (x+y-z)
4022
end
4023
return subHelper
4024
end)R";
4025
4026
StateRef globalState(luaL_newstate(), lua_close);
4027
lua_State* L = globalState.get();
4028
4029
luau_codegen_create(L);
4030
4031
luaL_openlibs(L);
4032
luaL_sandbox(L);
4033
luaL_sandboxthread(L);
4034
4035
size_t bytecodeSize = 0;
4036
char* bytecode = luau_compile(source.data(), source.size(), nullptr, &bytecodeSize);
4037
int result = luau_load(L, "=Code", bytecode, bytecodeSize, 0);
4038
free(bytecode);
4039
4040
REQUIRE(result == 0);
4041
4042
Luau::CodeGen::CompilationOptions nativeOptions{Luau::CodeGen::CodeGen_ColdFunctions};
4043
Luau::CodeGen::CompilationStats nativeStats = {};
4044
Luau::CodeGen::CompilationResult nativeResult = Luau::CodeGen::compile(L, -1, nativeOptions, &nativeStats);
4045
4046
CHECK(nativeResult.result == Luau::CodeGen::CodeGenCompilationResult::Success);
4047
4048
CHECK(!nativeResult.hasErrors());
4049
REQUIRE(nativeResult.protoFailures.empty());
4050
4051
// We should be able to compile at least one of our functions
4052
CHECK_EQ(nativeStats.functionsCompiled, 2);
4053
}
4054
4055
TEST_SUITE_END();
4056
4057