Browse Source

Missing Date methods, better overflow handling, refactoring (#147)

Dmitry Panov 5 years ago
parent
commit
60b86f2254
10 changed files with 657 additions and 435 deletions
  1. 479 385
      builtin_date.go
  2. 67 6
      date.go
  3. 10 0
      date_test.go
  4. 55 21
      object.go
  5. 8 0
      runtime.go
  6. 1 1
      string_ascii.go
  7. 1 1
      string_unicode.go
  8. 4 0
      tc39_test.go
  9. 21 17
      value.go
  10. 11 4
      vm.go

File diff suppressed because it is too large
+ 479 - 385
builtin_date.go


+ 67 - 6
date.go

@@ -1,6 +1,7 @@
 package goja
 
 import (
+	"math"
 	"time"
 )
 
@@ -13,12 +14,14 @@ const (
 	datetimeLayout_en_GB = "01/02/2006, 15:04:05"
 	dateLayout_en_GB     = "01/02/2006"
 	timeLayout_en_GB     = "15:04:05"
+
+	maxTime   = 8.64e15
+	timeUnset = math.MinInt64
 )
 
 type dateObject struct {
 	baseObject
-	time  time.Time
-	isSet bool
+	msec int64
 }
 
 var (
@@ -65,7 +68,7 @@ func dateParse(date string) (time.Time, bool) {
 		}
 	}
 	unix := timeToMsec(t)
-	return t, err == nil && unix >= -8640000000000000 && unix <= 8640000000000000
+	return t, err == nil && unix >= -maxTime && unix <= maxTime
 }
 
 func (r *Runtime) newDateObject(t time.Time, isSet bool, proto *Object) *Object {
@@ -77,8 +80,11 @@ func (r *Runtime) newDateObject(t time.Time, isSet bool, proto *Object) *Object
 	d.prototype = proto
 	d.extensible = true
 	d.init()
-	d.time = t.In(time.Local)
-	d.isSet = isSet
+	if isSet {
+		d.msec = timeToMsec(t)
+	} else {
+		d.msec = timeUnset
+	}
 	return v
 }
 
@@ -86,13 +92,68 @@ func dateFormat(t time.Time) string {
 	return t.Local().Format(dateTimeLayout)
 }
 
+func timeFromMsec(msec int64) time.Time {
+	sec := msec / 1000
+	nsec := (msec % 1000) * 1e6
+	return time.Unix(sec, nsec)
+}
+
+func timeToMsec(t time.Time) int64 {
+	return t.Unix()*1000 + int64(t.Nanosecond())/1e6
+}
+
 func (d *dateObject) toPrimitive() Value {
 	return d.toPrimitiveString()
 }
 
 func (d *dateObject) export() interface{} {
-	if d.isSet {
+	if d.isSet() {
 		return d.time
 	}
 	return nil
 }
+
+func (d *dateObject) setTime(year, m, day, hour, min, sec, nsec int64) Value {
+	t, ok := mkTime(year, m, day, hour, min, sec, nsec, time.Local)
+	if ok {
+		return d.setTimeMs(timeToMsec(t))
+	}
+	d.unset()
+	return _NaN
+}
+
+func (d *dateObject) setTimeUTC(year, m, day, hour, min, sec, nsec int64) Value {
+	t, ok := mkTime(year, m, day, hour, min, sec, nsec, time.UTC)
+	if ok {
+		t = t.In(time.Local)
+		return d.setTimeMs(timeToMsec(t))
+	}
+	d.unset()
+	return _NaN
+}
+
+func (d *dateObject) setTimeMs(ms int64) Value {
+	if ms >= 0 && ms <= maxTime || ms < 0 && ms >= -maxTime {
+		d.msec = ms
+		return intToValue(ms)
+	}
+
+	d.unset()
+	return _NaN
+}
+
+func (d *dateObject) isSet() bool {
+	return d.msec != timeUnset
+}
+
+func (d *dateObject) unset() {
+	d.msec = timeUnset
+}
+
+func (d *dateObject) time() time.Time {
+	return timeFromMsec(d.msec)
+}
+
+func (d *dateObject) timeUTC() time.Time {
+	return timeFromMsec(d.msec).In(time.UTC)
+}

+ 10 - 0
date_test.go

@@ -367,3 +367,13 @@ assert.sameValue(Date.parse(aboveRange), NaN, "parse above maximum time value");
 
 	testScript1(TESTLIB+SCRIPT, _undefined, t)
 }
