2011-04-07 14:42:37 +00:00
|
|
|
// Copyright 2011 the V8 project authors. All rights reserved.
|
2010-09-01 13:13:31 +00:00
|
|
|
// Redistribution and use in source and binary forms, with or without
|
|
|
|
// modification, are permitted provided that the following conditions are
|
|
|
|
// met:
|
|
|
|
//
|
|
|
|
// * Redistributions of source code must retain the above copyright
|
|
|
|
// notice, this list of conditions and the following disclaimer.
|
|
|
|
// * Redistributions in binary form must reproduce the above
|
|
|
|
// copyright notice, this list of conditions and the following
|
|
|
|
// disclaimer in the documentation and/or other materials provided
|
|
|
|
// with the distribution.
|
|
|
|
// * Neither the name of Google Inc. nor the names of its
|
|
|
|
// contributors may be used to endorse or promote products derived
|
|
|
|
// from this software without specific prior written permission.
|
|
|
|
//
|
|
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
|
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
|
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
|
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
|
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
|
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
|
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
2009-02-25 16:00:21 +00:00
|
|
|
//
|
|
|
|
// Tests of profiler-related functions from log.h
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
2021-08-23 13:01:06 +00:00
|
|
|
#include "include/v8-function.h"
|
2019-05-17 12:13:44 +00:00
|
|
|
#include "src/api/api-inl.h"
|
2021-06-22 13:27:00 +00:00
|
|
|
#include "src/base/strings.h"
|
2019-07-12 09:32:55 +00:00
|
|
|
#include "src/execution/frames.h"
|
2019-05-22 07:55:37 +00:00
|
|
|
#include "src/execution/isolate.h"
|
|
|
|
#include "src/execution/vm-state-inl.h"
|
2019-05-23 08:51:46 +00:00
|
|
|
#include "src/objects/objects-inl.h"
|
2019-08-21 15:22:17 +00:00
|
|
|
#include "src/profiler/tick-sample.h"
|
2014-06-03 08:12:43 +00:00
|
|
|
#include "test/cctest/cctest.h"
|
|
|
|
#include "test/cctest/trace-extension.h"
|
2009-02-25 16:00:21 +00:00
|
|
|
|
2017-08-11 11:22:28 +00:00
|
|
|
namespace v8 {
|
|
|
|
namespace internal {
|
2009-02-25 16:00:21 +00:00
|
|
|
|
2020-12-17 16:02:56 +00:00
|
|
|
static bool IsAddressWithinFuncCode(JSFunction function, Isolate* isolate,
|
|
|
|
void* addr) {
|
|
|
|
i::AbstractCode code = function.abstract_code(isolate);
|
2021-11-30 12:04:46 +00:00
|
|
|
return code.contains(isolate, reinterpret_cast<Address>(addr));
|
2011-02-22 16:31:24 +00:00
|
|
|
}
|
|
|
|
|
2013-09-19 13:30:47 +00:00
|
|
|
static bool IsAddressWithinFuncCode(v8::Local<v8::Context> context,
|
2020-12-17 16:02:56 +00:00
|
|
|
Isolate* isolate, const char* func_name,
|
|
|
|
void* addr) {
|
2015-11-17 12:42:14 +00:00
|
|
|
v8::Local<v8::Value> func =
|
|
|
|
context->Global()->Get(context, v8_str(func_name)).ToLocalChecked();
|
2011-02-22 16:31:24 +00:00
|
|
|
CHECK(func->IsFunction());
|
2018-12-08 02:59:17 +00:00
|
|
|
JSFunction js_func = JSFunction::cast(*v8::Utils::OpenHandle(*func));
|
2020-12-17 16:02:56 +00:00
|
|
|
return IsAddressWithinFuncCode(js_func, isolate, addr);
|
2009-05-07 09:24:43 +00:00
|
|
|
}
|
|
|
|
|
2010-09-01 13:13:31 +00:00
|
|
|
// This C++ function is called as a constructor, to grab the frame pointer
|
|
|
|
// from the calling function. When this function runs, the stack contains
|
|
|
|
// a C_Entry frame and a Construct frame above the calling function's frame.
|
2013-06-20 12:28:27 +00:00
|
|
|
static void construct_call(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
2013-02-15 09:27:10 +00:00
|
|
|
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(args.GetIsolate());
|
|
|
|
i::StackFrameIterator frame_iterator(isolate);
|
2016-06-30 06:55:22 +00:00
|
|
|
CHECK(frame_iterator.frame()->is_exit() ||
|
|
|
|
frame_iterator.frame()->is_builtin_exit());
|
2010-09-01 13:13:31 +00:00
|
|
|
frame_iterator.Advance();
|
|
|
|
CHECK(frame_iterator.frame()->is_construct());
|
|
|
|
frame_iterator.Advance();
|
2016-10-27 12:17:13 +00:00
|
|
|
if (frame_iterator.frame()->type() == i::StackFrame::STUB) {
|
2016-02-12 08:48:44 +00:00
|
|
|
// Skip over bytecode handler frame.
|
|
|
|
frame_iterator.Advance();
|
|
|
|
}
|
2010-09-01 13:13:31 +00:00
|
|
|
i::StackFrame* calling_frame = frame_iterator.frame();
|
|
|
|
CHECK(calling_frame->is_java_script());
|
|
|
|
|
2015-11-17 12:42:14 +00:00
|
|
|
v8::Local<v8::Context> context = args.GetIsolate()->GetCurrentContext();
|
2010-09-01 13:13:31 +00:00
|
|
|
#if defined(V8_HOST_ARCH_32_BIT)
|
2018-04-13 22:28:05 +00:00
|
|
|
int32_t low_bits = static_cast<int32_t>(calling_frame->fp());
|
2015-11-17 12:42:14 +00:00
|
|
|
args.This()
|
|
|
|
->Set(context, v8_str("low_bits"), v8_num(low_bits >> 1))
|
|
|
|
.FromJust();
|
2010-09-01 13:13:31 +00:00
|
|
|
#elif defined(V8_HOST_ARCH_64_BIT)
|
2018-04-13 22:28:05 +00:00
|
|
|
Address fp = calling_frame->fp();
|
2018-06-05 10:45:41 +00:00
|
|
|
uint64_t kSmiValueMask =
|
|
|
|
(static_cast<uintptr_t>(1) << (kSmiValueSize - 1)) - 1;
|
|
|
|
int32_t low_bits = static_cast<int32_t>(fp & kSmiValueMask);
|
|
|
|
fp >>= kSmiValueSize - 1;
|
|
|
|
int32_t high_bits = static_cast<int32_t>(fp & kSmiValueMask);
|
|
|
|
fp >>= kSmiValueSize - 1;
|
|
|
|
CHECK_EQ(fp, 0); // Ensure all the bits are successfully encoded.
|
|
|
|
args.This()->Set(context, v8_str("low_bits"), v8_int(low_bits)).FromJust();
|
|
|
|
args.This()->Set(context, v8_str("high_bits"), v8_int(high_bits)).FromJust();
|
2010-09-01 13:13:31 +00:00
|
|
|
#else
|
|
|
|
#error Host architecture is neither 32-bit nor 64-bit.
|
|
|
|
#endif
|
2013-06-20 12:28:27 +00:00
|
|
|
args.GetReturnValue().Set(args.This());
|
2010-09-01 13:13:31 +00:00
|
|
|
}
|
2009-05-07 09:24:43 +00:00
|
|
|
|
|
|
|
|
2010-09-01 13:13:31 +00:00
|
|
|
// Use the API to create a JSFunction object that calls the above C++ function.
|
2013-09-19 13:30:47 +00:00
|
|
|
void CreateFramePointerGrabberConstructor(v8::Local<v8::Context> context,
|
|
|
|
const char* constructor_name) {
|
2010-09-01 13:13:31 +00:00
|
|
|
Local<v8::FunctionTemplate> constructor_template =
|
2013-12-18 10:31:42 +00:00
|
|
|
v8::FunctionTemplate::New(context->GetIsolate(), construct_call);
|
2010-09-01 13:13:31 +00:00
|
|
|
constructor_template->SetClassName(v8_str("FPGrabber"));
|
2015-11-17 12:42:14 +00:00
|
|
|
Local<Function> fun =
|
|
|
|
constructor_template->GetFunction(context).ToLocalChecked();
|
|
|
|
context->Global()->Set(context, v8_str(constructor_name), fun).FromJust();
|
2010-09-01 13:13:31 +00:00
|
|
|
}
|
2009-05-07 09:24:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
// Creates a global function named 'func_name' that calls the tracing
|
|
|
|
// function 'trace_func_name' with an actual EBP register value,
|
2010-09-01 13:13:31 +00:00
|
|
|
// encoded as one or two Smis.
|
2013-09-19 13:30:47 +00:00
|
|
|
static void CreateTraceCallerFunction(v8::Local<v8::Context> context,
|
|
|
|
const char* func_name,
|
2009-05-07 09:24:43 +00:00
|
|
|
const char* trace_func_name) {
|
2021-06-17 15:43:55 +00:00
|
|
|
v8::base::EmbeddedVector<char, 256> trace_call_buf;
|
2021-06-22 13:27:00 +00:00
|
|
|
v8::base::SNPrintF(trace_call_buf,
|
|
|
|
"function %s() {"
|
|
|
|
" fp = new FPGrabber();"
|
|
|
|
" %s(fp.low_bits, fp.high_bits);"
|
|
|
|
"}",
|
|
|
|
func_name, trace_func_name);
|
2010-09-01 13:13:31 +00:00
|
|
|
|
|
|
|
// Create the FPGrabber function, which grabs the caller's frame pointer
|
|
|
|
// when called as a constructor.
|
2013-09-19 13:30:47 +00:00
|
|
|
CreateFramePointerGrabberConstructor(context, "FPGrabber");
|
2009-05-07 09:24:43 +00:00
|
|
|
|
|
|
|
// Compile the script.
|
2019-04-29 11:06:49 +00:00
|
|
|
CompileRun(trace_call_buf.begin());
|
2009-05-07 09:24:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-24 08:46:17 +00:00
|
|
|
// This test verifies that stack tracing works when called during
|
|
|
|
// execution of a native function called from JS code. In this case,
|
2013-04-17 07:53:12 +00:00
|
|
|
// TickSample::Trace uses Isolate::c_entry_fp as a starting point for stack
|
2010-03-24 08:46:17 +00:00
|
|
|
// walking.
|
2009-05-07 09:24:43 +00:00
|
|
|
TEST(CFromJSStackTrace) {
|
2011-04-04 15:03:34 +00:00
|
|
|
// BUG(1303) Inlining of JSFuncDoTrace() in JSTrace below breaks this test.
|
2015-10-15 12:01:52 +00:00
|
|
|
i::FLAG_turbo_inlining = false;
|
2012-03-21 09:23:09 +00:00
|
|
|
|
2009-05-07 09:24:43 +00:00
|
|
|
TickSample sample;
|
2014-01-17 10:52:00 +00:00
|
|
|
i::TraceExtension::InitTraceEnv(&sample);
|
2009-05-07 09:24:43 +00:00
|
|
|
|
2013-04-10 08:29:39 +00:00
|
|
|
v8::HandleScope scope(CcTest::isolate());
|
2019-01-16 17:31:37 +00:00
|
|
|
v8::Local<v8::Context> context = CcTest::NewContext({TRACE_EXTENSION_ID});
|
2013-09-19 13:30:47 +00:00
|
|
|
v8::Context::Scope context_scope(context);
|
|
|
|
|
2010-03-24 08:46:17 +00:00
|
|
|
// Create global function JSFuncDoTrace which calls
|
|
|
|
// extension function trace() with the current frame pointer value.
|
2013-09-19 13:30:47 +00:00
|
|
|
CreateTraceCallerFunction(context, "JSFuncDoTrace", "trace");
|
2010-03-23 12:42:47 +00:00
|
|
|
Local<Value> result = CompileRun(
|
2009-05-07 09:24:43 +00:00
|
|
|
"function JSTrace() {"
|
|
|
|
" JSFuncDoTrace();"
|
|
|
|
"};\n"
|
2010-03-23 12:42:47 +00:00
|
|
|
"JSTrace();\n"
|
|
|
|
"true;");
|
|
|
|
CHECK(!result.IsEmpty());
|
2010-03-24 08:46:17 +00:00
|
|
|
// When stack tracer is invoked, the stack should look as follows:
|
|
|
|
// script [JS]
|
|
|
|
// JSTrace() [JS]
|
|
|
|
// JSFuncDoTrace() [JS] [captures EBP value and encodes it as Smi]
|
2010-09-16 08:23:34 +00:00
|
|
|
// trace(EBP) [native (extension)]
|
2010-03-24 08:46:17 +00:00
|
|
|
// DoTrace(EBP) [native]
|
2013-04-17 07:53:12 +00:00
|
|
|
// TickSample::Trace
|
2010-12-07 11:31:57 +00:00
|
|
|
|
2013-04-19 11:55:01 +00:00
|
|
|
CHECK(sample.has_external_callback);
|
2016-03-04 18:55:48 +00:00
|
|
|
CHECK_EQ(FUNCTION_ADDR(i::TraceExtension::Trace),
|
2018-04-13 22:28:05 +00:00
|
|
|
reinterpret_cast<Address>(sample.external_callback_entry));
|
2010-12-07 11:31:57 +00:00
|
|
|
|
2010-03-24 08:46:17 +00:00
|
|
|
// Stack tracing will start from the first JS function, i.e. "JSFuncDoTrace"
|
2014-06-24 16:00:51 +00:00
|
|
|
unsigned base = 0;
|
2010-12-07 11:31:57 +00:00
|
|
|
CHECK_GT(sample.frames_count, base + 1);
|
2012-03-19 15:54:37 +00:00
|
|
|
|
2020-12-17 16:02:56 +00:00
|
|
|
CHECK(IsAddressWithinFuncCode(context, CcTest::i_isolate(), "JSFuncDoTrace",
|
|
|
|
sample.stack[base + 0]));
|
|
|
|
CHECK(IsAddressWithinFuncCode(context, CcTest::i_isolate(), "JSTrace",
|
|
|
|
sample.stack[base + 1]));
|
2009-05-07 09:24:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-24 08:46:17 +00:00
|
|
|
// This test verifies that stack tracing works when called during
|
2013-04-17 07:53:12 +00:00
|
|
|
// execution of JS code. However, as calling TickSample::Trace requires
|
2010-03-24 08:46:17 +00:00
|
|
|
// entering native code, we can only emulate pure JS by erasing
|
2013-04-17 07:53:12 +00:00
|
|
|
// Isolate::c_entry_fp value. In this case, TickSample::Trace uses passed frame
|
2010-03-24 08:46:17 +00:00
|
|
|
// pointer value as a starting point for stack walking.
|
2009-05-07 09:24:43 +00:00
|
|
|
TEST(PureJSStackTrace) {
|
2010-12-07 11:31:57 +00:00
|
|
|
// This test does not pass with inlining enabled since inlined functions
|
|
|
|
// don't appear in the stack trace.
|
2015-10-15 12:01:52 +00:00
|
|
|
i::FLAG_turbo_inlining = false;
|
2010-12-07 11:31:57 +00:00
|
|
|
|
2009-05-07 09:24:43 +00:00
|
|
|
TickSample sample;
|
2014-01-17 10:52:00 +00:00
|
|
|
i::TraceExtension::InitTraceEnv(&sample);
|
2009-05-07 09:24:43 +00:00
|
|
|
|
2013-04-10 08:29:39 +00:00
|
|
|
v8::HandleScope scope(CcTest::isolate());
|
2019-01-16 17:31:37 +00:00
|
|
|
v8::Local<v8::Context> context = CcTest::NewContext({TRACE_EXTENSION_ID});
|
2013-09-19 13:30:47 +00:00
|
|
|
v8::Context::Scope context_scope(context);
|
|
|
|
|
2010-03-24 08:46:17 +00:00
|
|
|
// Create global function JSFuncDoTrace which calls
|
|
|
|
// extension function js_trace() with the current frame pointer value.
|
2013-09-19 13:30:47 +00:00
|
|
|
CreateTraceCallerFunction(context, "JSFuncDoTrace", "js_trace");
|
2010-03-23 12:42:47 +00:00
|
|
|
Local<Value> result = CompileRun(
|
2009-05-07 09:24:43 +00:00
|
|
|
"function JSTrace() {"
|
|
|
|
" JSFuncDoTrace();"
|
|
|
|
"};\n"
|
|
|
|
"function OuterJSTrace() {"
|
|
|
|
" JSTrace();"
|
|
|
|
"};\n"
|
2010-03-23 12:42:47 +00:00
|
|
|
"OuterJSTrace();\n"
|
|
|
|
"true;");
|
|
|
|
CHECK(!result.IsEmpty());
|
2010-03-24 08:46:17 +00:00
|
|
|
// When stack tracer is invoked, the stack should look as follows:
|
|
|
|
// script [JS]
|
|
|
|
// OuterJSTrace() [JS]
|
|
|
|
// JSTrace() [JS]
|
2010-09-16 08:23:34 +00:00
|
|
|
// JSFuncDoTrace() [JS]
|
|
|
|
// js_trace(EBP) [native (extension)]
|
2010-03-24 08:46:17 +00:00
|
|
|
// DoTraceHideCEntryFPAddress(EBP) [native]
|
2013-04-17 07:53:12 +00:00
|
|
|
// TickSample::Trace
|
2010-03-24 08:46:17 +00:00
|
|
|
//
|
2010-12-07 11:31:57 +00:00
|
|
|
|
2013-04-19 11:55:01 +00:00
|
|
|
CHECK(sample.has_external_callback);
|
2016-03-04 18:55:48 +00:00
|
|
|
CHECK_EQ(FUNCTION_ADDR(i::TraceExtension::JSTrace),
|
2018-04-13 22:28:05 +00:00
|
|
|
reinterpret_cast<Address>(sample.external_callback_entry));
|
2010-12-07 11:31:57 +00:00
|
|
|
|
2009-05-07 09:24:43 +00:00
|
|
|
// Stack sampling will start from the caller of JSFuncDoTrace, i.e. "JSTrace"
|
2014-06-24 16:00:51 +00:00
|
|
|
unsigned base = 0;
|
2010-12-07 11:31:57 +00:00
|
|
|
CHECK_GT(sample.frames_count, base + 1);
|
2020-12-17 16:02:56 +00:00
|
|
|
CHECK(IsAddressWithinFuncCode(context, CcTest::i_isolate(), "JSTrace",
|
|
|
|
sample.stack[base + 0]));
|
|
|
|
CHECK(IsAddressWithinFuncCode(context, CcTest::i_isolate(), "OuterJSTrace",
|
|
|
|
sample.stack[base + 1]));
|
2009-05-07 09:24:43 +00:00
|
|
|
}
|
|
|
|
|
2018-04-13 22:28:05 +00:00
|
|
|
static void CFuncDoTrace(byte dummy_param) {
|
2009-05-07 09:24:43 +00:00
|
|
|
Address fp;
|
2014-10-20 12:04:22 +00:00
|
|
|
#if V8_HAS_BUILTIN_FRAME_ADDRESS
|
2009-05-07 09:24:43 +00:00
|
|
|
fp = reinterpret_cast<Address>(__builtin_frame_address(0));
|
2014-10-20 12:04:22 +00:00
|
|
|
#elif V8_CC_MSVC
|
2009-10-27 08:50:24 +00:00
|
|
|
// Approximate a frame pointer address. We compile without base pointers,
|
|
|
|
// so we can't trust ebp/rbp.
|
2021-04-29 16:54:13 +00:00
|
|
|
fp = reinterpret_cast<Address>(&dummy_param) - 2 * sizeof(void*);
|
2009-10-27 08:50:24 +00:00
|
|
|
#else
|
|
|
|
#error Unexpected platform.
|
2009-03-20 14:49:12 +00:00
|
|
|
#endif
|
2014-01-17 10:52:00 +00:00
|
|
|
i::TraceExtension::DoTrace(fp);
|
2009-03-20 14:49:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int CFunc(int depth) {
|
|
|
|
if (depth <= 0) {
|
2009-10-27 08:50:24 +00:00
|
|
|
CFuncDoTrace(0);
|
2009-03-20 14:49:12 +00:00
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
return CFunc(depth - 1) + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-24 08:46:17 +00:00
|
|
|
// This test verifies that stack tracing doesn't crash when called on
|
2013-04-17 07:53:12 +00:00
|
|
|
// pure native code. TickSample::Trace only unrolls JS code, so we can't
|
2010-03-24 08:46:17 +00:00
|
|
|
// get any meaningful info here.
|
2009-03-20 14:49:12 +00:00
|
|
|
TEST(PureCStackTrace) {
|
|
|
|
TickSample sample;
|
2014-01-17 10:52:00 +00:00
|
|
|
i::TraceExtension::InitTraceEnv(&sample);
|
2013-09-19 13:30:47 +00:00
|
|
|
v8::HandleScope scope(CcTest::isolate());
|
2019-01-16 17:31:37 +00:00
|
|
|
v8::Local<v8::Context> context = CcTest::NewContext({TRACE_EXTENSION_ID});
|
2013-09-19 13:30:47 +00:00
|
|
|
v8::Context::Scope context_scope(context);
|
2009-03-20 14:49:12 +00:00
|
|
|
// Check that sampler doesn't crash
|
|
|
|
CHECK_EQ(10, CFunc(10));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-06-02 09:33:17 +00:00
|
|
|
TEST(JsEntrySp) {
|
2013-04-10 08:29:39 +00:00
|
|
|
v8::HandleScope scope(CcTest::isolate());
|
2019-01-16 17:31:37 +00:00
|
|
|
v8::Local<v8::Context> context = CcTest::NewContext({TRACE_EXTENSION_ID});
|
2013-09-19 13:30:47 +00:00
|
|
|
v8::Context::Scope context_scope(context);
|
2015-01-30 09:29:25 +00:00
|
|
|
CHECK(!i::TraceExtension::GetJsEntrySp());
|
2009-06-02 09:33:17 +00:00
|
|
|
CompileRun("a = 1; b = a + 1;");
|
2015-01-30 09:29:25 +00:00
|
|
|
CHECK(!i::TraceExtension::GetJsEntrySp());
|
2009-06-02 09:33:17 +00:00
|
|
|
CompileRun("js_entry_sp();");
|
2015-01-30 09:29:25 +00:00
|
|
|
CHECK(!i::TraceExtension::GetJsEntrySp());
|
2009-06-02 09:33:17 +00:00
|
|
|
CompileRun("js_entry_sp_level2();");
|
2015-01-30 09:29:25 +00:00
|
|
|
CHECK(!i::TraceExtension::GetJsEntrySp());
|
2009-06-02 09:33:17 +00:00
|
|
|
}
|
2017-08-11 11:22:28 +00:00
|
|
|
|
|
|
|
} // namespace internal
|
|
|
|
} // namespace v8
|