/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "builtin/TypedObject.h" #include "mozilla/Casting.h" #include "mozilla/CheckedInt.h" #include "jscompartment.h" #include "jsfun.h" #include "jsutil.h" #include "gc/Marking.h" #include "js/Vector.h" #include "vm/GlobalObject.h" #include "vm/String.h" #include "vm/StringBuffer.h" #include "vm/TypedArrayObject.h" #include "jsatominlines.h" #include "jsobjinlines.h" #include "gc/StoreBuffer-inl.h" #include "vm/NativeObject-inl.h" #include "vm/Shape-inl.h" using mozilla::AssertedCast; using mozilla::CheckedInt32; using mozilla::DebugOnly; using mozilla::IsPowerOfTwo; using mozilla::PodCopy; using mozilla::PointerRangeSize; using namespace js; const Class js::TypedObjectModuleObject::class_ = { "TypedObject", JSCLASS_HAS_RESERVED_SLOTS(SlotCount) | JSCLASS_HAS_CACHED_PROTO(JSProto_TypedObject) }; static const JSFunctionSpec TypedObjectMethods[] = { JS_SELF_HOSTED_FN("objectType", "TypeOfTypedObject", 1, 0), JS_SELF_HOSTED_FN("storage", "StorageOfTypedObject", 1, 0), JS_FS_END }; static void ReportCannotConvertTo(JSContext* cx, HandleValue fromValue, const char* toType) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO, InformalValueTypeName(fromValue), toType); } template static inline T* ToObjectIf(HandleValue value) { if (!value.isObject()) return nullptr; if (!value.toObject().is()) return nullptr; return &value.toObject().as(); } static inline CheckedInt32 RoundUpToAlignment(CheckedInt32 address, uint32_t align) { MOZ_ASSERT(IsPowerOfTwo(align)); // Note: Be careful to order operators such that we first make the // value smaller and then larger, so that we don't get false // overflow errors due to (e.g.) adding `align` and then // subtracting `1` afterwards when merely adding `align-1` would // not have overflowed. Note that due to the nature of two's // complement representation, if `address` is already aligned, // then adding `align-1` cannot itself cause an overflow. return ((address + (align - 1)) / align) * align; } /* * Overwrites the contents of `typedObj` at offset `offset` with `val` * converted to the type `typeObj`. This is done by delegating to * self-hosted code. This is used for assignments and initializations. * * For example, consider the final assignment in this snippet: * * var Point = new StructType({x: float32, y: float32}); * var Line = new StructType({from: Point, to: Point}); * var line = new Line(); * line.to = {x: 22, y: 44}; * * This would result in a call to `ConvertAndCopyTo` * where: * - typeObj = Point * - typedObj = line * - offset = sizeof(Point) == 8 * - val = {x: 22, y: 44} * This would result in loading the value of `x`, converting * it to a float32, and hen storing it at the appropriate offset, * and then doing the same for `y`. * * Note that the type of `typeObj` may not be the * type of `typedObj` but rather some subcomponent of `typedObj`. */ static bool ConvertAndCopyTo(JSContext* cx, HandleTypeDescr typeObj, HandleTypedObject typedObj, int32_t offset, HandleAtom name, HandleValue val) { RootedFunction func(cx, SelfHostedFunction(cx, cx->names().ConvertAndCopyTo)); if (!func) return false; FixedInvokeArgs<5> args(cx); args[0].setObject(*typeObj); args[1].setObject(*typedObj); args[2].setInt32(offset); if (name) args[3].setString(name); else args[3].setNull(); args[4].set(val); RootedValue fval(cx, ObjectValue(*func)); RootedValue dummy(cx); // ignored by ConvertAndCopyTo return js::Call(cx, fval, dummy, args, &dummy); } static bool ConvertAndCopyTo(JSContext* cx, HandleTypedObject typedObj, HandleValue val) { Rooted type(cx, &typedObj->typeDescr()); return ConvertAndCopyTo(cx, type, typedObj, 0, nullptr, val); } /* * Overwrites the contents of `typedObj` at offset `offset` with `val` * converted to the type `typeObj` */ static bool Reify(JSContext* cx, HandleTypeDescr type, HandleTypedObject typedObj, size_t offset, MutableHandleValue to) { RootedFunction func(cx, SelfHostedFunction(cx, cx->names().Reify)); if (!func) return false; FixedInvokeArgs<3> args(cx); args[0].setObject(*type); args[1].setObject(*typedObj); args[2].setInt32(offset); RootedValue fval(cx, ObjectValue(*func)); return js::Call(cx, fval, UndefinedHandleValue, args, to); } // Extracts the `prototype` property from `obj`, throwing if it is // missing or not an object. static JSObject* GetPrototype(JSContext* cx, HandleObject obj) { RootedValue prototypeVal(cx); if (!GetProperty(cx, obj, obj, cx->names().prototype, &prototypeVal)) { return nullptr; } if (!prototypeVal.isObject()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_PROTOTYPE); return nullptr; } return &prototypeVal.toObject(); } /*************************************************************************** * Typed Prototypes * * Every type descriptor has an associated prototype. Instances of * that type descriptor use this as their prototype. Per the spec, * typed object prototypes cannot be mutated. */ const Class js::TypedProto::class_ = { "TypedProto", JSCLASS_HAS_RESERVED_SLOTS(JS_TYPROTO_SLOTS) }; /*************************************************************************** * Scalar type objects * * Scalar type objects like `uint8`, `uint16`, are all instances of * the ScalarTypeDescr class. Like all type objects, they have a reserved * slot pointing to a TypeRepresentation object, which is used to * distinguish which scalar type object this actually is. */ static const ClassOps ScalarTypeDescrClassOps = { nullptr, /* addProperty */ nullptr, /* delProperty */ nullptr, /* getProperty */ nullptr, /* setProperty */ nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ TypeDescr::finalize, ScalarTypeDescr::call }; const Class js::ScalarTypeDescr::class_ = { "Scalar", JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE, &ScalarTypeDescrClassOps }; const JSFunctionSpec js::ScalarTypeDescr::typeObjectMethods[] = { JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0), JS_SELF_HOSTED_FN("array", "ArrayShorthand", 1, 0), JS_SELF_HOSTED_FN("equivalent", "TypeDescrEquivalent", 1, 0), JS_FS_END }; uint32_t ScalarTypeDescr::size(Type t) { return AssertedCast(Scalar::byteSize(t)); } uint32_t ScalarTypeDescr::alignment(Type t) { return AssertedCast(Scalar::byteSize(t)); } /*static*/ const char* ScalarTypeDescr::typeName(Type type) { switch (type) { #define NUMERIC_TYPE_TO_STRING(constant_, type_, name_) \ case constant_: return #name_; JS_FOR_EACH_SCALAR_TYPE_REPR(NUMERIC_TYPE_TO_STRING) #undef NUMERIC_TYPE_TO_STRING case Scalar::Int64: case Scalar::MaxTypedArrayViewType: break; } MOZ_CRASH("Invalid type"); } bool ScalarTypeDescr::call(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() < 1) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, args.callee().getClass()->name, "0", "s"); return false; } Rooted descr(cx, &args.callee().as()); ScalarTypeDescr::Type type = descr->type(); double number; if (!ToNumber(cx, args[0], &number)) return false; if (type == Scalar::Uint8Clamped) number = ClampDoubleToUint8(number); switch (type) { #define SCALARTYPE_CALL(constant_, type_, name_) \ case constant_: { \ type_ converted = ConvertScalar(number); \ args.rval().setNumber((double) converted); \ return true; \ } JS_FOR_EACH_SCALAR_TYPE_REPR(SCALARTYPE_CALL) #undef SCALARTYPE_CALL case Scalar::Int64: case Scalar::MaxTypedArrayViewType: MOZ_CRASH(); } return true; } /*************************************************************************** * Reference type objects * * Reference type objects like `Any` or `Object` basically work the * same way that the scalar type objects do. There is one class with * many instances, and each instance has a reserved slot with a * TypeRepresentation object, which is used to distinguish which * reference type object this actually is. */ static const ClassOps ReferenceTypeDescrClassOps = { nullptr, /* addProperty */ nullptr, /* delProperty */ nullptr, /* getProperty */ nullptr, /* setProperty */ nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ TypeDescr::finalize, ReferenceTypeDescr::call }; const Class js::ReferenceTypeDescr::class_ = { "Reference", JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE, &ReferenceTypeDescrClassOps }; const JSFunctionSpec js::ReferenceTypeDescr::typeObjectMethods[] = { JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0), {"array", {nullptr, nullptr}, 1, 0, "ArrayShorthand"}, {"equivalent", {nullptr, nullptr}, 1, 0, "TypeDescrEquivalent"}, JS_FS_END }; static const uint32_t ReferenceSizes[] = { #define REFERENCE_SIZE(_kind, _type, _name) \ sizeof(_type), JS_FOR_EACH_REFERENCE_TYPE_REPR(REFERENCE_SIZE) 0 #undef REFERENCE_SIZE }; uint32_t ReferenceTypeDescr::size(Type t) { return ReferenceSizes[t]; } uint32_t ReferenceTypeDescr::alignment(Type t) { return ReferenceSizes[t]; } /*static*/ const char* ReferenceTypeDescr::typeName(Type type) { switch (type) { #define NUMERIC_TYPE_TO_STRING(constant_, type_, name_) \ case constant_: return #name_; JS_FOR_EACH_REFERENCE_TYPE_REPR(NUMERIC_TYPE_TO_STRING) #undef NUMERIC_TYPE_TO_STRING } MOZ_CRASH("Invalid type"); } bool js::ReferenceTypeDescr::call(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.callee().is()); Rooted descr(cx, &args.callee().as()); if (args.length() < 1) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, descr->typeName(), "0", "s"); return false; } switch (descr->type()) { case ReferenceTypeDescr::TYPE_ANY: args.rval().set(args[0]); return true; case ReferenceTypeDescr::TYPE_OBJECT: { RootedObject obj(cx, ToObject(cx, args[0])); if (!obj) return false; args.rval().setObject(*obj); return true; } case ReferenceTypeDescr::TYPE_STRING: { RootedString obj(cx, ToString(cx, args[0])); if (!obj) return false; args.rval().setString(&*obj); return true; } } MOZ_CRASH("Unhandled Reference type"); } /*************************************************************************** * ArrayMetaTypeDescr class */ /* * For code like: * * var A = new TypedObject.ArrayType(uint8, 10); * var S = new TypedObject.StructType({...}); * * As usual, the [[Prototype]] of A is * TypedObject.ArrayType.prototype. This permits adding methods to * all ArrayType types, by setting * TypedObject.ArrayType.prototype.methodName = function() { ... }. * The same holds for S with respect to TypedObject.StructType. * * We may also want to add methods to *instances* of an ArrayType: * * var a = new A(); * var s = new S(); * * As usual, the [[Prototype]] of a is A.prototype. What's * A.prototype? It's an empty object, and you can set * A.prototype.methodName = function() { ... } to add a method to all * A instances. (And the same with respect to s and S.) * * But what if you want to add a method to all ArrayType instances, * not just all A instances? (Or to all StructType instances.) The * [[Prototype]] of the A.prototype empty object is * TypedObject.ArrayType.prototype.prototype (two .prototype levels!). * So just set TypedObject.ArrayType.prototype.prototype.methodName = * function() { ... } to add a method to all ArrayType instances. * (And, again, same with respect to s and S.) * * This function creates the A.prototype/S.prototype object. It returns an * empty object with the .prototype.prototype object as its [[Prototype]]. */ static TypedProto* CreatePrototypeObjectForComplexTypeInstance(JSContext* cx, HandleObject ctorPrototype) { RootedObject ctorPrototypePrototype(cx, GetPrototype(cx, ctorPrototype)); if (!ctorPrototypePrototype) return nullptr; return NewObjectWithGivenProto(cx, ctorPrototypePrototype, SingletonObject); } static const ClassOps ArrayTypeDescrClassOps = { nullptr, /* addProperty */ nullptr, /* delProperty */ nullptr, /* getProperty */ nullptr, /* setProperty */ nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ TypeDescr::finalize, nullptr, /* call */ nullptr, /* hasInstance */ TypedObject::construct }; const Class ArrayTypeDescr::class_ = { "ArrayType", JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE, &ArrayTypeDescrClassOps }; const JSPropertySpec ArrayMetaTypeDescr::typeObjectProperties[] = { JS_PS_END }; const JSFunctionSpec ArrayMetaTypeDescr::typeObjectMethods[] = { {"array", {nullptr, nullptr}, 1, 0, "ArrayShorthand"}, JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0), {"equivalent", {nullptr, nullptr}, 1, 0, "TypeDescrEquivalent"}, JS_SELF_HOSTED_FN("build", "TypedObjectArrayTypeBuild", 3, 0), JS_SELF_HOSTED_FN("from", "TypedObjectArrayTypeFrom", 3, 0), JS_FS_END }; const JSPropertySpec ArrayMetaTypeDescr::typedObjectProperties[] = { JS_PS_END }; const JSFunctionSpec ArrayMetaTypeDescr::typedObjectMethods[] = { {"forEach", {nullptr, nullptr}, 1, 0, "ArrayForEach"}, {"redimension", {nullptr, nullptr}, 1, 0, "TypedObjectArrayRedimension"}, JS_SELF_HOSTED_FN("map", "TypedObjectArrayMap", 2, 0), JS_SELF_HOSTED_FN("reduce", "TypedObjectArrayReduce", 2, 0), JS_SELF_HOSTED_FN("filter", "TypedObjectArrayFilter", 1, 0), JS_FS_END }; bool js::CreateUserSizeAndAlignmentProperties(JSContext* cx, HandleTypeDescr descr) { // If data is transparent, also store the public slots. if (descr->transparent()) { // byteLength RootedValue typeByteLength(cx, Int32Value(AssertedCast(descr->size()))); if (!DefineProperty(cx, descr, cx->names().byteLength, typeByteLength, nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT)) { return false; } // byteAlignment RootedValue typeByteAlignment(cx, Int32Value(descr->alignment())); if (!DefineProperty(cx, descr, cx->names().byteAlignment, typeByteAlignment, nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT)) { return false; } } else { // byteLength if (!DefineProperty(cx, descr, cx->names().byteLength, UndefinedHandleValue, nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT)) { return false; } // byteAlignment if (!DefineProperty(cx, descr, cx->names().byteAlignment, UndefinedHandleValue, nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT)) { return false; } } return true; } static bool CreateTraceList(JSContext* cx, HandleTypeDescr descr); ArrayTypeDescr* ArrayMetaTypeDescr::create(JSContext* cx, HandleObject arrayTypePrototype, HandleTypeDescr elementType, HandleAtom stringRepr, int32_t size, int32_t length) { MOZ_ASSERT(arrayTypePrototype); Rooted obj(cx); obj = NewObjectWithGivenProto(cx, arrayTypePrototype, SingletonObject); if (!obj) return nullptr; obj->initReservedSlot(JS_DESCR_SLOT_KIND, Int32Value(ArrayTypeDescr::Kind)); obj->initReservedSlot(JS_DESCR_SLOT_STRING_REPR, StringValue(stringRepr)); obj->initReservedSlot(JS_DESCR_SLOT_ALIGNMENT, Int32Value(elementType->alignment())); obj->initReservedSlot(JS_DESCR_SLOT_SIZE, Int32Value(size)); obj->initReservedSlot(JS_DESCR_SLOT_OPAQUE, BooleanValue(elementType->opaque())); obj->initReservedSlot(JS_DESCR_SLOT_ARRAY_ELEM_TYPE, ObjectValue(*elementType)); obj->initReservedSlot(JS_DESCR_SLOT_ARRAY_LENGTH, Int32Value(length)); RootedValue elementTypeVal(cx, ObjectValue(*elementType)); if (!DefineProperty(cx, obj, cx->names().elementType, elementTypeVal, nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT)) { return nullptr; } RootedValue lengthValue(cx, NumberValue(length)); if (!DefineProperty(cx, obj, cx->names().length, lengthValue, nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT)) { return nullptr; } if (!CreateUserSizeAndAlignmentProperties(cx, obj)) return nullptr; // All arrays with the same element type have the same prototype. This // prototype is created lazily and stored in the element type descriptor. Rooted prototypeObj(cx); if (elementType->getReservedSlot(JS_DESCR_SLOT_ARRAYPROTO).isObject()) { prototypeObj = &elementType->getReservedSlot(JS_DESCR_SLOT_ARRAYPROTO).toObject().as(); } else { prototypeObj = CreatePrototypeObjectForComplexTypeInstance(cx, arrayTypePrototype); if (!prototypeObj) return nullptr; elementType->setReservedSlot(JS_DESCR_SLOT_ARRAYPROTO, ObjectValue(*prototypeObj)); } obj->initReservedSlot(JS_DESCR_SLOT_TYPROTO, ObjectValue(*prototypeObj)); if (!LinkConstructorAndPrototype(cx, obj, prototypeObj)) return nullptr; if (!CreateTraceList(cx, obj)) return nullptr; if (!cx->zone()->addTypeDescrObject(cx, obj)) { ReportOutOfMemory(cx); return nullptr; } return obj; } bool ArrayMetaTypeDescr::construct(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (!ThrowIfNotConstructing(cx, args, "ArrayType")) return false; RootedObject arrayTypeGlobal(cx, &args.callee()); // Expect two arguments. The first is a type object, the second is a length. if (args.length() < 2) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, "ArrayType", "1", ""); return false; } if (!args[0].isObject() || !args[0].toObject().is()) { ReportCannotConvertTo(cx, args[0], "ArrayType element specifier"); return false; } if (!args[1].isInt32() || args[1].toInt32() < 0) { ReportCannotConvertTo(cx, args[1], "ArrayType length specifier"); return false; } Rooted elementType(cx, &args[0].toObject().as()); int32_t length = args[1].toInt32(); // Compute the byte size. CheckedInt32 size = CheckedInt32(elementType->size()) * length; if (!size.isValid()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_TOO_BIG); return false; } // Construct a canonical string `new ArrayType(, N)`: StringBuffer contents(cx); if (!contents.append("new ArrayType(")) return false; if (!contents.append(&elementType->stringRepr())) return false; if (!contents.append(", ")) return false; if (!NumberValueToStringBuffer(cx, NumberValue(length), contents)) return false; if (!contents.append(")")) return false; RootedAtom stringRepr(cx, contents.finishAtom()); if (!stringRepr) return false; // Extract ArrayType.prototype RootedObject arrayTypePrototype(cx, GetPrototype(cx, arrayTypeGlobal)); if (!arrayTypePrototype) return false; // Create the instance of ArrayType Rooted obj(cx); obj = create(cx, arrayTypePrototype, elementType, stringRepr, size.value(), length); if (!obj) return false; args.rval().setObject(*obj); return true; } bool js::IsTypedObjectArray(JSObject& obj) { if (!obj.is()) return false; TypeDescr& d = obj.as().typeDescr(); return d.is(); } /********************************* * StructType class */ static const ClassOps StructTypeDescrClassOps = { nullptr, /* addProperty */ nullptr, /* delProperty */ nullptr, /* getProperty */ nullptr, /* setProperty */ nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ TypeDescr::finalize, nullptr, /* call */ nullptr, /* hasInstance */ TypedObject::construct }; const Class StructTypeDescr::class_ = { "StructType", JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE, &StructTypeDescrClassOps }; const JSPropertySpec StructMetaTypeDescr::typeObjectProperties[] = { JS_PS_END }; const JSFunctionSpec StructMetaTypeDescr::typeObjectMethods[] = { {"array", {nullptr, nullptr}, 1, 0, "ArrayShorthand"}, JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0), {"equivalent", {nullptr, nullptr}, 1, 0, "TypeDescrEquivalent"}, JS_FS_END }; const JSPropertySpec StructMetaTypeDescr::typedObjectProperties[] = { JS_PS_END }; const JSFunctionSpec StructMetaTypeDescr::typedObjectMethods[] = { JS_FS_END }; JSObject* StructMetaTypeDescr::create(JSContext* cx, HandleObject metaTypeDescr, HandleObject fields) { // Obtain names of fields, which are the own properties of `fields` AutoIdVector ids(cx); if (!GetPropertyKeys(cx, fields, JSITER_OWNONLY | JSITER_SYMBOLS, &ids)) return nullptr; // Iterate through each field. Collect values for the various // vectors below and also track total size and alignment. Be wary // of overflow! StringBuffer stringBuffer(cx); // Canonical string repr AutoValueVector fieldNames(cx); // Name of each field. AutoValueVector fieldTypeObjs(cx); // Type descriptor of each field. AutoValueVector fieldOffsets(cx); // Offset of each field field. RootedObject userFieldOffsets(cx); // User-exposed {f:offset} object RootedObject userFieldTypes(cx); // User-exposed {f:descr} object. CheckedInt32 sizeSoFar(0); // Size of struct thus far. uint32_t alignment = 1; // Alignment of struct. bool opaque = false; // Opacity of struct. userFieldOffsets = NewBuiltinClassInstance(cx, TenuredObject); if (!userFieldOffsets) return nullptr; userFieldTypes = NewBuiltinClassInstance(cx, TenuredObject); if (!userFieldTypes) return nullptr; if (!stringBuffer.append("new StructType({")) return nullptr; RootedValue fieldTypeVal(cx); RootedId id(cx); Rooted fieldType(cx); for (unsigned int i = 0; i < ids.length(); i++) { id = ids[i]; // Check that all the property names are non-numeric strings. uint32_t unused; if (!JSID_IS_ATOM(id) || JSID_TO_ATOM(id)->isIndex(&unused)) { RootedValue idValue(cx, IdToValue(id)); ReportCannotConvertTo(cx, idValue, "StructType field name"); return nullptr; } // Load the value for the current field from the `fields` object. // The value should be a type descriptor. if (!GetProperty(cx, fields, fields, id, &fieldTypeVal)) return nullptr; fieldType = ToObjectIf(fieldTypeVal); if (!fieldType) { ReportCannotConvertTo(cx, fieldTypeVal, "StructType field specifier"); return nullptr; } // Collect field name and type object RootedValue fieldName(cx, IdToValue(id)); if (!fieldNames.append(fieldName)) return nullptr; if (!fieldTypeObjs.append(ObjectValue(*fieldType))) return nullptr; // userFieldTypes[id] = typeObj if (!DefineProperty(cx, userFieldTypes, id, fieldTypeObjs[i], nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT)) { return nullptr; } // Append "f:Type" to the string repr if (i > 0 && !stringBuffer.append(", ")) return nullptr; if (!stringBuffer.append(JSID_TO_ATOM(id))) return nullptr; if (!stringBuffer.append(": ")) return nullptr; if (!stringBuffer.append(&fieldType->stringRepr())) return nullptr; // Offset of this field is the current total size adjusted for // the field's alignment. CheckedInt32 offset = RoundUpToAlignment(sizeSoFar, fieldType->alignment()); if (!offset.isValid()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_TOO_BIG); return nullptr; } MOZ_ASSERT(offset.value() >= 0); if (!fieldOffsets.append(Int32Value(offset.value()))) return nullptr; // userFieldOffsets[id] = offset RootedValue offsetValue(cx, Int32Value(offset.value())); if (!DefineProperty(cx, userFieldOffsets, id, offsetValue, nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT)) { return nullptr; } // Add space for this field to the total struct size. sizeSoFar = offset + fieldType->size(); if (!sizeSoFar.isValid()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_TOO_BIG); return nullptr; } // Struct is opaque if any field is opaque if (fieldType->opaque()) opaque = true; // Alignment of the struct is the max of the alignment of its fields. alignment = js::Max(alignment, fieldType->alignment()); } // Complete string representation. if (!stringBuffer.append("})")) return nullptr; RootedAtom stringRepr(cx, stringBuffer.finishAtom()); if (!stringRepr) return nullptr; // Adjust the total size to be a multiple of the final alignment. CheckedInt32 totalSize = RoundUpToAlignment(sizeSoFar, alignment); if (!totalSize.isValid()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_TOO_BIG); return nullptr; } // Now create the resulting type descriptor. RootedObject structTypePrototype(cx, GetPrototype(cx, metaTypeDescr)); if (!structTypePrototype) return nullptr; Rooted descr(cx); descr = NewObjectWithGivenProto(cx, structTypePrototype, SingletonObject); if (!descr) return nullptr; descr->initReservedSlot(JS_DESCR_SLOT_KIND, Int32Value(type::Struct)); descr->initReservedSlot(JS_DESCR_SLOT_STRING_REPR, StringValue(stringRepr)); descr->initReservedSlot(JS_DESCR_SLOT_ALIGNMENT, Int32Value(AssertedCast(alignment))); descr->initReservedSlot(JS_DESCR_SLOT_SIZE, Int32Value(totalSize.value())); descr->initReservedSlot(JS_DESCR_SLOT_OPAQUE, BooleanValue(opaque)); // Construct for internal use an array with the name for each field. { RootedObject fieldNamesVec(cx); fieldNamesVec = NewDenseCopiedArray(cx, fieldNames.length(), fieldNames.begin(), nullptr, TenuredObject); if (!fieldNamesVec) return nullptr; descr->initReservedSlot(JS_DESCR_SLOT_STRUCT_FIELD_NAMES, ObjectValue(*fieldNamesVec)); } // Construct for internal use an array with the type object for each field. RootedObject fieldTypeVec(cx); fieldTypeVec = NewDenseCopiedArray(cx, fieldTypeObjs.length(), fieldTypeObjs.begin(), nullptr, TenuredObject); if (!fieldTypeVec) return nullptr; descr->initReservedSlot(JS_DESCR_SLOT_STRUCT_FIELD_TYPES, ObjectValue(*fieldTypeVec)); // Construct for internal use an array with the offset for each field. { RootedObject fieldOffsetsVec(cx); fieldOffsetsVec = NewDenseCopiedArray(cx, fieldOffsets.length(), fieldOffsets.begin(), nullptr, TenuredObject); if (!fieldOffsetsVec) return nullptr; descr->initReservedSlot(JS_DESCR_SLOT_STRUCT_FIELD_OFFSETS, ObjectValue(*fieldOffsetsVec)); } // Create data properties fieldOffsets and fieldTypes if (!FreezeObject(cx, userFieldOffsets)) return nullptr; if (!FreezeObject(cx, userFieldTypes)) return nullptr; RootedValue userFieldOffsetsValue(cx, ObjectValue(*userFieldOffsets)); if (!DefineProperty(cx, descr, cx->names().fieldOffsets, userFieldOffsetsValue, nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT)) { return nullptr; } RootedValue userFieldTypesValue(cx, ObjectValue(*userFieldTypes)); if (!DefineProperty(cx, descr, cx->names().fieldTypes, userFieldTypesValue, nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT)) { return nullptr; } if (!CreateUserSizeAndAlignmentProperties(cx, descr)) return nullptr; Rooted prototypeObj(cx); prototypeObj = CreatePrototypeObjectForComplexTypeInstance(cx, structTypePrototype); if (!prototypeObj) return nullptr; descr->initReservedSlot(JS_DESCR_SLOT_TYPROTO, ObjectValue(*prototypeObj)); if (!LinkConstructorAndPrototype(cx, descr, prototypeObj)) return nullptr; if (!CreateTraceList(cx, descr)) return nullptr; if (!cx->zone()->addTypeDescrObject(cx, descr) || !cx->zone()->addTypeDescrObject(cx, fieldTypeVec)) { ReportOutOfMemory(cx); return nullptr; } return descr; } bool StructMetaTypeDescr::construct(JSContext* cx, unsigned int argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (!ThrowIfNotConstructing(cx, args, "StructType")) return false; if (args.length() >= 1 && args[0].isObject()) { RootedObject metaTypeDescr(cx, &args.callee()); RootedObject fields(cx, &args[0].toObject()); RootedObject obj(cx, create(cx, metaTypeDescr, fields)); if (!obj) return false; args.rval().setObject(*obj); return true; } JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_STRUCTTYPE_BAD_ARGS); return false; } size_t StructTypeDescr::fieldCount() const { return fieldInfoObject(JS_DESCR_SLOT_STRUCT_FIELD_NAMES).getDenseInitializedLength(); } bool StructTypeDescr::fieldIndex(jsid id, size_t* out) const { ArrayObject& fieldNames = fieldInfoObject(JS_DESCR_SLOT_STRUCT_FIELD_NAMES); size_t l = fieldNames.getDenseInitializedLength(); for (size_t i = 0; i < l; i++) { JSAtom& a = fieldNames.getDenseElement(i).toString()->asAtom(); if (JSID_IS_ATOM(id, &a)) { *out = i; return true; } } return false; } JSAtom& StructTypeDescr::fieldName(size_t index) const { return fieldInfoObject(JS_DESCR_SLOT_STRUCT_FIELD_NAMES).getDenseElement(index).toString()->asAtom(); } size_t StructTypeDescr::fieldOffset(size_t index) const { ArrayObject& fieldOffsets = fieldInfoObject(JS_DESCR_SLOT_STRUCT_FIELD_OFFSETS); MOZ_ASSERT(index < fieldOffsets.getDenseInitializedLength()); return AssertedCast(fieldOffsets.getDenseElement(index).toInt32()); } TypeDescr& StructTypeDescr::fieldDescr(size_t index) const { ArrayObject& fieldDescrs = fieldInfoObject(JS_DESCR_SLOT_STRUCT_FIELD_TYPES); MOZ_ASSERT(index < fieldDescrs.getDenseInitializedLength()); return fieldDescrs.getDenseElement(index).toObject().as(); } /****************************************************************************** * Creating the TypedObject "module" * * We create one global, `TypedObject`, which contains the following * members: * * 1. uint8, uint16, etc * 2. ArrayType * 3. StructType * * Each of these is a function and hence their prototype is * `Function.__proto__` (in terms of the JS Engine, they are not * JSFunctions but rather instances of their own respective JSClasses * which override the call and construct operations). * * Each type object also has its own `prototype` field. Therefore, * using `StructType` as an example, the basic setup is: * * StructType --__proto__--> Function.__proto__ * | * prototype -- prototype --> { } * | * v * { } -----__proto__--> Function.__proto__ * * When a new type object (e.g., an instance of StructType) is created, * it will look as follows: * * MyStruct -__proto__-> StructType.prototype -__proto__-> Function.__proto__ * | | * | prototype * | | * | v * prototype -----__proto__----> { } * | * v * { } --__proto__-> Object.prototype * * Finally, when an instance of `MyStruct` is created, its * structure is as follows: * * object -__proto__-> * MyStruct.prototype -__proto__-> * StructType.prototype.prototype -__proto__-> * Object.prototype */ // Here `T` is either `ScalarTypeDescr` or `ReferenceTypeDescr` template static bool DefineSimpleTypeDescr(JSContext* cx, Handle global, HandleObject module, typename T::Type type, HandlePropertyName className) { RootedObject objProto(cx, GlobalObject::getOrCreateObjectPrototype(cx, global)); if (!objProto) return false; RootedObject funcProto(cx, GlobalObject::getOrCreateFunctionPrototype(cx, global)); if (!funcProto) return false; Rooted descr(cx); descr = NewObjectWithGivenProto(cx, funcProto, SingletonObject); if (!descr) return false; descr->initReservedSlot(JS_DESCR_SLOT_KIND, Int32Value(T::Kind)); descr->initReservedSlot(JS_DESCR_SLOT_STRING_REPR, StringValue(className)); descr->initReservedSlot(JS_DESCR_SLOT_ALIGNMENT, Int32Value(T::alignment(type))); descr->initReservedSlot(JS_DESCR_SLOT_SIZE, Int32Value(AssertedCast(T::size(type)))); descr->initReservedSlot(JS_DESCR_SLOT_OPAQUE, BooleanValue(T::Opaque)); descr->initReservedSlot(JS_DESCR_SLOT_TYPE, Int32Value(type)); if (!CreateUserSizeAndAlignmentProperties(cx, descr)) return false; if (!JS_DefineFunctions(cx, descr, T::typeObjectMethods)) return false; // Create the typed prototype for the scalar type. This winds up // not being user accessible, but we still create one for consistency. Rooted proto(cx); proto = NewObjectWithGivenProto(cx, objProto, TenuredObject); if (!proto) return false; descr->initReservedSlot(JS_DESCR_SLOT_TYPROTO, ObjectValue(*proto)); RootedValue descrValue(cx, ObjectValue(*descr)); if (!DefineProperty(cx, module, className, descrValue, nullptr, nullptr, 0)) return false; if (!CreateTraceList(cx, descr)) return false; if (!cx->zone()->addTypeDescrObject(cx, descr)) return false; return true; } /////////////////////////////////////////////////////////////////////////// template static JSObject* DefineMetaTypeDescr(JSContext* cx, const char* name, Handle global, Handle module, TypedObjectModuleObject::Slot protoSlot) { RootedAtom className(cx, Atomize(cx, name, strlen(name))); if (!className) return nullptr; RootedObject funcProto(cx, GlobalObject::getOrCreateFunctionPrototype(cx, global)); if (!funcProto) return nullptr; // Create ctor.prototype, which inherits from Function.__proto__ RootedObject proto(cx, NewObjectWithGivenProto(cx, funcProto, SingletonObject)); if (!proto) return nullptr; // Create ctor.prototype.prototype, which inherits from Object.__proto__ RootedObject objProto(cx, GlobalObject::getOrCreateObjectPrototype(cx, global)); if (!objProto) return nullptr; RootedObject protoProto(cx); protoProto = NewObjectWithGivenProto(cx, objProto, SingletonObject); if (!protoProto) return nullptr; RootedValue protoProtoValue(cx, ObjectValue(*protoProto)); if (!DefineProperty(cx, proto, cx->names().prototype, protoProtoValue, nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT)) { return nullptr; } // Create ctor itself const int constructorLength = 2; RootedFunction ctor(cx); ctor = GlobalObject::createConstructor(cx, T::construct, className, constructorLength); if (!ctor || !LinkConstructorAndPrototype(cx, ctor, proto) || !DefinePropertiesAndFunctions(cx, proto, T::typeObjectProperties, T::typeObjectMethods) || !DefinePropertiesAndFunctions(cx, protoProto, T::typedObjectProperties, T::typedObjectMethods)) { return nullptr; } module->initReservedSlot(protoSlot, ObjectValue(*proto)); return ctor; } /* The initialization strategy for TypedObjects is mildly unusual * compared to other classes. Because all of the types are members * of a single global, `TypedObject`, we basically make the * initializer for the `TypedObject` class populate the * `TypedObject` global (which is referred to as "module" herein). */ /* static */ bool GlobalObject::initTypedObjectModule(JSContext* cx, Handle global) { RootedObject objProto(cx, GlobalObject::getOrCreateObjectPrototype(cx, global)); if (!objProto) return false; Rooted module(cx); module = NewObjectWithGivenProto(cx, objProto); if (!module) return false; if (!JS_DefineFunctions(cx, module, TypedObjectMethods)) return false; // uint8, uint16, any, etc #define BINARYDATA_SCALAR_DEFINE(constant_, type_, name_) \ if (!DefineSimpleTypeDescr(cx, global, module, constant_, \ cx->names().name_)) \ return false; JS_FOR_EACH_SCALAR_TYPE_REPR(BINARYDATA_SCALAR_DEFINE) #undef BINARYDATA_SCALAR_DEFINE #define BINARYDATA_REFERENCE_DEFINE(constant_, type_, name_) \ if (!DefineSimpleTypeDescr(cx, global, module, constant_, \ cx->names().name_)) \ return false; JS_FOR_EACH_REFERENCE_TYPE_REPR(BINARYDATA_REFERENCE_DEFINE) #undef BINARYDATA_REFERENCE_DEFINE // ArrayType. RootedObject arrayType(cx); arrayType = DefineMetaTypeDescr( cx, "ArrayType", global, module, TypedObjectModuleObject::ArrayTypePrototype); if (!arrayType) return false; RootedValue arrayTypeValue(cx, ObjectValue(*arrayType)); if (!DefineProperty(cx, module, cx->names().ArrayType, arrayTypeValue, nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT)) { return false; } // StructType. RootedObject structType(cx); structType = DefineMetaTypeDescr( cx, "StructType", global, module, TypedObjectModuleObject::StructTypePrototype); if (!structType) return false; RootedValue structTypeValue(cx, ObjectValue(*structType)); if (!DefineProperty(cx, module, cx->names().StructType, structTypeValue, nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT)) { return false; } // Everything is setup, install module on the global object: RootedValue moduleValue(cx, ObjectValue(*module)); if (!DefineProperty(cx, global, cx->names().TypedObject, moduleValue, nullptr, nullptr, JSPROP_RESOLVING)) { return false; } global->setConstructor(JSProto_TypedObject, moduleValue); return module; } JSObject* js::InitTypedObjectModuleObject(JSContext* cx, HandleObject obj) { Handle global = obj.as(); return GlobalObject::getOrCreateTypedObjectModule(cx, global); } /****************************************************************************** * Typed objects */ uint32_t TypedObject::offset() const { if (is()) return 0; return PointerRangeSize(typedMemBase(), typedMem()); } uint32_t TypedObject::length() const { return typeDescr().as().length(); } uint8_t* TypedObject::typedMem() const { MOZ_ASSERT(isAttached()); if (is()) return as().inlineTypedMem(); return as().outOfLineTypedMem(); } uint8_t* TypedObject::typedMemBase() const { MOZ_ASSERT(isAttached()); MOZ_ASSERT(is()); JSObject& owner = as().owner(); if (owner.is()) return owner.as().dataPointer(); return owner.as().inlineTypedMem(); } bool TypedObject::isAttached() const { if (is()) { ObjectWeakMap* table = compartment()->lazyArrayBuffers; if (table) { JSObject* buffer = table->lookup(this); if (buffer) return !buffer->as().isDetached(); } return true; } if (is()) return true; if (!as().outOfLineTypedMem()) return false; JSObject& owner = as().owner(); if (owner.is() && owner.as().isDetached()) return false; return true; } /* static */ bool TypedObject::GetBuffer(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); JSObject& obj = args[0].toObject(); ArrayBufferObject* buffer; if (obj.is()) buffer = obj.as().getOrCreateBuffer(cx); else buffer = obj.as().getOrCreateBuffer(cx); if (!buffer) return false; args.rval().setObject(*buffer); return true; } /* static */ bool TypedObject::GetByteOffset(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setInt32(AssertedCast(args[0].toObject().as().offset())); return true; } /****************************************************************************** * Outline typed objects */ /*static*/ OutlineTypedObject* OutlineTypedObject::createUnattached(JSContext* cx, HandleTypeDescr descr, int32_t length, gc::InitialHeap heap) { if (descr->opaque()) return createUnattachedWithClass(cx, &OutlineOpaqueTypedObject::class_, descr, length, heap); else return createUnattachedWithClass(cx, &OutlineTransparentTypedObject::class_, descr, length, heap); } void OutlineTypedObject::setOwnerAndData(JSObject* owner, uint8_t* data) { // Make sure we don't associate with array buffers whose data is from an // inline typed object, see obj_trace. MOZ_ASSERT_IF(owner && owner->is(), !owner->as().forInlineTypedObject()); // Typed objects cannot move from one owner to another, so don't worry // about pre barriers during this initialization. owner_ = owner; data_ = data; // Trigger a post barrier when attaching an object outside the nursery to // one that is inside it. if (owner && !IsInsideNursery(this) && IsInsideNursery(owner)) runtimeFromMainThread()->gc.storeBuffer.putWholeCell(this); } /*static*/ OutlineTypedObject* OutlineTypedObject::createUnattachedWithClass(JSContext* cx, const Class* clasp, HandleTypeDescr descr, int32_t length, gc::InitialHeap heap) { MOZ_ASSERT(clasp == &OutlineTransparentTypedObject::class_ || clasp == &OutlineOpaqueTypedObject::class_); RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, clasp, TaggedProto(&descr->typedProto()), descr)); if (!group) return nullptr; NewObjectKind newKind = (heap == gc::TenuredHeap) ? TenuredObject : GenericObject; OutlineTypedObject* obj = NewObjectWithGroup(cx, group, gc::AllocKind::OBJECT0, newKind); if (!obj) return nullptr; obj->setOwnerAndData(nullptr, nullptr); return obj; } void OutlineTypedObject::attach(JSContext* cx, ArrayBufferObject& buffer, uint32_t offset) { MOZ_ASSERT(!isAttached()); MOZ_ASSERT(offset <= buffer.byteLength()); MOZ_ASSERT(size() <= buffer.byteLength() - offset); // If the owner's data is from an inline typed object, associate this with // the inline typed object instead, to simplify tracing. if (buffer.forInlineTypedObject()) { InlineTypedObject& realOwner = buffer.firstView()->as(); attach(cx, realOwner, offset); return; } buffer.setHasTypedObjectViews(); { AutoEnterOOMUnsafeRegion oomUnsafe; if (!buffer.addView(cx, this)) oomUnsafe.crash("TypedObject::attach"); } setOwnerAndData(&buffer, buffer.dataPointer() + offset); } void OutlineTypedObject::attach(JSContext* cx, TypedObject& typedObj, uint32_t offset) { MOZ_ASSERT(!isAttached()); MOZ_ASSERT(typedObj.isAttached()); JSObject* owner = &typedObj; if (typedObj.is()) { owner = &typedObj.as().owner(); MOZ_ASSERT(typedObj.offset() <= UINT32_MAX - offset); offset += typedObj.offset(); } if (owner->is()) { attach(cx, owner->as(), offset); } else { MOZ_ASSERT(owner->is()); JS::AutoCheckCannotGC nogc(cx); setOwnerAndData(owner, owner->as().inlineTypedMem(nogc) + offset); } } // Returns a suitable JS_TYPEDOBJ_SLOT_LENGTH value for an instance of // the type `type`. static uint32_t TypedObjLengthFromType(TypeDescr& descr) { switch (descr.kind()) { case type::Scalar: case type::Reference: case type::Struct: return 0; case type::Array: return descr.as().length(); } MOZ_CRASH("Invalid kind"); } /*static*/ OutlineTypedObject* OutlineTypedObject::createDerived(JSContext* cx, HandleTypeDescr type, HandleTypedObject typedObj, uint32_t offset) { MOZ_ASSERT(offset <= typedObj->size()); MOZ_ASSERT(offset + type->size() <= typedObj->size()); int32_t length = TypedObjLengthFromType(*type); const js::Class* clasp = typedObj->opaque() ? &OutlineOpaqueTypedObject::class_ : &OutlineTransparentTypedObject::class_; Rooted obj(cx); obj = createUnattachedWithClass(cx, clasp, type, length); if (!obj) return nullptr; obj->attach(cx, *typedObj, offset); return obj; } /*static*/ TypedObject* TypedObject::createZeroed(JSContext* cx, HandleTypeDescr descr, int32_t length, gc::InitialHeap heap) { // If possible, create an object with inline data. if (descr->size() <= InlineTypedObject::MaximumSize) { AutoSetNewObjectMetadata metadata(cx); InlineTypedObject* obj = InlineTypedObject::create(cx, descr, heap); if (!obj) return nullptr; JS::AutoCheckCannotGC nogc(cx); descr->initInstances(cx->runtime(), obj->inlineTypedMem(nogc), 1); return obj; } // Create unattached wrapper object. Rooted obj(cx, OutlineTypedObject::createUnattached(cx, descr, length, heap)); if (!obj) return nullptr; // Allocate and initialize the memory for this instance. size_t totalSize = descr->size(); Rooted buffer(cx); buffer = ArrayBufferObject::create(cx, totalSize); if (!buffer) return nullptr; descr->initInstances(cx->runtime(), buffer->dataPointer(), 1); obj->attach(cx, *buffer, 0); return obj; } static bool ReportTypedObjTypeError(JSContext* cx, const unsigned errorNumber, HandleTypedObject obj) { // Serialize type string of obj RootedAtom typeReprAtom(cx, &obj->typeDescr().stringRepr()); UniqueChars typeReprStr(JS_EncodeStringToUTF8(cx, typeReprAtom)); if (!typeReprStr) return false; JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber, typeReprStr.get()); return false; } /* static */ void OutlineTypedObject::obj_trace(JSTracer* trc, JSObject* object) { OutlineTypedObject& typedObj = object->as(); TraceEdge(trc, &typedObj.shape_, "OutlineTypedObject_shape"); if (!typedObj.owner_) return; TypeDescr& descr = typedObj.typeDescr(); // Mark the owner, watching in case it is moved by the tracer. JSObject* oldOwner = typedObj.owner_; TraceManuallyBarrieredEdge(trc, &typedObj.owner_, "typed object owner"); JSObject* owner = typedObj.owner_; uint8_t* oldData = typedObj.outOfLineTypedMem(); uint8_t* newData = oldData; // Update the data pointer if the owner moved and the owner's data is // inline with it. Note that an array buffer pointing to data in an inline // typed object will never be used as an owner for another outline typed // object. In such cases, the owner will be the inline typed object itself. MakeAccessibleAfterMovingGC(owner); MOZ_ASSERT_IF(owner->is(), !owner->as().forInlineTypedObject()); if (owner != oldOwner && (owner->is() || owner->as().hasInlineData())) { newData += reinterpret_cast(owner) - reinterpret_cast(oldOwner); typedObj.setData(newData); trc->runtime()->gc.nursery.maybeSetForwardingPointer(trc, oldData, newData, /* direct = */ false); } if (!descr.opaque() || !typedObj.isAttached()) return; descr.traceInstances(trc, newData, 1); } bool TypeDescr::hasProperty(const JSAtomState& names, jsid id) { switch (kind()) { case type::Scalar: case type::Reference: return false; case type::Array: { uint32_t index; return IdIsIndex(id, &index) || JSID_IS_ATOM(id, names.length); } case type::Struct: { size_t index; return as().fieldIndex(id, &index); } } MOZ_CRASH("Unexpected kind"); } /* static */ bool TypedObject::obj_lookupProperty(JSContext* cx, HandleObject obj, HandleId id, MutableHandleObject objp, MutableHandle propp) { if (obj->as().typeDescr().hasProperty(cx->names(), id)) { propp.setNonNativeProperty(); objp.set(obj); return true; } RootedObject proto(cx, obj->staticPrototype()); if (!proto) { objp.set(nullptr); propp.setNotFound(); return true; } return LookupProperty(cx, proto, id, objp, propp); } static bool ReportPropertyError(JSContext* cx, const unsigned errorNumber, HandleId id) { RootedValue idVal(cx, IdToValue(id)); RootedString str(cx, ValueToSource(cx, idVal)); if (!str) return false; UniqueChars propName(JS_EncodeStringToUTF8(cx, str)); if (!propName) return false; JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber, propName.get()); return false; } bool TypedObject::obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id, Handle desc, ObjectOpResult& result) { Rooted typedObj(cx, &obj->as()); return ReportTypedObjTypeError(cx, JSMSG_OBJECT_NOT_EXTENSIBLE, typedObj); } bool TypedObject::obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp) { Rooted typedObj(cx, &obj->as()); switch (typedObj->typeDescr().kind()) { case type::Scalar: case type::Reference: break; case type::Array: { if (JSID_IS_ATOM(id, cx->names().length)) { *foundp = true; return true; } uint32_t index; // Elements are not inherited from the prototype. if (IdIsIndex(id, &index)) { *foundp = (index < uint32_t(typedObj->length())); return true; } break; } case type::Struct: size_t index; if (typedObj->typeDescr().as().fieldIndex(id, &index)) { *foundp = true; return true; } } RootedObject proto(cx, obj->staticPrototype()); if (!proto) { *foundp = false; return true; } return HasProperty(cx, proto, id, foundp); } bool TypedObject::obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiver, HandleId id, MutableHandleValue vp) { Rooted typedObj(cx, &obj->as()); // Dispatch elements to obj_getElement: uint32_t index; if (IdIsIndex(id, &index)) return obj_getElement(cx, obj, receiver, index, vp); // Handle everything else here: switch (typedObj->typeDescr().kind()) { case type::Scalar: case type::Reference: break; case type::Array: if (JSID_IS_ATOM(id, cx->names().length)) { if (!typedObj->isAttached()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_HANDLE_UNATTACHED); return false; } vp.setInt32(typedObj->length()); return true; } break; case type::Struct: { Rooted descr(cx, &typedObj->typeDescr().as()); size_t fieldIndex; if (!descr->fieldIndex(id, &fieldIndex)) break; size_t offset = descr->fieldOffset(fieldIndex); Rooted fieldType(cx, &descr->fieldDescr(fieldIndex)); return Reify(cx, fieldType, typedObj, offset, vp); } } RootedObject proto(cx, obj->staticPrototype()); if (!proto) { vp.setUndefined(); return true; } return GetProperty(cx, proto, receiver, id, vp); } bool TypedObject::obj_getElement(JSContext* cx, HandleObject obj, HandleValue receiver, uint32_t index, MutableHandleValue vp) { MOZ_ASSERT(obj->is()); Rooted typedObj(cx, &obj->as()); Rooted descr(cx, &typedObj->typeDescr()); switch (descr->kind()) { case type::Scalar: case type::Reference: case type::Struct: break; case type::Array: return obj_getArrayElement(cx, typedObj, descr, index, vp); } RootedObject proto(cx, obj->staticPrototype()); if (!proto) { vp.setUndefined(); return true; } return GetElement(cx, proto, receiver, index, vp); } /*static*/ bool TypedObject::obj_getArrayElement(JSContext* cx, Handle typedObj, Handle typeDescr, uint32_t index, MutableHandleValue vp) { // Elements are not inherited from the prototype. if (index >= (size_t) typedObj->length()) { vp.setUndefined(); return true; } Rooted elementType(cx, &typeDescr->as().elementType()); size_t offset = elementType->size() * index; return Reify(cx, elementType, typedObj, offset, vp); } bool TypedObject::obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, HandleValue receiver, ObjectOpResult& result) { Rooted typedObj(cx, &obj->as()); switch (typedObj->typeDescr().kind()) { case type::Scalar: case type::Reference: break; case type::Array: { if (JSID_IS_ATOM(id, cx->names().length)) { if (receiver.isObject() && obj == &receiver.toObject()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_REDEFINE_ARRAY_LENGTH); return false; } return result.failReadOnly(); } uint32_t index; if (IdIsIndex(id, &index)) { if (!receiver.isObject() || obj != &receiver.toObject()) return SetPropertyByDefining(cx, id, v, receiver, result); if (index >= uint32_t(typedObj->length())) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_BINARYARRAY_BAD_INDEX); return false; } Rooted elementType(cx); elementType = &typedObj->typeDescr().as().elementType(); size_t offset = elementType->size() * index; if (!ConvertAndCopyTo(cx, elementType, typedObj, offset, nullptr, v)) return false; return result.succeed(); } break; } case type::Struct: { Rooted descr(cx, &typedObj->typeDescr().as()); size_t fieldIndex; if (!descr->fieldIndex(id, &fieldIndex)) break; if (!receiver.isObject() || obj != &receiver.toObject()) return SetPropertyByDefining(cx, id, v, receiver, result); size_t offset = descr->fieldOffset(fieldIndex); Rooted fieldType(cx, &descr->fieldDescr(fieldIndex)); RootedAtom fieldName(cx, &descr->fieldName(fieldIndex)); if (!ConvertAndCopyTo(cx, fieldType, typedObj, offset, fieldName, v)) return false; return result.succeed(); } } return SetPropertyOnProto(cx, obj, id, v, receiver, result); } bool TypedObject::obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id, MutableHandle desc) { Rooted typedObj(cx, &obj->as()); if (!typedObj->isAttached()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_HANDLE_UNATTACHED); return false; } Rooted descr(cx, &typedObj->typeDescr()); switch (descr->kind()) { case type::Scalar: case type::Reference: break; case type::Array: { uint32_t index; if (IdIsIndex(id, &index)) { if (!obj_getArrayElement(cx, typedObj, descr, index, desc.value())) return false; desc.setAttributes(JSPROP_ENUMERATE | JSPROP_PERMANENT); desc.object().set(obj); return true; } if (JSID_IS_ATOM(id, cx->names().length)) { desc.value().setInt32(typedObj->length()); desc.setAttributes(JSPROP_READONLY | JSPROP_PERMANENT); desc.object().set(obj); return true; } break; } case type::Struct: { Rooted descr(cx, &typedObj->typeDescr().as()); size_t fieldIndex; if (!descr->fieldIndex(id, &fieldIndex)) break; size_t offset = descr->fieldOffset(fieldIndex); Rooted fieldType(cx, &descr->fieldDescr(fieldIndex)); if (!Reify(cx, fieldType, typedObj, offset, desc.value())) return false; desc.setAttributes(JSPROP_ENUMERATE | JSPROP_PERMANENT); desc.object().set(obj); return true; } } desc.object().set(nullptr); return true; } static bool IsOwnId(JSContext* cx, HandleObject obj, HandleId id) { uint32_t index; Rooted typedObj(cx, &obj->as()); switch (typedObj->typeDescr().kind()) { case type::Scalar: case type::Reference: return false; case type::Array: return IdIsIndex(id, &index) || JSID_IS_ATOM(id, cx->names().length); case type::Struct: size_t index; if (typedObj->typeDescr().as().fieldIndex(id, &index)) return true; } return false; } bool TypedObject::obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result) { if (IsOwnId(cx, obj, id)) return ReportPropertyError(cx, JSMSG_CANT_DELETE, id); RootedObject proto(cx, obj->staticPrototype()); if (!proto) return result.succeed(); return DeleteProperty(cx, proto, id, result); } bool TypedObject::obj_enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties, bool enumerableOnly) { MOZ_ASSERT(obj->is()); Rooted typedObj(cx, &obj->as()); Rooted descr(cx, &typedObj->typeDescr()); RootedId id(cx); switch (descr->kind()) { case type::Scalar: case type::Reference: { // Nothing to enumerate. break; } case type::Array: { if (!properties.reserve(typedObj->length())) return false; for (uint32_t index = 0; index < typedObj->length(); index++) { id = INT_TO_JSID(index); properties.infallibleAppend(id); } break; } case type::Struct: { size_t fieldCount = descr->as().fieldCount(); if (!properties.reserve(fieldCount)) return false; for (size_t index = 0; index < fieldCount; index++) { id = AtomToId(&descr->as().fieldName(index)); properties.infallibleAppend(id); } break; } } return true; } void OutlineTypedObject::notifyBufferDetached(void* newData) { setData(reinterpret_cast(newData)); } /****************************************************************************** * Inline typed objects */ /* static */ InlineTypedObject* InlineTypedObject::create(JSContext* cx, HandleTypeDescr descr, gc::InitialHeap heap) { gc::AllocKind allocKind = allocKindForTypeDescriptor(descr); const Class* clasp = descr->opaque() ? &InlineOpaqueTypedObject::class_ : &InlineTransparentTypedObject::class_; RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, clasp, TaggedProto(&descr->typedProto()), descr)); if (!group) return nullptr; NewObjectKind newKind = (heap == gc::TenuredHeap) ? TenuredObject : GenericObject; return NewObjectWithGroup(cx, group, allocKind, newKind); } /* static */ InlineTypedObject* InlineTypedObject::createCopy(JSContext* cx, Handle templateObject, gc::InitialHeap heap) { AutoSetNewObjectMetadata metadata(cx); Rooted descr(cx, &templateObject->typeDescr()); InlineTypedObject* res = create(cx, descr, heap); if (!res) return nullptr; memcpy(res->inlineTypedMem(), templateObject->inlineTypedMem(), templateObject->size()); return res; } /* static */ void InlineTypedObject::obj_trace(JSTracer* trc, JSObject* object) { InlineTypedObject& typedObj = object->as(); TraceEdge(trc, &typedObj.shape_, "InlineTypedObject_shape"); // Inline transparent objects do not have references and do not need more // tracing. If there is an entry in the compartment's LazyArrayBufferTable, // tracing that reference will be taken care of by the table itself. if (typedObj.is()) return; typedObj.typeDescr().traceInstances(trc, typedObj.inlineTypedMem(), 1); } /* static */ void InlineTypedObject::objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src) { // Inline typed object element arrays can be preserved on the stack by Ion // and need forwarding pointers created during a minor GC. We can't do this // in the trace hook because we don't have any stale data to determine // whether this object moved and where it was moved from. TypeDescr& descr = dst->as().typeDescr(); if (descr.kind() == type::Array) { // The forwarding pointer can be direct as long as there is enough // space for it. Other objects might point into the object's buffer, // but they will not set any direct forwarding pointers. uint8_t* oldData = reinterpret_cast(src) + offsetOfDataStart(); uint8_t* newData = dst->as().inlineTypedMem(); trc->runtime()->gc.nursery.maybeSetForwardingPointer(trc, oldData, newData, descr.size() >= sizeof(uintptr_t)); } } ArrayBufferObject* InlineTransparentTypedObject::getOrCreateBuffer(JSContext* cx) { ObjectWeakMap*& table = cx->compartment()->lazyArrayBuffers; if (!table) { table = cx->new_(cx); if (!table || !table->init()) return nullptr; } JSObject* obj = table->lookup(this); if (obj) return &obj->as(); ArrayBufferObject::BufferContents contents = ArrayBufferObject::BufferContents::createPlain(inlineTypedMem()); size_t nbytes = typeDescr().size(); // Prevent GC under ArrayBufferObject::create, which might move this object // and its contents. gc::AutoSuppressGC suppress(cx); ArrayBufferObject* buffer = ArrayBufferObject::create(cx, nbytes, contents, ArrayBufferObject::DoesntOwnData); if (!buffer) return nullptr; // The owning object must always be the array buffer's first view. This // both prevents the memory from disappearing out from under the buffer // (the first view is held strongly by the buffer) and is used by the // buffer marking code to detect whether its data pointer needs to be // relocated. JS_ALWAYS_TRUE(buffer->addView(cx, this)); buffer->setForInlineTypedObject(); buffer->setHasTypedObjectViews(); if (!table->add(cx, this, buffer)) return nullptr; if (IsInsideNursery(this)) { // Make sure the buffer is traced by the next generational collection, // so that its data pointer is updated after this typed object moves. cx->runtime()->gc.storeBuffer.putWholeCell(buffer); } return buffer; } ArrayBufferObject* OutlineTransparentTypedObject::getOrCreateBuffer(JSContext* cx) { if (owner().is()) return &owner().as(); return owner().as().getOrCreateBuffer(cx); } /****************************************************************************** * Typed object classes */ const ObjectOps TypedObject::objectOps_ = { TypedObject::obj_lookupProperty, TypedObject::obj_defineProperty, TypedObject::obj_hasProperty, TypedObject::obj_getProperty, TypedObject::obj_setProperty, TypedObject::obj_getOwnPropertyDescriptor, TypedObject::obj_deleteProperty, nullptr, /* getElements */ TypedObject::obj_enumerate, nullptr, /* thisValue */ }; #define DEFINE_TYPEDOBJ_CLASS(Name, Trace, flag) \ static const ClassOps Name##ClassOps = { \ nullptr, /* addProperty */ \ nullptr, /* delProperty */ \ nullptr, /* getProperty */ \ nullptr, /* setProperty */ \ nullptr, /* enumerate */ \ nullptr, /* resolve */ \ nullptr, /* mayResolve */ \ nullptr, /* finalize */ \ nullptr, /* call */ \ nullptr, /* hasInstance */ \ nullptr, /* construct */ \ Trace, \ }; \ const Class Name::class_ = { \ # Name, \ Class::NON_NATIVE | flag, \ &Name##ClassOps, \ JS_NULL_CLASS_SPEC, \ JS_NULL_CLASS_EXT, \ &TypedObject::objectOps_ \ } DEFINE_TYPEDOBJ_CLASS(OutlineTransparentTypedObject, OutlineTypedObject::obj_trace, 0); DEFINE_TYPEDOBJ_CLASS(OutlineOpaqueTypedObject, OutlineTypedObject::obj_trace, 0); DEFINE_TYPEDOBJ_CLASS(InlineTransparentTypedObject, InlineTypedObject::obj_trace, JSCLASS_DELAY_METADATA_BUILDER); DEFINE_TYPEDOBJ_CLASS(InlineOpaqueTypedObject, InlineTypedObject::obj_trace, JSCLASS_DELAY_METADATA_BUILDER); static int32_t LengthForType(TypeDescr& descr) { switch (descr.kind()) { case type::Scalar: case type::Reference: case type::Struct: return 0; case type::Array: return descr.as().length(); } MOZ_CRASH("Invalid kind"); } static bool CheckOffset(uint32_t offset, uint32_t size, uint32_t alignment, uint32_t bufferLength) { // Offset (plus size) must be fully contained within the buffer. if (offset > bufferLength) return false; if (offset + size < offset) return false; if (offset + size > bufferLength) return false; // Offset must be aligned. if ((offset % alignment) != 0) return false; return true; } template inline bool CheckOffset(T, U, V, W) = delete; /*static*/ bool TypedObject::construct(JSContext* cx, unsigned int argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.callee().is()); Rooted callee(cx, &args.callee().as()); // Typed object constructors are overloaded in three ways, in order of // precedence: // // new TypeObj() // new TypeObj(buffer, [offset]) // new TypeObj(data) // Zero argument constructor: if (args.length() == 0) { int32_t length = LengthForType(*callee); Rooted obj(cx, createZeroed(cx, callee, length)); if (!obj) return false; args.rval().setObject(*obj); return true; } // Buffer constructor. if (args[0].isObject() && args[0].toObject().is()) { Rooted buffer(cx); buffer = &args[0].toObject().as(); if (callee->opaque() || buffer->isDetached()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_BAD_ARGS); return false; } uint32_t offset; if (args.length() >= 2 && !args[1].isUndefined()) { if (!args[1].isInt32() || args[1].toInt32() < 0) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_BAD_ARGS); return false; } offset = args[1].toInt32(); } else { offset = 0; } if (args.length() >= 3 && !args[2].isUndefined()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_BAD_ARGS); return false; } if (!CheckOffset(offset, callee->size(), callee->alignment(), buffer->byteLength())) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_BAD_ARGS); return false; } Rooted obj(cx); obj = OutlineTypedObject::createUnattached(cx, callee, LengthForType(*callee)); if (!obj) return false; obj->attach(cx, *buffer, offset); args.rval().setObject(*obj); return true; } // Data constructor. if (args[0].isObject()) { // Create the typed object. int32_t length = LengthForType(*callee); Rooted obj(cx, createZeroed(cx, callee, length)); if (!obj) return false; // Initialize from `arg`. if (!ConvertAndCopyTo(cx, obj, args[0])) return false; args.rval().setObject(*obj); return true; } // Something bogus. JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_BAD_ARGS); return false; } /****************************************************************************** * Intrinsics */ bool js::NewOpaqueTypedObject(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 1); MOZ_ASSERT(args[0].isObject() && args[0].toObject().is()); Rooted descr(cx, &args[0].toObject().as()); int32_t length = TypedObjLengthFromType(*descr); Rooted obj(cx); obj = OutlineTypedObject::createUnattachedWithClass(cx, &OutlineOpaqueTypedObject::class_, descr, length); if (!obj) return false; args.rval().setObject(*obj); return true; } bool js::NewDerivedTypedObject(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 3); MOZ_ASSERT(args[0].isObject() && args[0].toObject().is()); MOZ_ASSERT(args[1].isObject() && args[1].toObject().is()); MOZ_ASSERT(args[2].isInt32()); Rooted descr(cx, &args[0].toObject().as()); Rooted typedObj(cx, &args[1].toObject().as()); uint32_t offset = AssertedCast(args[2].toInt32()); Rooted obj(cx); obj = OutlineTypedObject::createDerived(cx, descr, typedObj, offset); if (!obj) return false; args.rval().setObject(*obj); return true; } bool js::AttachTypedObject(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 3); MOZ_ASSERT(args[2].isInt32()); OutlineTypedObject& handle = args[0].toObject().as(); TypedObject& target = args[1].toObject().as(); MOZ_ASSERT(!handle.isAttached()); uint32_t offset = AssertedCast(args[2].toInt32()); handle.attach(cx, target, offset); return true; } bool js::SetTypedObjectOffset(JSContext*, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 2); MOZ_ASSERT(args[0].isObject() && args[0].toObject().is()); MOZ_ASSERT(args[1].isInt32()); OutlineTypedObject& typedObj = args[0].toObject().as(); int32_t offset = args[1].toInt32(); MOZ_ASSERT(typedObj.isAttached()); typedObj.resetOffset(offset); args.rval().setUndefined(); return true; } bool js::ObjectIsTypeDescr(JSContext*, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 1); MOZ_ASSERT(args[0].isObject()); args.rval().setBoolean(args[0].toObject().is()); return true; } bool js::ObjectIsTypedObject(JSContext*, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 1); MOZ_ASSERT(args[0].isObject()); args.rval().setBoolean(args[0].toObject().is()); return true; } bool js::ObjectIsOpaqueTypedObject(JSContext*, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 1); JSObject& obj = args[0].toObject(); args.rval().setBoolean(obj.is() && obj.as().opaque()); return true; } bool js::ObjectIsTransparentTypedObject(JSContext*, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 1); JSObject& obj = args[0].toObject(); args.rval().setBoolean(obj.is() && !obj.as().opaque()); return true; } bool js::TypeDescrIsSimpleType(JSContext*, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 1); MOZ_ASSERT(args[0].isObject()); MOZ_ASSERT(args[0].toObject().is()); args.rval().setBoolean(args[0].toObject().is()); return true; } bool js::TypeDescrIsArrayType(JSContext*, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 1); MOZ_ASSERT(args[0].isObject()); MOZ_ASSERT(args[0].toObject().is()); JSObject& obj = args[0].toObject(); args.rval().setBoolean(obj.is()); return true; } bool js::TypedObjectIsAttached(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); TypedObject& typedObj = args[0].toObject().as(); args.rval().setBoolean(typedObj.isAttached()); return true; } bool js::TypedObjectTypeDescr(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); TypedObject& typedObj = args[0].toObject().as(); args.rval().setObject(typedObj.typeDescr()); return true; } bool js::ClampToUint8(JSContext*, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 1); MOZ_ASSERT(args[0].isNumber()); args.rval().setNumber(ClampDoubleToUint8(args[0].toNumber())); return true; } bool js::GetTypedObjectModule(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); Rooted global(cx, cx->global()); MOZ_ASSERT(global); args.rval().setObject(global->getTypedObjectModule()); return true; } #define JS_STORE_SCALAR_CLASS_IMPL(_constant, T, _name) \ bool \ js::StoreScalar##T::Func(JSContext* cx, unsigned argc, Value* vp) \ { \ CallArgs args = CallArgsFromVp(argc, vp); \ MOZ_ASSERT(args.length() == 3); \ MOZ_ASSERT(args[0].isObject() && args[0].toObject().is()); \ MOZ_ASSERT(args[1].isInt32()); \ MOZ_ASSERT(args[2].isNumber()); \ \ TypedObject& typedObj = args[0].toObject().as(); \ int32_t offset = args[1].toInt32(); \ \ /* Should be guaranteed by the typed objects API: */ \ MOZ_ASSERT(offset % MOZ_ALIGNOF(T) == 0); \ \ JS::AutoCheckCannotGC nogc(cx); \ T* target = reinterpret_cast(typedObj.typedMem(offset, nogc)); \ double d = args[2].toNumber(); \ *target = ConvertScalar(d); \ args.rval().setUndefined(); \ return true; \ } #define JS_STORE_REFERENCE_CLASS_IMPL(_constant, T, _name) \ bool \ js::StoreReference##_name::Func(JSContext* cx, unsigned argc, Value* vp) \ { \ CallArgs args = CallArgsFromVp(argc, vp); \ MOZ_ASSERT(args.length() == 4); \ MOZ_ASSERT(args[0].isObject() && args[0].toObject().is()); \ MOZ_ASSERT(args[1].isInt32()); \ MOZ_ASSERT(args[2].isString() || args[2].isNull()); \ \ TypedObject& typedObj = args[0].toObject().as(); \ int32_t offset = args[1].toInt32(); \ \ jsid id = args[2].isString() \ ? IdToTypeId(AtomToId(&args[2].toString()->asAtom())) \ : JSID_VOID; \ \ /* Should be guaranteed by the typed objects API: */ \ MOZ_ASSERT(offset % MOZ_ALIGNOF(T) == 0); \ \ JS::AutoCheckCannotGC nogc(cx); \ T* target = reinterpret_cast(typedObj.typedMem(offset, nogc)); \ if (!store(cx, target, args[3], &typedObj, id)) \ return false; \ args.rval().setUndefined(); \ return true; \ } #define JS_LOAD_SCALAR_CLASS_IMPL(_constant, T, _name) \ bool \ js::LoadScalar##T::Func(JSContext* cx, unsigned argc, Value* vp) \ { \ CallArgs args = CallArgsFromVp(argc, vp); \ MOZ_ASSERT(args.length() == 2); \ MOZ_ASSERT(args[0].isObject() && args[0].toObject().is()); \ MOZ_ASSERT(args[1].isInt32()); \ \ TypedObject& typedObj = args[0].toObject().as(); \ int32_t offset = args[1].toInt32(); \ \ /* Should be guaranteed by the typed objects API: */ \ MOZ_ASSERT(offset % MOZ_ALIGNOF(T) == 0); \ \ JS::AutoCheckCannotGC nogc(cx); \ T* target = reinterpret_cast(typedObj.typedMem(offset, nogc)); \ args.rval().setNumber((double) *target); \ return true; \ } #define JS_LOAD_REFERENCE_CLASS_IMPL(_constant, T, _name) \ bool \ js::LoadReference##_name::Func(JSContext* cx, unsigned argc, Value* vp) \ { \ CallArgs args = CallArgsFromVp(argc, vp); \ MOZ_ASSERT(args.length() == 2); \ MOZ_ASSERT(args[0].isObject() && args[0].toObject().is()); \ MOZ_ASSERT(args[1].isInt32()); \ \ TypedObject& typedObj = args[0].toObject().as(); \ int32_t offset = args[1].toInt32(); \ \ /* Should be guaranteed by the typed objects API: */ \ MOZ_ASSERT(offset % MOZ_ALIGNOF(T) == 0); \ \ JS::AutoCheckCannotGC nogc(cx); \ T* target = reinterpret_cast(typedObj.typedMem(offset, nogc)); \ load(target, args.rval()); \ return true; \ } // Because the precise syntax for storing values/objects/strings // differs, we abstract it away using specialized variants of the // private methods `store()` and `load()`. bool StoreReferenceAny::store(JSContext* cx, GCPtrValue* heap, const Value& v, TypedObject* obj, jsid id) { // Undefined values are not included in type inference information for // value properties of typed objects, as these properties are always // considered to contain undefined. if (!v.isUndefined()) { if (cx->isJSContext()) AddTypePropertyId(cx->asJSContext(), obj, id, v); else if (!HasTypePropertyId(obj, id, v)) return false; } *heap = v; return true; } bool StoreReferenceObject::store(JSContext* cx, GCPtrObject* heap, const Value& v, TypedObject* obj, jsid id) { MOZ_ASSERT(v.isObjectOrNull()); // or else Store_object is being misused // Null pointers are not included in type inference information for // object properties of typed objects, as these properties are always // considered to contain null. if (v.isObject()) { if (cx->isJSContext()) AddTypePropertyId(cx->asJSContext(), obj, id, v); else if (!HasTypePropertyId(obj, id, v)) return false; } *heap = v.toObjectOrNull(); return true; } bool StoreReferencestring::store(JSContext* cx, GCPtrString* heap, const Value& v, TypedObject* obj, jsid id) { MOZ_ASSERT(v.isString()); // or else Store_string is being misused // Note: string references are not reflected in type information for the object. *heap = v.toString(); return true; } void LoadReferenceAny::load(GCPtrValue* heap, MutableHandleValue v) { v.set(*heap); } void LoadReferenceObject::load(GCPtrObject* heap, MutableHandleValue v) { if (*heap) v.setObject(**heap); else v.setNull(); } void LoadReferencestring::load(GCPtrString* heap, MutableHandleValue v) { v.setString(*heap); } // I was using templates for this stuff instead of macros, but ran // into problems with the Unagi compiler. JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(JS_STORE_SCALAR_CLASS_IMPL) JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(JS_LOAD_SCALAR_CLASS_IMPL) JS_FOR_EACH_REFERENCE_TYPE_REPR(JS_STORE_REFERENCE_CLASS_IMPL) JS_FOR_EACH_REFERENCE_TYPE_REPR(JS_LOAD_REFERENCE_CLASS_IMPL) /////////////////////////////////////////////////////////////////////////// // Walking memory template static void visitReferences(TypeDescr& descr, uint8_t* mem, V& visitor) { if (descr.transparent()) return; switch (descr.kind()) { case type::Scalar: return; case type::Reference: visitor.visitReference(descr.as(), mem); return; case type::Array: { ArrayTypeDescr& arrayDescr = descr.as(); TypeDescr& elementDescr = arrayDescr.elementType(); for (uint32_t i = 0; i < arrayDescr.length(); i++) { visitReferences(elementDescr, mem, visitor); mem += elementDescr.size(); } return; } case type::Struct: { StructTypeDescr& structDescr = descr.as(); for (size_t i = 0; i < structDescr.fieldCount(); i++) { TypeDescr& descr = structDescr.fieldDescr(i); size_t offset = structDescr.fieldOffset(i); visitReferences(descr, mem + offset, visitor); } return; } } MOZ_CRASH("Invalid type repr kind"); } /////////////////////////////////////////////////////////////////////////// // Initializing instances namespace { class MemoryInitVisitor { const JSRuntime* rt_; public: explicit MemoryInitVisitor(const JSRuntime* rt) : rt_(rt) {} void visitReference(ReferenceTypeDescr& descr, uint8_t* mem); }; } // namespace void MemoryInitVisitor::visitReference(ReferenceTypeDescr& descr, uint8_t* mem) { switch (descr.type()) { case ReferenceTypeDescr::TYPE_ANY: { js::GCPtrValue* heapValue = reinterpret_cast(mem); heapValue->init(UndefinedValue()); return; } case ReferenceTypeDescr::TYPE_OBJECT: { js::GCPtrObject* objectPtr = reinterpret_cast(mem); objectPtr->init(nullptr); return; } case ReferenceTypeDescr::TYPE_STRING: { js::GCPtrString* stringPtr = reinterpret_cast(mem); stringPtr->init(rt_->emptyString); return; } } MOZ_CRASH("Invalid kind"); } void TypeDescr::initInstances(const JSRuntime* rt, uint8_t* mem, size_t length) { MOZ_ASSERT(length >= 1); MemoryInitVisitor visitor(rt); // Initialize the 0th instance memset(mem, 0, size()); if (opaque()) visitReferences(*this, mem, visitor); // Stamp out N copies of later instances uint8_t* target = mem; for (size_t i = 1; i < length; i++) { target += size(); memcpy(target, mem, size()); } } /////////////////////////////////////////////////////////////////////////// // Tracing instances namespace { class MemoryTracingVisitor { JSTracer* trace_; public: explicit MemoryTracingVisitor(JSTracer* trace) : trace_(trace) {} void visitReference(ReferenceTypeDescr& descr, uint8_t* mem); }; } // namespace void MemoryTracingVisitor::visitReference(ReferenceTypeDescr& descr, uint8_t* mem) { switch (descr.type()) { case ReferenceTypeDescr::TYPE_ANY: { GCPtrValue* heapValue = reinterpret_cast(mem); TraceEdge(trace_, heapValue, "reference-val"); return; } case ReferenceTypeDescr::TYPE_OBJECT: { GCPtrObject* objectPtr = reinterpret_cast(mem); TraceNullableEdge(trace_, objectPtr, "reference-obj"); return; } case ReferenceTypeDescr::TYPE_STRING: { GCPtrString* stringPtr = reinterpret_cast(mem); TraceNullableEdge(trace_, stringPtr, "reference-str"); return; } } MOZ_CRASH("Invalid kind"); } void TypeDescr::traceInstances(JSTracer* trace, uint8_t* mem, size_t length) { MemoryTracingVisitor visitor(trace); for (size_t i = 0; i < length; i++) { visitReferences(*this, mem, visitor); mem += size(); } } namespace { struct TraceListVisitor { typedef Vector VectorType; VectorType stringOffsets, objectOffsets, valueOffsets; void visitReference(ReferenceTypeDescr& descr, uint8_t* mem); bool fillList(Vector& entries); }; } // namespace void TraceListVisitor::visitReference(ReferenceTypeDescr& descr, uint8_t* mem) { VectorType* offsets; switch (descr.type()) { case ReferenceTypeDescr::TYPE_ANY: offsets = &valueOffsets; break; case ReferenceTypeDescr::TYPE_OBJECT: offsets = &objectOffsets; break; case ReferenceTypeDescr::TYPE_STRING: offsets = &stringOffsets; break; default: MOZ_CRASH("Invalid kind"); } AutoEnterOOMUnsafeRegion oomUnsafe; if (!offsets->append((uintptr_t) mem)) oomUnsafe.crash("TraceListVisitor::visitReference"); } bool TraceListVisitor::fillList(Vector& entries) { return entries.appendAll(stringOffsets) && entries.append(-1) && entries.appendAll(objectOffsets) && entries.append(-1) && entries.appendAll(valueOffsets) && entries.append(-1); } static bool CreateTraceList(JSContext* cx, HandleTypeDescr descr) { // Trace lists are only used for inline typed objects. We don't use them // for larger objects, both to limit the size of the trace lists and // because tracing outline typed objects is considerably more complicated // than inline ones. if (descr->size() > InlineTypedObject::MaximumSize || descr->transparent()) return true; TraceListVisitor visitor; visitReferences(*descr, nullptr, visitor); Vector entries(cx); if (!visitor.fillList(entries)) return false; // Trace lists aren't necessary for descriptors with no references. MOZ_ASSERT(entries.length() >= 3); if (entries.length() == 3) return true; int32_t* list = cx->pod_malloc(entries.length()); if (!list) return false; PodCopy(list, entries.begin(), entries.length()); descr->initReservedSlot(JS_DESCR_SLOT_TRACE_LIST, PrivateValue(list)); return true; } /* static */ void TypeDescr::finalize(FreeOp* fop, JSObject* obj) { TypeDescr& descr = obj->as(); if (descr.hasTraceList()) js_free(const_cast(descr.traceList())); }