+
+func TestDateMaxValues(t *testing.T) {
+	const SCRIPT = `
+	assert.sameValue((new Date(0)).setUTCMilliseconds(8.64e15), 8.64e15);
+	assert.sameValue((new Date(0)).setUTCSeconds(8640000000000), 8.64e15);
+	assert.sameValue((new Date(0)).setUTCMilliseconds(-8.64e15), -8.64e15);
+	assert.sameValue((new Date(0)).setUTCSeconds(-8640000000000), -8.64e15);
+	`
+	testScript1(TESTLIB+SCRIPT, _undefined, t)
+}

+ 55 - 21
object.go

@@ -31,6 +31,12 @@ const (
 	classStringIterator = "String Iterator"
 )
 
+var (
+	hintDefault Value = asciiString("default")
+	hintNumber  Value = asciiString("number")
+	hintString  Value = asciiString("string")
+)
+
 type weakCollection interface {
 	removeId(uint64)
 }
@@ -802,19 +808,8 @@ func (o *baseObject) _putSym(s *valueSymbol, prop Value) {
 	o.symValues.set(s, prop)
 }
 
-func (o *baseObject) tryExoticToPrimitive(hint string) Value {
-	exoticToPrimitive := toMethod(o.getSym(symToPrimitive, nil))
-	if exoticToPrimitive != nil {
-		return exoticToPrimitive(FunctionCall{
-			This:      o.val,
-			Arguments: []Value{newStringValue(hint)},
-		})
-	}
-	return nil
-}
-
-func (o *baseObject) tryPrimitive(methodName string) Value {
-	if method, ok := o.val.self.getStr(unistring.String(methodName), nil).(*Object); ok {
+func (o *baseObject) tryPrimitive(methodName unistring.String) Value {
+	if method, ok := o.val.self.getStr(methodName, nil).(*Object); ok {
 		if call, ok := method.self.assertCallable(); ok {
 			v := call(FunctionCall{
 				This: o.val,
@@ -828,10 +823,6 @@ func (o *baseObject) tryPrimitive(methodName string) Value {
 }
 
 func (o *baseObject) toPrimitiveNumber() Value {
-	if v := o.tryExoticToPrimitive("number"); v != nil {
-		return v
-	}
-
 	if v := o.tryPrimitive("valueOf"); v != nil {
 		return v
 	}
@@ -845,24 +836,67 @@ func (o *baseObject) toPrimitiveNumber() Value {
 }
 
 func (o *baseObject) toPrimitiveString() Value {
-	if v := o.tryExoticToPrimitive("string"); v != nil {
+	if v := o.tryPrimitive("toString"); v != nil {
 		return v
 	}
 
-	if v := o.tryPrimitive("toString"); v != nil {
+	if v := o.tryPrimitive("valueOf"); v != nil {
 		return v
 	}
 
+	o.val.runtime.typeErrorResult(true, "Could not convert %v to primitive", o)
+	return nil
+}
+
+func (o *baseObject) toPrimitive() Value {
 	if v := o.tryPrimitive("valueOf"); v != nil {
 		return v
 	}
 
+	if v := o.tryPrimitive("toString"); v != nil {
+		return v
+	}
+
 	o.val.runtime.typeErrorResult(true, "Could not convert %v to primitive", o)
 	return nil
 }
 
-func (o *baseObject) toPrimitive() Value {
-	return o.toPrimitiveNumber()
+func (o *Object) tryExoticToPrimitive(hint Value) Value {
+	exoticToPrimitive := toMethod(o.self.getSym(symToPrimitive, nil))
+	if exoticToPrimitive != nil {
+		ret := exoticToPrimitive(FunctionCall{
+			This:      o,
+			Arguments: []Value{hint},
+		})
+		if _, fail := ret.(*Object); !fail {
+			return ret
+		}
+		panic(o.runtime.NewTypeError("Cannot convert object to primitive value"))
+	}
+	return nil
+}
+
+func (o *Object) toPrimitiveNumber() Value {
+	if v := o.tryExoticToPrimitive(hintNumber); v != nil {
+		return v
+	}
+
+	return o.self.toPrimitiveNumber()
+}
+
+func (o *Object) toPrimitiveString() Value {
+	if v := o.tryExoticToPrimitive(hintString); v != nil {
+		return v
+	}
+
+	return o.self.toPrimitiveString()
+}
+
+func (o *Object) toPrimitive() Value {
+	if v := o.tryExoticToPrimitive(hintDefault); v != nil {
+		return v
+	}
+	return o.self.toPrimitive()
 }
 
 func (o *baseObject) assertCallable() (func(FunctionCall) Value, bool) {

+ 8 - 0
runtime.go

@@ -1963,3 +1963,11 @@ func isRegexp(v Value) bool {
 	}
 	return false
 }
+
+func limitCallArgs(call FunctionCall, n int) FunctionCall {
+	if len(call.Arguments) > n {
+		return FunctionCall{This: call.This, Arguments: call.Arguments[:n]}
+	} else {
+		return call
+	}
+}

+ 1 - 1
string_ascii.go

@@ -201,7 +201,7 @@ func (s asciiString) Equals(other Value) bool {
 	}
 
 	if o, ok := other.(*Object); ok {
-		return s.Equals(o.self.toPrimitive())
+		return s.Equals(o.toPrimitive())
 	}
 	return false
 }

+ 1 - 1
string_unicode.go

@@ -208,7 +208,7 @@ func (s unicodeString) Equals(other Value) bool {
 	}
 
 	if o, ok := other.(*Object); ok {
-		return s.Equals(o.self.toPrimitive())
+		return s.Equals(o.toPrimitive())
 	}
 	return false
 }

+ 4 - 0
tc39_test.go

@@ -61,6 +61,8 @@ var (
 		"test/language/statements/class/subclass/builtin-objects/String/super-must-be-called.js":          true,
 		"test/language/statements/class/subclass/builtin-objects/String/regular-subclassing.js":           true,
 		"test/language/statements/class/subclass/builtin-objects/String/length.js":                        true,
+		"test/language/statements/class/subclass/builtin-objects/Date/super-must-be-called.js":            true,
+		"test/language/statements/class/subclass/builtin-objects/Date/regular-subclassing.js":             true,
 
 		// full unicode regexp flag
 		"test/built-ins/RegExp/prototype/Symbol.match/u-advance-after-empty.js":               true,
@@ -106,6 +108,7 @@ var (
 		"12.9.4",
 		"19.1",
 		"19.4",
+		"20.3",
 		"21.1",
 		"21.2.5.6",
 		"22.1.2.1",
@@ -130,6 +133,7 @@ var (
 		"sec-array.prototype.includes",
 		"sec-%typedarray%",
 		"sec-string.prototype",
+		"sec-date",
 	}
 )
 

+ 21 - 17
value.go

@@ -189,7 +189,7 @@ func (i valueInt) Equals(other Value) bool {
 	case valueBool:
 		return int64(i) == o.ToInteger()
 	case *Object:
-		return i.Equals(o.self.toPrimitiveNumber())
+		return i.Equals(o.toPrimitiveNumber())
 	}
 
 	return false
@@ -527,16 +527,20 @@ func (p *valueProperty) hash(*maphash.Hash) uint64 {
 	panic("valueProperty should never be used in maps or sets")
 }
 
-func (f valueFloat) ToInteger() int64 {
+func floatToIntClip(n float64) int64 {
 	switch {
-	case math.IsNaN(float64(f)):
+	case math.IsNaN(n):
 		return 0
-	case math.IsInf(float64(f), 1):
-		return int64(math.MaxInt64)
-	case math.IsInf(float64(f), -1):
-		return int64(math.MinInt64)
+	case n >= math.MaxInt64:
+		return math.MaxInt64
+	case n <= math.MinInt64:
+		return math.MinInt64
 	}
-	return int64(f)
+	return int64(n)
+}
+
+func (f valueFloat) ToInteger() int64 {
+	return floatToIntClip(float64(f))
 }
 
 func (f valueFloat) toString() valueString {
@@ -623,7 +627,7 @@ func (f valueFloat) Equals(other Value) bool {
 	case valueString, valueBool:
 		return float64(f) == o.ToFloat()
 	case *Object:
-		return f.Equals(o.self.toPrimitiveNumber())
+		return f.Equals(o.toPrimitiveNumber())
 	}
 
 	return false
@@ -660,27 +664,27 @@ func (f valueFloat) hash(*maphash.Hash) uint64 {
 }
 
 func (o *Object) ToInteger() int64 {
-	return o.self.toPrimitiveNumber().ToNumber().ToInteger()
+	return o.toPrimitiveNumber().ToNumber().ToInteger()
 }
 
 func (o *Object) toString() valueString {
-	return o.self.toPrimitiveString().toString()
+	return o.toPrimitiveString().toString()
 }
 
 func (o *Object) string() unistring.String {
-	return o.self.toPrimitiveString().string()
+	return o.toPrimitiveString().string()
 }
 
 func (o *Object) ToPrimitiveString() Value {
-	return o.self.toPrimitiveString().ToPrimitiveString()
+	return o.toPrimitiveString().ToPrimitiveString()
 }
 
 func (o *Object) String() string {
-	return o.self.toPrimitiveString().String()
+	return o.toPrimitiveString().String()
 }
 
 func (o *Object) ToFloat() float64 {
-	return o.self.toPrimitiveNumber().ToFloat()
+	return o.toPrimitiveNumber().ToFloat()
 }
 
 func (o *Object) ToBoolean() bool {
@@ -692,7 +696,7 @@ func (o *Object) ToObject(*Runtime) *Object {
 }
 
 func (o *Object) ToNumber() Value {
-	return o.self.toPrimitiveNumber().ToNumber()
+	return o.toPrimitiveNumber().ToNumber()
 }
 
 func (o *Object) SameAs(other Value) bool {
@@ -709,7 +713,7 @@ func (o *Object) Equals(other Value) bool {
 
 	switch o1 := other.(type) {
 	case valueInt, valueFloat, valueString:
-		return o.self.toPrimitive().Equals(other)
+		return o.toPrimitive().Equals(other)
 	case valueBool:
 		return o.Equals(o1.ToNumber())
 	}

+ 11 - 4
vm.go

@@ -133,7 +133,7 @@ func intToValue(i int64) Value {
 		}
 		return valueInt(i)
 	}
-	return valueFloat(float64(i))
+	return valueFloat(i)
 }
 
 func floatToInt(f float64) (result int64, ok bool) {
@@ -617,11 +617,11 @@ func (_add) exec(vm *vm) {
 	left := vm.stack[vm.sp-2]
 
 	if o, ok := left.(*Object); ok {
-		left = o.self.toPrimitive()
+		left = o.toPrimitive()
 	}
 
 	if o, ok := right.(*Object); ok {
-		right = o.self.toPrimitive()
+		right = o.toPrimitive()
 	}
 
 	var ret Value
@@ -1983,7 +1983,14 @@ func (_not) exec(vm *vm) {
 
 func toPrimitiveNumber(v Value) Value {
 	if o, ok := v.(*Object); ok {
-		return o.self.toPrimitiveNumber()
+		return o.toPrimitiveNumber()
+	}
+	return v
+}
+
+func toPrimitive(v Value) Value {
+	if o, ok := v.(*Object); ok {
+		return o.toPrimitive()
 	}
 	return v
 }

Some files were not shown because too many files changed in this diff