All checks were successful
Flatpak / flatpak (push) Successful in 17m58s
-mllvm -wasm-enable-sjlj, in every wasm compile because setjmp.h does not compile without it, turns setjmp and longjmp into calls to __wasm_setjmp, __wasm_setjmp_test and __wasm_longjmp and a throw of the __c_longjmp tag, all of which live in wasi-libc's libsetjmp. The driver does not link it, so a program that used setjmp, or a C library that handles its errors that way, failed to link with undefined __wasm_setjmp until the project put -lsetjmp in its linkFlags. Build() adds it to every wasm link, next to -lunwind. A static archive contributes only the members something references, so a program without setjmp gets nothing from it, and every sysroot that passes the exception check ships it. WasmExceptions drops its own -lsetjmp, and its C fixture now fails to link without this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
84 lines
4.2 KiB
C++
84 lines
4.2 KiB
C++
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
|
|
|
import std;
|
|
import Crafter.Build;
|
|
namespace fs = std::filesystem;
|
|
using namespace Crafter;
|
|
|
|
// C++ exceptions in a wasm build, run under wasmtime: the fixture throws
|
|
// through frames whose destructors have to run, catches the exception,
|
|
// catches one from a libc++ header (vector::at) and one thrown inside
|
|
// libc++.a itself (stoi), and with an argument lets one escape main, which
|
|
// has to end the run as an uncaught exception rather than exit 0, trap or
|
|
// hang. Its C source uses setjmp, which compiles and links next to the C++
|
|
// objects only when C gets the same wasm flags, and links at all only
|
|
// because the build adds libsetjmp: the configuration asks for nothing.
|
|
//
|
|
// wasm builds used to be compiled with -fno-exceptions, so a throw did not
|
|
// compile and libc++'s own throws aborted. Each part of turning them on fails
|
|
// in its own way, and this is the test that sees it: a std module or a unit
|
|
// built without exceptions (clang rejects the BMI), a link without libunwind
|
|
// (undefined __cpp_exception), the libc++ built without exceptions (stoi
|
|
// aborts), or LLVM's default legacy encoding, which wasmtime refuses to load.
|
|
// Skips like WasmHelloRuns when there is no exception-enabled WASI sysroot or
|
|
// no wasmtime; inside the Flatpak both are bundled.
|
|
namespace {
|
|
std::int32_t failures = 0;
|
|
|
|
void Check(bool cond, std::string_view msg) {
|
|
if (!cond) {
|
|
std::println(std::cerr, "FAIL: {}", msg);
|
|
++failures;
|
|
}
|
|
}
|
|
}
|
|
|
|
int main() {
|
|
const Toolchain& tc = GetToolchain();
|
|
const fs::path libDir = tc.wasiSysroot / "lib" / "wasm32-wasip1";
|
|
if (!fs::exists(libDir / "eh" / "libunwind.a") && !fs::exists(libDir / "libunwind.a")) {
|
|
std::println(std::cerr, "SKIP: no exception-enabled WASI sysroot at {}", tc.wasiSysroot.string());
|
|
return 77;
|
|
}
|
|
if (std::system("which wasmtime > /dev/null 2>&1") != 0) {
|
|
std::println(std::cerr, "SKIP: wasmtime not on PATH");
|
|
return 77;
|
|
}
|
|
|
|
Configuration cfg;
|
|
cfg.path = fs::current_path() / "tests" / "WasmExceptions" / "fixture";
|
|
cfg.name = "wasm-exceptions";
|
|
cfg.outputName = "wasm-exceptions";
|
|
cfg.target = "wasm32-wasip1";
|
|
cfg.type = ConfigurationType::Executable;
|
|
std::array<fs::path, 0> ifaces = {};
|
|
std::array<fs::path, 1> impls = { "main" };
|
|
cfg.GetInterfacesAndImplementations(ifaces, impls);
|
|
cfg.cFiles = { cfg.path / "jump" };
|
|
|
|
std::unordered_map<fs::path, std::shared_future<BuildResult>> depResults;
|
|
std::mutex depMutex;
|
|
const BuildResult r = Build(cfg, depResults, depMutex);
|
|
if (!r.result.empty()) {
|
|
std::println(std::cerr, "build failed: {}", r.result);
|
|
return 1;
|
|
}
|
|
const fs::path wasm = ExpectedOutput(cfg);
|
|
|
|
const auto caught = RunCommandWithTimeout(std::format("wasmtime {}", wasm.string()), std::chrono::seconds(60));
|
|
Check(caught.exitCode == 0, std::format("the run that catches everything exits 0 (exit {}): {}", caught.exitCode, caught.output));
|
|
Check(caught.output.contains("caught: thrown in wasm after 4 destructors"), std::format("the exception is caught after unwinding all four frames: {}", caught.output));
|
|
Check(caught.output.contains("caught: out_of_range from libc++"), std::format("an exception from a libc++ header is catchable: {}", caught.output));
|
|
Check(caught.output.contains("caught: invalid_argument from inside libc++"), std::format("an exception from inside libc++.a is catchable: {}", caught.output));
|
|
Check(caught.output.contains("longjmp came back with 7"), std::format("setjmp in a C source works next to the C++ objects: {}", caught.output));
|
|
|
|
const auto escaped = RunCommandWithTimeout(std::format("wasmtime {} uncaught", wasm.string()), std::chrono::seconds(60));
|
|
Check(escaped.exitCode != 0 && !escaped.timedOut && !escaped.crashed && escaped.output.contains("thrown Wasm exception"), std::format("an exception escaping main ends the run as an uncaught exception (exit {}): {}", escaped.exitCode, escaped.output));
|
|
|
|
if (failures > 0) {
|
|
std::println(std::cerr, "{} assertions failed", failures);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|