Handle "//# sourceURL" comments in the Parser instead of the JS.

BUG=v8:2948
LOG=N
R=svenpanne@chromium.org, yurys@chromium.org

Review URL: https://codereview.chromium.org/316173002

git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@22137 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
marja@chromium.org 2014-07-02 07:01:31 +00:00
parent 9bc3d1a8fe
commit 7717f2366f
16 changed files with 362 additions and 44 deletions

View File

@ -946,6 +946,15 @@ class V8_EXPORT UnboundScript {
int GetId();
Handle<Value> GetScriptName();
/**
* Data read from magic sourceURL comments.
*/
Handle<Value> GetSourceURL();
/**
* Data read from magic sourceMappingURL comments.
*/
Handle<Value> GetSourceMappingURL();
/**
* Returns zero based line number of the code_pos location in the script.
* -1 will be returned if no information available.

View File

@ -585,6 +585,77 @@ Handle<AccessorInfo> Accessors::ScriptLineEndsInfo(
}
//
// Accessors::ScriptSourceUrl
//
void Accessors::ScriptSourceUrlGetter(
v8::Local<v8::String> name,
const v8::PropertyCallbackInfo<v8::Value>& info) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(info.GetIsolate());
DisallowHeapAllocation no_allocation;
HandleScope scope(isolate);
Object* object = *Utils::OpenHandle(*info.This());
Object* url = Script::cast(JSValue::cast(object)->value())->source_url();
info.GetReturnValue().Set(Utils::ToLocal(Handle<Object>(url, isolate)));
}
void Accessors::ScriptSourceUrlSetter(
v8::Local<v8::String> name,
v8::Local<v8::Value> value,
const v8::PropertyCallbackInfo<void>& info) {
UNREACHABLE();
}
Handle<AccessorInfo> Accessors::ScriptSourceUrlInfo(
Isolate* isolate, PropertyAttributes attributes) {
return MakeAccessor(isolate,
isolate->factory()->source_url_string(),
&ScriptSourceUrlGetter,
&ScriptSourceUrlSetter,
attributes);
}
//
// Accessors::ScriptSourceMappingUrl
//
void Accessors::ScriptSourceMappingUrlGetter(
v8::Local<v8::String> name,
const v8::PropertyCallbackInfo<v8::Value>& info) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(info.GetIsolate());
DisallowHeapAllocation no_allocation;
HandleScope scope(isolate);
Object* object = *Utils::OpenHandle(*info.This());
Object* url =
Script::cast(JSValue::cast(object)->value())->source_mapping_url();
info.GetReturnValue().Set(Utils::ToLocal(Handle<Object>(url, isolate)));
}
void Accessors::ScriptSourceMappingUrlSetter(
v8::Local<v8::String> name,
v8::Local<v8::Value> value,
const v8::PropertyCallbackInfo<void>& info) {
UNREACHABLE();
}
Handle<AccessorInfo> Accessors::ScriptSourceMappingUrlInfo(
Isolate* isolate, PropertyAttributes attributes) {
return MakeAccessor(isolate,
isolate->factory()->source_mapping_url_string(),
&ScriptSourceMappingUrlGetter,
&ScriptSourceMappingUrlSetter,
attributes);
}
//
// Accessors::ScriptGetContextData
//

View File

@ -32,6 +32,8 @@ namespace internal {
V(ScriptName) \
V(ScriptSource) \
V(ScriptType) \
V(ScriptSourceUrl) \
V(ScriptSourceMappingUrl) \
V(StringLength)
// Accessors contains all predefined proxy accessors.

View File

@ -1633,6 +1633,38 @@ Handle<Value> UnboundScript::GetScriptName() {
}
Handle<Value> UnboundScript::GetSourceURL() {
i::Handle<i::SharedFunctionInfo> obj =
i::Handle<i::SharedFunctionInfo>::cast(Utils::OpenHandle(this));
i::Isolate* isolate = obj->GetIsolate();
ON_BAILOUT(isolate, "v8::UnboundScript::GetSourceURL()",
return Handle<String>());
LOG_API(isolate, "UnboundScript::GetSourceURL");
if (obj->script()->IsScript()) {
i::Object* url = i::Script::cast(obj->script())->source_url();
return Utils::ToLocal(i::Handle<i::Object>(url, isolate));
} else {
return Handle<String>();
}
}
Handle<Value> UnboundScript::GetSourceMappingURL() {
i::Handle<i::SharedFunctionInfo> obj =
i::Handle<i::SharedFunctionInfo>::cast(Utils::OpenHandle(this));
i::Isolate* isolate = obj->GetIsolate();
ON_BAILOUT(isolate, "v8::UnboundScript::GetSourceMappingURL()",
return Handle<String>());
LOG_API(isolate, "UnboundScript::GetSourceMappingURL");
if (obj->script()->IsScript()) {
i::Object* url = i::Script::cast(obj->script())->source_mapping_url();
return Utils::ToLocal(i::Handle<i::Object>(url, isolate));
} else {
return Handle<String>();
}
}
Local<Value> Script::Run() {
i::Handle<i::Object> obj = Utils::OpenHandle(this, true);
// If execution is terminating, Compile(..)->Run() requires this

View File

@ -1762,7 +1762,7 @@ bool Genesis::InstallNatives() {
native_context()->set_script_function(*script_fun);
Handle<Map> script_map = Handle<Map>(script_fun->initial_map());
Map::EnsureDescriptorSlack(script_map, 13);
Map::EnsureDescriptorSlack(script_map, 14);
PropertyAttributes attribs =
static_cast<PropertyAttributes>(DONT_ENUM | DONT_DELETE | READ_ONLY);
@ -1869,6 +1869,23 @@ bool Genesis::InstallNatives() {
script_map->AppendDescriptor(&d);
}
Handle<AccessorInfo> script_source_url =
Accessors::ScriptSourceUrlInfo(isolate(), attribs);
{
CallbacksDescriptor d(Handle<Name>(Name::cast(script_source_url->name())),
script_source_url, attribs);
script_map->AppendDescriptor(&d);
}
Handle<AccessorInfo> script_source_mapping_url =
Accessors::ScriptSourceMappingUrlInfo(isolate(), attribs);
{
CallbacksDescriptor d(
Handle<Name>(Name::cast(script_source_mapping_url->name())),
script_source_mapping_url, attribs);
script_map->AppendDescriptor(&d);
}
// Allocate the empty script.
Handle<Script> script = factory()->NewScript(factory()->empty_string());
script->set_type(Smi::FromInt(Script::TYPE_NATIVE));

View File

@ -290,6 +290,8 @@ namespace internal {
V(nan_string, "NaN") \
V(RegExp_string, "RegExp") \
V(source_string, "source") \
V(source_url_string, "source_url") \
V(source_mapping_url_string, "source_mapping_url") \
V(global_string, "global") \
V(ignore_case_string, "ignoreCase") \
V(multiline_string, "multiline") \

View File

@ -560,44 +560,16 @@ function ScriptNameOrSourceURL() {
if (this.line_offset > 0 || this.column_offset > 0) {
return this.name;
}
// The result is cached as on long scripts it takes noticable time to search
// for the sourceURL.
if (this.hasCachedNameOrSourceURL) {
return this.cachedNameOrSourceURL;
if (this.source_url) {
return this.source_url;
}
this.hasCachedNameOrSourceURL = true;
// TODO(608): the spaces in a regexp below had to be escaped as \040
// because this file is being processed by js2c whose handling of spaces
// in regexps is broken. Also, ['"] are excluded from allowed URLs to
// avoid matches against sources that invoke evals with sourceURL.
// A better solution would be to detect these special comments in
// the scanner/parser.
var source = ToString(this.source);
var sourceUrlPos = %StringIndexOf(source, "sourceURL=", 0);
this.cachedNameOrSourceURL = this.name;
if (sourceUrlPos > 4) {
var sourceUrlPattern =
/\/\/[#@][\040\t]sourceURL=[\040\t]*([^\s\'\"]*)[\040\t]*$/gm;
// Don't reuse lastMatchInfo here, so we create a new array with room
// for four captures (array with length one longer than the index
// of the fourth capture, where the numbering is zero-based).
var matchInfo = new InternalArray(CAPTURE(3) + 1);
var match =
%_RegExpExec(sourceUrlPattern, source, sourceUrlPos - 4, matchInfo);
if (match) {
this.cachedNameOrSourceURL =
%_SubString(source, matchInfo[CAPTURE(2)], matchInfo[CAPTURE(3)]);
}
}
return this.cachedNameOrSourceURL;
return this.name;
}
SetUpLockedPrototype(Script,
$Array("source", "name", "line_ends", "line_offset", "column_offset",
"cachedNameOrSourceURL", "hasCachedNameOrSourceURL" ),
$Array("source", "name", "source_url", "source_mapping_url", "line_ends",
"line_offset", "column_offset"),
$Array(
"lineFromPosition", ScriptLineFromPosition,
"locationFromPosition", ScriptLocationFromPosition,

View File

@ -5263,6 +5263,8 @@ ACCESSORS_TO_SMI(Script, eval_from_instructions_offset,
kEvalFrominstructionsOffsetOffset)
ACCESSORS_TO_SMI(Script, flags, kFlagsOffset)
BOOL_ACCESSORS(Script, flags, is_shared_cross_origin, kIsSharedCrossOriginBit)
ACCESSORS(Script, source_url, Object, kSourceUrlOffset)
ACCESSORS(Script, source_mapping_url, Object, kSourceMappingUrlOffset)
Script::CompilationType Script::compilation_type() {
return BooleanBit::get(flags(), kCompilationTypeBit) ?

View File

@ -6911,6 +6911,12 @@ class Script: public Struct {
// [flags]: Holds an exciting bitfield.
DECL_ACCESSORS(flags, Smi)
// [source_url]: sourceURL from magic comment
DECL_ACCESSORS(source_url, Object)
// [source_url]: sourceMappingURL magic comment
DECL_ACCESSORS(source_mapping_url, Object)
// [compilation_type]: how the the script was compiled. Encoded in the
// 'flags' field.
inline CompilationType compilation_type();
@ -6967,7 +6973,9 @@ class Script: public Struct {
kEvalFromSharedOffset + kPointerSize;
static const int kFlagsOffset =
kEvalFrominstructionsOffsetOffset + kPointerSize;
static const int kSize = kFlagsOffset + kPointerSize;
static const int kSourceUrlOffset = kFlagsOffset + kPointerSize;
static const int kSourceMappingUrlOffset = kSourceUrlOffset + kPointerSize;
static const int kSize = kSourceMappingUrlOffset + kPointerSize;
private:
int GetLineNumberWithArray(int code_pos);

View File

@ -895,6 +895,9 @@ FunctionLiteral* Parser::DoParseProgram(CompilationInfo* info,
bool ok = true;
int beg_pos = scanner()->location().beg_pos;
ParseSourceElements(body, Token::EOS, info->is_eval(), true, &ok);
HandleSourceURLComments();
if (ok && strict_mode() == STRICT) {
CheckOctalLiteral(beg_pos, scanner()->location().end_pos, &ok);
}
@ -3882,6 +3885,18 @@ void Parser::RegisterTargetUse(Label* target, Target* stop) {
}
void Parser::HandleSourceURLComments() {
if (scanner_.source_url()->length() > 0) {
info_->script()->set_source_url(
*scanner_.source_url()->Internalize(isolate()));
}
if (scanner_.source_mapping_url()->length() > 0) {
info_->script()->set_source_mapping_url(
*scanner_.source_mapping_url()->Internalize(isolate()));
}
}
void Parser::ThrowPendingError() {
ASSERT(ast_value_factory_->IsInternalized());
if (has_pending_error_) {

View File

@ -796,6 +796,8 @@ class Parser : public ParserBase<ParserTraits> {
const AstRawString* function_name, int pos, Variable* fvar,
Token::Value fvar_init_op, bool is_generator, bool* ok);
void HandleSourceURLComments();
void ThrowPendingError();
void InternalizeUseCounts();

View File

@ -19,6 +19,15 @@
namespace v8 {
namespace internal {
Handle<String> LiteralBuffer::Internalize(Isolate* isolate) const {
if (is_one_byte()) {
return isolate->factory()->InternalizeOneByteString(one_byte_literal());
}
return isolate->factory()->InternalizeTwoByteString(two_byte_literal());
}
// ----------------------------------------------------------------------------
// Scanner
@ -295,6 +304,68 @@ Token::Value Scanner::SkipSingleLineComment() {
}
Token::Value Scanner::SkipSourceURLComment() {
TryToParseSourceURLComment();
while (c0_ >= 0 && !unicode_cache_->IsLineTerminator(c0_)) {
Advance();
}
return Token::WHITESPACE;
}
void Scanner::TryToParseSourceURLComment() {
// Magic comments are of the form: //[#@]\s<name>=\s*<value>\s*.* and this
// function will just return if it cannot parse a magic comment.
if (!unicode_cache_->IsWhiteSpace(c0_))
return;
Advance();
LiteralBuffer name;
while (c0_ >= 0 && !unicode_cache_->IsWhiteSpaceOrLineTerminator(c0_) &&
c0_ != '=') {
name.AddChar(c0_);
Advance();
}
if (!name.is_one_byte()) return;
Vector<const uint8_t> name_literal = name.one_byte_literal();
LiteralBuffer* value;
if (name_literal == STATIC_ASCII_VECTOR("sourceURL")) {
value = &source_url_;
} else if (name_literal == STATIC_ASCII_VECTOR("sourceMappingURL")) {
value = &source_mapping_url_;
} else {
return;
}
if (c0_ != '=')
return;
Advance();
value->Reset();
while (c0_ >= 0 && unicode_cache_->IsWhiteSpace(c0_)) {
Advance();
}
while (c0_ >= 0 && !unicode_cache_->IsLineTerminator(c0_)) {
// Disallowed characters.
if (c0_ == '"' || c0_ == '\'') {
value->Reset();
return;
}
if (unicode_cache_->IsWhiteSpace(c0_)) {
break;
}
value->AddChar(c0_);
Advance();
}
// Allow whitespace at the end.
while (c0_ >= 0 && !unicode_cache_->IsLineTerminator(c0_)) {
if (!unicode_cache_->IsWhiteSpace(c0_)) {
value->Reset();
break;
}
Advance();
}
}
Token::Value Scanner::SkipMultiLineComment() {
ASSERT(c0_ == '*');
Advance();
@ -459,7 +530,14 @@ void Scanner::Scan() {
// / // /* /=
Advance();
if (c0_ == '/') {
token = SkipSingleLineComment();
Advance();
if (c0_ == '@' || c0_ == '#') {
Advance();
token = SkipSourceURLComment();
} else {
PushBack(c0_);
token = SkipSingleLineComment();
}
} else if (c0_ == '*') {
token = SkipMultiLineComment();
} else if (c0_ == '=') {

View File

@ -216,14 +216,14 @@ class LiteralBuffer {
position_ += kUC16Size;
}
bool is_one_byte() { return is_one_byte_; }
bool is_one_byte() const { return is_one_byte_; }
bool is_contextual_keyword(Vector<const char> keyword) {
bool is_contextual_keyword(Vector<const char> keyword) const {
return is_one_byte() && keyword.length() == position_ &&
(memcmp(keyword.start(), backing_store_.start(), position_) == 0);
}
Vector<const uint16_t> two_byte_literal() {
Vector<const uint16_t> two_byte_literal() const {
ASSERT(!is_one_byte_);
ASSERT((position_ & 0x1) == 0);
return Vector<const uint16_t>(
@ -231,14 +231,14 @@ class LiteralBuffer {
position_ >> 1);
}
Vector<const uint8_t> one_byte_literal() {
Vector<const uint8_t> one_byte_literal() const {
ASSERT(is_one_byte_);
return Vector<const uint8_t>(
reinterpret_cast<const uint8_t*>(backing_store_.start()),
position_);
}
int length() {
int length() const {
return is_one_byte_ ? position_ : (position_ >> 1);
}
@ -247,6 +247,8 @@ class LiteralBuffer {
is_one_byte_ = true;
}
Handle<String> Internalize(Isolate* isolate) const;
private:
static const int kInitialCapacity = 16;
static const int kGrowthFactory = 4;
@ -451,6 +453,11 @@ class Scanner {
// be empty).
bool ScanRegExpFlags();
const LiteralBuffer* source_url() const { return &source_url_; }
const LiteralBuffer* source_mapping_url() const {
return &source_mapping_url_;
}
private:
// The current and look-ahead token.
struct TokenDesc {
@ -572,6 +579,8 @@ class Scanner {
bool SkipWhiteSpace();
Token::Value SkipSingleLineComment();
Token::Value SkipSourceURLComment();
void TryToParseSourceURLComment();
Token::Value SkipMultiLineComment();
// Scans a possible HTML comment -- begins with '<!'.
Token::Value ScanHtmlComment();
@ -606,6 +615,10 @@ class Scanner {
LiteralBuffer literal_buffer1_;
LiteralBuffer literal_buffer2_;
// Values parsed from magic comments.
LiteralBuffer source_url_;
LiteralBuffer source_mapping_url_;
TokenDesc current_; // desc for current token (as returned by Next())
TokenDesc next_; // desc for next token (one token look-ahead)

View File

@ -100,6 +100,17 @@ class Vector {
input.length() * sizeof(S) / sizeof(T));
}
bool operator==(const Vector<T>& other) const {
if (length_ != other.length_) return false;
if (start_ == other.start_) return true;
for (int i = 0; i < length_; ++i) {
if (start_[i] != other.start_[i]) {
return false;
}
}
return true;
}
protected:
void set_start(T* start) { start_ = start; }

View File

@ -22761,3 +22761,85 @@ TEST(CrossActivationEval) {
Local<Value> result = CompileRun("CallEval();");
CHECK_EQ(result, v8::Integer::New(isolate, 1));
}
void SourceURLHelper(const char* source, const char* expected_source_url,
const char* expected_source_mapping_url) {
Local<Script> script = v8_compile(source);
if (expected_source_url != NULL) {
v8::String::Utf8Value url(script->GetUnboundScript()->GetSourceURL());
CHECK_EQ(expected_source_url, *url);
} else {
CHECK(script->GetUnboundScript()->GetSourceURL()->IsUndefined());
}
if (expected_source_mapping_url != NULL) {
v8::String::Utf8Value url(
script->GetUnboundScript()->GetSourceMappingURL());
CHECK_EQ(expected_source_mapping_url, *url);
} else {
CHECK(script->GetUnboundScript()->GetSourceMappingURL()->IsUndefined());
}
}
TEST(ScriptSourceURLAndSourceMappingURL) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
SourceURLHelper("function foo() {}\n"
"//# sourceURL=bar1.js\n", "bar1.js", NULL);
SourceURLHelper("function foo() {}\n"
"//# sourceMappingURL=bar2.js\n", NULL, "bar2.js");
// Both sourceURL and sourceMappingURL.
SourceURLHelper("function foo() {}\n"
"//# sourceURL=bar3.js\n"
"//# sourceMappingURL=bar4.js\n", "bar3.js", "bar4.js");
// Two source URLs; the first one is ignored.
SourceURLHelper("function foo() {}\n"
"//# sourceURL=ignoreme.js\n"
"//# sourceURL=bar5.js\n", "bar5.js", NULL);
SourceURLHelper("function foo() {}\n"
"//# sourceMappingURL=ignoreme.js\n"
"//# sourceMappingURL=bar6.js\n", NULL, "bar6.js");
// SourceURL or sourceMappingURL in the middle of the script.
SourceURLHelper("function foo() {}\n"
"//# sourceURL=bar7.js\n"
"function baz() {}\n", "bar7.js", NULL);
SourceURLHelper("function foo() {}\n"
"//# sourceMappingURL=bar8.js\n"
"function baz() {}\n", NULL, "bar8.js");
// Too much whitespace.
SourceURLHelper("function foo() {}\n"
"//# sourceURL=bar9.js\n"
"//# sourceMappingURL=bar10.js\n", NULL, NULL);
SourceURLHelper("function foo() {}\n"
"//# sourceURL =bar11.js\n"
"//# sourceMappingURL =bar12.js\n", NULL, NULL);
// Disallowed characters in value.
SourceURLHelper("function foo() {}\n"
"//# sourceURL=bar13 .js \n"
"//# sourceMappingURL=bar14 .js \n",
NULL, NULL);
SourceURLHelper("function foo() {}\n"
"//# sourceURL=bar15\t.js \n"
"//# sourceMappingURL=bar16\t.js \n",
NULL, NULL);
SourceURLHelper("function foo() {}\n"
"//# sourceURL=bar17'.js \n"
"//# sourceMappingURL=bar18'.js \n",
NULL, NULL);
SourceURLHelper("function foo() {}\n"
"//# sourceURL=bar19\".js \n"
"//# sourceMappingURL=bar20\".js \n",
NULL, NULL);
// Not too much whitespace.
SourceURLHelper("function foo() {}\n"
"//# sourceURL= bar21.js \n"
"//# sourceMappingURL= bar22.js \n", "bar21.js", "bar22.js");
}

View File

@ -85,9 +85,11 @@ function listener(event, exec_state, event_data, data) {
assertTrue('context' in msg.body.script);
// Check that we pick script name from //# sourceURL, iff present
assertEquals(current_source.indexOf('sourceURL') >= 0 ?
'myscript.js' : undefined,
event_data.script().name());
if (event == Debug.DebugEvent.AfterCompile) {
assertEquals(current_source.indexOf('sourceURL') >= 0 ?
'myscript.js' : undefined,
event_data.script().name());
}
}
} catch (e) {
exception = e