Protect native cache paths and snapshot gameplay sources

This commit is contained in:
Emil
2026-09-24 01:55:50 +03:00
parent 4ec5a3cd8a
commit abbb71d0c1
5 changed files with 302 additions and 38 deletions
+72 -3
View File
@@ -59,19 +59,88 @@ void run(const fs::path& root) {
"Lua entry declaration changes the source snapshot");
const auto native = config.build_directory / "Debug";
editor::ensure_native_toolchain_stamp(native, changed_declaration);
editor::ensure_native_toolchain_stamp(config, native, changed_declaration);
atomic_write(native / "sentinel", "keep\n");
editor::ensure_native_toolchain_stamp(native, changed_declaration);
editor::ensure_native_toolchain_stamp(config, native, changed_declaration);
check(fs::exists(native / "sentinel"), "Unchanged toolchain preserves the native tree");
atomic_write(compiler, "compiler-v2\n");
const auto changed_tool = editor::capture_build_inputs(config, lua);
check(changed_tool.source_hash == changed_declaration.source_hash &&
changed_tool.toolchain_hash != changed_declaration.toolchain_hash,
"Changing compiler bytes at the same path changes toolchain identity");
editor::ensure_native_toolchain_stamp(native, changed_tool);
editor::ensure_native_toolchain_stamp(config, native, changed_tool);
check(!fs::exists(native / "sentinel"),
"Changed toolchain invalidates only the generated native tree");
const auto user_directory = root / "important-user-data";
atomic_write(user_directory / "do-not-delete.txt", "valuable\n");
bool refused_unowned{};
try {
editor::ensure_native_toolchain_stamp(config, user_directory, changed_tool);
} catch (const std::exception&) {
refused_unowned = true;
}
check(refused_unowned && fs::is_regular_file(user_directory / "do-not-delete.txt"),
"Unowned native directory is never cleared");
std::error_code native_link_error;
fs::create_directory_symlink(user_directory, config.build_directory / "Release",
native_link_error);
if (!native_link_error) {
bool refused_link{};
try {
editor::ensure_native_toolchain_stamp(config, config.build_directory / "Release",
changed_tool);
} catch (const std::exception&) {
refused_link = true;
}
check(refused_link && fs::is_regular_file(user_directory / "do-not-delete.txt"),
"Symlinked native directory cannot redirect cleanup into user data");
}
const auto relative_tool = project / "tools/cmake-fixture";
atomic_write(relative_tool, "relative-cmake-v1\n");
auto relative_config = config;
relative_config.cmake = "tools/cmake-fixture";
const auto relative_before = editor::capture_build_inputs(relative_config, lua);
atomic_write(relative_tool, "relative-cmake-v2\n");
check(relative_before.toolchain_hash !=
editor::capture_build_inputs(relative_config, lua).toolchain_hash,
"Relative CMake executable resolves from the project working directory");
auto typed_config = config;
const auto typed_compiler = root / "typed-compiler";
atomic_write(typed_compiler, "typed-v1\n");
typed_config.configure_arguments = {
"-DCMAKE_C_COMPILER:FILEPATH=" + path_to_utf8(typed_compiler)};
const auto typed_before = editor::capture_build_inputs(typed_config, lua);
atomic_write(typed_compiler, "typed-v2\n");
check(typed_before.toolchain_hash !=
editor::capture_build_inputs(typed_config, lua).toolchain_hash,
"Typed CMake compiler overrides are included in toolchain identity");
auto malicious_config = config;
malicious_config.configure_arguments.push_back(
"-DFASET_GAMEPLAY_SOURCE_DIR:PATH=" + path_to_utf8(root / "outside"));
bool refused_source_override{};
try {
(void)editor::capture_build_inputs(malicious_config, lua);
} catch (const std::exception&) {
refused_source_override = true;
}
check(refused_source_override,
"Configure arguments cannot redirect gameplay compilation away from hashed Scripts");
const auto staged = editor::stage_gameplay_sources(config, changed_tool, lua,
"snapshot-fixture");
check(staged != scripts && fs::is_regular_file(staged / "Gameplay.cpp") &&
read_text(staged / "Extensions/Extra.hpp") == "#define SPEED 2\n",
"Native gameplay compilation uses a complete staged Scripts snapshot");
atomic_write(scripts / "Extensions/Extra.hpp", "#define SPEED 999\n");
check(read_text(staged / "Extensions/Extra.hpp") == "#define SPEED 2\n",
"A live edit cannot alter the source bytes of an in-flight native build");
atomic_write(scripts / "Extensions/Extra.hpp", "#define SPEED 2\n");
check(editor::stage_gameplay_sources(config, changed_tool, lua, "snapshot-again") ==
staged,
"Identical sources retain a stable native path for incremental Ninja builds");
atomic_write(native / "faset_player", "player-v1\n");
atomic_write(native / "faset_schema_exporter", "exporter-v1\n");
atomic_write(native / "CMakeCache.txt", "recipe-v1\n");