// Copyright 2015 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/debug/debug-scopes.h" #include #include "src/ast/ast.h" #include "src/ast/scopes.h" #include "src/common/globals.h" #include "src/debug/debug.h" #include "src/execution/frames-inl.h" #include "src/execution/isolate-inl.h" #include "src/objects/js-generator-inl.h" #include "src/objects/source-text-module.h" #include "src/objects/string-set-inl.h" #include "src/parsing/parse-info.h" #include "src/parsing/parsing.h" #include "src/parsing/rewriter.h" #include "src/utils/ostreams.h" namespace v8 { namespace internal { ScopeIterator::ScopeIterator(Isolate* isolate, FrameInspector* frame_inspector, ReparseStrategy strategy) : isolate_(isolate), frame_inspector_(frame_inspector), function_(frame_inspector_->GetFunction()), script_(frame_inspector_->GetScript()) { if (!frame_inspector->GetContext()->IsContext()) { // Optimized frame, context or function cannot be materialized. Give up. return; } context_ = Handle::cast(frame_inspector->GetContext()); #if V8_ENABLE_WEBASSEMBLY // We should not instantiate a ScopeIterator for wasm frames. DCHECK_NE(Script::TYPE_WASM, frame_inspector->GetScript()->type()); #endif // V8_ENABLE_WEBASSEMBLY TryParseAndRetrieveScopes(strategy); } ScopeIterator::~ScopeIterator() = default; Handle ScopeIterator::GetFunctionDebugName() const { if (!function_.is_null()) return JSFunction::GetDebugName(function_); if (!context_->IsNativeContext()) { DisallowGarbageCollection no_gc; ScopeInfo closure_info = context_->closure_context().scope_info(); Handle debug_name(closure_info.FunctionDebugName(), isolate_); if (debug_name->length() > 0) return debug_name; } return isolate_->factory()->undefined_value(); } ScopeIterator::ScopeIterator(Isolate* isolate, Handle function) : isolate_(isolate), context_(function->context(), isolate) { if (!function->shared().IsSubjectToDebugging()) { context_ = Handle(); return; } script_ = handle(Script::cast(function->shared().script()), isolate); UnwrapEvaluationContext(); } ScopeIterator::ScopeIterator(Isolate* isolate, Handle generator) : isolate_(isolate), generator_(generator), function_(generator->function(), isolate), context_(generator->context(), isolate), script_(Script::cast(function_->shared().script()), isolate) { CHECK(function_->shared().IsSubjectToDebugging()); TryParseAndRetrieveScopes(ReparseStrategy::kFunctionLiteral); } void ScopeIterator::Restart() { DCHECK_NOT_NULL(frame_inspector_); function_ = frame_inspector_->GetFunction(); context_ = Handle::cast(frame_inspector_->GetContext()); current_scope_ = start_scope_; DCHECK_NOT_NULL(current_scope_); UnwrapEvaluationContext(); } namespace { // Takes the scope of a parsed script, a function and a break location // inside the function. The result is the innermost lexical scope around // the break point, which serves as the starting point of the ScopeIterator. // And the scope of the function that was passed in (called closure scope). // // The start scope is guaranteed to be either the closure scope itself, // or a child of the closure scope. class ScopeChainRetriever { public: ScopeChainRetriever(DeclarationScope* scope, Handle function, int position) : scope_(scope), break_scope_start_(function->shared().StartPosition()), break_scope_end_(function->shared().EndPosition()), is_default_constructor_( IsDefaultConstructor(function->shared().kind())), position_(position) { DCHECK_NOT_NULL(scope); RetrieveScopes(); } DeclarationScope* ClosureScope() { return closure_scope_; } Scope* StartScope() { return start_scope_; } private: DeclarationScope* scope_; const int break_scope_start_; const int break_scope_end_; const bool is_default_constructor_; const int position_; DeclarationScope* closure_scope_ = nullptr; Scope* start_scope_ = nullptr; void RetrieveScopes() { if (is_default_constructor_) { // Even though the DefaultBaseConstructor is a child of a Class scope, the // source positions are *not* nested. This means the actual scope for the // DefaultBaseConstructor needs to be found by doing a DFS. RetrieveScopeChainDefaultConstructor(scope_); } else { RetrieveScopeChain(); } DCHECK_NOT_NULL(closure_scope_); DCHECK_NOT_NULL(start_scope_); } bool RetrieveScopeChainDefaultConstructor(Scope* scope) { const int beg_pos = scope->start_position(); const int end_pos = scope->end_position(); if (beg_pos == position_ && end_pos == position_) { DCHECK(scope->is_function_scope()); DCHECK( IsDefaultConstructor(scope->AsDeclarationScope()->function_kind())); start_scope_ = scope; closure_scope_ = scope->AsDeclarationScope(); return true; } for (Scope* inner_scope = scope->inner_scope(); inner_scope != nullptr; inner_scope = inner_scope->sibling()) { if (RetrieveScopeChainDefaultConstructor(inner_scope)) return true; } return false; } void RetrieveScopeChain() { Scope* parent = nullptr; Scope* current = scope_; SetClosureScopeIfFound(current); while (parent != current) { parent = current; for (Scope* inner_scope = current->inner_scope(); inner_scope != nullptr; inner_scope = inner_scope->sibling()) { if (SetClosureScopeIfFound(inner_scope) || ContainsPosition(inner_scope)) { current = inner_scope; break; } } } start_scope_ = current; } bool SetClosureScopeIfFound(Scope* scope) { const int start = scope->start_position(); const int end = scope->end_position(); if (start == break_scope_start_ && end == break_scope_end_) { closure_scope_ = scope->AsDeclarationScope(); return true; } return false; } bool ContainsPosition(Scope* scope) { const int start = scope->start_position(); const int end = scope->end_position(); // In case the closure_scope_ hasn't been found yet, we are less strict // about recursing downwards. This might be the case for nested arrow // functions that have the same end position. const bool position_fits_end = closure_scope_ ? position_ < end : position_ <= end; // While we're evaluating a class, the calling function will have a class // context on the stack with a range that starts at Token::CLASS, and the // source position will also point to Token::CLASS. To identify the // matching scope we include start in the accepted range for class scopes. const bool position_fits_start = scope->is_class_scope() ? start <= position_ : start < position_; return position_fits_start && position_fits_end; } }; } // namespace void ScopeIterator::TryParseAndRetrieveScopes(ReparseStrategy strategy) { // Catch the case when the debugger stops in an internal function. Handle shared_info(function_->shared(), isolate_); Handle scope_info(shared_info->scope_info(), isolate_); if (shared_info->script().IsUndefined(isolate_)) { current_scope_ = closure_scope_ = nullptr; context_ = handle(function_->context(), isolate_); function_ = Handle(); return; } // Class fields initializer functions don't have any scope // information. We short circuit the parsing of the class literal // and return an empty context here. if (IsClassMembersInitializerFunction(shared_info->kind())) { current_scope_ = closure_scope_ = nullptr; context_ = Handle(); function_ = Handle(); return; } bool ignore_nested_scopes = false; if (shared_info->HasBreakInfo() && frame_inspector_ != nullptr) { // The source position at return is always the end of the function, // which is not consistent with the current scope chain. Therefore all // nested with, catch and block contexts are skipped, and we can only // inspect the function scope. // This can only happen if we set a break point inside right before the // return, which requires a debug info to be available. Handle debug_info(shared_info->GetDebugInfo(), isolate_); // Find the break point where execution has stopped. BreakLocation location = BreakLocation::FromFrame(debug_info, GetFrame()); ignore_nested_scopes = location.IsReturn(); } // Reparse the code and analyze the scopes. // Depending on the choosen strategy, the whole script or just // the closure is re-parsed for function scopes. Handle