Update code documentation for enum in EWK headers
[platform/framework/web/chromium-efl.git] / chrome / renderer / v8_unwinder_unittest.cc
1 // Copyright 2019 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/renderer/v8_unwinder.h"
6
7 #include <memory>
8 #include <utility>
9 #include <vector>
10
11 #include "base/functional/bind.h"
12 #include "base/functional/callback.h"
13 #include "base/location.h"
14 #include "base/profiler/module_cache.h"
15 #include "base/profiler/stack_sampling_profiler_test_util.h"
16 #include "base/synchronization/waitable_event.h"
17 #include "base/test/bind.h"
18 #include "base/test/task_environment.h"
19 #include "build/build_config.h"
20 #include "gin/public/isolate_holder.h"
21 #include "testing/gmock/include/gmock/gmock.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23 #include "v8/include/v8.h"
24
25 namespace {
26
27 using ::testing::AllOf;
28 using ::testing::AnyOf;
29 using ::testing::Contains;
30 using ::testing::Eq;
31 using ::testing::Field;
32 using ::testing::NotNull;
33 using ::testing::Pointee;
34 using ::testing::Property;
35
36 v8::Local<v8::String> ToV8String(const char* str) {
37   return v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), str)
38       .ToLocalChecked();
39 }
40
41 v8::Local<v8::Object> CreatePointerHolder(const void* ptr) {
42   v8::Local<v8::ObjectTemplate> object_template =
43       v8::ObjectTemplate::New(v8::Isolate::GetCurrent());
44   object_template->SetInternalFieldCount(1);
45   v8::Local<v8::Object> holder =
46       object_template
47           ->NewInstance(v8::Isolate::GetCurrent()->GetCurrentContext())
48           .ToLocalChecked();
49   holder->SetAlignedPointerInInternalField(0, const_cast<void*>(ptr));
50   return holder;
51 }
52
53 template <typename T>
54 T* GetPointerFromHolder(v8::Local<v8::Object> holder) {
55   return reinterpret_cast<T*>(holder->GetAlignedPointerFromInternalField(0));
56 }
57
58 // Sets up the environment necessary to execute V8 code.
59 class ScopedV8Environment {
60  public:
61   ScopedV8Environment()
62       : isolate_holder_(task_environment_.GetMainThreadTaskRunner(),
63                         gin::IsolateHolder::IsolateType::kBlinkMainThread) {
64     isolate()->Enter();
65     v8::HandleScope handle_scope(isolate());
66     context_.Reset(isolate(), v8::Context::New(isolate()));
67     v8::Local<v8::Context>::New(isolate(), context_)->Enter();
68   }
69
70   ~ScopedV8Environment() {
71     {
72       v8::HandleScope handle_scope(isolate());
73       v8::Local<v8::Context>::New(isolate(), context_)->Exit();
74       context_.Reset();
75     }
76     isolate()->Exit();
77   }
78
79   v8::Isolate* isolate() { return isolate_holder_.isolate(); }
80
81  private:
82   base::test::TaskEnvironment task_environment_;
83   gin::IsolateHolder isolate_holder_;
84   v8::Persistent<v8::Context> context_;
85 };
86
87 // C++ function to be invoked from V8 which calls back into the provided closure
88 // pointer (passed via a holder object) to wait for a stack sample to be taken.
89 void WaitForSampleNative(const v8::FunctionCallbackInfo<v8::Value>& info) {
90   base::OnceClosure* wait_for_sample =
91       GetPointerFromHolder<base::OnceClosure>(info[0].As<v8::Object>());
92   if (wait_for_sample)
93     std::move(*wait_for_sample).Run();
94 }
95
96 // Causes a stack sample to be taken after setting up a call stack from C++ to
97 // JavaScript and back into C++.
98 base::FunctionAddressRange CallThroughV8(
99     const base::RepeatingCallback<void(v8::Isolate*)>& report_isolate,
100     base::OnceClosure wait_for_sample) {
101   const void* start_program_counter = base::GetProgramCounter();
102
103   if (wait_for_sample) {
104     // Set up V8 runtime environment.
105     // Allows use of natives (functions starting with '%') within JavaScript
106     // code, which allows us to control compilation of the JavaScript function
107     // we define.
108     // TODO(wittman): The flag should be set only for the duration of this test
109     // but the V8 API currently doesn't support this. http://crbug.com/v8/9210
110     // covers adding the necessary functionality to V8.
111     v8::V8::SetFlagsFromString("--allow-natives-syntax");
112     ScopedV8Environment v8_environment;
113     v8::Isolate* isolate = v8_environment.isolate();
114     report_isolate.Run(isolate);
115     v8::HandleScope handle_scope(isolate);
116     v8::Local<v8::Context> context = isolate->GetCurrentContext();
117
118     // Define a V8 function WaitForSampleNative() backed by the C++ function
119     // WaitForSampleNative().
120     v8::Local<v8::FunctionTemplate> js_wait_for_sample_native_template =
121         v8::FunctionTemplate::New(isolate, WaitForSampleNative);
122     v8::Local<v8::Function> js_wait_for_sample_native =
123         js_wait_for_sample_native_template->GetFunction(context)
124             .ToLocalChecked();
125     js_wait_for_sample_native->SetName(ToV8String("WaitForSampleNative"));
126     context->Global()
127         ->Set(context, ToV8String("WaitForSampleNative"),
128               js_wait_for_sample_native)
129         .FromJust();
130
131     // Run a script to create the V8 function waitForSample() that invokes
132     // WaitForSampleNative(), and a function that ensures that waitForSample()
133     // gets compiled. waitForSample() just passes the holder object for the
134     // pointer to the wait_for_sample Closure back into the C++ code. We ensure
135     // that the function is compiled to test walking through both builtin and
136     // runtime-generated code.
137     const char kWaitForSampleJs[] = R"(
138         function waitForSample(closure_pointer_holder) {
139           if (closure_pointer_holder)
140             WaitForSampleNative(closure_pointer_holder);
141         }
142
143         // Set up the function to be compiled rather than interpreted.
144         function compileWaitForSample(closure_pointer_holder) {
145           %PrepareFunctionForOptimization(waitForSample);
146           waitForSample(closure_pointer_holder);
147           waitForSample(closure_pointer_holder);
148           %OptimizeFunctionOnNextCall(waitForSample);
149         }
150         )";
151     v8::Local<v8::Script> script =
152         v8::Script::Compile(context, ToV8String(kWaitForSampleJs))
153             .ToLocalChecked();
154     script->Run(context).ToLocalChecked();
155
156     // Run compileWaitForSample(), using a null closure pointer to avoid
157     // actually waiting.
158     v8::Local<v8::Function> js_compile_wait_for_sample =
159         v8::Local<v8::Function>::Cast(
160             context->Global()
161                 ->Get(context, ToV8String("compileWaitForSample"))
162                 .ToLocalChecked());
163     v8::Local<v8::Value> argv[] = {CreatePointerHolder(nullptr)};
164     js_compile_wait_for_sample
165         ->Call(context, v8::Undefined(isolate), std::size(argv), argv)
166         .ToLocalChecked();
167
168     // Run waitForSample() with the real closure pointer.
169     argv[0] = CreatePointerHolder(&wait_for_sample);
170     v8::Local<v8::Function> js_wait_for_sample = v8::Local<v8::Function>::Cast(
171         context->Global()
172             ->Get(context, ToV8String("waitForSample"))
173             .ToLocalChecked());
174     js_wait_for_sample
175         ->Call(context, v8::Undefined(isolate), std::size(argv), argv)
176         .ToLocalChecked();
177   }
178
179   // Volatile to prevent a tail call to GetProgramCounter().
180   const void* volatile end_program_counter = base::GetProgramCounter();
181   return {start_program_counter, end_program_counter};
182 }
183
184 class UpdateModulesTestUnwinder : public V8Unwinder {
185  public:
186   explicit UpdateModulesTestUnwinder(v8::Isolate* isolate)
187       : V8Unwinder(isolate) {}
188
189   void SetCodePages(std::vector<v8::MemoryRange> code_pages) {
190     code_pages_to_provide_ = code_pages;
191   }
192
193  protected:
194   size_t CopyCodePages(size_t capacity, v8::MemoryRange* code_pages) override {
195     std::copy_n(code_pages_to_provide_.begin(),
196                 std::min(capacity, code_pages_to_provide_.size()), code_pages);
197     return code_pages_to_provide_.size();
198   }
199
200  private:
201   std::vector<v8::MemoryRange> code_pages_to_provide_;
202 };
203
204 v8::MemoryRange GetEmbeddedCodeRange(v8::Isolate* isolate) {
205   v8::MemoryRange range;
206   isolate->GetEmbeddedCodeRange(&range.start, &range.length_in_bytes);
207   return range;
208 }
209
210 }  // namespace
211
212 TEST(V8UnwinderTest, EmbeddedCodeRangeModule) {
213   ScopedV8Environment v8_environment;
214   V8Unwinder unwinder(v8_environment.isolate());
215   base::ModuleCache module_cache;
216
217   unwinder.Initialize(&module_cache);
218
219   v8::MemoryRange embedded_code_range;
220   v8_environment.isolate()->GetEmbeddedCodeRange(
221       &embedded_code_range.start, &embedded_code_range.length_in_bytes);
222
223   const base::ModuleCache::Module* module = module_cache.GetModuleForAddress(
224       reinterpret_cast<uintptr_t>(embedded_code_range.start));
225   ASSERT_NE(nullptr, module);
226   EXPECT_EQ(V8Unwinder::kV8EmbeddedCodeRangeBuildId, module->GetId());
227 }
228
229 TEST(V8UnwinderTest, EmbeddedCodeRangeModulePreservedOnUpdate) {
230   ScopedV8Environment v8_environment;
231   UpdateModulesTestUnwinder unwinder(v8_environment.isolate());
232   base::ModuleCache module_cache;
233
234   unwinder.Initialize(&module_cache);
235
236   unwinder.SetCodePages({{reinterpret_cast<void*>(1), 10},
237                          GetEmbeddedCodeRange(v8_environment.isolate())});
238
239   unwinder.OnStackCapture();
240   unwinder.UpdateModules();
241
242   v8::MemoryRange embedded_code_range;
243   v8_environment.isolate()->GetEmbeddedCodeRange(
244       &embedded_code_range.start, &embedded_code_range.length_in_bytes);
245
246   const base::ModuleCache::Module* module = module_cache.GetModuleForAddress(
247       reinterpret_cast<uintptr_t>(embedded_code_range.start));
248   ASSERT_NE(nullptr, module);
249   EXPECT_EQ(V8Unwinder::kV8EmbeddedCodeRangeBuildId, module->GetId());
250 }
251
252 // Checks that the embedded code range is preserved even if it wasn't included
253 // in the code pages due to insufficient capacity.
254 TEST(V8UnwinderTest, EmbeddedCodeRangeModulePreservedOnOverCapacityUpdate) {
255   ScopedV8Environment v8_environment;
256   UpdateModulesTestUnwinder unwinder(v8_environment.isolate());
257   base::ModuleCache module_cache;
258
259   unwinder.Initialize(&module_cache);
260
261   const int kDefaultCapacity = v8::Isolate::kMinCodePagesBufferSize;
262   std::vector<v8::MemoryRange> code_pages;
263   code_pages.reserve(kDefaultCapacity + 1);
264   for (int i = 0; i < kDefaultCapacity + 1; ++i)
265     code_pages.push_back({reinterpret_cast<void*>(i + 1), 1});
266   unwinder.SetCodePages(code_pages);
267
268   unwinder.OnStackCapture();
269   unwinder.UpdateModules();
270
271   v8::MemoryRange embedded_code_range;
272   v8_environment.isolate()->GetEmbeddedCodeRange(
273       &embedded_code_range.start, &embedded_code_range.length_in_bytes);
274
275   const base::ModuleCache::Module* module = module_cache.GetModuleForAddress(
276       reinterpret_cast<uintptr_t>(embedded_code_range.start));
277   ASSERT_NE(nullptr, module);
278   EXPECT_EQ(V8Unwinder::kV8EmbeddedCodeRangeBuildId, module->GetId());
279 }
280
281 TEST(V8UnwinderTest, UpdateModules_ModuleAdded) {
282   ScopedV8Environment v8_environment;
283   UpdateModulesTestUnwinder unwinder(v8_environment.isolate());
284   base::ModuleCache module_cache;
285
286   unwinder.Initialize(&module_cache);
287   unwinder.SetCodePages({{reinterpret_cast<void*>(1), 10},
288                          GetEmbeddedCodeRange(v8_environment.isolate())});
289   unwinder.OnStackCapture();
290   unwinder.UpdateModules();
291
292   const base::ModuleCache::Module* module = module_cache.GetModuleForAddress(1);
293   ASSERT_NE(nullptr, module);
294   EXPECT_EQ(1u, module->GetBaseAddress());
295   EXPECT_EQ(10u, module->GetSize());
296   EXPECT_EQ(V8Unwinder::kV8CodeRangeBuildId, module->GetId());
297   EXPECT_EQ("V8 Code Range", module->GetDebugBasename().MaybeAsASCII());
298 }
299
300 // Check that modules added before the last module are propagated to the
301 // ModuleCache. This case takes a different code path in the implementation.
302 TEST(V8UnwinderTest, UpdateModules_ModuleAddedBeforeLast) {
303   ScopedV8Environment v8_environment;
304   UpdateModulesTestUnwinder unwinder(v8_environment.isolate());
305   base::ModuleCache module_cache;
306
307   unwinder.Initialize(&module_cache);
308
309   unwinder.SetCodePages({{reinterpret_cast<void*>(100), 10},
310                          GetEmbeddedCodeRange(v8_environment.isolate())});
311   unwinder.OnStackCapture();
312   unwinder.UpdateModules();
313
314   unwinder.SetCodePages({{reinterpret_cast<void*>(1), 10},
315                          {reinterpret_cast<void*>(100), 10},
316                          GetEmbeddedCodeRange(v8_environment.isolate())});
317   unwinder.OnStackCapture();
318   unwinder.UpdateModules();
319
320   const base::ModuleCache::Module* module = module_cache.GetModuleForAddress(1);
321   ASSERT_NE(nullptr, module);
322   EXPECT_EQ(1u, module->GetBaseAddress());
323   EXPECT_EQ(10u, module->GetSize());
324   EXPECT_EQ(V8Unwinder::kV8CodeRangeBuildId, module->GetId());
325   EXPECT_EQ("V8 Code Range", module->GetDebugBasename().MaybeAsASCII());
326 }
327
328 TEST(V8UnwinderTest, UpdateModules_ModuleRetained) {
329   ScopedV8Environment v8_environment;
330   UpdateModulesTestUnwinder unwinder(v8_environment.isolate());
331   base::ModuleCache module_cache;
332
333   unwinder.Initialize(&module_cache);
334
335   unwinder.SetCodePages({{reinterpret_cast<void*>(1), 10},
336                          GetEmbeddedCodeRange(v8_environment.isolate())});
337   unwinder.OnStackCapture();
338   unwinder.UpdateModules();
339
340   // Code pages remain the same for this stack capture.
341   unwinder.OnStackCapture();
342   unwinder.UpdateModules();
343
344   const base::ModuleCache::Module* module = module_cache.GetModuleForAddress(1);
345   ASSERT_NE(nullptr, module);
346   EXPECT_EQ(1u, module->GetBaseAddress());
347   EXPECT_EQ(10u, module->GetSize());
348   EXPECT_EQ(V8Unwinder::kV8CodeRangeBuildId, module->GetId());
349   EXPECT_EQ("V8 Code Range", module->GetDebugBasename().MaybeAsASCII());
350 }
351
352 TEST(V8UnwinderTest, UpdateModules_ModuleRetainedWithDifferentSize) {
353   ScopedV8Environment v8_environment;
354   UpdateModulesTestUnwinder unwinder(v8_environment.isolate());
355   base::ModuleCache module_cache;
356
357   unwinder.Initialize(&module_cache);
358
359   unwinder.SetCodePages({{reinterpret_cast<void*>(1), 10},
360                          GetEmbeddedCodeRange(v8_environment.isolate())});
361   unwinder.OnStackCapture();
362   unwinder.UpdateModules();
363
364   // Code pages remain the same for this stack capture.
365   unwinder.SetCodePages({{reinterpret_cast<void*>(1), 20},
366                          GetEmbeddedCodeRange(v8_environment.isolate())});
367   unwinder.OnStackCapture();
368   unwinder.UpdateModules();
369
370   const base::ModuleCache::Module* module =
371       module_cache.GetModuleForAddress(11);
372   ASSERT_NE(nullptr, module);
373   EXPECT_EQ(1u, module->GetBaseAddress());
374   EXPECT_EQ(20u, module->GetSize());
375 }
376
377 TEST(V8UnwinderTest, UpdateModules_ModuleRemoved) {
378   ScopedV8Environment v8_environment;
379   UpdateModulesTestUnwinder unwinder(v8_environment.isolate());
380   base::ModuleCache module_cache;
381
382   unwinder.Initialize(&module_cache);
383
384   unwinder.SetCodePages({{{reinterpret_cast<void*>(1), 10},
385                           GetEmbeddedCodeRange(v8_environment.isolate())}});
386   unwinder.OnStackCapture();
387   unwinder.UpdateModules();
388
389   unwinder.SetCodePages({GetEmbeddedCodeRange(v8_environment.isolate())});
390   unwinder.OnStackCapture();
391   unwinder.UpdateModules();
392
393   EXPECT_EQ(nullptr, module_cache.GetModuleForAddress(1));
394 }
395
396 // Check that modules removed before the last module are propagated to the
397 // ModuleCache. This case takes a different code path in the implementation.
398 TEST(V8UnwinderTest, UpdateModules_ModuleRemovedBeforeLast) {
399   ScopedV8Environment v8_environment;
400   UpdateModulesTestUnwinder unwinder(v8_environment.isolate());
401   base::ModuleCache module_cache;
402
403   unwinder.Initialize(&module_cache);
404
405   unwinder.SetCodePages({{{reinterpret_cast<void*>(1), 10},
406                           {reinterpret_cast<void*>(100), 10},
407                           GetEmbeddedCodeRange(v8_environment.isolate())}});
408   unwinder.OnStackCapture();
409   unwinder.UpdateModules();
410
411   unwinder.SetCodePages({{reinterpret_cast<void*>(100), 10},
412                          GetEmbeddedCodeRange(v8_environment.isolate())});
413   unwinder.OnStackCapture();
414   unwinder.UpdateModules();
415
416   EXPECT_EQ(nullptr, module_cache.GetModuleForAddress(1));
417 }
418
419 TEST(V8UnwinderTest, UpdateModules_CapacityExceeded) {
420   ScopedV8Environment v8_environment;
421   UpdateModulesTestUnwinder unwinder(v8_environment.isolate());
422   base::ModuleCache module_cache;
423
424   unwinder.Initialize(&module_cache);
425
426   const int kDefaultCapacity = v8::Isolate::kMinCodePagesBufferSize;
427
428   std::vector<v8::MemoryRange> code_pages;
429   // Create kDefaultCapacity + 2 code pages, with the last being the embedded
430   // code page.
431   code_pages.reserve(kDefaultCapacity + 2);
432   for (int i = 0; i < kDefaultCapacity + 1; ++i)
433     code_pages.push_back({reinterpret_cast<void*>(i + 1), 1});
434   code_pages.push_back(GetEmbeddedCodeRange(v8_environment.isolate()));
435
436   // The first sample should successfully create modules up to the default
437   // capacity.
438   unwinder.SetCodePages(code_pages);
439   unwinder.OnStackCapture();
440   unwinder.UpdateModules();
441
442   EXPECT_NE(nullptr, module_cache.GetModuleForAddress(kDefaultCapacity));
443   EXPECT_EQ(nullptr, module_cache.GetModuleForAddress(kDefaultCapacity + 1));
444
445   // The capacity should be expanded by the second sample.
446   unwinder.SetCodePages(code_pages);
447   unwinder.OnStackCapture();
448   unwinder.UpdateModules();
449
450   EXPECT_NE(nullptr, module_cache.GetModuleForAddress(kDefaultCapacity));
451   EXPECT_NE(nullptr, module_cache.GetModuleForAddress(kDefaultCapacity + 1));
452 }
453
454 // Checks that the implementation can handle the capacity being exceeded by a
455 // large amount.
456 TEST(V8UnwinderTest, UpdateModules_CapacitySubstantiallyExceeded) {
457   ScopedV8Environment v8_environment;
458   UpdateModulesTestUnwinder unwinder(v8_environment.isolate());
459   base::ModuleCache module_cache;
460
461   unwinder.Initialize(&module_cache);
462
463   const int kDefaultCapacity = v8::Isolate::kMinCodePagesBufferSize;
464   const int kCodePages = kDefaultCapacity * 3;
465   std::vector<v8::MemoryRange> code_pages;
466   code_pages.reserve(kCodePages);
467   // Create kCodePages with the last being the embedded code page.
468   for (int i = 0; i < kCodePages - 1; ++i)
469     code_pages.push_back({reinterpret_cast<void*>(i + 1), 1});
470   code_pages.push_back(GetEmbeddedCodeRange(v8_environment.isolate()));
471
472   // The first sample should successfully create modules up to the default
473   // capacity.
474   unwinder.SetCodePages(code_pages);
475   unwinder.OnStackCapture();
476   unwinder.UpdateModules();
477
478   EXPECT_NE(nullptr, module_cache.GetModuleForAddress(kDefaultCapacity));
479   EXPECT_EQ(nullptr, module_cache.GetModuleForAddress(kDefaultCapacity + 1));
480
481   // The capacity should be expanded by the second sample to handle all the
482   // available modules.
483   unwinder.SetCodePages(code_pages);
484   unwinder.OnStackCapture();
485   unwinder.UpdateModules();
486
487   EXPECT_NE(nullptr, module_cache.GetModuleForAddress(kCodePages - 1));
488 }
489
490 TEST(V8UnwinderTest, CanUnwindFrom_V8Module) {
491   ScopedV8Environment v8_environment;
492   UpdateModulesTestUnwinder unwinder(v8_environment.isolate());
493   base::ModuleCache module_cache;
494
495   unwinder.Initialize(&module_cache);
496
497   unwinder.SetCodePages({{reinterpret_cast<void*>(1), 10},
498                          GetEmbeddedCodeRange(v8_environment.isolate())});
499   unwinder.OnStackCapture();
500   unwinder.UpdateModules();
501
502   const base::ModuleCache::Module* module = module_cache.GetModuleForAddress(1);
503   ASSERT_NE(nullptr, module);
504
505   EXPECT_TRUE(unwinder.CanUnwindFrom({1, module}));
506 }
507
508 TEST(V8UnwinderTest, CanUnwindFrom_OtherModule) {
509   ScopedV8Environment v8_environment;
510   UpdateModulesTestUnwinder unwinder(v8_environment.isolate());
511   base::ModuleCache module_cache;
512
513   unwinder.Initialize(&module_cache);
514
515   unwinder.SetCodePages({GetEmbeddedCodeRange(v8_environment.isolate())});
516   unwinder.OnStackCapture();
517   unwinder.UpdateModules();
518
519   auto other_module = std::make_unique<base::TestModule>(1, 10);
520   const base::ModuleCache::Module* other_module_ptr = other_module.get();
521   module_cache.AddCustomNativeModule(std::move(other_module));
522
523   EXPECT_FALSE(unwinder.CanUnwindFrom({1, other_module_ptr}));
524 }
525
526 TEST(V8UnwinderTest, CanUnwindFrom_NullModule) {
527   ScopedV8Environment v8_environment;
528   UpdateModulesTestUnwinder unwinder(v8_environment.isolate());
529   base::ModuleCache module_cache;
530
531   unwinder.Initialize(&module_cache);
532
533   // Insert a non-native module to potentially exercise the Module comparator.
534   unwinder.SetCodePages({{reinterpret_cast<void*>(1), 10},
535                          GetEmbeddedCodeRange(v8_environment.isolate())});
536   unwinder.OnStackCapture();
537   unwinder.UpdateModules();
538
539   EXPECT_FALSE(unwinder.CanUnwindFrom({20, nullptr}));
540 }
541
542 // Checks that unwinding from C++ through JavaScript and back into C++ succeeds.
543 #if (BUILDFLAG(IS_WIN) && defined(ARCH_CPU_64_BITS)) || BUILDFLAG(IS_MAC) || \
544     (BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_ARMEL))
545 #define MAYBE_UnwindThroughV8Frames UnwindThroughV8Frames
546 #else
547 #define MAYBE_UnwindThroughV8Frames DISABLED_UnwindThroughV8Frames
548 #endif
549 TEST(V8UnwinderTest, MAYBE_UnwindThroughV8Frames) {
550   v8::Isolate* isolate = nullptr;
551   base::WaitableEvent isolate_available;
552
553   const auto set_isolate = [&](v8::Isolate* isolate_state) {
554     isolate = isolate_state;
555     isolate_available.Signal();
556   };
557
558   const auto create_v8_unwinder = [&]() -> std::unique_ptr<base::Unwinder> {
559     isolate_available.Wait();
560     return std::make_unique<V8Unwinder>(isolate);
561   };
562
563   base::UnwindScenario scenario(base::BindRepeating(
564       &CallThroughV8, base::BindLambdaForTesting(set_isolate)));
565   base::ModuleCache module_cache;
566
567   std::vector<base::Frame> sample = SampleScenario(
568       &scenario, &module_cache, base::BindLambdaForTesting(create_v8_unwinder));
569
570   // The stack should contain a full unwind.
571   ExpectStackContains(sample, {scenario.GetWaitForSampleAddressRange(),
572                                scenario.GetSetupFunctionAddressRange(),
573                                scenario.GetOuterFunctionAddressRange()});
574
575   // The stack should contain a frame from a JavaScript module.
576   EXPECT_THAT(sample,
577               Contains(Field(
578                   "module", &base::Frame::module,
579                   AllOf(NotNull(),
580                         Pointee(Property(
581                             "module.id", &base::ModuleCache::Module::GetId,
582                             AnyOf(Eq(V8Unwinder::kV8EmbeddedCodeRangeBuildId),
583                                   Eq(V8Unwinder::kV8CodeRangeBuildId))))))));
584 }