blob: 7b415f6a592647fadcfaff5bb986813d47c10a3f [file] [log] [blame]
// Copyright 2024 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "dl-load-tests.h"
namespace {
using dl::testing::DlTests;
TYPED_TEST_SUITE(DlTests, dl::testing::TestTypes);
using dl::testing::RunFunction;
using dl::testing::TestModule;
using dl::testing::TestShlib;
using dl::testing::TestSym;
// Load a module that depends on a symbol provided directly by a dependency.
TYPED_TEST(DlTests, BasicDep) {
constexpr int64_t kReturnValue = 17;
const std::string kBasicDepFile = TestModule("basic-dep");
const std::string kDepAFile = TestShlib("libld-dep-a");
this->ExpectRootModule(kBasicDepFile);
this->Needed({kDepAFile});
auto open = this->DlOpen(kBasicDepFile.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open.is_ok()) << open.error_value();
EXPECT_TRUE(open.value());
auto sym = this->DlSym(open.value(), TestSym("TestStart").c_str());
ASSERT_TRUE(sym.is_ok()) << sym.error_value();
ASSERT_TRUE(sym.value());
EXPECT_EQ(RunFunction<int64_t>(sym.value()), kReturnValue);
ASSERT_TRUE(this->DlClose(open.value()).is_ok());
}
// Load a module that depends on a symbols provided directly and transitively by
// several dependencies. Dependency ordering is serialized such that a module
// depends on a symbol provided by a dependency only one hop away
// (e.g. in its DT_NEEDED list):
TYPED_TEST(DlTests, IndirectDeps) {
constexpr int64_t kReturnValue = 17;
const std::string kIndirectDepsFile = TestModule("indirect-deps");
const std::string kIndirectDepsAFile = TestShlib("libindirect-deps-a");
const std::string IndirectDepsBFile = TestShlib("libindirect-deps-b");
const std::string kIndirectDepsCFile = TestShlib("libindirect-deps-c");
this->ExpectRootModule(kIndirectDepsFile);
this->Needed({kIndirectDepsAFile, IndirectDepsBFile, kIndirectDepsCFile});
auto open = this->DlOpen(kIndirectDepsFile.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open.is_ok()) << open.error_value();
EXPECT_TRUE(open.value());
auto sym = this->DlSym(open.value(), TestSym("TestStart").c_str());
ASSERT_TRUE(sym.is_ok()) << sym.error_value();
ASSERT_TRUE(sym.value());
EXPECT_EQ(RunFunction<int64_t>(sym.value()), kReturnValue);
ASSERT_TRUE(this->DlClose(open.value()).is_ok());
}
// Load a module that depends on symbols provided directly and transitively by
// several dependencies. Dependency ordering is DAG-like where several modules
// share a dependency.
TYPED_TEST(DlTests, ManyDeps) {
constexpr int64_t kReturnValue = 17;
const std::string kManyDepsFile = TestModule("many-deps");
const std::string kDepAFile = TestShlib("libld-dep-a");
const std::string kDepBFile = TestShlib("libld-dep-b");
const std::string kDepFFile = TestShlib("libld-dep-f");
const std::string kDepCFile = TestShlib("libld-dep-c");
const std::string kDepDFile = TestShlib("libld-dep-d");
const std::string kDepEFile = TestShlib("libld-dep-e");
this->ExpectRootModule(kManyDepsFile);
this->Needed({kDepAFile, kDepBFile, kDepFFile, kDepCFile, kDepDFile, kDepEFile});
auto open = this->DlOpen(kManyDepsFile.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open.is_ok()) << open.error_value();
EXPECT_TRUE(open.value());
auto sym = this->DlSym(open.value(), TestSym("TestStart").c_str());
ASSERT_TRUE(sym.is_ok()) << sym.error_value();
ASSERT_TRUE(sym.value());
EXPECT_EQ(RunFunction<int64_t>(sym.value()), kReturnValue);
ASSERT_TRUE(this->DlClose(open.value()).is_ok());
}
// Test that you can dlopen a dependency from a previously loaded module.
TYPED_TEST(DlTests, OpenDepDirectly) {
const std::string kHasFooV1File = TestShlib("libhas-foo-v1");
const std::string kFooV1File = TestShlib("libld-dep-foo-v1");
this->Needed({kHasFooV1File, kFooV1File});
auto open_has_foo_v1 = this->DlOpen(kHasFooV1File.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open_has_foo_v1.is_ok()) << open_has_foo_v1.error_value();
EXPECT_TRUE(open_has_foo_v1.value());
// dlopen kFooV1File expecting it to already be loaded.
auto open_foo_v1 = this->DlOpen(kFooV1File.c_str(), RTLD_NOW | RTLD_LOCAL | RTLD_NOLOAD);
ASSERT_TRUE(open_foo_v1.is_ok()) << open_foo_v1.error_value();
EXPECT_TRUE(open_foo_v1.value());
// Test that dlsym will resolve the same symbol pointer from the shared
// dependency between kHasFooV1File (open_has_foo_v1) and kFooV1File (open_foo_v1).
auto has_foo_v1_foo = this->DlSym(open_has_foo_v1.value(), TestSym("foo").c_str());
ASSERT_TRUE(has_foo_v1_foo.is_ok()) << has_foo_v1_foo.error_value();
ASSERT_TRUE(has_foo_v1_foo.value());
auto foo_v2_foo = this->DlSym(open_foo_v1.value(), TestSym("foo").c_str());
ASSERT_TRUE(foo_v2_foo.is_ok()) << foo_v2_foo.error_value();
ASSERT_TRUE(foo_v2_foo.value());
EXPECT_EQ(has_foo_v1_foo.value(), foo_v2_foo.value());
EXPECT_EQ(RunFunction<int64_t>(has_foo_v1_foo.value()), RunFunction<int64_t>(foo_v2_foo.value()));
ASSERT_TRUE(this->DlClose(open_has_foo_v1.value()).is_ok());
ASSERT_TRUE(this->DlClose(open_foo_v1.value()).is_ok());
}
// These are test scenarios that test symbol resolution from just the dependency
// graph, ie from the local scope of the dlopen-ed module.
// Test that dep ordering is preserved in the dependency graph.
// dlopen multiple-foo-deps -> calls foo()
// - foo-v1 -> foo() returns 2
// - foo-v2 -> foo() returns 7
// call foo() from multiple-foo-deps pointer and expect 2 from foo-v1.
TYPED_TEST(DlTests, DepOrder) {
constexpr const int64_t kReturnValue = 2;
const std::string kMultipleFooDepsFile = TestModule("multiple-foo-deps");
const std::string kFooV1File = TestShlib("libld-dep-foo-v1");
const std::string kFooV2File = TestShlib("libld-dep-foo-v2");
this->ExpectRootModule(kMultipleFooDepsFile);
this->Needed({kFooV1File, kFooV2File});
auto open = this->DlOpen(kMultipleFooDepsFile.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open.is_ok()) << open.error_value();
EXPECT_TRUE(open.value());
auto sym = this->DlSym(open.value(), TestSym("call_foo").c_str());
ASSERT_TRUE(sym.is_ok()) << sym.error_value();
ASSERT_TRUE(sym.value());
EXPECT_EQ(RunFunction<int64_t>(sym.value()), kReturnValue);
ASSERT_TRUE(this->DlClose(open.value()).is_ok());
}
// Test that transitive dep ordering is preserved the dependency graph.
// dlopen transitive-foo-dep -> calls foo()
// - has-foo-v1:
// - foo-v1 -> foo() returns 2
// - foo-v2 -> foo() returns 7
// call foo() from transitive-foo-dep pointer and expect 7 from foo-v2.
TYPED_TEST(DlTests, TransitiveDepOrder) {
constexpr const int64_t kReturnValue = 7;
const std::string kTransitiveFooDepFile = TestModule("transitive-foo-dep");
const std::string kHasFooV1File = TestShlib("libhas-foo-v1");
const std::string kFooV2File = TestShlib("libld-dep-foo-v2");
const std::string kFooV1File = TestShlib("libld-dep-foo-v1");
this->ExpectRootModule(kTransitiveFooDepFile);
this->Needed({kHasFooV1File, kFooV2File, kFooV1File});
auto open = this->DlOpen(kTransitiveFooDepFile.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open.is_ok()) << open.error_value();
EXPECT_TRUE(open.value());
auto sym = this->DlSym(open.value(), TestSym("call_foo").c_str());
ASSERT_TRUE(sym.is_ok()) << sym.error_value();
ASSERT_TRUE(sym.value());
EXPECT_EQ(RunFunction<int64_t>(sym.value()), kReturnValue);
ASSERT_TRUE(this->DlClose(open.value()).is_ok());
}
// Test that a module graph with a dependency cycle involving the root module
// will complete dlopen() & dlsym() calls.
// dlopen cyclic-dep-parent: foo() returns 2.
// - has-cyclic-dep:
// - cyclic-dep-parent
// - foo-v1: foo() returns 2.
TYPED_TEST(DlTests, CyclicalDependency) {
constexpr const int64_t kReturnValue = 2;
const auto kCyclicDepParentFile = TestModule("cyclic-dep-parent");
const auto kHasCyclicDepFile = TestShlib("libhas-cyclic-dep");
const auto kFooV1File = TestShlib("libld-dep-foo-v1");
this->ExpectRootModule(kCyclicDepParentFile);
this->Needed({kHasCyclicDepFile, kFooV1File});
auto open = this->DlOpen(kCyclicDepParentFile.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open.is_ok()) << open.error_value();
EXPECT_TRUE(open.value());
auto sym = this->DlSym(open.value(), TestSym("call_foo").c_str());
ASSERT_TRUE(sym.is_ok()) << sym.error_value();
ASSERT_TRUE(sym.value());
EXPECT_EQ(RunFunction<int64_t>(sym.value()), kReturnValue);
ASSERT_TRUE(this->DlClose(open.value()).is_ok());
}
// These are test scenarios that test symbol resolution from the local scope
// of the dlopen-ed module, when some (or all) of its dependencies have already
// been loaded.
// Test that dependency ordering is always preserved in the local symbol scope,
// even if a module with the same symbol was already loaded (with RTLD_LOCAL).
// dlopen foo-v2 -> foo() returns 7
// dlopen multiple-foo-deps:
// - foo-v1 -> foo() returns 2
// - foo-v2 -> foo() returns 7
// call foo() from multiple-foo-deps and expect 2 from foo-v1 because it is
// first in multiple-foo-deps local scope.
TYPED_TEST(DlTests, LocalPrecedence) {
const std::string kMultipleFooDepsFile = TestModule("multiple-foo-deps");
const std::string kFooV1File = TestShlib("libld-dep-foo-v1");
const std::string kFooV2File = TestShlib("libld-dep-foo-v2");
constexpr int64_t kFooV1ReturnValue = 2;
constexpr int64_t kFooV2ReturnValue = 7;
this->Needed({kFooV2File});
auto open_foo_v2 = this->DlOpen(kFooV2File.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open_foo_v2.is_ok()) << open_foo_v2.error_value();
EXPECT_TRUE(open_foo_v2.value());
auto foo_v2_foo = this->DlSym(open_foo_v2.value(), TestSym("foo").c_str());
ASSERT_TRUE(foo_v2_foo.is_ok()) << foo_v2_foo.error_value();
ASSERT_TRUE(foo_v2_foo.value());
EXPECT_EQ(RunFunction<int64_t>(foo_v2_foo.value()), kFooV2ReturnValue);
this->ExpectRootModule(kMultipleFooDepsFile);
this->Needed({kFooV1File});
auto open_multiple_foo_deps = this->DlOpen(kMultipleFooDepsFile.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open_multiple_foo_deps.is_ok()) << open_multiple_foo_deps.error_value();
EXPECT_TRUE(open_multiple_foo_deps.value());
// Test the `foo` value that is used by the root module's `call_foo()` function.
auto multiple_foo_deps_call_foo =
this->DlSym(open_multiple_foo_deps.value(), TestSym("call_foo").c_str());
ASSERT_TRUE(multiple_foo_deps_call_foo.is_ok()) << multiple_foo_deps_call_foo.error_value();
ASSERT_TRUE(multiple_foo_deps_call_foo.value());
if constexpr (TestFixture::kStrictLoadOrderPriority) {
// Musl will prioritize the symbol that was loaded first (from libfoo-v2),
// even though the file does not have global scope.
EXPECT_EQ(RunFunction<int64_t>(multiple_foo_deps_call_foo.value()), kFooV2ReturnValue);
} else {
// Glibc & libdl will use the symbol that is first encountered in the
// module's local scope, from libfoo-v1.
EXPECT_EQ(RunFunction<int64_t>(multiple_foo_deps_call_foo.value()), kFooV1ReturnValue);
}
// dlsym will always use dependency ordering from the local scope when looking
// up a symbol. Unlike `call_foo()`, `foo()` is provided by a dependency, and
// both musl and glibc will only look for this symbol in the root module's
// local-scope dependency set.
auto multiple_foo_deps_foo = this->DlSym(open_multiple_foo_deps.value(), TestSym("foo").c_str());
ASSERT_TRUE(multiple_foo_deps_foo.is_ok()) << multiple_foo_deps_foo.error_value();
ASSERT_TRUE(multiple_foo_deps_foo.value());
EXPECT_EQ(RunFunction<int64_t>(multiple_foo_deps_foo.value()), kFooV1ReturnValue);
ASSERT_TRUE(this->DlClose(open_foo_v2.value()).is_ok());
ASSERT_TRUE(this->DlClose(open_multiple_foo_deps.value()).is_ok());
}
// Test the local symbol scope precedence is not affected by transitive
// dependencies that were already loaded.
// dlopen has-foo-v1:
// - foo-v1 -> foo() returns 2
// dlopen transitive-foo-dep:
// - has-foo-v1:
// - foo-v1 -> foo() returns 2
// - foo-v2 -> foo() returns 7
// call foo() from transitive-foo-deps and expect 7 from foo-v2 because
// it is first in multiple-transitive-foo-dep's local scope.
TYPED_TEST(DlTests, LocalPrecedenceTransitiveDeps) {
const std::string kTransitiveFooDepFile = TestModule("transitive-foo-dep");
const std::string kHasFooV1File = TestShlib("libhas-foo-v1");
const std::string kFooV1File = TestShlib("libld-dep-foo-v1");
const std::string FooV2File = TestShlib("libld-dep-foo-v2");
constexpr int64_t kFooV1ReturnValue = 2;
constexpr int64_t kFooV2ReturnValue = 7;
this->Needed({kHasFooV1File, kFooV1File});
auto open_has_foo_v1 = this->DlOpen(kHasFooV1File.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open_has_foo_v1.is_ok()) << open_has_foo_v1.error_value();
EXPECT_TRUE(open_has_foo_v1.value());
auto has_foo_v1_call_foo = this->DlSym(open_has_foo_v1.value(), TestSym("call_foo").c_str());
ASSERT_TRUE(has_foo_v1_call_foo.is_ok()) << has_foo_v1_call_foo.error_value();
ASSERT_TRUE(has_foo_v1_call_foo.value());
EXPECT_EQ(RunFunction<int64_t>(has_foo_v1_call_foo.value()), kFooV1ReturnValue);
this->ExpectRootModule(kTransitiveFooDepFile);
this->Needed({FooV2File});
auto open_transitive_foo_dep = this->DlOpen(kTransitiveFooDepFile.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open_transitive_foo_dep.is_ok()) << open_transitive_foo_dep.error_value();
EXPECT_TRUE(open_transitive_foo_dep.value());
auto transitive_foo_dep_call_fo =
this->DlSym(open_transitive_foo_dep.value(), TestSym("call_foo").c_str());
ASSERT_TRUE(transitive_foo_dep_call_fo.is_ok()) << transitive_foo_dep_call_fo.error_value();
ASSERT_TRUE(transitive_foo_dep_call_fo.value());
if (TestFixture::kStrictLoadOrderPriority) {
// Musl will prioritize the symbol that was loaded first (from has-foo-v1 +
// foo-v1), even though the file does not have global scope.
EXPECT_EQ(RunFunction<int64_t>(transitive_foo_dep_call_fo.value()), kFooV1ReturnValue);
} else {
// Glibc & libdl will use the symbol that is first encountered in the
// module's local scope, from foo-v2.
EXPECT_EQ(RunFunction<int64_t>(transitive_foo_dep_call_fo.value()), kFooV2ReturnValue);
}
ASSERT_TRUE(this->DlClose(open_has_foo_v1.value()).is_ok());
ASSERT_TRUE(this->DlClose(open_transitive_foo_dep.value()).is_ok());
}
// The following tests will test dlopen-ing a module and resolving symbols from
// transitive dependency modules, all of which have already been loaded.
// dlopen has-foo-v2:
// - foo-v2 -> foo() returns 7
// dlopen has-foo-v1:
// - foo-v1 -> foo() returns 2
// dlopen multiple-transitive-foo-deps:
// - has-foo-v1:
// - foo-v1 -> foo() returns 2
// - has-foo-v2:
// - foo-v2 -> foo() returns 7
// Call foo() from multiple-transitive-foo-deps and expect 2 from foo-v1,
// because it is first in multiple-transitive-foo-dep's local scope.
TYPED_TEST(DlTests, LoadedTransitiveDepOrder) {
const std::string kMultipleTransitiveFooDepsFile = TestShlib("libmultiple-transitive-foo-deps");
const std::string kHasFooV2File = TestShlib("libhas-foo-v2");
const std::string kFooV2File = TestShlib("libld-dep-foo-v2");
const std::string kHasFooV1File = TestShlib("libhas-foo-v1");
const std::string kFooV1File = TestShlib("libld-dep-foo-v1");
constexpr int64_t kFooV1ReturnValue = 2;
constexpr int64_t kFooV2ReturnValue = 7;
this->Needed({kHasFooV2File, kFooV2File});
auto open_has_foo_v2 = this->DlOpen(kHasFooV2File.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open_has_foo_v2.is_ok()) << open_has_foo_v2.error_value();
EXPECT_TRUE(open_has_foo_v2.value());
auto has_foo_v2_call_foo = this->DlSym(open_has_foo_v2.value(), TestSym("call_foo").c_str());
ASSERT_TRUE(has_foo_v2_call_foo.is_ok()) << has_foo_v2_call_foo.error_value();
ASSERT_TRUE(has_foo_v2_call_foo.value());
EXPECT_EQ(RunFunction<int64_t>(has_foo_v2_call_foo.value()), kFooV2ReturnValue);
this->Needed({kHasFooV1File, kFooV1File});
auto open_has_foo_v1 = this->DlOpen(kHasFooV1File.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open_has_foo_v1.is_ok()) << open_has_foo_v1.error_value();
EXPECT_TRUE(open_has_foo_v1.value());
auto has_foo_v1_call_foo = this->DlSym(open_has_foo_v1.value(), TestSym("call_foo").c_str());
ASSERT_TRUE(has_foo_v1_call_foo.is_ok()) << has_foo_v1_call_foo.error_value();
ASSERT_TRUE(has_foo_v1_call_foo.value());
EXPECT_EQ(RunFunction<int64_t>(has_foo_v1_call_foo.value()), kFooV1ReturnValue);
this->Needed({kMultipleTransitiveFooDepsFile});
auto open_multiple_transitive_foo_deps =
this->DlOpen(kMultipleTransitiveFooDepsFile.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open_multiple_transitive_foo_deps.is_ok())
<< open_multiple_transitive_foo_deps.error_value();
EXPECT_TRUE(open_multiple_transitive_foo_deps.value());
auto multiple_transitive_foo_deps_call_foo =
this->DlSym(open_multiple_transitive_foo_deps.value(), TestSym("call_foo").c_str());
ASSERT_TRUE(multiple_transitive_foo_deps_call_foo.is_ok())
<< multiple_transitive_foo_deps_call_foo.error_value();
ASSERT_TRUE(multiple_transitive_foo_deps_call_foo.value());
// Both Glibc & Musl's agree on the resolved symbol here here, because
// has-foo-v1 is looked up first (and it is already loaded), so its symbols
// take precedence.
EXPECT_EQ(RunFunction<int64_t>(multiple_transitive_foo_deps_call_foo.value()), kFooV1ReturnValue);
ASSERT_TRUE(this->DlClose(open_has_foo_v2.value()).is_ok());
ASSERT_TRUE(this->DlClose(open_has_foo_v1.value()).is_ok());
ASSERT_TRUE(this->DlClose(open_multiple_transitive_foo_deps.value()).is_ok());
}
// Whereas the above tests test how symbols are resolved for the root module,
// the following tests will test how symbols are resolved for dependencies of
// the root module.
// Test that the load-order of the local scope has precedence in the symbol
// resolution for symbols used by dependencies (i.e. a sibling's symbol is used
// used when it's earlier in the load-order set).
// dlopen precedence-in-dep-resolution:
// - bar-v1 -> bar_v1() calls foo().
// - foo-v1 -> foo() returns 2
// - bar-v2 -> bar_v2() calls foo().
// - foo-v2 -> foo() returns 7
// bar_v1() from precedence-in-dep-resolution and expect 2.
// bar_v2() from precedence-in-dep-resolution and expect 2 from foo-v1.
TYPED_TEST(DlTests, PrecedenceInDepResolution) {
const std::string kPrecedenceInDepResolutionFile = TestModule("precedence-in-dep-resolution");
const std::string kBarV1File = TestShlib("libbar-v1");
const std::string kBarV2File = TestShlib("libbar-v2");
const std::string kFooV1File = TestShlib("libld-dep-foo-v1");
const std::string kFooV2File = TestShlib("libld-dep-foo-v2");
constexpr int64_t kFooV1ReturnValue = 2;
this->ExpectRootModule(kPrecedenceInDepResolutionFile);
this->Needed({kBarV1File, kBarV2File, kFooV1File, kFooV2File});
auto open = this->DlOpen(kPrecedenceInDepResolutionFile.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open.is_ok()) << open.error_value();
EXPECT_TRUE(open.value());
auto bar_v1 = this->DlSym(open.value(), TestSym("bar_v1").c_str());
ASSERT_TRUE(bar_v1.is_ok()) << bar_v1.error_value();
ASSERT_TRUE(bar_v1.value());
EXPECT_EQ(RunFunction<int64_t>(bar_v1.value()), kFooV1ReturnValue);
auto bar_v2 = this->DlSym(open.value(), TestSym("bar_v2").c_str());
ASSERT_TRUE(bar_v2.is_ok()) << bar_v2.error_value();
ASSERT_TRUE(bar_v2.value());
EXPECT_EQ(RunFunction<int64_t>(bar_v2.value()), kFooV1ReturnValue);
ASSERT_TRUE(this->DlClose(open.value()).is_ok());
}
// Test that the root module symbols have precedence in the symbol resolution
// for symbols used by dependencies.
// dlopen root-precedence-in-dep-resolution: foo() returns 17
// - bar-v1 -> bar_v1() calls foo().
// - foo-v1 -> foo() returns 2
// - bar-v2 -> bar_v2() calls foo().
// - foo-v2 -> foo() returns 7
// call foo() from root-precedence-in-dep-resolution and expect 17.
// bar_v1() from root-precedence-in-dep-resolution and expect 17.
// bar_v2() from root-precedence-in-dep-resolution and expect 17.
TYPED_TEST(DlTests, RootPrecedenceInDepResolution) {
const std::string kRootFile = TestModule("root-precedence-in-dep-resolution");
const std::string kBarV1File = TestShlib("libbar-v1");
const std::string kBarV2File = TestShlib("libbar-v2");
const std::string kFooV1File = TestShlib("libld-dep-foo-v1");
const std::string kFooV2File = TestShlib("libld-dep-foo-v2");
constexpr int64_t kReturnValueFromRootModule = 17;
constexpr int64_t kFooV1ReturnValue = 2;
constexpr int64_t kFooV2ReturnValue = 7;
this->ExpectRootModule(kRootFile);
this->Needed({kBarV1File, kBarV2File, kFooV1File, kFooV2File});
auto open_root = this->DlOpen(kRootFile.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open_root.is_ok()) << open_root.error_value();
EXPECT_TRUE(open_root.value());
auto root_foo = this->DlSym(open_root.value(), TestSym("foo").c_str());
ASSERT_TRUE(root_foo.is_ok()) << root_foo.error_value();
ASSERT_TRUE(root_foo.value());
EXPECT_EQ(RunFunction<int64_t>(root_foo.value()), kReturnValueFromRootModule);
auto root_bar_v1 = this->DlSym(open_root.value(), TestSym("bar_v1").c_str());
ASSERT_TRUE(root_bar_v1.is_ok()) << root_bar_v1.error_value();
ASSERT_TRUE(root_bar_v1.value());
EXPECT_EQ(RunFunction<int64_t>(root_bar_v1.value()), kReturnValueFromRootModule);
auto root_bar_v2 = this->DlSym(open_root.value(), TestSym("bar_v2").c_str());
ASSERT_TRUE(root_bar_v2.is_ok()) << root_bar_v2.error_value();
ASSERT_TRUE(root_bar_v2.value());
EXPECT_EQ(RunFunction<int64_t>(root_bar_v2.value()), kReturnValueFromRootModule);
// Test that when we dlopen the dep directly, foo is resolved to the
// transitive dependency, while bar_v1/bar_v2 continue to use the root
// module's foo symbol.
auto open_bar_v1 = this->DlOpen(kBarV1File.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open_bar_v1.is_ok()) << open_bar_v1.error_value();
EXPECT_TRUE(open_bar_v1.value());
auto foo1 = this->DlSym(open_bar_v1.value(), TestSym("foo").c_str());
ASSERT_TRUE(foo1.is_ok()) << foo1.error_value();
ASSERT_TRUE(foo1.value());
EXPECT_EQ(RunFunction<int64_t>(foo1.value()), kFooV1ReturnValue);
auto bar_v1 = this->DlSym(open_bar_v1.value(), TestSym("bar_v1").c_str());
ASSERT_TRUE(bar_v1.is_ok()) << bar_v1.error_value();
ASSERT_TRUE(bar_v1.value());
EXPECT_EQ(RunFunction<int64_t>(bar_v1.value()), kReturnValueFromRootModule);
auto open_bar_v2 = this->DlOpen(kBarV2File.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open_bar_v2.is_ok()) << open_bar_v2.error_value();
EXPECT_TRUE(open_bar_v2.value());
auto bar_v2_foo = this->DlSym(open_bar_v2.value(), TestSym("foo").c_str());
ASSERT_TRUE(bar_v2_foo.is_ok()) << bar_v2_foo.error_value();
ASSERT_TRUE(bar_v2_foo.value());
EXPECT_EQ(RunFunction<int64_t>(bar_v2_foo.value()), kFooV2ReturnValue);
auto bar_v2 = this->DlSym(open_bar_v2.value(), TestSym("bar_v2").c_str());
ASSERT_TRUE(bar_v2.is_ok()) << bar_v2.error_value();
ASSERT_TRUE(bar_v2.value());
EXPECT_EQ(RunFunction<int64_t>(bar_v2.value()), kReturnValueFromRootModule);
ASSERT_TRUE(this->DlClose(open_bar_v1.value()).is_ok());
ASSERT_TRUE(this->DlClose(open_bar_v2.value()).is_ok());
ASSERT_TRUE(this->DlClose(open_root.value()).is_ok());
}
} // namespace