Protect native cache paths and snapshot gameplay sources
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -147,8 +147,48 @@ int test_main(int argc, char** argv) {
|
||||
authoring::AuthoringService authoring(config.project_root, authoring::builtin_schemas());
|
||||
const auto valid = manifest();
|
||||
atomic_write_json(config.project_root / "schema-fixture.json", valid);
|
||||
{
|
||||
auto compiler_config = config;
|
||||
compiler_config.project_root = root / "c-only-compiler-project";
|
||||
compiler_config.configure_arguments = {
|
||||
"-DCMAKE_C_COMPILER:FILEPATH=fixture-c-only"};
|
||||
editor::BuildService compiler_build(compiler_config);
|
||||
compiler_build.scaffold("Compiler argument fixture", 2);
|
||||
atomic_write_json(compiler_config.project_root / "schema-fixture.json", valid);
|
||||
const auto compiler_result = compiler_build.wait(compiler_build.start_build());
|
||||
check(compiler_result.state == "succeeded",
|
||||
"C-only compiler override fixture builds: " + compiler_result.error);
|
||||
const auto compiler_args =
|
||||
read_json(compiler_config.project_root / "configure-fixture.json");
|
||||
std::size_t c_overrides{};
|
||||
bool cxx_default{};
|
||||
for (const auto& arg : compiler_args) {
|
||||
const auto text = arg.get<std::string>();
|
||||
if (text.starts_with("-DCMAKE_C_COMPILER")) {
|
||||
++c_overrides;
|
||||
check(text == "-DCMAKE_C_COMPILER:FILEPATH=fixture-c-only",
|
||||
"Typed C override is not replaced by a default compiler");
|
||||
}
|
||||
if (text.starts_with("-DCMAKE_CXX_COMPILER="))
|
||||
cxx_default = true;
|
||||
}
|
||||
check(c_overrides == 1 && cxx_default,
|
||||
"C and C++ compiler defaults are selected independently");
|
||||
}
|
||||
auto first = builds.wait(builds.start_build());
|
||||
check(first.state == "succeeded", "Valid custom schema v2 publishes: " + first.error);
|
||||
fs::path compiled_scripts;
|
||||
for (const auto& arg : read_json(config.project_root / "configure-fixture.json")) {
|
||||
const auto text = arg.get<std::string>();
|
||||
constexpr std::string_view prefix = "-DFASET_GAMEPLAY_SOURCE_DIR=";
|
||||
if (text.starts_with(prefix))
|
||||
compiled_scripts = path_from_utf8(text.substr(prefix.size()));
|
||||
}
|
||||
check(!compiled_scripts.empty() &&
|
||||
compiled_scripts != config.project_root / "Scripts" &&
|
||||
read_text(compiled_scripts / "Extensions/BuildOnly.hpp") ==
|
||||
"#define BUILD_ONLY 1\n",
|
||||
"Native C++ build receives the immutable source snapshot");
|
||||
const auto repeated = builds.wait(builds.start_build());
|
||||
check(repeated.state == "succeeded" &&
|
||||
repeated.result.at("generation") == first.result.at("generation") &&
|
||||
|
||||
Reference in New Issue
Block a user