Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions pj_plugins/include/pj_plugins/host/plugin_catalog.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,9 @@ struct PluginScanResult {
namespace detail {

/// Return the plugin families whose getter symbols are defined by @p dso_path
/// itself. Missing getters and getters supplied only by dependencies are omitted.
[[nodiscard]] std::vector<PluginFamily> exportedPluginFamilies(
/// itself. Missing getters are omitted; provenance and path-identity failures
/// are returned to the caller instead of being mistaken for absent families.
[[nodiscard]] Expected<std::vector<PluginFamily>> exportedPluginFamilies(
const std::shared_ptr<void>& handle, const std::filesystem::path& dso_path);

} // namespace detail
Expand Down
44 changes: 34 additions & 10 deletions pj_plugins/src/plugin_catalog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
#include <fmt/format.h>

#include <algorithm>
#include <array>
#include <filesystem>
#include <memory>
#include <nlohmann/json.hpp>
#include <sstream>
#include <string>
#include <string_view>
#include <system_error>
#include <utility>

#include "detail/library_loader.hpp"
#include "detail/vtable_validation.hpp"
Expand Down Expand Up @@ -43,6 +45,16 @@ bool hasDsoSuffix(const std::filesystem::path& path) {
return path.extension().string() == kDsoSuffix;
}

bool nativeSymbolPresent(void* handle, const char* symbol) noexcept {
#if defined(_WIN32)
return GetProcAddress(reinterpret_cast<HMODULE>(handle), symbol) != nullptr;
#else
dlerror();
void* resolved = dlsym(handle, symbol);
return dlerror() == nullptr && resolved != nullptr;
#endif
}

// Direct-vtable families share the exact same probe sequence: resolve symbol,
// call entry, check protocol_version, check struct_size, read manifest_json.
// Only the family-specific types and constants vary.
Expand Down Expand Up @@ -348,26 +360,38 @@ Expected<PluginDescriptor> inspectPluginDso(

namespace detail {

std::vector<PluginFamily> exportedPluginFamilies(
Expected<std::vector<PluginFamily>> exportedPluginFamilies(
const std::shared_ptr<void>& handle, const std::filesystem::path& dso_path) {
std::vector<PluginFamily> families;
if (handle == nullptr) {
return families;
return unexpected("library not loaded");
}
auto recorded_path = recordLibraryPathIdentity(dso_path);
if (!recorded_path) {
return families;
return unexpected(recorded_path.error());
}

auto append_if_owned = [&](const char* symbol, PluginFamily family) {
if (resolveSymbol(handle.get(), symbol, *recorded_path)) {
families.push_back(family);
auto append_if_owned = [&](const char* symbol, PluginFamily family) -> Expected<void> {
if (!nativeSymbolPresent(handle.get(), symbol)) {
return {};
}
auto resolved = resolveSymbol(handle.get(), symbol, *recorded_path);
if (!resolved) {
return unexpected(resolved.error());
}
families.push_back(family);
return {};
};
append_if_owned("PJ_get_data_source_vtable", PluginFamily::kDataSource);
append_if_owned("PJ_get_message_parser_vtable", PluginFamily::kMessageParser);
append_if_owned("PJ_get_toolbox_vtable", PluginFamily::kToolbox);
append_if_owned("PJ_get_dialog_vtable", PluginFamily::kDialog);
for (const auto& [symbol, family] : std::array<std::pair<const char*, PluginFamily>, 4>{
std::pair{"PJ_get_data_source_vtable", PluginFamily::kDataSource},
{"PJ_get_message_parser_vtable", PluginFamily::kMessageParser},
{"PJ_get_toolbox_vtable", PluginFamily::kToolbox},
{"PJ_get_dialog_vtable", PluginFamily::kDialog},
}) {
if (auto status = append_if_owned(symbol, family); !status) {
return unexpected(status.error());
}
}
return families;
}

Expand Down
17 changes: 16 additions & 1 deletion pj_plugins/tests/plugin_catalog_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,20 @@ TEST_F(PluginCatalogTest, EntryPointSymbolResolvedFromDependencyIsRejected) {
auto dependency_descriptor = inspectPluginDso(PJ_ENTRY_POINT_VIA_DEPENDENCY_PLUGIN_PATH);
ASSERT_FALSE(dependency_descriptor.has_value());
EXPECT_TRUE(is_provenance_error(dependency_descriptor.error())) << dependency_descriptor.error();

auto raw_handle = detail::loadLibraryHandle(PJ_ENTRY_POINT_VIA_DEPENDENCY_PLUGIN_PATH);
ASSERT_TRUE(raw_handle.has_value()) << raw_handle.error();
auto family_query = detail::exportedPluginFamilies(
detail::adoptLibraryHandle(*raw_handle), PJ_ENTRY_POINT_VIA_DEPENDENCY_PLUGIN_PATH);
#if defined(__APPLE__)
// RTLD_FIRST hides dependency symbols from dlsym outright, so the getter is
// cleanly absent on macOS rather than a provenance violation.
ASSERT_TRUE(family_query.has_value()) << family_query.error();
EXPECT_TRUE(family_query->empty());
#else
ASSERT_FALSE(family_query.has_value());
EXPECT_TRUE(is_provenance_error(family_query.error())) << family_query.error();
#endif
#endif

auto library = DataSourceLibrary::load(PJ_ENTRY_POINT_WITH_OWN_EXPORTS_PLUGIN_PATH);
Expand Down Expand Up @@ -193,7 +207,8 @@ TEST_F(PluginCatalogTest, AlreadyOpenHandleSupportsInspectionLoadingAndFamilyQue
auto shared_handle = detail::adoptLibraryHandleNonOwning(owner.get());

const auto families = detail::exportedPluginFamilies(shared_handle, plugin_path);
EXPECT_EQ(families, std::vector<PluginFamily>{PluginFamily::kDataSource});
ASSERT_TRUE(families.has_value()) << families.error();
EXPECT_EQ(*families, std::vector<PluginFamily>{PluginFamily::kDataSource});

auto descriptor = inspectPluginDso(shared_handle, plugin_path);
ASSERT_TRUE(descriptor.has_value()) << descriptor.error();
Expand Down
Loading