text: // Copyright 2011 the V8 project authors. All rights reserved.
  // 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.
  //
  // Tests of profiler-related functions from log.h
  
  #include <stdlib.h>
  
  #include "include/v8-profiler.h"
  #include "src/api-inl.h"
  #include "src/disassembler.h"
  #include "src/isolate.h"
  #include "src/objects-inl.h"
  #include "src/v8.h"
  #include "src/vm-state-inl.h"
  #include "test/cctest/cctest.h"
  #include "test/cctest/trace-extension.h"
  
  namespace v8 {
  namespace internal {
  
  static bool IsAddressWithinFuncCode(JSFunction function, void* addr) {
    i::AbstractCode code = function->abstract_code();
    return code->contains(reinterpret_cast<Address>(addr));
  }
  
  static bool IsAddressWithinFuncCode(v8::Local<v8::Context> context,
                                      const char* func_name, void* addr) {
    v8::Local<v8::Value> func =
        context->Global()->Get(context, v8_str(func_name)).ToLocalChecked();
    CHECK(func->IsFunction());
    JSFunction js_func = JSFunction::cast(*v8::Utils::OpenHandle(*func));
    return IsAddressWithinFuncCode(js_func, addr);
  }
  
  
  // 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.
  static void construct_call(const v8::FunctionCallbackInfo<v8::Value>& args) {
    i::Isolate* isolate = reinterpret_cast<i::Isolate*>(args.GetIsolate());
    i::StackFrameIterator frame_iterator(isolate);
    CHECK(frame_iterator.frame()->is_exit() ||
          frame_iterator.frame()->is_builtin_exit());
    frame_iterator.Advance();
    CHECK(frame_iterator.frame()->is_construct());
    frame_iterator.Advance();
    if (frame_iterator.frame()->type() == i::StackFrame::STUB) {
      // Skip over bytecode handler frame.
      frame_iterator.Advance();
    }
    i::StackFrame* calling_frame = frame_iterator.frame();
    CHECK(calling_frame->is_java_script());
  
    v8::Local<v8::Context> context = args.GetIsolate()->GetCurrentContext();
  #if defined(V8_HOST_ARCH_32_BIT)
    int32_t low_bits = static_cast<int32_t>(calling_frame->fp());
    args.This()
        ->Set(context, v8_str("low_bits"), v8_num(low_bits >> 1))
        .FromJust();
  #elif defined(V8_HOST_ARCH_64_BIT)
    Address fp = calling_frame->fp();
    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();
  #else
  #error Host architecture is neither 32-bit nor 64-bit.
  #endif
    args.GetReturnValue().Set(args.This());
  }
  
  
  // Use the API to create a JSFunction object that calls the above C++ function.
  void CreateFramePointerGrabberConstructor(v8::Local<v8::Context> context,
                                            const char* constructor_name) {
      Local<v8::FunctionTemplate> constructor_template =
          v8::FunctionTemplate::New(context->GetIsolate(), construct_call);
      constructor_template->SetClassName(v8_str("FPGrabber"));
      Local<Function> fun =
          constructor_template->GetFunction(context).ToLocalChecked();
      context->Global()->Set(context, v8_str(constructor_name), fun).FromJust();
  }
  
  
  // Creates a global function named 'func_name' that calls the tracing
  // function 'trace_func_name' with an actual EBP register value,
  // encoded as one or two Smis.
  static void CreateTraceCallerFunction(v8::Local<v8::Context> context,
                                        const char* func_name,
                                        const char* trace_func_name) {
    i::EmbeddedVector<char, 256> trace_call_buf;
    i::SNPrintF(trace_call_buf,
                "function %s() {"
                "  fp = new FPGrabber();"
                "  %s(fp.low_bits, fp.high_bits);"
                "}",
                func_name, trace_func_name);
  
    // Create the FPGrabber function, which grabs the caller's frame pointer
    // when called as a constructor.
    CreateFramePointerGrabberConstructor(context, "FPGrabber");
  
    // Compile the script.
    CompileRun(trace_call_buf.start());
  }
  
  
  // This test verifies that stack tracing works when called during
  // execution of a native function called from JS code. In this case,
  // TickSample::Trace uses Isolate::c_entry_fp as a starting point for stack
  // walking.
  TEST(CFromJSStackTrace) {
    // BUG(1303) Inlining of JSFuncDoTrace() in JSTrace below breaks this test.
    i::FLAG_turbo_inlining = false;
  
    TickSample sample;
    i::TraceExtension::InitTraceEnv(&sample);
  
    v8::HandleScope scope(CcTest::isolate());
    v8::Local<v8::Context> context = CcTest::NewContext({TRACE_EXTENSION_ID});
    v8::Context::Scope context_scope(context);
  
    // Create global function JSFuncDoTrace which calls
    // extension function trace() with the current frame pointer value.
    CreateTraceCallerFunction(context, "JSFuncDoTrace", "trace");
    Local<Value> result = CompileRun(
        "function JSTrace() {"
        "         JSFuncDoTrace();"
        "};\n"
        "JSTrace();\n"
        "true;");
    CHECK(!result.IsEmpty());
    // 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]
    //       trace(EBP) [native (extension)]
    //         DoTrace(EBP) [native]
    //           TickSample::Trace
  
    CHECK(sample.has_external_callback);
    CHECK_EQ(FUNCTION_ADDR(i::TraceExtension::Trace),
             reinterpret_cast<Address>(sample.external_callback_entry));
  
    // Stack tracing will start from the first JS function, i.e. "JSFuncDoTrace"
    unsigned base = 0;
    CHECK_GT(sample.frames_count, base + 1);
  
    CHECK(IsAddressWithinFuncCode(
        context, "JSFuncDoTrace", sample.stack[base + 0]));
    CHECK(IsAddressWithinFuncCode(context, "JSTrace", sample.stack[base + 1]));
  }
  
  
  // This test verifies that stack tracing works when called during
  // execution of JS code. However, as calling TickSample::Trace requires
  // entering native code, we can only emulate pure JS by erasing
  // Isolate::c_entry_fp value. In this case, TickSample::Trace uses passed frame
  // pointer value as a starting point for stack walking.
  TEST(PureJSStackTrace) {
    // This test does not pass with inlining enabled since inlined functions
    // don't appear in the stack trace.
    i::FLAG_turbo_inlining = false;
  
    TickSample sample;
    i::TraceExtension::InitTraceEnv(&sample);
  
    v8::HandleScope scope(CcTest::isolate());
    v8::Local<v8::Context> context = CcTest::NewContext({TRACE_EXTENSION_ID});
    v8::Context::Scope context_scope(context);
  
    // Create global function JSFuncDoTrace which calls
    // extension function js_trace() with the current frame pointer value.
    CreateTraceCallerFunction(context, "JSFuncDoTrace", "js_trace");
    Local<Value> result = CompileRun(
        "function JSTrace() {"
        "         JSFuncDoTrace();"
        "};\n"
        "function OuterJSTrace() {"
        "         JSTrace();"
        "};\n"
        "OuterJSTrace();\n"
        "true;");
    CHECK(!result.IsEmpty());
    // When stack tracer is invoked, the stack should look as follows:
    // script [JS]
    //   OuterJSTrace() [JS]
    //     JSTrace() [JS]
    //       JSFuncDoTrace() [JS]
    //         js_trace(EBP) [native (extension)]
    //           DoTraceHideCEntryFPAddress(EBP) [native]
    //             TickSample::Trace
    //
  
    CHECK(sample.has_external_callback);
    CHECK_EQ(FUNCTION_ADDR(i::TraceExtension::JSTrace),
             reinterpret_cast<Address>(sample.external_callback_entry));
  
    // Stack sampling will start from the caller of JSFuncDoTrace, i.e. "JSTrace"
    unsigned base = 0;
    CHECK_GT(sample.frames_count, base + 1);
    CHECK(IsAddressWithinFuncCode(context, "JSTrace", sample.stack[base + 0]));
    CHECK(IsAddressWithinFuncCode(
        context, "OuterJSTrace", sample.stack[base + 1]));
  }
  
  static void CFuncDoTrace(byte dummy_param) {
    Address fp;
  #if V8_HAS_BUILTIN_FRAME_ADDRESS
    fp = reinterpret_cast<Address>(__builtin_frame_address(0));
  #elif V8_CC_MSVC
    // Approximate a frame pointer address. We compile without base pointers,
    // so we can't trust ebp/rbp.
    fp = reinterpret_cast<Address>(&dummy_param) - 2 * sizeof(void*);  // NOLINT
  #else
  #error Unexpected platform.
  #endif
    i::TraceExtension::DoTrace(fp);
  }
  
  
  static int CFunc(int depth) {
    if (depth <= 0) {
      CFuncDoTrace(0);
      return 0;
    } else {
      return CFunc(depth - 1) + 1;
    }
  }
  
  
  // This test verifies that stack tracing doesn't crash when called on
  // pure native code. TickSample::Trace only unrolls JS code, so we can't
  // get any meaningful info here.
  TEST(PureCStackTrace) {
    TickSample sample;
    i::TraceExtension::InitTraceEnv(&sample);
    v8::HandleScope scope(CcTest::isolate());
    v8::Local<v8::Context> context = CcTest::NewContext({TRACE_EXTENSION_ID});
    v8::Context::Scope context_scope(context);
    // Check that sampler doesn't crash
    CHECK_EQ(10, CFunc(10));
  }
  
  
  TEST(JsEntrySp) {
    v8::HandleScope scope(CcTest::isolate());
    v8::Local<v8::Context> context = CcTest::NewContext({TRACE_EXTENSION_ID});
    v8::Context::Scope context_scope(context);
    CHECK(!i::TraceExtension::GetJsEntrySp());
    CompileRun("a = 1; b = a + 1;");
    CHECK(!i::TraceExtension::GetJsEntrySp());
    CompileRun("js_entry_sp();");
    CHECK(!i::TraceExtension::GetJsEntrySp());
    CompileRun("js_entry_sp_level2();");
    CHECK(!i::TraceExtension::GetJsEntrySp());
  }
  
  }  // namespace internal
  }  // namespace v8
  