}
-void Debug::OnDebugBreak(Handle<Object> break_points_hit,
- bool auto_continue) {
+void Debug::OnDebugBreak(Handle<Object> break_points_hit, bool auto_continue) {
// The caller provided for DebugScope.
AssertDebugContext();
// Bail out if there is no listener for this event
// line_number is already shifted by the script_line_offset.
int relative_line_number = line_number - script_line_offset;
if (!column_key_.is_null() && relative_line_number >= 0) {
- Handle<FixedArray> line_ends(FixedArray::cast(script->line_ends()));
- int start = (relative_line_number == 0) ? 0 :
- Smi::cast(line_ends->get(relative_line_number - 1))->value() + 1;
- int column_offset = position - start;
- if (relative_line_number == 0) {
- // For the case where the code is on the same line as the script
- // tag.
- column_offset += script->column_offset()->value();
- }
+ int column = Script::GetColumnNumber(script, position);
JSObject::AddProperty(stack_frame, column_key_,
- handle(Smi::FromInt(column_offset + 1), isolate_),
- NONE);
+ handle(Smi::FromInt(column + 1), isolate_), NONE);
}
JSObject::AddProperty(stack_frame, line_key_,
handle(Smi::FromInt(line_number + 1), isolate_),
# Must match OrderedHashTable::kNotFound.
define NOT_FOUND = -1;
+# Line ends array constants - see v8::internal::Script
+define REDUCTION_INDEX = 0;
+define NUMBER_OF_LINES_INDEX = 1;
+define FIRST_LINE_END_INDEX = 2;
+define ASCII_NL = 10;
+define ASCII_CR = 13;
+
# Check whether debug is active.
define DEBUG_IS_ACTIVE = (%_DebugIsActive() != 0);
macro DEBUG_IS_STEPPING(function) = (%_DebugIsActive() != 0 && %DebugCallbackSupportsStepping(function));
var ObjectDefineProperty = utils.ObjectDefineProperty;
var ArrayJoin;
+var MathFloor;
var ObjectToString;
var StringCharAt;
var StringIndexOf;
utils.Import(function(from) {
ArrayJoin = from.ArrayJoin;
+ MathFloor = from.MathFloor;
ObjectToString = from.ObjectToString;
StringCharAt = from.StringCharAt;
StringIndexOf = from.StringIndexOf;
return location.sourceText();
}
+
+function Newlines(source, from, to, reduction) {
+ var newLines = new InternalArray();
+ if (!IS_STRING(source)) return newLines;
+
+ var length = source.length;
+ for (; from < to && from < length && newLines.length < reduction - 1
+ ; ++from) {
+ var c = %_StringCharCodeAt(source, from);
+ if (c == ASCII_CR) {
+ if (from < length - 1) {
+ var c2 = %_StringCharCodeAt(source, from + 1);
+ if (c2 == ASCII_NL) {
+ from++; // CR-LF counts as one newline.
+ }
+ }
+ newLines.push(from);
+ } else if (c == ASCII_NL) {
+ newLines.push(from);
+ }
+ }
+ // End-of-file virtual end-of-line.
+ if (to >= length) {
+ var last = length != 0 ? %_StringCharCodeAt(source, length - 1) : 0;
+ if (last != ASCII_NL && last != ASCII_CR) newLines.push(source.length - 1);
+ }
+ return newLines;
+}
+
+
+function ScriptLineEnd(line) {
+ if (line < 0) return -1;
+ var source = this.source;
+ if (!IS_STRING(source)) return -1;
+ var line_ends = this.line_ends;
+ var reduction = line_ends[REDUCTION_INDEX];
+ var index = MathFloor(line / reduction) + FIRST_LINE_END_INDEX;
+ if (index >= line_ends.length) return -1;
+ var position = line_ends[index];
+ if (line % reduction == 0) return position;
+ var lines = Newlines(source, position + 1, source.length, reduction);
+ return lines[line % reduction - 1];
+}
+
+
/**
* Find a line number given a specific source position.
* @param {number} position The source position.
- * @return {number} 0 if input too small, -1 if input too large,
- else the line number.
+ * @return {number} -1 if position too large, else the 0-based line number.
*/
function ScriptLineFromPosition(position) {
- var lower = 0;
- var upper = this.lineCount() - 1;
+ var source = this.source;
+ if (!IS_STRING(source)) return -1;
+
var line_ends = this.line_ends;
+ var lower = FIRST_LINE_END_INDEX;
+ var upper = line_ends.length - 1;
- // We'll never find invalid positions so bail right away.
- if (position > line_ends[upper]) {
- return -1;
- }
+ var reduction = line_ends[REDUCTION_INDEX];
+ // This '>' would normally be a '>=', but due to {}-less 'with' statements in
+ // top-level code we sometimes encounter code positions that are one character
+ // after the end of the source. See comment in Rewriter::Rewrite.
+ if (position > source.length) return -1;
- // This means we don't have to safe-guard indexing line_ends[i - 1].
- if (position <= line_ends[0]) {
- return 0;
- }
+ var index = 0;
// Binary search to find line # from position range.
- while (upper >= 1) {
- var i = (lower + upper) >> 1;
-
- if (position > line_ends[i]) {
- lower = i + 1;
- } else if (position <= line_ends[i - 1]) {
- upper = i - 1;
- } else {
- return i;
+ if (position > line_ends[upper]) {
+ index = upper;
+ } else {
+ // Invariant: position > line_ends[lower]
+ // Invariant: position <= line_ends[upper]
+ while (lower + 1 < upper) {
+ // Since they differ by at least 2, i must be different from both
+ // upper or lower.
+ var i = (lower + upper) >> 1;
+ if (position > line_ends[i]) {
+ lower = i;
+ } else {
+ upper = i;
+ }
}
+ index = lower;
}
- return -1;
+ var line = (index - FIRST_LINE_END_INDEX) * reduction;
+ return line +
+ Newlines(source, line_ends[index] + 1, position, reduction).length;
}
/**
*/
function ScriptLocationFromPosition(position,
include_resource_offset) {
+ // Get zero-based line number.
var line = this.lineFromPosition(position);
if (line == -1) return null;
// Determine start, end and column.
- var line_ends = this.line_ends;
- var start = line == 0 ? 0 : line_ends[line - 1] + 1;
- var end = line_ends[line];
- if (end > 0 && %_CallFunction(this.source, end - 1, StringCharAt) == '\r') {
+ var start = this.lineEnd(line) + 1;
+ // End will be used for substr, so make it non-inclusive.
+ var end = this.lineEnd(line + 1) + 1;
+ if (end > this.source.length) end = this.source.length;
+ // But trim the newline if there is one (there might not be at EOF).
+ while (end > start) {
+ var trim_char = %_CallFunction(this.source, end - 1, StringCharAt);
+ if (trim_char != '\n' && trim_char != '\r') break;
end--;
}
var column = position - start;
}
return this.locationFromPosition(
- this.line_ends[offset_line + line - 1] + 1 + column); // line > 0 here.
+ this.lineEnd(offset_line + line) + 1 + column); // line > 0 here.
}
}
return null;
}
- var line_ends = this.line_ends;
- var from_position = from_line == 0 ? 0 : line_ends[from_line - 1] + 1;
- var to_position = to_line == 0 ? 0 : line_ends[to_line - 1] + 1;
+ var from_position = this.lineEnd(from_line) + 1;
+ var to_position = this.lineEnd(to_line) + 1;
// Return a source slice with line numbers re-adjusted to the resource.
return new SourceSlice(this,
from_line + this.line_offset,
to_line + this.line_offset,
- from_position, to_position);
+ from_position, to_position);
}
}
// Return the source line.
- var line_ends = this.line_ends;
- var start = line == 0 ? 0 : line_ends[line - 1] + 1;
- var end = line_ends[line];
+ var start = this.lineEnd(line) + 1;
+ var end = this.lineEnd(line + 1);
return %_CallFunction(this.source, start, end, StringSubstring);
}
*/
function ScriptLineCount() {
// Return number of source lines.
- return this.line_ends.length;
+ return this.line_ends[NUMBER_OF_LINES_INDEX];
}
"sourceSlice", ScriptSourceSlice,
"sourceLine", ScriptSourceLine,
"lineCount", ScriptLineCount,
- "nameOrSourceURL", ScriptNameOrSourceURL
+ "nameOrSourceURL", ScriptNameOrSourceURL,
+ "lineEnd", ScriptLineEnd
]
);
Vector<const SourceChar> src,
bool include_ending_line) {
const int src_len = src.length();
+ bool exotic_newlines = false;
+ if (include_ending_line) {
+ // Initally assume reduction is 1, ie all line endings are in the array.
+ DCHECK_EQ(line_ends->length(), Script::kReductionIndex);
+ line_ends->Add(1);
+ // Write a placeholder for the number-of-lines indicator.
+ DCHECK_EQ(line_ends->length(), Script::kNumberOfLinesIndex);
+ line_ends->Add(0);
+ DCHECK_EQ(line_ends->length(), Script::kFirstLineEndIndex);
+ // There's a fictional newline just before the first character. This
+ // simplifies a lot of things.
+ line_ends->Add(-1);
+ }
UnicodeCache* cache = isolate->unicode_cache();
for (int i = 0; i < src_len - 1; i++) {
SourceChar current = src[i];
SourceChar next = src[i + 1];
- if (cache->IsLineTerminatorSequence(current, next)) line_ends->Add(i);
+ if (cache->IsLineTerminatorSequence(current, next)) {
+ if (current != '\n' && current != '\r') exotic_newlines = true;
+ line_ends->Add(i);
+ }
}
- if (src_len > 0 && cache->IsLineTerminatorSequence(src[src_len - 1], 0)) {
- line_ends->Add(src_len - 1);
+ int last_posn = src_len - 1;
+ if (last_posn >= 0 && cache->IsLineTerminatorSequence(src[last_posn], 0)) {
+ if (src[last_posn] != '\n' && src[last_posn] != '\r')
+ exotic_newlines = true;
+ line_ends->Add(last_posn);
} else if (include_ending_line) {
- // Even if the last line misses a line end, it is counted.
- line_ends->Add(src_len);
+ // Even if the last line misses a line end, it is counted. Because we
+ // sometimes use character positions that are one beyond the end of the
+ // source (see Rewriter::Rewrite) we set the newline one beyond that.
+ // This is used for substr calculations, which trims to string length,
+ // so it's harmless.
+ line_ends->Add(last_posn + 1);
+ }
+ if (include_ending_line) {
+ // Update number of lines in script.
+ int lines = line_ends->length() - (Script::kFirstLineEndIndex + 1);
+ line_ends->Set(Script::kNumberOfLinesIndex, lines);
+ // Abuse some flags. The bots will run with a good variety of these flags,
+ // giving better coverage for the reduction code.
+ bool always_reduce = FLAG_always_opt;
+ bool never_reduce = !FLAG_crankshaft;
+ if (!never_reduce && !exotic_newlines &&
+ (always_reduce ||
+ (line_ends->length() > 5 && line_ends->length() * 8 > src_len / 12))) {
+ // If the line-ends array (8 bytes per entry) is larger than about 8%
+ // of the source length, then we reduce it to save memory. This won't
+ // trigger if lines are > 100 characters on average. If it triggers, then
+ // the goal is for it to take only 3% of the source size.
+ int reduction =
+ always_reduce ? 2 : (line_ends->length() * 8 * 33 / src_len);
+ DCHECK(reduction > 1);
+ line_ends->Set(Script::kReductionIndex, reduction);
+ }
}
}
include_ending_line);
}
}
- int line_count = line_ends.length();
- Handle<FixedArray> array = isolate->factory()->NewFixedArray(line_count);
- for (int i = 0; i < line_count; i++) {
- array->set(i, Smi::FromInt(line_ends[i]));
+ if (include_ending_line) {
+ const int kReductionIndex = Script::kReductionIndex;
+ const int kFirstLineEndIndex = Script::kFirstLineEndIndex;
+ int line_count = line_ends.length() - kFirstLineEndIndex;
+ int reduction = line_ends[kReductionIndex];
+ int reduced_lines = (line_count + reduction - 1) / reduction;
+ Handle<FixedArray> array =
+ isolate->factory()->NewFixedArray(kFirstLineEndIndex + reduced_lines);
+ for (int i = 0; i < kFirstLineEndIndex; i++) {
+ array->set(i, Smi::FromInt(line_ends[i]));
+ }
+ int j = kFirstLineEndIndex;
+ for (int i = 0; i < line_count; i += reduction, ++j) {
+ array->set(j, Smi::FromInt(line_ends[i + kFirstLineEndIndex]));
+ }
+ return array;
+ } else {
+ Handle<FixedArray> array =
+ isolate->factory()->NewFixedArray(line_ends.length());
+ for (int i = 0; i < line_ends.length(); i++) {
+ array->set(i, Smi::FromInt(line_ends[i]));
+ }
+ return array;
}
- return array;
}
}
+static int CountForwardNNewlines(Handle<Script> script, int block_position,
+ int n) {
+ int position = block_position;
+ Handle<Object> source_object(script->source(), script->GetIsolate());
+ if (!source_object->IsString() || n == 0) return position;
+ Handle<String> source(Handle<String>::cast(source_object));
+ int length = source->length();
+ for (int i = position; i < length; i++) {
+ uc16 current = source->Get(i);
+ if (current == '\r') {
+ n--;
+ if (i + 1 < length && source->Get(i + 1) == '\n') i++;
+ } else if (current == '\n') {
+ n--;
+ }
+ if (n == 0) return i + 1;
+ }
+ if (n == 1 && length > 0) {
+ uc16 last = source->Get(length - 1);
+ if (last != '\n' && last != '\r') return length;
+ }
+ return -1;
+}
+
+
int Script::GetColumnNumber(Handle<Script> script, int code_pos) {
+ // Get zero-based line number.
int line_number = GetLineNumber(script, code_pos);
if (line_number == -1) return -1;
DisallowHeapAllocation no_allocation;
FixedArray* line_ends_array = FixedArray::cast(script->line_ends());
line_number = line_number - script->line_offset()->value();
- if (line_number == 0) return code_pos + script->column_offset()->value();
- int prev_line_end_pos =
- Smi::cast(line_ends_array->get(line_number - 1))->value();
- return code_pos - (prev_line_end_pos + 1);
+ int reduction = Smi::cast(line_ends_array->get(kReductionIndex))->value();
+
+ int line_block_position =
+ Smi::cast(line_ends_array->get(line_number / reduction +
+ kFirstLineEndIndex))->value() +
+ 1;
+
+ int line_position = CountForwardNNewlines(script, line_block_position,
+ line_number % reduction);
+ if (line_number == 0) line_position = -script->column_offset()->value();
+ return code_pos - line_position;
}
+// Zero-based line number, calculated from UTF16 character position.
int Script::GetLineNumberWithArray(int code_pos) {
DisallowHeapAllocation no_allocation;
DCHECK(line_ends()->IsFixedArray());
FixedArray* line_ends_array = FixedArray::cast(line_ends());
int line_ends_len = line_ends_array->length();
- if (line_ends_len == 0) return -1;
+ if (line_ends_len == 0) return -1; // This happens if there is no source.
+ // There's always at least one line ending: A fictional newline just before
+ // the start.
+ DCHECK_GE(line_ends_len, kFirstLineEndIndex + 1);
+ int lower = kFirstLineEndIndex;
+ int upper = line_ends_len - 1;
+
+ if (code_pos < 0) return -1;
+ int index = 0;
- if ((Smi::cast(line_ends_array->get(0)))->value() >= code_pos) {
- return line_offset()->value();
+ if (code_pos > Smi::cast(line_ends_array->get(upper))->value()) {
+ index = upper;
+ } else {
+ while (lower + 1 < upper) {
+ DCHECK_LE(Smi::cast(line_ends_array->get(lower))->value(), code_pos);
+ DCHECK_LE(code_pos, Smi::cast(line_ends_array->get(upper))->value());
+ int i = (lower + upper) >> 1;
+ DCHECK(lower != i && upper != i);
+ if ((Smi::cast(line_ends_array->get(i)))->value() >= code_pos) {
+ upper = i;
+ } else {
+ lower = i;
+ }
+ }
+ index = lower;
}
- int left = 0;
- int right = line_ends_len;
- while (int half = (right - left) / 2) {
- if ((Smi::cast(line_ends_array->get(left + half)))->value() > code_pos) {
- right -= half;
- } else {
- left += half;
+ int reduction = Smi::cast(line_ends_array->get(kReductionIndex))->value();
+ int line_number = (index - kFirstLineEndIndex) * reduction;
+
+ // We only saved an nth of the line ends in the array, because there were so
+ // many.
+ int start_of_earlier_line =
+ Smi::cast(line_ends_array->get(index))->value() + 1;
+
+ if (reduction == 1 || !source()->IsString()) {
+ return line_number + line_offset()->value();
+ }
+ String* src = String::cast(source());
+ // This '>' would normally be a '>=', but due to {}-less 'with' statements in
+ // top-level code we sometimes encounter code positions that are one character
+ // after the end of the source. See comment in Rewriter::Rewrite.
+ if (code_pos > src->length()) return -1;
+ for (int i = start_of_earlier_line; i < src->length() && i < code_pos; i++) {
+ uc16 current = src->Get(i);
+ if (current == '\r') {
+ if (i < code_pos - 1 && i < src->length() - 1 && src->Get(i + 1) == '\n')
+ i++;
+ line_number++;
+ } else if (current == '\n') {
+ line_number++;
}
}
- return right + line_offset()->value();
+ return line_number + line_offset()->value();
}
static const int kSourceMappingUrlOffset = kSourceUrlOffset + kPointerSize;
static const int kSize = kSourceMappingUrlOffset + kPointerSize;
+ // Sync with constants in macros.py.
+ static const int kReductionIndex = 0;
+ static const int kNumberOfLinesIndex = 1;
+ static const int kFirstLineEndIndex = 2;
+
private:
int GetLineNumberWithArray(int code_pos);
// eval('with ({x:1}) x = 1');
// the end position of the function generated for executing the eval code
// coincides with the end of the with scope which is the position of '1'.
+ // Note that this may mean the position is outside the source code
+ // completely if there is no terminal newline, curly brace, or semicolon,
+ // often the case for 'eval'.
int pos = function->end_position();
VariableProxy* result_proxy =
processor.factory()->NewVariableProxy(result, pos);
}
+void ChangeNewlines(int kind, char* dest, size_t dest_len, const char* source) {
+ if (kind == 0) {
+ for (size_t i = 0; i <= strlen(source); i++) {
+ dest[i] = source[i];
+ }
+ } else {
+ for (size_t i = 0; i <= strlen(source); i++) {
+ char c = source[i];
+ if (c == '\n') {
+ if (kind == 1) {
+ *dest++ = '\r';
+ *dest++ = '\n';
+ } else {
+ // UTF-8 version of 0x2028 newline.
+ *dest++ = '\xe2';
+ *dest++ = '\x80';
+ *dest++ = '\xa8';
+ }
+ } else {
+ *dest++ = c;
+ }
+ }
+ }
+}
+
+
// Tests the C++ StackTrace API.
// TODO(3074796): Reenable this as a THREADED_TEST once it passes.
// THREADED_TEST(CaptureStackTrace) {
v8::FunctionTemplate::New(isolate, AnalyzeStackInNativeCode));
LocalContext context(0, templ);
- // Test getting OVERVIEW information. Should ignore information that is not
- // script name, function name, line number, and column offset.
- const char *overview_source =
- "function bar() {\n"
- " var y; AnalyzeStackInNativeCode(1);\n"
- "}\n"
- "function foo() {\n"
- "\n"
- " bar();\n"
- "}\n"
- "var x;eval('new foo();');";
- v8::Handle<v8::String> overview_src =
- v8::String::NewFromUtf8(isolate, overview_source);
- v8::ScriptCompiler::Source script_source(overview_src,
- v8::ScriptOrigin(origin));
- v8::Handle<Value> overview_result(
- v8::ScriptCompiler::CompileUnbound(isolate, &script_source)
- ->BindToCurrentContext()
- ->Run());
- CHECK(!overview_result.IsEmpty());
- CHECK(overview_result->IsObject());
-
- // Test getting DETAILED information.
- const char *detailed_source =
- "function bat() {AnalyzeStackInNativeCode(2);\n"
- "}\n"
- "\n"
- "function baz() {\n"
- " bat();\n"
- "}\n"
- "eval('new baz();');";
- v8::Handle<v8::String> detailed_src =
- v8::String::NewFromUtf8(isolate, detailed_source);
- // Make the script using a non-zero line and column offset.
- v8::Handle<v8::Integer> line_offset = v8::Integer::New(isolate, 3);
- v8::Handle<v8::Integer> column_offset = v8::Integer::New(isolate, 5);
- v8::ScriptOrigin detailed_origin(origin, line_offset, column_offset);
- v8::ScriptCompiler::Source script_source2(detailed_src, detailed_origin);
- v8::Handle<v8::UnboundScript> detailed_script(
- v8::ScriptCompiler::CompileUnbound(isolate, &script_source2));
- v8::Handle<Value> detailed_result(
- detailed_script->BindToCurrentContext()->Run());
- CHECK(!detailed_result.IsEmpty());
- CHECK(detailed_result->IsObject());
+ for (int i = 0; i < 3; i++) {
+ // Test getting OVERVIEW information. Should ignore information that is not
+ // script name, function name, line number, and column offset.
+ const char* overview_source =
+ "function bar() {\n"
+ " var y; AnalyzeStackInNativeCode(1);\n"
+ "}\n"
+ "function foo() {\n"
+ "\n"
+ " bar();\n"
+ "}\n"
+ "var x;eval('new foo();');";
+ size_t munged_length = strlen(overview_source) * 3 + 1;
+ char* overview_munged_source = new char[munged_length];
+ ChangeNewlines(i, overview_munged_source, munged_length, overview_source);
+
+ v8::Handle<v8::String> overview_src =
+ v8::String::NewFromUtf8(isolate, overview_munged_source);
+ delete[] overview_munged_source;
+ v8::ScriptCompiler::Source script_source(overview_src,
+ v8::ScriptOrigin(origin));
+ v8::Handle<Value> overview_result(
+ v8::ScriptCompiler::CompileUnbound(isolate, &script_source)
+ ->BindToCurrentContext()
+ ->Run());
+ CHECK(!overview_result.IsEmpty());
+ CHECK(overview_result->IsObject());
+
+ // Test getting DETAILED information.
+ const char* detailed_source =
+ "function bat() {AnalyzeStackInNativeCode(2);\n"
+ "}\n"
+ "\n"
+ "function baz() {\n"
+ " bat();\n"
+ "}\n"
+ "eval('new baz();');";
+ munged_length = strlen(detailed_source) * 3 + 1;
+ char* detailed_munged_source = new char[munged_length];
+ ChangeNewlines(i, detailed_munged_source, munged_length, detailed_source);
+ v8::Handle<v8::String> detailed_src =
+ v8::String::NewFromUtf8(isolate, detailed_munged_source);
+ delete[] detailed_munged_source;
+ // Make the script using a non-zero line and column offset.
+ v8::Handle<v8::Integer> line_offset = v8::Integer::New(isolate, 3);
+ v8::Handle<v8::Integer> column_offset = v8::Integer::New(isolate, 5);
+ v8::ScriptOrigin detailed_origin(origin, line_offset, column_offset);
+ v8::ScriptCompiler::Source script_source2(detailed_src, detailed_origin);
+ v8::Handle<v8::UnboundScript> detailed_script(
+ v8::ScriptCompiler::CompileUnbound(isolate, &script_source2));
+ v8::Handle<Value> detailed_result(
+ detailed_script->BindToCurrentContext()->Run());
+ CHECK(!detailed_result.IsEmpty());
+ CHECK(detailed_result->IsObject());
+ }
}
// This is the last position in the entire file (note: this equals
// file size of <debug-sourceinfo.js> - 1, since starting at 0).
-var last_position = 11337;
+var last_position = 11591;
// This is the last line of entire file (note: starting at 0).
-var last_line = 265;
-// This is the last column of last line (note: starting at 0 and +1, due
-// to trailing <LF>).
+var last_line = 268;
+// This is the column of the last character (note: starting at 0) due to
+// final line having a trailing newline that is conceptually part of that line.
var last_column = 1;
// This magic number is the length or the first line comment (actually number
// Make sure invalid inputs work properly.
assertEquals(0, script.locationFromPosition(-1).line);
-assertEquals(null, script.locationFromPosition(last_position + 1));
+// We might expect last_position + 1 to be the first illegal position, but we
+// sometimes generate character positions that are one past the last character.
+// See Rewriter::Rewrite for details.
+assertEquals(null, script.locationFromPosition(last_position + 2));
// Test last position.
assertEquals(last_position, script.locationFromPosition(last_position).position);