| //===- InProcessEPCTest.cpp -- Tests for InProcessEPC ---------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "llvm/ExecutionEngine/Orc/InProcessEPC.h" |
| |
| #include "llvm/ADT/FunctionExtras.h" |
| #include "llvm/ExecutionEngine/Orc/AbsoluteSymbols.h" |
| #include "llvm/ExecutionEngine/Orc/Core.h" |
| #include "llvm/ExecutionEngine/Orc/Shared/WrapperFunctionUtils.h" |
| #include "llvm/Testing/Support/Error.h" |
| #include "gtest/gtest.h" |
| |
| #include <condition_variable> |
| #include <mutex> |
| #include <optional> |
| #include <string> |
| #include <vector> |
| |
| using namespace llvm; |
| using namespace llvm::orc; |
| |
| namespace { |
| |
| // A minimal stand-in for orc_rt::InProcessControllerAccess. Owns a refcounted |
| // ConnectionImpl that mirrors the lifecycle semantics of the real one (refcount |
| // + Connected + InFlightCalls), exposes hooks for tests to react to incoming |
| // controller-side calls, and exposes helpers to drive cross-calls in the |
| // controller -> JIT direction. |
| class MockIPCA { |
| public: |
| using Connection = InProcessEPC::Connection; |
| using OnCallWrapperFn = unique_function<void( |
| uint64_t CallId, void *Fn, shared::WrapperFunctionBuffer ArgBytes)>; |
| using OnReturnJITDispatchResultFn = unique_function<void( |
| uint64_t CallId, shared::WrapperFunctionBuffer ResultBytes)>; |
| |
| MockIPCA() : C(new ConnectionImpl(*this)) {} |
| |
| MockIPCA(const MockIPCA &) = delete; |
| MockIPCA &operator=(const MockIPCA &) = delete; |
| |
| ~MockIPCA() { C->Release(C); } |
| |
| Connection *getConnection() { return C; } |
| |
| bool isConnected() const { return C->isConnected(); } |
| |
| void setOnCallWrapper(OnCallWrapperFn F) { OnCallWrapper = std::move(F); } |
| void setOnReturnJITDispatchResult(OnReturnJITDispatchResultFn F) { |
| OnReturnJITDispatchResult = std::move(F); |
| } |
| |
| // Initiate a JIT-dispatch from the controller side. Returns the chosen |
| // CallId, or std::nullopt if the message scope is closed. |
| std::optional<uint64_t> |
| callJITDispatch(void *HandlerTag, shared::WrapperFunctionBuffer ArgBytes) { |
| if (C->EnterMessageScope(C)) { |
| uint64_t CallId = NextCallId++; |
| C->CallJITDispatch(C->IPEPC, CallId, HandlerTag, ArgBytes.release()); |
| C->LeaveMessageScope(C); |
| return CallId; |
| } |
| return std::nullopt; |
| } |
| |
| // Send a wrapper result back for a prior CallWrapper invocation, or with |
| // an arbitrary CallId for the "unknown id" path. |
| void returnWrapperResult(uint64_t CallId, |
| shared::WrapperFunctionBuffer ResultBytes) { |
| if (C->EnterMessageScope(C)) { |
| C->ReturnWrapperResult(C->IPEPC, CallId, ResultBytes.release()); |
| C->LeaveMessageScope(C); |
| } |
| } |
| |
| private: |
| struct ConnectionImpl : public Connection { |
| ConnectionImpl(MockIPCA &Owner) { |
| Retain = &retainEntry; |
| Release = &releaseEntry; |
| Disconnect = &disconnectEntry; |
| EnterMessageScope = &enterMessageScopeEntry; |
| LeaveMessageScope = &leaveMessageScopeEntry; |
| IPCA = &Owner; |
| CallWrapper = &callWrapperEntry; |
| ReturnJITDispatchResult = &returnJITDispatchResultEntry; |
| } |
| |
| bool isConnected() const { |
| std::scoped_lock<std::mutex> Lock(M); |
| return Connected; |
| } |
| |
| private: |
| void retain() { |
| std::scoped_lock<std::mutex> Lock(M); |
| ++RefCount; |
| } |
| |
| static void retainEntry(Connection *C) { |
| static_cast<ConnectionImpl *>(C)->retain(); |
| } |
| |
| bool release() { |
| std::scoped_lock<std::mutex> Lock(M); |
| return --RefCount == 0; |
| } |
| |
| static void releaseEntry(Connection *C) { |
| if (static_cast<ConnectionImpl *>(C)->release()) |
| delete static_cast<ConnectionImpl *>(C); |
| } |
| |
| void disconnect() { |
| std::unique_lock<std::mutex> Lock(M); |
| if (!Connected) |
| return; |
| Connected = false; |
| CV.wait(Lock, [this]() { return InFlightCalls == 0; }); |
| } |
| |
| static void disconnectEntry(Connection *C) { |
| static_cast<ConnectionImpl *>(C)->disconnect(); |
| } |
| |
| int enterMessageScope() { |
| std::scoped_lock<std::mutex> Lock(M); |
| if (!Connected) |
| return 0; |
| ++InFlightCalls; |
| return 1; |
| } |
| |
| static int enterMessageScopeEntry(Connection *C) { |
| return static_cast<ConnectionImpl *>(C)->enterMessageScope(); |
| } |
| |
| void leaveMessageScope() { |
| bool Notify = false; |
| { |
| std::scoped_lock<std::mutex> Lock(M); |
| --InFlightCalls; |
| if (!Connected && InFlightCalls == 0) |
| Notify = true; |
| } |
| if (Notify) |
| CV.notify_one(); |
| } |
| |
| static void leaveMessageScopeEntry(Connection *C) { |
| static_cast<ConnectionImpl *>(C)->leaveMessageScope(); |
| } |
| |
| mutable std::mutex M; |
| std::condition_variable CV; |
| bool Connected = true; |
| size_t InFlightCalls = 0; |
| size_t RefCount = 1; |
| }; |
| |
| static void callWrapperEntry(void *IPCA, uint64_t CallId, void *Fn, |
| shared::CWrapperFunctionBuffer ArgBytes) { |
| auto *Self = static_cast<MockIPCA *>(IPCA); |
| shared::WrapperFunctionBuffer Buf(ArgBytes); |
| if (Self->OnCallWrapper) |
| Self->OnCallWrapper(CallId, Fn, std::move(Buf)); |
| } |
| |
| static void |
| returnJITDispatchResultEntry(void *IPCA, uint64_t CallId, |
| shared::CWrapperFunctionBuffer ResultBytes) { |
| auto *Self = static_cast<MockIPCA *>(IPCA); |
| shared::WrapperFunctionBuffer Buf(ResultBytes); |
| if (Self->OnReturnJITDispatchResult) |
| Self->OnReturnJITDispatchResult(CallId, std::move(Buf)); |
| } |
| |
| ConnectionImpl *C; |
| uint64_t NextCallId = 0; |
| OnCallWrapperFn OnCallWrapper; |
| OnReturnJITDispatchResultFn OnReturnJITDispatchResult; |
| }; |
| |
| // Provides a BootstrapInfoAccess backed by test-supplied page-size, triple, |
| // value entries, and symbol entries, with knobs to simulate iteration errors. |
| class MockBootstrapInfoAccess : public InProcessEPC::BootstrapInfoAccess { |
| public: |
| MockBootstrapInfoAccess() { |
| GetPageSize = &getPageSizeEntry; |
| GetTargetTriple = &getTargetTripleEntry; |
| GetNextValue = &getNextValueEntry; |
| GetNextSymbol = &getNextSymbolEntry; |
| } |
| |
| void setPageSize(uint64_t PS) { PageSize = PS; } |
| void setTargetTriple(std::string TT) { TargetTriple = std::move(TT); } |
| void addValue(std::string Name, std::vector<char> Bytes) { |
| Values.push_back({std::move(Name), std::move(Bytes)}); |
| } |
| void addSymbol(std::string Name, uint64_t Addr) { |
| Symbols.push_back({std::move(Name), Addr}); |
| } |
| void setValueIterCorrupt() { ValuesCorrupt = true; } |
| void setSymbolIterCorrupt() { SymbolsCorrupt = true; } |
| |
| private: |
| static uint64_t getPageSizeEntry(void *BIA) { |
| return static_cast<MockBootstrapInfoAccess *>(BIA)->PageSize; |
| } |
| |
| static const char *getTargetTripleEntry(void *BIA) { |
| auto &Self = *static_cast<MockBootstrapInfoAccess *>(BIA); |
| return Self.TargetTriple.empty() ? nullptr : Self.TargetTriple.c_str(); |
| } |
| |
| static int getNextValueEntry(void *BIA, const char **Name, |
| const char **ValueBytes, uint64_t *ValueSize) { |
| auto &Self = *static_cast<MockBootstrapInfoAccess *>(BIA); |
| if (Self.NextValue == Self.Values.size()) |
| return Self.ValuesCorrupt ? -1 : 0; |
| auto &V = Self.Values[Self.NextValue++]; |
| *Name = V.first.c_str(); |
| *ValueBytes = V.second.data(); |
| *ValueSize = V.second.size(); |
| return 1; |
| } |
| |
| static int getNextSymbolEntry(void *BIA, const char **Name, uint64_t *Addr) { |
| auto &Self = *static_cast<MockBootstrapInfoAccess *>(BIA); |
| if (Self.NextSymbol == Self.Symbols.size()) |
| return Self.SymbolsCorrupt ? -1 : 0; |
| auto &S = Self.Symbols[Self.NextSymbol++]; |
| *Name = S.first.c_str(); |
| *Addr = S.second; |
| return 1; |
| } |
| |
| uint64_t PageSize = 4096; |
| std::string TargetTriple = "x86_64-unknown-linux-gnu"; |
| std::vector<std::pair<std::string, std::vector<char>>> Values; |
| std::vector<std::pair<std::string, uint64_t>> Symbols; |
| size_t NextValue = 0; |
| size_t NextSymbol = 0; |
| bool ValuesCorrupt = false; |
| bool SymbolsCorrupt = false; |
| }; |
| |
| Expected<std::unique_ptr<InProcessEPC>> |
| createIPEPC(MockIPCA &IPCA, MockBootstrapInfoAccess &BIA) { |
| return InProcessEPC::Create(IPCA.getConnection(), &BIA); |
| } |
| |
| } // namespace |
| |
| TEST(InProcessEPCTest, CreateSuccess) { |
| MockIPCA IPCA; |
| MockBootstrapInfoAccess BIA; |
| BIA.setPageSize(8192); |
| BIA.setTargetTriple("arm64-apple-darwin"); |
| BIA.addValue("greeting", {'h', 'i'}); |
| BIA.addSymbol("malloc", 0x1234); |
| |
| auto EPC = createIPEPC(IPCA, BIA); |
| ASSERT_THAT_EXPECTED(EPC, Succeeded()); |
| |
| EXPECT_EQ((*EPC)->getPageSize(), 8192U); |
| EXPECT_EQ((*EPC)->getTargetTriple().str(), "arm64-apple-darwin"); |
| |
| const auto &VMap = (*EPC)->getBootstrapMap(); |
| ASSERT_EQ(VMap.size(), 1U); |
| auto VI = VMap.find("greeting"); |
| ASSERT_NE(VI, VMap.end()); |
| EXPECT_EQ(std::string(VI->second.begin(), VI->second.end()), "hi"); |
| |
| const auto &SMap = (*EPC)->getBootstrapSymbolsMap(); |
| ASSERT_EQ(SMap.size(), 1U); |
| auto SI = SMap.find("malloc"); |
| ASSERT_NE(SI, SMap.end()); |
| EXPECT_EQ(SI->second, ExecutorAddr(0x1234)); |
| |
| auto *C = IPCA.getConnection(); |
| EXPECT_NE(C->IPEPC, nullptr); |
| EXPECT_NE(C->CallJITDispatch, nullptr); |
| EXPECT_NE(C->ReturnWrapperResult, nullptr); |
| |
| cantFail((*EPC)->disconnect()); |
| } |
| |
| TEST(InProcessEPCTest, CreateFailsOnZeroPageSize) { |
| MockIPCA IPCA; |
| MockBootstrapInfoAccess BIA; |
| BIA.setPageSize(0); |
| |
| auto EPC = createIPEPC(IPCA, BIA); |
| EXPECT_THAT_EXPECTED(std::move(EPC), Failed()); |
| EXPECT_FALSE(IPCA.isConnected()) |
| << "Failed Create should leave the connection torn down"; |
| } |
| |
| TEST(InProcessEPCTest, CreateFailsOnEmptyTriple) { |
| MockIPCA IPCA; |
| MockBootstrapInfoAccess BIA; |
| BIA.setTargetTriple(""); |
| |
| auto EPC = createIPEPC(IPCA, BIA); |
| EXPECT_THAT_EXPECTED(std::move(EPC), Failed()); |
| EXPECT_FALSE(IPCA.isConnected()); |
| } |
| |
| TEST(InProcessEPCTest, CreateFailsOnDuplicateBootstrapValue) { |
| MockIPCA IPCA; |
| MockBootstrapInfoAccess BIA; |
| BIA.addValue("dup", {'a'}); |
| BIA.addValue("dup", {'b'}); |
| |
| auto EPC = createIPEPC(IPCA, BIA); |
| EXPECT_THAT_EXPECTED(std::move(EPC), Failed()); |
| EXPECT_FALSE(IPCA.isConnected()); |
| } |
| |
| TEST(InProcessEPCTest, CreateFailsOnDuplicateBootstrapSymbol) { |
| MockIPCA IPCA; |
| MockBootstrapInfoAccess BIA; |
| BIA.addSymbol("dup", 0x1); |
| BIA.addSymbol("dup", 0x2); |
| |
| auto EPC = createIPEPC(IPCA, BIA); |
| EXPECT_THAT_EXPECTED(std::move(EPC), Failed()); |
| EXPECT_FALSE(IPCA.isConnected()); |
| } |
| |
| TEST(InProcessEPCTest, CreateFailsOnCorruptedValueIteration) { |
| MockIPCA IPCA; |
| MockBootstrapInfoAccess BIA; |
| BIA.setValueIterCorrupt(); |
| |
| auto EPC = createIPEPC(IPCA, BIA); |
| EXPECT_THAT_EXPECTED(std::move(EPC), Failed()); |
| EXPECT_FALSE(IPCA.isConnected()); |
| } |
| |
| TEST(InProcessEPCTest, CreateFailsOnCorruptedSymbolIteration) { |
| MockIPCA IPCA; |
| MockBootstrapInfoAccess BIA; |
| BIA.setSymbolIterCorrupt(); |
| |
| auto EPC = createIPEPC(IPCA, BIA); |
| EXPECT_THAT_EXPECTED(std::move(EPC), Failed()); |
| EXPECT_FALSE(IPCA.isConnected()); |
| } |
| |
| #ifndef NDEBUG |
| TEST(InProcessEPCDeathTest, CreateAssertsOnNullIPCA) { |
| MockIPCA IPCA; |
| MockBootstrapInfoAccess BIA; |
| IPCA.getConnection()->IPCA = nullptr; |
| |
| EXPECT_DEATH( |
| { (void)createIPEPC(IPCA, BIA); }, "C->IPCA not set by controller"); |
| } |
| #endif |
| |
| TEST(InProcessEPCTest, CallWrapperAsyncSuccess) { |
| MockIPCA IPCA; |
| MockBootstrapInfoAccess BIA; |
| auto EPCExp = createIPEPC(IPCA, BIA); |
| ASSERT_THAT_EXPECTED(EPCExp, Succeeded()); |
| auto EPC = std::move(*EPCExp); |
| |
| // Echo back whatever the JIT side sent us. |
| IPCA.setOnCallWrapper( |
| [&](uint64_t CallId, void *, shared::WrapperFunctionBuffer ArgBytes) { |
| IPCA.returnWrapperResult(CallId, std::move(ArgBytes)); |
| }); |
| |
| std::optional<std::string> Result; |
| std::string Payload = "hello"; |
| EPC->callWrapperAsync(ExecutorAddr(0x42), |
| ExecutorProcessControl::RunInPlace()( |
| [&](shared::WrapperFunctionBuffer R) { |
| ASSERT_FALSE(R.getOutOfBandError()) |
| << "Unexpected OOB error: " |
| << R.getOutOfBandError(); |
| Result = std::string(R.data(), R.size()); |
| }), |
| ArrayRef<char>(Payload.data(), Payload.size())); |
| |
| ASSERT_TRUE(Result); |
| EXPECT_EQ(*Result, Payload); |
| |
| cantFail(EPC->disconnect()); |
| } |
| |
| TEST(InProcessEPCTest, CallWrapperAsyncOutOfBandError) { |
| MockIPCA IPCA; |
| MockBootstrapInfoAccess BIA; |
| auto EPCExp = createIPEPC(IPCA, BIA); |
| ASSERT_THAT_EXPECTED(EPCExp, Succeeded()); |
| auto EPC = std::move(*EPCExp); |
| |
| IPCA.setOnCallWrapper( |
| [&](uint64_t CallId, void *, shared::WrapperFunctionBuffer) { |
| IPCA.returnWrapperResult( |
| CallId, |
| shared::WrapperFunctionBuffer::createOutOfBandError("simulated")); |
| }); |
| |
| std::optional<std::string> ErrMsg; |
| std::string Payload = "x"; |
| EPC->callWrapperAsync(ExecutorAddr(0x42), |
| ExecutorProcessControl::RunInPlace()( |
| [&](shared::WrapperFunctionBuffer R) { |
| if (const char *Msg = R.getOutOfBandError()) |
| ErrMsg = Msg; |
| }), |
| ArrayRef<char>(Payload.data(), Payload.size())); |
| |
| ASSERT_TRUE(ErrMsg); |
| EXPECT_EQ(*ErrMsg, "simulated"); |
| |
| cantFail(EPC->disconnect()); |
| } |
| |
| TEST(InProcessEPCTest, CallWrapperAsyncFailsAfterDisconnect) { |
| MockIPCA IPCA; |
| MockBootstrapInfoAccess BIA; |
| auto EPCExp = createIPEPC(IPCA, BIA); |
| ASSERT_THAT_EXPECTED(EPCExp, Succeeded()); |
| auto EPC = std::move(*EPCExp); |
| |
| cantFail(EPC->disconnect()); |
| |
| std::optional<std::string> ErrMsg; |
| std::string Payload = "x"; |
| EPC->callWrapperAsync(ExecutorAddr(0x42), |
| ExecutorProcessControl::RunInPlace()( |
| [&](shared::WrapperFunctionBuffer R) { |
| if (const char *Msg = R.getOutOfBandError()) |
| ErrMsg = Msg; |
| }), |
| ArrayRef<char>(Payload.data(), Payload.size())); |
| |
| ASSERT_TRUE(ErrMsg); |
| EXPECT_EQ(*ErrMsg, "connection closed"); |
| } |
| |
| TEST(InProcessEPCTest, DisconnectDrainsPendingCallWrapperHandlers) { |
| MockIPCA IPCA; |
| MockBootstrapInfoAccess BIA; |
| auto EPCExp = createIPEPC(IPCA, BIA); |
| ASSERT_THAT_EXPECTED(EPCExp, Succeeded()); |
| auto EPC = std::move(*EPCExp); |
| |
| // Mock receives the call but never sends a result. |
| IPCA.setOnCallWrapper([](uint64_t, void *, shared::WrapperFunctionBuffer) {}); |
| |
| std::optional<std::string> ErrMsg; |
| std::string Payload = "payload"; |
| EPC->callWrapperAsync(ExecutorAddr(0x42), |
| ExecutorProcessControl::RunInPlace()( |
| [&](shared::WrapperFunctionBuffer R) { |
| if (const char *Msg = R.getOutOfBandError()) |
| ErrMsg = Msg; |
| }), |
| ArrayRef<char>(Payload.data(), Payload.size())); |
| |
| ASSERT_FALSE(ErrMsg) << "Handler fired before disconnect"; |
| |
| cantFail(EPC->disconnect()); |
| |
| ASSERT_TRUE(ErrMsg); |
| EXPECT_EQ(*ErrMsg, "disconnected"); |
| } |
| |
| TEST(InProcessEPCTest, DestructorDrainsPendingCallWrapperHandlers) { |
| MockIPCA IPCA; |
| MockBootstrapInfoAccess BIA; |
| auto EPCExp = createIPEPC(IPCA, BIA); |
| ASSERT_THAT_EXPECTED(EPCExp, Succeeded()); |
| auto EPC = std::move(*EPCExp); |
| |
| IPCA.setOnCallWrapper([](uint64_t, void *, shared::WrapperFunctionBuffer) {}); |
| |
| std::optional<std::string> ErrMsg; |
| std::string Payload = "x"; |
| EPC->callWrapperAsync(ExecutorAddr(0x42), |
| ExecutorProcessControl::RunInPlace()( |
| [&](shared::WrapperFunctionBuffer R) { |
| if (const char *Msg = R.getOutOfBandError()) |
| ErrMsg = Msg; |
| }), |
| ArrayRef<char>(Payload.data(), Payload.size())); |
| |
| ASSERT_FALSE(ErrMsg); |
| |
| // Drop the IPEPC without an explicit disconnect call: the destructor must |
| // still drive doDisconnect and drain the pending handler. |
| EPC.reset(); |
| |
| ASSERT_TRUE(ErrMsg); |
| EXPECT_EQ(*ErrMsg, "disconnected"); |
| } |
| |
| TEST(InProcessEPCTest, DoubleDisconnectIsIdempotent) { |
| MockIPCA IPCA; |
| MockBootstrapInfoAccess BIA; |
| auto EPCExp = createIPEPC(IPCA, BIA); |
| ASSERT_THAT_EXPECTED(EPCExp, Succeeded()); |
| auto EPC = std::move(*EPCExp); |
| |
| cantFail(EPC->disconnect()); |
| EXPECT_FALSE(IPCA.isConnected()); |
| |
| // Second disconnect (here: via destructor) must not re-fire any handlers or |
| // double-release the connection -- the test would deadlock/segfault if it |
| // did. |
| EPC.reset(); |
| EXPECT_FALSE(IPCA.isConnected()); |
| } |
| |
| namespace { |
| |
| // Custom EPC-attached ES that hands the EPC ownership to the session but |
| // retains a raw pointer for tests that need to drive the EPC directly. |
| struct SessionFixture { |
| SessionFixture(std::unique_ptr<InProcessEPC> EPC) |
| : EPCPtr(EPC.get()), ES(std::move(EPC)), |
| JD(ES.createBareJITDylib("TestJD")) {} |
| |
| ~SessionFixture() { cantFail(ES.endSession()); } |
| |
| InProcessEPC *EPCPtr; |
| ExecutionSession ES; |
| JITDylib &JD; |
| }; |
| |
| } // namespace |
| |
| TEST(InProcessEPCTest, ReturnWrapperResultForInvalidCallIdIsReported) { |
| MockIPCA IPCA; |
| MockBootstrapInfoAccess BIA; |
| auto EPCExp = createIPEPC(IPCA, BIA); |
| ASSERT_THAT_EXPECTED(EPCExp, Succeeded()); |
| |
| SessionFixture Fix(std::move(*EPCExp)); |
| |
| std::string CapturedErr; |
| Fix.ES.setErrorReporter( |
| [&](Error E) { CapturedErr = toString(std::move(E)); }); |
| |
| IPCA.returnWrapperResult( |
| /*CallId=*/0xdead, shared::WrapperFunctionBuffer::copyFrom("ignored", 7)); |
| |
| EXPECT_NE(CapturedErr.find("invalid call id"), std::string::npos) |
| << "Expected invalid-call-id report, got: " << CapturedErr; |
| } |
| |
| TEST(InProcessEPCTest, CallControllerSuccess) { |
| MockIPCA IPCA; |
| MockBootstrapInfoAccess BIA; |
| auto EPCExp = createIPEPC(IPCA, BIA); |
| ASSERT_THAT_EXPECTED(EPCExp, Succeeded()); |
| |
| SessionFixture Fix(std::move(*EPCExp)); |
| |
| constexpr ExecutorAddr TagAddr(0xCAFEBABE); |
| auto Tag = Fix.ES.intern("echo_tag"); |
| cantFail(Fix.JD.define( |
| absoluteSymbols({{Tag, {TagAddr, JITSymbolFlags::Exported}}}))); |
| |
| cantFail(Fix.ES.registerCallControllerHandlers( |
| Fix.JD, ExecutionSession::CallControllerHandlerBinding( |
| SymbolNameSpec::verbatim("echo_tag"), |
| [](ExecutionSession::CallControllerReturnFn Return, |
| shared::WrapperFunctionBuffer ArgBytes) { |
| Return(std::move(ArgBytes)); |
| }))); |
| |
| std::optional<std::string> Result; |
| std::optional<uint64_t> RxCallId; |
| IPCA.setOnReturnJITDispatchResult( |
| [&](uint64_t CallId, shared::WrapperFunctionBuffer ResultBytes) { |
| RxCallId = CallId; |
| Result = std::string(ResultBytes.data(), ResultBytes.size()); |
| }); |
| |
| auto SentCallId = |
| IPCA.callJITDispatch(TagAddr.toPtr<void *>(), |
| shared::WrapperFunctionBuffer::copyFrom("ping", 4)); |
| |
| ASSERT_TRUE(SentCallId); |
| ASSERT_TRUE(Result); |
| EXPECT_EQ(*Result, "ping"); |
| ASSERT_TRUE(RxCallId); |
| EXPECT_EQ(*RxCallId, *SentCallId); |
| } |
| |
| TEST(InProcessEPCTest, CallControllerUnknownHandler) { |
| MockIPCA IPCA; |
| MockBootstrapInfoAccess BIA; |
| auto EPCExp = createIPEPC(IPCA, BIA); |
| ASSERT_THAT_EXPECTED(EPCExp, Succeeded()); |
| |
| SessionFixture Fix(std::move(*EPCExp)); |
| |
| std::optional<std::string> ErrMsg; |
| IPCA.setOnReturnJITDispatchResult( |
| [&](uint64_t, shared::WrapperFunctionBuffer R) { |
| if (const char *Msg = R.getOutOfBandError()) |
| ErrMsg = Msg; |
| }); |
| |
| auto SentCallId = |
| IPCA.callJITDispatch(ExecutorAddr(0xdeadbeef).toPtr<void *>(), |
| shared::WrapperFunctionBuffer::copyFrom("x", 1)); |
| ASSERT_TRUE(SentCallId); |
| |
| ASSERT_TRUE(ErrMsg) |
| << "Expected ReturnJITDispatchResult to deliver an OOB error"; |
| } |
| |
| TEST(InProcessEPCTest, CallControllerAfterDisconnectIsDropped) { |
| MockIPCA IPCA; |
| MockBootstrapInfoAccess BIA; |
| auto EPCExp = createIPEPC(IPCA, BIA); |
| ASSERT_THAT_EXPECTED(EPCExp, Succeeded()); |
| |
| SessionFixture Fix(std::move(*EPCExp)); |
| |
| bool ResultFired = false; |
| IPCA.setOnReturnJITDispatchResult( |
| [&](uint64_t, shared::WrapperFunctionBuffer) { ResultFired = true; }); |
| |
| // Tear down the connection. After this the controller side's |
| // EnterMessageScope returns 0 and CallJITDispatch is never invoked. |
| cantFail(Fix.EPCPtr->disconnect()); |
| |
| auto SentCallId = |
| IPCA.callJITDispatch(ExecutorAddr(0xdeadbeef).toPtr<void *>(), |
| shared::WrapperFunctionBuffer::copyFrom("x", 1)); |
| |
| EXPECT_FALSE(SentCallId) |
| << "callJITDispatch should be dropped after disconnect"; |
| EXPECT_FALSE(ResultFired); |
| } |