mirror of
https://github.com/luau-lang/luau.git
synced 2025-08-26 11:27:08 +01:00
# General * Expose an optional `get_alias` API as an alternative to `get_config` in Luau.Require and Luau.RequireNavigator. * Improve the Luau CLI's virtual filesystem implementation to fix bugs related to `init.luau`. Fixes https://github.com/luau-lang/luau/issues/1816 # New Type Solver * Avoid double reporting errors when erroneous arguments are provided to type functions. * Fix some instances of unresovable cyclic type functions in loops by only considering the first loop cycles. This results in some type inference inaccuracies when the type of a variable in loop through multiple iterations. Fixes https://github.com/luau-lang/luau/issues/1413. * Better generalize free types that have meaningful lower and upper bounds, especially for table indexers. * Report more specific errors when assigning or returning table literal types, instead of citing the *entire* table type. * Inference for functions with generic type packs is greatly improved. * Fix some internal compiler exceptions when using type-stating functions like `table.freeze` in `if _ then _ else _` expressions and short circuiting binary operations. * More consistently simplify unions of primitive types, especially in array-like and dictionary-like tables. * Fix a crash when type checking an erroneous type alias containing `typeof` with a type assertion expression, as in: ``` type MyTable = {} -- This will error at type checking time as it's a duplicate type MyTable = typeof(setmetatable(SomeTable :: {}, SomeMetaTable)); ``` * Fix a crash when inferring the type of an index expression where the indexee is invalid (e.g. `nil`). # Runtime * Avoid throwing an exception from `luau_load` if we run out of memory. * Type functions are no longer compiled and included in bytecode. Fixes #1817. * Fix some instances of Luau C API functions reading invalid debug information (generally when the first or last instruction of a block was being inspected). Fixes #1369. * Avoid potential signed integer overflow when doing bounds checks on tables. * Support 16 byte aligned userdata objects when system allocation alignment is also 16 bytes. * Fix memory leaks in `Luau.Require` when using VM build with no exceptions. Fixes #1827. --------- Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Ariel Weiss <aaronweiss@roblox.com> Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com> Co-authored-by: James McNellis <jmcnellis@roblox.com> Co-authored-by: Sora Kanosue <skanosue@roblox.com> Co-authored-by: Talha Pathan <tpathan@roblox.com> Co-authored-by: Varun Saini <vsaini@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
222 lines
6.8 KiB
C++
222 lines
6.8 KiB
C++
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
|
#include "Luau/ReplRequirer.h"
|
|
|
|
#include "Luau/CodeGen.h"
|
|
#include "Luau/CodeGenOptions.h"
|
|
#include "Luau/FileUtils.h"
|
|
#include "Luau/Require.h"
|
|
#include "Luau/VfsNavigator.h"
|
|
|
|
#include "lua.h"
|
|
#include "lualib.h"
|
|
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <utility>
|
|
|
|
static luarequire_WriteResult write(std::optional<std::string> contents, char* buffer, size_t bufferSize, size_t* sizeOut)
|
|
{
|
|
if (!contents)
|
|
return luarequire_WriteResult::WRITE_FAILURE;
|
|
|
|
size_t nullTerminatedSize = contents->size() + 1;
|
|
|
|
if (bufferSize < nullTerminatedSize)
|
|
{
|
|
*sizeOut = nullTerminatedSize;
|
|
return luarequire_WriteResult::WRITE_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
*sizeOut = nullTerminatedSize;
|
|
memcpy(buffer, contents->c_str(), nullTerminatedSize);
|
|
return luarequire_WriteResult::WRITE_SUCCESS;
|
|
}
|
|
|
|
static luarequire_NavigateResult convert(NavigationStatus status)
|
|
{
|
|
if (status == NavigationStatus::Success)
|
|
return NAVIGATE_SUCCESS;
|
|
else if (status == NavigationStatus::Ambiguous)
|
|
return NAVIGATE_AMBIGUOUS;
|
|
else
|
|
return NAVIGATE_NOT_FOUND;
|
|
}
|
|
|
|
static bool is_require_allowed(lua_State* L, void* ctx, const char* requirer_chunkname)
|
|
{
|
|
std::string_view chunkname = requirer_chunkname;
|
|
return chunkname == "=stdin" || (!chunkname.empty() && chunkname[0] == '@');
|
|
}
|
|
|
|
static luarequire_NavigateResult reset(lua_State* L, void* ctx, const char* requirer_chunkname)
|
|
{
|
|
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
|
|
|
|
std::string chunkname = requirer_chunkname;
|
|
if (chunkname == "=stdin")
|
|
return convert(req->vfs.resetToStdIn());
|
|
else if (!chunkname.empty() && chunkname[0] == '@')
|
|
return convert(req->vfs.resetToPath(chunkname.substr(1)));
|
|
|
|
return NAVIGATE_NOT_FOUND;
|
|
}
|
|
|
|
static luarequire_NavigateResult jump_to_alias(lua_State* L, void* ctx, const char* path)
|
|
{
|
|
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
|
|
|
|
if (!isAbsolutePath(path))
|
|
return NAVIGATE_NOT_FOUND;
|
|
|
|
return convert(req->vfs.resetToPath(path));
|
|
}
|
|
|
|
static luarequire_NavigateResult to_parent(lua_State* L, void* ctx)
|
|
{
|
|
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
|
|
return convert(req->vfs.toParent());
|
|
}
|
|
|
|
static luarequire_NavigateResult to_child(lua_State* L, void* ctx, const char* name)
|
|
{
|
|
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
|
|
return convert(req->vfs.toChild(name));
|
|
}
|
|
|
|
static bool is_module_present(lua_State* L, void* ctx)
|
|
{
|
|
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
|
|
return isFile(req->vfs.getFilePath());
|
|
}
|
|
|
|
static luarequire_WriteResult get_chunkname(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out)
|
|
{
|
|
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
|
|
return write("@" + req->vfs.getFilePath(), buffer, buffer_size, size_out);
|
|
}
|
|
|
|
static luarequire_WriteResult get_loadname(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out)
|
|
{
|
|
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
|
|
return write(req->vfs.getAbsoluteFilePath(), buffer, buffer_size, size_out);
|
|
}
|
|
|
|
static luarequire_WriteResult get_cache_key(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out)
|
|
{
|
|
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
|
|
return write(req->vfs.getAbsoluteFilePath(), buffer, buffer_size, size_out);
|
|
}
|
|
|
|
static bool is_config_present(lua_State* L, void* ctx)
|
|
{
|
|
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
|
|
return isFile(req->vfs.getLuaurcPath());
|
|
}
|
|
|
|
static luarequire_WriteResult get_config(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out)
|
|
{
|
|
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
|
|
return write(readFile(req->vfs.getLuaurcPath()), buffer, buffer_size, size_out);
|
|
}
|
|
|
|
static int load(lua_State* L, void* ctx, const char* path, const char* chunkname, const char* loadname)
|
|
{
|
|
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
|
|
|
|
// module needs to run in a new thread, isolated from the rest
|
|
// note: we create ML on main thread so that it doesn't inherit environment of L
|
|
lua_State* GL = lua_mainthread(L);
|
|
lua_State* ML = lua_newthread(GL);
|
|
lua_xmove(GL, L, 1);
|
|
|
|
// new thread needs to have the globals sandboxed
|
|
luaL_sandboxthread(ML);
|
|
|
|
bool hadContents = false;
|
|
int status = LUA_OK;
|
|
|
|
// Handle C++ RAII objects in a scope which doesn't cause a Luau error
|
|
{
|
|
std::optional<std::string> contents = readFile(loadname);
|
|
hadContents = contents.has_value();
|
|
|
|
if (contents)
|
|
{
|
|
// now we can compile & run module on the new thread
|
|
std::string bytecode = Luau::compile(*contents, req->copts());
|
|
status = luau_load(ML, chunkname, bytecode.data(), bytecode.size(), 0);
|
|
}
|
|
}
|
|
|
|
if (!hadContents)
|
|
luaL_error(L, "could not read file '%s'", loadname);
|
|
|
|
if (status == 0)
|
|
{
|
|
if (req->codegenEnabled())
|
|
{
|
|
Luau::CodeGen::CompilationOptions nativeOptions;
|
|
Luau::CodeGen::compile(ML, -1, nativeOptions);
|
|
}
|
|
|
|
if (req->coverageActive())
|
|
req->coverageTrack(ML, -1);
|
|
|
|
int status = lua_resume(ML, L, 0);
|
|
|
|
if (status == 0)
|
|
{
|
|
if (lua_gettop(ML) == 0)
|
|
lua_pushstring(ML, "module must return a value");
|
|
else if (!lua_istable(ML, -1) && !lua_isfunction(ML, -1))
|
|
lua_pushstring(ML, "module must return a table or function");
|
|
}
|
|
else if (status == LUA_YIELD)
|
|
{
|
|
lua_pushstring(ML, "module can not yield");
|
|
}
|
|
else if (!lua_isstring(ML, -1))
|
|
{
|
|
lua_pushstring(ML, "unknown error while running module");
|
|
}
|
|
}
|
|
|
|
// add ML result to L stack
|
|
lua_xmove(ML, L, 1);
|
|
if (lua_isstring(L, -1))
|
|
lua_error(L);
|
|
|
|
// remove ML thread from L stack
|
|
lua_remove(L, -2);
|
|
|
|
// added one value to L stack: module result
|
|
return 1;
|
|
}
|
|
|
|
void requireConfigInit(luarequire_Configuration* config)
|
|
{
|
|
if (config == nullptr)
|
|
return;
|
|
|
|
config->is_require_allowed = is_require_allowed;
|
|
config->reset = reset;
|
|
config->jump_to_alias = jump_to_alias;
|
|
config->to_parent = to_parent;
|
|
config->to_child = to_child;
|
|
config->is_module_present = is_module_present;
|
|
config->is_config_present = is_config_present;
|
|
config->get_chunkname = get_chunkname;
|
|
config->get_loadname = get_loadname;
|
|
config->get_cache_key = get_cache_key;
|
|
config->get_alias = nullptr;
|
|
config->get_config = get_config;
|
|
config->load = load;
|
|
}
|
|
|
|
ReplRequirer::ReplRequirer(CompileOptions copts, BoolCheck coverageActive, BoolCheck codegenEnabled, Coverage coverageTrack)
|
|
: copts(copts)
|
|
, coverageActive(coverageActive)
|
|
, codegenEnabled(codegenEnabled)
|
|
, coverageTrack(coverageTrack)
|
|
{
|
|
}
|