Commit 61bee5c8 authored by yangguo's avatar yangguo Committed by Commit bot

Correctly escape RegExp source.

R=ulan@chromium.org
BUG=v8:3229
LOG=N

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

Cr-Commit-Position: refs/heads/master@{#25457}
parent cf572694
......@@ -322,6 +322,95 @@ Handle<AccessorInfo> Accessors::StringLengthInfo(
}
template <typename Char>
inline int CountRequiredEscapes(Handle<String> source) {
DisallowHeapAllocation no_gc;
int escapes = 0;
Vector<const Char> src = source->GetCharVector<Char>();
for (int i = 0; i < src.length(); i++) {
if (src[i] == '/' && (i == 0 || src[i - 1] != '\\')) escapes++;
}
return escapes;
}
template <typename Char, typename StringType>
inline Handle<StringType> WriteEscapedRegExpSource(Handle<String> source,
Handle<StringType> result) {
DisallowHeapAllocation no_gc;
Vector<const Char> src = source->GetCharVector<Char>();
Vector<Char> dst(result->GetChars(), result->length());
int s = 0;
int d = 0;
while (s < src.length()) {
if (src[s] == '/' && (s == 0 || src[s - 1] != '\\')) dst[d++] = '\\';
dst[d++] = src[s++];
}
DCHECK_EQ(result->length(), d);
return result;
}
MaybeHandle<String> EscapeRegExpSource(Isolate* isolate,
Handle<String> source) {
String::Flatten(source);
if (source->length() == 0) return isolate->factory()->query_colon_string();
bool one_byte = source->IsOneByteRepresentationUnderneath();
int escapes = one_byte ? CountRequiredEscapes<uint8_t>(source)
: CountRequiredEscapes<uc16>(source);
if (escapes == 0) return source;
int length = source->length() + escapes;
if (one_byte) {
Handle<SeqOneByteString> result;
ASSIGN_RETURN_ON_EXCEPTION(isolate, result,
isolate->factory()->NewRawOneByteString(length),
String);
return WriteEscapedRegExpSource<uint8_t>(source, result);
} else {
Handle<SeqTwoByteString> result;
ASSIGN_RETURN_ON_EXCEPTION(isolate, result,
isolate->factory()->NewRawTwoByteString(length),
String);
return WriteEscapedRegExpSource<uc16>(source, result);
}
}
// Implements ECMA262 ES6 draft 21.2.5.9
void Accessors::RegExpSourceGetter(
v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(info.GetIsolate());
HandleScope scope(isolate);
Handle<Object> receiver =
Utils::OpenHandle(*v8::Local<v8::Value>(info.This()));
Handle<JSRegExp> regexp = Handle<JSRegExp>::cast(receiver);
Handle<String> pattern(regexp->Pattern(), isolate);
MaybeHandle<String> maybe = EscapeRegExpSource(isolate, pattern);
Handle<String> result;
if (!maybe.ToHandle(&result)) {
isolate->OptionalRescheduleException(false);
return;
}
info.GetReturnValue().Set(Utils::ToLocal(result));
}
void Accessors::RegExpSourceSetter(v8::Local<v8::Name> name,
v8::Local<v8::Value> value,
const v8::PropertyCallbackInfo<void>& info) {
UNREACHABLE();
}
Handle<AccessorInfo> Accessors::RegExpSourceInfo(
Isolate* isolate, PropertyAttributes attributes) {
return MakeAccessor(isolate, isolate->factory()->source_string(),
&RegExpSourceGetter, &RegExpSourceSetter, attributes);
}
//
// Accessors::ScriptColumnOffset
//
......
......@@ -21,6 +21,7 @@ namespace internal {
V(FunctionName) \
V(FunctionLength) \
V(FunctionPrototype) \
V(RegExpSource) \
V(ScriptColumnOffset) \
V(ScriptCompilationType) \
V(ScriptContextData) \
......
......@@ -1044,11 +1044,10 @@ void Genesis::InitializeGlobal(Handle<GlobalObject> global_object,
{
// ECMA-262, section 15.10.7.1.
FieldDescriptor field(factory->source_string(),
JSRegExp::kSourceFieldIndex,
final,
Representation::Tagged());
initial_map->AppendDescriptor(&field);
Handle<AccessorInfo> regexp_source(
Accessors::RegExpSourceInfo(isolate, final));
CallbacksDescriptor d(factory->source_string(), regexp_source, final);
initial_map->AppendDescriptor(&d);
}
{
// ECMA-262, section 15.10.7.2.
......@@ -1085,18 +1084,17 @@ void Genesis::InitializeGlobal(Handle<GlobalObject> global_object,
initial_map->AppendDescriptor(&field);
}
initial_map->set_inobject_properties(5);
initial_map->set_pre_allocated_property_fields(5);
static const int num_fields = JSRegExp::kInObjectFieldCount;
initial_map->set_inobject_properties(num_fields);
initial_map->set_pre_allocated_property_fields(num_fields);
initial_map->set_unused_property_fields(0);
initial_map->set_instance_size(
initial_map->instance_size() + 5 * kPointerSize);
initial_map->set_instance_size(initial_map->instance_size() +
num_fields * kPointerSize);
// RegExp prototype object is itself a RegExp.
Handle<Map> proto_map = Map::Copy(initial_map, "RegExpPrototype");
proto_map->set_prototype(native_context()->initial_object_prototype());
Handle<JSObject> proto = factory->NewJSObjectFromMap(proto_map);
proto->InObjectPropertyAtPut(JSRegExp::kSourceFieldIndex,
heap->query_colon_string());
proto->InObjectPropertyAtPut(JSRegExp::kGlobalFieldIndex,
heap->false_value());
proto->InObjectPropertyAtPut(JSRegExp::kIgnoreCaseFieldIndex,
......
......@@ -97,9 +97,6 @@ class BasicJsonStringifier BASE_EMBEDDED {
template <typename Char>
INLINE(static bool DoNotEscape(Char c));
template <typename Char>
INLINE(static Vector<const Char> GetCharVector(Handle<String> string));
Result StackPush(Handle<Object> object);
void StackPop();
......@@ -634,7 +631,7 @@ void BasicJsonStringifier::SerializeString_(Handle<String> string) {
int worst_case_length = length << 3;
if (builder_.CurrentPartCanFit(worst_case_length)) {
DisallowHeapAllocation no_gc;
Vector<const SrcChar> vector = GetCharVector<SrcChar>(string);
Vector<const SrcChar> vector = string->GetCharVector<SrcChar>();
IncrementalStringBuilder::NoExtendBuilder<DestChar> no_extend(
&builder_, worst_case_length);
SerializeStringUnchecked_(vector, &no_extend);
......@@ -666,23 +663,6 @@ bool BasicJsonStringifier::DoNotEscape(uint16_t c) {
}
template <>
Vector<const uint8_t> BasicJsonStringifier::GetCharVector(
Handle<String> string) {
String::FlatContent flat = string->GetFlatContent();
DCHECK(flat.IsOneByte());
return flat.ToOneByteVector();
}
template <>
Vector<const uc16> BasicJsonStringifier::GetCharVector(Handle<String> string) {
String::FlatContent flat = string->GetFlatContent();
DCHECK(flat.IsTwoByte());
return flat.ToUC16Vector();
}
void BasicJsonStringifier::SerializeString(Handle<String> object) {
object = String::Flatten(object);
if (builder_.CurrentEncoding() == String::ONE_BYTE_ENCODING) {
......
......@@ -8298,6 +8298,22 @@ String::FlatContent String::GetFlatContent() {
}
template <>
Vector<const uint8_t> String::GetCharVector() {
String::FlatContent flat = GetFlatContent();
DCHECK(flat.IsOneByte());
return flat.ToOneByteVector();
}
template <>
Vector<const uc16> String::GetCharVector() {
String::FlatContent flat = GetFlatContent();
DCHECK(flat.IsTwoByte());
return flat.ToUC16Vector();
}
SmartArrayPointer<char> String::ToCString(AllowNullsFlag allow_nulls,
RobustnessFlag robust_flag,
int offset,
......
......@@ -7933,12 +7933,11 @@ class JSRegExp: public JSObject {
FixedArray::kHeaderSize + kIrregexpCaptureCountIndex * kPointerSize;
// In-object fields.
static const int kSourceFieldIndex = 0;
static const int kGlobalFieldIndex = 1;
static const int kIgnoreCaseFieldIndex = 2;
static const int kMultilineFieldIndex = 3;
static const int kLastIndexFieldIndex = 4;
static const int kInObjectFieldCount = 5;
static const int kGlobalFieldIndex = 0;
static const int kIgnoreCaseFieldIndex = 1;
static const int kMultilineFieldIndex = 2;
static const int kLastIndexFieldIndex = 3;
static const int kInObjectFieldCount = 4;
// The uninitialized value for a regexp code object.
static const int kUninitializedValue = -1;
......@@ -8819,6 +8818,9 @@ class String: public Name {
friend class String;
};
template <typename Char>
Vector<const Char> GetCharVector();
// Get and set the length of the string.
inline int length() const;
inline void set_length(int value);
......
......@@ -865,7 +865,6 @@ RUNTIME_FUNCTION(Runtime_RegExpInitializeAndCompile) {
if (!FLAG_harmony_regexps && constructor->IsJSFunction() &&
JSFunction::cast(constructor)->initial_map() == map) {
// If we still have the original map, set in-object properties directly.
regexp->InObjectPropertyAtPut(JSRegExp::kSourceFieldIndex, *source);
// Both true and false are immovable immortal objects so no need for write
// barrier.
regexp->InObjectPropertyAtPut(JSRegExp::kGlobalFieldIndex, *global,
......@@ -887,8 +886,6 @@ RUNTIME_FUNCTION(Runtime_RegExpInitializeAndCompile) {
PropertyAttributes writable =
static_cast<PropertyAttributes>(DONT_ENUM | DONT_DELETE);
Handle<Object> zero(Smi::FromInt(0), isolate);
JSObject::SetOwnPropertyIgnoreAttributes(regexp, factory->source_string(),
source, final).Check();
JSObject::SetOwnPropertyIgnoreAttributes(regexp, factory->global_string(),
global, final).Check();
JSObject::SetOwnPropertyIgnoreAttributes(
......
......@@ -38,8 +38,8 @@ assertEquals(8, eval("6;'abc';8"));
// "/" comes just before "0".
assertThrows('"\\x1/"');
assertThrows('"\\u111/"');
assertEquals("\\x1/", RegExp("\\x1/").source);
assertEquals("\\u111/", RegExp("\\u111/").source);
assertEquals("\\x1\\/", RegExp("\\x1/").source);
assertEquals("\\u111\\/", RegExp("\\u111/").source);
// ":" comes just after "9".
assertThrows('"\\x1:"');
......
// Copyright 2014 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.
// Escape '/'.
function testEscapes(expected, regexp) {
assertEquals(expected, regexp.source);
assertEquals("/" + expected + "/", regexp.toString());
}
testEscapes("\\/", /\//);
testEscapes("\\/\\/", /\/\//);
testEscapes("\\/", new RegExp("/"));
testEscapes("\\/", new RegExp("\\/"));
testEscapes("\\\\/", new RegExp("\\\\/"));
testEscapes("\\/\\/", new RegExp("\\/\\/"));
testEscapes("\\/\\/\\/\\/", new RegExp("////"));
testEscapes("\\/\\/\\/\\/", new RegExp("\\//\\//"));
testEscapes("(?:)", new RegExp(""));
testEscapes("(?:)", RegExp.prototype);
// Read-only property.
var r = /\/\//;
testEscapes("\\/\\/", r);
r.source = "garbage";
testEscapes("\\/\\/", r);
......@@ -26,23 +26,23 @@ This page tests toString conversion of RegExp objects, particularly wrt to '/' c
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
FAIL RegExp('/').source should be \/. Was /.
PASS RegExp('/').source is "\\/"
PASS RegExp('').source is "(?:)"
PASS RegExp.prototype.source is "(?:)"
FAIL RegExp('/').toString() should be /\//. Was ///.
PASS RegExp('/').toString() is "/\\//"
PASS RegExp('').toString() is "/(?:)/"
PASS RegExp.prototype.toString() is "/(?:)/"
FAIL testForwardSlash("^/$", "/"); should be true. Threw exception SyntaxError: Unexpected end of input
FAIL testForwardSlash("^/$", "/"); should be true. Threw exception SyntaxError: Unexpected end of input
FAIL testForwardSlash("^\/$", "/"); should be true. Threw exception SyntaxError: Unexpected end of input
PASS testForwardSlash("^/$", "/"); is true
PASS testForwardSlash("^/$", "/"); is true
PASS testForwardSlash("^\/$", "/"); is true
PASS testForwardSlash("^\\/$", "\/"); is true
PASS testForwardSlash("^\\\/$", "\/"); is true
FAIL testForwardSlash("^\\\\/$", "\\/"); should be true. Threw exception SyntaxError: Unexpected end of input
FAIL testForwardSlash("^\\\\\/$", "\\/"); should be true. Threw exception SyntaxError: Unexpected end of input
FAIL testForwardSlash("x/x/x", "x\/x\/x"); should be true. Threw exception SyntaxError: Unexpected end of input
FAIL testForwardSlash("x\/x/x", "x\/x\/x"); should be true. Threw exception SyntaxError: Unexpected end of input
FAIL testForwardSlash("x/x\/x", "x\/x\/x"); should be true. Threw exception SyntaxError: Unexpected end of input
FAIL testForwardSlash("x\/x\/x", "x\/x\/x"); should be true. Threw exception SyntaxError: Unexpected end of input
PASS testForwardSlash("x/x/x", "x\/x\/x"); is true
PASS testForwardSlash("x\/x/x", "x\/x\/x"); is true
PASS testForwardSlash("x/x\/x", "x\/x\/x"); is true
PASS testForwardSlash("x\/x\/x", "x\/x\/x"); is true
FAIL testLineTerminator("\n"); should be false. Was true.
PASS testLineTerminator("\\n"); is false
FAIL testLineTerminator("\r"); should be false. Was true.
......@@ -51,8 +51,8 @@ FAIL testLineTerminator("\u2028"); should be false. Was true.
PASS testLineTerminator("\\u2028"); is false
FAIL testLineTerminator("\u2029"); should be false. Was true.
PASS testLineTerminator("\\u2029"); is false
PASS RegExp('[/]').source is '[/]'
FAIL RegExp('\\[/]').source should be \[\/]. Was \[/].
FAIL RegExp('[/]').source should be [/]. Was [\/].
PASS RegExp('\\[/]').source is '\\[\\/]'
PASS var o = new RegExp(); o.toString() === '/'+o.source+'/' && eval(o.toString()+'.exec(String())') is [""]
PASS successfullyParsed is true
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment