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