Files
TheRock/rocm-libraries/projects/hipdnn/tests/frontend/IntegrationMalformedVersionPlugin.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

237 lines
9.2 KiB
C++

// Copyright © Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
// Frontend integration test for backend dispatch hardening. A plugin that
// reports an unparseable `apiVersion` string must be rejected at load time by the host's
// `EnginePluginManager::validateBeforeAdding`. The bad plugin is
// caught by the existing `tryCatch` wrapper inside `loadPluginFromFile`,
// logged, and skipped; well-formed plugins loaded alongside it continue
// to function and serve dispatch.
//
// Without the load-time guard, the per-call `Version{plugin->apiVersion()}`
// in `EnginePluginResourceManager::getApplicableEngineIds` would throw
// `std::invalid_argument` on every graph execute — a single bad plugin
// could brick every dispatch in the host.
#include <gtest/gtest.h>
#include <hip/hip_runtime.h>
#include "OverrideTestUtils.hpp"
#include <hipdnn_data_sdk/utilities/Tensor.hpp>
#include <hipdnn_frontend.hpp>
#include <hipdnn_test_sdk/utilities/TestUtilities.hpp>
#include <test_plugins/TestPluginCommon.hpp>
#include <test_plugins/TestPluginConstants.hpp>
#include <array>
#include <cstdint>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
using namespace hipdnn_frontend;
using namespace hipdnn_frontend::graph;
using namespace hipdnn_data_sdk::utilities;
namespace
{
/// Build a minimal pointwise (RELU) graph for execute-path coverage. The
/// graph is non-override (legacy) so it admits every plugin reporting at
/// least the post-PR1 baseline `"1.0.0"` — the malformed plugin is the
/// only one that should be filtered out, and only at load time. Thin
/// wrapper over the shared `buildPointwiseReluGraph` helper.
std::shared_ptr<Graph> createSimpleReluGraph(const std::string& graphName,
const std::vector<int64_t>& dims)
{
return hipdnn_tests::override_test_utils::buildPointwiseReluGraph(
graphName, dims, /*strides=*/{}, /*overrideShapeEnabled=*/false);
}
class IntegrationMalformedVersionPlugin : public ::testing::Test
{
protected:
void SetUp() override
{
SKIP_IF_NO_DEVICES();
ASSERT_EQ(hipInit(0), hipSuccess);
int deviceId = 0;
ASSERT_EQ(hipGetDevice(&deviceId), hipSuccess);
}
void TearDown() override
{
if(_handle != nullptr)
{
ASSERT_EQ(hipdnnDestroy(_handle), HIPDNN_STATUS_SUCCESS);
_handle = nullptr;
}
}
hipdnnHandle_t _handle = nullptr;
};
} // namespace
/// Dispatch-hardening fix #1: loading a malformed-version plugin alongside
/// a well-formed plugin must not crash hipdnnCreate, must not throw out of
/// graph execute, and the well-formed plugin must remain available to
/// serve the graph. Without the fix, `Version{...}` would be re-parsed in
/// `getApplicableEngineIds` and throw `std::invalid_argument`.
TEST_F(IntegrationMalformedVersionPlugin, BadPluginLoadedAlongsideGoodPluginDoesNotCrashDispatch)
{
const std::array<const char*, 2> paths
= {hipdnn_tests::plugin_constants::testMalformedVersionPluginPath().c_str(),
hipdnn_tests::plugin_constants::testGoodPluginPath().c_str()};
// The host must absorb the malformed plugin's load-time rejection inside
// its `tryCatch` wrapper; `hipdnnCreate` should still succeed.
ASSERT_EQ(
hipdnnSetEnginePluginPaths_ext(paths.size(), paths.data(), HIPDNN_PLUGIN_LOADING_ABSOLUTE),
HIPDNN_STATUS_SUCCESS);
ASSERT_EQ(hipdnnCreate(&_handle), HIPDNN_STATUS_SUCCESS);
// Compile a non-override graph — the well-formed test_good_plugin
// reports "1.0.0" so it satisfies the baseline requirement.
const std::vector<int64_t> dims = {1, 3, 4, 4};
Tensor<float> xTensor(dims);
Tensor<float> yTensor(dims);
xTensor.fillWithValue(1.0F);
yTensor.fillWithValue(0.0F);
auto graph = createSimpleReluGraph("MalformedVersion_GoodAlongside", dims);
hipdnn_tests::override_test_utils::compileGraph(graph, _handle);
std::unordered_map<int64_t, void*> variantPack;
variantPack[1] = xTensor.memory().deviceData();
variantPack[2] = yTensor.memory().deviceData();
// Critical assertion: dispatch does NOT throw. With the per-call
// `Version{plugin->apiVersion()}` parse, the malformed plugin would
// trip an `std::invalid_argument` here even though it never produced
// any applicable engine.
auto result = graph->execute(_handle, variantPack, nullptr);
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
}
/// Defensive: even when the malformed plugin is the ONLY one loaded, the
/// host must still come up cleanly. Engine selection then has nothing to
/// offer, but that is a normal failure mode (no applicable plugin) — not a
/// crash.
TEST_F(IntegrationMalformedVersionPlugin, BadPluginLoadedAloneDoesNotCrashCreate)
{
const std::array<const char*, 1> paths
= {hipdnn_tests::plugin_constants::testMalformedVersionPluginPath().c_str()};
ASSERT_EQ(
hipdnnSetEnginePluginPaths_ext(paths.size(), paths.data(), HIPDNN_PLUGIN_LOADING_ABSOLUTE),
HIPDNN_STATUS_SUCCESS);
// The malformed plugin is rejected at load time by `validateBeforeAdding`;
// the rejection is caught by `loadPluginFromFile`'s `tryCatch` so the
// backend handle still constructs successfully even with no usable plugin.
ASSERT_EQ(hipdnnCreate(&_handle), HIPDNN_STATUS_SUCCESS);
}
TEST_F(IntegrationMalformedVersionPlugin,
VersionZeroPluginLoadedAlongsideGoodPluginDispatchesThroughGoodPlugin)
{
const std::array<const char*, 2> paths
= {hipdnn_tests::plugin_constants::testVersionZeroPluginPath().c_str(),
hipdnn_tests::plugin_constants::testGoodPluginPath().c_str()};
ASSERT_EQ(
hipdnnSetEnginePluginPaths_ext(paths.size(), paths.data(), HIPDNN_PLUGIN_LOADING_ABSOLUTE),
HIPDNN_STATUS_SUCCESS);
ASSERT_EQ(hipdnnCreate(&_handle), HIPDNN_STATUS_SUCCESS);
const std::vector<int64_t> dims = {1, 3, 4, 4};
Tensor<float> xTensor(dims);
Tensor<float> yTensor(dims);
xTensor.fillWithValue(1.0F);
yTensor.fillWithValue(0.0F);
auto graph = createSimpleReluGraph("VersionZero_GoodAlongside", dims);
hipdnn_tests::override_test_utils::compileGraph(graph, _handle);
std::unordered_map<int64_t, void*> variantPack;
variantPack[1] = xTensor.memory().deviceData();
variantPack[2] = yTensor.memory().deviceData();
auto result = graph->execute(_handle, variantPack, nullptr);
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
}
TEST_F(IntegrationMalformedVersionPlugin, VersionZeroPluginLoadedAloneHasNoApplicableEngines)
{
const std::array<const char*, 1> paths
= {hipdnn_tests::plugin_constants::testVersionZeroPluginPath().c_str()};
ASSERT_EQ(
hipdnnSetEnginePluginPaths_ext(paths.size(), paths.data(), HIPDNN_PLUGIN_LOADING_ABSOLUTE),
HIPDNN_STATUS_SUCCESS);
ASSERT_EQ(hipdnnCreate(&_handle), HIPDNN_STATUS_SUCCESS);
const std::vector<int64_t> dims = {1, 3, 4, 4};
auto graph = createSimpleReluGraph("VersionZero_Alone", dims);
auto result = graph->validate();
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
result = graph->build_operation_graph(_handle);
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
const auto plansResult = graph->create_execution_plans();
if(plansResult.code == ErrorCode::OK)
{
const auto supportResult = graph->check_support();
EXPECT_NE(supportResult.code, ErrorCode::OK)
<< "Version-zero plugin must not serve graphs requiring the baseline API.";
}
else
{
EXPECT_NE(plansResult.code, ErrorCode::OK);
}
}
#ifdef HIPDNN_ENABLE_SDPA
TEST_F(IntegrationMalformedVersionPlugin, VersionLiarPluginExcludedForOverrideGraph)
{
const auto& liarPath = hipdnn_tests::plugin_constants::testVersionLiarPluginPath();
const std::array<const char*, 1> paths = {liarPath.c_str()};
ASSERT_EQ(
hipdnnSetEnginePluginPaths_ext(paths.size(), paths.data(), HIPDNN_PLUGIN_LOADING_ABSOLUTE),
HIPDNN_STATUS_SUCCESS);
ASSERT_EQ(hipdnnCreate(&_handle), HIPDNN_STATUS_SUCCESS);
auto graph = hipdnn_tests::override_test_utils::buildPointwiseReluGraph(
"VersionLiar_OverrideGraph", {1, 3, 4, 4}, /*strides=*/{}, /*overrideShapeEnabled=*/true);
resetLastCallRecordIfLoaded(liarPath, "VersionLiar");
auto result = graph->validate();
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
result = graph->build_operation_graph(_handle);
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
const auto plansResult = graph->create_execution_plans();
if(plansResult.code == ErrorCode::OK)
{
const auto supportResult = graph->check_support();
EXPECT_EQ(supportResult.code, ErrorCode::GRAPH_NOT_SUPPORTED)
<< "A plugin reporting the override API version but missing the override symbol "
"must be ineligible during engine selection.";
}
else
{
EXPECT_EQ(plansResult.code, ErrorCode::GRAPH_NOT_SUPPORTED);
}
const auto* record = getLastCallRecordIfLoaded(liarPath, "VersionLiar");
ASSERT_NE(record, nullptr);
EXPECT_EQ(record->whichEntry, TestPluginExecuteEntry::NONE)
<< "Missing override symbol must not fall back to legacy execute.";
}
#endif