Introduce v8::StackTrace::CurrentScriptNameOrSourceURL

This CL introduces a dedicated API to retrieve the current (w.r.t. the
JS stack) script name or sourceURL. Currently, API clients will
collect multiple stack traces in increasing sizes to accomplish the
same goal. The new method walks the JS stack in the same way as the
stack trace collection mechanic but doesn't create/allocate stack info
or callsite objects along the way.

R=bmeurer@chromium.org, yangguo@chromium.org

Doc: https://bit.ly/v8-current-script-name
Bug: chromium:1286677
Change-Id: Id53e4f04bf17349d34f3d581bc712b1f4aa055db
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3382818
Reviewed-by: Benedikt Meurer <bmeurer@chromium.org>
Commit-Queue: Simon Zünd <szuend@chromium.org>
Cr-Commit-Position: refs/heads/main@{#78645}
This commit is contained in:
Simon Zünd 2022-01-12 12:43:40 +01:00 committed by V8 LUCI CQ
parent bd1cc7b009
commit 44a8a7d685
5 changed files with 120 additions and 0 deletions

View File

@ -149,6 +149,18 @@ class V8_EXPORT StackTrace {
*/
static Local<StackTrace> CurrentStackTrace(
Isolate* isolate, int frame_limit, StackTraceOptions options = kDetailed);
/**
* Returns the first valid script name or source URL starting at the top of
* the JS stack. The returned string is either an empty handle if no script
* name/url was found or a non-zero-length string.
*
* This method is equivalent to calling StackTrace::CurrentStackTrace and
* walking the resulting frames from the beginning until a non-empty script
* name/url is found. The difference is that this method won't allocate
* a stack trace.
*/
static Local<String> CurrentScriptNameOrSourceURL(Isolate* isolate);
};
} // namespace v8

View File

@ -3248,6 +3248,14 @@ Local<StackTrace> StackTrace::CurrentStackTrace(Isolate* isolate,
return Utils::StackTraceToLocal(stackTrace);
}
Local<String> StackTrace::CurrentScriptNameOrSourceURL(Isolate* v8_isolate) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
ENTER_V8_NO_SCRIPT_NO_EXCEPTION(isolate);
i::Handle<i::String> name_or_source_url =
isolate->CurrentScriptNameOrSourceURL();
return Utils::ToLocal(name_or_source_url);
}
// --- S t a c k F r a m e ---
Location StackFrame::GetLocation() const {

View File

@ -1338,6 +1338,47 @@ Handle<FixedArray> Isolate::CaptureDetailedStackTrace(
return stack_trace;
}
namespace {
class CurrentScriptNameStackVisitor {
public:
explicit CurrentScriptNameStackVisitor(Isolate* isolate)
: isolate_(isolate) {}
bool Visit(FrameSummary& summary) {
// Skip frames that aren't subject to debugging. Keep this in sync with
// StackFrameBuilder::Visit so both visitors visit the same frames.
if (!summary.is_subject_to_debugging()) return true;
// Frames that are subject to debugging always have a valid script object.
Handle<Script> script = Handle<Script>::cast(summary.script());
Handle<Object> name_or_url_obj =
handle(script->GetNameOrSourceURL(), isolate_);
if (!name_or_url_obj->IsString()) return true;
Handle<String> name_or_url = Handle<String>::cast(name_or_url_obj);
if (!name_or_url->length()) return true;
name_or_url_ = name_or_url;
return false;
}
Handle<String> CurrentScriptNameOrSourceURL() const { return name_or_url_; }
private:
Isolate* const isolate_;
Handle<String> name_or_url_;
};
} // namespace
Handle<String> Isolate::CurrentScriptNameOrSourceURL() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.stack_trace"), __func__);
CurrentScriptNameStackVisitor visitor(this);
VisitStack(this, &visitor);
return visitor.CurrentScriptNameOrSourceURL();
}
void Isolate::PrintStack(FILE* out, PrintStackMode mode) {
if (stack_trace_nesting_level_ == 0) {
stack_trace_nesting_level_++;

View File

@ -905,6 +905,10 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
Handle<Object> caller);
Handle<FixedArray> GetDetailedStackTrace(Handle<JSReceiver> error_object);
Handle<FixedArray> GetSimpleStackTrace(Handle<JSReceiver> error_object);
// Walks the JS stack to find the first frame with a script name or
// source URL. The inspected frames are the same as for the detailed stack
// trace.
Handle<String> CurrentScriptNameOrSourceURL();
Address GetAbstractPC(int* line, int* column);

View File

@ -887,3 +887,58 @@ UNINITIALIZED_TEST(CaptureStackTraceForStackOverflow) {
isolate->Exit();
isolate->Dispose();
}
void AnalyzeScriptNameInStack(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::HandleScope scope(args.GetIsolate());
v8::Local<v8::String> name =
v8::StackTrace::CurrentScriptNameOrSourceURL(args.GetIsolate());
CHECK(!name.IsEmpty());
CHECK(name->StringEquals(v8_str("test.js")));
}
TEST(CurrentScriptNameOrSourceURL_Name) {
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
templ->Set(
isolate, "AnalyzeScriptNameInStack",
v8::FunctionTemplate::New(CcTest::isolate(), AnalyzeScriptNameInStack));
LocalContext context(nullptr, templ);
const char* source = R"(
function foo() {
AnalyzeScriptNameInStack();
}
foo();
)";
CHECK(CompileRunWithOrigin(source, "test.js")->IsUndefined());
}
void AnalyzeScriptURLInStack(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::HandleScope scope(args.GetIsolate());
v8::Local<v8::String> name =
v8::StackTrace::CurrentScriptNameOrSourceURL(args.GetIsolate());
CHECK(!name.IsEmpty());
CHECK(name->StringEquals(v8_str("foo.js")));
}
TEST(CurrentScriptNameOrSourceURL_SourceURL) {
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
templ->Set(
isolate, "AnalyzeScriptURLInStack",
v8::FunctionTemplate::New(CcTest::isolate(), AnalyzeScriptURLInStack));
LocalContext context(nullptr, templ);
const char* source = R"(
function foo() {
AnalyzeScriptURLInStack();
}
foo();
//# sourceURL=foo.js
)";
CHECK(CompileRunWithOrigin(source, "")->IsUndefined());
}