Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
348 changes: 348 additions & 0 deletions src/compile/semantic_analysis.cpp

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions src/compile/semantic_analysis.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "compile/syntax_tree.h"
#include "fakelua.h"
#include <unordered_map>
#include <unordered_set>
#include <vector>

namespace fakelua {
Expand Down Expand Up @@ -41,6 +42,21 @@ class SemanticAnalysis {
void CheckForIn(const SyntaxTreeInterfacePtr &node);
void CheckExp(const SyntaxTreeInterfacePtr &node);
void CheckGlobalConstExp(const SyntaxTreeInterfacePtr &exp);
// fakelua 刻意不支持 Lua 的隐式全局:每个简单名必须能解析到 local/形参/upvalue、
// 文件级 local 或文件级函数,或宿主注册的原生函数。未声明名字在编译期直接报错
// (读返回 nil、写改写 const kNil 的旧行为都是隐患)。豁免:直接调用位 f(...) 的
// 整条点号调用链(未知名维持运行时 FakeluaCallByName 的 "not found" 报错,且原生
// 函数允许编译后注册)、math/string 等点号原生库的模块根名,以及文件首行 package
// 声明(package "X" / package = "X")的名字。
void CheckUndeclaredVars(const SyntaxTreeInterfacePtr &chunk, const AnalysisResult &ar);
// CheckUndeclaredVars 的词法作用域递归,作用域栈语义与 CGen::ResolveScopes 对齐。
void CheckVarScopes(const SyntaxTreeInterfacePtr &node, std::vector<std::unordered_set<std::string>> &scopes,
const std::unordered_set<std::string> &file_level_names,
const std::unordered_set<const SyntaxTreeInterface *> &exempt_vars,
const std::unordered_set<const SyntaxTreeInterface *> &lvalue_vars);
[[nodiscard]] bool IsDeclaredSimpleName(const std::string &name,
const std::vector<std::unordered_set<std::string>> &scopes,
const std::unordered_set<std::string> &file_level_names) const;
[[noreturn]] void ThrowError(const std::string &msg, const SyntaxTreeInterfacePtr &ptr);

void AnalyzeFunctionReturnCounts(const SyntaxTreeInterfacePtr &chunk, AnalysisResult &ar);
Expand Down
13 changes: 12 additions & 1 deletion src/jit/vm.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,23 @@ class Vm {
native_functions_[name] = NativeFuncEntry{arg_count, is_vararg, std::move(callback)};
}

// 查找原生函数条目(供 FakeluaCallByName 使用)
// 查找原生函数条目(供 FakeluaCallByName 分发用)
[[nodiscard]] const NativeFuncEntry *FindNativeFunction(std::string_view name) const {
const auto it = native_functions_.find(name);
return it != native_functions_.end() ? &it->second : nullptr;
}

// 是否存在以 prefix 开头的注册原生函数名(语义分析用它识别 math.xxx / string.xxx
// 这类点号原生库的模块根名,模块名本身不是脚本变量)
[[nodiscard]] bool HasNativeFunctionWithPrefix(std::string_view prefix) const {
for (const auto &[name, entry]: native_functions_) {
if (name.starts_with(prefix)) {
return true;
}
}
return false;
}

// 分配一个唯一的全局变量名
std::string AllocGlobalName() {
return std::format("__fakelua_global_{}__", global_name_++);
Expand Down
4 changes: 4 additions & 0 deletions test/lua/exception/test_undeclared_field_base_error.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
function test(a)
local x = unknown_object.field
return x
end
4 changes: 4 additions & 0 deletions test/lua/exception/test_undeclared_var_read_error.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
function test(a)
local x = unknown_global
return x
end
5 changes: 5 additions & 0 deletions test/lua/exception/test_undeclared_var_write_read_error.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function test(a)
if a < 0 then flag = 1 end
if flag == 1 then flag = 0 end
return 0
end
58 changes: 58 additions & 0 deletions test/test_exception.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,64 @@ TEST(exception, no_define_lvalue_error) {
}
}

// fakelua 刻意不支持 Lua 隐式全局:函数体内未声明的简单名(读 / 写 / 先写后读 /
// 点号字段的未声明根名)必须在语义分析阶段直接报错,并带上文件名、行、列,
// 而不是把读编成 kNil、把写编成对 const kNil 的赋值。
TEST(exception, undeclared_var_error) {
struct Case {
const char *file;
const char *name;
int line;
};
const Case cases[] = {
{"./exception/test_undeclared_var_read_error.lua", "unknown_global", 2},
{"./exception/test_undeclared_var_write_read_error.lua", "flag", 2},
{"./exception/test_undeclared_field_base_error.lua", "unknown_object", 2},
{"./exception/test_no_define_lvalue_error.lua", "b", 2},
};
for (const auto &c: cases) {
SCOPED_TRACE(c.file);
FakeluaStateGuard sg;
auto s = sg.GetState();
ASSERT_NE(s, nullptr);
SetDebugLogLevel(s, 0);

try {
CompileFile(s, c.file, {});
FAIL() << "expected CompileFile to throw for " << c.file;
} catch (const std::exception &e) {
const std::string msg = e.what();
EXPECT_NE(msg.find("no implicit globals"), std::string::npos) << msg;
EXPECT_NE(msg.find(c.name), std::string::npos) << msg;
EXPECT_NE(msg.find(std::string(c.file) + ":" + std::to_string(c.line) + ":"), std::string::npos) << msg;
}
}
}

// 模块名不是脚本变量:点号原生库调用与模块常量必须正常编译,
// 点号链 / 调用链上的模块根名不应被误判成未声明变量。
TEST(exception, undeclared_var_check_allows_module_names) {
FakeluaStateGuard sg;
auto s = sg.GetState();
ASSERT_NE(s, nullptr);
SetDebugLogLevel(s, 0);

const std::string script = R"(
function f(x)
local t = {}
table.insert(t, math.floor(x))
local p = math.pi
local c = string.charpattern
local s = tostring(math.maxinteger)
if t[1] == 3 and p > 3.0 and #c == 1 and #s > 0 then
return s
end
return "bad"
end
)";
ASSERT_NO_THROW(CompileString(s, script, {}));
}

TEST(exception, global_duplicate_lvalue_error) {
FakeluaStateGuard sg;
auto s = sg.GetState();
Expand Down
13 changes: 8 additions & 5 deletions test/test_string.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -587,19 +587,22 @@ TEST(test_string, test_string_sub_undeclared_var) {
ASSERT_NE(s, nullptr);
CompileConfig config;

// fmod 与 C 标准库 math.h 中的 fmod 同名;未在 Lua 中声明时应求值为 nil,
// 传给 string.sub 必须抛出异常(与 Lua 5.4 行为一致),不得被当成 C 函数指针调用。
// fmod 与 C 标准库 math.h 中的 fmod 同名。fakelua 不支持隐式全局:未声明的简单名
// 在编译期直接报错——既不会静默求值为 nil,更不可能被当成 C 函数指针传给 string.sub。
const std::string script = R"(
function test_fmod_sub()
local suA_ub = string.sub(fmod, 3)
return 0
end
)";

for (auto jit_type: AllJitTypes()) {
try {
CompileString(s, script, config);
int64_t res = 0;
EXPECT_THROW(Call(s, jit_type, "test_fmod_sub", res), std::exception);
FAIL() << "expected CompileString to throw for undeclared variable fmod";
} catch (const std::exception &e) {
const std::string msg = e.what();
EXPECT_NE(msg.find("unknown variable 'fmod'"), std::string::npos) << msg;
EXPECT_NE(msg.find("no implicit globals"), std::string::npos) << msg;
}

FakeluaDeleteState(s);
Expand Down
Loading