// Copyright 2012 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/v8.h" #include "src/liveedit.h" #include "src/code-stubs.h" #include "src/compilation-cache.h" #include "src/compiler.h" #include "src/debug.h" #include "src/deoptimizer.h" #include "src/global-handles.h" #include "src/messages.h" #include "src/parser.h" #include "src/scopeinfo.h" #include "src/scopes.h" #include "src/v8memory.h" namespace v8 { namespace internal { void SetElementSloppy(Handle object, uint32_t index, Handle value) { // Ignore return value from SetElement. It can only be a failure if there // are element setters causing exceptions and the debugger context has none // of these. JSObject::SetElement(object, index, value, NONE, SLOPPY).Assert(); } // A simple implementation of dynamic programming algorithm. It solves // the problem of finding the difference of 2 arrays. It uses a table of results // of subproblems. Each cell contains a number together with 2-bit flag // that helps building the chunk list. class Differencer { public: explicit Differencer(Comparator::Input* input) : input_(input), len1_(input->GetLength1()), len2_(input->GetLength2()) { buffer_ = NewArray(len1_ * len2_); } ~Differencer() { DeleteArray(buffer_); } void Initialize() { int array_size = len1_ * len2_; for (int i = 0; i < array_size; i++) { buffer_[i] = kEmptyCellValue; } } // Makes sure that result for the full problem is calculated and stored // in the table together with flags showing a path through subproblems. void FillTable() { CompareUpToTail(0, 0); } void SaveResult(Comparator::Output* chunk_writer) { ResultWriter writer(chunk_writer); int pos1 = 0; int pos2 = 0; while (true) { if (pos1 < len1_) { if (pos2 < len2_) { Direction dir = get_direction(pos1, pos2); switch (dir) { case EQ: writer.eq(); pos1++; pos2++; break; case SKIP1: writer.skip1(1); pos1++; break; case SKIP2: case SKIP_ANY: writer.skip2(1); pos2++; break; default: UNREACHABLE(); } } else { writer.skip1(len1_ - pos1); break; } } else { if (len2_ != pos2) { writer.skip2(len2_ - pos2); } break; } } writer.close(); } private: Comparator::Input* input_; int* buffer_; int len1_; int len2_; enum Direction { EQ = 0, SKIP1, SKIP2, SKIP_ANY, MAX_DIRECTION_FLAG_VALUE = SKIP_ANY }; // Computes result for a subtask and optionally caches it in the buffer table. // All results values are shifted to make space for flags in the lower bits. int CompareUpToTail(int pos1, int pos2) { if (pos1 < len1_) { if (pos2 < len2_) { int cached_res = get_value4(pos1, pos2); if (cached_res == kEmptyCellValue) { Direction dir; int res; if (input_->Equals(pos1, pos2)) { res = CompareUpToTail(pos1 + 1, pos2 + 1); dir = EQ; } else { int res1 = CompareUpToTail(pos1 + 1, pos2) + (1 << kDirectionSizeBits); int res2 = CompareUpToTail(pos1, pos2 + 1) + (1 << kDirectionSizeBits); if (res1 == res2) { res = res1; dir = SKIP_ANY; } else if (res1 < res2) { res = res1; dir = SKIP1; } else { res = res2; dir = SKIP2; } } set_value4_and_dir(pos1, pos2, res, dir); cached_res = res; } return cached_res; } else { return (len1_ - pos1) << kDirectionSizeBits; } } else { return (len2_ - pos2) << kDirectionSizeBits; } } inline int& get_cell(int i1, int i2) { return buffer_[i1 + i2 * len1_]; } // Each cell keeps a value plus direction. Value is multiplied by 4. void set_value4_and_dir(int i1, int i2, int value4, Direction dir) { DCHECK((value4 & kDirectionMask) == 0); get_cell(i1, i2) = value4 | dir; } int get_value4(int i1, int i2) { return get_cell(i1, i2) & (kMaxUInt32 ^ kDirectionMask); } Direction get_direction(int i1, int i2) { return static_cast(get_cell(i1, i2) & kDirectionMask); } static const int kDirectionSizeBits = 2; static const int kDirectionMask = (1 << kDirectionSizeBits) - 1; static const int kEmptyCellValue = ~0u << kDirectionSizeBits; // This method only holds static assert statement (unfortunately you cannot // place one in class scope). void StaticAssertHolder() { STATIC_ASSERT(MAX_DIRECTION_FLAG_VALUE < (1 << kDirectionSizeBits)); } class ResultWriter { public: explicit ResultWriter(Comparator::Output* chunk_writer) : chunk_writer_(chunk_writer), pos1_(0), pos2_(0), pos1_begin_(-1), pos2_begin_(-1), has_open_chunk_(false) { } void eq() { FlushChunk(); pos1_++; pos2_++; } void skip1(int len1) { StartChunk(); pos1_ += len1; } void skip2(int len2) { StartChunk(); pos2_ += len2; } void close() { FlushChunk(); } private: Comparator::Output* chunk_writer_; int pos1_; int pos2_; int pos1_begin_; int pos2_begin_; bool has_open_chunk_; void StartChunk() { if (!has_open_chunk_) { pos1_begin_ = pos1_; pos2_begin_ = pos2_; has_open_chunk_ = true; } } void FlushChunk() { if (has_open_chunk_) { chunk_writer_->AddChunk(pos1_begin_, pos2_begin_, pos1_ - pos1_begin_, pos2_ - pos2_begin_); has_open_chunk_ = false; } } }; }; void Comparator::CalculateDifference(Comparator::Input* input, Comparator::Output* result_writer) { Differencer differencer(input); differencer.Initialize(); differencer.FillTable(); differencer.SaveResult(result_writer); } static bool CompareSubstrings(Handle s1, int pos1, Handle s2, int pos2, int len) { for (int i = 0; i < len; i++) { if (s1->Get(i + pos1) != s2->Get(i + pos2)) { return false; } } return true; } // Additional to Input interface. Lets switch Input range to subrange. // More elegant way would be to wrap one Input as another Input object // and translate positions there, but that would cost us additional virtual // call per comparison. class SubrangableInput : public Comparator::Input { public: virtual void SetSubrange1(int offset, int len) = 0; virtual void SetSubrange2(int offset, int len) = 0; }; class SubrangableOutput : public Comparator::Output { public: virtual void SetSubrange1(int offset, int len) = 0; virtual void SetSubrange2(int offset, int len) = 0; }; static int min(int a, int b) { return a < b ? a : b; } // Finds common prefix and suffix in input. This parts shouldn't take space in // linear programming table. Enable subranging in input and output. static void NarrowDownInput(SubrangableInput* input, SubrangableOutput* output) { const int len1 = input->GetLength1(); const int len2 = input->GetLength2(); int common_prefix_len; int common_suffix_len; { common_prefix_len = 0; int prefix_limit = min(len1, len2); while (common_prefix_len < prefix_limit && input->Equals(common_prefix_len, common_prefix_len)) { common_prefix_len++; } common_suffix_len = 0; int suffix_limit = min(len1 - common_prefix_len, len2 - common_prefix_len); while (common_suffix_len < suffix_limit && input->Equals(len1 - common_suffix_len - 1, len2 - common_suffix_len - 1)) { common_suffix_len++; } } if (common_prefix_len > 0 || common_suffix_len > 0) { int new_len1 = len1 - common_suffix_len - common_prefix_len; int new_len2 = len2 - common_suffix_len - common_prefix_len; input->SetSubrange1(common_prefix_len, new_len1); input->SetSubrange2(common_prefix_len, new_len2); output->SetSubrange1(common_prefix_len, new_len1); output->SetSubrange2(common_prefix_len, new_len2); } } // A helper class that writes chunk numbers into JSArray. // Each chunk is stored as 3 array elements: (pos1_begin, pos1_end, pos2_end). class CompareOutputArrayWriter { public: explicit CompareOutputArrayWriter(Isolate* isolate) : array_(isolate->factory()->NewJSArray(10)), current_size_(0) {} Handle GetResult() { return array_; } void WriteChunk(int char_pos1, int char_pos2, int char_len1, int char_len2) { Isolate* isolate = array_->GetIsolate(); SetElementSloppy(array_, current_size_, Handle(Smi::FromInt(char_pos1), isolate)); SetElementSloppy(array_, current_size_ + 1, Handle(Smi::FromInt(char_pos1 + char_len1), isolate)); SetElementSloppy(array_, current_size_ + 2, Handle(Smi::FromInt(char_pos2 + char_len2), isolate)); current_size_ += 3; } private: Handle array_; int current_size_; }; // Represents 2 strings as 2 arrays of tokens. // TODO(LiveEdit): Currently it's actually an array of charactres. // Make array of tokens instead. class TokensCompareInput : public Comparator::Input { public: TokensCompareInput(Handle s1, int offset1, int len1, Handle s2, int offset2, int len2) : s1_(s1), offset1_(offset1), len1_(len1), s2_(s2), offset2_(offset2), len2_(len2) { } virtual int GetLength1() { return len1_; } virtual int GetLength2() { return len2_; } bool Equals(int index1, int index2) { return s1_->Get(offset1_ + index1) == s2_->Get(offset2_ + index2); } private: Handle s1_; int offset1_; int len1_; Handle s2_; int offset2_; int len2_; }; // Stores compare result in JSArray. Converts substring positions // to absolute positions. class TokensCompareOutput : public Comparator::Output { public: TokensCompareOutput(CompareOutputArrayWriter* array_writer, int offset1, int offset2) : array_writer_(array_writer), offset1_(offset1), offset2_(offset2) { } void AddChunk(int pos1, int pos2, int len1, int len2) { array_writer_->WriteChunk(pos1 + offset1_, pos2 + offset2_, len1, len2); } private: CompareOutputArrayWriter* array_writer_; int offset1_; int offset2_; }; // Wraps raw n-elements line_ends array as a list of n+1 lines. The last line // never has terminating new line character. class LineEndsWrapper { public: explicit LineEndsWrapper(Handle string) : ends_array_(String::CalculateLineEnds(string, false)), string_len_(string->length()) { } int length() { return ends_array_->length() + 1; } // Returns start for any line including start of the imaginary line after // the last line. int GetLineStart(int index) { if (index == 0) { return 0; } else { return GetLineEnd(index - 1); } } int GetLineEnd(int index) { if (index == ends_array_->length()) { // End of the last line is always an end of the whole string. // If the string ends with a new line character, the last line is an // empty string after this character. return string_len_; } else { return GetPosAfterNewLine(index); } } private: Handle ends_array_; int string_len_; int GetPosAfterNewLine(int index) { return Smi::cast(ends_array_->get(index))->value() + 1; } }; // Represents 2 strings as 2 arrays of lines. class LineArrayCompareInput : public SubrangableInput { public: LineArrayCompareInput(Handle s1, Handle s2, LineEndsWrapper line_ends1, LineEndsWrapper line_ends2) : s1_(s1), s2_(s2), line_ends1_(line_ends1), line_ends2_(line_ends2), subrange_offset1_(0), subrange_offset2_(0), subrange_len1_(line_ends1_.length()), subrange_len2_(line_ends2_.length()) { } int GetLength1() { return subrange_len1_; } int GetLength2() { return subrange_len2_; } bool Equals(int index1, int index2) { index1 += subrange_offset1_; index2 += subrange_offset2_; int line_start1 = line_ends1_.GetLineStart(index1); int line_start2 = line_ends2_.GetLineStart(index2); int line_end1 = line_ends1_.GetLineEnd(index1); int line_end2 = line_ends2_.GetLineEnd(index2); int len1 = line_end1 - line_start1; int len2 = line_end2 - line_start2; if (len1 != len2) { return false; } return CompareSubstrings(s1_, line_start1, s2_, line_start2, len1); } void SetSubrange1(int offset, int len) { subrange_offset1_ = offset; subrange_len1_ = len; } void SetSubrange2(int offset, int len) { subrange_offset2_ = offset; subrange_len2_ = len; } private: Handle s1_; Handle s2_; LineEndsWrapper line_ends1_; LineEndsWrapper line_ends2_; int subrange_offset1_; int subrange_offset2_; int subrange_len1_; int subrange_len2_; }; // Stores compare result in JSArray. For each chunk tries to conduct // a fine-grained nested diff token-wise. class TokenizingLineArrayCompareOutput : public SubrangableOutput { public: TokenizingLineArrayCompareOutput(LineEndsWrapper line_ends1, LineEndsWrapper line_ends2, Handle s1, Handle s2) : array_writer_(s1->GetIsolate()), line_ends1_(line_ends1), line_ends2_(line_ends2), s1_(s1), s2_(s2), subrange_offset1_(0), subrange_offset2_(0) { } void AddChunk(int line_pos1, int line_pos2, int line_len1, int line_len2) { line_pos1 += subrange_offset1_; line_pos2 += subrange_offset2_; int char_pos1 = line_ends1_.GetLineStart(line_pos1); int char_pos2 = line_ends2_.GetLineStart(line_pos2); int char_len1 = line_ends1_.GetLineStart(line_pos1 + line_len1) - char_pos1; int char_len2 = line_ends2_.GetLineStart(line_pos2 + line_len2) - char_pos2; if (char_len1 < CHUNK_LEN_LIMIT && char_len2 < CHUNK_LEN_LIMIT) { // Chunk is small enough to conduct a nested token-level diff. HandleScope subTaskScope(s1_->GetIsolate()); TokensCompareInput tokens_input(s1_, char_pos1, char_len1, s2_, char_pos2, char_len2); TokensCompareOutput tokens_output(&array_writer_, char_pos1, char_pos2); Comparator::CalculateDifference(&tokens_input, &tokens_output); } else { array_writer_.WriteChunk(char_pos1, char_pos2, char_len1, char_len2); } } void SetSubrange1(int offset, int len) { subrange_offset1_ = offset; } void SetSubrange2(int offset, int len) { subrange_offset2_ = offset; } Handle GetResult() { return array_writer_.GetResult(); } private: static const int CHUNK_LEN_LIMIT = 800; CompareOutputArrayWriter array_writer_; LineEndsWrapper line_ends1_; LineEndsWrapper line_ends2_; Handle s1_; Handle s2_; int subrange_offset1_; int subrange_offset2_; }; Handle LiveEdit::CompareStrings(Handle s1, Handle s2) { s1 = String::Flatten(s1); s2 = String::Flatten(s2); LineEndsWrapper line_ends1(s1); LineEndsWrapper line_ends2(s2); LineArrayCompareInput input(s1, s2, line_ends1, line_ends2); TokenizingLineArrayCompareOutput output(line_ends1, line_ends2, s1, s2); NarrowDownInput(&input, &output); Comparator::CalculateDifference(&input, &output); return output.GetResult(); } // Unwraps JSValue object, returning its field "value" static Handle UnwrapJSValue(Handle jsValue) { return Handle(jsValue->value(), jsValue->GetIsolate()); } // Wraps any object into a OpaqueReference, that will hide the object // from JavaScript. static Handle WrapInJSValue(Handle object) { Isolate* isolate = object->GetIsolate(); Handle constructor = isolate->opaque_reference_function(); Handle result = Handle::cast(isolate->factory()->NewJSObject(constructor)); result->set_value(*object); return result; } static Handle UnwrapSharedFunctionInfoFromJSValue( Handle jsValue) { Object* shared = jsValue->value(); CHECK(shared->IsSharedFunctionInfo()); return Handle(SharedFunctionInfo::cast(shared)); } static int GetArrayLength(Handle array) { Object* length = array->length(); CHECK(length->IsSmi()); return Smi::cast(length)->value(); } void FunctionInfoWrapper::SetInitialProperties(Handle name, int start_position, int end_position, int param_num, int literal_count, int parent_index) { HandleScope scope(isolate()); this->SetField(kFunctionNameOffset_, name); this->SetSmiValueField(kStartPositionOffset_, start_position); this->SetSmiValueField(kEndPositionOffset_, end_position); this->SetSmiValueField(kParamNumOffset_, param_num); this->SetSmiValueField(kLiteralNumOffset_, literal_count); this->SetSmiValueField(kParentIndexOffset_, parent_index); } void FunctionInfoWrapper::SetFunctionCode(Handle function_code, Handle code_scope_info) { Handle code_wrapper = WrapInJSValue(function_code); this->SetField(kCodeOffset_, code_wrapper); Handle scope_wrapper = WrapInJSValue(code_scope_info); this->SetField(kCodeScopeInfoOffset_, scope_wrapper); } void FunctionInfoWrapper::SetSharedFunctionInfo( Handle info) { Handle info_holder = WrapInJSValue(info); this->SetField(kSharedFunctionInfoOffset_, info_holder); } Handle FunctionInfoWrapper::GetFunctionCode() { Handle element = this->GetField(kCodeOffset_); Handle value_wrapper = Handle::cast(element); Handle raw_result = UnwrapJSValue(value_wrapper); CHECK(raw_result->IsCode()); return Handle::cast(raw_result); } MaybeHandle FunctionInfoWrapper::GetFeedbackVector() { Handle element = this->GetField(kSharedFunctionInfoOffset_); if (element->IsJSValue()) { Handle value_wrapper = Handle::cast(element); Handle raw_result = UnwrapJSValue(value_wrapper); Handle shared = Handle::cast(raw_result); return Handle(shared->feedback_vector(), isolate()); } else { // Scripts may never have a SharedFunctionInfo created. return MaybeHandle(); } } Handle FunctionInfoWrapper::GetCodeScopeInfo() { Handle element = this->GetField(kCodeScopeInfoOffset_); return UnwrapJSValue(Handle::cast(element)); } void SharedInfoWrapper::SetProperties(Handle name, int start_position, int end_position, Handle info) { HandleScope scope(isolate()); this->SetField(kFunctionNameOffset_, name); Handle info_holder = WrapInJSValue(info); this->SetField(kSharedInfoOffset_, info_holder); this->SetSmiValueField(kStartPositionOffset_, start_position); this->SetSmiValueField(kEndPositionOffset_, end_position); } Handle SharedInfoWrapper::GetInfo() { Handle element = this->GetField(kSharedInfoOffset_); Handle value_wrapper = Handle::cast(element); return UnwrapSharedFunctionInfoFromJSValue(value_wrapper); } class FunctionInfoListener { public: explicit FunctionInfoListener(Isolate* isolate) { current_parent_index_ = -1; len_ = 0; result_ = isolate->factory()->NewJSArray(10); } void FunctionStarted(FunctionLiteral* fun) { HandleScope scope(isolate()); FunctionInfoWrapper info = FunctionInfoWrapper::Create(isolate()); info.SetInitialProperties(fun->name(), fun->start_position(), fun->end_position(), fun->parameter_count(), fun->materialized_literal_count(), current_parent_index_); current_parent_index_ = len_; SetElementSloppy(result_, len_, info.GetJSArray()); len_++; } void FunctionDone() { HandleScope scope(isolate()); FunctionInfoWrapper info = FunctionInfoWrapper::cast( *Object::GetElement( isolate(), result_, current_parent_index_).ToHandleChecked()); current_parent_index_ = info.GetParentIndex(); } // Saves only function code, because for a script function we // may never create a SharedFunctionInfo object. void FunctionCode(Handle function_code) { FunctionInfoWrapper info = FunctionInfoWrapper::cast( *Object::GetElement( isolate(), result_, current_parent_index_).ToHandleChecked()); info.SetFunctionCode(function_code, Handle(isolate()->heap()->null_value())); } // Saves full information about a function: its code, its scope info // and a SharedFunctionInfo object. void FunctionInfo(Handle shared, Scope* scope, Zone* zone) { if (!shared->IsSharedFunctionInfo()) { return; } FunctionInfoWrapper info = FunctionInfoWrapper::cast( *Object::GetElement( isolate(), result_, current_parent_index_).ToHandleChecked()); info.SetFunctionCode(Handle(shared->code()), Handle(shared->scope_info())); info.SetSharedFunctionInfo(shared); Handle scope_info_list = SerializeFunctionScope(scope, zone); info.SetFunctionScopeInfo(scope_info_list); } Handle GetResult() { return result_; } private: Isolate* isolate() const { return result_->GetIsolate(); } Handle SerializeFunctionScope(Scope* scope, Zone* zone) { Handle scope_info_list = isolate()->factory()->NewJSArray(10); int scope_info_length = 0; // Saves some description of scope. It stores name and indexes of // variables in the whole scope chain. Null-named slots delimit // scopes of this chain. Scope* current_scope = scope; while (current_scope != NULL) { HandleScope handle_scope(isolate()); ZoneList stack_list(current_scope->StackLocalCount(), zone); ZoneList context_list( current_scope->ContextLocalCount(), zone); current_scope->CollectStackAndContextLocals(&stack_list, &context_list); context_list.Sort(&Variable::CompareIndex); for (int i = 0; i < context_list.length(); i++) { SetElementSloppy(scope_info_list, scope_info_length, context_list[i]->name()); scope_info_length++; SetElementSloppy( scope_info_list, scope_info_length, Handle(Smi::FromInt(context_list[i]->index()), isolate())); scope_info_length++; } SetElementSloppy(scope_info_list, scope_info_length, Handle(isolate()->heap()->null_value(), isolate())); scope_info_length++; current_scope = current_scope->outer_scope(); } return scope_info_list; } Handle result_; int len_; int current_parent_index_; }; void LiveEdit::InitializeThreadLocal(Debug* debug) { debug->thread_local_.frame_drop_mode_ = LiveEdit::FRAMES_UNTOUCHED; } bool LiveEdit::SetAfterBreakTarget(Debug* debug) { Code* code = NULL; Isolate* isolate = debug->isolate_; switch (debug->thread_local_.frame_drop_mode_) { case FRAMES_UNTOUCHED: return false; case FRAME_DROPPED_IN_IC_CALL: // We must have been calling IC stub. Do not go there anymore. code = isolate->builtins()->builtin(Builtins::kPlainReturn_LiveEdit); break; case FRAME_DROPPED_IN_DEBUG_SLOT_CALL: // Debug break slot stub does not return normally, instead it manually // cleans the stack and jumps. We should patch the jump address. code = isolate->builtins()->builtin(Builtins::kFrameDropper_LiveEdit); break; case FRAME_DROPPED_IN_DIRECT_CALL: // Nothing to do, after_break_target is not used here. return true; case FRAME_DROPPED_IN_RETURN_CALL: code = isolate->builtins()->builtin(Builtins::kFrameDropper_LiveEdit); break; case CURRENTLY_SET_MODE: UNREACHABLE(); break; } debug->after_break_target_ = code->entry(); return true; } MaybeHandle LiveEdit::GatherCompileInfo(Handle