Chromium Code Reviews| Index: src/builtins.cc |
| diff --git a/src/builtins.cc b/src/builtins.cc |
| index a4cc0b4e04c29256e2ca00eddcd675bffd2337ca..2e91d6de91ed72442376ab341ac81dd02198c872 100644 |
| --- a/src/builtins.cc |
| +++ b/src/builtins.cc |
| @@ -9,6 +9,7 @@ |
| #include "src/arguments.h" |
| #include "src/base/once.h" |
| #include "src/bootstrapper.h" |
| +#include "src/dateparser-inl.h" |
| #include "src/elements.h" |
| #include "src/frames-inl.h" |
| #include "src/gdb-jit.h" |
| @@ -148,6 +149,17 @@ BUILTIN_LIST_C(DEF_ARG_TYPE) |
| // ---------------------------------------------------------------------------- |
| +#define CHECK_RECEIVER(Type, name, method) \ |
| + if (!args.receiver()->Is##Type()) { \ |
| + THROW_NEW_ERROR_RETURN_FAILURE( \ |
| + isolate, \ |
| + NewTypeError(MessageTemplate::kIncompatibleMethodReceiver, \ |
| + isolate->factory()->NewStringFromAsciiChecked(method), \ |
| + args.receiver())); \ |
| + } \ |
| + Handle<Type> name = Handle<Type>::cast(args.receiver()) |
| + |
| + |
| inline bool ClampedToInteger(Object* object, int* out) { |
| // This is an extended version of ECMA-262 7.1.11 handling signed values |
| // Try to convert object to a number and clamp values to [kMinInt, kMaxInt] |
| @@ -1938,18 +1950,385 @@ BUILTIN(ReflectSetPrototypeOf) { |
| } |
| -// ES6 section 20.3.4.45 Date.prototype [ @@toPrimitive ] ( hint ) |
| -BUILTIN(DateToPrimitive) { |
| +// ----------------------------------------------------------------------------- |
| +// ES6 section 20.3 Date Objects |
| + |
| + |
| +namespace { |
| + |
| +// ES6 section 20.3.1.1 Time Values and Time Range |
| +const double kMinYear = -1000000.0; |
| +const double kMaxYear = -kMinYear; |
| +const double kMinMonth = -10000000.0; |
| +const double kMaxMonth = -kMinMonth; |
| + |
| + |
| +// 20.3.1.2 Day Number and Time within Day |
| +const double kMsPerDay = 86400000.0; |
| + |
| + |
| +// ES6 section 20.3.1.11 Hours, Minutes, Second, and Milliseconds |
| +const double kMsPerSecond = 1000.0; |
| +const double kMsPerMinute = 60000.0; |
| +const double kMsPerHour = 3600000.0; |
| + |
| + |
| +// ES6 section 20.3.1.14 MakeDate (day, time) |
| +double MakeDate(double day, double time) { |
| + if (std::isfinite(day) && std::isfinite(time)) { |
| + return time + day * kMsPerDay; |
| + } |
| + return std::numeric_limits<double>::quiet_NaN(); |
| +} |
| + |
| + |
| +// ES6 section 20.3.1.13 MakeDay (year, month, date) |
| +double MakeDay(double year, double month, double date) { |
| + if ((kMinYear <= year && year <= kMaxYear) && |
| + (kMinMonth <= month && month <= kMaxMonth) && std::isfinite(date)) { |
| + int y = FastD2I(year); |
| + int m = FastD2I(month); |
| + y += m / 12; |
| + m %= 12; |
| + if (m < 0) { |
| + m += 12; |
| + y -= 1; |
| + } |
| + DCHECK_LE(0, m); |
| + DCHECK_LT(m, 12); |
| + |
| + // kYearDelta is an arbitrary number such that: |
| + // a) kYearDelta = -1 (mod 400) |
| + // b) year + kYearDelta > 0 for years in the range defined by |
| + // ECMA 262 - 15.9.1.1, i.e. upto 100,000,000 days on either side of |
| + // Jan 1 1970. This is required so that we don't run into integer |
| + // division of negative numbers. |
| + // c) there shouldn't be an overflow for 32-bit integers in the following |
| + // operations. |
| + static const int kYearDelta = 399999; |
| + static const int kBaseDay = |
| + 365 * (1970 + kYearDelta) + (1970 + kYearDelta) / 4 - |
| + (1970 + kYearDelta) / 100 + (1970 + kYearDelta) / 400; |
| + int day_from_year = 365 * (y + kYearDelta) + (y + kYearDelta) / 4 - |
| + (y + kYearDelta) / 100 + (y + kYearDelta) / 400 - |
| + kBaseDay; |
| + if ((y % 4 != 0) || (y % 100 == 0 && y % 400 != 0)) { |
| + static const int kDayFromMonth[] = {0, 31, 59, 90, 120, 151, |
| + 181, 212, 243, 273, 304, 334}; |
| + day_from_year += kDayFromMonth[m]; |
| + } else { |
| + static const int kDayFromMonth[] = {0, 31, 60, 91, 121, 152, |
| + 182, 213, 244, 274, 305, 335}; |
| + day_from_year += kDayFromMonth[m]; |
| + } |
| + return static_cast<double>(day_from_year - 1) + date; |
| + } |
| + return std::numeric_limits<double>::quiet_NaN(); |
| +} |
| + |
| + |
| +// ES6 section 20.3.1.12 MakeTime (hour, min, sec, ms) |
| +double MakeTime(double hour, double min, double sec, double ms) { |
| + if (std::isfinite(hour) && std::isfinite(min) && std::isfinite(sec) && |
| + std::isfinite(ms)) { |
| + double const h = DoubleToInteger(hour); |
| + double const m = DoubleToInteger(min); |
| + double const s = DoubleToInteger(sec); |
| + double const milli = DoubleToInteger(ms); |
| + return h * kMsPerHour + m * kMsPerMinute + s * kMsPerSecond + milli; |
| + } |
| + return std::numeric_limits<double>::quiet_NaN(); |
| +} |
| + |
| + |
| +// ES6 section 20.3.1.15 TimeClip (time) |
| +double TimeClip(double time) { |
| + if (-DateCache::kMaxTimeInMs <= time && time <= DateCache::kMaxTimeInMs) { |
| + return DoubleToInteger(time) + 0.0; |
| + } |
| + return std::numeric_limits<double>::quiet_NaN(); |
| +} |
| + |
| + |
| +const char* kShortWeekDays[] = {"Sun", "Mon", "Tue", "Wed", |
| + "Thu", "Fri", "Sat"}; |
| +const char* kShortMonths[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", |
| + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; |
| + |
| + |
| +// ES6 section 20.3.1.16 Date Time String Format |
| +double ParseDateTimeString(Handle<String> str) { |
| + Isolate* const isolate = str->GetIsolate(); |
| + str = String::Flatten(str); |
| + // TODO(bmeurer): Change DateParser to not use the FixedArray. |
| + Handle<FixedArray> tmp = |
| + isolate->factory()->NewFixedArray(DateParser::OUTPUT_SIZE); |
| + DisallowHeapAllocation no_gc; |
| + String::FlatContent str_content = str->GetFlatContent(); |
| + bool result; |
| + if (str_content.IsOneByte()) { |
| + result = DateParser::Parse(str_content.ToOneByteVector(), *tmp, |
| + isolate->unicode_cache()); |
| + } else { |
| + result = DateParser::Parse(str_content.ToUC16Vector(), *tmp, |
| + isolate->unicode_cache()); |
| + } |
| + if (!result) return std::numeric_limits<double>::quiet_NaN(); |
| + double const day = MakeDay(tmp->get(0)->Number(), tmp->get(1)->Number(), |
| + tmp->get(2)->Number()); |
| + double const time = MakeTime(tmp->get(3)->Number(), tmp->get(4)->Number(), |
| + tmp->get(5)->Number(), tmp->get(6)->Number()); |
| + double date = MakeDate(day, time); |
| + if (tmp->get(7)->IsNull()) { |
| + date = isolate->date_cache()->ToUTC(static_cast<int64_t>(date)); |
| + } else { |
| + date -= tmp->get(7)->Number() * 1000.0; |
| + } |
| + return date; |
| +} |
| + |
| + |
| +// ES6 section 20.3.4.41.1 ToDateString(tv) |
| +void ToDateString(double tv, Vector<char> str, DateCache* date_cache) { |
| + if (std::isnan(tv)) { |
| + SNPrintF(str, "Invalid Date"); |
| + } else { |
| + int64_t time_ms = static_cast<int64_t>(tv); |
| + int64_t local_time_ms = date_cache->ToLocal(time_ms); |
| + int year, month, day, weekday, hour, min, sec, ms; |
| + date_cache->BreakDown(local_time_ms, &year, &month, &day, &weekday, &hour, |
| + &min, &sec, &ms); |
| + int timezone_offset = -date_cache->TimezoneOffset(time_ms); |
| + int timezone_hour = std::abs(timezone_offset) / 60; |
| + int timezone_min = std::abs(timezone_offset) % 60; |
| + const char* local_timezone = date_cache->LocalTimezone(time_ms); |
| + SNPrintF(str, "%s %s %02d %4d %02d:%02d:%02d GMT%c%02d%02d (%s)", |
| + kShortWeekDays[weekday], kShortMonths[month], day, year, hour, min, |
| + sec, (timezone_offset < 0) ? '-' : '+', timezone_hour, |
| + timezone_min, local_timezone); |
| + } |
| +} |
| + |
| +} // namespace |
| + |
| + |
| +// ES6 section 20.3.2 The Date Constructor for the [[Call]] case. |
| +BUILTIN(DateConstructor) { |
| HandleScope scope(isolate); |
| - DCHECK_EQ(2, args.length()); |
| - if (!args.receiver()->IsJSReceiver()) { |
| + double const tv = JSDate::CurrentTimeValue(isolate); |
| + char buffer[128]; |
| + Vector<char> str(buffer, arraysize(buffer)); |
| + ToDateString(tv, str, isolate->date_cache()); |
| + return *isolate->factory()->NewStringFromAsciiChecked(str.start()); |
| +} |
| + |
| + |
| +// ES6 section 20.3.2 The Date Constructor for the [[Construct]] case. |
| +BUILTIN(DateConstructor_ConstructStub) { |
| + HandleScope scope(isolate); |
| + int const argc = args.length() - 1; |
| + Handle<JSFunction> target = args.target(); |
| + Handle<JSReceiver> new_target = Handle<JSReceiver>::cast(args.new_target()); |
| + double tv; |
|
Camillo Bruni
2016/01/05 10:26:59
I'd prefer having time_value where (even though th
Benedikt Meurer
2016/01/05 10:30:59
Done.
|
| + if (argc == 0) { |
| + tv = JSDate::CurrentTimeValue(isolate); |
| + } else if (argc == 1) { |
| + Handle<Object> value = args.at<Object>(1); |
| + if (value->IsJSDate()) { |
| + tv = Handle<JSDate>::cast(value)->value()->Number(); |
| + } else { |
| + ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, value, |
| + Object::ToPrimitive(value)); |
| + if (value->IsString()) { |
| + tv = ParseDateTimeString(Handle<String>::cast(value)); |
| + } else { |
| + ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, value, |
| + Object::ToNumber(value)); |
| + tv = value->Number(); |
| + } |
| + } |
| + } else { |
| + Handle<Object> year_object; |
| + ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, year_object, |
| + Object::ToNumber(args.at<Object>(1))); |
| + Handle<Object> month_object; |
| + ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, month_object, |
| + Object::ToNumber(args.at<Object>(2))); |
| + double year = year_object->Number(); |
| + double month = month_object->Number(); |
| + double date = 1.0, hours = 0.0, minutes = 0.0, seconds = 0.0, ms = 0.0; |
| + if (argc >= 3) { |
| + Handle<Object> date_object; |
| + ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, date_object, |
| + Object::ToNumber(args.at<Object>(3))); |
| + date = date_object->Number(); |
| + if (argc >= 4) { |
| + Handle<Object> hours_object; |
| + ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| + isolate, hours_object, Object::ToNumber(args.at<Object>(4))); |
| + hours = hours_object->Number(); |
| + if (argc >= 5) { |
| + Handle<Object> minutes_object; |
| + ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| + isolate, minutes_object, Object::ToNumber(args.at<Object>(5))); |
| + minutes = minutes_object->Number(); |
| + if (argc >= 6) { |
| + Handle<Object> seconds_object; |
| + ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| + isolate, seconds_object, Object::ToNumber(args.at<Object>(6))); |
| + seconds = seconds_object->Number(); |
| + if (argc >= 7) { |
| + Handle<Object> ms_object; |
| + ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| + isolate, ms_object, Object::ToNumber(args.at<Object>(7))); |
| + ms = ms_object->Number(); |
| + } |
| + } |
| + } |
| + } |
| + } |
| + if (!std::isnan(year)) { |
| + double const y = DoubleToInteger(year); |
| + if (0.0 <= y && y <= 99) year = 1900 + y; |
| + } |
| + double const day = MakeDay(year, month, date); |
| + double const time = MakeTime(hours, minutes, seconds, ms); |
| + tv = MakeDate(day, time); |
| + if (tv >= -DateCache::kMaxTimeBeforeUTCInMs && |
| + tv <= DateCache::kMaxTimeBeforeUTCInMs) { |
| + tv = isolate->date_cache()->ToUTC(static_cast<int64_t>(tv)); |
| + } else { |
| + tv = std::numeric_limits<double>::quiet_NaN(); |
| + } |
| + } |
| + Handle<JSDate> result; |
| + ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, result, |
| + JSDate::New(target, new_target, tv)); |
| + return *result; |
| +} |
| + |
| + |
| +// ES6 section 20.3.3.1 Date.now ( ) |
| +BUILTIN(DateNow) { |
| + HandleScope scope(isolate); |
| + return *isolate->factory()->NewNumber(JSDate::CurrentTimeValue(isolate)); |
| +} |
| + |
| + |
| +// ES6 section 20.3.3.2 Date.parse ( string ) |
| +BUILTIN(DateParse) { |
| + HandleScope scope(isolate); |
| + Handle<String> string; |
| + ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| + isolate, string, |
| + Object::ToString(isolate, args.atOrUndefined(isolate, 1))); |
| + double const tv = ParseDateTimeString(string); |
| + return *isolate->factory()->NewNumber(tv); |
| +} |
| + |
| + |
| +// ES6 section 20.3.3.4 Date.UTC (year,month,date,hours,minutes,seconds,ms) |
| +BUILTIN(DateUTC) { |
| + HandleScope scope(isolate); |
| + int const argc = args.length() - 1; |
| + double year = std::numeric_limits<double>::quiet_NaN(); |
| + double month = std::numeric_limits<double>::quiet_NaN(); |
| + double date = 1.0, hours = 0.0, minutes = 0.0, seconds = 0.0, ms = 0.0; |
| + if (argc >= 1) { |
| + Handle<Object> year_object; |
| + ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, year_object, |
| + Object::ToNumber(args.at<Object>(1))); |
| + year = year_object->Number(); |
| + if (argc >= 2) { |
| + Handle<Object> month_object; |
| + ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, month_object, |
| + Object::ToNumber(args.at<Object>(2))); |
| + month = month_object->Number(); |
| + if (argc >= 3) { |
| + Handle<Object> date_object; |
| + ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| + isolate, date_object, Object::ToNumber(args.at<Object>(3))); |
| + date = date_object->Number(); |
| + if (argc >= 4) { |
| + Handle<Object> hours_object; |
| + ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| + isolate, hours_object, Object::ToNumber(args.at<Object>(4))); |
| + hours = hours_object->Number(); |
| + if (argc >= 5) { |
| + Handle<Object> minutes_object; |
| + ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| + isolate, minutes_object, Object::ToNumber(args.at<Object>(5))); |
| + minutes = minutes_object->Number(); |
| + if (argc >= 6) { |
| + Handle<Object> seconds_object; |
| + ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| + isolate, seconds_object, |
| + Object::ToNumber(args.at<Object>(6))); |
| + seconds = seconds_object->Number(); |
| + if (argc >= 7) { |
| + Handle<Object> ms_object; |
| + ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| + isolate, ms_object, Object::ToNumber(args.at<Object>(7))); |
| + ms = ms_object->Number(); |
| + } |
| + } |
| + } |
| + } |
| + } |
| + } |
| + } |
| + if (!std::isnan(year)) { |
| + double const y = DoubleToInteger(year); |
| + if (0.0 <= y && y <= 99) year = 1900 + y; |
| + } |
| + double const day = MakeDay(year, month, date); |
| + double const time = MakeTime(hours, minutes, seconds, ms); |
| + return *isolate->factory()->NewNumber(TimeClip(MakeDate(day, time))); |
|
Camillo Bruni
2016/01/05 10:26:59
Could you combine this part with the parsing step
|
| +} |
| + |
| + |
| +// ES6 section 20.3.4.36 Date.prototype.toISOString ( ) |
| +BUILTIN(DatePrototypeToISOString) { |
| + HandleScope scope(isolate); |
| + CHECK_RECEIVER(JSDate, date, "Date.prototype.toISOString"); |
| + double const tv = date->value()->Number(); |
| + if (std::isnan(tv)) { |
|
Camillo Bruni
2016/01/05 10:26:59
shouldn't +/-INF also be covered here? AFAIK std:i
Benedikt Meurer
2016/01/05 10:30:59
JSDate values can only be NaN or in the valid date
|
| THROW_NEW_ERROR_RETURN_FAILURE( |
| - isolate, NewTypeError(MessageTemplate::kIncompatibleMethodReceiver, |
| - isolate->factory()->NewStringFromAsciiChecked( |
| - "Date.prototype [ @@toPrimitive ]"), |
| - args.receiver())); |
| + isolate, NewRangeError(MessageTemplate::kInvalidTimeValue)); |
| + } |
| + int64_t const time_ms = static_cast<int64_t>(tv); |
| + int year, month, day, weekday, hour, min, sec, ms; |
| + isolate->date_cache()->BreakDown(time_ms, &year, &month, &day, &weekday, |
| + &hour, &min, &sec, &ms); |
| + char buffer[128]; |
| + Vector<char> str(buffer, arraysize(buffer)); |
| + if (year >= 0 && year <= 9999) { |
| + SNPrintF(str, "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", year, month + 1, day, |
| + hour, min, sec, ms); |
| + } else if (year < 0) { |
| + SNPrintF(str, "-%06d-%02d-%02dT%02d:%02d:%02d.%03dZ", -year, month + 1, day, |
| + hour, min, sec, ms); |
| + } else { |
| + SNPrintF(str, "+%06d-%02d-%02dT%02d:%02d:%02d.%03dZ", year, month + 1, day, |
| + hour, min, sec, ms); |
| } |
| - Handle<JSReceiver> receiver = args.at<JSReceiver>(0); |
| + return *isolate->factory()->NewStringFromAsciiChecked(str.start()); |
| +} |
| + |
| + |
| +// ES6 section 20.3.4.44 Date.prototype.valueOf ( ) |
| +BUILTIN(DatePrototypeValueOf) { |
| + HandleScope scope(isolate); |
| + CHECK_RECEIVER(JSDate, date, "Date.prototype.valueOf"); |
| + return date->value(); |
| +} |
| + |
| + |
| +// ES6 section 20.3.4.45 Date.prototype [ @@toPrimitive ] ( hint ) |
| +BUILTIN(DatePrototypeToPrimitive) { |
| + HandleScope scope(isolate); |
| + DCHECK_EQ(2, args.length()); |
| + CHECK_RECEIVER(JSReceiver, receiver, "Date.prototype [ @@toPrimitive ]"); |
| Handle<Object> hint = args.at<Object>(1); |
| Handle<Object> result; |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, result, |