Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/fuzz/proto.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 "src/libfuzzer/libfuzzer_macro.h"
3
#include "luau.pb.h"
4
5
#include "Luau/BuiltinDefinitions.h"
6
#include "Luau/BytecodeBuilder.h"
7
#include "Luau/CodeGen.h"
8
#include "Luau/Common.h"
9
#include "Luau/Compiler.h"
10
#include "Luau/Config.h"
11
#include "Luau/Frontend.h"
12
#include "Luau/Linter.h"
13
#include "Luau/ModuleResolver.h"
14
#include "Luau/Parser.h"
15
#include "Luau/PrettyPrinter.h"
16
#include "Luau/ToString.h"
17
#include "Luau/Type.h"
18
#include "Luau/TypeInfer.h"
19
20
#include "lua.h"
21
#include "lualib.h"
22
23
#include <chrono>
24
#include <string>
25
#include <vector>
26
#include <cstring>
27
28
static bool getEnvParam(const char* name, bool def)
29
{
30
char* val = getenv(name);
31
if (val == nullptr)
32
return def;
33
else
34
return strcmp(val, "0") != 0;
35
}
36
37
// Select components to fuzz
38
const bool kFuzzCompiler = getEnvParam("LUAU_FUZZ_COMPILER", true);
39
const bool kFuzzLinter = getEnvParam("LUAU_FUZZ_LINTER", true);
40
const bool kFuzzTypeck = getEnvParam("LUAU_FUZZ_TYPE_CHECK", true);
41
const bool kFuzzVM = getEnvParam("LUAU_FUZZ_VM", true);
42
const bool kFuzzPrettyPrint = getEnvParam("LUAU_FUZZ_PRETTY_PRINT", true);
43
const bool kFuzzCodegenVM = getEnvParam("LUAU_FUZZ_CODEGEN_VM", true);
44
const bool kFuzzCodegenAssembly = getEnvParam("LUAU_FUZZ_CODEGEN_ASM", true);
45
46
// Should we generate type annotations?
47
const bool kFuzzTypes = getEnvParam("LUAU_FUZZ_GEN_TYPES", true);
48
49
const Luau::CodeGen::AssemblyOptions::Target kFuzzCodegenTarget = Luau::CodeGen::AssemblyOptions::A64;
50
51
std::vector<std::string> protoprint(const luau::ModuleSet& stat, bool types);
52
53
LUAU_FASTINT(LuauTypeInferRecursionLimit)
54
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit)
55
LUAU_FASTINT(LuauCheckRecursionLimit)
56
LUAU_FASTINT(LuauTableTypeMaximumStringifierLength)
57
LUAU_FASTINT(LuauTypeInferIterationLimit)
58
LUAU_FASTINT(LuauTarjanChildLimit)
59
LUAU_FASTFLAG(DebugLuauFreezeArena)
60
LUAU_FASTFLAG(DebugLuauAbortingChecks)
61
62
const double kTypecheckTimeoutSec = 4.0;
63
64
std::chrono::milliseconds kInterruptTimeout(10);
65
std::chrono::time_point<std::chrono::system_clock> interruptDeadline;
66
67
size_t kHeapLimit = 512 * 1024 * 1024;
68
size_t heapSize = 0;
69
70
void interrupt(lua_State* L, int gc)
71
{
72
if (gc >= 0)
73
return;
74
75
if (std::chrono::system_clock::now() > interruptDeadline)
76
{
77
lua_checkstack(L, 1);
78
luaL_error(L, "execution timed out");
79
}
80
}
81
82
void* allocate(void* ud, void* ptr, size_t osize, size_t nsize)
83
{
84
if (nsize == 0)
85
{
86
heapSize -= osize;
87
free(ptr);
88
return NULL;
89
}
90
else
91
{
92
if (heapSize - osize + nsize > kHeapLimit)
93
return NULL;
94
95
heapSize -= osize;
96
heapSize += nsize;
97
98
return realloc(ptr, nsize);
99
}
100
}
101
102
int lua_silence(lua_State* L)
103
{
104
return 0;
105
}
106
107
lua_State* createGlobalState()
108
{
109
lua_State* L = lua_newstate(allocate, NULL);
110
111
if (kFuzzCodegenVM && Luau::CodeGen::isSupported())
112
Luau::CodeGen::create(L);
113
114
lua_callbacks(L)->interrupt = interrupt;
115
116
luaL_openlibs(L);
117
118
std::vector<luaL_Reg> funcs;
119
funcs.push_back({"print", lua_silence}); // do not let fuzz input to print to stdout
120
funcs.push_back({nullptr, nullptr}); // "null" terminate the list of functions to register
121
122
lua_pushvalue(L, LUA_GLOBALSINDEX);
123
luaL_register(L, nullptr, funcs.data());
124
lua_pop(L, 1);
125
126
luaL_sandbox(L);
127
128
return L;
129
}
130
131
int registerTypes(Luau::Frontend& frontend, Luau::GlobalTypes& globals, bool forAutocomplete)
132
{
133
using namespace Luau;
134
using std::nullopt;
135
136
Luau::registerBuiltinGlobals(frontend, globals, forAutocomplete);
137
138
TypeArena& arena = globals.globalTypes;
139
BuiltinTypes& builtinTypes = *globals.builtinTypes;
140
141
// Vector3 stub
142
TypeId vector3MetaType = arena.addType(TableType{});
143
144
TypeId vector3InstanceType = arena.addType(ExternType{"Vector3", {}, nullopt, vector3MetaType, {}, {}, "Test", {}});
145
getMutable<ExternType>(vector3InstanceType)->props = {
146
{"X", {builtinTypes.numberType}},
147
{"Y", {builtinTypes.numberType}},
148
{"Z", {builtinTypes.numberType}},
149
};
150
151
getMutable<TableType>(vector3MetaType)->props = {
152
{"__add", {makeFunction(arena, nullopt, {vector3InstanceType, vector3InstanceType}, {vector3InstanceType})}},
153
};
154
getMutable<TableType>(vector3MetaType)->state = TableState::Sealed;
155
156
globals.globalScope->exportedTypeBindings["Vector3"] = TypeFun{{}, vector3InstanceType};
157
158
// Instance stub
159
TypeId instanceType = arena.addType(ExternType{"Instance", {}, nullopt, nullopt, {}, {}, "Test", {}});
160
getMutable<ExternType>(instanceType)->props = {
161
{"Name", {builtinTypes.stringType}},
162
};
163
164
globals.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, instanceType};
165
166
// Part stub
167
TypeId partType = arena.addType(ExternType{"Part", {}, instanceType, nullopt, {}, {}, "Test", {}});
168
getMutable<ExternType>(partType)->props = {
169
{"Position", {vector3InstanceType}},
170
};
171
172
globals.globalScope->exportedTypeBindings["Part"] = TypeFun{{}, partType};
173
174
for (const auto& [_, fun] : globals.globalScope->exportedTypeBindings)
175
persist(fun.type);
176
177
return 0;
178
}
179
180
static void setupFrontend(Luau::Frontend& frontend)
181
{
182
registerTypes(frontend, frontend.globals, false);
183
Luau::freeze(frontend.globals.globalTypes);
184
185
registerTypes(frontend, frontend.globalsForAutocomplete, true);
186
Luau::freeze(frontend.globalsForAutocomplete.globalTypes);
187
188
frontend.iceHandler.onInternalError = [](const char* error)
189
{
190
printf("ICE: %s\n", error);
191
LUAU_ASSERT(!"ICE");
192
};
193
}
194
195
static Luau::FrontendOptions getFrontendOptions()
196
{
197
Luau::FrontendOptions options;
198
199
options.retainFullTypeGraphs = true;
200
options.forAutocomplete = false;
201
options.runLintChecks = kFuzzLinter;
202
203
options.moduleTimeLimitSec = kTypecheckTimeoutSec;
204
205
return options;
206
}
207
208
struct FuzzFileResolver : Luau::FileResolver
209
{
210
std::optional<Luau::SourceCode> readSource(const Luau::ModuleName& name) override
211
{
212
auto it = source.find(name);
213
if (it == source.end())
214
return std::nullopt;
215
216
return Luau::SourceCode{it->second, Luau::SourceCode::Module};
217
}
218
219
std::optional<Luau::ModuleInfo> resolveModule(const Luau::ModuleInfo* context, Luau::AstExpr* expr, const Luau::TypeCheckLimits& _limits) override
220
{
221
if (Luau::AstExprGlobal* g = expr->as<Luau::AstExprGlobal>())
222
return Luau::ModuleInfo{g->name.value};
223
224
return std::nullopt;
225
}
226
227
std::string getHumanReadableModuleName(const Luau::ModuleName& name) const override
228
{
229
return name;
230
}
231
232
std::optional<std::string> getEnvironmentForModule(const Luau::ModuleName& name) const override
233
{
234
return std::nullopt;
235
}
236
237
std::unordered_map<Luau::ModuleName, std::string> source;
238
};
239
240
struct FuzzConfigResolver : Luau::ConfigResolver
241
{
242
FuzzConfigResolver()
243
{
244
defaultConfig.mode = Luau::Mode::Nonstrict;
245
defaultConfig.enabledLint.warningMask = ~0ull;
246
defaultConfig.parseOptions.captureComments = true;
247
}
248
249
virtual const Luau::Config& getConfig(const Luau::ModuleName& name, const Luau::TypeCheckLimits& _limits) const override
250
{
251
return defaultConfig;
252
}
253
254
Luau::Config defaultConfig;
255
};
256
257
static std::vector<std::string> debugsources;
258
259
DEFINE_PROTO_FUZZER(const luau::ModuleSet& message)
260
{
261
if (!kFuzzCompiler && (kFuzzCodegenAssembly || kFuzzCodegenVM || kFuzzVM))
262
{
263
printf("Compiler is required in order to fuzz codegen or the VM\n");
264
LUAU_ASSERT(false);
265
return;
266
}
267
268
FInt::LuauTypeInferRecursionLimit.value = 100;
269
FInt::LuauTypeInferTypePackLoopLimit.value = 100;
270
FInt::LuauCheckRecursionLimit.value = 100;
271
FInt::LuauTypeInferIterationLimit.value = 1000;
272
FInt::LuauTarjanChildLimit.value = 1000;
273
FInt::LuauTableTypeMaximumStringifierLength.value = 100;
274
275
for (Luau::FValue<bool>* flag = Luau::FValue<bool>::list; flag; flag = flag->next)
276
{
277
if (strncmp(flag->name, "Luau", 4) == 0)
278
flag->value = true;
279
}
280
281
FFlag::DebugLuauFreezeArena.value = true;
282
FFlag::DebugLuauAbortingChecks.value = true;
283
284
std::vector<std::string> sources = protoprint(message, kFuzzTypes);
285
286
// stash source in a global for easier crash dump debugging
287
debugsources = sources;
288
289
static bool debug = getenv("LUAU_DEBUG") != 0;
290
291
if (debug)
292
{
293
for (std::string& source : sources)
294
fprintf(stdout, "--\n%s\n", source.c_str());
295
fflush(stdout);
296
}
297
298
// parse all sources
299
std::vector<std::unique_ptr<Luau::Allocator>> parseAllocators;
300
std::vector<std::unique_ptr<Luau::AstNameTable>> parseNameTables;
301
302
Luau::ParseOptions parseOptions;
303
parseOptions.captureComments = true;
304
305
std::vector<Luau::ParseResult> parseResults;
306
307
for (std::string& source : sources)
308
{
309
parseAllocators.push_back(std::make_unique<Luau::Allocator>());
310
parseNameTables.push_back(std::make_unique<Luau::AstNameTable>(*parseAllocators.back()));
311
312
parseResults.push_back(Luau::Parser::parse(source.c_str(), source.size(), *parseNameTables.back(), *parseAllocators.back(), parseOptions));
313
}
314
315
// typecheck all sources
316
if (kFuzzTypeck)
317
{
318
static FuzzFileResolver fileResolver;
319
static FuzzConfigResolver configResolver;
320
static Luau::FrontendOptions defaultOptions = getFrontendOptions();
321
static Luau::Frontend frontend(Luau::SolverMode::New, &fileResolver, &configResolver, defaultOptions);
322
323
static int once = (setupFrontend(frontend), 0);
324
(void)once;
325
326
// restart
327
frontend.clear();
328
fileResolver.source.clear();
329
330
// load sources
331
for (size_t i = 0; i < sources.size(); i++)
332
{
333
std::string name = "module" + std::to_string(i);
334
fileResolver.source[name] = sources[i];
335
}
336
337
// check sources
338
for (size_t i = 0; i < sources.size(); i++)
339
{
340
std::string name = "module" + std::to_string(i);
341
342
try
343
{
344
frontend.check(name);
345
346
// Second pass in strict mode (forced by auto-complete)
347
Luau::FrontendOptions options = defaultOptions;
348
options.forAutocomplete = true;
349
frontend.check(name, options);
350
}
351
catch (std::exception&)
352
{
353
// This catches internal errors that the type checker currently (unfortunately) throws in some cases
354
}
355
}
356
357
// validate sharedEnv post-typecheck; valuable for debugging some typeck crashes but slows fuzzing down
358
// note: it's important for typeck to be destroyed at this point!
359
for (auto& p : frontend.globals.globalScope->bindings)
360
{
361
Luau::ToStringOptions opts;
362
opts.exhaustive = true;
363
opts.maxTableLength = 0;
364
opts.maxTypeLength = 0;
365
366
toString(p.second.typeId, opts); // toString walks the entire type, making sure ASAN catches access to destroyed type arenas
367
}
368
}
369
370
if (kFuzzPrettyPrint)
371
{
372
for (Luau::ParseResult& parseResult : parseResults)
373
{
374
if (parseResult.root)
375
prettyPrintWithTypes(*parseResult.root);
376
}
377
}
378
379
std::string bytecode;
380
381
// compile
382
if (kFuzzCompiler)
383
{
384
for (size_t i = 0; i < parseResults.size(); i++)
385
{
386
Luau::ParseResult& parseResult = parseResults[i];
387
Luau::AstNameTable& parseNameTable = *parseNameTables[i];
388
389
if (parseResult.errors.empty())
390
{
391
Luau::CompileOptions compileOptions;
392
393
try
394
{
395
Luau::BytecodeBuilder bcb;
396
Luau::compileOrThrow(bcb, parseResult, parseNameTable, compileOptions);
397
bytecode = bcb.getBytecode();
398
}
399
catch (const Luau::CompileError&)
400
{
401
// not all valid ASTs can be compiled due to limits on number of registers
402
}
403
}
404
}
405
}
406
407
// run codegen on resulting bytecode (in separate state)
408
if (kFuzzCodegenAssembly && bytecode.size())
409
{
410
static lua_State* globalState = luaL_newstate();
411
412
if (luau_load(globalState, "=fuzz", bytecode.data(), bytecode.size(), 0) == 0)
413
{
414
Luau::CodeGen::AssemblyOptions options;
415
options.compilationOptions.flags = Luau::CodeGen::CodeGen_ColdFunctions;
416
options.outputBinary = true;
417
options.target = kFuzzCodegenTarget;
418
Luau::CodeGen::getAssembly(globalState, -1, options);
419
}
420
421
lua_pop(globalState, 1);
422
lua_gc(globalState, LUA_GCCOLLECT, 0);
423
}
424
425
// run resulting bytecode (from last successfully compiler module)
426
if ((kFuzzVM || kFuzzCodegenVM) && bytecode.size())
427
{
428
static lua_State* globalState = createGlobalState();
429
430
auto runCode = [](const std::string& bytecode, bool useCodegen)
431
{
432
lua_State* L = lua_newthread(globalState);
433
luaL_sandboxthread(L);
434
435
if (luau_load(L, "=fuzz", bytecode.data(), bytecode.size(), 0) == 0)
436
{
437
if (useCodegen)
438
Luau::CodeGen::compile(L, -1, Luau::CodeGen::CodeGen_ColdFunctions);
439
440
interruptDeadline = std::chrono::system_clock::now() + kInterruptTimeout;
441
442
lua_resume(L, NULL, 0);
443
}
444
445
lua_pop(globalState, 1);
446
447
// we'd expect full GC to reclaim all memory allocated by the script
448
lua_gc(globalState, LUA_GCCOLLECT, 0);
449
LUAU_ASSERT(heapSize < 256 * 1024);
450
};
451
452
if (kFuzzVM)
453
runCode(bytecode, false);
454
455
if (kFuzzCodegenVM && Luau::CodeGen::isSupported())
456
runCode(bytecode, true);
457
}
458
}
459
460