diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /mozglue/tests/gtest | |
parent | Initial commit. (diff) | |
download | firefox-upstream/124.0.1.tar.xz firefox-upstream/124.0.1.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'mozglue/tests/gtest')
-rw-r--r-- | mozglue/tests/gtest/TestNativeNtGTest.cpp | 63 | ||||
-rw-r--r-- | mozglue/tests/gtest/TestStackWalk.cpp | 279 | ||||
-rw-r--r-- | mozglue/tests/gtest/moz.build | 20 |
3 files changed, 362 insertions, 0 deletions
diff --git a/mozglue/tests/gtest/TestNativeNtGTest.cpp b/mozglue/tests/gtest/TestNativeNtGTest.cpp new file mode 100644 index 0000000000..cc48f16fdd --- /dev/null +++ b/mozglue/tests/gtest/TestNativeNtGTest.cpp @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <windows.h> + +#include "gtest/gtest.h" + +#include "mozilla/NativeNt.h" +#include "nsHashKeys.h" +#include "nsTHashSet.h" + +TEST(TestNativeNtGTest, GenerateDependentModuleSet) +{ + mozilla::nt::PEHeaders executable(::GetModuleHandleW(nullptr)); + nsTHashSet<nsStringCaseInsensitiveHashKey> dependentModules; + executable.EnumImportChunks([&](const char* aModule) { + dependentModules.Insert( + mozilla::nt::GetLeafName(NS_ConvertASCIItoUTF16(aModule))); + }); + + EXPECT_TRUE(dependentModules.Contains(u"mozglue.dll"_ns)); + EXPECT_TRUE(dependentModules.Contains(u"MOZGLUE.dll"_ns)); + EXPECT_FALSE(dependentModules.Contains(u"xxx.dll"_ns)); +} + +TEST(TestNativeNtGTest, GetLeafName) +{ + nsAutoString str; + str = mozilla::nt::GetLeafName(u""_ns); + EXPECT_STREQ(str.get(), L""); + str = mozilla::nt::GetLeafName(u"\\"_ns); + EXPECT_STREQ(str.get(), L""); + str = mozilla::nt::GetLeafName(u"\\\\"_ns); + EXPECT_STREQ(str.get(), L""); + str = mozilla::nt::GetLeafName(u"abc\\def\\ghi"_ns); + EXPECT_STREQ(str.get(), L"ghi"); + str = mozilla::nt::GetLeafName(u"abcdef"_ns); + EXPECT_STREQ(str.get(), L"abcdef"); + str = mozilla::nt::GetLeafName(u"\\abcdef"_ns); + EXPECT_STREQ(str.get(), L"abcdef"); + + const auto kEntireText = + u"\\"_ns + u"\\\\abc"_ns + u"\\x\\y\\z"_ns + u"123\\456\\"_ns + u"789"_ns; + str = mozilla::nt::GetLeafName(Substring(kEntireText, 0, 0)); + EXPECT_STREQ(str.get(), L""); + str = mozilla::nt::GetLeafName(Substring(kEntireText, 0, 1)); + EXPECT_STREQ(str.get(), L""); + str = mozilla::nt::GetLeafName(Substring(kEntireText, 1, 5)); + EXPECT_STREQ(str.get(), L"abc"); + str = mozilla::nt::GetLeafName(Substring(kEntireText, 6, 6)); + EXPECT_STREQ(str.get(), L"z"); + str = mozilla::nt::GetLeafName(Substring(kEntireText, 12, 8)); + EXPECT_STREQ(str.get(), L""); + str = mozilla::nt::GetLeafName(Substring(kEntireText, 20)); + EXPECT_STREQ(str.get(), L"789"); +} diff --git a/mozglue/tests/gtest/TestStackWalk.cpp b/mozglue/tests/gtest/TestStackWalk.cpp new file mode 100644 index 0000000000..cec21ffc5e --- /dev/null +++ b/mozglue/tests/gtest/TestStackWalk.cpp @@ -0,0 +1,279 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// The header under test. +#include "mozilla/StackWalk.h" + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" + +#include <algorithm> + +#include "gtest/gtest.h" + +MOZ_EXPORT bool gStackWalkTesterDummy = true; + +struct StackWalkTester; + +// Descriptor of the recursive function calls wanted, and for each of them +// whether to perform tail call optimization or not. +struct CallInfo { + int (*mFunc)(int aDepth, int aLastSkipped, int aIgnored, + StackWalkTester& aTester); + bool mTailCall; + + bool TailCall() { +#if defined(__i386__) || defined(MOZ_CODE_COVERAGE) + // We can't make tail calls happen on i386 because all arguments to + // functions are on the stack, so the stack pointer needs to be updated + // before the call and restored after the call, so tail call optimization + // never happens. + // Similarly, code-coverage flags don't guarantee that tail call + // optimization will happen. + return false; +#else + return mTailCall; +#endif + } +}; + +struct PCRange { + void* mStart; + void* mEnd; +}; + +// PCRange pretty printer for gtest assertions. +std::ostream& operator<<(std::ostream& aStream, const PCRange& aRange) { + aStream << aRange.mStart; + aStream << "-"; + aStream << aRange.mEnd; + return aStream; +} + +// Allow to use EXPECT_EQ with a vector of PCRanges and a vector of plain +// addresses, allowing a more useful output when the test fails, showing +// both lists. +bool operator==(const std::vector<PCRange>& aRanges, + const std::vector<void*>& aPtrs) { + if (aRanges.size() != aPtrs.size()) { + return false; + } + for (size_t i = 0; i < aRanges.size(); i++) { + auto range = aRanges[i]; + auto ptr = reinterpret_cast<uintptr_t>(aPtrs[i]); + if (ptr <= reinterpret_cast<uintptr_t>(range.mStart) || + ptr >= reinterpret_cast<uintptr_t>(range.mEnd)) { + return false; + } + } + return true; +} + +struct StackWalkTester { + // Description of the recursion of functions to perform for the testcase. + std::vector<CallInfo> mFuncCalls; + // Collection of PCs reported by MozStackWalk. + std::vector<void*> mFramePCs; + // Collection of PCs expected per what was observed while recursing. + std::vector<PCRange> mExpectedFramePCs; + // The aFirstFramePC value that will be passed to MozStackWalk. + void* mFirstFramePC = nullptr; + + // Callback to be given to the stack walker. + // aClosure should point at an instance of this class. + static void StackWalkCallback(uint32_t aFrameNumber, void* aPC, void* aSP, + void* aClosure) { + ASSERT_NE(aClosure, nullptr); + StackWalkTester& tester = *reinterpret_cast<StackWalkTester*>(aClosure); + tester.mFramePCs.push_back(aPC); + EXPECT_EQ(tester.mFramePCs.size(), size_t(aFrameNumber)) + << "Frame number doesn't match"; + } + + // Callers of this function get a range of addresses with: + // ``` + // label: + // recursion(); + // AddExpectedPC(&&label); + // ``` + // This intends to record the range from label to the return of AddExpectedPC. + // The ideal code would be: + // ``` + // recursion(); + // label: + // AddExpectedPC(&&label); + // ``` + // and we wouldn't need to keep ranges. But while this works fine with Clang, + // GCC actually sometimes reorders code such the address received by + // AddExpectedPC is the address *before* the recursion. + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99784 Using a label before the + // recursion and CallerPC() from a function call after the recursion makes it + // less likely for things to go wrong. + MOZ_NEVER_INLINE void AddExpectedPC(void* aPC) { + mExpectedFramePCs.push_back({aPC, CallerPC()}); + } + + // Function intended to be called in sequence for recursion. + // CallInfo lists are meant to contain a sequence of IntermediateCallback<1>, + // IntermediateCallback<2>, etc. + // aDepth is a counter of how deep the recursion has gone so far; + // aLastSkipped is the depth of the last frame we want skipped in the + // testcase; aIgnored is there to avoid the compiler merging both recursive + // function calls, which would prevent tail call optimization happening on one + // of them. aTester is the instance of this class for the testcase. + template <int Id> + MOZ_NEVER_INLINE MOZ_EXPORT static int IntermediateCallback( + int aDepth, int aLastSkipped, int aIgnored, StackWalkTester& aTester) { + auto& callInfo = aTester.mFuncCalls.at(aDepth + 1); + if (aDepth == aLastSkipped) { + aTester.mFirstFramePC = CallerPC(); + } + if (aTester.mFuncCalls.at(aDepth).TailCall()) { + return callInfo.mFunc(aDepth + 1, aLastSkipped, Id, aTester); + // Since we're doing a tail call, we're not expecting this frame appearing + // in the trace. + } + here: + callInfo.mFunc(aDepth + 1, aLastSkipped, Id + 1, aTester); + aTester.AddExpectedPC(&&here); + return 0; + } + +#if defined(__clang__) + __attribute__((no_sanitize("function"))) +#endif + MOZ_NEVER_INLINE MOZ_EXPORT static void + LeafCallback(int aDepth, int aLastSkipped, int aIgnored, + StackWalkTester& aTester) { + if (aDepth == aLastSkipped) { + aTester.mFirstFramePC = CallerPC(); + } + if (aTester.mFuncCalls.at(aDepth).TailCall()) { + // For the same reason that we have the aIgnored argument on these + // callbacks, we need to avoid both MozStackWalk calls to be merged by the + // compiler, so we use different values of aMaxFrames for that. + return MozStackWalk(StackWalkTester::StackWalkCallback, + aTester.mFirstFramePC, + /*aMaxFrames*/ 19, &aTester); + // Since we're doing a tail call, we're not expecting this frame appearing + // in the trace. + } + here: + MozStackWalk(StackWalkTester::StackWalkCallback, aTester.mFirstFramePC, + /*aMaxFrames*/ 20, &aTester); + aTester.AddExpectedPC(&&here); + // Because we return nothing from this function, simply returning here would + // produce a tail-call optimization, which we explicitly don't want to + // happen. So we add a branch that depends on an extern value to prevent + // that from happening. + MOZ_RELEASE_ASSERT(gStackWalkTesterDummy); + } + + explicit StackWalkTester(std::initializer_list<CallInfo> aFuncCalls) + : mFuncCalls(aFuncCalls) {} + + // Dump a vector of PCRanges as WalkTheStack would, for test failure output. + // Only the end of the range is shown. Not ideal, but + // MozFormatCodeAddressDetails only knows to deal with one address at a time. + // The full ranges would be printed by EXPECT_EQ anyways. + static std::string DumpFrames(std::vector<PCRange>& aFramePCRanges) { + std::vector<void*> framePCs; + framePCs.reserve(aFramePCRanges.size()); + for (auto range : aFramePCRanges) { + framePCs.push_back(range.mEnd); + } + return DumpFrames(framePCs); + } + + // Dump a vector of addresses as WalkTheStack would, for test failure output. + static std::string DumpFrames(std::vector<void*>& aFramePCs) { + size_t n = 0; + std::string result; + for (auto* framePC : aFramePCs) { + char buf[1024]; + MozCodeAddressDetails details; + result.append(" "); + n++; + if (MozDescribeCodeAddress(framePC, &details)) { + int length = + MozFormatCodeAddressDetails(buf, sizeof(buf), n, framePC, &details); + result.append(buf, std::min(length, (int)sizeof(buf) - 1)); + } else { + result.append("MozDescribeCodeAddress failed"); + } + result.append("\n"); + } + return result; + } + + // Dump a description of the given test case. + static std::string DumpFuncCalls(std::vector<CallInfo>& aFuncCalls) { + std::string result; + for (auto funcCall : aFuncCalls) { + MozCodeAddressDetails details; + result.append(" "); + if (MozDescribeCodeAddress(reinterpret_cast<void*>(funcCall.mFunc), + &details)) { + result.append(details.function); + if (funcCall.TailCall()) { + result.append(" tail call"); + } + } else { + result.append("MozDescribeCodeAddress failed"); + } + result.append("\n"); + } + return result; + } + + MOZ_EXPORT MOZ_NEVER_INLINE void RunTest(int aLastSkipped) { + ASSERT_TRUE(aLastSkipped < (int)mFuncCalls.size()); + mFramePCs.clear(); + mExpectedFramePCs.clear(); + mFirstFramePC = nullptr; + auto& callInfo = mFuncCalls.at(0); + here: + callInfo.mFunc(0, aLastSkipped, 0, *this); + AddExpectedPC(&&here); + if (aLastSkipped < 0) { + aLastSkipped = mFuncCalls.size(); + } + for (int i = (int)mFuncCalls.size() - 1; i >= aLastSkipped; i--) { + if (!mFuncCalls.at(i).TailCall()) { + mExpectedFramePCs.erase(mExpectedFramePCs.begin()); + } + } + mFramePCs.resize(std::min(mExpectedFramePCs.size(), mFramePCs.size())); + EXPECT_EQ(mExpectedFramePCs, mFramePCs) + << "Expected frames:\n" + << DumpFrames(mExpectedFramePCs) << "Found frames:\n" + << DumpFrames(mFramePCs) + << "Function calls data (last skipped: " << aLastSkipped << "):\n" + << DumpFuncCalls(mFuncCalls); + } +}; + +TEST(TestStackWalk, StackWalk) +{ + const auto foo = StackWalkTester::IntermediateCallback<1>; + const auto bar = StackWalkTester::IntermediateCallback<2>; + const auto qux = StackWalkTester::IntermediateCallback<3>; + const auto leaf = reinterpret_cast<int (*)(int, int, int, StackWalkTester&)>( + StackWalkTester::LeafCallback); + + const std::initializer_list<CallInfo> tests[] = { + {{foo, false}, {bar, false}, {qux, false}, {leaf, false}}, + {{foo, false}, {bar, true}, {qux, false}, {leaf, false}}, + {{foo, false}, {bar, false}, {qux, false}, {leaf, true}}, + {{foo, true}, {bar, false}, {qux, true}, {leaf, true}}, + }; + for (auto test : tests) { + StackWalkTester tester(test); + for (int i = -1; i < (int)test.size(); i++) { + tester.RunTest(i); + } + } +} diff --git a/mozglue/tests/gtest/moz.build b/mozglue/tests/gtest/moz.build new file mode 100644 index 0000000000..8f6ba9ad7b --- /dev/null +++ b/mozglue/tests/gtest/moz.build @@ -0,0 +1,20 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +if CONFIG["OS_ARCH"] == "WINNT": + SOURCES += [ + "TestNativeNtGTest.cpp", + ] + + +if not CONFIG["MOZ_TSAN"]: + SOURCES += [ + "TestStackWalk.cpp", + ] + + # The test relies on optimizations being on, so we can't let --disable-optimize + # getting in the way. See details in the source file. + SOURCES["TestStackWalk.cpp"].flags += ["-O2"] + +FINAL_LIBRARY = "xul-gtest" |