Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/Config/src/LuauConfig.cpp
2725 views
1
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
2
#include "Luau/LuauConfig.h"
3
4
#include "Luau/Compiler.h"
5
#include "Luau/Config.h"
6
7
#include "lua.h"
8
#include "lualib.h"
9
10
#include <functional>
11
#include <memory>
12
#include <optional>
13
#include <string>
14
15
#define RETURN_WITH_ERROR(msg) \
16
do \
17
{ \
18
if (error) \
19
*error = msg; \
20
return std::nullopt; \
21
} while (false)
22
23
namespace Luau
24
{
25
26
struct ThreadPopper
27
{
28
explicit ThreadPopper(lua_State* L)
29
: L(L)
30
{
31
LUAU_ASSERT(L);
32
}
33
34
ThreadPopper(const ThreadPopper&) = delete;
35
ThreadPopper& operator=(const ThreadPopper&) = delete;
36
37
~ThreadPopper()
38
{
39
lua_pop(L, 1);
40
}
41
42
lua_State* L = nullptr;
43
};
44
45
static std::optional<ConfigTable> serializeTable(lua_State* L, std::string* error)
46
{
47
ThreadPopper popper(L); // Remove table from stack after processing
48
ConfigTable table;
49
50
lua_pushnil(L);
51
while (lua_next(L, -2) != 0)
52
{
53
ThreadPopper popper(L); // Remove value from stack after processing
54
55
// Process key
56
ConfigTableKey key;
57
switch (lua_type(L, -2))
58
{
59
case LUA_TNUMBER:
60
key = lua_tonumber(L, -2);
61
break;
62
case LUA_TSTRING:
63
key = std::string{lua_tostring(L, -2)};
64
break;
65
default:
66
RETURN_WITH_ERROR("configuration table keys must be strings or numbers");
67
}
68
69
// Process value
70
switch (lua_type(L, -1))
71
{
72
case LUA_TNUMBER:
73
table[key] = lua_tonumber(L, -1);
74
break;
75
case LUA_TSTRING:
76
table[key] = std::string{lua_tostring(L, -1)};
77
break;
78
case LUA_TBOOLEAN:
79
table[key] = static_cast<bool>(lua_toboolean(L, -1));
80
break;
81
case LUA_TTABLE:
82
{
83
lua_pushvalue(L, -1); // Copy table for recursive call
84
if (std::optional<ConfigTable> nested = serializeTable(L, error))
85
table[key] = std::move(*nested);
86
else
87
return std::nullopt; // Error already set in recursive call
88
break;
89
}
90
default:
91
std::string msg = "configuration value for key \"" + key.toString() + "\" must be a string, number, boolean, or nested table";
92
RETURN_WITH_ERROR(std::move(msg));
93
}
94
}
95
96
return table;
97
}
98
99
static std::optional<std::string> load(lua_State* L, const std::string& source)
100
{
101
std::string bytecode = compile(source);
102
if (luau_load(L, "=config", bytecode.data(), bytecode.size(), 0) != 0)
103
return lua_tostring(L, -1);
104
105
return std::nullopt;
106
}
107
108
std::optional<ConfigTable> extractConfig(const std::string& source, const InterruptCallbacks& callbacks, std::string* error)
109
{
110
// Initialize Luau VM
111
std::unique_ptr<lua_State, void (*)(lua_State*)> state{luaL_newstate(), lua_close};
112
lua_State* L = state.get();
113
luaL_openlibs(L);
114
luaL_sandbox(L);
115
116
if (std::optional<std::string> loadError = load(L, source))
117
RETURN_WITH_ERROR(*loadError);
118
119
// Execute configuration
120
if (callbacks.initCallback)
121
callbacks.initCallback(L);
122
lua_callbacks(L)->interrupt = callbacks.interruptCallback;
123
switch (lua_resume(L, nullptr, 0))
124
{
125
case LUA_OK:
126
break;
127
case LUA_BREAK: // debugging not supported, at least for now
128
case LUA_YIELD:
129
RETURN_WITH_ERROR("configuration execution cannot yield");
130
default:
131
RETURN_WITH_ERROR(lua_tostring(L, -1));
132
}
133
134
// Extract returned table
135
if (lua_gettop(L) != 1)
136
RETURN_WITH_ERROR("configuration must return exactly one value");
137
138
if (lua_type(L, -1) != LUA_TTABLE)
139
RETURN_WITH_ERROR("configuration did not return a table");
140
141
return serializeTable(L, error);
142
}
143
144
static std::optional<std::string> createLuauConfigFromLuauTable(
145
Config& config,
146
const ConfigTable& luauTable,
147
std::optional<ConfigOptions::AliasOptions> aliasOptions
148
)
149
{
150
for (const auto& [k, v] : luauTable)
151
{
152
const std::string* key = k.get_if<std::string>();
153
if (!key)
154
return "configuration keys in \"luau\" table must be strings";
155
156
if (*key == "languagemode")
157
{
158
const std::string* value = v.get_if<std::string>();
159
if (!value)
160
return "configuration value for key \"languagemode\" must be a string";
161
162
if (std::optional<std::string> errorMessage = parseModeString(config.mode, *value))
163
return errorMessage;
164
}
165
166
if (*key == "lint")
167
{
168
const ConfigTable* lint = v.get_if<ConfigTable>();
169
if (!lint)
170
return "configuration value for key \"lint\" must be a table";
171
172
// Handle wildcard first to ensure overrides work as expected.
173
if (const ConfigValue* value = lint->find("*"))
174
{
175
const bool* enabled = value->get_if<bool>();
176
if (!enabled)
177
return "configuration values in \"lint\" table must be booleans";
178
179
if (std::optional<std::string> errorMessage =
180
parseLintRuleString(config.enabledLint, config.fatalLint, "*", *enabled ? "true" : "false"))
181
return errorMessage;
182
}
183
184
for (const auto& [k, v] : *lint)
185
{
186
const std::string* warningName = k.get_if<std::string>();
187
if (!warningName)
188
return "configuration keys in \"lint\" table must be strings";
189
190
if (*warningName == "*")
191
continue; // Handled above
192
193
const bool* enabled = v.get_if<bool>();
194
if (!enabled)
195
return "configuration values in \"lint\" table must be booleans";
196
197
if (std::optional<std::string> errorMessage =
198
parseLintRuleString(config.enabledLint, config.fatalLint, *warningName, *enabled ? "true" : "false"))
199
return errorMessage;
200
}
201
}
202
203
if (*key == "linterrors")
204
{
205
const bool* value = v.get_if<bool>();
206
if (!value)
207
return "configuration value for key \"linterrors\" must be a boolean";
208
209
config.lintErrors = *value;
210
}
211
212
if (*key == "typeerrors")
213
{
214
const bool* value = v.get_if<bool>();
215
if (!value)
216
return "configuration value for key \"typeerrors\" must be a boolean";
217
218
config.typeErrors = *value;
219
}
220
221
if (*key == "globals")
222
{
223
const ConfigTable* globalsTable = v.get_if<ConfigTable>();
224
if (!globalsTable)
225
return "configuration value for key \"globals\" must be an array of strings";
226
227
std::vector<std::string> globals;
228
globals.resize(globalsTable->size());
229
230
for (const auto& [k, v] : *globalsTable)
231
{
232
const double* key = k.get_if<double>();
233
if (!key)
234
return "configuration array \"globals\" must only have numeric keys";
235
236
const size_t index = static_cast<size_t>(*key);
237
if (index < 1 || globalsTable->size() < index)
238
return "configuration array \"globals\" contains invalid numeric key";
239
240
const std::string* global = v.get_if<std::string>();
241
if (!global)
242
return "configuration value in \"globals\" table must be a string";
243
244
LUAU_ASSERT(0 <= index - 1 && index - 1 < globalsTable->size());
245
globals[index - 1] = *global;
246
}
247
248
config.globals = std::move(globals);
249
}
250
251
if (*key == "aliases")
252
{
253
const ConfigTable* aliases = v.get_if<ConfigTable>();
254
if (!aliases)
255
return "configuration value for key \"aliases\" must be a table";
256
257
for (const auto& [k, v] : *aliases)
258
{
259
const std::string* aliasKey = k.get_if<std::string>();
260
if (!aliasKey)
261
return "configuration keys in \"aliases\" table must be strings";
262
263
const std::string* aliasValue = v.get_if<std::string>();
264
if (!aliasValue)
265
return "configuration values in \"aliases\" table must be strings";
266
267
if (std::optional<std::string> errorMessage = parseAlias(config, *aliasKey, *aliasValue, aliasOptions))
268
return errorMessage;
269
}
270
}
271
}
272
273
return std::nullopt;
274
}
275
276
std::optional<std::string> extractLuauConfig(
277
const std::string& source,
278
Config& config,
279
std::optional<ConfigOptions::AliasOptions> aliasOptions,
280
InterruptCallbacks callbacks
281
)
282
{
283
std::string error;
284
std::optional<ConfigTable> configTable = extractConfig(source, callbacks, &error);
285
if (!configTable)
286
return error;
287
288
if (!configTable->contains("luau"))
289
return std::nullopt;
290
291
ConfigTable* luauTable = (*configTable)["luau"].get_if<ConfigTable>();
292
if (!luauTable)
293
return "configuration value for key \"luau\" must be a table";
294
295
return createLuauConfigFromLuauTable(config, *luauTable, aliasOptions);
296
}
297
298
} // namespace Luau
299
300