From 5d555a10a2ba591d8b5c0da066bc7a3bb96746d2 Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Thu, 10 Feb 2022 12:07:19 +0100 Subject: [PATCH 1/5] Thunks: Explicitly put thunks into the text library section Previously, defining zero-initialized variables right before LOAD_LIB could cause the compiler to put thunk definitions into bss, hence triggering errors during assembly ("attempt to store non-zero value in section `.bss'"). --- ThunkLibs/include/common/Guest.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ThunkLibs/include/common/Guest.h b/ThunkLibs/include/common/Guest.h index e994427c2..29775f6cf 100644 --- a/ThunkLibs/include/common/Guest.h +++ b/ThunkLibs/include/common/Guest.h @@ -4,7 +4,7 @@ #ifndef _M_ARM_64 #define MAKE_THUNK(lib, name, hash) \ extern "C" int fexthunks_##lib##_##name(void *args); \ - asm("fexthunks_" #lib "_" #name ":\n.byte 0xF, 0x3F\n.byte " hash ); + asm(".text\nfexthunks_" #lib "_" #name ":\n.byte 0xF, 0x3F\n.byte " hash ); #else // We're compiling for IDE integration, so provide a dummy-implementation that just calls an undefined function. // The name of that function serves as an error message if this library somehow gets loaded at runtime. From 4aed6fc56cf16cadb6de2fb74555c88af6672a85 Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Thu, 10 Feb 2022 12:07:21 +0100 Subject: [PATCH 2/5] Thunks/gen: Remove now unneeded code --- ThunkLibs/Generator/gen.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/ThunkLibs/Generator/gen.cpp b/ThunkLibs/Generator/gen.cpp index 38720d2f4..39d420855 100644 --- a/ThunkLibs/Generator/gen.cpp +++ b/ThunkLibs/Generator/gen.cpp @@ -45,17 +45,6 @@ struct ThunkedFunction : FunctionParams { // This is implied e.g. for thunks generated for variadic functions bool custom_host_impl = false; - FunctionParams GetNonvariadicParams() const { - if (!is_variadic) { - return *this; - } - - auto ret = param_types; - ret.pop_back(); - ret.pop_back(); - return { std::move(ret) }; - } - std::string GetOriginalFunctionName() const { const std::string suffix = "_internal"; assert(function_name.length() > suffix.size()); From 3c7872c6d59e9232bd9f242dec0cfaaa2d4c6396 Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Thu, 10 Feb 2022 12:07:22 +0100 Subject: [PATCH 3/5] Thunks: Rename FrontendAction to GenerateThunkLibsAction --- ThunkLibs/Generator/gen.cpp | 6 +++--- ThunkLibs/Generator/interface.h | 4 ++-- ThunkLibs/Generator/main.cpp | 8 ++++---- unittests/ThunkLibs/generator.cpp | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ThunkLibs/Generator/gen.cpp b/ThunkLibs/Generator/gen.cpp index 39d420855..91d946f16 100644 --- a/ThunkLibs/Generator/gen.cpp +++ b/ThunkLibs/Generator/gen.cpp @@ -365,7 +365,7 @@ public: } }; -FrontendAction::FrontendAction(const std::string& libname_, const OutputFilenames& output_filenames_) +GenerateThunkLibsAction::GenerateThunkLibsAction(const std::string& libname_, const OutputFilenames& output_filenames_) : libfilename(libname_), libname(libname_), output_filenames(output_filenames_) { for (auto& c : libname) { if (c == '-') { @@ -395,7 +395,7 @@ static std::string format_function_args(const FunctionParams& params) { [](std::size_t idx) -> std::string { return "args->a_" + std::to_string(idx); }); } -void FrontendAction::EndSourceFileAction() { +void GenerateThunkLibsAction::EndSourceFileAction() { static auto format_decl = [](clang::QualType type, const std::string_view& name) { if (type->isFunctionPointerType()) { auto signature = type.getAsString(); @@ -758,6 +758,6 @@ void FrontendAction::EndSourceFileAction() { } } -std::unique_ptr FrontendAction::CreateASTConsumer(clang::CompilerInstance&, clang::StringRef) { +std::unique_ptr GenerateThunkLibsAction::CreateASTConsumer(clang::CompilerInstance&, clang::StringRef) { return std::make_unique(); } diff --git a/ThunkLibs/Generator/interface.h b/ThunkLibs/Generator/interface.h index 0dac1b5f9..52f82df11 100644 --- a/ThunkLibs/Generator/interface.h +++ b/ThunkLibs/Generator/interface.h @@ -25,9 +25,9 @@ struct OutputFilenames { std::string symbol_list; }; -class FrontendAction : public clang::ASTFrontendAction { +class GenerateThunkLibsAction : public clang::ASTFrontendAction { public: - FrontendAction(const std::string& libname, const OutputFilenames&); + GenerateThunkLibsAction(const std::string& libname, const OutputFilenames&); void EndSourceFileAction() override; diff --git a/ThunkLibs/Generator/main.cpp b/ThunkLibs/Generator/main.cpp index 2b10a6b0e..93208b99f 100644 --- a/ThunkLibs/Generator/main.cpp +++ b/ThunkLibs/Generator/main.cpp @@ -12,14 +12,14 @@ void print_usage(const char* program_name) { std::cerr << "Usage: " << program_name << " -- \n"; } -class ThunkGenFrontendActionFactory : public clang::tooling::FrontendActionFactory { +class GenerateThunkLibsActionFactory : public clang::tooling::FrontendActionFactory { public: - ThunkGenFrontendActionFactory(std::string_view libname_, OutputFilenames output_filenames_) + GenerateThunkLibsActionFactory(std::string_view libname_, OutputFilenames output_filenames_) : libname(std::move(libname_)), output_filenames(std::move(output_filenames_)) { } std::unique_ptr create() override { - return std::make_unique(libname, output_filenames); + return std::make_unique(libname, output_filenames); } private: @@ -85,5 +85,5 @@ int main(int argc, char* argv[]) { } ClangTool Tool(*compile_db, { filename }); - return Tool.run(std::make_unique(std::move(libname), std::move(output_filenames)).get()); + return Tool.run(std::make_unique(std::move(libname), std::move(output_filenames)).get()); } diff --git a/unittests/ThunkLibs/generator.cpp b/unittests/ThunkLibs/generator.cpp index 28611cde1..f4c5212a8 100644 --- a/unittests/ThunkLibs/generator.cpp +++ b/unittests/ThunkLibs/generator.cpp @@ -181,7 +181,7 @@ public: /** * The "silent" parameter is used to suppress non-fatal diagnostics in tests that expect failure */ -static void run_tool(std::unique_ptr action, std::string_view code, bool silent = false) { +static void run_tool(std::unique_ptr action, std::string_view code, bool silent = false) { const char* memory_filename = "gen_input.cpp"; auto adjuster = clang::tooling::getClangStripDependencyFileAdjuster(); std::vector args = { "clang-tool", "-fsyntax-only", "-std=c++17", "-Werror", "-I.", memory_filename }; @@ -214,7 +214,7 @@ struct callback_guest : callback_annotation_base {}; * Generates guest thunk library code from the given input */ std::string Fixture::run_thunkgen_guest(std::string_view code, bool silent) { - run_tool(std::make_unique(libname, output_filenames), code, silent); + run_tool(std::make_unique(libname, output_filenames), code, silent); std::string result = "#define MAKE_THUNK(lib, name, hash) extern \"C\" int fexthunks_##lib##_##name(void*);\n"; for (auto& filename : { @@ -235,7 +235,7 @@ std::string Fixture::run_thunkgen_guest(std::string_view code, bool silent) { * Generates host thunk library code from the given input */ std::string Fixture::run_thunkgen_host(std::string_view code, bool silent) { - run_tool(std::make_unique(libname, output_filenames), code, silent); + run_tool(std::make_unique(libname, output_filenames), code, silent); std::string result = "#include \n" From ee6b55800088457329845ad8f2b36ad226261e66 Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Thu, 10 Feb 2022 12:07:23 +0100 Subject: [PATCH 4/5] Thunks: Fix warning about unused field --- ThunkLibs/include/common/Host.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ThunkLibs/include/common/Host.h b/ThunkLibs/include/common/Host.h index 533adf156..992078fd7 100644 --- a/ThunkLibs/include/common/Host.h +++ b/ThunkLibs/include/common/Host.h @@ -36,7 +36,7 @@ static fex_call_callback_t* call_guest; */ struct fex_guest_function_ptr { private: - void* value = nullptr; + [[maybe_unused]] void* value = nullptr; public: fex_guest_function_ptr() = default; From 68426735a5a474a0ec62504d500efefdb061cf12 Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Thu, 10 Feb 2022 12:07:24 +0100 Subject: [PATCH 5/5] Thunks: Clean up ASTMatcher-based testing helpers The run_thunkgen* helpers now parse generated source code and return its AST representation, so HasASTMatching helper calls don't each need to redundantly compile it themselves. This also ensures the generator output actually compiles in tests where we didn't explicitly check that before. This also allows printing the full AST of the generator output on test failures. This must be enabled manually by changing a variable in the ostream output operator for SourceWithAST. --- ThunkLibs/Generator/interface.h | 16 +++ ThunkLibs/Generator/main.cpp | 15 --- unittests/ThunkLibs/generator.cpp | 181 +++++++++++++++++++----------- 3 files changed, 134 insertions(+), 78 deletions(-) diff --git a/ThunkLibs/Generator/interface.h b/ThunkLibs/Generator/interface.h index 52f82df11..54f0eaccd 100644 --- a/ThunkLibs/Generator/interface.h +++ b/ThunkLibs/Generator/interface.h @@ -1,4 +1,5 @@ #include +#include #include #include @@ -38,3 +39,18 @@ private: std::string libname; // sanitized filename, usable as part of emitted function names const OutputFilenames& output_filenames; }; + +class GenerateThunkLibsActionFactory : public clang::tooling::FrontendActionFactory { +public: + GenerateThunkLibsActionFactory(std::string_view libname_, OutputFilenames output_filenames_) + : libname(std::move(libname_)), output_filenames(std::move(output_filenames_)) { + } + + std::unique_ptr create() override { + return std::make_unique(libname, output_filenames); + } + +private: + std::string libname; + OutputFilenames output_filenames; +}; diff --git a/ThunkLibs/Generator/main.cpp b/ThunkLibs/Generator/main.cpp index 93208b99f..43093c43e 100644 --- a/ThunkLibs/Generator/main.cpp +++ b/ThunkLibs/Generator/main.cpp @@ -12,21 +12,6 @@ void print_usage(const char* program_name) { std::cerr << "Usage: " << program_name << " -- \n"; } -class GenerateThunkLibsActionFactory : public clang::tooling::FrontendActionFactory { -public: - GenerateThunkLibsActionFactory(std::string_view libname_, OutputFilenames output_filenames_) - : libname(std::move(libname_)), output_filenames(std::move(output_filenames_)) { - } - - std::unique_ptr create() override { - return std::make_unique(libname, output_filenames); - } - -private: - std::string libname; - OutputFilenames output_filenames; -}; - int main(int argc, char* argv[]) { if (argc < 6) { print_usage(argv[0]); diff --git a/unittests/ThunkLibs/generator.cpp b/unittests/ThunkLibs/generator.cpp index f4c5212a8..62109cd8a 100644 --- a/unittests/ThunkLibs/generator.cpp +++ b/unittests/ThunkLibs/generator.cpp @@ -2,15 +2,49 @@ #include #include +#include #include #include +#include + #include #include #include #include +/** + * This class parses its input code and stores it alongside its AST representation. + * + * Use this with HasASTMatching in Catch2's CHECK_THAT/REQUIRE_THAT macros. + */ +struct SourceWithAST { + std::string code; + std::unique_ptr ast; + + SourceWithAST(std::string_view input); +}; + +std::ostream& operator<<(std::ostream& os, const SourceWithAST& ast) { + os << ast.code; + + // Additionally, change this to true to print the full AST on test failures + const bool print_ast = false; + if (print_ast) { + for (auto it = ast.ast->top_level_begin(); it != ast.ast->top_level_end(); ++it) { + // Skip header declarations + if (!ast.ast->isInMainFileID((*it)->getBeginLoc())) { + continue; + } + + auto llvm_os = llvm::raw_os_ostream { os }; + (*it)->dump(llvm_os); + } + } + return os; +} + struct Fixture { Fixture() { tmpdir = std::tmpnam(nullptr); @@ -36,13 +70,19 @@ struct Fixture { } struct GenOutput { - std::string guest; - std::string host; + SourceWithAST guest; + SourceWithAST host; }; - std::string run_thunkgen_guest(std::string_view code, bool silent = false); - std::string run_thunkgen_host(std::string_view code, bool silent = false); - GenOutput run_thunkgen(std::string_view code, bool silent = false); + /** + * Runs the given given code through the thunk generator and verifies the output compiles. + * + * Input code with common definitions (types, functions, ...) should be specified in "prelude". + * It will be prepended to "code" before processing and also to the generator output. + */ + SourceWithAST run_thunkgen_guest(std::string_view prelude, std::string_view code, bool silent = false); + SourceWithAST run_thunkgen_host(std::string_view prelude, std::string_view code, bool silent = false); + GenOutput run_thunkgen(std::string_view prelude, std::string_view code, bool silent = false); const std::string libname = "libtest"; std::string tmpdir; @@ -83,10 +123,14 @@ public: } }; +/** + * This class connects the libclang AST to Catch2 test matchers, allowing for + * code compiled via SourceWithAST objects to be pattern-matched using the + * libclang ASTMatcher API. + */ template -class HasASTMatching : public Catch::MatcherBase { - const ClangMatcher& matcher; - +class HasASTMatching : public Catch::MatcherBase { + ClangMatcher matcher; MatchCallback callback; public: @@ -100,12 +144,12 @@ public: return *this; } - bool match(const std::string& code) const override { + bool match(const SourceWithAST& code) const override { MatchCallback result = callback; clang::ast_matchers::MatchFinder finder; finder.addMatcher(matcher, &result); - bool compile_success = clang::tooling::runToolOnCodeWithArgs(clang::tooling::newFrontendActionFactory(&finder)->create(), code, { "-std=c++17", "-Werror" }); - return compile_success && result.matched(); + finder.matchAST(code.ast->getASTContext()); + return result.matched(); } std::string describe() const override { @@ -126,19 +170,11 @@ HasASTMatching matches(const StatementMatcher& matcher_) { /** * Catch matcher that checks if a tested C++ source defines a function with the given name */ -class DefinesPublicFunction : public Catch::MatcherBase { +class DefinesPublicFunction : public HasASTMatching { std::string function_name; public: - DefinesPublicFunction(std::string_view name) : function_name(name) { - } - - bool match(const std::string& code) const override { - MatchCallback result; - clang::ast_matchers::MatchFinder finder; - finder.addMatcher(functionDecl(hasName(function_name)), &result); - clang::tooling::runToolOnCodeWithArgs(clang::tooling::newFrontendActionFactory(&finder)->create(), code, { "-std=c++17" }); - return result.matched(); + DefinesPublicFunction(std::string_view name) : HasASTMatching(functionDecl(hasName(name))), function_name(name) { } std::string describe() const override { @@ -181,7 +217,7 @@ public: /** * The "silent" parameter is used to suppress non-fatal diagnostics in tests that expect failure */ -static void run_tool(std::unique_ptr action, std::string_view code, bool silent = false) { +static void run_tool(clang::tooling::ToolAction& action, std::string_view code, bool silent = false) { const char* memory_filename = "gen_input.cpp"; auto adjuster = clang::tooling::getClangStripDependencyFileAdjuster(); std::vector args = { "clang-tool", "-fsyntax-only", "-std=c++17", "-Werror", "-I.", memory_filename }; @@ -195,12 +231,15 @@ struct callback_guest : callback_annotation_base {}; } // namespace fexgen )"; + llvm::IntrusiveRefCntPtr overlay_fs(new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem())); llvm::IntrusiveRefCntPtr memory_fs(new llvm::vfs::InMemoryFileSystem); + overlay_fs->pushOverlay(memory_fs); memory_fs->addFile(memory_filename, 0, llvm::MemoryBuffer::getMemBufferCopy(code)); memory_fs->addFile("thunks_common.h", 0, llvm::MemoryBuffer::getMemBufferCopy(common_header_code)); - llvm::IntrusiveRefCntPtr files(new clang::FileManager(clang::FileSystemOptions(), memory_fs)); + llvm::IntrusiveRefCntPtr files(new clang::FileManager(clang::FileSystemOptions(), overlay_fs)); + + auto invocation = clang::tooling::ToolInvocation(args, &action, files.get(), std::make_shared()); - auto invocation = clang::tooling::ToolInvocation(args, std::move(action), files.get()); TestDiagnosticConsumer consumer(silent); invocation.setDiagnosticConsumer(&consumer); invocation.run(); @@ -210,11 +249,37 @@ struct callback_guest : callback_annotation_base {}; } } +static void run_tool(std::unique_ptr action, std::string_view code, bool silent = false) { + return run_tool(*action, code, silent); +} + +SourceWithAST::SourceWithAST(std::string_view input) : code(input) { + // Call run_tool with a ToolAction that assigns this->ast + + struct ToolAction : clang::tooling::ToolAction { + std::unique_ptr& ast; + + ToolAction(std::unique_ptr& ast_) : ast(ast_) { } + + bool runInvocation(std::shared_ptr invocation, + clang::FileManager* files, + std::shared_ptr pch, + clang::DiagnosticConsumer *diag_consumer) override { + auto diagnostics = clang::CompilerInstance::createDiagnostics(&invocation->getDiagnosticOpts(), diag_consumer, false); + ast = clang::ASTUnit::LoadFromCompilerInvocation(invocation, std::move(pch), std::move(diagnostics), files); + return (ast != nullptr); + } + } tool_action { ast }; + + run_tool(tool_action, code); +} + /** * Generates guest thunk library code from the given input */ -std::string Fixture::run_thunkgen_guest(std::string_view code, bool silent) { - run_tool(std::make_unique(libname, output_filenames), code, silent); +SourceWithAST Fixture::run_thunkgen_guest(std::string_view prelude, std::string_view code, bool silent) { + const std::string full_code = std::string { prelude } + std::string { code }; + run_tool(std::make_unique(libname, output_filenames), full_code, silent); std::string result = "#define MAKE_THUNK(lib, name, hash) extern \"C\" int fexthunks_##lib##_##name(void*);\n"; for (auto& filename : { @@ -228,14 +293,15 @@ std::string Fixture::run_thunkgen_guest(std::string_view code, bool silent) { result.resize(result.size() + new_data_size); file.read(result.data() + current_size, result.size()); } - return result; + return SourceWithAST { std::string { prelude } + result }; } /** * Generates host thunk library code from the given input */ -std::string Fixture::run_thunkgen_host(std::string_view code, bool silent) { - run_tool(std::make_unique(libname, output_filenames), code, silent); +SourceWithAST Fixture::run_thunkgen_host(std::string_view prelude, std::string_view code, bool silent) { + const std::string full_code = std::string { prelude } + std::string { code }; + run_tool(std::make_unique(libname, output_filenames), full_code, silent); std::string result = "#include \n" @@ -276,16 +342,16 @@ std::string Fixture::run_thunkgen_host(std::string_view code, bool silent) { result += "};\n"; } } - return result; + return SourceWithAST { std::string { prelude } + result }; } -Fixture::GenOutput Fixture::run_thunkgen(std::string_view code, bool silent) { - return { run_thunkgen_guest(code, silent), - run_thunkgen_host(code, silent) }; +Fixture::GenOutput Fixture::run_thunkgen(std::string_view prelude, std::string_view code, bool silent) { + return { run_thunkgen_guest(prelude, code, silent), + run_thunkgen_host(prelude, code, silent) }; } TEST_CASE_METHOD(Fixture, "Trivial") { - const auto output = run_thunkgen( + const auto output = run_thunkgen("", "#include \n" "void func();\n" "template struct fex_gen_config {};\n" @@ -314,20 +380,18 @@ TEST_CASE_METHOD(Fixture, "Trivial") { // Unknown annotations trigger an error TEST_CASE_METHOD(Fixture, "UnknownAnnotation") { - REQUIRE_THROWS(run_thunkgen( - "void func();\n" + REQUIRE_THROWS(run_thunkgen("void func();\n", "struct invalid_annotation {};\n" "template struct fex_gen_config {};\n" "template<> struct fex_gen_config : invalid_annotation {};\n", true)); - REQUIRE_THROWS(run_thunkgen( - "void func();\n" + REQUIRE_THROWS(run_thunkgen("void func();\n", "template struct fex_gen_config {};\n" "template<> struct fex_gen_config { int invalid_field_annotation; };\n", true)); } TEST_CASE_METHOD(Fixture, "VersionedLibrary") { - const auto output = run_thunkgen_host( + const auto output = run_thunkgen_host("", "template struct fex_gen_config { int version = 123; };\n"); CHECK_THAT(output, @@ -342,8 +406,7 @@ TEST_CASE_METHOD(Fixture, "VersionedLibrary") { // Parameter is a function pointer TEST_CASE_METHOD(Fixture, "FunctionPointerParameter") { - const auto output = run_thunkgen_guest( - "void func(int (*funcptr)(char, char));\n" + const auto output = run_thunkgen_guest("void func(int (*funcptr)(char, char));\n", "template struct fex_gen_config {};\n" "template<> struct fex_gen_config {};\n"); @@ -361,9 +424,8 @@ TEST_CASE_METHOD(Fixture, "GuestFunctionPointerParameter") { const std::string prelude = "struct fex_guest_function_ptr { int (*x)(char,char); };\n" "void fexfn_impl_libtest_func(fex_guest_function_ptr);\n"; - const auto output = run_thunkgen( - "#include \n" + - prelude + + const auto output = run_thunkgen(prelude, + "#include \n" "void func(int (*funcptr)(char, char));\n" "template struct fex_gen_config {};\n" "template<> struct fex_gen_config : fexgen::callback_guest, fexgen::custom_host_impl {};\n"); @@ -377,24 +439,21 @@ TEST_CASE_METHOD(Fixture, "GuestFunctionPointerParameter") { ))); // Host-side implementation only sees an opaque type that it can't call - CHECK_THAT(prelude + output.host, + CHECK_THAT(output.host, matches(callExpr(callee(functionDecl(hasName("fexfn_impl_libtest_func"))), hasArgument(0, hasType(asString("struct fex_guest_function_ptr"))) ))); } TEST_CASE_METHOD(Fixture, "MultipleParameters") { - const std::string prelude = "struct TestStruct { int member; };\n"; + const std::string prelude = + "struct TestStruct { int member; };\n"; - auto output = run_thunkgen( - prelude + + auto output = run_thunkgen(prelude, "void func(int arg, char, unsigned long, TestStruct);\n" "template struct fex_gen_config {};\n" "template<> struct fex_gen_config {};\n"); - output.guest = prelude + output.guest; - output.host = prelude + output.host; - // Guest code CHECK_THAT(output.guest, DefinesPublicFunction("func")); @@ -436,28 +495,25 @@ TEST_CASE_METHOD(Fixture, "MultipleParameters") { // Returning a function pointer should trigger an error unless an annotation is provided TEST_CASE_METHOD(Fixture, "ReturnFunctionPointer") { - const std::string prelude = "#include \nusing funcptr = void (*)(char, char);\n"; + const std::string prelude = "using funcptr = void (*)(char, char);\n"; - REQUIRE_THROWS(run_thunkgen_guest( - prelude + + REQUIRE_THROWS(run_thunkgen_guest(prelude, "funcptr func(int);\n" "template struct fex_gen_config {};\n" "template<> struct fex_gen_config {};\n", true)); - REQUIRE_NOTHROW(run_thunkgen_guest( - prelude + + REQUIRE_NOTHROW(run_thunkgen_guest(prelude, + "#include \n" "funcptr func(int);\n" "template struct fex_gen_config {};\n" "template<> struct fex_gen_config : fexgen::returns_guest_pointer {};\n")); } TEST_CASE_METHOD(Fixture, "VariadicFunction") { - const std::string input = - "void func(int arg, ...);\n" - "template struct fex_gen_config {};\n"; + const std::string prelude = "void func(int arg, ...);\n"; - const auto output = run_thunkgen_guest( - input + + const auto output = run_thunkgen_guest(prelude, + "template struct fex_gen_config {};\n" "template<> struct fex_gen_config {\n" " using uniform_va_type = char;\n" "};\n"); @@ -475,8 +531,7 @@ TEST_CASE_METHOD(Fixture, "VariadicFunction") { // Variadic functions without annotation trigger an error TEST_CASE_METHOD(Fixture, "VariadicFunctionsWithoutAnnotation") { - REQUIRE_THROWS(run_thunkgen_guest( - "void func(int arg, ...);\n" + REQUIRE_THROWS(run_thunkgen_guest("void func(int arg, ...);\n", "template struct fex_gen_config {};\n" "template<> struct fex_gen_config {};\n", true)); }