tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

TestStackWalk.cpp (10223B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 // The header under test.
      8 #include "mozilla/StackWalk.h"
      9 
     10 #include "mozilla/Assertions.h"
     11 #include "mozilla/Attributes.h"
     12 
     13 #include <algorithm>
     14 
     15 #include "gtest/gtest.h"
     16 
     17 MOZ_EXPORT bool gStackWalkTesterDummy = true;
     18 
     19 struct StackWalkTester;
     20 
     21 // Descriptor of the recursive function calls wanted, and for each of them
     22 // whether to perform tail call optimization or not.
     23 struct CallInfo {
     24  int (*mFunc)(int aDepth, int aLastSkipped, int aIgnored,
     25               StackWalkTester& aTester);
     26  bool mTailCall;
     27 
     28  bool TailCall() {
     29 #if defined(__i386__) || defined(MOZ_CODE_COVERAGE)
     30    // We can't make tail calls happen on i386 because all arguments to
     31    // functions are on the stack, so the stack pointer needs to be updated
     32    // before the call and restored after the call, so tail call optimization
     33    // never happens.
     34    // Similarly, code-coverage flags don't guarantee that tail call
     35    // optimization will happen.
     36    return false;
     37 #else
     38    return mTailCall;
     39 #endif
     40  }
     41 };
     42 
     43 struct PCRange {
     44  void* mStart;
     45  void* mEnd;
     46 };
     47 
     48 // PCRange pretty printer for gtest assertions.
     49 std::ostream& operator<<(std::ostream& aStream, const PCRange& aRange) {
     50  aStream << aRange.mStart;
     51  aStream << "-";
     52  aStream << aRange.mEnd;
     53  return aStream;
     54 }
     55 
     56 // Allow to use EXPECT_EQ with a vector of PCRanges and a vector of plain
     57 // addresses, allowing a more useful output when the test fails, showing
     58 // both lists.
     59 bool operator==(const std::vector<PCRange>& aRanges,
     60                const std::vector<void*>& aPtrs) {
     61  if (aRanges.size() != aPtrs.size()) {
     62    return false;
     63  }
     64  for (size_t i = 0; i < aRanges.size(); i++) {
     65    auto range = aRanges[i];
     66    auto ptr = reinterpret_cast<uintptr_t>(aPtrs[i]);
     67    if (ptr <= reinterpret_cast<uintptr_t>(range.mStart) ||
     68        ptr >= reinterpret_cast<uintptr_t>(range.mEnd)) {
     69      return false;
     70    }
     71  }
     72  return true;
     73 }
     74 
     75 struct StackWalkTester {
     76  // Description of the recursion of functions to perform for the testcase.
     77  std::vector<CallInfo> mFuncCalls;
     78  // Collection of PCs reported by MozStackWalk.
     79  std::vector<void*> mFramePCs;
     80  // Collection of PCs expected per what was observed while recursing.
     81  std::vector<PCRange> mExpectedFramePCs;
     82  // The aFirstFramePC value that will be passed to MozStackWalk.
     83  void* mFirstFramePC = nullptr;
     84 
     85  // Callback to be given to the stack walker.
     86  // aClosure should point at an instance of this class.
     87  static void StackWalkCallback(uint32_t aFrameNumber, void* aPC, void* aSP,
     88                                void* aClosure) {
     89    ASSERT_NE(aClosure, nullptr);
     90    StackWalkTester& tester = *reinterpret_cast<StackWalkTester*>(aClosure);
     91    tester.mFramePCs.push_back(aPC);
     92    EXPECT_EQ(tester.mFramePCs.size(), size_t(aFrameNumber))
     93        << "Frame number doesn't match";
     94  }
     95 
     96  // Callers of this function get a range of addresses with:
     97  // ```
     98  // label:
     99  //   recursion();
    100  //   AddExpectedPC(&&label);
    101  // ```
    102  // This intends to record the range from label to the return of AddExpectedPC.
    103  // The ideal code would be:
    104  // ```
    105  //   recursion();
    106  // label:
    107  //   AddExpectedPC(&&label);
    108  // ```
    109  // and we wouldn't need to keep ranges. But while this works fine with Clang,
    110  // GCC actually sometimes reorders code such the address received by
    111  // AddExpectedPC is the address *before* the recursion.
    112  // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99784 Using a label before the
    113  // recursion and CallerPC() from a function call after the recursion makes it
    114  // less likely for things to go wrong.
    115  MOZ_NEVER_INLINE void AddExpectedPC(void* aPC) {
    116    mExpectedFramePCs.push_back({aPC, CallerPC()});
    117  }
    118 
    119  // Function intended to be called in sequence for recursion.
    120  // CallInfo lists are meant to contain a sequence of IntermediateCallback<1>,
    121  // IntermediateCallback<2>, etc.
    122  // aDepth is a counter of how deep the recursion has gone so far;
    123  // aLastSkipped is the depth of the last frame we want skipped in the
    124  // testcase; aIgnored is there to avoid the compiler merging both recursive
    125  // function calls, which would prevent tail call optimization happening on one
    126  // of them. aTester is the instance of this class for the testcase.
    127  template <int Id>
    128  MOZ_NEVER_INLINE MOZ_EXPORT static int IntermediateCallback(
    129      int aDepth, int aLastSkipped, int aIgnored, StackWalkTester& aTester) {
    130    auto& callInfo = aTester.mFuncCalls.at(aDepth + 1);
    131    if (aDepth == aLastSkipped) {
    132      aTester.mFirstFramePC = CallerPC();
    133    }
    134    if (aTester.mFuncCalls.at(aDepth).TailCall()) {
    135      return callInfo.mFunc(aDepth + 1, aLastSkipped, Id, aTester);
    136      // Since we're doing a tail call, we're not expecting this frame appearing
    137      // in the trace.
    138    }
    139  here:
    140    callInfo.mFunc(aDepth + 1, aLastSkipped, Id + 1, aTester);
    141    aTester.AddExpectedPC(&&here);
    142    return 0;
    143  }
    144 
    145 #if defined(__clang__)
    146  __attribute__((no_sanitize("function")))
    147 #endif
    148  MOZ_NEVER_INLINE MOZ_EXPORT static void
    149  LeafCallback(int aDepth, int aLastSkipped, int aIgnored,
    150               StackWalkTester& aTester) {
    151    if (aDepth == aLastSkipped) {
    152      aTester.mFirstFramePC = CallerPC();
    153    }
    154    if (aTester.mFuncCalls.at(aDepth).TailCall()) {
    155      // For the same reason that we have the aIgnored argument on these
    156      // callbacks, we need to avoid both MozStackWalk calls to be merged by the
    157      // compiler, so we use different values of aMaxFrames for that.
    158      return MozStackWalk(StackWalkTester::StackWalkCallback,
    159                          aTester.mFirstFramePC,
    160                          /*aMaxFrames*/ 19, &aTester);
    161      // Since we're doing a tail call, we're not expecting this frame appearing
    162      // in the trace.
    163    }
    164  here:
    165    MozStackWalk(StackWalkTester::StackWalkCallback, aTester.mFirstFramePC,
    166                 /*aMaxFrames*/ 20, &aTester);
    167    aTester.AddExpectedPC(&&here);
    168    // Because we return nothing from this function, simply returning here would
    169    // produce a tail-call optimization, which we explicitly don't want to
    170    // happen. So we add a branch that depends on an extern value to prevent
    171    // that from happening.
    172    MOZ_RELEASE_ASSERT(gStackWalkTesterDummy);
    173  }
    174 
    175  explicit StackWalkTester(std::initializer_list<CallInfo> aFuncCalls)
    176      : mFuncCalls(aFuncCalls) {}
    177 
    178  // Dump a vector of PCRanges as WalkTheStack would, for test failure output.
    179  // Only the end of the range is shown. Not ideal, but
    180  // MozFormatCodeAddressDetails only knows to deal with one address at a time.
    181  // The full ranges would be printed by EXPECT_EQ anyways.
    182  static std::string DumpFrames(std::vector<PCRange>& aFramePCRanges) {
    183    std::vector<void*> framePCs;
    184    framePCs.reserve(aFramePCRanges.size());
    185    for (auto range : aFramePCRanges) {
    186      framePCs.push_back(range.mEnd);
    187    }
    188    return DumpFrames(framePCs);
    189  }
    190 
    191  // Dump a vector of addresses as WalkTheStack would, for test failure output.
    192  static std::string DumpFrames(std::vector<void*>& aFramePCs) {
    193    size_t n = 0;
    194    std::string result;
    195    for (auto* framePC : aFramePCs) {
    196      char buf[1024];
    197      MozCodeAddressDetails details;
    198      result.append("  ");
    199      n++;
    200      if (MozDescribeCodeAddress(framePC, &details)) {
    201        int length =
    202            MozFormatCodeAddressDetails(buf, sizeof(buf), n, framePC, &details);
    203        result.append(buf, std::min(length, (int)sizeof(buf) - 1));
    204      } else {
    205        result.append("MozDescribeCodeAddress failed");
    206      }
    207      result.append("\n");
    208    }
    209    return result;
    210  }
    211 
    212  // Dump a description of the given test case.
    213  static std::string DumpFuncCalls(std::vector<CallInfo>& aFuncCalls) {
    214    std::string result;
    215    for (auto funcCall : aFuncCalls) {
    216      MozCodeAddressDetails details;
    217      result.append("  ");
    218      if (MozDescribeCodeAddress(reinterpret_cast<void*>(funcCall.mFunc),
    219                                 &details)) {
    220        result.append(details.function);
    221        if (funcCall.TailCall()) {
    222          result.append(" tail call");
    223        }
    224      } else {
    225        result.append("MozDescribeCodeAddress failed");
    226      }
    227      result.append("\n");
    228    }
    229    return result;
    230  }
    231 
    232  MOZ_EXPORT MOZ_NEVER_INLINE void RunTest(int aLastSkipped) {
    233    ASSERT_TRUE(aLastSkipped < (int)mFuncCalls.size());
    234    mFramePCs.clear();
    235    mExpectedFramePCs.clear();
    236    mFirstFramePC = nullptr;
    237    auto& callInfo = mFuncCalls.at(0);
    238  here:
    239    callInfo.mFunc(0, aLastSkipped, 0, *this);
    240    AddExpectedPC(&&here);
    241    if (aLastSkipped < 0) {
    242      aLastSkipped = mFuncCalls.size();
    243    }
    244    for (int i = (int)mFuncCalls.size() - 1; i >= aLastSkipped; i--) {
    245      if (!mFuncCalls.at(i).TailCall()) {
    246        mExpectedFramePCs.erase(mExpectedFramePCs.begin());
    247      }
    248    }
    249    mFramePCs.resize(std::min(mExpectedFramePCs.size(), mFramePCs.size()));
    250    EXPECT_EQ(mExpectedFramePCs, mFramePCs)
    251        << "Expected frames:\n"
    252        << DumpFrames(mExpectedFramePCs) << "Found frames:\n"
    253        << DumpFrames(mFramePCs)
    254        << "Function calls data (last skipped: " << aLastSkipped << "):\n"
    255        << DumpFuncCalls(mFuncCalls);
    256  }
    257 };
    258 
    259 TEST(TestStackWalk, StackWalk)
    260 {
    261  const auto foo = StackWalkTester::IntermediateCallback<1>;
    262  const auto bar = StackWalkTester::IntermediateCallback<2>;
    263  const auto qux = StackWalkTester::IntermediateCallback<3>;
    264  const auto leaf = reinterpret_cast<int (*)(int, int, int, StackWalkTester&)>(
    265      StackWalkTester::LeafCallback);
    266 
    267  const std::initializer_list<CallInfo> tests[] = {
    268      {{foo, false}, {bar, false}, {qux, false}, {leaf, false}},
    269      {{foo, false}, {bar, true}, {qux, false}, {leaf, false}},
    270      {{foo, false}, {bar, false}, {qux, false}, {leaf, true}},
    271      {{foo, true}, {bar, false}, {qux, true}, {leaf, true}},
    272  };
    273  for (auto test : tests) {
    274    StackWalkTester tester(test);
    275    for (int i = -1; i < (int)test.size(); i++) {
    276      tester.RunTest(i);
    277    }
    278  }
    279 }