// 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/debug/debug.h"
|
#include "src/frames-inl.h"
|
#include "src/globals.h"
|
#include "src/isolate-inl.h"
|
#include "src/objects/js-generator-inl.h"
|
#include "src/objects/module.h"
|
#include "src/parsing/parse-info.h"
|
#include "src/parsing/parsing.h"
|
#include "src/parsing/rewriter.h"
|
|
namespace v8 {
|
namespace internal {
|
|
ScopeIterator::ScopeIterator(Isolate* isolate, FrameInspector* frame_inspector,
|
ScopeIterator::Option option)
|
: 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(option);
|
}
|
|
ScopeIterator::~ScopeIterator() { delete info_; }
|
|
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),
|
script_(Script::cast(function->shared()->script()), isolate) {
|
if (!function->shared()->IsSubjectToDebugging()) {
|
context_ = Handle<Context>();
|
return;
|
}
|
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) {
|
if (!function_->shared()->IsSubjectToDebugging()) {
|
context_ = Handle<Context>();
|
return;
|
}
|
TryParseAndRetrieveScopes(DEFAULT);
|
}
|
|
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();
|
}
|
|
void ScopeIterator::TryParseAndRetrieveScopes(ScopeIterator::Option option) {
|
// 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;
|
}
|
|
DCHECK_NE(IGNORE_NESTED_SCOPES, option);
|
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.
|
// Check whether we are in global, eval or function code.
|
if (scope_info->scope_type() == FUNCTION_SCOPE) {
|
// Inner function.
|
info_ = new ParseInfo(isolate_, shared_info);
|
} else {
|
// Global or eval code.
|
Handle<Script> script(Script::cast(shared_info->script()), isolate_);
|
info_ = new ParseInfo(isolate_, script);
|
if (scope_info->scope_type() == EVAL_SCOPE) {
|
info_->set_eval();
|
if (!context_->IsNativeContext()) {
|
info_->set_outer_scope_info(handle(context_->scope_info(), isolate_));
|
}
|
// Language mode may be inherited from the eval caller.
|
// Retrieve it from shared function info.
|
info_->set_language_mode(shared_info->language_mode());
|
} else if (scope_info->scope_type() == MODULE_SCOPE) {
|
DCHECK(info_->is_module());
|
} else {
|
DCHECK_EQ(SCRIPT_SCOPE, scope_info->scope_type());
|
}
|
}
|
|
if (parsing::ParseAny(info_, shared_info, isolate_) &&
|
Rewriter::Rewrite(info_)) {
|
info_->ast_value_factory()->Internalize(isolate_);
|
closure_scope_ = info_->literal()->scope();
|
|
if (option == COLLECT_NON_LOCALS) {
|
DCHECK(non_locals_.is_null());
|
non_locals_ = info_->literal()->scope()->CollectNonLocals(
|
isolate_, info_, StringSet::New(isolate_));
|
}
|
|
CHECK(DeclarationScope::Analyze(info_));
|
if (ignore_nested_scopes) {
|
current_scope_ = closure_scope_;
|
start_scope_ = current_scope_;
|
if (closure_scope_->NeedsContext()) {
|
context_ = handle(context_->closure_context(), isolate_);
|
}
|
} else {
|
RetrieveScopeChain(closure_scope_);
|
}
|
UnwrapEvaluationContext();
|
} else {
|
// A failed reparse indicates that the preparser has diverged from the
|
// parser or that the preparse data given to the initial parse has been
|
// faulty. We fail in debug mode but in release mode we only provide the
|
// information we get from the context chain but nothing about
|
// completely stack allocated scopes or stack allocated locals.
|
// Or it could be due to stack overflow.
|
// Silently fail by presenting an empty context chain.
|
CHECK(isolate_->has_pending_exception());
|
isolate_->clear_pending_exception();
|
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_NOT_NULL(current->previous());
|
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) {
|
declares_local = true;
|
return true;
|
};
|
VisitScope(visitor, mode);
|
return declares_local;
|
}
|
|
bool ScopeIterator::HasContext() const {
|
return !InInnerScope() || current_scope_->NeedsContext();
|
}
|
|
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 inner = InInnerScope();
|
if (current_scope_ == closure_scope_) function_ = Handle<JSFunction>();
|
|
if (scope_type == ScopeTypeScript) {
|
DCHECK_IMPLIES(InInnerScope(), current_scope_->is_script_scope());
|
seen_script_scope_ = true;
|
if (context_->IsScriptContext()) {
|
context_ = handle(context_->previous(), isolate_);
|
}
|
} else if (!inner) {
|
DCHECK(!context_->IsNativeContext());
|
context_ = handle(context_->previous(), isolate_);
|
} else {
|
DCHECK_NOT_NULL(current_scope_);
|
do {
|
if (current_scope_->NeedsContext()) {
|
DCHECK_NOT_NULL(context_->previous());
|
context_ = handle(context_->previous(), isolate_);
|
}
|
DCHECK_IMPLIES(InInnerScope(), current_scope_->outer_scope() != nullptr);
|
current_scope_ = current_scope_->outer_scope();
|
// Repeat to skip hidden scopes.
|
} while (current_scope_->is_hidden());
|
}
|
|
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(current_scope_->NeedsContext(),
|
context_->IsFunctionContext());
|
return ScopeTypeLocal;
|
case MODULE_SCOPE:
|
DCHECK_IMPLIES(current_scope_->NeedsContext(),
|
context_->IsModuleContext());
|
return ScopeTypeModule;
|
case SCRIPT_SCOPE:
|
DCHECK_IMPLIES(
|
current_scope_->NeedsContext(),
|
context_->IsScriptContext() || context_->IsNativeContext());
|
return ScopeTypeScript;
|
case WITH_SCOPE:
|
DCHECK_IMPLIES(
|
current_scope_->NeedsContext(),
|
context_->IsWithContext() || context_->IsDebugEvaluateContext());
|
return ScopeTypeWith;
|
case CATCH_SCOPE:
|
DCHECK(context_->IsCatchContext());
|
return ScopeTypeCatch;
|
case BLOCK_SCOPE:
|
DCHECK_IMPLIES(current_scope_->NeedsContext(),
|
context_->IsBlockContext());
|
return ScopeTypeBlock;
|
case EVAL_SCOPE:
|
DCHECK_IMPLIES(current_scope_->NeedsContext(),
|
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()) {
|
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() || context_->IsDebugEvaluateContext());
|
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) {
|
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);
|
case ScopeTypeModule:
|
if (InInnerScope()) {
|
return VisitLocalScope(visitor, mode);
|
}
|
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 (!current_scope_->NeedsContext()) 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;
|
}
|
|
Handle<StringSet> ScopeIterator::GetNonLocals() { return non_locals_; }
|
|
#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 (current_scope_->NeedsContext()) {
|
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());
|
return generator_->source_position();
|
}
|
}
|
|
void ScopeIterator::RetrieveScopeChain(DeclarationScope* scope) {
|
DCHECK_NOT_NULL(scope);
|
|
const int position = GetSourcePosition();
|
|
Scope* parent = nullptr;
|
Scope* current = scope;
|
while (parent != current) {
|
parent = current;
|
for (Scope* inner_scope = current->inner_scope(); inner_scope != nullptr;
|
inner_scope = inner_scope->sibling()) {
|
int beg_pos = inner_scope->start_position();
|
int end_pos = inner_scope->end_position();
|
DCHECK((beg_pos >= 0 && end_pos >= 0) || inner_scope->is_hidden());
|
if (beg_pos <= position && position < end_pos) {
|
// Don't walk into inner functions.
|
if (!inner_scope->is_function_scope()) {
|
current = inner_scope;
|
}
|
break;
|
}
|
}
|
}
|
|
start_scope_ = current;
|
current_scope_ = current;
|
}
|
|
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->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)) 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_)) return;
|
|
int count_index = scope_info->ModuleVariableCountIndex();
|
int module_variable_count = Smi::cast(scope_info->get(count_index))->value();
|
|
Handle<Module> 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);
|
CHECK(!ScopeInfo::VariableIsSynthetic(raw_name));
|
name = handle(raw_name, isolate_);
|
}
|
Handle<Object> value = Module::LoadVariable(isolate_, module, index);
|
|
// Reflect variables under TDZ as undeclared in scope object.
|
if (value->IsTheHole(isolate_)) continue;
|
if (visitor(name, value)) return;
|
}
|
}
|
|
bool ScopeIterator::VisitContextLocals(const Visitor& visitor,
|
Handle<ScopeInfo> scope_info,
|
Handle<Context> context) 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 = Context::MIN_CONTEXT_SLOTS + i;
|
Handle<Object> value(context->get(context_index), isolate_);
|
// Reflect variables under TDZ as undefined in scope object.
|
if (value->IsTheHole(isolate_)) continue;
|
if (visitor(name, value)) return true;
|
}
|
return false;
|
}
|
|
bool ScopeIterator::VisitLocals(const Visitor& visitor, Mode mode) const {
|
for (Variable* var : *current_scope_->locals()) {
|
if (!var->is_this() && ScopeInfo::VariableIsSynthetic(*var->name())) {
|
continue;
|
}
|
|
int index = var->index();
|
Handle<Object> value;
|
switch (var->location()) {
|
case VariableLocation::LOOKUP:
|
UNREACHABLE();
|
break;
|
|
case VariableLocation::UNALLOCATED:
|
if (!var->is_this()) continue;
|
// No idea why we only add it sometimes.
|
if (mode == Mode::ALL) continue;
|
// No idea why this diverges...
|
value = frame_inspector_->GetReceiver();
|
break;
|
|
case VariableLocation::PARAMETER: {
|
if (frame_inspector_ == nullptr) {
|
// Get the variable from the suspended generator.
|
DCHECK(!generator_.is_null());
|
if (var->is_this()) {
|
value = handle(generator_->receiver(), isolate_);
|
} else {
|
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 = var->is_this() ? frame_inspector_->GetReceiver()
|
: frame_inspector_->GetParameter(index);
|
|
if (value->IsOptimizedOut(isolate_)) {
|
value = isolate_->factory()->undefined_value();
|
} else if (var->is_this() && value->IsTheHole(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_);
|
if (value->IsTheHole(isolate_)) {
|
value = isolate_->factory()->undefined_value();
|
}
|
} 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();
|
} else if (value->IsTheHole(isolate_)) {
|
// Reflect variables under TDZ as undeclared in scope object.
|
continue;
|
}
|
}
|
break;
|
|
case VariableLocation::CONTEXT:
|
if (mode == Mode::STACK) continue;
|
// TODO(verwaest): Why don't we want to show it if it's there?...
|
if (var->is_this()) continue;
|
DCHECK(var->IsContextSlot());
|
value = handle(context_->get(index), isolate_);
|
// Reflect variables under TDZ as undeclared in scope object.
|
if (value->IsTheHole(isolate_)) continue;
|
break;
|
|
case VariableLocation::MODULE: {
|
if (mode == Mode::STACK) continue;
|
// if (var->IsExport()) continue;
|
Handle<Module> module(context_->module(), isolate_);
|
value = Module::LoadVariable(isolate_, module, var->index());
|
// Reflect variables under TDZ as undeclared in scope object.
|
if (value->IsTheHole(isolate_)) continue;
|
break;
|
}
|
}
|
|
if (visitor(var->name(), value)) 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) const {
|
if (InInnerScope()) {
|
if (VisitLocals(visitor, mode)) 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() &&
|
!non_locals_->Has(isolate_, isolate_->factory()->this_string())) {
|
if (visitor(isolate_->factory()->this_string(),
|
isolate_->factory()->undefined_value()))
|
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)) return;
|
}
|
}
|
} else {
|
DCHECK_EQ(Mode::ALL, mode);
|
Handle<ScopeInfo> scope_info(context_->scope_info(), isolate_);
|
if (VisitContextLocals(visitor, scope_info, context_)) return;
|
}
|
|
if (mode == Mode::ALL && HasContext()) {
|
DCHECK(!context_->IsScriptContext());
|
DCHECK(!context_->IsNativeContext());
|
DCHECK(!context_->IsWithContext());
|
if (!context_->scope_info()->CallsSloppyEval()) return;
|
if (context_->extension_object() == nullptr) 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)) 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::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<Module> module(context_->module(), isolate_);
|
Module::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::HasOwnProperty(ext, variable_name);
|
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) {
|
Handle<ScopeInfo> scope_info(context_->scope_info(), isolate_);
|
|
VariableMode mode;
|
InitializationFlag flag;
|
MaybeAssignedFlag maybe_assigned_flag;
|
int slot_index = ScopeInfo::ContextSlotIndex(scope_info, variable_name, &mode,
|
&flag, &maybe_assigned_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) {
|
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 (ModuleDescriptor::GetCellIndexKind(cell_index) !=
|
ModuleDescriptor::kExport) {
|
return false;
|
}
|
|
Handle<Module> module(context_->module(), isolate_);
|
Module::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
|