| // 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 <memory> |
| |
| #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<Context>::cast(frame_inspector->GetContext()); |
| |
| // We should not instantiate a ScopeIterator for wasm frames. |
| DCHECK_NE(Script::TYPE_WASM, frame_inspector->GetScript()->type()); |
| |
| TryParseAndRetrieveScopes(strategy); |
| } |
| |
| ScopeIterator::~ScopeIterator() = default; |
| |
| Handle<Object> ScopeIterator::GetFunctionDebugName() const { |
| if (!function_.is_null()) return JSFunction::GetDebugName(function_); |
| |
| if (!context_->IsNativeContext()) { |
| DisallowHeapAllocation no_gc; |
| ScopeInfo closure_info = context_->closure_context().scope_info(); |
| Handle<String> debug_name(closure_info.FunctionDebugName(), isolate_); |
| if (debug_name->length() > 0) return debug_name; |
| } |
| return isolate_->factory()->undefined_value(); |
| } |
| |
| ScopeIterator::ScopeIterator(Isolate* isolate, Handle<JSFunction> function) |
| : isolate_(isolate), context_(function->context(), isolate) { |
| if (!function->shared().IsSubjectToDebugging()) { |
| context_ = Handle<Context>(); |
| return; |
| } |
| script_ = handle(Script::cast(function->shared().script()), isolate); |
| UnwrapEvaluationContext(); |
| } |
| |
| ScopeIterator::ScopeIterator(Isolate* isolate, |
| Handle<JSGeneratorObject> 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<Context>::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<JSFunction> 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; |
| return start < position_ && position_fits_end; |
| } |
| }; |
| |
| } // namespace |
| |
| void ScopeIterator::TryParseAndRetrieveScopes(ReparseStrategy strategy) { |
| // Catch the case when the debugger stops in an internal function. |
| Handle<SharedFunctionInfo> shared_info(function_->shared(), isolate_); |
| Handle<ScopeInfo> 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<JSFunction>(); |
| 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<Context>(); |
| function_ = Handle<JSFunction>(); |
| 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<DebugInfo> 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<Script> script(Script::cast(shared_info->script()), isolate_); |
| |
| // Pick between flags for a single function compilation, or an eager |
| // compilation of the whole script. |
| UnoptimizedCompileFlags flags = |
| (scope_info->scope_type() == FUNCTION_SCOPE && |
| strategy == ReparseStrategy::kFunctionLiteral) |
| ? UnoptimizedCompileFlags::ForFunctionCompile(isolate_, *shared_info) |
| : UnoptimizedCompileFlags::ForScriptCompile(isolate_, *script) |
| .set_is_eager(true); |
| |
| MaybeHandle<ScopeInfo> maybe_outer_scope; |
| if (scope_info->scope_type() == EVAL_SCOPE || script->is_wrapped()) { |
| flags.set_is_eval(true); |
| if (!context_->IsNativeContext()) { |
| maybe_outer_scope = handle(context_->scope_info(), isolate_); |
| } |
| // Language mode may be inherited from the eval caller. |
| // Retrieve it from shared function info. |
| flags.set_outer_language_mode(shared_info->language_mode()); |
| } else if (scope_info->scope_type() == MODULE_SCOPE) { |
| DCHECK(flags.is_module()); |
| } else { |
| DCHECK(scope_info->scope_type() == SCRIPT_SCOPE || |
| scope_info->scope_type() == FUNCTION_SCOPE); |
| } |
| |
| UnoptimizedCompileState compile_state(isolate_); |
| |
| info_ = std::make_unique<ParseInfo>(isolate_, flags, &compile_state); |
| |
| const bool parse_result = |
| flags.is_toplevel() |
| ? parsing::ParseProgram(info_.get(), script, maybe_outer_scope, |
| isolate_, parsing::ReportStatisticsMode::kNo) |
| : parsing::ParseFunction(info_.get(), shared_info, isolate_, |
| parsing::ReportStatisticsMode::kNo); |
| |
| if (parse_result) { |
| DeclarationScope* literal_scope = info_->literal()->scope(); |
| |
| ScopeChainRetriever scope_chain_retriever(literal_scope, function_, |
| GetSourcePosition()); |
| start_scope_ = scope_chain_retriever.StartScope(); |
| current_scope_ = start_scope_; |
| |
| // In case of a FUNCTION_SCOPE, the ScopeIterator expects |
| // {closure_scope_} to be set to the scope of the function. |
| closure_scope_ = scope_info->scope_type() == FUNCTION_SCOPE |
| ? scope_chain_retriever.ClosureScope() |
| : literal_scope; |
| |
| if (ignore_nested_scopes) { |
| current_scope_ = closure_scope_; |
| start_scope_ = current_scope_; |
| // ignore_nested_scopes is only used for the return-position breakpoint, |
| // so we can safely assume that the closure context for the current |
| // function exists if it needs one. |
| if (closure_scope_->NeedsContext()) { |
| context_ = handle(context_->closure_context(), isolate_); |
| } |
| } |
| |
| UnwrapEvaluationContext(); |
| } else { |
| // A failed reparse indicates that the preparser has diverged from the |
| // parser, that the preparse data given to the initial parse was faulty, or |
| // a stack overflow. |
| // TODO(leszeks): This error is pretty unexpected, so we could report the |
| // error in debug mode. Better to not fail in release though, in case it's |
| // just a stack overflow. |
| |
| // Silently fail by presenting an empty context chain. |
| context_ = Handle<Context>(); |
| } |
| } |
| |
| void ScopeIterator::UnwrapEvaluationContext() { |
| if (!context_->IsDebugEvaluateContext()) return; |
| Context current = *context_; |
| do { |
| Object wrapped = current.get(Context::WRAPPED_CONTEXT_INDEX); |
| if (wrapped.IsContext()) { |
| current = Context::cast(wrapped); |
| } else { |
| DCHECK(!current.previous().is_null()); |
| current = current.previous(); |
| } |
| } while (current.IsDebugEvaluateContext()); |
| context_ = handle(current, isolate_); |
| } |
| |
| Handle<JSObject> ScopeIterator::MaterializeScopeDetails() { |
| // Calculate the size of the result. |
| Handle<FixedArray> details = |
| isolate_->factory()->NewFixedArray(kScopeDetailsSize); |
| // Fill in scope details. |
| details->set(kScopeDetailsTypeIndex, Smi::FromInt(Type())); |
| Handle<JSObject> scope_object = ScopeObject(Mode::ALL); |
| details->set(kScopeDetailsObjectIndex, *scope_object); |
| if (Type() == ScopeTypeGlobal || Type() == ScopeTypeScript) { |
| return isolate_->factory()->NewJSArrayWithElements(details); |
| } else if (HasContext()) { |
| Handle<Object> closure_name = GetFunctionDebugName(); |
| details->set(kScopeDetailsNameIndex, *closure_name); |
| details->set(kScopeDetailsStartPositionIndex, |
| Smi::FromInt(start_position())); |
| details->set(kScopeDetailsEndPositionIndex, Smi::FromInt(end_position())); |
| if (InInnerScope()) { |
| details->set(kScopeDetailsFunctionIndex, *function_); |
| } |
| } |
| return isolate_->factory()->NewJSArrayWithElements(details); |
| } |
| |
| bool ScopeIterator::HasPositionInfo() { |
| return InInnerScope() || !context_->IsNativeContext(); |
| } |
| |
| int ScopeIterator::start_position() { |
| if (InInnerScope()) return current_scope_->start_position(); |
| if (context_->IsNativeContext()) return 0; |
| return context_->closure_context().scope_info().StartPosition(); |
| } |
| |
| int ScopeIterator::end_position() { |
| if (InInnerScope()) return current_scope_->end_position(); |
| if (context_->IsNativeContext()) return 0; |
| return context_->closure_context().scope_info().EndPosition(); |
| } |
| |
| bool ScopeIterator::DeclaresLocals(Mode mode) const { |
| ScopeType type = Type(); |
| |
| if (type == ScopeTypeWith) return mode == Mode::ALL; |
| if (type == ScopeTypeGlobal) return mode == Mode::ALL; |
| |
| bool declares_local = false; |
| auto visitor = [&](Handle<String> name, Handle<Object> value, |
| ScopeType scope_type) { |
| declares_local = true; |
| return true; |
| }; |
| VisitScope(visitor, mode); |
| return declares_local; |
| } |
| |
| bool ScopeIterator::HasContext() const { |
| return !InInnerScope() || NeedsAndHasContext(); |
| } |
| |
| bool ScopeIterator::NeedsAndHasContext() const { |
| if (!current_scope_->NeedsContext()) return false; |
| // Generally, if a scope needs a context, then we can assume that it has a |
| // context. However, the stack check during function entry happens before the |
| // function has a chance to create and push its own context, so we must check |
| // for the case where the function is executing in its parent context. This |
| // case is only possible in function scopes; top-level code (modules and |
| // non-module scripts) begin execution in the context they need and don't have |
| // a separate step to push the correct context. |
| return !(current_scope_ == closure_scope_ && |
| current_scope_->is_function_scope() && !function_.is_null() && |
| function_->context() == *context_); |
| } |
| |
| void ScopeIterator::AdvanceOneScope() { |
| if (NeedsAndHasContext()) { |
| DCHECK(!context_->previous().is_null()); |
| context_ = handle(context_->previous(), isolate_); |
| } |
| DCHECK(current_scope_->outer_scope() != nullptr); |
| current_scope_ = current_scope_->outer_scope(); |
| } |
| |
| void ScopeIterator::AdvanceToNonHiddenScope() { |
| do { |
| AdvanceOneScope(); |
| } while (current_scope_->is_hidden()); |
| } |
| |
| void ScopeIterator::AdvanceContext() { |
| DCHECK(!context_->IsNativeContext()); |
| context_ = handle(context_->previous(), isolate_); |
| |
| // While advancing one context, we need to advance at least one |
| // scope, but until we hit the next scope that actually requires |
| // a context. All the locals collected along the way build the |
| // blocklist for debug-evaluate for this context. |
| locals_ = StringSet::New(isolate_); |
| do { |
| if (!current_scope_ || !current_scope_->outer_scope()) break; |
| |
| current_scope_ = current_scope_->outer_scope(); |
| CollectLocalsFromCurrentScope(); |
| } while (!NeedsAndHasContext()); |
| } |
| |
| void ScopeIterator::Next() { |
| DCHECK(!Done()); |
| |
| ScopeType scope_type = Type(); |
| |
| if (scope_type == ScopeTypeGlobal) { |
| // The global scope is always the last in the chain. |
| DCHECK(context_->IsNativeContext()); |
| context_ = Handle<Context>(); |
| DCHECK(Done()); |
| return; |
| } |
| |
| bool leaving_closure = current_scope_ == closure_scope_; |
| |
| if (scope_type == ScopeTypeScript) { |
| DCHECK_IMPLIES(InInnerScope() && !leaving_closure, |
| current_scope_->is_script_scope()); |
| seen_script_scope_ = true; |
| if (context_->IsScriptContext()) { |
| context_ = handle(context_->previous(), isolate_); |
| } |
| } else if (!InInnerScope()) { |
| AdvanceContext(); |
| } else { |
| DCHECK_NOT_NULL(current_scope_); |
| AdvanceToNonHiddenScope(); |
| |
| if (leaving_closure) { |
| DCHECK(current_scope_ != closure_scope_); |
| // Edge case when we just go past {closure_scope_}. This case |
| // already needs to start collecting locals for the blocklist. |
| locals_ = StringSet::New(isolate_); |
| CollectLocalsFromCurrentScope(); |
| } |
| } |
| |
| if (leaving_closure) function_ = Handle<JSFunction>(); |
| |
| UnwrapEvaluationContext(); |
| } |
| |
| // Return the type of the current scope. |
| ScopeIterator::ScopeType ScopeIterator::Type() const { |
| DCHECK(!Done()); |
| if (InInnerScope()) { |
| switch (current_scope_->scope_type()) { |
| case FUNCTION_SCOPE: |
| DCHECK_IMPLIES(NeedsAndHasContext(), |
| context_->IsFunctionContext() || |
| context_->IsDebugEvaluateContext()); |
| return ScopeTypeLocal; |
| case MODULE_SCOPE: |
| DCHECK_IMPLIES(NeedsAndHasContext(), context_->IsModuleContext()); |
| return ScopeTypeModule; |
| case SCRIPT_SCOPE: |
| DCHECK_IMPLIES(NeedsAndHasContext(), context_->IsScriptContext() || |
| context_->IsNativeContext()); |
| return ScopeTypeScript; |
| case WITH_SCOPE: |
| DCHECK_IMPLIES(NeedsAndHasContext(), context_->IsWithContext()); |
| return ScopeTypeWith; |
| case CATCH_SCOPE: |
| DCHECK(context_->IsCatchContext()); |
| return ScopeTypeCatch; |
| case BLOCK_SCOPE: |
| case CLASS_SCOPE: |
| DCHECK_IMPLIES(NeedsAndHasContext(), context_->IsBlockContext()); |
| return ScopeTypeBlock; |
| case EVAL_SCOPE: |
| DCHECK_IMPLIES(NeedsAndHasContext(), context_->IsEvalContext()); |
| return ScopeTypeEval; |
| } |
| UNREACHABLE(); |
| } |
| if (context_->IsNativeContext()) { |
| DCHECK(context_->global_object().IsJSGlobalObject()); |
| // If we are at the native context and have not yet seen script scope, |
| // fake it. |
| return seen_script_scope_ ? ScopeTypeGlobal : ScopeTypeScript; |
| } |
| if (context_->IsFunctionContext() || context_->IsEvalContext() || |
| context_->IsDebugEvaluateContext()) { |
| return ScopeTypeClosure; |
| } |
| if (context_->IsCatchContext()) { |
| return ScopeTypeCatch; |
| } |
| if (context_->IsBlockContext()) { |
| return ScopeTypeBlock; |
| } |
| if (context_->IsModuleContext()) { |
| return ScopeTypeModule; |
| } |
| if (context_->IsScriptContext()) { |
| return ScopeTypeScript; |
| } |
| DCHECK(context_->IsWithContext()); |
| return ScopeTypeWith; |
| } |
| |
| Handle<JSObject> ScopeIterator::ScopeObject(Mode mode) { |
| DCHECK(!Done()); |
| |
| ScopeType type = Type(); |
| if (type == ScopeTypeGlobal) { |
| DCHECK_EQ(Mode::ALL, mode); |
| return handle(context_->global_proxy(), isolate_); |
| } |
| if (type == ScopeTypeWith) { |
| DCHECK_EQ(Mode::ALL, mode); |
| return WithContextExtension(); |
| } |
| |
| Handle<JSObject> scope = isolate_->factory()->NewJSObjectWithNullProto(); |
| auto visitor = [=](Handle<String> name, Handle<Object> value, |
| ScopeType scope_type) { |
| if (value->IsTheHole(isolate_)) { |
| // Reflect variables under TDZ as undefined in scope object. |
| if (scope_type == ScopeTypeScript && |
| JSReceiver::HasOwnProperty(scope, name).FromMaybe(true)) { |
| // We also use the hole to represent overridden let-declarations via |
| // REPL mode in a script context. Catch this case. |
| return false; |
| } |
| value = isolate_->factory()->undefined_value(); |
| } |
| JSObject::AddProperty(isolate_, scope, name, value, NONE); |
| return false; |
| }; |
| |
| VisitScope(visitor, mode); |
| return scope; |
| } |
| |
| void ScopeIterator::VisitScope(const Visitor& visitor, Mode mode) const { |
| switch (Type()) { |
| case ScopeTypeLocal: |
| case ScopeTypeClosure: |
| case ScopeTypeCatch: |
| case ScopeTypeBlock: |
| case ScopeTypeEval: |
| return VisitLocalScope(visitor, mode, Type()); |
| case ScopeTypeModule: |
| if (InInnerScope()) { |
| return VisitLocalScope(visitor, mode, Type()); |
| } |
| DCHECK_EQ(Mode::ALL, mode); |
| return VisitModuleScope(visitor); |
| case ScopeTypeScript: |
| DCHECK_EQ(Mode::ALL, mode); |
| return VisitScriptScope(visitor); |
| case ScopeTypeWith: |
| case ScopeTypeGlobal: |
| UNREACHABLE(); |
| } |
| } |
| |
| bool ScopeIterator::SetVariableValue(Handle<String> name, |
| Handle<Object> value) { |
| DCHECK(!Done()); |
| name = isolate_->factory()->InternalizeString(name); |
| switch (Type()) { |
| case ScopeTypeGlobal: |
| case ScopeTypeWith: |
| break; |
| |
| case ScopeTypeEval: |
| case ScopeTypeBlock: |
| case ScopeTypeCatch: |
| case ScopeTypeModule: |
| if (InInnerScope()) return SetLocalVariableValue(name, value); |
| if (Type() == ScopeTypeModule && SetModuleVariableValue(name, value)) { |
| return true; |
| } |
| return SetContextVariableValue(name, value); |
| |
| case ScopeTypeLocal: |
| case ScopeTypeClosure: |
| if (InInnerScope()) { |
| DCHECK_EQ(ScopeTypeLocal, Type()); |
| if (SetLocalVariableValue(name, value)) return true; |
| // There may not be an associated context since we're InInnerScope(). |
| if (!NeedsAndHasContext()) return false; |
| } else { |
| DCHECK_EQ(ScopeTypeClosure, Type()); |
| if (SetContextVariableValue(name, value)) return true; |
| } |
| // The above functions only set variables statically declared in the |
| // function. There may be eval-introduced variables. Check them in |
| // SetContextExtensionValue. |
| return SetContextExtensionValue(name, value); |
| |
| case ScopeTypeScript: |
| return SetScriptVariableValue(name, value); |
| } |
| return false; |
| } |
| |
| bool ScopeIterator::ClosureScopeHasThisReference() const { |
| return !closure_scope_->has_this_declaration() && |
| closure_scope_->HasThisReference(); |
| } |
| |
| void ScopeIterator::CollectLocalsFromCurrentScope() { |
| DCHECK(locals_->IsStringSet()); |
| for (Variable* var : *current_scope_->locals()) { |
| if (var->location() == VariableLocation::PARAMETER || |
| var->location() == VariableLocation::LOCAL) { |
| locals_ = StringSet::Add(isolate_, locals_, var->name()); |
| } |
| } |
| } |
| |
| #ifdef DEBUG |
| // Debug print of the content of the current scope. |
| void ScopeIterator::DebugPrint() { |
| StdoutStream os; |
| DCHECK(!Done()); |
| switch (Type()) { |
| case ScopeIterator::ScopeTypeGlobal: |
| os << "Global:\n"; |
| context_->Print(os); |
| break; |
| |
| case ScopeIterator::ScopeTypeLocal: { |
| os << "Local:\n"; |
| if (NeedsAndHasContext()) { |
| context_->Print(os); |
| if (context_->has_extension()) { |
| Handle<HeapObject> extension(context_->extension(), isolate_); |
| DCHECK(extension->IsJSContextExtensionObject()); |
| extension->Print(os); |
| } |
| } |
| break; |
| } |
| |
| case ScopeIterator::ScopeTypeWith: |
| os << "With:\n"; |
| context_->extension().Print(os); |
| break; |
| |
| case ScopeIterator::ScopeTypeCatch: |
| os << "Catch:\n"; |
| context_->extension().Print(os); |
| context_->get(Context::THROWN_OBJECT_INDEX).Print(os); |
| break; |
| |
| case ScopeIterator::ScopeTypeClosure: |
| os << "Closure:\n"; |
| context_->Print(os); |
| if (context_->has_extension()) { |
| Handle<HeapObject> extension(context_->extension(), isolate_); |
| DCHECK(extension->IsJSContextExtensionObject()); |
| extension->Print(os); |
| } |
| break; |
| |
| case ScopeIterator::ScopeTypeScript: |
| os << "Script:\n"; |
| context_->global_object().native_context().script_context_table().Print( |
| os); |
| break; |
| |
| default: |
| UNREACHABLE(); |
| } |
| PrintF("\n"); |
| } |
| #endif |
| |
| int ScopeIterator::GetSourcePosition() { |
| if (frame_inspector_) { |
| return frame_inspector_->GetSourcePosition(); |
| } else { |
| DCHECK(!generator_.is_null()); |
| SharedFunctionInfo::EnsureSourcePositionsAvailable( |
| isolate_, handle(generator_->function().shared(), isolate_)); |
| return generator_->source_position(); |
| } |
| } |
| |
| void ScopeIterator::VisitScriptScope(const Visitor& visitor) const { |
| Handle<JSGlobalObject> global(context_->global_object(), isolate_); |
| Handle<ScriptContextTable> script_contexts( |
| global->native_context().script_context_table(), isolate_); |
| |
| // Skip the first script since that just declares 'this'. |
| for (int context_index = 1; |
| context_index < script_contexts->synchronized_used(); context_index++) { |
| Handle<Context> context = ScriptContextTable::GetContext( |
| isolate_, script_contexts, context_index); |
| Handle<ScopeInfo> scope_info(context->scope_info(), isolate_); |
| if (VisitContextLocals(visitor, scope_info, context, ScopeTypeScript)) |
| return; |
| } |
| } |
| |
| void ScopeIterator::VisitModuleScope(const Visitor& visitor) const { |
| DCHECK(context_->IsModuleContext()); |
| |
| Handle<ScopeInfo> scope_info(context_->scope_info(), isolate_); |
| if (VisitContextLocals(visitor, scope_info, context_, ScopeTypeModule)) |
| return; |
| |
| int count_index = scope_info->ModuleVariableCountIndex(); |
| int module_variable_count = Smi::cast(scope_info->get(count_index)).value(); |
| |
| Handle<SourceTextModule> module(context_->module(), isolate_); |
| |
| for (int i = 0; i < module_variable_count; ++i) { |
| int index; |
| Handle<String> name; |
| { |
| String raw_name; |
| scope_info->ModuleVariable(i, &raw_name, &index); |
| if (ScopeInfo::VariableIsSynthetic(raw_name)) continue; |
| name = handle(raw_name, isolate_); |
| } |
| Handle<Object> value = |
| SourceTextModule::LoadVariable(isolate_, module, index); |
| |
| if (visitor(name, value, ScopeTypeModule)) return; |
| } |
| } |
| |
| bool ScopeIterator::VisitContextLocals(const Visitor& visitor, |
| Handle<ScopeInfo> scope_info, |
| Handle<Context> context, |
| ScopeType scope_type) const { |
| // Fill all context locals to the context extension. |
| for (int i = 0; i < scope_info->ContextLocalCount(); ++i) { |
| Handle<String> name(scope_info->ContextLocalName(i), isolate_); |
| if (ScopeInfo::VariableIsSynthetic(*name)) continue; |
| int context_index = scope_info->ContextHeaderLength() + i; |
| Handle<Object> value(context->get(context_index), isolate_); |
| if (visitor(name, value, scope_type)) return true; |
| } |
| return false; |
| } |
| |
| bool ScopeIterator::VisitLocals(const Visitor& visitor, Mode mode, |
| ScopeType scope_type) const { |
| if (mode == Mode::STACK && current_scope_->is_declaration_scope() && |
| current_scope_->AsDeclarationScope()->has_this_declaration()) { |
| // TODO(bmeurer): We should refactor the general variable lookup |
| // around "this", since the current way is rather hacky when the |
| // receiver is context-allocated. |
| auto this_var = current_scope_->AsDeclarationScope()->receiver(); |
| Handle<Object> receiver = |
| this_var->location() == VariableLocation::CONTEXT |
| ? handle(context_->get(this_var->index()), isolate_) |
| : frame_inspector_ == nullptr |
| ? handle(generator_->receiver(), isolate_) |
| : frame_inspector_->GetReceiver(); |
| if (receiver->IsOptimizedOut(isolate_)) { |
| receiver = isolate_->factory()->undefined_value(); |
| } |
| if (visitor(isolate_->factory()->this_string(), receiver, scope_type)) |
| return true; |
| } |
| |
| if (current_scope_->is_function_scope()) { |
| Variable* function_var = |
| current_scope_->AsDeclarationScope()->function_var(); |
| if (function_var != nullptr) { |
| Handle<JSFunction> function = frame_inspector_ == nullptr |
| ? function_ |
| : frame_inspector_->GetFunction(); |
| Handle<String> name = function_var->name(); |
| if (visitor(name, function, scope_type)) return true; |
| } |
| } |
| |
| for (Variable* var : *current_scope_->locals()) { |
| DCHECK(!var->is_this()); |
| if (ScopeInfo::VariableIsSynthetic(*var->name())) continue; |
| |
| int index = var->index(); |
| Handle<Object> value; |
| switch (var->location()) { |
| case VariableLocation::LOOKUP: |
| UNREACHABLE(); |
| break; |
| |
| case VariableLocation::REPL_GLOBAL: |
| // REPL declared variables are ignored for now. |
| case VariableLocation::UNALLOCATED: |
| continue; |
| |
| case VariableLocation::PARAMETER: { |
| if (frame_inspector_ == nullptr) { |
| // Get the variable from the suspended generator. |
| DCHECK(!generator_.is_null()); |
| FixedArray parameters_and_registers = |
| generator_->parameters_and_registers(); |
| DCHECK_LT(index, parameters_and_registers.length()); |
| value = handle(parameters_and_registers.get(index), isolate_); |
| } else { |
| value = frame_inspector_->GetParameter(index); |
| |
| if (value->IsOptimizedOut(isolate_)) { |
| value = isolate_->factory()->undefined_value(); |
| } |
| } |
| break; |
| } |
| |
| case VariableLocation::LOCAL: |
| if (frame_inspector_ == nullptr) { |
| // Get the variable from the suspended generator. |
| DCHECK(!generator_.is_null()); |
| FixedArray parameters_and_registers = |
| generator_->parameters_and_registers(); |
| int parameter_count = |
| function_->shared().scope_info().ParameterCount(); |
| index += parameter_count; |
| DCHECK_LT(index, parameters_and_registers.length()); |
| value = handle(parameters_and_registers.get(index), isolate_); |
| } else { |
| value = frame_inspector_->GetExpression(index); |
| if (value->IsOptimizedOut(isolate_)) { |
| // We'll rematerialize this later. |
| if (current_scope_->is_declaration_scope() && |
| current_scope_->AsDeclarationScope()->arguments() == var) { |
| continue; |
| } |
| value = isolate_->factory()->undefined_value(); |
| } |
| } |
| break; |
| |
| case VariableLocation::CONTEXT: |
| if (mode == Mode::STACK) continue; |
| DCHECK(var->IsContextSlot()); |
| value = handle(context_->get(index), isolate_); |
| break; |
| |
| case VariableLocation::MODULE: { |
| if (mode == Mode::STACK) continue; |
| // if (var->IsExport()) continue; |
| Handle<SourceTextModule> module(context_->module(), isolate_); |
| value = SourceTextModule::LoadVariable(isolate_, module, var->index()); |
| break; |
| } |
| } |
| |
| if (visitor(var->name(), value, scope_type)) return true; |
| } |
| return false; |
| } |
| |
| // Retrieve the with-context extension object. If the extension object is |
| // a proxy, return an empty object. |
| Handle<JSObject> ScopeIterator::WithContextExtension() { |
| DCHECK(context_->IsWithContext()); |
| if (context_->extension_receiver().IsJSProxy()) { |
| return isolate_->factory()->NewJSObjectWithNullProto(); |
| } |
| return handle(JSObject::cast(context_->extension_receiver()), isolate_); |
| } |
| |
| // Create a plain JSObject which materializes the block scope for the specified |
| // block context. |
| void ScopeIterator::VisitLocalScope(const Visitor& visitor, Mode mode, |
| ScopeType scope_type) const { |
| if (InInnerScope()) { |
| if (VisitLocals(visitor, mode, scope_type)) return; |
| if (mode == Mode::STACK && Type() == ScopeTypeLocal) { |
| // Hide |this| in arrow functions that may be embedded in other functions |
| // but don't force |this| to be context-allocated. Otherwise we'd find the |
| // wrong |this| value. |
| if (!closure_scope_->has_this_declaration() && |
| !closure_scope_->HasThisReference()) { |
| if (visitor(isolate_->factory()->this_string(), |
| isolate_->factory()->undefined_value(), scope_type)) |
| return; |
| } |
| // Add |arguments| to the function scope even if it wasn't used. |
| // Currently we don't yet support materializing the arguments object of |
| // suspended generators. We'd need to read the arguments out from the |
| // suspended generator rather than from an activation as |
| // FunctionGetArguments does. |
| if (frame_inspector_ != nullptr && !closure_scope_->is_arrow_scope() && |
| (closure_scope_->arguments() == nullptr || |
| frame_inspector_->GetExpression(closure_scope_->arguments()->index()) |
| ->IsOptimizedOut(isolate_))) { |
| JavaScriptFrame* frame = GetFrame(); |
| Handle<JSObject> arguments = Accessors::FunctionGetArguments( |
| frame, frame_inspector_->inlined_frame_index()); |
| if (visitor(isolate_->factory()->arguments_string(), arguments, |
| scope_type)) |
| return; |
| } |
| } |
| } else { |
| DCHECK_EQ(Mode::ALL, mode); |
| Handle<ScopeInfo> scope_info(context_->scope_info(), isolate_); |
| if (VisitContextLocals(visitor, scope_info, context_, scope_type)) return; |
| } |
| |
| if (mode == Mode::ALL && HasContext()) { |
| DCHECK(!context_->IsScriptContext()); |
| DCHECK(!context_->IsNativeContext()); |
| DCHECK(!context_->IsWithContext()); |
| if (!context_->scope_info().SloppyEvalCanExtendVars()) return; |
| if (context_->extension_object().is_null()) return; |
| Handle<JSObject> extension(context_->extension_object(), isolate_); |
| Handle<FixedArray> keys = |
| KeyAccumulator::GetKeys(extension, KeyCollectionMode::kOwnOnly, |
| ENUMERABLE_STRINGS) |
| .ToHandleChecked(); |
| |
| for (int i = 0; i < keys->length(); i++) { |
| // Names of variables introduced by eval are strings. |
| DCHECK(keys->get(i).IsString()); |
| Handle<String> key(String::cast(keys->get(i)), isolate_); |
| Handle<Object> value = JSReceiver::GetDataProperty(extension, key); |
| if (visitor(key, value, scope_type)) return; |
| } |
| } |
| } |
| |
| bool ScopeIterator::SetLocalVariableValue(Handle<String> variable_name, |
| Handle<Object> new_value) { |
| // TODO(verwaest): Walk parameters backwards, not forwards. |
| // TODO(verwaest): Use VariableMap rather than locals() list for lookup. |
| for (Variable* var : *current_scope_->locals()) { |
| if (String::Equals(isolate_, var->name(), variable_name)) { |
| int index = var->index(); |
| switch (var->location()) { |
| case VariableLocation::LOOKUP: |
| case VariableLocation::UNALLOCATED: |
| // Drop assignments to unallocated locals. |
| DCHECK(var->is_this() || |
| *variable_name == ReadOnlyRoots(isolate_).arguments_string()); |
| return false; |
| |
| case VariableLocation::REPL_GLOBAL: |
| // Assignments to REPL declared variables are ignored for now. |
| return false; |
| |
| case VariableLocation::PARAMETER: { |
| if (var->is_this()) return false; |
| if (frame_inspector_ == nullptr) { |
| // Set the variable in the suspended generator. |
| DCHECK(!generator_.is_null()); |
| Handle<FixedArray> parameters_and_registers( |
| generator_->parameters_and_registers(), isolate_); |
| DCHECK_LT(index, parameters_and_registers->length()); |
| parameters_and_registers->set(index, *new_value); |
| } else { |
| JavaScriptFrame* frame = GetFrame(); |
| if (frame->is_optimized()) return false; |
| |
| frame->SetParameterValue(index, *new_value); |
| } |
| return true; |
| } |
| |
| case VariableLocation::LOCAL: |
| if (frame_inspector_ == nullptr) { |
| // Set the variable in the suspended generator. |
| DCHECK(!generator_.is_null()); |
| int parameter_count = |
| function_->shared().scope_info().ParameterCount(); |
| index += parameter_count; |
| Handle<FixedArray> parameters_and_registers( |
| generator_->parameters_and_registers(), isolate_); |
| DCHECK_LT(index, parameters_and_registers->length()); |
| parameters_and_registers->set(index, *new_value); |
| } else { |
| // Set the variable on the stack. |
| JavaScriptFrame* frame = GetFrame(); |
| if (frame->is_optimized()) return false; |
| |
| frame->SetExpression(index, *new_value); |
| } |
| return true; |
| |
| case VariableLocation::CONTEXT: |
| DCHECK(var->IsContextSlot()); |
| context_->set(index, *new_value); |
| return true; |
| |
| case VariableLocation::MODULE: |
| if (!var->IsExport()) return false; |
| Handle<SourceTextModule> module(context_->module(), isolate_); |
| SourceTextModule::StoreVariable(module, var->index(), new_value); |
| return true; |
| } |
| UNREACHABLE(); |
| } |
| } |
| |
| return false; |
| } |
| |
| bool ScopeIterator::SetContextExtensionValue(Handle<String> variable_name, |
| Handle<Object> new_value) { |
| if (!context_->has_extension()) return false; |
| |
| DCHECK(context_->extension_object().IsJSContextExtensionObject()); |
| Handle<JSObject> ext(context_->extension_object(), isolate_); |
| LookupIterator it(isolate_, ext, variable_name, LookupIterator::OWN); |
| Maybe<bool> maybe = JSReceiver::HasProperty(&it); |
| DCHECK(maybe.IsJust()); |
| if (!maybe.FromJust()) return false; |
| |
| CHECK(Object::SetDataProperty(&it, new_value).ToChecked()); |
| return true; |
| } |
| |
| bool ScopeIterator::SetContextVariableValue(Handle<String> variable_name, |
| Handle<Object> new_value) { |
| DisallowHeapAllocation no_gc; |
| VariableMode mode; |
| InitializationFlag flag; |
| MaybeAssignedFlag maybe_assigned_flag; |
| IsStaticFlag is_static_flag; |
| int slot_index = |
| ScopeInfo::ContextSlotIndex(context_->scope_info(), *variable_name, &mode, |
| &flag, &maybe_assigned_flag, &is_static_flag); |
| if (slot_index < 0) return false; |
| |
| context_->set(slot_index, *new_value); |
| return true; |
| } |
| |
| bool ScopeIterator::SetModuleVariableValue(Handle<String> variable_name, |
| Handle<Object> new_value) { |
| DisallowHeapAllocation no_gc; |
| int cell_index; |
| VariableMode mode; |
| InitializationFlag init_flag; |
| MaybeAssignedFlag maybe_assigned_flag; |
| cell_index = context_->scope_info().ModuleIndex( |
| *variable_name, &mode, &init_flag, &maybe_assigned_flag); |
| |
| // Setting imports is currently not supported. |
| if (SourceTextModuleDescriptor::GetCellIndexKind(cell_index) != |
| SourceTextModuleDescriptor::kExport) { |
| return false; |
| } |
| |
| Handle<SourceTextModule> module(context_->module(), isolate_); |
| SourceTextModule::StoreVariable(module, cell_index, new_value); |
| return true; |
| } |
| |
| bool ScopeIterator::SetScriptVariableValue(Handle<String> variable_name, |
| Handle<Object> new_value) { |
| Handle<ScriptContextTable> script_contexts( |
| context_->global_object().native_context().script_context_table(), |
| isolate_); |
| ScriptContextTable::LookupResult lookup_result; |
| if (ScriptContextTable::Lookup(isolate_, *script_contexts, *variable_name, |
| &lookup_result)) { |
| Handle<Context> script_context = ScriptContextTable::GetContext( |
| isolate_, script_contexts, lookup_result.context_index); |
| script_context->set(lookup_result.slot_index, *new_value); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| } // namespace internal |
| } // namespace v8 |