1package goja 2 3import ( 4 "fmt" 5 "go/ast" 6 "reflect" 7 "strings" 8 9 "github.com/dop251/goja/parser" 10 "github.com/dop251/goja/unistring" 11) 12 13// JsonEncodable allows custom JSON encoding by JSON.stringify() 14// Note that if the returned value itself also implements JsonEncodable, it won't have any effect. 15type JsonEncodable interface { 16 JsonEncodable() interface{} 17} 18 19// FieldNameMapper provides custom mapping between Go and JavaScript property names. 20type FieldNameMapper interface { 21 // FieldName returns a JavaScript name for the given struct field in the given type. 22 // If this method returns "" the field becomes hidden. 23 FieldName(t reflect.Type, f reflect.StructField) string 24 25 // MethodName returns a JavaScript name for the given method in the given type. 26 // If this method returns "" the method becomes hidden. 27 MethodName(t reflect.Type, m reflect.Method) string 28} 29 30type tagFieldNameMapper struct { 31 tagName string 32 uncapMethods bool 33} 34 35func (tfm tagFieldNameMapper) FieldName(_ reflect.Type, f reflect.StructField) string { 36 tag := f.Tag.Get(tfm.tagName) 37 if idx := strings.IndexByte(tag, ','); idx != -1 { 38 tag = tag[:idx] 39 } 40 if parser.IsIdentifier(tag) { 41 return tag 42 } 43 return "" 44} 45 46func uncapitalize(s string) string { 47 return strings.ToLower(s[0:1]) + s[1:] 48} 49 50func (tfm tagFieldNameMapper) MethodName(_ reflect.Type, m reflect.Method) string { 51 if tfm.uncapMethods { 52 return uncapitalize(m.Name) 53 } 54 return m.Name 55} 56 57type uncapFieldNameMapper struct { 58} 59 60func (u uncapFieldNameMapper) FieldName(_ reflect.Type, f reflect.StructField) string { 61 return uncapitalize(f.Name) 62} 63 64func (u uncapFieldNameMapper) MethodName(_ reflect.Type, m reflect.Method) string { 65 return uncapitalize(m.Name) 66} 67 68type reflectFieldInfo struct { 69 Index []int 70 Anonymous bool 71} 72 73type reflectTypeInfo struct { 74 Fields map[string]reflectFieldInfo 75 Methods map[string]int 76 FieldNames, MethodNames []string 77} 78 79type objectGoReflect struct { 80 baseObject 81 origValue, value reflect.Value 82 83 valueTypeInfo, origValueTypeInfo *reflectTypeInfo 84 85 toJson func() interface{} 86} 87 88func (o *objectGoReflect) init() { 89 o.baseObject.init() 90 switch o.value.Kind() { 91 case reflect.Bool: 92 o.class = classBoolean 93 o.prototype = o.val.runtime.global.BooleanPrototype 94 case reflect.String: 95 o.class = classString 96 o.prototype = o.val.runtime.global.StringPrototype 97 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 98 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, 99 reflect.Float32, reflect.Float64: 100 101 o.class = classNumber 102 o.prototype = o.val.runtime.global.NumberPrototype 103 default: 104 o.class = classObject 105 o.prototype = o.val.runtime.global.ObjectPrototype 106 } 107 o.extensible = true 108 109 o.baseObject._putProp("toString", o.val.runtime.newNativeFunc(o.toStringFunc, nil, "toString", nil, 0), true, false, true) 110 o.baseObject._putProp("valueOf", o.val.runtime.newNativeFunc(o.valueOfFunc, nil, "valueOf", nil, 0), true, false, true) 111 112 o.valueTypeInfo = o.val.runtime.typeInfo(o.value.Type()) 113 o.origValueTypeInfo = o.val.runtime.typeInfo(o.origValue.Type()) 114 115 if j, ok := o.origValue.Interface().(JsonEncodable); ok { 116 o.toJson = j.JsonEncodable 117 } 118} 119 120func (o *objectGoReflect) toStringFunc(FunctionCall) Value { 121 return o.toPrimitiveString() 122} 123 124func (o *objectGoReflect) valueOfFunc(FunctionCall) Value { 125 return o.toPrimitive() 126} 127 128func (o *objectGoReflect) getStr(name unistring.String, receiver Value) Value { 129 if v := o._get(name.String()); v != nil { 130 return v 131 } 132 return o.baseObject.getStr(name, receiver) 133} 134 135func (o *objectGoReflect) _getField(jsName string) reflect.Value { 136 if info, exists := o.valueTypeInfo.Fields[jsName]; exists { 137 v := o.value.FieldByIndex(info.Index) 138 return v 139 } 140 141 return reflect.Value{} 142} 143 144func (o *objectGoReflect) _getMethod(jsName string) reflect.Value { 145 if idx, exists := o.origValueTypeInfo.Methods[jsName]; exists { 146 return o.origValue.Method(idx) 147 } 148 149 return reflect.Value{} 150} 151 152func (o *objectGoReflect) getAddr(v reflect.Value) reflect.Value { 153 if (v.Kind() == reflect.Struct || v.Kind() == reflect.Slice) && v.CanAddr() { 154 return v.Addr() 155 } 156 return v 157} 158 159func (o *objectGoReflect) _get(name string) Value { 160 if o.value.Kind() == reflect.Struct { 161 if v := o._getField(name); v.IsValid() { 162 return o.val.runtime.ToValue(o.getAddr(v).Interface()) 163 } 164 } 165 166 if v := o._getMethod(name); v.IsValid() { 167 return o.val.runtime.ToValue(v.Interface()) 168 } 169 170 return nil 171} 172 173func (o *objectGoReflect) getOwnPropStr(name unistring.String) Value { 174 n := name.String() 175 if o.value.Kind() == reflect.Struct { 176 if v := o._getField(n); v.IsValid() { 177 return &valueProperty{ 178 value: o.val.runtime.ToValue(o.getAddr(v).Interface()), 179 writable: v.CanSet(), 180 enumerable: true, 181 } 182 } 183 } 184 185 if v := o._getMethod(n); v.IsValid() { 186 return &valueProperty{ 187 value: o.val.runtime.ToValue(v.Interface()), 188 enumerable: true, 189 } 190 } 191 192 return nil 193} 194 195func (o *objectGoReflect) setOwnStr(name unistring.String, val Value, throw bool) bool { 196 has, ok := o._put(name.String(), val, throw) 197 if !has { 198 if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok { 199 o.val.runtime.typeErrorResult(throw, "Cannot assign to property %s of a host object", name) 200 return false 201 } else { 202 return res 203 } 204 } 205 return ok 206} 207 208func (o *objectGoReflect) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { 209 return o._setForeignStr(name, trueValIfPresent(o._has(name.String())), val, receiver, throw) 210} 211 212func (o *objectGoReflect) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) { 213 return o._setForeignIdx(idx, nil, val, receiver, throw) 214} 215 216func (o *objectGoReflect) _put(name string, val Value, throw bool) (has, ok bool) { 217 if o.value.Kind() == reflect.Struct { 218 if v := o._getField(name); v.IsValid() { 219 if !v.CanSet() { 220 o.val.runtime.typeErrorResult(throw, "Cannot assign to a non-addressable or read-only property %s of a host object", name) 221 return true, false 222 } 223 err := o.val.runtime.toReflectValue(val, v, &objectExportCtx{}) 224 if err != nil { 225 o.val.runtime.typeErrorResult(throw, "Go struct conversion error: %v", err) 226 return true, false 227 } 228 return true, true 229 } 230 } 231 return false, false 232} 233 234func (o *objectGoReflect) _putProp(name unistring.String, value Value, writable, enumerable, configurable bool) Value { 235 if _, ok := o._put(name.String(), value, false); ok { 236 return value 237 } 238 return o.baseObject._putProp(name, value, writable, enumerable, configurable) 239} 240 241func (r *Runtime) checkHostObjectPropertyDescr(name unistring.String, descr PropertyDescriptor, throw bool) bool { 242 if descr.Getter != nil || descr.Setter != nil { 243 r.typeErrorResult(throw, "Host objects do not support accessor properties") 244 return false 245 } 246 if descr.Writable == FLAG_FALSE { 247 r.typeErrorResult(throw, "Host object field %s cannot be made read-only", name) 248 return false 249 } 250 if descr.Configurable == FLAG_TRUE { 251 r.typeErrorResult(throw, "Host object field %s cannot be made configurable", name) 252 return false 253 } 254 return true 255} 256 257func (o *objectGoReflect) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { 258 if o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) { 259 n := name.String() 260 if has, ok := o._put(n, descr.Value, throw); !has { 261 o.val.runtime.typeErrorResult(throw, "Cannot define property '%s' on a host object", n) 262 return false 263 } else { 264 return ok 265 } 266 } 267 return false 268} 269 270func (o *objectGoReflect) _has(name string) bool { 271 if o.value.Kind() == reflect.Struct { 272 if v := o._getField(name); v.IsValid() { 273 return true 274 } 275 } 276 if v := o._getMethod(name); v.IsValid() { 277 return true 278 } 279 return false 280} 281 282func (o *objectGoReflect) hasOwnPropertyStr(name unistring.String) bool { 283 return o._has(name.String()) 284} 285 286func (o *objectGoReflect) _toNumber() Value { 287 switch o.value.Kind() { 288 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 289 return intToValue(o.value.Int()) 290 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 291 return intToValue(int64(o.value.Uint())) 292 case reflect.Bool: 293 if o.value.Bool() { 294 return intToValue(1) 295 } else { 296 return intToValue(0) 297 } 298 case reflect.Float32, reflect.Float64: 299 return floatToValue(o.value.Float()) 300 } 301 return nil 302} 303 304func (o *objectGoReflect) _toString() Value { 305 switch o.value.Kind() { 306 case reflect.String: 307 return newStringValue(o.value.String()) 308 case reflect.Bool: 309 if o.value.Interface().(bool) { 310 return stringTrue 311 } else { 312 return stringFalse 313 } 314 } 315 switch v := o.origValue.Interface().(type) { 316 case fmt.Stringer: 317 return newStringValue(v.String()) 318 case error: 319 return newStringValue(v.Error()) 320 } 321 322 return stringObjectObject 323} 324 325func (o *objectGoReflect) toPrimitiveNumber() Value { 326 if v := o._toNumber(); v != nil { 327 return v 328 } 329 return o._toString() 330} 331 332func (o *objectGoReflect) toPrimitiveString() Value { 333 if v := o._toNumber(); v != nil { 334 return v.toString() 335 } 336 return o._toString() 337} 338 339func (o *objectGoReflect) toPrimitive() Value { 340 if o.prototype == o.val.runtime.global.NumberPrototype { 341 return o.toPrimitiveNumber() 342 } 343 return o.toPrimitiveString() 344} 345 346func (o *objectGoReflect) deleteStr(name unistring.String, throw bool) bool { 347 n := name.String() 348 if o._has(n) { 349 o.val.runtime.typeErrorResult(throw, "Cannot delete property %s from a Go type", n) 350 return false 351 } 352 return o.baseObject.deleteStr(name, throw) 353} 354 355type goreflectPropIter struct { 356 o *objectGoReflect 357 idx int 358} 359 360func (i *goreflectPropIter) nextField() (propIterItem, iterNextFunc) { 361 names := i.o.valueTypeInfo.FieldNames 362 if i.idx < len(names) { 363 name := names[i.idx] 364 i.idx++ 365 return propIterItem{name: unistring.NewFromString(name), enumerable: _ENUM_TRUE}, i.nextField 366 } 367 368 i.idx = 0 369 return i.nextMethod() 370} 371 372func (i *goreflectPropIter) nextMethod() (propIterItem, iterNextFunc) { 373 names := i.o.origValueTypeInfo.MethodNames 374 if i.idx < len(names) { 375 name := names[i.idx] 376 i.idx++ 377 return propIterItem{name: unistring.NewFromString(name), enumerable: _ENUM_TRUE}, i.nextMethod 378 } 379 380 return propIterItem{}, nil 381} 382 383func (o *objectGoReflect) enumerateOwnKeys() iterNextFunc { 384 r := &goreflectPropIter{ 385 o: o, 386 } 387 if o.value.Kind() == reflect.Struct { 388 return r.nextField 389 } 390 391 return r.nextMethod 392} 393 394func (o *objectGoReflect) ownKeys(_ bool, accum []Value) []Value { 395 // all own keys are enumerable 396 for _, name := range o.valueTypeInfo.FieldNames { 397 accum = append(accum, newStringValue(name)) 398 } 399 400 for _, name := range o.valueTypeInfo.MethodNames { 401 accum = append(accum, newStringValue(name)) 402 } 403 404 return accum 405} 406 407func (o *objectGoReflect) export(*objectExportCtx) interface{} { 408 return o.origValue.Interface() 409} 410 411func (o *objectGoReflect) exportType() reflect.Type { 412 return o.origValue.Type() 413} 414 415func (o *objectGoReflect) equal(other objectImpl) bool { 416 if other, ok := other.(*objectGoReflect); ok { 417 return o.value.Interface() == other.value.Interface() 418 } 419 return false 420} 421 422func (r *Runtime) buildFieldInfo(t reflect.Type, index []int, info *reflectTypeInfo) { 423 n := t.NumField() 424 for i := 0; i < n; i++ { 425 field := t.Field(i) 426 name := field.Name 427 if !ast.IsExported(name) { 428 continue 429 } 430 if r.fieldNameMapper != nil { 431 name = r.fieldNameMapper.FieldName(t, field) 432 } 433 434 if name != "" { 435 if inf, exists := info.Fields[name]; !exists { 436 info.FieldNames = append(info.FieldNames, name) 437 } else { 438 if len(inf.Index) <= len(index) { 439 continue 440 } 441 } 442 } 443 444 if name != "" || field.Anonymous { 445 idx := make([]int, len(index)+1) 446 copy(idx, index) 447 idx[len(idx)-1] = i 448 449 if name != "" { 450 info.Fields[name] = reflectFieldInfo{ 451 Index: idx, 452 Anonymous: field.Anonymous, 453 } 454 } 455 if field.Anonymous { 456 typ := field.Type 457 for typ.Kind() == reflect.Ptr { 458 typ = typ.Elem() 459 } 460 if typ.Kind() == reflect.Struct { 461 r.buildFieldInfo(typ, idx, info) 462 } 463 } 464 } 465 } 466} 467 468func (r *Runtime) buildTypeInfo(t reflect.Type) (info *reflectTypeInfo) { 469 info = new(reflectTypeInfo) 470 if t.Kind() == reflect.Struct { 471 info.Fields = make(map[string]reflectFieldInfo) 472 n := t.NumField() 473 info.FieldNames = make([]string, 0, n) 474 r.buildFieldInfo(t, nil, info) 475 } 476 477 info.Methods = make(map[string]int) 478 n := t.NumMethod() 479 info.MethodNames = make([]string, 0, n) 480 for i := 0; i < n; i++ { 481 method := t.Method(i) 482 name := method.Name 483 if !ast.IsExported(name) { 484 continue 485 } 486 if r.fieldNameMapper != nil { 487 name = r.fieldNameMapper.MethodName(t, method) 488 if name == "" { 489 continue 490 } 491 } 492 493 if _, exists := info.Methods[name]; !exists { 494 info.MethodNames = append(info.MethodNames, name) 495 } 496 497 info.Methods[name] = i 498 } 499 return 500} 501 502func (r *Runtime) typeInfo(t reflect.Type) (info *reflectTypeInfo) { 503 var exists bool 504 if info, exists = r.typeInfoCache[t]; !exists { 505 info = r.buildTypeInfo(t) 506 if r.typeInfoCache == nil { 507 r.typeInfoCache = make(map[reflect.Type]*reflectTypeInfo) 508 } 509 r.typeInfoCache[t] = info 510 } 511 512 return 513} 514 515// SetFieldNameMapper sets a custom field name mapper for Go types. It can be called at any time, however 516// the mapping for any given value is fixed at the point of creation. 517// Setting this to nil restores the default behaviour which is all exported fields and methods are mapped to their 518// original unchanged names. 519func (r *Runtime) SetFieldNameMapper(mapper FieldNameMapper) { 520 r.fieldNameMapper = mapper 521 r.typeInfoCache = nil 522} 523 524// TagFieldNameMapper returns a FieldNameMapper that uses the given tagName for struct fields and optionally 525// uncapitalises (making the first letter lower case) method names. 526// The common tag value syntax is supported (name[,options]), however options are ignored. 527// Setting name to anything other than a valid ECMAScript identifier makes the field hidden. 528func TagFieldNameMapper(tagName string, uncapMethods bool) FieldNameMapper { 529 return tagFieldNameMapper{ 530 tagName: tagName, 531 uncapMethods: uncapMethods, 532 } 533} 534 535// UncapFieldNameMapper returns a FieldNameMapper that uncapitalises struct field and method names 536// making the first letter lower case. 537func UncapFieldNameMapper() FieldNameMapper { 538 return uncapFieldNameMapper{} 539} 540