diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp index a2417e796f..c599904903 100644 --- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -3008,5 +3008,21 @@ SetDocumentAndPageUseCounter(JSContext* aCx, JSObject* aObject, } } +void +DeprecationWarning(JSContext* aCx, JSObject* aObject, + nsIDocument::DeprecatedOperations aOperation) +{ + GlobalObject global(aCx, aObject); + if (global.Failed()) { + NS_ERROR("Could not create global for DeprecationWarning"); + return; + } + + nsCOMPtr window = do_QueryInterface(global.GetAsSupports()); + if (window && window->GetExtantDoc()) { + window->GetExtantDoc()->WarnOnceAbout(aOperation); + } +} + } // namespace dom } // namespace mozilla diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h index 23940fc2d1..bb3d8f1b3c 100644 --- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -28,6 +28,7 @@ #include "mozilla/ErrorResult.h" #include "mozilla/Likely.h" #include "mozilla/MemoryReporting.h" +#include "nsIDocument.h" #include "nsIGlobalObject.h" #include "nsIXPConnect.h" #include "nsJSUtils.h" @@ -3345,6 +3346,11 @@ void SetDocumentAndPageUseCounter(JSContext* aCx, JSObject* aObject, UseCounter aUseCounter); +// Warnings +void +DeprecationWarning(JSContext* aCx, JSObject* aObject, + nsIDocument::DeprecatedOperations aOperation); + // A callback to perform funToString on an interface object JSString* InterfaceObjectToString(JSContext* aCx, JS::Handle aObject, diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index 35bd93c9bd..e6d9b9a52d 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -1929,6 +1929,12 @@ DOMInterfaces = { 'TestExampleProxyInterface' : { 'headerFile': 'TestExampleProxyInterface-example.h', 'register': False + }, + +'TestDeprecatedInterface' : { + # Keep this in sync with TestExampleInterface + 'headerFile': 'TestBindingHeader.h', + 'register': False } } diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index 6456baa169..0e7294cee3 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -486,7 +486,6 @@ class CGDOMJSClass(CGThing): ${enumerate}, /* enumerate */ ${resolve}, /* resolve */ ${mayResolve}, /* mayResolve */ - nullptr, /* convert */ ${finalize}, /* finalize */ ${call}, /* call */ nullptr, /* hasInstance */ @@ -629,7 +628,6 @@ class CGPrototypeJSClass(CGThing): nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ nullptr, /* finalize */ nullptr, /* call */ nullptr, /* hasInstance */ @@ -726,7 +724,6 @@ class CGInterfaceObjectJSClass(CGThing): nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ nullptr, /* finalize */ ${ctorname}, /* call */ ${hasInstance}, /* hasInstance */ @@ -6932,20 +6929,13 @@ class CGPerSignatureCall(CGThing): } """))) - if idlNode.getExtendedAttribute("Deprecated"): + deprecated = (idlNode.getExtendedAttribute("Deprecated") or + (static and descriptor.interface.getExtendedAttribute("Deprecated"))) + if deprecated: cgThings.append(CGGeneric(dedent( """ - { - GlobalObject global(cx, obj); - if (global.Failed()) { - return false; - } - nsCOMPtr pWindow = do_QueryInterface(global.GetAsSupports()); - if (pWindow && pWindow->GetExtantDoc()) { - pWindow->GetExtantDoc()->WarnOnceAbout(nsIDocument::e%s); - } - } - """ % idlNode.getExtendedAttribute("Deprecated")[0]))) + DeprecationWarning(cx, obj, nsIDocument::e%s); + """ % deprecated[0]))) lenientFloatCode = None if idlNode.getExtendedAttribute('LenientFloat') is not None: diff --git a/dom/bindings/parser/WebIDL.py b/dom/bindings/parser/WebIDL.py index 5d3f132596..572dd3679d 100644 --- a/dom/bindings/parser/WebIDL.py +++ b/dom/bindings/parser/WebIDL.py @@ -1410,7 +1410,8 @@ class IDLInterface(IDLObjectWithScope, IDLExposureMixins): identifier == "AvailableIn" or identifier == "Func" or identifier == "CheckAnyPermissions" or - identifier == "CheckAllPermissions"): + identifier == "CheckAllPermissions" or + identifier == "Deprecated"): # Known extended attributes that take a string value if not attr.hasValue(): raise WebIDLError("[%s] must have a value" % identifier, diff --git a/dom/bindings/test/TestBindingHeader.h b/dom/bindings/test/TestBindingHeader.h index 57b6ad6c99..57c6e149ba 100644 --- a/dom/bindings/test/TestBindingHeader.h +++ b/dom/bindings/test/TestBindingHeader.h @@ -1357,6 +1357,20 @@ class TestChildInterface : public TestParentInterface { }; +class TestDeprecatedInterface : public nsISupports, public nsWrapperCache +{ +public: + NS_DECL_ISUPPORTS + + static + already_AddRefed + Constructor(const GlobalObject&, ErrorResult&); + + static void AlsoDeprecated(const GlobalObject&); + + virtual nsISupports* GetParentObject(); +}; + } // namespace dom } // namespace mozilla diff --git a/dom/bindings/test/TestCodeGen.webidl b/dom/bindings/test/TestCodeGen.webidl index 5de8b97969..f96e359fe7 100644 --- a/dom/bindings/test/TestCodeGen.webidl +++ b/dom/bindings/test/TestCodeGen.webidl @@ -1156,3 +1156,8 @@ interface TestCppKeywordNamedMethodsInterface { long volatile(); }; +[Deprecated="GetAttributeNode", Constructor()] +interface TestDeprecatedInterface { + static void alsoDeprecated(); +}; + diff --git a/dom/indexedDB/ActorsParent.cpp b/dom/indexedDB/ActorsParent.cpp index cfbcd4181c..4fd73a436e 100644 --- a/dom/indexedDB/ActorsParent.cpp +++ b/dom/indexedDB/ActorsParent.cpp @@ -23542,7 +23542,6 @@ const JSClass NormalJSRuntime::kGlobalClass = { /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ nullptr, - /* convert */ nullptr, /* finalize */ nullptr, /* call */ nullptr, /* hasInstance */ nullptr, diff --git a/dom/plugins/base/nsJSNPRuntime.cpp b/dom/plugins/base/nsJSNPRuntime.cpp index 1e568a2d57..c59feafb34 100644 --- a/dom/plugins/base/nsJSNPRuntime.cpp +++ b/dom/plugins/base/nsJSNPRuntime.cpp @@ -215,7 +215,6 @@ const static js::Class sNPObjectJSWrapperClass = nullptr, NPObjWrapper_Resolve, nullptr, /* mayResolve */ - nullptr, /* convert */ NPObjWrapper_Finalize, NPObjWrapper_Call, nullptr, /* hasInstance */ @@ -271,7 +270,7 @@ static const JSClass sNPObjectMemberClass = { "NPObject Ambiguous Member class", JSCLASS_HAS_PRIVATE, nullptr, nullptr, NPObjectMember_GetProperty, nullptr, - nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, NPObjectMember_Finalize, NPObjectMember_Call, nullptr, nullptr, NPObjectMember_Trace }; diff --git a/dom/workers/WorkerScope.cpp b/dom/workers/WorkerScope.cpp index 8a7b801407..fce6087321 100644 --- a/dom/workers/WorkerScope.cpp +++ b/dom/workers/WorkerScope.cpp @@ -763,7 +763,6 @@ const js::Class workerdebuggersandbox_class = { workerdebuggersandbox_enumerate, workerdebuggersandbox_resolve, nullptr, /* mayResolve */ - nullptr, /* convert */ workerdebuggersandbox_finalize, nullptr, nullptr, diff --git a/dom/xbl/nsXBLBinding.cpp b/dom/xbl/nsXBLBinding.cpp index 1560031627..0b563864ac 100644 --- a/dom/xbl/nsXBLBinding.cpp +++ b/dom/xbl/nsXBLBinding.cpp @@ -92,7 +92,7 @@ static const JSClass gPrototypeJSClass = { // Our one reserved slot holds the relevant nsXBLPrototypeBinding JSCLASS_HAS_RESERVED_SLOTS(1), nullptr, nullptr, nullptr, nullptr, - XBLEnumerate, nullptr, nullptr, + XBLEnumerate, nullptr, nullptr, XBLFinalize, nullptr, nullptr, nullptr, nullptr }; diff --git a/js/public/Class.h b/js/public/Class.h index e95e64c2c5..a0e0fa4a33 100644 --- a/js/public/Class.h +++ b/js/public/Class.h @@ -338,12 +338,6 @@ typedef bool typedef bool (* JSMayResolveOp)(const JSAtomState& names, jsid id, JSObject* maybeObj); -// Convert obj to the given type, returning true with the resulting value in -// *vp on success, and returning false on error or exception. -typedef bool -(* JSConvertOp)(JSContext* cx, JS::HandleObject obj, JSType type, - JS::MutableHandleValue vp); - // Finalize obj, which the garbage collector has determined to be unreachable // from other live objects or from GC roots. Obviously, finalizers must never // store a reference to obj. @@ -478,7 +472,6 @@ typedef void JSEnumerateOp enumerate; \ JSResolveOp resolve; \ JSMayResolveOp mayResolve; \ - JSConvertOp convert; \ FinalizeOpType finalize; \ JSNative call; \ JSHasInstanceOp hasInstance; \ @@ -807,8 +800,6 @@ static_assert(offsetof(JSClass, resolve) == offsetof(Class, resolve), "Class and JSClass must be consistent"); static_assert(offsetof(JSClass, mayResolve) == offsetof(Class, mayResolve), "Class and JSClass must be consistent"); -static_assert(offsetof(JSClass, convert) == offsetof(Class, convert), - "Class and JSClass must be consistent"); static_assert(offsetof(JSClass, finalize) == offsetof(Class, finalize), "Class and JSClass must be consistent"); static_assert(offsetof(JSClass, call) == offsetof(Class, call), diff --git a/js/public/Principals.h b/js/public/Principals.h index db7affe165..49db1f9878 100644 --- a/js/public/Principals.h +++ b/js/public/Principals.h @@ -86,7 +86,7 @@ JS_GetSecurityCallbacks(JSRuntime* rt); * called again, passing nullptr for 'prin'. */ extern JS_PUBLIC_API(void) -JS_SetTrustedPrincipals(JSRuntime* rt, const JSPrincipals* prin); +JS_SetTrustedPrincipals(JSRuntime* rt, JSPrincipals* prin); typedef void (* JSDestroyPrincipalsOp)(JSPrincipals* principals); diff --git a/js/public/TrackedOptimizationInfo.h b/js/public/TrackedOptimizationInfo.h index 9569d30d37..57d3710924 100644 --- a/js/public/TrackedOptimizationInfo.h +++ b/js/public/TrackedOptimizationInfo.h @@ -132,8 +132,8 @@ namespace JS { _(ICGetElemStub_Dense) \ _(ICGetElemStub_DenseHole) \ _(ICGetElemStub_TypedArray) \ - _(ICGetElemStub_ArgsElement) \ - _(ICGetElemStub_ArgsElementStrict) \ + _(ICGetElemStub_ArgsElementMapped) \ + _(ICGetElemStub_ArgsElementUnmapped) \ \ _(ICSetElemStub_Dense) \ _(ICSetElemStub_TypedArray) \ diff --git a/js/public/UbiNode.h b/js/public/UbiNode.h index a510727e57..66ba815774 100644 --- a/js/public/UbiNode.h +++ b/js/public/UbiNode.h @@ -13,12 +13,16 @@ #include "mozilla/Maybe.h" #include "mozilla/MemoryReporting.h" #include "mozilla/Move.h" +#include "mozilla/RangedPtr.h" +#include "mozilla/TypeTraits.h" #include "mozilla/UniquePtr.h" +#include "mozilla/Variant.h" #include "jspubtd.h" #include "js/GCAPI.h" #include "js/HashTable.h" +#include "js/RootingAPI.h" #include "js/TracingAPI.h" #include "js/TypeDecls.h" #include "js/Vector.h" @@ -139,25 +143,299 @@ // teach the GC how to root ubi::Nodes, fix up hash tables that use them as // keys, etc. +class JSAtom; + namespace JS { namespace ubi { class Edge; class EdgeRange; +class StackFrame; } // namespace ubi } // namespace JS namespace mozilla { + template<> class DefaultDelete : public JS::DeletePolicy { }; + +template<> +class DefaultDelete : public JS::DeletePolicy { }; + } // namespace mozilla namespace JS { namespace ubi { using mozilla::Maybe; +using mozilla::Move; +using mozilla::RangedPtr; using mozilla::UniquePtr; +using mozilla::Variant; + +/*** ubi::StackFrame ******************************************************************************/ + +// Concrete JS::ubi::StackFrame instances backed by a live SavedFrame object +// store their strings as JSAtom*, while deserialized stack frames from offline +// heap snapshots store their strings as const char16_t*. In order to provide +// zero-cost accessors to these strings in a single interface that works with +// both cases, we use this variant type. +using AtomOrTwoByteChars = Variant; + +// The base class implemented by each ConcreteStackFrame type. Subclasses +// must not add data members to this class. +class JS_FRIEND_API(BaseStackFrame) { + friend class StackFrame; + + BaseStackFrame(const StackFrame&) = delete; + BaseStackFrame& operator=(const StackFrame&) = delete; + + protected: + void* ptr; + explicit BaseStackFrame(void* ptr) : ptr(ptr) { } + + public: + // This is a value type that should not have a virtual destructor. Don't add + // destructors in subclasses! + + // Get a unique identifier for this StackFrame. The identifier is not valid + // across garbage collections. + virtual uintptr_t identifier() const { return reinterpret_cast(ptr); } + + // Get this frame's parent frame. + virtual StackFrame parent() const = 0; + + // Get this frame's line number. + virtual uint32_t line() const = 0; + + // Get this frame's column number. + virtual uint32_t column() const = 0; + + // Get this frame's source name. Never null. + virtual AtomOrTwoByteChars source() const = 0; + + // Return this frame's function name if named, otherwise the inferred + // display name. Can be null. + virtual AtomOrTwoByteChars functionDisplayName() const = 0; + + // Returns true if this frame's function is system JavaScript running with + // trusted principals, false otherwise. + virtual bool isSystem() const = 0; + + // Return true if this frame's function is a self-hosted JavaScript builtin, + // false otherwise. + virtual bool isSelfHosted() const = 0; + + // Construct a SavedFrame stack for the stack starting with this frame and + // containing all of its parents. The SavedFrame objects will be placed into + // cx's current compartment. + // + // Note that the process of + // + // SavedFrame + // | + // V + // JS::ubi::StackFrame + // | + // V + // offline heap snapshot + // | + // V + // JS::ubi::StackFrame + // | + // V + // SavedFrame + // + // is lossy because we cannot serialize and deserialize the SavedFrame's + // principals in the offline heap snapshot, so JS::ubi::StackFrame + // simplifies the principals check into the boolean isSystem() state. This + // is fine because we only expose JS::ubi::Stack to devtools and chrome + // code, and not to the web platform. + virtual bool constructSavedFrameStack(JSContext* cx, + MutableHandleObject outSavedFrameStack) const = 0; + + // Trace the concrete implementation of JS::ubi::StackFrame. + virtual void trace(JSTracer* trc) = 0; +}; + +// A traits template with a specialization for each backing type that implements +// the ubi::BaseStackFrame interface. Each specialization must be the a subclass +// of ubi::BaseStackFrame. +template class ConcreteStackFrame; + +// A JS::ubi::StackFrame represents a frame in a recorded stack. It can be +// backed either by a live SavedFrame object or by a structure deserialized from +// an offline heap snapshot. +// +// It is a value type that may be memcpy'd hither and thither without worrying +// about constructors or destructors, similar to POD types. +// +// Its lifetime is the same as the lifetime of the graph that is being analyzed +// by the JS::ubi::Node that the JS::ubi::StackFrame came from. That is, if the +// graph being analyzed is the live heap graph, the JS::ubi::StackFrame is only +// valid within the scope of an AutoCheckCannotGC; if the graph being analyzed +// is an offline heap snapshot, the JS::ubi::StackFrame is valid as long as the +// offline heap snapshot is alive. +class JS_FRIEND_API(StackFrame) : public JS::Traceable { + // Storage in which we allocate BaseStackFrame subclasses. + mozilla::AlignedStorage2 storage; + + BaseStackFrame* base() { return storage.addr(); } + const BaseStackFrame* base() const { return storage.addr(); } + + template + void construct(T* ptr) { + static_assert(mozilla::IsBaseOf>::value, + "ConcreteStackFrame must inherit from BaseStackFrame"); + static_assert(sizeof(ConcreteStackFrame) == sizeof(*base()), + "ubi::ConcreteStackFrame specializations must be the same size as " + "ubi::BaseStackFrame"); + ConcreteStackFrame::construct(base(), ptr); + } + struct ConstructFunctor; + + public: + StackFrame() { construct(nullptr); } + + template + MOZ_IMPLICIT StackFrame(T* ptr) { + construct(ptr); + } + + template + StackFrame& operator=(T* ptr) { + construct(ptr); + return *this; + } + + // Constructors accepting SpiderMonkey's generic-pointer-ish types. + + template + explicit StackFrame(const JS::Handle& handle) { + construct(handle.get()); + } + + template + StackFrame& operator=(const JS::Handle& handle) { + construct(handle.get()); + return *this; + } + + template + explicit StackFrame(const JS::Rooted& root) { + construct(root.get()); + } + + template + StackFrame& operator=(const JS::Rooted& root) { + construct(root.get()); + return *this; + } + + // Because StackFrame is just a vtable pointer and an instance pointer, we + // can memcpy everything around instead of making concrete classes define + // virtual constructors. See the comment above Node's copy constructor for + // more details; that comment applies here as well. + StackFrame(const StackFrame& rhs) { + memcpy(storage.u.mBytes, rhs.storage.u.mBytes, sizeof(storage.u)); + } + + StackFrame& operator=(const StackFrame& rhs) { + memcpy(storage.u.mBytes, rhs.storage.u.mBytes, sizeof(storage.u)); + return *this; + } + + bool operator==(const StackFrame& rhs) const { return base()->ptr == rhs.base()->ptr; } + bool operator!=(const StackFrame& rhs) const { return !(*this == rhs); } + + explicit operator bool() const { + return base()->ptr != nullptr; + } + + // Copy this StackFrame's source name into the given |destination| + // buffer. Copy no more than |length| characters. The result is *not* null + // terminated. Returns how many characters were written into the buffer. + size_t source(RangedPtr destination, size_t length) const; + + // Copy this StackFrame's function display name into the given |destination| + // buffer. Copy no more than |length| characters. The result is *not* null + // terminated. Returns how many characters were written into the buffer. + size_t functionDisplayName(RangedPtr destination, size_t length) const; + + // Get the size of the respective strings. 0 is returned for null strings. + size_t sourceLength(); + size_t functionDisplayNameLength(); + + // JS::Traceable implementation just forwards to our virtual trace method. + static void trace(StackFrame* frame, JSTracer* trc) { + if (frame) + frame->trace(trc); + } + + // Methods that forward to virtual calls through BaseStackFrame. + + void trace(JSTracer* trc) { base()->trace(trc); } + uintptr_t identifier() const { return base()->identifier(); } + uint32_t line() const { return base()->line(); } + uint32_t column() const { return base()->column(); } + AtomOrTwoByteChars source() const { return base()->source(); } + AtomOrTwoByteChars functionDisplayName() const { return base()->functionDisplayName(); } + StackFrame parent() const { return base()->parent(); } + bool isSystem() const { return base()->isSystem(); } + bool isSelfHosted() const { return base()->isSelfHosted(); } + bool constructSavedFrameStack(JSContext* cx, + MutableHandleObject outSavedFrameStack) const { + return base()->constructSavedFrameStack(cx, outSavedFrameStack); + } + + struct HashPolicy { + using Lookup = JS::ubi::StackFrame; + + static js::HashNumber hash(const Lookup& lookup) { + return lookup.identifier(); + } + + static bool match(const StackFrame& key, const Lookup& lookup) { + return key == lookup; + } + + static void rekey(StackFrame& k, const StackFrame& newKey) { + k = newKey; + } + }; +}; + +// The ubi::StackFrame null pointer. Any attempt to operate on a null +// ubi::StackFrame crashes. +template<> +class JS_FRIEND_API(ConcreteStackFrame) : public BaseStackFrame { + explicit ConcreteStackFrame(void* ptr) : BaseStackFrame(ptr) { } + + public: + static void construct(void* storage, void*) { new (storage) ConcreteStackFrame(nullptr); } + + uintptr_t identifier() const override { return 0; } + void trace(JSTracer* trc) override { } + bool constructSavedFrameStack(JSContext* cx, MutableHandleObject out) const override { + out.set(nullptr); + return true; + } + + uint32_t line() const override { MOZ_CRASH("null JS::ubi::StackFrame"); } + uint32_t column() const override { MOZ_CRASH("null JS::ubi::StackFrame"); } + AtomOrTwoByteChars source() const override { MOZ_CRASH("null JS::ubi::StackFrame"); } + AtomOrTwoByteChars functionDisplayName() const override { MOZ_CRASH("null JS::ubi::StackFrame"); } + StackFrame parent() const override { MOZ_CRASH("null JS::ubi::StackFrame"); } + bool isSystem() const override { MOZ_CRASH("null JS::ubi::StackFrame"); } + bool isSelfHosted() const override { MOZ_CRASH("null JS::ubi::StackFrame"); } +}; + +JS_FRIEND_API(bool) ConstructSavedFrameStackSlow(JSContext* cx, JS::ubi::StackFrame& frame, + MutableHandleObject outSavedFrameStack); + + +/*** ubi::Node ************************************************************************************/ // The base class implemented by each ubi::Node referent type. Subclasses must // not add data members to this class. @@ -237,6 +515,16 @@ class JS_FRIEND_API(Base) { // nullptr is returned. virtual JSCompartment* compartment() const { return nullptr; } + // Return whether this node's referent's allocation stack was captured. + virtual bool hasAllocationStack() const { return false; } + + // Get the stack recorded at the time this node's referent was + // allocated. This must only be called when hasAllocationStack() is true. + virtual StackFrame allocationStack() const { + MOZ_CRASH("Concrete classes that have an allocation stack must override both " + "hasAllocationStack and allocationStack."); + } + // Methods for JSObject Referents // // These methods are only semantically valid if the referent is either a @@ -400,6 +688,11 @@ class JS_FRIEND_API(Node) { return base()->edges(cx, wantNames); } + bool hasAllocationStack() const { return base()->hasAllocationStack(); } + StackFrame allocationStack() const { + return base()->allocationStack(); + } + typedef Base::Id Id; Id identifier() const { return base()->identifier(); } @@ -419,6 +712,8 @@ class JS_FRIEND_API(Node) { }; +/*** Edge and EdgeRange ***************************************************************************/ + // Edge is the abstract base class representing an outgoing edge of a node. // Edges are owned by EdgeRanges, and need not have assignment operators or copy // constructors. @@ -549,6 +844,7 @@ class PreComputedEdgeRange : public EdgeRange { } }; +/*** RootList *************************************************************************************/ // RootList is a class that can be pointed to by a |ubi::Node|, creating a // fictional root-of-roots which has edges to every GC root in the JS @@ -606,7 +902,7 @@ class MOZ_STACK_CLASS JS_FRIEND_API(RootList) { }; -// Concrete classes for ubi::Node referent types. +/*** Concrete classes for ubi::Node referent types ************************************************/ template<> struct JS_FRIEND_API(Concrete) : public Base { @@ -667,6 +963,9 @@ class JS_FRIEND_API(Concrete) : public TracerConcreteWithCompartment& outName) const override; size_t size(mozilla::MallocSizeOf mallocSizeOf) const override; + bool hasAllocationStack() const override; + StackFrame allocationStack() const override; + protected: explicit Concrete(JSObject* ptr) : TracerConcreteWithCompartment(ptr) { } @@ -711,6 +1010,7 @@ namespace js { // Make ubi::Node::HashPolicy the default hash policy for ubi::Node. template<> struct DefaultHasher : JS::ubi::Node::HashPolicy { }; +template<> struct DefaultHasher : JS::ubi::StackFrame::HashPolicy { }; } // namespace js diff --git a/js/src/asmjs/AsmJSModule.cpp b/js/src/asmjs/AsmJSModule.cpp index 03a0487909..af71352cf0 100644 --- a/js/src/asmjs/AsmJSModule.cpp +++ b/js/src/asmjs/AsmJSModule.cpp @@ -1013,7 +1013,6 @@ const Class AsmJSModuleObject::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ AsmJSModuleObject_finalize, nullptr, /* call */ nullptr, /* hasInstance */ diff --git a/js/src/builtin/Intl.cpp b/js/src/builtin/Intl.cpp index d91fa7c90c..55c3b22777 100644 --- a/js/src/builtin/Intl.cpp +++ b/js/src/builtin/Intl.cpp @@ -569,7 +569,6 @@ static const Class CollatorClass = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ collator_finalize }; @@ -1064,7 +1063,6 @@ static const Class NumberFormatClass = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ numberFormat_finalize }; @@ -1534,7 +1532,6 @@ static const Class DateTimeFormatClass = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ dateTimeFormat_finalize }; diff --git a/js/src/builtin/MapObject.cpp b/js/src/builtin/MapObject.cpp index 83fdce5ba8..30efb760ea 100644 --- a/js/src/builtin/MapObject.cpp +++ b/js/src/builtin/MapObject.cpp @@ -114,7 +114,6 @@ const Class MapIteratorObject::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ MapIteratorObject::finalize }; @@ -228,7 +227,6 @@ const Class MapObject::class_ = { nullptr, // enumerate nullptr, // resolve nullptr, // mayResolve - nullptr, // convert finalize, nullptr, // call nullptr, // hasInstance @@ -835,7 +833,6 @@ const Class SetIteratorObject::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ SetIteratorObject::finalize }; @@ -973,7 +970,6 @@ const Class SetObject::class_ = { nullptr, // enumerate nullptr, // resolve nullptr, // mayResolve - nullptr, // convert finalize, nullptr, // call nullptr, // hasInstance diff --git a/js/src/builtin/ModuleObject.cpp b/js/src/builtin/ModuleObject.cpp index d0b4b97b62..35794b43b6 100644 --- a/js/src/builtin/ModuleObject.cpp +++ b/js/src/builtin/ModuleObject.cpp @@ -6,11 +6,14 @@ #include "builtin/ModuleObject.h" +#include "frontend/ParseNode.h" +#include "frontend/SharedContext.h" #include "gc/Tracer.h" #include "jsobjinlines.h" using namespace js; +using namespace js::frontend; typedef JS::Rooted RootedImportEntry; typedef JS::Rooted RootedExportEntry; @@ -231,8 +234,7 @@ ModuleObject::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ - ModuleObject::finalize, + nullptr, /* finalize */ nullptr, /* call */ nullptr, /* hasInstance */ nullptr, /* construct */ diff --git a/js/src/builtin/Object.cpp b/js/src/builtin/Object.cpp index bf9e1edce7..5d7990074e 100644 --- a/js/src/builtin/Object.cpp +++ b/js/src/builtin/Object.cpp @@ -1114,7 +1114,6 @@ const Class PlainObject::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ nullptr, /* finalize */ nullptr, /* call */ nullptr, /* hasInstance */ diff --git a/js/src/builtin/SIMD.cpp b/js/src/builtin/SIMD.cpp index 76c30c4750..dd410735f6 100644 --- a/js/src/builtin/SIMD.cpp +++ b/js/src/builtin/SIMD.cpp @@ -175,7 +175,6 @@ const Class SimdTypeDescr::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ TypeDescr::finalize, call }; diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index 612402e686..dfa4588a20 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -1073,7 +1073,6 @@ static const JSClass FinalizeCounterClass = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ finalize_counter_finalize }; @@ -1767,7 +1766,6 @@ const Class CloneBufferObject::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ Finalize }; diff --git a/js/src/builtin/TypedObject.cpp b/js/src/builtin/TypedObject.cpp index 74a0ac8623..5ab8846c7e 100644 --- a/js/src/builtin/TypedObject.cpp +++ b/js/src/builtin/TypedObject.cpp @@ -19,7 +19,6 @@ #include "vm/String.h" #include "vm/StringBuffer.h" #include "vm/TypedArrayObject.h" -#include "vm/WeakMapObject.h" #include "jsatominlines.h" #include "jsobjinlines.h" @@ -225,7 +224,6 @@ const Class js::ScalarTypeDescr::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ TypeDescr::finalize, ScalarTypeDescr::call }; @@ -323,7 +321,6 @@ const Class js::ReferenceTypeDescr::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ TypeDescr::finalize, ReferenceTypeDescr::call }; @@ -503,7 +500,6 @@ const Class ArrayTypeDescr::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ TypeDescr::finalize, nullptr, /* call */ nullptr, /* hasInstance */ @@ -732,7 +728,6 @@ const Class StructTypeDescr::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ TypeDescr::finalize, nullptr, /* call */ nullptr, /* hasInstance */ @@ -2258,7 +2253,6 @@ OutlineTransparentTypedObject::getOrCreateBuffer(JSContext* cx) nullptr, /* enumerate */ \ nullptr, /* resolve */ \ nullptr, /* mayResolve */ \ - nullptr, /* convert */ \ nullptr, /* finalize */ \ nullptr, /* call */ \ nullptr, /* hasInstance */ \ diff --git a/js/src/builtin/WeakMapObject.cpp b/js/src/builtin/WeakMapObject.cpp new file mode 100644 index 0000000000..6429b33072 --- /dev/null +++ b/js/src/builtin/WeakMapObject.cpp @@ -0,0 +1,484 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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/WeakMapObject.h" + +#include "jsapi.h" +#include "jscntxt.h" + +#include "vm/Interpreter-inl.h" + +using namespace js; +using namespace js::gc; + +using mozilla::UniquePtr; + +MOZ_ALWAYS_INLINE bool +IsWeakMap(HandleValue v) +{ + return v.isObject() && v.toObject().is(); +} + +MOZ_ALWAYS_INLINE bool +WeakMap_has_impl(JSContext* cx, const CallArgs& args) +{ + MOZ_ASSERT(IsWeakMap(args.thisv())); + + if (!args.get(0).isObject()) { + args.rval().setBoolean(false); + return true; + } + + if (ObjectValueMap* map = args.thisv().toObject().as().getMap()) { + JSObject* key = &args[0].toObject(); + if (map->has(key)) { + args.rval().setBoolean(true); + return true; + } + } + + args.rval().setBoolean(false); + return true; +} + +bool +js::WeakMap_has(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +MOZ_ALWAYS_INLINE bool +WeakMap_clear_impl(JSContext* cx, const CallArgs& args) +{ + MOZ_ASSERT(IsWeakMap(args.thisv())); + + // We can't js_delete the weakmap because the data gathered during GC is + // used by the Cycle Collector. + if (ObjectValueMap* map = args.thisv().toObject().as().getMap()) + map->clear(); + + args.rval().setUndefined(); + return true; +} + +bool +js::WeakMap_clear(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +MOZ_ALWAYS_INLINE bool +WeakMap_get_impl(JSContext* cx, const CallArgs& args) +{ + MOZ_ASSERT(IsWeakMap(args.thisv())); + + if (!args.get(0).isObject()) { + args.rval().setUndefined(); + return true; + } + + if (ObjectValueMap* map = args.thisv().toObject().as().getMap()) { + JSObject* key = &args[0].toObject(); + if (ObjectValueMap::Ptr ptr = map->lookup(key)) { + args.rval().set(ptr->value()); + return true; + } + } + + args.rval().setUndefined(); + return true; +} + +bool +js::WeakMap_get(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +MOZ_ALWAYS_INLINE bool +WeakMap_delete_impl(JSContext* cx, const CallArgs& args) +{ + MOZ_ASSERT(IsWeakMap(args.thisv())); + + if (!args.get(0).isObject()) { + args.rval().setBoolean(false); + return true; + } + + if (ObjectValueMap* map = args.thisv().toObject().as().getMap()) { + JSObject* key = &args[0].toObject(); + if (ObjectValueMap::Ptr ptr = map->lookup(key)) { + map->remove(ptr); + args.rval().setBoolean(true); + return true; + } + } + + args.rval().setBoolean(false); + return true; +} + +bool +js::WeakMap_delete(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +static bool +TryPreserveReflector(JSContext* cx, HandleObject obj) +{ + if (obj->getClass()->ext.isWrappedNative || + (obj->getClass()->flags & JSCLASS_IS_DOMJSCLASS) || + (obj->is() && + obj->as().handler()->family() == GetDOMProxyHandlerFamily())) + { + MOZ_ASSERT(cx->runtime()->preserveWrapperCallback); + if (!cx->runtime()->preserveWrapperCallback(cx, obj)) { + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_BAD_WEAKMAP_KEY); + return false; + } + } + return true; +} + +static inline void +WeakMapPostWriteBarrier(JSRuntime* rt, ObjectValueMap* weakMap, JSObject* key) +{ + if (key && IsInsideNursery(key)) + rt->gc.storeBuffer.putGeneric(gc::HashKeyRef(weakMap, key)); +} + +static MOZ_ALWAYS_INLINE bool +SetWeakMapEntryInternal(JSContext* cx, Handle mapObj, + HandleObject key, HandleValue value) +{ + ObjectValueMap* map = mapObj->getMap(); + if (!map) { + AutoInitGCManagedObject newMap( + cx->make_unique(cx, mapObj.get())); + if (!newMap) + return false; + if (!newMap->init()) { + JS_ReportOutOfMemory(cx); + return false; + } + map = newMap.release(); + mapObj->setPrivate(map); + } + + // Preserve wrapped native keys to prevent wrapper optimization. + if (!TryPreserveReflector(cx, key)) + return false; + + if (JSWeakmapKeyDelegateOp op = key->getClass()->ext.weakmapKeyDelegateOp) { + RootedObject delegate(cx, op(key)); + if (delegate && !TryPreserveReflector(cx, delegate)) + return false; + } + + MOZ_ASSERT(key->compartment() == mapObj->compartment()); + MOZ_ASSERT_IF(value.isObject(), value.toObject().compartment() == mapObj->compartment()); + if (!map->put(key, value)) { + JS_ReportOutOfMemory(cx); + return false; + } + WeakMapPostWriteBarrier(cx->runtime(), map, key.get()); + return true; +} + +MOZ_ALWAYS_INLINE bool +WeakMap_set_impl(JSContext* cx, const CallArgs& args) +{ + MOZ_ASSERT(IsWeakMap(args.thisv())); + + if (!args.get(0).isObject()) { + UniquePtr bytes = + DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, args.get(0), nullptr); + if (!bytes) + return false; + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, bytes.get()); + return false; + } + + RootedObject key(cx, &args[0].toObject()); + Rooted thisObj(cx, &args.thisv().toObject()); + Rooted map(cx, &thisObj->as()); + + if (!SetWeakMapEntryInternal(cx, map, key, args.get(1))) + return false; + args.rval().set(args.thisv()); + return true; +} + +bool +js::WeakMap_set(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +JS_FRIEND_API(bool) +JS_NondeterministicGetWeakMapKeys(JSContext* cx, HandleObject objArg, MutableHandleObject ret) +{ + RootedObject obj(cx, objArg); + obj = UncheckedUnwrap(obj); + if (!obj || !obj->is()) { + ret.set(nullptr); + return true; + } + RootedObject arr(cx, NewDenseEmptyArray(cx)); + if (!arr) + return false; + ObjectValueMap* map = obj->as().getMap(); + if (map) { + // Prevent GC from mutating the weakmap while iterating. + AutoSuppressGC suppress(cx); + for (ObjectValueMap::Base::Range r = map->all(); !r.empty(); r.popFront()) { + JS::ExposeObjectToActiveJS(r.front().key()); + RootedObject key(cx, r.front().key()); + if (!cx->compartment()->wrap(cx, &key)) + return false; + if (!NewbornArrayPush(cx, arr, ObjectValue(*key))) + return false; + } + } + ret.set(arr); + return true; +} + +static void +WeakMap_mark(JSTracer* trc, JSObject* obj) +{ + if (ObjectValueMap* map = obj->as().getMap()) + map->trace(trc); +} + +static void +WeakMap_finalize(FreeOp* fop, JSObject* obj) +{ + if (ObjectValueMap* map = obj->as().getMap()) { +#ifdef DEBUG + map->~ObjectValueMap(); + memset(static_cast(map), 0xdc, sizeof(*map)); + fop->free_(map); +#else + fop->delete_(map); +#endif + } +} + +JS_PUBLIC_API(JSObject*) +JS::NewWeakMapObject(JSContext* cx) +{ + return NewBuiltinClassInstance(cx, &WeakMapObject::class_); +} + +JS_PUBLIC_API(bool) +JS::IsWeakMapObject(JSObject* obj) +{ + return obj->is(); +} + +JS_PUBLIC_API(bool) +JS::GetWeakMapEntry(JSContext* cx, HandleObject mapObj, HandleObject key, + MutableHandleValue rval) +{ + CHECK_REQUEST(cx); + assertSameCompartment(cx, key); + rval.setUndefined(); + ObjectValueMap* map = mapObj->as().getMap(); + if (!map) + return true; + if (ObjectValueMap::Ptr ptr = map->lookup(key)) { + // Read barrier to prevent an incorrectly gray value from escaping the + // weak map. See the comment before UnmarkGrayChildren in gc/Marking.cpp + ExposeValueToActiveJS(ptr->value().get()); + rval.set(ptr->value()); + } + return true; +} + +JS_PUBLIC_API(bool) +JS::SetWeakMapEntry(JSContext* cx, HandleObject mapObj, HandleObject key, + HandleValue val) +{ + CHECK_REQUEST(cx); + assertSameCompartment(cx, key, val); + Rooted rootedMap(cx, &mapObj->as()); + return SetWeakMapEntryInternal(cx, rootedMap, key, val); +} + +static bool +WeakMap_construct(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // ES6 draft rev 31 (15 Jan 2015) 23.3.1.1 step 1. + if (!ThrowIfNotConstructing(cx, args, "WeakMap")) + return false; + + RootedObject obj(cx, NewBuiltinClassInstance(cx, &WeakMapObject::class_)); + if (!obj) + return false; + + // Steps 5-6, 11. + if (!args.get(0).isNullOrUndefined()) { + // Steps 7a-b. + RootedValue adderVal(cx); + if (!GetProperty(cx, obj, obj, cx->names().set, &adderVal)) + return false; + + // Step 7c. + if (!IsCallable(adderVal)) + return ReportIsNotFunction(cx, adderVal); + + bool isOriginalAdder = IsNativeFunction(adderVal, WeakMap_set); + RootedValue mapVal(cx, ObjectValue(*obj)); + FastInvokeGuard fig(cx, adderVal); + InvokeArgs& args2 = fig.args(); + + // Steps 7d-e. + JS::ForOfIterator iter(cx); + if (!iter.init(args[0])) + return false; + + RootedValue pairVal(cx); + RootedObject pairObject(cx); + RootedValue keyVal(cx); + RootedObject keyObject(cx); + RootedValue val(cx); + while (true) { + // Steps 12a-e. + bool done; + if (!iter.next(&pairVal, &done)) + return false; + if (done) + break; + + // Step 12f. + if (!pairVal.isObject()) { + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, + JSMSG_INVALID_MAP_ITERABLE, "WeakMap"); + return false; + } + + pairObject = &pairVal.toObject(); + if (!pairObject) + return false; + + // Steps 12g-h. + if (!GetElement(cx, pairObject, pairObject, 0, &keyVal)) + return false; + + // Steps 12i-j. + if (!GetElement(cx, pairObject, pairObject, 1, &val)) + return false; + + // Steps 12k-l. + if (isOriginalAdder) { + if (keyVal.isPrimitive()) { + UniquePtr bytes = + DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, keyVal, nullptr); + if (!bytes) + return false; + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, bytes.get()); + return false; + } + + keyObject = &keyVal.toObject(); + if (!SetWeakMapEntry(cx, obj, keyObject, val)) + return false; + } else { + if (!args2.init(2)) + return false; + + args2.setCallee(adderVal); + args2.setThis(mapVal); + args2[0].set(keyVal); + args2[1].set(val); + + if (!fig.invoke(cx)) + return false; + } + } + } + + args.rval().setObject(*obj); + return true; +} + +const Class WeakMapObject::class_ = { + "WeakMap", + JSCLASS_HAS_PRIVATE | + JSCLASS_HAS_CACHED_PROTO(JSProto_WeakMap), + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* getProperty */ + nullptr, /* setProperty */ + nullptr, /* enumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + WeakMap_finalize, + nullptr, /* call */ + nullptr, /* hasInstance */ + nullptr, /* construct */ + WeakMap_mark +}; + +static const JSFunctionSpec weak_map_methods[] = { + JS_FN("has", WeakMap_has, 1, 0), + JS_FN("get", WeakMap_get, 1, 0), + JS_FN("delete", WeakMap_delete, 1, 0), + JS_FN("set", WeakMap_set, 2, 0), + JS_FN("clear", WeakMap_clear, 0, 0), + JS_FS_END +}; + +static JSObject* +InitWeakMapClass(JSContext* cx, HandleObject obj, bool defineMembers) +{ + MOZ_ASSERT(obj->isNative()); + + Rooted global(cx, &obj->as()); + + RootedPlainObject proto(cx, NewBuiltinClassInstance(cx)); + if (!proto) + return nullptr; + + RootedFunction ctor(cx, global->createConstructor(cx, WeakMap_construct, + cx->names().WeakMap, 0)); + if (!ctor) + return nullptr; + + if (!LinkConstructorAndPrototype(cx, ctor, proto)) + return nullptr; + + if (defineMembers) { + if (!DefinePropertiesAndFunctions(cx, proto, nullptr, weak_map_methods)) + return nullptr; + } + + if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_WeakMap, ctor, proto)) + return nullptr; + return proto; +} + +JSObject* +js::InitWeakMapClass(JSContext* cx, HandleObject obj) +{ + return InitWeakMapClass(cx, obj, true); +} + +JSObject* +js::InitBareWeakMapCtor(JSContext* cx, HandleObject obj) +{ + return InitWeakMapClass(cx, obj, false); +} + diff --git a/js/src/builtin/WeakMapObject.h b/js/src/builtin/WeakMapObject.h new file mode 100644 index 0000000000..c9b95cea39 --- /dev/null +++ b/js/src/builtin/WeakMapObject.h @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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/. */ + +#ifndef builtin_WeakMapObject_h +#define builtin_WeakMapObject_h + +#include "jsobj.h" +#include "jsweakmap.h" + +namespace js { + +class WeakMapObject : public NativeObject +{ + public: + static const Class class_; + + ObjectValueMap* getMap() { return static_cast(getPrivate()); } +}; + +} // namespace js + +#endif /* builtin_WeakMapObject_h */ diff --git a/js/src/ctypes/CTypes.cpp b/js/src/ctypes/CTypes.cpp index bb1b70dcd5..a548b5488e 100644 --- a/js/src/ctypes/CTypes.cpp +++ b/js/src/ctypes/CTypes.cpp @@ -546,7 +546,7 @@ static const JSClass sCABIClass = { static const JSClass sCTypeProtoClass = { "CType", JSCLASS_HAS_RESERVED_SLOTS(CTYPEPROTO_SLOTS), - nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, ConstructAbstract, nullptr, ConstructAbstract }; @@ -561,7 +561,7 @@ static const JSClass sCDataProtoClass = { static const JSClass sCTypeClass = { "CType", JSCLASS_HAS_RESERVED_SLOTS(CTYPE_SLOTS), - nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, CType::Finalize, CType::ConstructData, CType::HasInstance, CType::ConstructData, CType::Trace @@ -571,14 +571,14 @@ static const JSClass sCDataClass = { "CData", JSCLASS_HAS_RESERVED_SLOTS(CDATA_SLOTS), nullptr, nullptr, ArrayType::Getter, ArrayType::Setter, - nullptr, nullptr, nullptr, nullptr, CData::Finalize, + nullptr, nullptr, nullptr, CData::Finalize, FunctionType::Call, nullptr, FunctionType::Call }; static const JSClass sCClosureClass = { "CClosure", JSCLASS_HAS_RESERVED_SLOTS(CCLOSURE_SLOTS), - nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, CClosure::Finalize, nullptr, nullptr, nullptr, CClosure::Trace }; @@ -600,7 +600,7 @@ static const JSClass sCDataFinalizerProtoClass = { static const JSClass sCDataFinalizerClass = { "CDataFinalizer", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(CDATAFINALIZER_SLOTS), - nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, CDataFinalizer::Finalize }; @@ -786,14 +786,14 @@ static const JSClass sUInt64ProtoClass = { static const JSClass sInt64Class = { "Int64", JSCLASS_HAS_RESERVED_SLOTS(INT64_SLOTS), - nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, Int64Base::Finalize }; static const JSClass sUInt64Class = { "UInt64", JSCLASS_HAS_RESERVED_SLOTS(INT64_SLOTS), - nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, Int64Base::Finalize }; diff --git a/js/src/ctypes/Library.cpp b/js/src/ctypes/Library.cpp index 19895d8b6f..5ddaa68a39 100644 --- a/js/src/ctypes/Library.cpp +++ b/js/src/ctypes/Library.cpp @@ -35,7 +35,7 @@ typedef Rooted RootedFlatString; static const JSClass sLibraryClass = { "Library", JSCLASS_HAS_RESERVED_SLOTS(LIBRARY_SLOTS), - nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, Library::Finalize }; diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index ed78820667..4ab4aeff20 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -13,6 +13,7 @@ using namespace js; using namespace js::frontend; +using mozilla::ArrayLength; using mozilla::IsFinite; #ifdef DEBUG diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 386c383f29..bc390e2bd2 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -656,6 +656,8 @@ FunctionBox::FunctionBox(ExclusiveContext* cx, ObjectBox* traceListHead, JSFunct enclosingStaticScope_(enclosingStaticScope), bufStart(0), bufEnd(0), + startLine(1), + startColumn(0), length(0), generatorKindBits_(GeneratorKindAsBits(generatorKind)), inGenexpLambda(false), @@ -880,7 +882,10 @@ Parser::standaloneModule(HandleModuleObject module) TokenKind tt; if (!tokenStream.getToken(&tt, TokenStream::Operand)) return null(); - MOZ_ASSERT(tt == TOK_EOF); + if (tt != TOK_EOF) { + report(ParseError, false, null(), JSMSG_GARBAGE_AFTER_INPUT, "module", TokenKindToDesc(tt)); + return null(); + } if (!FoldConstants(context, &pn, this)) return null(); @@ -1474,8 +1479,13 @@ Parser::newFunction(HandleAtom atom, FunctionSyntaxKind kind, return fun; } +/* + * WARNING: Do not call this function directly. + * Call either MatchOrInsertSemicolonAfterExpression or + * MatchOrInsertSemicolonAfterNonExpression instead, depending on context. + */ static bool -MatchOrInsertSemicolon(TokenStream& ts, TokenStream::Modifier modifier = TokenStream::None) +MatchOrInsertSemicolonHelper(TokenStream& ts, TokenStream::Modifier modifier) { TokenKind tt = TOK_EOF; if (!ts.peekTokenSameLine(&tt, modifier)) @@ -1494,6 +1504,18 @@ MatchOrInsertSemicolon(TokenStream& ts, TokenStream::Modifier modifier = TokenSt return true; } +static bool +MatchOrInsertSemicolonAfterExpression(TokenStream& ts) +{ + return MatchOrInsertSemicolonHelper(ts, TokenStream::None); +} + +static bool +MatchOrInsertSemicolonAfterNonExpression(TokenStream& ts) +{ + return MatchOrInsertSemicolonHelper(ts, TokenStream::Operand); +} + /* * The function LexicalLookup searches a static binding for the given name in * the stack of statements enclosing the statement currently being parsed. Each @@ -2850,7 +2872,7 @@ Parser::functionArgsAndBodyGeneric(InHandling inHandling, if (tokenStream.hadError()) return false; funbox->bufEnd = pos().end; - if (kind == Statement && !MatchOrInsertSemicolon(tokenStream)) + if (kind == Statement && !MatchOrInsertSemicolonAfterExpression(tokenStream)) return false; } @@ -2961,6 +2983,17 @@ IsEscapeFreeStringLiteral(const TokenPos& pos, JSAtom* str) return pos.begin + str->length() + 2 == pos.end; } +template +bool +Parser::checkUnescapedName() +{ + if (!tokenStream.currentToken().nameContainsEscape()) + return true; + + report(ParseError, false, null(), JSMSG_ESCAPED_KEYWORD); + return false; +} + template <> bool Parser::asmJS(Node list) @@ -4174,9 +4207,27 @@ Parser::variables(YieldHandling yieldHandling, handler.addList(pn, pn2); bool matched; - if (!tokenStream.matchToken(&matched, TOK_ASSIGN)) + // The '=' context after a variable name in a declaration is an + // opportunity for ASI, and thus for the next token to start an + // ExpressionStatement: + // + // var foo // VariableDeclaration + // /bar/g; // ExpressionStatement + // + // Therefore we must get the token here as Operand. + if (!tokenStream.matchToken(&matched, TOK_ASSIGN, TokenStream::Operand)) return null(); - if (matched) { + if (!matched) { + tokenStream.addModifierException(TokenStream::NoneIsOperand); + + if (data.isConst() && location == NotInForInit) { + report(ParseError, false, null(), JSMSG_BAD_CONST_DECL); + return null(); + } + + if (!data.bind(name, this)) + return null(); + } else { if (psimple) *psimple = false; @@ -4225,14 +4276,6 @@ Parser::variables(YieldHandling yieldHandling, if (!handler.finishInitializerAssignment(pn2, init, data.op())) return null(); } - } else { - if (data.isConst() && location == NotInForInit) { - report(ParseError, false, null(), JSMSG_BAD_CONST_DECL); - return null(); - } - - if (!data.bind(name, this)) - return null(); } handler.setEndPosition(pn, pn2); @@ -4398,7 +4441,7 @@ Parser::lexicalDeclaration(YieldHandling yieldHandling, bool i if (!pn) return null(); pn->pn_xflags = PNX_POPVAR; - return MatchOrInsertSemicolon(tokenStream) ? pn : nullptr; + return MatchOrInsertSemicolonAfterExpression(tokenStream) ? pn : nullptr; } template <> @@ -4459,10 +4502,11 @@ Parser::namedImportsOrNamespaceImport(TokenKind tt, Node impor if (!importName) return false; - if (!tokenStream.getToken(&tt)) + bool foundAs; + if (!tokenStream.matchContextualKeyword(&foundAs, context->names().as)) return false; - if (tt == TOK_NAME && tokenStream.currentName() == context->names().as) { + if (foundAs) { MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_BINDING_NAME); } else { // Keywords cannot be bound to themselves, so an import name @@ -4476,7 +4520,6 @@ Parser::namedImportsOrNamespaceImport(TokenKind tt, Node impor report(ParseError, false, null(), JSMSG_AS_AFTER_RESERVED_WORD, bytes.ptr()); return false; } - tokenStream.ungetToken(); } Node bindingName = newBoundImportForCurrentName(); @@ -4510,6 +4553,9 @@ Parser::namedImportsOrNamespaceImport(TokenKind tt, Node impor return false; } + if (!checkUnescapedName()) + return false; + MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_BINDING_NAME); Node importName = newName(context->names().star); @@ -4606,6 +4652,9 @@ Parser::importDeclaration() return null(); } + if (!checkUnescapedName()) + return null(); + MUST_MATCH_TOKEN(TOK_STRING, JSMSG_MODULE_SPEC_AFTER_FROM); } else if (tt == TOK_STRING) { // Handle the form |import 'a'| by leaving the list empty. This is @@ -4620,7 +4669,7 @@ Parser::importDeclaration() if (!moduleSpec) return null(); - if (!MatchOrInsertSemicolon(tokenStream, TokenStream::Operand)) + if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream)) return null(); return handler.newImportDeclaration(importSpecSet, moduleSpec, TokenPos(begin, pos().end)); @@ -4683,94 +4732,135 @@ Parser::exportDeclaration() TokenKind tt; if (!tokenStream.getToken(&tt)) return null(); - bool isExportStar = tt == TOK_MUL; switch (tt) { - case TOK_LC: - case TOK_MUL: + case TOK_LC: { kid = handler.newList(PNK_EXPORT_SPEC_LIST); if (!kid) return null(); - if (tt == TOK_LC) { - while (true) { - // Handle the forms |export {}| and |export { ..., }| (where ... - // is non empty), by escaping the loop early if the next token - // is }. - if (!tokenStream.peekToken(&tt)) - return null(); - if (tt == TOK_RC) - break; + while (true) { + // Handle the forms |export {}| and |export { ..., }| (where ... + // is non empty), by escaping the loop early if the next token + // is }. + if (!tokenStream.peekToken(&tt)) + return null(); + if (tt == TOK_RC) + break; - MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_BINDING_NAME); - Node bindingName = newName(tokenStream.currentName()); - if (!bindingName) - return null(); + MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_BINDING_NAME); + Node bindingName = newName(tokenStream.currentName()); + if (!bindingName) + return null(); - if (!tokenStream.getToken(&tt)) + bool foundAs; + if (!tokenStream.matchContextualKeyword(&foundAs, context->names().as)) + return null(); + if (foundAs) { + if (!tokenStream.getToken(&tt, TokenStream::KeywordIsName)) + return null(); + if (tt != TOK_NAME) { + report(ParseError, false, null(), JSMSG_NO_EXPORT_NAME); return null(); - if (tt == TOK_NAME && tokenStream.currentName() == context->names().as) { - if (!tokenStream.getToken(&tt, TokenStream::KeywordIsName)) - return null(); - if (tt != TOK_NAME) { - report(ParseError, false, null(), JSMSG_NO_EXPORT_NAME); - return null(); - } - } else { - tokenStream.ungetToken(); } - Node exportName = newName(tokenStream.currentName()); - if (!exportName) - return null(); - - if (!addExportName(exportName->pn_atom)) - return null(); - - Node exportSpec = handler.newBinary(PNK_EXPORT_SPEC, bindingName, exportName); - if (!exportSpec) - return null(); - - handler.addList(kid, exportSpec); - - bool matched; - if (!tokenStream.matchToken(&matched, TOK_COMMA)) - return null(); - if (!matched) - break; } - MUST_MATCH_TOKEN(TOK_RC, JSMSG_RC_AFTER_EXPORT_SPEC_LIST); - } else { - // Handle the form |export *| by adding a special export batch - // specifier to the list. - Node exportSpec = handler.newNullary(PNK_EXPORT_BATCH_SPEC, JSOP_NOP, pos()); + Node exportName = newName(tokenStream.currentName()); + if (!exportName) + return null(); + + if (!addExportName(exportName->pn_atom)) + return null(); + + Node exportSpec = handler.newBinary(PNK_EXPORT_SPEC, bindingName, exportName); if (!exportSpec) return null(); handler.addList(kid, exportSpec); + + bool matched; + if (!tokenStream.matchToken(&matched, TOK_COMMA)) + return null(); + if (!matched) + break; } - if (!tokenStream.getToken(&tt)) + + MUST_MATCH_TOKEN(TOK_RC, JSMSG_RC_AFTER_EXPORT_SPEC_LIST); + + // Careful! If |from| follows, even on a new line, it must start a + // FromClause: + // + // export { x } + // from "foo"; // a single ExportDeclaration + // + // But if it doesn't, we might have an ASI opportunity in Operand + // context, so simply matching a contextual keyword won't work: + // + // export { x } // ExportDeclaration, terminated by ASI + // fro\u006D // ExpressionStatement, the name "from" + // + // In that case let MatchOrInsertSemicolonAfterNonExpression sort out + // ASI or any necessary error. + TokenKind tt; + if (!tokenStream.getToken(&tt, TokenStream::Operand)) return null(); - if (tt == TOK_NAME && tokenStream.currentName() == context->names().from) { + + if (tt == TOK_NAME && + tokenStream.currentToken().name() == context->names().from && + !tokenStream.currentToken().nameContainsEscape()) + { MUST_MATCH_TOKEN(TOK_STRING, JSMSG_MODULE_SPEC_AFTER_FROM); Node moduleSpec = stringLiteral(); if (!moduleSpec) return null(); - if (!MatchOrInsertSemicolon(tokenStream)) + if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream)) return null(); return handler.newExportFromDeclaration(begin, kid, moduleSpec); - } else if (isExportStar) { - report(ParseError, false, null(), JSMSG_FROM_AFTER_EXPORT_STAR); - return null(); - } else { - tokenStream.ungetToken(); } - if (!MatchOrInsertSemicolon(tokenStream)) + tokenStream.ungetToken(); + + if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream)) return null(); break; + } + + case TOK_MUL: { + kid = handler.newList(PNK_EXPORT_SPEC_LIST); + if (!kid) + return null(); + + // Handle the form |export *| by adding a special export batch + // specifier to the list. + Node exportSpec = handler.newNullary(PNK_EXPORT_BATCH_SPEC, JSOP_NOP, pos()); + if (!exportSpec) + return null(); + + handler.addList(kid, exportSpec); + + if (!tokenStream.getToken(&tt)) + return null(); + if (tt != TOK_NAME || tokenStream.currentName() != context->names().from) { + report(ParseError, false, null(), JSMSG_FROM_AFTER_EXPORT_STAR); + return null(); + } + + if (!checkUnescapedName()) + return null(); + + MUST_MATCH_TOKEN(TOK_STRING, JSMSG_MODULE_SPEC_AFTER_FROM); + + Node moduleSpec = stringLiteral(); + if (!moduleSpec) + return null(); + + if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream)) + return null(); + + return handler.newExportFromDeclaration(begin, kid, moduleSpec); + } case TOK_FUNCTION: kid = functionStmt(YieldIsKeyword, NameRequired); @@ -4799,7 +4889,7 @@ Parser::exportDeclaration() return null(); kid->pn_xflags = PNX_POPVAR; - kid = MatchOrInsertSemicolon(tokenStream) ? kid : nullptr; + kid = MatchOrInsertSemicolonAfterExpression(tokenStream) ? kid : nullptr; if (!kid) return null(); @@ -4836,7 +4926,7 @@ Parser::exportDeclaration() return null(); kid = assignExpr(InAllowed, YieldIsKeyword); if (kid) { - if (!MatchOrInsertSemicolon(tokenStream)) + if (!MatchOrInsertSemicolonAfterExpression(tokenStream)) return null(); } break; @@ -4878,7 +4968,7 @@ Parser::expressionStatement(YieldHandling yieldHandling, InvokedPr Node pnexpr = expr(InAllowed, yieldHandling, invoked); if (!pnexpr) return null(); - if (!MatchOrInsertSemicolon(tokenStream)) + if (!MatchOrInsertSemicolonAfterExpression(tokenStream)) return null(); return handler.newExprStatement(pnexpr, pos().end); } @@ -4992,8 +5082,12 @@ Parser::matchInOrOf(bool* isForInp, bool* isForOfp) return false; *isForInp = tt == TOK_IN; *isForOfp = tt == TOK_NAME && tokenStream.currentToken().name() == context->names().of; - if (!*isForInp && !*isForOfp) + if (!*isForInp && !*isForOfp) { tokenStream.ungetToken(); + } else { + if (tt == TOK_NAME && !checkUnescapedName()) + return false; + } return true; } @@ -5690,7 +5784,7 @@ Parser::continueStatement(YieldHandling yieldHandling) } } - if (!MatchOrInsertSemicolon(tokenStream, TokenStream::Operand)) + if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream)) return null(); return handler.newContinueStatement(label, TokenPos(begin, pos().end)); @@ -5727,7 +5821,7 @@ Parser::breakStatement(YieldHandling yieldHandling) } } - if (!MatchOrInsertSemicolon(tokenStream, TokenStream::Operand)) + if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream)) return null(); return handler.newBreakStatement(label, TokenPos(begin, pos().end)); @@ -5749,7 +5843,6 @@ Parser::returnStatement(YieldHandling yieldHandling) TokenKind tt = TOK_EOF; if (!tokenStream.peekTokenSameLine(&tt, TokenStream::Operand)) return null(); - TokenStream::Modifier modifier = TokenStream::Operand; switch (tt) { case TOK_EOL: case TOK_EOF: @@ -5762,13 +5855,17 @@ Parser::returnStatement(YieldHandling yieldHandling) exprNode = expr(InAllowed, yieldHandling); if (!exprNode) return null(); - modifier = TokenStream::None; pc->funHasReturnExpr = true; } } - if (!MatchOrInsertSemicolon(tokenStream, modifier)) - return null(); + if (exprNode) { + if (!MatchOrInsertSemicolonAfterExpression(tokenStream)) + return null(); + } else { + if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream)) + return null(); + } Node genrval = null(); if (pc->isStarGenerator()) { @@ -6041,7 +6138,7 @@ Parser::throwStatement(YieldHandling yieldHandling) if (!throwExpr) return null(); - if (!MatchOrInsertSemicolon(tokenStream)) + if (!MatchOrInsertSemicolonAfterExpression(tokenStream)) return null(); return handler.newThrowStatement(throwExpr, TokenPos(begin, pos().end)); @@ -6236,7 +6333,7 @@ Parser::debuggerStatement() { TokenPos p; p.begin = pos().begin; - if (!MatchOrInsertSemicolon(tokenStream, TokenStream::Operand)) + if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream)) return null(); p.end = pos().end; @@ -6390,6 +6487,9 @@ Parser::classDefinition(YieldHandling yieldHandling, if (!tokenStream.peekToken(&tt, TokenStream::KeywordIsName)) return null(); if (tt != TOK_LP) { + if (!checkUnescapedName()) + return null(); + isStatic = true; } else { tokenStream.addModifierException(TokenStream::NoneIsKeywordIsName); @@ -6530,7 +6630,7 @@ Parser::statement(YieldHandling yieldHandling, bool canHaveDirecti // Tell js_EmitTree to generate a final POP. handler.setListFlag(pn, PNX_POPVAR); - if (!MatchOrInsertSemicolon(tokenStream)) + if (!MatchOrInsertSemicolonAfterExpression(tokenStream)) return null(); return pn; } @@ -6553,10 +6653,15 @@ Parser::statement(YieldHandling yieldHandling, bool canHaveDirecti return expressionStatement(yieldHandling); case TOK_YIELD: { + // Don't use a ternary operator here due to obscure linker issues + // around using static consts in the arms of a ternary. + TokenStream::Modifier modifier; + if (yieldExpressionsSupported()) + modifier = TokenStream::Operand; + else + modifier = TokenStream::None; + TokenKind next; - TokenStream::Modifier modifier = yieldExpressionsSupported() - ? TokenStream::Operand - : TokenStream::None; if (!tokenStream.peekToken(&next, modifier)) return null(); if (next == TOK_COLON) { @@ -6653,13 +6758,12 @@ Parser::statement(YieldHandling yieldHandling, bool canHaveDirecti // LexicalDeclaration[In, ?Yield] case TOK_LET: - if (!abortIfSyntaxParser()) - return null(); - return lexicalDeclaration(yieldHandling, /* isConst = */ false); case TOK_CONST: if (!abortIfSyntaxParser()) return null(); - return lexicalDeclaration(yieldHandling, /* isConst = */ true); + // [In] is the default behavior, because for-loops currently specially + // parse their heads to handle |in| in this situation. + return lexicalDeclaration(yieldHandling, /* isConst = */ tt == TOK_CONST); // ImportDeclaration (only inside modules) case TOK_IMPORT: @@ -8774,13 +8878,23 @@ Parser::propertyName(YieldHandling yieldHandling, Node propList, // We have parsed |get| or |set|. Look for an accessor property // name next. TokenKind tt; - if (!tokenStream.getToken(&tt, TokenStream::KeywordIsName)) + if (!tokenStream.peekToken(&tt, TokenStream::KeywordIsName)) return null(); if (tt == TOK_NAME) { + if (!checkUnescapedName()) + return null(); + + tokenStream.consumeKnownToken(TOK_NAME, TokenStream::KeywordIsName); + propAtom.set(tokenStream.currentName()); return handler.newObjectLiteralPropertyName(propAtom, pos()); } if (tt == TOK_STRING) { + if (!checkUnescapedName()) + return null(); + + tokenStream.consumeKnownToken(TOK_STRING, TokenStream::KeywordIsName); + propAtom.set(tokenStream.currentToken().atom()); uint32_t index; @@ -8793,16 +8907,26 @@ Parser::propertyName(YieldHandling yieldHandling, Node propList, return stringLiteral(); } if (tt == TOK_NUMBER) { + if (!checkUnescapedName()) + return null(); + + tokenStream.consumeKnownToken(TOK_NUMBER, TokenStream::KeywordIsName); + propAtom.set(DoubleToAtom(context, tokenStream.currentToken().number())); if (!propAtom.get()) return null(); return newNumber(tokenStream.currentToken()); } - if (tt == TOK_LB) + if (tt == TOK_LB) { + if (!checkUnescapedName()) + return null(); + + tokenStream.consumeKnownToken(TOK_LB, TokenStream::KeywordIsName); + return computedPropertyName(yieldHandling, propList); + } // Not an accessor property after all. - tokenStream.ungetToken(); propName = handler.newObjectLiteralPropertyName(propAtom.get(), pos()); if (!propName) return null(); @@ -9037,6 +9161,9 @@ Parser::tryNewTarget(Node &newTarget) return false; } + if (!checkUnescapedName()) + return false; + if (!pc->sc->allowNewTarget()) { reportWithOffset(ParseError, false, begin, JSMSG_BAD_NEWTARGET); return false; diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index c11571ebd1..d8a279f891 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -552,6 +552,8 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter bool isUnexpectedEOF() const { return isUnexpectedEOF_; } + bool checkUnescapedName(); + private: Parser* thisForCtor() { return this; } diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index 6671e7bf58..0522c06dd4 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -1013,16 +1013,6 @@ TokenStream::checkForKeyword(const KeywordInfo* kw, TokenKind* ttp) return reportStrictModeError(JSMSG_RESERVED_ID, kw->chars); } -bool -TokenStream::checkForKeyword(const char16_t* s, size_t length, TokenKind* ttp) -{ - const KeywordInfo* kw = FindKeyword(s, length); - if (!kw) - return true; - - return checkForKeyword(kw, ttp); -} - bool TokenStream::checkForKeyword(JSAtom* atom, TokenKind* ttp) { @@ -1233,13 +1223,23 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) length = userbuf.addressOfNextRawChar() - identStart; } - // Check for keywords unless the parser told us not to. + // Represent keywords as keyword tokens unless told otherwise. if (modifier != KeywordIsName) { - tp->type = TOK_NAME; - if (!checkForKeyword(chars, length, &tp->type)) - goto error; - if (tp->type != TOK_NAME) - goto out; + if (const KeywordInfo* kw = FindKeyword(chars, length)) { + // That said, keywords can't contain escapes. (Contexts where + // keywords are treated as names, that also sometimes treat + // keywords as keywords, must manually check this requirement.) + if (hadUnicodeEscape) { + reportError(JSMSG_ESCAPED_KEYWORD); + goto error; + } + + tp->type = TOK_NAME; + if (!checkForKeyword(kw, &tp->type)) + goto error; + if (tp->type != TOK_NAME) + goto out; + } } JSAtom* atom = AtomizeChars(cx, chars, length); diff --git a/js/src/frontend/TokenStream.h b/js/src/frontend/TokenStream.h index ce30320843..1750073181 100644 --- a/js/src/frontend/TokenStream.h +++ b/js/src/frontend/TokenStream.h @@ -226,6 +226,11 @@ struct Token return u.name->asPropertyName(); // poor-man's type verification } + bool nameContainsEscape() const { + PropertyName* n = name(); + return pos.begin + n->length() != pos.end; + } + JSAtom* atom() const { MOZ_ASSERT(type == TOK_STRING || type == TOK_TEMPLATE_HEAD || @@ -642,11 +647,28 @@ class MOZ_STACK_CLASS TokenStream MOZ_ALWAYS_TRUE(matched); } - bool matchContextualKeyword(bool* matchedp, Handle keyword) { + // Like matchToken(..., TOK_NAME) but further matching the name token only + // if it has the given characters, without containing escape sequences. + // If the name token has the given characters yet *does* contain an escape, + // a syntax error will be reported. + // + // This latter behavior makes this method unsuitable for use in any context + // where ASI might occur. In such places, an escaped "contextual keyword" + // on a new line is the start of an ExpressionStatement, not a continuation + // of a StatementListItem (or ImportDeclaration or ExportDeclaration, in + // modules). + bool matchContextualKeyword(bool* matchedp, Handle keyword, + Modifier modifier = None) + { TokenKind token; - if (!getToken(&token)) + if (!getToken(&token, modifier)) return false; if (token == TOK_NAME && currentToken().name() == keyword) { + if (currentToken().nameContainsEscape()) { + reportError(JSMSG_ESCAPED_KEYWORD); + return false; + } + *matchedp = true; } else { *matchedp = false; @@ -720,8 +742,8 @@ class MOZ_STACK_CLASS TokenStream return sourceMapURL_.get(); } - // If the name at s[0:length] is not a keyword in this version, return - // true with *ttp unchanged. + // If |atom| is not a keyword in this version, return true with *ttp + // unchanged. // // If it is a reserved word in this version and strictness mode, and thus // can't be present in correct code, report a SyntaxError and return false. @@ -730,10 +752,11 @@ class MOZ_STACK_CLASS TokenStream // null, report a SyntaxError ("if is a reserved identifier") and return // false. If ttp is non-null, return true with the keyword's TokenKind in // *ttp. - bool checkForKeyword(const KeywordInfo* kw, TokenKind* ttp); - bool checkForKeyword(const char16_t* s, size_t length, TokenKind* ttp); bool checkForKeyword(JSAtom* atom, TokenKind* ttp); + // Same semantics as above, but for the provided keyword. + bool checkForKeyword(const KeywordInfo* kw, TokenKind* ttp); + // This class maps a userbuf offset (which is 0-indexed) to a line number // (which is 1-indexed) and a column index (which is 0-indexed). class SourceCoords diff --git a/js/src/gc/RootMarking.cpp b/js/src/gc/RootMarking.cpp index ac254fd23d..a816f032f3 100644 --- a/js/src/gc/RootMarking.cpp +++ b/js/src/gc/RootMarking.cpp @@ -24,7 +24,6 @@ #include "js/HashTable.h" #include "vm/Debugger.h" #include "vm/JSONParser.h" -#include "vm/WeakMapObject.h" #include "jsgcinlines.h" #include "jsobjinlines.h" diff --git a/js/src/gdb/gdb-tests.cpp b/js/src/gdb/gdb-tests.cpp index ada42171ba..9ab81825c7 100644 --- a/js/src/gdb/gdb-tests.cpp +++ b/js/src/gdb/gdb-tests.cpp @@ -17,7 +17,7 @@ const JSClass global_class = { "global", JSCLASS_GLOBAL_FLAGS, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook }; diff --git a/js/src/irregexp/RegExpAST.h b/js/src/irregexp/RegExpAST.h index 7fc4b51009..c3d930e512 100644 --- a/js/src/irregexp/RegExpAST.h +++ b/js/src/irregexp/RegExpAST.h @@ -33,6 +33,11 @@ #include "irregexp/RegExpEngine.h" +#ifdef _MSC_VER +#undef min +#undef max +#endif + namespace js { namespace irregexp { diff --git a/js/src/jit-test/tests/modules/bug1204857.js b/js/src/jit-test/tests/modules/bug1204857.js new file mode 100644 index 0000000000..451da2e286 --- /dev/null +++ b/js/src/jit-test/tests/modules/bug1204857.js @@ -0,0 +1,2 @@ +// |jit-test| error: SyntaxError: unexpected garbage after module +parseModule(("}")); diff --git a/js/src/jit-test/tests/parser/modifier-regexp-vs-div.js b/js/src/jit-test/tests/parser/modifier-regexp-vs-div.js new file mode 100644 index 0000000000..8c9f044628 --- /dev/null +++ b/js/src/jit-test/tests/parser/modifier-regexp-vs-div.js @@ -0,0 +1,12 @@ +load(libdir + "syntax.js"); + +var repl_expr_flags = [ + "/bar/g; @", + "\n/bar/g; @", +]; + +function check_syntax_error(e, code) { + assertEq(e instanceof SyntaxError, true); +} + +test_syntax(repl_expr_flags, check_syntax_error, true); diff --git a/js/src/jit-test/tests/parser/regexp-after-variable.js b/js/src/jit-test/tests/parser/regexp-after-variable.js new file mode 100644 index 0000000000..3ba2206c1f --- /dev/null +++ b/js/src/jit-test/tests/parser/regexp-after-variable.js @@ -0,0 +1,8 @@ +// RegExp after variable name get parsed or throws error correctly. + +load(libdir + "asserts.js"); + +assertNoWarning(() => Function("var foo \n /bar/g"), SyntaxError, + "RegExp in next line should be parsed"); +assertThrowsInstanceOf(() => Function("var foo /bar/g"), SyntaxError, + "RegExp in same line should be error"); diff --git a/js/src/jit/BaselineDebugModeOSR.cpp b/js/src/jit/BaselineDebugModeOSR.cpp index ca7caec6a0..4ad1a8960d 100644 --- a/js/src/jit/BaselineDebugModeOSR.cpp +++ b/js/src/jit/BaselineDebugModeOSR.cpp @@ -8,6 +8,7 @@ #include "mozilla/DebugOnly.h" +#include "jit/BaselineIC.h" #include "jit/JitcodeMap.h" #include "jit/Linker.h" #include "jit/PerfSpewer.h" diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 09ee14acd1..59728470fd 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -2391,9 +2391,9 @@ TryAttachGetElemStub(JSContext* cx, JSScript* script, jsbytecode* pc, ICGetElem_ // Check for ArgumentsObj[int] accesses if (obj->is() && rhs.isInt32()) { - ICGetElem_Arguments::Which which = ICGetElem_Arguments::Normal; - if (obj->is()) - which = ICGetElem_Arguments::Strict; + ICGetElem_Arguments::Which which = ICGetElem_Arguments::Mapped; + if (obj->is()) + which = ICGetElem_Arguments::Unmapped; if (!ArgumentsGetElemStubExists(stub, which)) { JitSpew(JitSpew_BaselineIC, " Generating GetElem(ArgsObj[Int32]) stub"); ICGetElem_Arguments::Compiler compiler( @@ -3382,11 +3382,12 @@ ICGetElem_Arguments::Compiler::generateStubCode(MacroAssembler& masm) return true; } - MOZ_ASSERT(which_ == ICGetElem_Arguments::Strict || - which_ == ICGetElem_Arguments::Normal); + MOZ_ASSERT(which_ == ICGetElem_Arguments::Mapped || + which_ == ICGetElem_Arguments::Unmapped); - bool isStrict = which_ == ICGetElem_Arguments::Strict; - const Class* clasp = isStrict ? &StrictArgumentsObject::class_ : &NormalArgumentsObject::class_; + const Class* clasp = (which_ == ICGetElem_Arguments::Mapped) + ? &MappedArgumentsObject::class_ + : &UnmappedArgumentsObject::class_; AllocatableGeneralRegisterSet regs(availableGeneralRegs(2)); Register scratchReg = regs.takeAny(); @@ -5504,10 +5505,10 @@ TryAttachLengthStub(JSContext* cx, JSScript* script, ICGetProp_Fallback* stub, H if (obj->is() && res.isInt32()) { JitSpew(JitSpew_BaselineIC, " Generating GetProp(ArgsObj.length %s) stub", - obj->is() ? "Strict" : "Normal"); - ICGetProp_ArgumentsLength::Which which = ICGetProp_ArgumentsLength::Normal; - if (obj->is()) - which = ICGetProp_ArgumentsLength::Strict; + obj->is() ? "Mapped" : "Unmapped"); + ICGetProp_ArgumentsLength::Which which = ICGetProp_ArgumentsLength::Mapped; + if (obj->is()) + which = ICGetProp_ArgumentsLength::Unmapped; ICGetProp_ArgumentsLength::Compiler compiler(cx, which); ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); if (!newStub) @@ -6975,11 +6976,12 @@ ICGetProp_ArgumentsLength::Compiler::generateStubCode(MacroAssembler& masm) EmitStubGuardFailure(masm); return true; } - MOZ_ASSERT(which_ == ICGetProp_ArgumentsLength::Strict || - which_ == ICGetProp_ArgumentsLength::Normal); + MOZ_ASSERT(which_ == ICGetProp_ArgumentsLength::Mapped || + which_ == ICGetProp_ArgumentsLength::Unmapped); - bool isStrict = which_ == ICGetProp_ArgumentsLength::Strict; - const Class* clasp = isStrict ? &StrictArgumentsObject::class_ : &NormalArgumentsObject::class_; + const Class* clasp = (which_ == ICGetProp_ArgumentsLength::Mapped) + ? &MappedArgumentsObject::class_ + : &UnmappedArgumentsObject::class_; Register scratchReg = R1.scratchReg(); @@ -11145,6 +11147,13 @@ ICGetElem_NativePrototypeCallNative::Clone(JSContext* cx, other.holderShape()); } +template ICGetElem_NativePrototypeCallNative* +ICGetElem_NativePrototypeCallNative::Clone(JSContext*, ICStubSpace*, ICStub*, + ICGetElem_NativePrototypeCallNative&); +template ICGetElem_NativePrototypeCallNative* +ICGetElem_NativePrototypeCallNative::Clone(JSContext*, ICStubSpace*, ICStub*, + ICGetElem_NativePrototypeCallNative&); + template /* static */ ICGetElem_NativePrototypeCallScripted* ICGetElem_NativePrototypeCallScripted::Clone(JSContext* cx, @@ -11158,6 +11167,13 @@ ICGetElem_NativePrototypeCallScripted::Clone(JSContext* cx, other.holderShape()); } +template ICGetElem_NativePrototypeCallScripted* +ICGetElem_NativePrototypeCallScripted::Clone(JSContext*, ICStubSpace*, ICStub*, + ICGetElem_NativePrototypeCallScripted&); +template ICGetElem_NativePrototypeCallScripted* +ICGetElem_NativePrototypeCallScripted::Clone(JSContext*, ICStubSpace*, ICStub*, + ICGetElem_NativePrototypeCallScripted&); + ICGetElem_Dense::ICGetElem_Dense(JitCode* stubCode, ICStub* firstMonitorStub, Shape* shape) : ICMonitoredStub(GetElem_Dense, stubCode, firstMonitorStub), shape_(shape) diff --git a/js/src/jit/BaselineIC.h b/js/src/jit/BaselineIC.h index 25242df5fc..f2efd0007b 100644 --- a/js/src/jit/BaselineIC.h +++ b/js/src/jit/BaselineIC.h @@ -1492,7 +1492,7 @@ class ICGetElem_Arguments : public ICMonitoredStub { friend class ICStubSpace; public: - enum Which { Normal, Strict, Magic }; + enum Which { Mapped, Unmapped, Magic }; private: ICGetElem_Arguments(JitCode* stubCode, ICStub* firstMonitorStub, Which which) @@ -3243,7 +3243,7 @@ class ICGetProp_ArgumentsLength : public ICStub { friend class ICStubSpace; public: - enum Which { Normal, Strict, Magic }; + enum Which { Mapped, Unmapped, Magic }; protected: explicit ICGetProp_ArgumentsLength(JitCode* stubCode) diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 06dea37322..3327a575b0 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -34,6 +34,7 @@ #include "jit/MIRGenerator.h" #include "jit/MoveEmitter.h" #include "jit/RangeAnalysis.h" +#include "jit/SharedICHelpers.h" #include "vm/MatchPairs.h" #include "vm/RegExpStatics.h" #include "vm/TraceLogging.h" diff --git a/js/src/jit/InstructionReordering.cpp b/js/src/jit/InstructionReordering.cpp index bc1eb993ca..50f9bf1c11 100644 --- a/js/src/jit/InstructionReordering.cpp +++ b/js/src/jit/InstructionReordering.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "jit/InstructionReordering.h" +#include "jit/MIRGraph.h" using namespace js; using namespace js::jit; diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index 83cc33c09b..0dc96e0725 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -24,6 +24,7 @@ #include "jit/EagerSimdUnbox.h" #include "jit/EdgeCaseAnalysis.h" #include "jit/EffectiveAddressAnalysis.h" +#include "jit/InstructionReordering.h" #include "jit/IonAnalysis.h" #include "jit/IonBuilder.h" #include "jit/IonOptimizationLevels.h" diff --git a/js/src/jit/IonCaches.cpp b/js/src/jit/IonCaches.cpp index e37f011d37..a9fdc8757c 100644 --- a/js/src/jit/IonCaches.cpp +++ b/js/src/jit/IonCaches.cpp @@ -1869,7 +1869,7 @@ GetPropertyIC::tryAttachArgumentsLength(JSContext* cx, HandleScript outerScript, if (!(outputType == MIRType_Value || outputType == MIRType_Int32)) return true; - if (hasArgumentsLengthStub(obj->is())) + if (hasArgumentsLengthStub(obj->is())) return true; *emitted = true; @@ -1889,10 +1889,7 @@ GetPropertyIC::tryAttachArgumentsLength(JSContext* cx, HandleScript outerScript, } MOZ_ASSERT(object() != tmpReg); - const Class* clasp = obj->is() ? &StrictArgumentsObject::class_ - : &NormalArgumentsObject::class_; - - masm.branchTestObjClass(Assembler::NotEqual, object(), tmpReg, clasp, &failures); + masm.branchTestObjClass(Assembler::NotEqual, object(), tmpReg, obj->getClass(), &failures); // Get initial ArgsObj length value, test if length has been overridden. masm.unboxInt32(Address(object(), ArgumentsObject::getInitialLengthSlotOffset()), tmpReg); @@ -1912,16 +1909,16 @@ GetPropertyIC::tryAttachArgumentsLength(JSContext* cx, HandleScript outerScript, masm.bind(&failures); attacher.jumpNextStub(masm); - if (obj->is()) { - MOZ_ASSERT(!hasStrictArgumentsLengthStub_); - hasStrictArgumentsLengthStub_ = true; - return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj length (strict)", + if (obj->is()) { + MOZ_ASSERT(!hasUnmappedArgumentsLengthStub_); + hasUnmappedArgumentsLengthStub_ = true; + return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj length (unmapped)", JS::TrackedOutcome::ICGetPropStub_ArgumentsLength); } - MOZ_ASSERT(!hasNormalArgumentsLengthStub_); - hasNormalArgumentsLengthStub_ = true; - return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj length (normal)", + MOZ_ASSERT(!hasMappedArgumentsLengthStub_); + hasMappedArgumentsLengthStub_ = true; + return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj length (mapped)", JS::TrackedOutcome::ICGetPropStub_ArgumentsLength); } @@ -2039,8 +2036,8 @@ GetPropertyIC::reset(ReprotectCode reprotect) IonCache::reset(reprotect); hasTypedArrayLengthStub_ = false; hasSharedTypedArrayLengthStub_ = false; - hasStrictArgumentsLengthStub_ = false; - hasNormalArgumentsLengthStub_ = false; + hasMappedArgumentsLengthStub_ = false; + hasUnmappedArgumentsLengthStub_ = false; hasGenericProxyStub_ = false; } @@ -3953,10 +3950,7 @@ GetElementIC::attachArgumentsElement(JSContext* cx, HandleScript outerScript, Io Register tmpReg = output().scratchReg().gpr(); MOZ_ASSERT(tmpReg != InvalidReg); - const Class* clasp = obj->is() ? &StrictArgumentsObject::class_ - : &NormalArgumentsObject::class_; - - masm.branchTestObjClass(Assembler::NotEqual, object(), tmpReg, clasp, &failures); + masm.branchTestObjClass(Assembler::NotEqual, object(), tmpReg, obj->getClass(), &failures); // Get initial ArgsObj length value, test if length has been overridden. masm.unboxInt32(Address(object(), ArgumentsObject::getInitialLengthSlotOffset()), tmpReg); @@ -4038,18 +4032,17 @@ GetElementIC::attachArgumentsElement(JSContext* cx, HandleScript outerScript, Io masm.bind(&failures); attacher.jumpNextStub(masm); - - if (obj->is()) { - MOZ_ASSERT(!hasStrictArgumentsStub_); - hasStrictArgumentsStub_ = true; - return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj element (strict)", - JS::TrackedOutcome::ICGetElemStub_ArgsElementStrict); + if (obj->is()) { + MOZ_ASSERT(!hasUnmappedArgumentsStub_); + hasUnmappedArgumentsStub_ = true; + return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj element (unmapped)", + JS::TrackedOutcome::ICGetElemStub_ArgsElementUnmapped); } - MOZ_ASSERT(!hasNormalArgumentsStub_); - hasNormalArgumentsStub_ = true; - return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj element (normal)", - JS::TrackedOutcome::ICGetElemStub_ArgsElement); + MOZ_ASSERT(!hasMappedArgumentsStub_); + hasMappedArgumentsStub_ = true; + return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj element (mapped)", + JS::TrackedOutcome::ICGetElemStub_ArgsElementMapped); } bool @@ -4080,7 +4073,7 @@ GetElementIC::update(JSContext* cx, HandleScript outerScript, size_t cacheIndex, bool attachedStub = false; if (cache.canAttachStub()) { if (IsOptimizableArgumentsObjectForGetElem(obj, idval) && - !cache.hasArgumentsStub(obj->is()) && + !cache.hasArgumentsStub(obj->is()) && (cache.index().hasValue() || cache.index().type() == MIRType_Int32) && (cache.output().hasValue() || !cache.output().typedReg().isFloat())) @@ -4138,8 +4131,8 @@ GetElementIC::reset(ReprotectCode reprotect) { IonCache::reset(reprotect); hasDenseStub_ = false; - hasStrictArgumentsStub_ = false; - hasNormalArgumentsStub_ = false; + hasMappedArgumentsStub_ = false; + hasUnmappedArgumentsStub_ = false; } static bool diff --git a/js/src/jit/IonCaches.h b/js/src/jit/IonCaches.h index 20da49dbf0..86f17d182f 100644 --- a/js/src/jit/IonCaches.h +++ b/js/src/jit/IonCaches.h @@ -392,8 +392,8 @@ class GetPropertyIC : public IonCache bool monitoredResult_ : 1; bool hasTypedArrayLengthStub_ : 1; bool hasSharedTypedArrayLengthStub_ : 1; - bool hasStrictArgumentsLengthStub_ : 1; - bool hasNormalArgumentsLengthStub_ : 1; + bool hasMappedArgumentsLengthStub_ : 1; + bool hasUnmappedArgumentsLengthStub_ : 1; bool hasGenericProxyStub_ : 1; public: @@ -410,8 +410,8 @@ class GetPropertyIC : public IonCache monitoredResult_(monitoredResult), hasTypedArrayLengthStub_(false), hasSharedTypedArrayLengthStub_(false), - hasStrictArgumentsLengthStub_(false), - hasNormalArgumentsLengthStub_(false), + hasMappedArgumentsLengthStub_(false), + hasUnmappedArgumentsLengthStub_(false), hasGenericProxyStub_(false) { } @@ -435,8 +435,8 @@ class GetPropertyIC : public IonCache bool hasAnyTypedArrayLengthStub(HandleObject obj) const { return obj->is() ? hasTypedArrayLengthStub_ : hasSharedTypedArrayLengthStub_; } - bool hasArgumentsLengthStub(bool strict) const { - return strict ? hasStrictArgumentsLengthStub_ : hasNormalArgumentsLengthStub_; + bool hasArgumentsLengthStub(bool mapped) const { + return mapped ? hasMappedArgumentsLengthStub_ : hasUnmappedArgumentsLengthStub_; } bool hasGenericProxyStub() const { return hasGenericProxyStub_; @@ -622,8 +622,8 @@ class GetElementIC : public IonCache bool monitoredResult_ : 1; bool allowDoubleResult_ : 1; bool hasDenseStub_ : 1; - bool hasStrictArgumentsStub_ : 1; - bool hasNormalArgumentsStub_ : 1; + bool hasMappedArgumentsStub_ : 1; + bool hasUnmappedArgumentsStub_ : 1; size_t failedUpdates_; @@ -639,8 +639,8 @@ class GetElementIC : public IonCache monitoredResult_(monitoredResult), allowDoubleResult_(allowDoubleResult), hasDenseStub_(false), - hasStrictArgumentsStub_(false), - hasNormalArgumentsStub_(false), + hasMappedArgumentsStub_(false), + hasUnmappedArgumentsStub_(false), failedUpdates_(0) { } @@ -667,8 +667,8 @@ class GetElementIC : public IonCache bool hasDenseStub() const { return hasDenseStub_; } - bool hasArgumentsStub(bool strict) const { - return strict ? hasStrictArgumentsStub_ : hasNormalArgumentsStub_; + bool hasArgumentsStub(bool mapped) const { + return mapped ? hasMappedArgumentsStub_ : hasUnmappedArgumentsStub_; } void setHasDenseStub() { MOZ_ASSERT(!hasDenseStub()); diff --git a/js/src/jit/MoveResolver.h b/js/src/jit/MoveResolver.h index b35a4eca2e..bb56b482dc 100644 --- a/js/src/jit/MoveResolver.h +++ b/js/src/jit/MoveResolver.h @@ -10,6 +10,7 @@ #include "jit/InlineList.h" #include "jit/JitAllocPolicy.h" #include "jit/Registers.h" +#include "jit/RegisterSets.h" namespace js { namespace jit { diff --git a/js/src/jit/x64/BaselineIC-x64.cpp b/js/src/jit/x64/BaselineIC-x64.cpp index 15ac76c961..f04052b0dd 100644 --- a/js/src/jit/x64/BaselineIC-x64.cpp +++ b/js/src/jit/x64/BaselineIC-x64.cpp @@ -6,6 +6,7 @@ #include "jit/BaselineIC.h" #include "jit/SharedICHelpers.h" +#include "jit/MacroAssembler-inl.h" using namespace js; using namespace js::jit; diff --git a/js/src/js.msg b/js/src/js.msg index 6adac6178c..fbc4b5d588 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -343,6 +343,7 @@ MSG_DEF(JSMSG_BAD_COLUMN_NUMBER, 0, JSEXN_RANGEERR, "column number out of MSG_DEF(JSMSG_COMPUTED_NAME_IN_PATTERN,0, JSEXN_SYNTAXERR, "computed property names aren't supported in this destructuring declaration") MSG_DEF(JSMSG_DEFAULT_IN_PATTERN, 0, JSEXN_SYNTAXERR, "destructuring defaults aren't supported in this destructuring declaration") MSG_DEF(JSMSG_BAD_NEWTARGET, 0, JSEXN_SYNTAXERR, "new.target only allowed in non-exotic functions") +MSG_DEF(JSMSG_ESCAPED_KEYWORD, 0, JSEXN_SYNTAXERR, "keywords must be written literally, without embedded escapes") // asm.js MSG_DEF(JSMSG_USE_ASM_TYPE_FAIL, 1, JSEXN_TYPEERR, "asm.js type error: {0}") diff --git a/js/src/jsapi-tests/testChromeBuffer.cpp b/js/src/jsapi-tests/testChromeBuffer.cpp index caf2f72d13..d4544a87db 100644 --- a/js/src/jsapi-tests/testChromeBuffer.cpp +++ b/js/src/jsapi-tests/testChromeBuffer.cpp @@ -22,7 +22,6 @@ static const JSClass global_class = { nullptr, nullptr, nullptr, - nullptr, JS_GlobalObjectTraceHook }; diff --git a/js/src/jsapi-tests/testFreshGlobalEvalRedefinition.cpp b/js/src/jsapi-tests/testFreshGlobalEvalRedefinition.cpp index 0646de6a84..ddf82c6175 100644 --- a/js/src/jsapi-tests/testFreshGlobalEvalRedefinition.cpp +++ b/js/src/jsapi-tests/testFreshGlobalEvalRedefinition.cpp @@ -24,8 +24,8 @@ BEGIN_TEST(testRedefineGlobalEval) static const JSClass cls = { "global", JSCLASS_GLOBAL_FLAGS, nullptr, nullptr, nullptr, nullptr, - GlobalEnumerate, GlobalResolve, nullptr, - nullptr, nullptr, nullptr, nullptr, nullptr, + GlobalEnumerate, GlobalResolve, nullptr, nullptr, + nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook }; diff --git a/js/src/jsapi-tests/testNewObject.cpp b/js/src/jsapi-tests/testNewObject.cpp index b6488ddf5f..4289313f59 100644 --- a/js/src/jsapi-tests/testNewObject.cpp +++ b/js/src/jsapi-tests/testNewObject.cpp @@ -103,7 +103,7 @@ BEGIN_TEST(testNewObject_1) 0, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, constructHook + nullptr, nullptr, constructHook }; JS::RootedObject ctor(cx, JS_NewObject(cx, &cls)); CHECK(ctor); diff --git a/js/src/jsapi-tests/testPersistentRooted.cpp b/js/src/jsapi-tests/testPersistentRooted.cpp index b53460c287..0856420c09 100644 --- a/js/src/jsapi-tests/testPersistentRooted.cpp +++ b/js/src/jsapi-tests/testPersistentRooted.cpp @@ -31,7 +31,6 @@ const JSClass BarkWhenTracedClass::class_ = { nullptr, nullptr, nullptr, - nullptr, finalize, nullptr, nullptr, diff --git a/js/src/jsapi-tests/testResolveRecursion.cpp b/js/src/jsapi-tests/testResolveRecursion.cpp index bd59faeed3..29ddab99cd 100644 --- a/js/src/jsapi-tests/testResolveRecursion.cpp +++ b/js/src/jsapi-tests/testResolveRecursion.cpp @@ -160,7 +160,6 @@ const JSClass* getGlobalClass() override { nullptr, // enumerate my_resolve, nullptr, // mayResolve - nullptr, // convert nullptr, // finalize nullptr, // call nullptr, // hasInstance diff --git a/js/src/jsapi-tests/testUbiNode.cpp b/js/src/jsapi-tests/testUbiNode.cpp index 50a7f0ab35..d99878bcac 100644 --- a/js/src/jsapi-tests/testUbiNode.cpp +++ b/js/src/jsapi-tests/testUbiNode.cpp @@ -2,9 +2,10 @@ * 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/TestingFunctions.h" #include "js/UbiNode.h" - #include "jsapi-tests/tests.h" +#include "vm/SavedFrame.h" using JS::RootedObject; using JS::RootedScript; @@ -104,3 +105,94 @@ BEGIN_TEST(test_ubiNodeJSObjectConstructorName) return true; } END_TEST(test_ubiNodeJSObjectConstructorName) + +template +static bool +checkString(const char* expected, F fillBufferFunction, G stringGetterFunction) +{ + auto expectedLength = strlen(expected); + char16_t buf[1024]; + if (fillBufferFunction(mozilla::RangedPtr(buf, 1024), 1024) != expectedLength || + !EqualChars(buf, expected, expectedLength)) + { + return false; + } + + auto string = stringGetterFunction(); + // Expecting a |JSAtom*| from a live |JS::ubi::StackFrame|. + if (!string.template is() || + !StringEqualsAscii(string.template as(), expected)) + { + return false; + } + + return true; +} + +BEGIN_TEST(test_ubiStackFrame) +{ + CHECK(js::DefineTestingFunctions(cx, global, false)); + + JS::RootedValue val(cx); + CHECK(evaluate("(function one() { \n" // 1 + " return (function two() { \n" // 2 + " return (function three() { \n" // 3 + " return saveStack(); \n" // 4 + " }()); \n" // 5 + " }()); \n" // 6 + "}()); \n", // 7 + "filename.js", + 1, + &val)); + + CHECK(val.isObject()); + JS::RootedObject obj(cx, &val.toObject()); + + CHECK(obj->is()); + JS::Rooted savedFrame(cx, &obj->as()); + + JS::ubi::StackFrame ubiFrame(savedFrame); + + // All frames should be from the "filename.js" source. + while (ubiFrame) { + CHECK(checkString("filename.js", + [&] (mozilla::RangedPtr ptr, size_t length) { + return ubiFrame.source(ptr, length); + }, + [&] { + return ubiFrame.source(); + })); + ubiFrame = ubiFrame.parent(); + } + + ubiFrame = savedFrame; + + auto bufferFunctionDisplayName = [&] (mozilla::RangedPtr ptr, size_t length) { + return ubiFrame.functionDisplayName(ptr, length); + }; + auto getFunctionDisplayName = [&] { + return ubiFrame.functionDisplayName(); + }; + + CHECK(checkString("three", bufferFunctionDisplayName, getFunctionDisplayName)); + CHECK(ubiFrame.line() == 4); + + ubiFrame = ubiFrame.parent(); + CHECK(checkString("two", bufferFunctionDisplayName, getFunctionDisplayName)); + CHECK(ubiFrame.line() == 3); + + ubiFrame = ubiFrame.parent(); + CHECK(checkString("one", bufferFunctionDisplayName, getFunctionDisplayName)); + CHECK(ubiFrame.line() == 2); + + ubiFrame = ubiFrame.parent(); + CHECK(ubiFrame.functionDisplayName().is()); + CHECK(ubiFrame.functionDisplayName().as() == nullptr); + CHECK(ubiFrame.line() == 1); + + ubiFrame = ubiFrame.parent(); + CHECK(!ubiFrame); + + return true; +} +END_TEST(test_ubiStackFrame) diff --git a/js/src/jsapi-tests/testWeakMap.cpp b/js/src/jsapi-tests/testWeakMap.cpp index 2b17681c01..f4acb092ee 100644 --- a/js/src/jsapi-tests/testWeakMap.cpp +++ b/js/src/jsapi-tests/testWeakMap.cpp @@ -158,7 +158,6 @@ JSObject* newKey() nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ nullptr, /* finalize */ nullptr, /* call */ nullptr, /* hasInstance */ @@ -215,7 +214,6 @@ JSObject* newDelegate() nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ nullptr, /* finalize */ nullptr, /* call */ nullptr, /* hasInstance */ diff --git a/js/src/jsapi-tests/tests.h b/js/src/jsapi-tests/tests.h index 1b5b35a27c..cbc0531f32 100644 --- a/js/src/jsapi-tests/tests.h +++ b/js/src/jsapi-tests/tests.h @@ -227,7 +227,7 @@ class JSAPITest "global", JSCLASS_GLOBAL_FLAGS, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook }; return &c; diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index f0cbd1ae28..a0f8c7e8d7 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -83,7 +83,6 @@ #include "vm/StringBuffer.h" #include "vm/Symbol.h" #include "vm/TypedArrayCommon.h" -#include "vm/WeakMapObject.h" #include "vm/WrapperObject.h" #include "vm/Xdr.h" @@ -3331,7 +3330,7 @@ JS_GetSecurityCallbacks(JSRuntime* rt) } JS_PUBLIC_API(void) -JS_SetTrustedPrincipals(JSRuntime* rt, const JSPrincipals* prin) +JS_SetTrustedPrincipals(JSRuntime* rt, JSPrincipals* prin) { rt->setTrustedPrincipals(prin); } diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index 9f95847c44..b0900f588d 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -3307,7 +3307,6 @@ const Class ArrayObject::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ nullptr, /* finalize */ nullptr, /* call */ nullptr, /* hasInstance */ diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index 51a1b54f00..34e328e2b4 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -78,8 +78,8 @@ JSCompartment::JSCompartment(Zone* zone, const JS::CompartmentOptions& options = scheduledForDestruction(false), maybeAlive(true), jitCompartment_(nullptr), - normalArgumentsTemplate_(nullptr), - strictArgumentsTemplate_(nullptr) + mappedArgumentsTemplate_(nullptr), + unmappedArgumentsTemplate_(nullptr) { runtime_->numCompartments++; MOZ_ASSERT_IF(options.mergeable(), options.invisibleToDebugger()); @@ -706,11 +706,11 @@ JSCompartment::sweepCrossCompartmentWrappers() void JSCompartment::sweepTemplateObjects() { - if (normalArgumentsTemplate_ && IsAboutToBeFinalized(&normalArgumentsTemplate_)) - normalArgumentsTemplate_.set(nullptr); + if (mappedArgumentsTemplate_ && IsAboutToBeFinalized(&mappedArgumentsTemplate_)) + mappedArgumentsTemplate_.set(nullptr); - if (strictArgumentsTemplate_ && IsAboutToBeFinalized(&strictArgumentsTemplate_)) - strictArgumentsTemplate_.set(nullptr); + if (unmappedArgumentsTemplate_ && IsAboutToBeFinalized(&unmappedArgumentsTemplate_)) + unmappedArgumentsTemplate_.set(nullptr); } /* static */ void diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index c20153a220..5d8081767c 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -714,8 +714,8 @@ struct JSCompartment private: js::jit::JitCompartment* jitCompartment_; - js::ReadBarriered normalArgumentsTemplate_; - js::ReadBarriered strictArgumentsTemplate_; + js::ReadBarriered mappedArgumentsTemplate_; + js::ReadBarriered unmappedArgumentsTemplate_; public: bool ensureJitCompartmentExists(JSContext* cx); @@ -736,7 +736,7 @@ struct JSCompartment DeprecatedLanguageExtensionCount }; - js::ArgumentsObject* getOrCreateArgumentsTemplateObject(JSContext* cx, bool strict); + js::ArgumentsObject* getOrCreateArgumentsTemplateObject(JSContext* cx, bool mapped); }; inline bool diff --git a/js/src/jsdate.cpp b/js/src/jsdate.cpp index 8c3cde8127..9dfebfd362 100644 --- a/js/src/jsdate.cpp +++ b/js/src/jsdate.cpp @@ -3181,7 +3181,6 @@ const Class DateObject::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ nullptr, /* finalize */ nullptr, /* call */ nullptr, /* hasInstance */ @@ -3208,7 +3207,6 @@ const Class DateObject::protoClass_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ nullptr, /* finalize */ nullptr, /* call */ nullptr, /* hasInstance */ diff --git a/js/src/jsexn.cpp b/js/src/jsexn.cpp index f518f438f9..11e50e287f 100644 --- a/js/src/jsexn.cpp +++ b/js/src/jsexn.cpp @@ -77,7 +77,6 @@ static const JSFunctionSpec exception_methods[] = { nullptr, /* enumerate */ \ nullptr, /* resolve */ \ nullptr, /* mayResolve */ \ - nullptr, /* convert */ \ exn_finalize, \ nullptr, /* call */ \ nullptr, /* hasInstance */ \ @@ -108,7 +107,6 @@ ErrorObject::classes[JSEXN_LIMIT] = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ exn_finalize, nullptr, /* call */ nullptr, /* hasInstance */ diff --git a/js/src/jsfriendapi.cpp b/js/src/jsfriendapi.cpp index d4ec1e6658..ba665694de 100644 --- a/js/src/jsfriendapi.cpp +++ b/js/src/jsfriendapi.cpp @@ -24,7 +24,6 @@ #include "proxy/DeadObjectProxy.h" #include "vm/ArgumentsObject.h" #include "vm/Time.h" -#include "vm/WeakMapObject.h" #include "vm/WrapperObject.h" #include "jsobjinlines.h" diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index def7cf1dd8..aa2b538ab0 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -300,7 +300,6 @@ namespace js { nullptr, /* enumerate */ \ nullptr, /* resolve */ \ nullptr, /* mayResolve */ \ - nullptr, /* convert */ \ js::proxy_Finalize, /* finalize */ \ nullptr, /* call */ \ js::proxy_HasInstance, /* hasInstance */ \ diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index ed905c3282..6f40f082bd 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -907,7 +907,6 @@ const Class JSFunction::class_ = { fun_enumerate, fun_resolve, fun_mayResolve, - nullptr, /* convert */ nullptr, /* finalize */ nullptr, /* call */ fun_hasInstance, diff --git a/js/src/jsiter.cpp b/js/src/jsiter.cpp index 6deae9bf62..0342053e89 100644 --- a/js/src/jsiter.cpp +++ b/js/src/jsiter.cpp @@ -1050,7 +1050,6 @@ const Class PropertyIteratorObject::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ finalize, nullptr, /* call */ nullptr, /* hasInstance */ @@ -1410,7 +1409,6 @@ const Class StopIterationObject::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ nullptr, /* finalize */ nullptr, /* call */ stopiter_hasInstance diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 5f7a3733db..d2054bb178 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -1525,7 +1525,6 @@ const Class ScriptSourceObject::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ finalize, nullptr, /* call */ nullptr, /* hasInstance */ diff --git a/js/src/jsweakmap.cpp b/js/src/jsweakmap.cpp index bcfaf450c9..9aed71a091 100644 --- a/js/src/jsweakmap.cpp +++ b/js/src/jsweakmap.cpp @@ -16,18 +16,12 @@ #include "js/GCAPI.h" #include "vm/GlobalObject.h" -#include "vm/WeakMapObject.h" #include "jsobjinlines.h" -#include "vm/Interpreter-inl.h" -#include "vm/NativeObject-inl.h" - using namespace js; using namespace js::gc; -using mozilla::UniquePtr; - WeakMapBase::WeakMapBase(JSObject* memOf, Zone* zone) : memberOf(memOf), zone(zone), @@ -213,473 +207,6 @@ ObjectValueMap::findZoneEdges() return true; } -MOZ_ALWAYS_INLINE bool -IsWeakMap(HandleValue v) -{ - return v.isObject() && v.toObject().is(); -} - -MOZ_ALWAYS_INLINE bool -WeakMap_has_impl(JSContext* cx, const CallArgs& args) -{ - MOZ_ASSERT(IsWeakMap(args.thisv())); - - if (!args.get(0).isObject()) { - args.rval().setBoolean(false); - return true; - } - - if (ObjectValueMap* map = args.thisv().toObject().as().getMap()) { - JSObject* key = &args[0].toObject(); - if (map->has(key)) { - args.rval().setBoolean(true); - return true; - } - } - - args.rval().setBoolean(false); - return true; -} - -bool -js::WeakMap_has(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - return CallNonGenericMethod(cx, args); -} - -MOZ_ALWAYS_INLINE bool -WeakMap_clear_impl(JSContext* cx, const CallArgs& args) -{ - MOZ_ASSERT(IsWeakMap(args.thisv())); - - // We can't js_delete the weakmap because the data gathered during GC is - // used by the Cycle Collector. - if (ObjectValueMap* map = args.thisv().toObject().as().getMap()) - map->clear(); - - args.rval().setUndefined(); - return true; -} - -bool -js::WeakMap_clear(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - return CallNonGenericMethod(cx, args); -} - -MOZ_ALWAYS_INLINE bool -WeakMap_get_impl(JSContext* cx, const CallArgs& args) -{ - MOZ_ASSERT(IsWeakMap(args.thisv())); - - if (!args.get(0).isObject()) { - args.rval().setUndefined(); - return true; - } - - if (ObjectValueMap* map = args.thisv().toObject().as().getMap()) { - JSObject* key = &args[0].toObject(); - if (ObjectValueMap::Ptr ptr = map->lookup(key)) { - args.rval().set(ptr->value()); - return true; - } - } - - args.rval().setUndefined(); - return true; -} - -bool -js::WeakMap_get(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - return CallNonGenericMethod(cx, args); -} - -MOZ_ALWAYS_INLINE bool -WeakMap_delete_impl(JSContext* cx, const CallArgs& args) -{ - MOZ_ASSERT(IsWeakMap(args.thisv())); - - if (!args.get(0).isObject()) { - args.rval().setBoolean(false); - return true; - } - - if (ObjectValueMap* map = args.thisv().toObject().as().getMap()) { - JSObject* key = &args[0].toObject(); - if (ObjectValueMap::Ptr ptr = map->lookup(key)) { - map->remove(ptr); - args.rval().setBoolean(true); - return true; - } - } - - args.rval().setBoolean(false); - return true; -} - -bool -js::WeakMap_delete(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - return CallNonGenericMethod(cx, args); -} - -static bool -TryPreserveReflector(JSContext* cx, HandleObject obj) -{ - if (obj->getClass()->ext.isWrappedNative || - (obj->getClass()->flags & JSCLASS_IS_DOMJSCLASS) || - (obj->is() && - obj->as().handler()->family() == GetDOMProxyHandlerFamily())) - { - MOZ_ASSERT(cx->runtime()->preserveWrapperCallback); - if (!cx->runtime()->preserveWrapperCallback(cx, obj)) { - JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_BAD_WEAKMAP_KEY); - return false; - } - } - return true; -} - -static inline void -WeakMapPostWriteBarrier(JSRuntime* rt, ObjectValueMap* weakMap, JSObject* key) -{ - if (key && IsInsideNursery(key)) - rt->gc.storeBuffer.putGeneric(gc::HashKeyRef(weakMap, key)); -} - -static MOZ_ALWAYS_INLINE bool -SetWeakMapEntryInternal(JSContext* cx, Handle mapObj, - HandleObject key, HandleValue value) -{ - ObjectValueMap* map = mapObj->getMap(); - if (!map) { - AutoInitGCManagedObject newMap( - cx->make_unique(cx, mapObj.get())); - if (!newMap) - return false; - if (!newMap->init()) { - JS_ReportOutOfMemory(cx); - return false; - } - map = newMap.release(); - mapObj->setPrivate(map); - } - - // Preserve wrapped native keys to prevent wrapper optimization. - if (!TryPreserveReflector(cx, key)) - return false; - - if (JSWeakmapKeyDelegateOp op = key->getClass()->ext.weakmapKeyDelegateOp) { - RootedObject delegate(cx, op(key)); - if (delegate && !TryPreserveReflector(cx, delegate)) - return false; - } - - MOZ_ASSERT(key->compartment() == mapObj->compartment()); - MOZ_ASSERT_IF(value.isObject(), value.toObject().compartment() == mapObj->compartment()); - if (!map->put(key, value)) { - JS_ReportOutOfMemory(cx); - return false; - } - WeakMapPostWriteBarrier(cx->runtime(), map, key.get()); - return true; -} - -MOZ_ALWAYS_INLINE bool -WeakMap_set_impl(JSContext* cx, const CallArgs& args) -{ - MOZ_ASSERT(IsWeakMap(args.thisv())); - - if (!args.get(0).isObject()) { - UniquePtr bytes = - DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, args.get(0), nullptr); - if (!bytes) - return false; - JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, bytes.get()); - return false; - } - - RootedObject key(cx, &args[0].toObject()); - Rooted thisObj(cx, &args.thisv().toObject()); - Rooted map(cx, &thisObj->as()); - - if (!SetWeakMapEntryInternal(cx, map, key, args.get(1))) - return false; - args.rval().set(args.thisv()); - return true; -} - -bool -js::WeakMap_set(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - return CallNonGenericMethod(cx, args); -} - -JS_FRIEND_API(bool) -JS_NondeterministicGetWeakMapKeys(JSContext* cx, HandleObject objArg, MutableHandleObject ret) -{ - RootedObject obj(cx, objArg); - obj = UncheckedUnwrap(obj); - if (!obj || !obj->is()) { - ret.set(nullptr); - return true; - } - RootedObject arr(cx, NewDenseEmptyArray(cx)); - if (!arr) - return false; - ObjectValueMap* map = obj->as().getMap(); - if (map) { - // Prevent GC from mutating the weakmap while iterating. - AutoSuppressGC suppress(cx); - for (ObjectValueMap::Base::Range r = map->all(); !r.empty(); r.popFront()) { - JS::ExposeObjectToActiveJS(r.front().key()); - RootedObject key(cx, r.front().key()); - if (!cx->compartment()->wrap(cx, &key)) - return false; - if (!NewbornArrayPush(cx, arr, ObjectValue(*key))) - return false; - } - } - ret.set(arr); - return true; -} - -static void -WeakMap_mark(JSTracer* trc, JSObject* obj) -{ - if (ObjectValueMap* map = obj->as().getMap()) - map->trace(trc); -} - -static void -WeakMap_finalize(FreeOp* fop, JSObject* obj) -{ - if (ObjectValueMap* map = obj->as().getMap()) { -#ifdef DEBUG - map->~ObjectValueMap(); - memset(static_cast(map), 0xdc, sizeof(*map)); - fop->free_(map); -#else - fop->delete_(map); -#endif - } -} - -JS_PUBLIC_API(JSObject*) -JS::NewWeakMapObject(JSContext* cx) -{ - return NewBuiltinClassInstance(cx, &WeakMapObject::class_); -} - -JS_PUBLIC_API(bool) -JS::IsWeakMapObject(JSObject* obj) -{ - return obj->is(); -} - -JS_PUBLIC_API(bool) -JS::GetWeakMapEntry(JSContext* cx, HandleObject mapObj, HandleObject key, - MutableHandleValue rval) -{ - CHECK_REQUEST(cx); - assertSameCompartment(cx, key); - rval.setUndefined(); - ObjectValueMap* map = mapObj->as().getMap(); - if (!map) - return true; - if (ObjectValueMap::Ptr ptr = map->lookup(key)) { - // Read barrier to prevent an incorrectly gray value from escaping the - // weak map. See the comment before UnmarkGrayChildren in gc/Marking.cpp - ExposeValueToActiveJS(ptr->value().get()); - rval.set(ptr->value()); - } - return true; -} - -JS_PUBLIC_API(bool) -JS::SetWeakMapEntry(JSContext* cx, HandleObject mapObj, HandleObject key, - HandleValue val) -{ - CHECK_REQUEST(cx); - assertSameCompartment(cx, key, val); - Rooted rootedMap(cx, &mapObj->as()); - return SetWeakMapEntryInternal(cx, rootedMap, key, val); -} - -static bool -WeakMap_construct(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - - // ES6 draft rev 31 (15 Jan 2015) 23.3.1.1 step 1. - if (!ThrowIfNotConstructing(cx, args, "WeakMap")) - return false; - - RootedObject obj(cx, NewBuiltinClassInstance(cx, &WeakMapObject::class_)); - if (!obj) - return false; - - // Steps 5-6, 11. - if (!args.get(0).isNullOrUndefined()) { - // Steps 7a-b. - RootedValue adderVal(cx); - if (!GetProperty(cx, obj, obj, cx->names().set, &adderVal)) - return false; - - // Step 7c. - if (!IsCallable(adderVal)) - return ReportIsNotFunction(cx, adderVal); - - bool isOriginalAdder = IsNativeFunction(adderVal, WeakMap_set); - RootedValue mapVal(cx, ObjectValue(*obj)); - FastInvokeGuard fig(cx, adderVal); - InvokeArgs& args2 = fig.args(); - - // Steps 7d-e. - JS::ForOfIterator iter(cx); - if (!iter.init(args[0])) - return false; - - RootedValue pairVal(cx); - RootedObject pairObject(cx); - RootedValue keyVal(cx); - RootedObject keyObject(cx); - RootedValue val(cx); - while (true) { - // Steps 12a-e. - bool done; - if (!iter.next(&pairVal, &done)) - return false; - if (done) - break; - - // Step 12f. - if (!pairVal.isObject()) { - JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, - JSMSG_INVALID_MAP_ITERABLE, "WeakMap"); - return false; - } - - pairObject = &pairVal.toObject(); - if (!pairObject) - return false; - - // Steps 12g-h. - if (!GetElement(cx, pairObject, pairObject, 0, &keyVal)) - return false; - - // Steps 12i-j. - if (!GetElement(cx, pairObject, pairObject, 1, &val)) - return false; - - // Steps 12k-l. - if (isOriginalAdder) { - if (keyVal.isPrimitive()) { - UniquePtr bytes = - DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, keyVal, nullptr); - if (!bytes) - return false; - JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, bytes.get()); - return false; - } - - keyObject = &keyVal.toObject(); - if (!SetWeakMapEntry(cx, obj, keyObject, val)) - return false; - } else { - if (!args2.init(2)) - return false; - - args2.setCallee(adderVal); - args2.setThis(mapVal); - args2[0].set(keyVal); - args2[1].set(val); - - if (!fig.invoke(cx)) - return false; - } - } - } - - args.rval().setObject(*obj); - return true; -} - -const Class WeakMapObject::class_ = { - "WeakMap", - JSCLASS_HAS_PRIVATE | - JSCLASS_HAS_CACHED_PROTO(JSProto_WeakMap), - nullptr, /* addProperty */ - nullptr, /* delProperty */ - nullptr, /* getProperty */ - nullptr, /* setProperty */ - nullptr, /* enumerate */ - nullptr, /* resolve */ - nullptr, /* mayResolve */ - nullptr, /* convert */ - WeakMap_finalize, - nullptr, /* call */ - nullptr, /* hasInstance */ - nullptr, /* construct */ - WeakMap_mark -}; - -static const JSFunctionSpec weak_map_methods[] = { - JS_FN("has", WeakMap_has, 1, 0), - JS_FN("get", WeakMap_get, 1, 0), - JS_FN("delete", WeakMap_delete, 1, 0), - JS_FN("set", WeakMap_set, 2, 0), - JS_FN("clear", WeakMap_clear, 0, 0), - JS_FS_END -}; - -static JSObject* -InitWeakMapClass(JSContext* cx, HandleObject obj, bool defineMembers) -{ - MOZ_ASSERT(obj->isNative()); - - Rooted global(cx, &obj->as()); - - RootedPlainObject proto(cx, NewBuiltinClassInstance(cx)); - if (!proto) - return nullptr; - - RootedFunction ctor(cx, global->createConstructor(cx, WeakMap_construct, - cx->names().WeakMap, 0)); - if (!ctor) - return nullptr; - - if (!LinkConstructorAndPrototype(cx, ctor, proto)) - return nullptr; - - if (defineMembers) { - if (!DefinePropertiesAndFunctions(cx, proto, nullptr, weak_map_methods)) - return nullptr; - } - - if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_WeakMap, ctor, proto)) - return nullptr; - return proto; -} - -JSObject* -js::InitWeakMapClass(JSContext* cx, HandleObject obj) -{ - return InitWeakMapClass(cx, obj, true); -} - -JSObject* -js::InitBareWeakMapCtor(JSContext* cx, HandleObject obj) -{ - return InitWeakMapClass(cx, obj, false); -} - ObjectWeakMap::ObjectWeakMap(JSContext* cx) : map(cx, nullptr) {} diff --git a/js/src/jsweakmap.h b/js/src/jsweakmap.h index aff5b262c7..fa72ddbaf6 100644 --- a/js/src/jsweakmap.h +++ b/js/src/jsweakmap.h @@ -397,6 +397,44 @@ WeakMap_clear(JSContext* cx, unsigned argc, Value* vp); extern JSObject* InitWeakMapClass(JSContext* cx, HandleObject obj); + +class ObjectValueMap : public WeakMap +{ + public: + ObjectValueMap(JSContext* cx, JSObject* obj) + : WeakMap(cx, obj) {} + + virtual bool findZoneEdges(); +}; + + +// Generic weak map for mapping objects to other objects. +class ObjectWeakMap +{ + private: + ObjectValueMap map; + typedef gc::HashKeyRef StoreBufferRef; + + public: + explicit ObjectWeakMap(JSContext* cx); + bool init(); + ~ObjectWeakMap(); + + JSObject* lookup(const JSObject* obj); + bool add(JSContext* cx, JSObject* obj, JSObject* target); + void clear(); + + void trace(JSTracer* trc); + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf); + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) { + return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf); + } + +#ifdef JSGC_HASH_TABLE_CHECKS + void checkAfterMovingGC(); +#endif +}; + } /* namespace js */ #endif /* jsweakmap_h */ diff --git a/js/src/moz.build b/js/src/moz.build index 44082d77f8..8a34941c02 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -156,6 +156,7 @@ UNIFIED_SOURCES += [ 'builtin/SymbolObject.cpp', 'builtin/TestingFunctions.cpp', 'builtin/TypedObject.cpp', + 'builtin/WeakMapObject.cpp', 'builtin/WeakSetObject.cpp', 'devtools/sharkctl.cpp', 'ds/LifoAlloc.cpp', diff --git a/js/src/perf/jsperf.cpp b/js/src/perf/jsperf.cpp index 8ea79051e0..5d5506477e 100644 --- a/js/src/perf/jsperf.cpp +++ b/js/src/perf/jsperf.cpp @@ -163,7 +163,7 @@ static void pm_finalize(JSFreeOp* fop, JSObject* obj); static const JSClass pm_class = { "PerfMeasurement", JSCLASS_HAS_PRIVATE, - nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, pm_finalize }; diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 57c980a43a..4abf85184f 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -2395,7 +2395,7 @@ static const JSClass sandbox_class = { JSCLASS_GLOBAL_FLAGS, nullptr, nullptr, nullptr, nullptr, sandbox_enumerate, sandbox_resolve, - nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook }; @@ -5192,7 +5192,7 @@ static const JSClass global_class = { "global", JSCLASS_GLOBAL_FLAGS, nullptr, nullptr, nullptr, nullptr, global_enumerate, global_resolve, global_mayResolve, - nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook }; diff --git a/js/src/tests/ecma_6/Syntax/browser.js b/js/src/tests/ecma_6/Syntax/browser.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/js/src/tests/ecma_6/Syntax/keyword-unescaped-requirement.js b/js/src/tests/ecma_6/Syntax/keyword-unescaped-requirement.js new file mode 100644 index 0000000000..84a569e9d2 --- /dev/null +++ b/js/src/tests/ecma_6/Syntax/keyword-unescaped-requirement.js @@ -0,0 +1,73 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +//----------------------------------------------------------------------------- +var BUGNUMBER = 1204027; +var summary = + "Escape sequences aren't allowed in bolded grammar tokens (that is, in " + + "keywords, possibly contextual keywords)"; + +print(BUGNUMBER + ": " + summary); + +/************** + * BEGIN TEST * + **************/ + +function classSyntax(code) +{ + return classesEnabled() ? "(class { constructor() {} " + code + " });" : "@"; +} + +function memberVariants(code) +{ + return [classesEnabled() ? "(class { constructor() {} " + code + " });" : "@", + "({ " + code + " })"]; +} + +var badScripts = + [ + classSyntax("st\\u0061tic m() { return 0; }"), + classSyntax("st\\u0061tic get foo() { return 0; }"), + classSyntax("st\\u0061tic set foo(v) {}"), + classSyntax("st\\u0061tic get ['hi']() { return 0; }"), + classSyntax("st\\u0061tic set ['hi'](v) {}"), + classSyntax("st\\u0061tic get 'hi'() { return 0; }"), + classSyntax("st\\u0061tic set 'hi'(v) {}"), + classSyntax("st\\u0061tic get 42() { return 0; }"), + classSyntax("st\\u0061tic set 42(v) {}"), + ...memberVariants("\\u0067et foo() { return 0; }"), + ...memberVariants("\\u0073et foo() {}"), + ...memberVariants("g\\u0065t foo() { return 0; }"), + ...memberVariants("s\\u0065t foo() {}"), + ...memberVariants("g\\u0065t ['hi']() { return 0; }"), + ...memberVariants("s\\u0065t ['hi']() {}"), + ...memberVariants("g\\u0065t 'hi'() { return 0; }"), + ...memberVariants("s\\u0065t 'hi'() {}"), + ...memberVariants("g\\u0065t 42() { return 0; }"), + ...memberVariants("s\\u0065t 42() {}"), + "for (var foo o\\u0066 [1]) ;", + "for (var foo \\u006ff [1]) ;", + "for (var foo i\\u006e [1]) ;", + "for (var foo \\u0069n [1]) ;", + "function f() { return n\\u0065w.target }", + "function f() { return \\u006eew.target }", + "function f() { return new.t\\u0061rget }", + "function f() { return new.\\u0074arget }", + "function f() { return n\\u0065w Array }", + "function f() { return \\u006eew Array }", + "\\u0064o { } while (0)", + "[for (x \\u006ff [1]) x]", + "[for (x o\\u0066 [1]) x]", + ]; + +for (var script of badScripts) + assertThrowsInstanceOf(() => Function(script), SyntaxError); + +/******************************************************************************/ + +if (typeof reportCompare === "function") + reportCompare(true, true); + +print("Tests complete"); diff --git a/js/src/tests/ecma_6/Syntax/shell.js b/js/src/tests/ecma_6/Syntax/shell.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/js/src/tests/ecma_6/extensions/keyword-unescaped-requirement-modules.js b/js/src/tests/ecma_6/extensions/keyword-unescaped-requirement-modules.js new file mode 100644 index 0000000000..6d10114c13 --- /dev/null +++ b/js/src/tests/ecma_6/extensions/keyword-unescaped-requirement-modules.js @@ -0,0 +1,97 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +//----------------------------------------------------------------------------- +var BUGNUMBER = 1204027; +var summary = + "Escape sequences aren't allowed in bolded grammar tokens (that is, in " + + "keywords, possibly contextual keywords)"; + +print(BUGNUMBER + ": " + summary); + +/************** + * BEGIN TEST * + **************/ + +var badModules = + [ + "\\u0069mport f from 'g'", + "i\\u006dport g from 'h'", + "import * \\u0061s foo", + "import {} fro\\u006d 'bar'", + "import { x \\u0061s y } from 'baz'", + + "\\u0065xport function f() {}", + "e\\u0078port function g() {}", + "export * fro\\u006d 'fnord'", + "export d\\u0065fault var x = 3;", + "export { q } fro\\u006d 'qSupplier';", + + ]; + +if (typeof parseModule === "function") +{ + for (var module of badModules) + { + assertThrowsInstanceOf(() => parseModule(module), SyntaxError, + "bad behavior for: " + module); + } +} + +if (typeof Reflect.parse === "function") +{ + var twoStatementAST = + Reflect.parse(`export { x } /* ASI should trigger here */ + fro\\u006D`, + { target: "module" }); + + var statements = twoStatementAST.body; + assertEq(statements.length, 2, + "should have two items in the module, not one ExportDeclaration"); + assertEq(statements[0].type, "ExportDeclaration"); + assertEq(statements[1].type, "ExpressionStatement"); + assertEq(statements[1].expression.name, "from"); + + var oneStatementAST = + Reflect.parse(`export { x } /* no ASI here */ + from 'foo'`, + { target: "module" }); + + assertEq(oneStatementAST.body.length, 1); + assertEq(oneStatementAST.body[0].type, "ExportDeclaration"); + + twoStatementAST = + Reflect.parse(`export { x } from "bar" + /bar/g`, + { target: "module" }); + + statements = twoStatementAST.body; + assertEq(statements.length, 2, + "should have two items in the module, not one ExportDeclaration"); + assertEq(statements[0].type, "ExportDeclaration"); + assertEq(statements[1].type, "ExpressionStatement"); + assertEq(statements[1].expression.type, "Literal"); + assertEq(statements[1].expression.value.toString(), "/bar/g"); + + twoStatementAST = + Reflect.parse(`export * from "bar" + /bar/g`, + { target: "module" }); + + statements = twoStatementAST.body; + assertEq(statements.length, 2, + "should have two items in the module, not one ExportDeclaration"); + assertEq(statements[0].type, "ExportDeclaration"); + assertEq(statements[1].type, "ExpressionStatement"); + assertEq(statements[1].expression.type, "Literal"); + assertEq(statements[1].expression.value.toString(), "/bar/g"); +} + +/******************************************************************************/ + +if (typeof reportCompare === "function") + reportCompare(true, true); + +print("Tests complete"); diff --git a/js/src/tests/ecma_6/extensions/keyword-unescaped-requirement.js b/js/src/tests/ecma_6/extensions/keyword-unescaped-requirement.js new file mode 100644 index 0000000000..20d3555e3d --- /dev/null +++ b/js/src/tests/ecma_6/extensions/keyword-unescaped-requirement.js @@ -0,0 +1,43 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +//----------------------------------------------------------------------------- +var BUGNUMBER = 1204027; +var summary = + "Escape sequences aren't allowed in bolded grammar tokens (that is, in " + + "keywords, possibly contextual keywords)"; + +print(BUGNUMBER + ": " + summary); + +/************** + * BEGIN TEST * + **************/ + +var randomExtensions = + [ + "for \\u0065ach (var x in []);", + "for e\\u0061ch (var x in []);", + "[0 for \\u0065ach (var x in [])]", + "[0 for e\\u0061ch (var x in [])]", + "(0 for \\u0065ach (var x in []))", + "(0 for e\\u0061ch (var x in []))", + + // Soon to be not an extension, maybe... + "(for (x \\u006ff [1]) x)", + "(for (x o\\u0066 [1]) x)", + ]; + +for (var extension of randomExtensions) +{ + assertThrowsInstanceOf(() => Function(extension), SyntaxError, + "bad behavior for: " + extension); +} + +/******************************************************************************/ + +if (typeof reportCompare === "function") + reportCompare(true, true); + +print("Tests complete"); diff --git a/js/src/vm/ArgumentsObject.cpp b/js/src/vm/ArgumentsObject.cpp index 7d0181ac9a..af2c018f7d 100644 --- a/js/src/vm/ArgumentsObject.cpp +++ b/js/src/vm/ArgumentsObject.cpp @@ -158,9 +158,11 @@ struct CopyScriptFrameIterArgs }; ArgumentsObject* -ArgumentsObject::createTemplateObject(JSContext* cx, bool strict) +ArgumentsObject::createTemplateObject(JSContext* cx, bool mapped) { - const Class* clasp = strict ? &StrictArgumentsObject::class_ : &NormalArgumentsObject::class_; + const Class* clasp = mapped + ? &MappedArgumentsObject::class_ + : &UnmappedArgumentsObject::class_; RootedObject proto(cx, cx->global()->getOrCreateObjectPrototype(cx)); if (!proto) @@ -186,16 +188,16 @@ ArgumentsObject::createTemplateObject(JSContext* cx, bool strict) } ArgumentsObject* -JSCompartment::getOrCreateArgumentsTemplateObject(JSContext* cx, bool strict) +JSCompartment::getOrCreateArgumentsTemplateObject(JSContext* cx, bool mapped) { ReadBarriered& obj = - strict ? strictArgumentsTemplate_ : normalArgumentsTemplate_; + mapped ? mappedArgumentsTemplate_ : unmappedArgumentsTemplate_; ArgumentsObject* templateObj = obj; if (templateObj) return templateObj; - templateObj = ArgumentsObject::createTemplateObject(cx, strict); + templateObj = ArgumentsObject::createTemplateObject(cx, mapped); if (!templateObj) return nullptr; @@ -207,8 +209,8 @@ template /* static */ ArgumentsObject* ArgumentsObject::create(JSContext* cx, HandleFunction callee, unsigned numActuals, CopyArgs& copy) { - bool strict = !callee->nonLazyScript()->hasMappedArgsObj(); // TODO: rename "strict" everywhere? - ArgumentsObject* templateObj = cx->compartment()->getOrCreateArgumentsTemplateObject(cx, strict); + bool mapped = callee->nonLazyScript()->hasMappedArgsObj(); + ArgumentsObject* templateObj = cx->compartment()->getOrCreateArgumentsTemplateObject(cx, mapped); if (!templateObj) return nullptr; @@ -313,8 +315,9 @@ ArgumentsObject::createForIon(JSContext* cx, jit::JitFrameLayout* frame, HandleO return create(cx, callee, frame->numActualArgs(), copy); } -static bool -args_delProperty(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result) +/* static */ bool +ArgumentsObject::obj_delProperty(JSContext* cx, HandleObject obj, HandleId id, + ObjectOpResult& result) { ArgumentsObject& argsobj = obj->as(); if (JSID_IS_INT(id)) { @@ -324,15 +327,15 @@ args_delProperty(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& r } else if (JSID_IS_ATOM(id, cx->names().length)) { argsobj.markLengthOverridden(); } else if (JSID_IS_ATOM(id, cx->names().callee)) { - argsobj.as().clearCallee(); + argsobj.as().clearCallee(); } return result.succeed(); } static bool -ArgGetter(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp) +MappedArgGetter(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp) { - NormalArgumentsObject& argsobj = obj->as(); + MappedArgumentsObject& argsobj = obj->as(); if (JSID_IS_INT(id)) { /* * arg can exceed the number of arguments if a script changed the @@ -353,12 +356,12 @@ ArgGetter(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp) } static bool -ArgSetter(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp, - ObjectOpResult& result) +MappedArgSetter(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp, + ObjectOpResult& result) { - if (!obj->is()) + if (!obj->is()) return result.succeed(); - Handle argsobj = obj.as(); + Handle argsobj = obj.as(); Rooted desc(cx); if (!GetOwnPropertyDescriptor(cx, argsobj, id, &desc)) @@ -384,20 +387,21 @@ ArgSetter(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp, /* * For simplicity we use delete/define to replace the property with a - * simple data property. Note that we rely on args_delProperty to clear the - * corresponding reserved slot so the GC can collect its value. Note also - * that we must define the property instead of setting it in case the user - * has changed the prototype to an object that has a setter for this id. + * simple data property. Note that we rely on ArgumentsObject::obj_delProperty + * to clear the corresponding reserved slot so the GC can collect its value. + * Note also that we must define the property instead of setting it in case + * the user has changed the prototype to an object that has a setter for + * this id. */ ObjectOpResult ignored; return NativeDeleteProperty(cx, argsobj, id, ignored) && NativeDefineProperty(cx, argsobj, id, vp, nullptr, nullptr, attrs, result); } -static bool -args_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) +/* static */ bool +MappedArgumentsObject::obj_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) { - Rooted argsobj(cx, &obj->as()); + Rooted argsobj(cx, &obj->as()); unsigned attrs = JSPROP_SHARED | JSPROP_SHADOWABLE | JSPROP_RESOLVING; if (JSID_IS_INT(id)) { @@ -417,17 +421,20 @@ args_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) return true; } - if (!NativeDefineProperty(cx, argsobj, id, UndefinedHandleValue, ArgGetter, ArgSetter, attrs)) + if (!NativeDefineProperty(cx, argsobj, id, UndefinedHandleValue, + MappedArgGetter, MappedArgSetter, attrs)) + { return false; + } *resolvedp = true; return true; } -static bool -args_enumerate(JSContext* cx, HandleObject obj) +/* static */ bool +MappedArgumentsObject::obj_enumerate(JSContext* cx, HandleObject obj) { - Rooted argsobj(cx, &obj->as()); + Rooted argsobj(cx, &obj->as()); RootedId id(cx); bool found; @@ -451,9 +458,9 @@ args_enumerate(JSContext* cx, HandleObject obj) } static bool -StrictArgGetter(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp) +UnmappedArgGetter(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp) { - StrictArgumentsObject& argsobj = obj->as(); + UnmappedArgumentsObject& argsobj = obj->as(); if (JSID_IS_INT(id)) { /* @@ -472,12 +479,12 @@ StrictArgGetter(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue } static bool -StrictArgSetter(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp, - ObjectOpResult& result) +UnmappedArgSetter(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp, + ObjectOpResult& result) { - if (!obj->is()) + if (!obj->is()) return result.succeed(); - Handle argsobj = obj.as(); + Handle argsobj = obj.as(); Rooted desc(cx); if (!GetOwnPropertyDescriptor(cx, argsobj, id, &desc)) @@ -499,22 +506,22 @@ StrictArgSetter(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue /* * For simplicity we use delete/define to replace the property with a - * simple data property. Note that we rely on args_delProperty to clear the - * corresponding reserved slot so the GC can collect its value. + * simple data property. Note that we rely on ArgumentsObject::obj_delProperty + * to clear the corresponding reserved slot so the GC can collect its value. */ ObjectOpResult ignored; return NativeDeleteProperty(cx, argsobj, id, ignored) && NativeDefineProperty(cx, argsobj, id, vp, nullptr, nullptr, attrs, result); } -static bool -strictargs_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) +/* static */ bool +UnmappedArgumentsObject::obj_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) { - Rooted argsobj(cx, &obj->as()); + Rooted argsobj(cx, &obj->as()); unsigned attrs = JSPROP_SHARED | JSPROP_SHADOWABLE; - GetterOp getter = StrictArgGetter; - SetterOp setter = StrictArgSetter; + GetterOp getter = UnmappedArgGetter; + SetterOp setter = UnmappedArgSetter; if (JSID_IS_INT(id)) { uint32_t arg = uint32_t(JSID_TO_INT(id)); @@ -542,10 +549,10 @@ strictargs_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp return true; } -static bool -strictargs_enumerate(JSContext* cx, HandleObject obj) +/* static */ bool +UnmappedArgumentsObject::obj_enumerate(JSContext* cx, HandleObject obj) { - Rooted argsobj(cx, &obj->as()); + Rooted argsobj(cx, &obj->as()); RootedId id(cx); bool found; @@ -625,21 +632,20 @@ ArgumentsObject::objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject * stack frame with their corresponding property values in the frame's * arguments object. */ -const Class NormalArgumentsObject::class_ = { +const Class MappedArgumentsObject::class_ = { "Arguments", JSCLASS_DELAY_METADATA_CALLBACK | - JSCLASS_HAS_RESERVED_SLOTS(NormalArgumentsObject::RESERVED_SLOTS) | + JSCLASS_HAS_RESERVED_SLOTS(MappedArgumentsObject::RESERVED_SLOTS) | JSCLASS_HAS_CACHED_PROTO(JSProto_Object) | JSCLASS_SKIP_NURSERY_FINALIZE | JSCLASS_BACKGROUND_FINALIZE, nullptr, /* addProperty */ - args_delProperty, + ArgumentsObject::obj_delProperty, nullptr, /* getProperty */ nullptr, /* setProperty */ - args_enumerate, - args_resolve, + MappedArgumentsObject::obj_enumerate, + MappedArgumentsObject::obj_resolve, nullptr, /* mayResolve */ - nullptr, /* convert */ ArgumentsObject::finalize, nullptr, /* call */ nullptr, /* hasInstance */ @@ -648,25 +654,23 @@ const Class NormalArgumentsObject::class_ = { }; /* - * Strict mode arguments is significantly less magical than non-strict mode - * arguments, so it is represented by a different class while sharing some - * functionality. + * Unmapped arguments is significantly less magical than mapped arguments, so + * it is represented by a different class while sharing some functionality. */ -const Class StrictArgumentsObject::class_ = { +const Class UnmappedArgumentsObject::class_ = { "Arguments", JSCLASS_DELAY_METADATA_CALLBACK | - JSCLASS_HAS_RESERVED_SLOTS(StrictArgumentsObject::RESERVED_SLOTS) | + JSCLASS_HAS_RESERVED_SLOTS(UnmappedArgumentsObject::RESERVED_SLOTS) | JSCLASS_HAS_CACHED_PROTO(JSProto_Object) | JSCLASS_SKIP_NURSERY_FINALIZE | JSCLASS_BACKGROUND_FINALIZE, nullptr, /* addProperty */ - args_delProperty, + ArgumentsObject::obj_delProperty, nullptr, /* getProperty */ nullptr, /* setProperty */ - strictargs_enumerate, - strictargs_resolve, + UnmappedArgumentsObject::obj_enumerate, + UnmappedArgumentsObject::obj_resolve, nullptr, /* mayResolve */ - nullptr, /* convert */ ArgumentsObject::finalize, nullptr, /* call */ nullptr, /* hasInstance */ diff --git a/js/src/vm/ArgumentsObject.h b/js/src/vm/ArgumentsObject.h index ae338c3789..5c945fbbd9 100644 --- a/js/src/vm/ArgumentsObject.h +++ b/js/src/vm/ArgumentsObject.h @@ -136,6 +136,9 @@ class ArgumentsObject : public NativeObject return reinterpret_cast(getFixedSlot(DATA_SLOT).toPrivate()); } + static bool obj_delProperty(JSContext* cx, HandleObject obj, HandleId id, + ObjectOpResult& result); + public: static const uint32_t RESERVED_SLOTS = 3; static const gc::AllocKind FINALIZE_KIND = gc::AllocKind::OBJECT4_BACKGROUND; @@ -154,7 +157,7 @@ class ArgumentsObject : public NativeObject static ArgumentsObject* createForIon(JSContext* cx, jit::JitFrameLayout* frame, HandleObject scopeChain); - static ArgumentsObject* createTemplateObject(JSContext* cx, bool strict); + static ArgumentsObject* createTemplateObject(JSContext* cx, bool mapped); /* * Return the initial length of the arguments. This may differ from the @@ -310,7 +313,7 @@ class ArgumentsObject : public NativeObject ArgumentsObject* obj, ArgumentsData* data); }; -class NormalArgumentsObject : public ArgumentsObject +class MappedArgumentsObject : public ArgumentsObject { public: static const Class class_; @@ -327,12 +330,20 @@ class NormalArgumentsObject : public ArgumentsObject void clearCallee() { data()->callee = MagicValue(JS_OVERWRITTEN_CALLEE); } + + private: + static bool obj_enumerate(JSContext* cx, HandleObject obj); + static bool obj_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp); }; -class StrictArgumentsObject : public ArgumentsObject +class UnmappedArgumentsObject : public ArgumentsObject { public: static const Class class_; + + private: + static bool obj_enumerate(JSContext* cx, HandleObject obj); + static bool obj_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp); }; } // namespace js @@ -341,7 +352,7 @@ template<> inline bool JSObject::is() const { - return is() || is(); + return is() || is(); } #endif /* vm_ArgumentsObject_h */ diff --git a/js/src/vm/ArrayBufferObject.cpp b/js/src/vm/ArrayBufferObject.cpp index c808603bac..ebca74f38a 100644 --- a/js/src/vm/ArrayBufferObject.cpp +++ b/js/src/vm/ArrayBufferObject.cpp @@ -111,7 +111,6 @@ const Class ArrayBufferObject::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ ArrayBufferObject::finalize, nullptr, /* call */ nullptr, /* hasInstance */ diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index 36b7d871c6..ec25fae328 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -2658,7 +2658,7 @@ const Class Debugger::jsclass = { "Debugger", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUG_COUNT), - nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, Debugger::finalize, nullptr, /* call */ nullptr, /* hasInstance */ @@ -4577,7 +4577,7 @@ const Class DebuggerScript_class = { "Script", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGSCRIPT_COUNT), - nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, /* call */ nullptr, /* hasInstance */ @@ -5667,7 +5667,7 @@ const Class DebuggerSource_class = { "Source", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGSOURCE_COUNT), - nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, /* call */ nullptr, /* hasInstance */ @@ -6017,7 +6017,7 @@ DebuggerFrame_finalize(FreeOp* fop, JSObject* obj) const Class DebuggerFrame_class = { "Frame", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGFRAME_COUNT), - nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, DebuggerFrame_finalize }; @@ -6793,7 +6793,7 @@ const Class DebuggerObject_class = { "Object", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGOBJECT_COUNT), - nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, /* call */ nullptr, /* hasInstance */ @@ -7739,7 +7739,7 @@ const Class DebuggerEnv_class = { "Environment", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGENV_COUNT), - nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, /* call */ nullptr, /* hasInstance */ diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp index a7c3bf2de9..e10cfaf787 100644 --- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -27,13 +27,13 @@ #include "builtin/SIMD.h" #include "builtin/SymbolObject.h" #include "builtin/TypedObject.h" +#include "builtin/WeakMapObject.h" #include "builtin/WeakSetObject.h" #include "vm/HelperThreads.h" #include "vm/PIC.h" #include "vm/RegExpStatics.h" #include "vm/RegExpStaticsObject.h" #include "vm/StopIterationObject.h" -#include "vm/WeakMapObject.h" #include "jscompartmentinlines.h" #include "jsobjinlines.h" @@ -529,7 +529,7 @@ GlobalDebuggees_finalize(FreeOp* fop, JSObject* obj) static const Class GlobalDebuggees_class = { "GlobalDebuggee", JSCLASS_HAS_PRIVATE, - nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, GlobalDebuggees_finalize }; diff --git a/js/src/vm/HelperThreads.cpp b/js/src/vm/HelperThreads.cpp index 6a14d22068..c37662a913 100644 --- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -193,7 +193,7 @@ static const JSClass parseTaskGlobalClass = { "internal-parse-task-global", JSCLASS_GLOBAL_FLAGS, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook }; diff --git a/js/src/vm/PIC.cpp b/js/src/vm/PIC.cpp index 863eb9c949..92572aab6f 100644 --- a/js/src/vm/PIC.cpp +++ b/js/src/vm/PIC.cpp @@ -296,7 +296,7 @@ ForOfPIC_traceObject(JSTracer* trc, JSObject* obj) const Class ForOfPIC::jsclass = { "ForOfPIC", JSCLASS_HAS_PRIVATE, - nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, ForOfPIC_finalize, nullptr, /* call */ nullptr, /* hasInstance */ diff --git a/js/src/vm/RegExpObject.cpp b/js/src/vm/RegExpObject.cpp index f131eafeed..057bac0d43 100644 --- a/js/src/vm/RegExpObject.cpp +++ b/js/src/vm/RegExpObject.cpp @@ -252,7 +252,6 @@ const Class RegExpObject::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ nullptr, /* finalize */ nullptr, /* call */ nullptr, /* hasInstance */ diff --git a/js/src/vm/RegExpStatics.cpp b/js/src/vm/RegExpStatics.cpp index cd49c44aa2..381d05be66 100644 --- a/js/src/vm/RegExpStatics.cpp +++ b/js/src/vm/RegExpStatics.cpp @@ -45,7 +45,6 @@ const Class RegExpStaticsObject::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ resc_finalize, nullptr, /* call */ nullptr, /* hasInstance */ diff --git a/js/src/vm/SavedFrame.h b/js/src/vm/SavedFrame.h index afb1fe11fc..dc49a9f207 100644 --- a/js/src/vm/SavedFrame.h +++ b/js/src/vm/SavedFrame.h @@ -7,6 +7,8 @@ #ifndef vm_SavedFrame_h #define vm_SavedFrame_h +#include "js/UbiNode.h" + namespace js { class SavedFrame : public NativeObject { @@ -197,6 +199,81 @@ SavedFrame::RootedIterator::operator++() range_->frame_ = range_->frame_->getParent(); } +// When we reconstruct a SavedFrame stack from a JS::ubi::StackFrame, we may not +// have access to the principals that the original stack was captured +// with. Instead, we use these two singleton principals based on whether +// JS::ubi::StackFrame::isSystem or not. These singletons should never be passed +// to the subsumes callback, and should be special cased with a shortcut before +// that. +struct ReconstructedSavedFramePrincipals : public JSPrincipals +{ + explicit ReconstructedSavedFramePrincipals() + : JSPrincipals() + { + MOZ_ASSERT(is(this)); + this->refcount = 1; + } + + static ReconstructedSavedFramePrincipals IsSystem; + static ReconstructedSavedFramePrincipals IsNotSystem; + + // Return true if the given JSPrincipals* points to one of the + // ReconstructedSavedFramePrincipals singletons, false otherwise. + static bool is(JSPrincipals* p) { return p == &IsSystem || p == &IsNotSystem;} + + // Get the appropriate ReconstructedSavedFramePrincipals singleton for the + // given JS::ubi::StackFrame that is being reconstructed as a SavedFrame + // stack. + static JSPrincipals* getSingleton(JS::ubi::StackFrame& f) { + return f.isSystem() ? &IsSystem : &IsNotSystem; + } +}; + } // namespace js +namespace JS { +namespace ubi { + +using js::SavedFrame; + +// A concrete JS::ubi::StackFrame that is backed by a live SavedFrame object. +template<> +class ConcreteStackFrame : public BaseStackFrame { + explicit ConcreteStackFrame(SavedFrame* ptr) : BaseStackFrame(ptr) { } + SavedFrame& get() const { return *static_cast(ptr); } + + public: + static void construct(void* storage, SavedFrame* ptr) { new (storage) ConcreteStackFrame(ptr); } + + StackFrame parent() const override { return get().getParent(); } + uint32_t line() const override { return get().getLine(); } + uint32_t column() const override { return get().getColumn(); } + + AtomOrTwoByteChars source() const override { + auto source = get().getSource(); + return AtomOrTwoByteChars(source); + } + + AtomOrTwoByteChars functionDisplayName() const override { + auto name = get().getFunctionDisplayName(); + return AtomOrTwoByteChars(name); + } + + void trace(JSTracer* trc) override { + JSObject* obj = &get(); + js::TraceManuallyBarrieredEdge(trc, &obj, "ConcreteStackFrame::ptr"); + ptr = obj; + } + + bool isSelfHosted() const override { return get().isSelfHosted(); } + + bool isSystem() const override; + + bool constructSavedFrameStack(JSContext* cx, + MutableHandleObject outSavedFrameStack) const override; +}; + +} // namespace ubi +} // namespace JS + #endif // vm_SavedFrame_h diff --git a/js/src/vm/SavedStacks.cpp b/js/src/vm/SavedStacks.cpp index 10417a4a85..a97ba9a529 100644 --- a/js/src/vm/SavedStacks.cpp +++ b/js/src/vm/SavedStacks.cpp @@ -24,6 +24,7 @@ #include "gc/Rooting.h" #include "js/Vector.h" #include "vm/Debugger.h" +#include "vm/SavedFrame.h" #include "vm/StringBuffer.h" #include "vm/Time.h" #include "vm/WrapperObject.h" @@ -136,8 +137,8 @@ LiveSavedFrameCache::find(JSContext* cx, FrameIter& frameIter, MutableHandleSave struct SavedFrame::Lookup { Lookup(JSAtom* source, uint32_t line, uint32_t column, JSAtom* functionDisplayName, JSAtom* asyncCause, SavedFrame* parent, - JSPrincipals* principals, Maybe framePtr, jsbytecode* pc, - Activation* activation) + JSPrincipals* principals, Maybe framePtr = Nothing(), + jsbytecode* pc = nullptr, Activation* activation = nullptr) : source(source), line(line), column(column), @@ -290,7 +291,6 @@ SavedFrame::finishSavedFrameInit(JSContext* cx, HandleObject ctor, HandleObject nullptr, // enumerate nullptr, // resolve nullptr, // mayResolve - nullptr, // convert SavedFrame::finalize, // finalize nullptr, // call nullptr, // hasInstance @@ -442,6 +442,28 @@ SavedFrame::construct(JSContext* cx, unsigned argc, Value* vp) return false; } +static bool +SavedFrameSubsumedByCaller(JSContext* cx, HandleSavedFrame frame) +{ + auto subsumes = cx->runtime()->securityCallbacks->subsumes; + if (!subsumes) + return true; + + auto currentCompartmentPrincipals = cx->compartment()->principals(); + MOZ_ASSERT(!ReconstructedSavedFramePrincipals::is(currentCompartmentPrincipals)); + + auto framePrincipals = frame->getPrincipals(); + + // Handle SavedFrames that have been reconstructed from stacks in a heap + // snapshot. + if (framePrincipals == &ReconstructedSavedFramePrincipals::IsSystem) + return cx->runningWithTrustedPrincipals(); + if (framePrincipals == &ReconstructedSavedFramePrincipals::IsNotSystem) + return true; + + return subsumes(currentCompartmentPrincipals, framePrincipals); +} + // Return the first SavedFrame in the chain that starts with |frame| whose // principals are subsumed by |principals|, according to |subsumes|. If there is // no such frame, return nullptr. |skippedAsync| is set to true if any of the @@ -452,14 +474,8 @@ GetFirstSubsumedFrame(JSContext* cx, HandleSavedFrame frame, bool& skippedAsync) { skippedAsync = false; - JSSubsumesOp subsumes = cx->runtime()->securityCallbacks->subsumes; - if (!subsumes) - return frame; - - JSPrincipals* principals = cx->compartment()->principals(); - RootedSavedFrame rootedFrame(cx, frame); - while (rootedFrame && !subsumes(principals, rootedFrame->getPrincipals())) { + while (rootedFrame && !SavedFrameSubsumedByCaller(cx, rootedFrame)) { if (rootedFrame->getAsyncCause()) skippedAsync = true; rootedFrame = rootedFrame->getParent(); @@ -733,12 +749,10 @@ BuildStackString(JSContext* cx, HandleObject stack, MutableHandleString stringp) stringp.set(cx->runtime()->emptyString); return true; } - DebugOnly subsumes = cx->runtime()->securityCallbacks->subsumes; - DebugOnly principals = cx->compartment()->principals(); js::RootedSavedFrame parent(cx); do { - MOZ_ASSERT_IF(subsumes, (*subsumes)(principals, frame->getPrincipals())); + MOZ_ASSERT(SavedFrameSubsumedByCaller(cx, frame)); if (!frame->isSelfHosted()) { RootedString asyncCause(cx, frame->getAsyncCause()); @@ -1372,4 +1386,110 @@ CompartmentChecker::check(SavedStacks* stacks) } #endif /* JS_CRASH_DIAGNOSTICS */ +/* static */ ReconstructedSavedFramePrincipals ReconstructedSavedFramePrincipals::IsSystem; +/* static */ ReconstructedSavedFramePrincipals ReconstructedSavedFramePrincipals::IsNotSystem; + } /* namespace js */ + +namespace JS { +namespace ubi { + +bool +ConcreteStackFrame::isSystem() const +{ + auto trustedPrincipals = get().runtimeFromAnyThread()->trustedPrincipals(); + return get().getPrincipals() == trustedPrincipals || + get().getPrincipals() == &js::ReconstructedSavedFramePrincipals::IsSystem; +} + +bool +ConcreteStackFrame::constructSavedFrameStack(JSContext* cx, + MutableHandleObject outSavedFrameStack) + const +{ + outSavedFrameStack.set(&get()); + if (!cx->compartment()->wrap(cx, outSavedFrameStack)) { + outSavedFrameStack.set(nullptr); + return false; + } + return true; +} + +// A `mozilla::Variant` matcher that converts the inner value of a +// `JS::ubi::AtomOrTwoByteChars` string to a `JSAtom*`. +struct MOZ_STACK_CLASS AtomizingMatcher +{ + using ReturnType = JSAtom*; + + JSContext* cx; + size_t length; + + explicit AtomizingMatcher(JSContext* cx, size_t length) + : cx(cx) + , length(length) + { } + + JSAtom* match(JSAtom* atom) { + MOZ_ASSERT(atom); + return atom; + } + + JSAtom* match(const char16_t* chars) { + MOZ_ASSERT(chars); + return AtomizeChars(cx, chars, length); + } +}; + +bool ConstructSavedFrameStackSlow(JSContext* cx, JS::ubi::StackFrame& frame, + MutableHandleObject outSavedFrameStack) +{ + SavedFrame::AutoLookupVector stackChain(cx); + Rooted ubiFrame(cx, frame); + + while (ubiFrame.get()) { + // Convert the source and functionDisplayName strings to atoms. + + js::RootedAtom source(cx); + AtomizingMatcher atomizer(cx, ubiFrame.get().sourceLength()); + source = ubiFrame.get().source().match(atomizer); + if (!source) + return false; + + js::RootedAtom functionDisplayName(cx); + auto nameLength = ubiFrame.get().functionDisplayNameLength(); + if (nameLength > 0) { + AtomizingMatcher atomizer(cx, nameLength); + functionDisplayName = ubiFrame.get().functionDisplayName().match(atomizer); + if (!functionDisplayName) + return false; + } + + auto principals = js::ReconstructedSavedFramePrincipals::getSingleton(ubiFrame.get()); + + if (!stackChain->emplaceBack(source, ubiFrame.get().line(), ubiFrame.get().column(), + functionDisplayName, /* asyncCause */ nullptr, + /* parent */ nullptr, principals)) + { + ReportOutOfMemory(cx); + return false; + } + + ubiFrame = ubiFrame.get().parent(); + } + + js::RootedSavedFrame parentFrame(cx); + for (size_t i = stackChain->length(); i != 0; i--) { + SavedFrame::HandleLookup lookup = stackChain[i-1]; + lookup->parent = parentFrame; + parentFrame = cx->compartment()->savedStacks().getOrCreateSavedFrame(cx, lookup); + if (!parentFrame) + return false; + } + + outSavedFrameStack.set(parentFrame); + return true; +} + + +} // namespace ubi +} // namespace JS diff --git a/js/src/vm/SavedStacks.h b/js/src/vm/SavedStacks.h index c0d45c6f85..f74a8c659e 100644 --- a/js/src/vm/SavedStacks.h +++ b/js/src/vm/SavedStacks.h @@ -147,6 +147,9 @@ namespace js { class SavedStacks { friend JSObject* SavedStacksMetadataCallback(JSContext* cx, JSObject* target); + friend bool JS::ubi::ConstructSavedFrameStackSlow(JSContext* cx, + JS::ubi::StackFrame& ubiFrame, + MutableHandleObject outSavedFrameStack); public: SavedStacks() diff --git a/js/src/vm/ScopeObject.cpp b/js/src/vm/ScopeObject.cpp index 1bbd8c6545..2a16997070 100644 --- a/js/src/vm/ScopeObject.cpp +++ b/js/src/vm/ScopeObject.cpp @@ -18,7 +18,6 @@ #include "vm/GlobalObject.h" #include "vm/ProxyObject.h" #include "vm/Shape.h" -#include "vm/WeakMapObject.h" #include "vm/Xdr.h" #include "jsatominlines.h" @@ -326,7 +325,6 @@ const Class ModuleEnvironmentObject::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ nullptr, /* finalize */ nullptr, /* call */ nullptr, /* hasInstance */ @@ -761,7 +759,6 @@ const Class DynamicWithObject::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ nullptr, /* finalize */ nullptr, /* call */ nullptr, /* hasInstance */ @@ -1224,7 +1221,6 @@ const Class UninitializedLexicalObject::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ nullptr, /* finalize */ nullptr, /* call */ nullptr, /* hasInstance */ diff --git a/js/src/vm/ScopeObject.h b/js/src/vm/ScopeObject.h index 6bf64fa547..cdc7ca04fa 100644 --- a/js/src/vm/ScopeObject.h +++ b/js/src/vm/ScopeObject.h @@ -14,7 +14,6 @@ #include "gc/Barrier.h" #include "vm/ArgumentsObject.h" #include "vm/ProxyObject.h" -#include "vm/WeakMapObject.h" namespace js { diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 27320591b2..995dad3d51 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -1701,7 +1701,7 @@ JSRuntime::createSelfHostingGlobal(JSContext* cx) "self-hosting-global", JSCLASS_GLOBAL_FLAGS, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook }; diff --git a/js/src/vm/SharedArrayObject.cpp b/js/src/vm/SharedArrayObject.cpp index 1f43dbce1a..2f42cad5a0 100644 --- a/js/src/vm/SharedArrayObject.cpp +++ b/js/src/vm/SharedArrayObject.cpp @@ -321,7 +321,6 @@ const Class SharedArrayBufferObject::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ SharedArrayBufferObject::Finalize, nullptr, /* call */ nullptr, /* hasInstance */ diff --git a/js/src/vm/SharedTypedArrayObject.cpp b/js/src/vm/SharedTypedArrayObject.cpp index 46684cd70b..8217acf225 100644 --- a/js/src/vm/SharedTypedArrayObject.cpp +++ b/js/src/vm/SharedTypedArrayObject.cpp @@ -716,7 +716,6 @@ IMPL_SHARED_TYPED_ARRAY_COMBINED_UNWRAPPERS(Float64, double, double) nullptr, /* enumerate */ \ nullptr, /* resolve */ \ nullptr, /* mayResolve */ \ - nullptr, /* convert */ \ nullptr, /* finalize */ \ nullptr, /* call */ \ nullptr, /* hasInstance */ \ @@ -738,7 +737,6 @@ IMPL_SHARED_TYPED_ARRAY_COMBINED_UNWRAPPERS(Float64, double, double) nullptr, /* enumerate */ \ nullptr, /* resolve */ \ nullptr, /* mayResolve */ \ - nullptr, /* convert */ \ nullptr, /* finalize */ \ nullptr, /* call */ \ nullptr, /* hasInstance */ \ diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index d6d8ab8ba7..0eba001f07 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -832,7 +832,6 @@ TypedArrayObject::sharedTypedArrayPrototypeClass = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ nullptr, /* finalize */ nullptr, /* call */ nullptr, /* hasInstance */ @@ -1804,7 +1803,6 @@ IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Float64, double, double) nullptr, /* enumerate */ \ nullptr, /* resolve */ \ nullptr, /* mayResolve */ \ - nullptr, /* convert */ \ nullptr, /* finalize */ \ nullptr, /* call */ \ nullptr, /* hasInstance */ \ @@ -1850,7 +1848,6 @@ const Class TypedArrayObject::classes[Scalar::MaxTypedArrayViewType] = { nullptr, /* enumerate */ \ nullptr, /* resolve */ \ nullptr, /* mayResolve */ \ - nullptr, /* convert */ \ nullptr, /* finalize */ \ nullptr, /* call */ \ nullptr, /* hasInstance */ \ @@ -1905,7 +1902,6 @@ const Class DataViewObject::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ nullptr, /* finalize */ nullptr, /* call */ nullptr, /* hasInstance */ diff --git a/js/src/vm/UbiNode.cpp b/js/src/vm/UbiNode.cpp index 3288e4ce18..0aa64a6015 100644 --- a/js/src/vm/UbiNode.cpp +++ b/js/src/vm/UbiNode.cpp @@ -11,6 +11,8 @@ #include "mozilla/Range.h" #include "mozilla/Scoped.h" +#include + #include "jscntxt.h" #include "jsobj.h" #include "jsscript.h" @@ -22,6 +24,7 @@ #include "js/TypeDecls.h" #include "js/Utility.h" #include "js/Vector.h" +#include "vm/Debugger.h" #include "vm/GlobalObject.h" #include "vm/ScopeObject.h" #include "vm/Shape.h" @@ -32,24 +35,118 @@ #include "vm/Debugger-inl.h" using mozilla::Some; +using mozilla::RangedPtr; using mozilla::UniquePtr; using JS::DispatchTraceKindTyped; using JS::HandleValue; using JS::Value; using JS::ZoneSet; +using JS::ubi::AtomOrTwoByteChars; using JS::ubi::Concrete; using JS::ubi::Edge; using JS::ubi::EdgeRange; using JS::ubi::Node; using JS::ubi::SimpleEdge; using JS::ubi::SimpleEdgeVector; +using JS::ubi::StackFrame; using JS::ubi::TracerConcrete; using JS::ubi::TracerConcreteWithCompartment; +template +static size_t +copyToBuffer(const CharT* src, RangedPtr dest, size_t length) +{ + size_t i = 0; + for ( ; i < length; i++) + dest[i] = src[i]; + return i; +} + +struct CopyToBufferMatcher +{ + using ReturnType = size_t; + + RangedPtr destination; + size_t maxLength; + + CopyToBufferMatcher(RangedPtr destination, size_t maxLength) + : destination(destination) + , maxLength(maxLength) + { } + + size_t + match(JSAtom* atom) + { + if (!atom) + return 0; + + size_t length = std::min(atom->length(), maxLength); + JS::AutoCheckCannotGC noGC; + return atom->hasTwoByteChars() + ? copyToBuffer(atom->twoByteChars(noGC), destination, length) + : copyToBuffer(atom->latin1Chars(noGC), destination, length); + } + + size_t + match(const char16_t* chars) + { + if (!chars) + return 0; + + size_t length = std::min(js_strlen(chars), maxLength); + return copyToBuffer(chars, destination, length); + } +}; + +size_t +StackFrame::source(RangedPtr destination, size_t length) const +{ + CopyToBufferMatcher m(destination, length); + return source().match(m); +} + +size_t +StackFrame::functionDisplayName(RangedPtr destination, size_t length) const +{ + CopyToBufferMatcher m(destination, length); + return functionDisplayName().match(m); +} + +struct LengthMatcher +{ + using ReturnType = size_t; + + size_t + match(JSAtom* atom) + { + return atom ? atom->length() : 0; + } + + size_t + match(const char16_t* chars) + { + return chars ? js_strlen(chars) : 0; + } +}; + +size_t +StackFrame::sourceLength() +{ + LengthMatcher m; + return source().match(m); +} + +size_t +StackFrame::functionDisplayNameLength() +{ + LengthMatcher m; + return functionDisplayName().match(m); +} + // All operations on null ubi::Nodes crash. -const char16_t* Concrete::typeName() const { MOZ_CRASH("null ubi::Node"); } -JS::Zone* Concrete::zone() const { MOZ_CRASH("null ubi::Node"); } -JSCompartment* Concrete::compartment() const { MOZ_CRASH("null ubi::Node"); } +const char16_t* Concrete::typeName() const { MOZ_CRASH("null ubi::Node"); } +JS::Zone* Concrete::zone() const { MOZ_CRASH("null ubi::Node"); } +JSCompartment* Concrete::compartment() const { MOZ_CRASH("null ubi::Node"); } UniquePtr Concrete::edges(JSContext*, bool) const { @@ -218,6 +315,19 @@ TracerConcreteWithCompartment::compartment() const return TracerBase::get().compartment(); } +bool +Concrete::hasAllocationStack() const +{ + return !!js::Debugger::getObjectAllocationSite(get()); +} + +StackFrame +Concrete::allocationStack() const +{ + MOZ_ASSERT(hasAllocationStack()); + return StackFrame(js::Debugger::getObjectAllocationSite(get())); +} + const char* Concrete::jsObjectClassName() const { diff --git a/js/src/vm/UnboxedObject.cpp b/js/src/vm/UnboxedObject.cpp index e16dfa41d8..5bb1d97714 100644 --- a/js/src/vm/UnboxedObject.cpp +++ b/js/src/vm/UnboxedObject.cpp @@ -943,7 +943,6 @@ const Class UnboxedPlainObject::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ nullptr, /* finalize */ nullptr, /* call */ nullptr, /* hasInstance */ @@ -1629,7 +1628,6 @@ const Class UnboxedArrayObject::class_ = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ UnboxedArrayObject::finalize, nullptr, /* call */ nullptr, /* hasInstance */ diff --git a/js/src/vm/WeakMapObject.h b/js/src/vm/WeakMapObject.h deleted file mode 100644 index abeffd973c..0000000000 --- a/js/src/vm/WeakMapObject.h +++ /dev/null @@ -1,61 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- - * vim: set ts=8 sts=4 et sw=4 tw=99: - * 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/. */ - -#ifndef vm_WeakMapObject_h -#define vm_WeakMapObject_h - -#include "jsobj.h" -#include "jsweakmap.h" - -namespace js { - -class ObjectValueMap : public WeakMap -{ - public: - ObjectValueMap(JSContext* cx, JSObject* obj) - : WeakMap(cx, obj) {} - - virtual bool findZoneEdges(); -}; - -class WeakMapObject : public NativeObject -{ - public: - static const Class class_; - - ObjectValueMap* getMap() { return static_cast(getPrivate()); } -}; - -// Generic weak map for mapping objects to other objects. -class ObjectWeakMap -{ - private: - ObjectValueMap map; - typedef gc::HashKeyRef StoreBufferRef; - - public: - explicit ObjectWeakMap(JSContext* cx); - bool init(); - ~ObjectWeakMap(); - - JSObject* lookup(const JSObject* obj); - bool add(JSContext* cx, JSObject* obj, JSObject* target); - void clear(); - - void trace(JSTracer* trc); - size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf); - size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) { - return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf); - } - -#ifdef JSGC_HASH_TABLE_CHECKS - void checkAfterMovingGC(); -#endif -}; - -} // namespace js - -#endif /* vm_WeakMapObject_h */ diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h index e4ed5dce3d..14fdfccad7 100644 --- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -29,11 +29,11 @@ namespace js { * * https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode */ -static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 300; +static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 301; static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND); -static_assert(JSErr_Limit == 412, +static_assert(JSErr_Limit == 413, "GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or " "removed MSG_DEFs from js.msg, you should increment " "XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's " diff --git a/js/xpconnect/src/Sandbox.cpp b/js/xpconnect/src/Sandbox.cpp index fce9fc9ee8..a2cdd176b3 100644 --- a/js/xpconnect/src/Sandbox.cpp +++ b/js/xpconnect/src/Sandbox.cpp @@ -559,7 +559,6 @@ static const js::Class SandboxClass = { nullptr, nullptr, nullptr, nullptr, sandbox_enumerate, sandbox_resolve, nullptr, /* mayResolve */ - nullptr, /* convert */ sandbox_finalize, nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook, JS_NULL_CLASS_SPEC, @@ -581,7 +580,6 @@ static const js::Class SandboxWriteToProtoClass = { sandbox_addProperty, nullptr, nullptr, nullptr, sandbox_enumerate, sandbox_resolve, nullptr, /* mayResolve */ - nullptr, /* convert */ sandbox_finalize, nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook, JS_NULL_CLASS_SPEC, diff --git a/js/xpconnect/src/XPCWrappedJSClass.cpp b/js/xpconnect/src/XPCWrappedJSClass.cpp index 3be7db8547..b8b05fa92d 100644 --- a/js/xpconnect/src/XPCWrappedJSClass.cpp +++ b/js/xpconnect/src/XPCWrappedJSClass.cpp @@ -1421,7 +1421,6 @@ static const JSClass XPCOutParamClass = { nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ - nullptr, /* convert */ FinalizeStub, nullptr, /* call */ nullptr, /* hasInstance */ diff --git a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp index 40b8690499..1f1d926051 100644 --- a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp +++ b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp @@ -635,7 +635,6 @@ const XPCWrappedNativeJSClass XPC_WN_NoHelper_JSClass = { XPC_WN_Shared_Enumerate, // enumerate XPC_WN_NoHelper_Resolve, // resolve nullptr, // mayResolve - nullptr, // convert XPC_WN_NoHelper_Finalize, // finalize /* Optionally non-null members start here. */ @@ -1276,7 +1275,6 @@ const js::Class XPC_WN_ModsAllowed_WithCall_Proto_JSClass = { XPC_WN_Shared_Proto_Enumerate, // enumerate; XPC_WN_ModsAllowed_Proto_Resolve, // resolve; nullptr, // mayResolve; - nullptr, // convert; XPC_WN_Shared_Proto_Finalize, // finalize; /* Optionally non-null members start here. */ @@ -1302,7 +1300,6 @@ const js::Class XPC_WN_ModsAllowed_NoCall_Proto_JSClass = { XPC_WN_Shared_Proto_Enumerate, // enumerate; XPC_WN_ModsAllowed_Proto_Resolve, // resolve; nullptr, // mayResolve; - nullptr, // convert; XPC_WN_Shared_Proto_Finalize, // finalize; /* Optionally non-null members start here. */ @@ -1381,7 +1378,6 @@ const js::Class XPC_WN_NoMods_WithCall_Proto_JSClass = { XPC_WN_Shared_Proto_Enumerate, // enumerate; XPC_WN_NoMods_Proto_Resolve, // resolve; nullptr, // mayResolve; - nullptr, // convert; XPC_WN_Shared_Proto_Finalize, // finalize; /* Optionally non-null members start here. */ @@ -1407,7 +1403,6 @@ const js::Class XPC_WN_NoMods_NoCall_Proto_JSClass = { XPC_WN_Shared_Proto_Enumerate, // enumerate; XPC_WN_NoMods_Proto_Resolve, // resolve; nullptr, // mayResolve; - nullptr, // convert; XPC_WN_Shared_Proto_Finalize, // finalize; /* Optionally non-null members start here. */ @@ -1503,7 +1498,6 @@ const js::Class XPC_WN_Tearoff_JSClass = { XPC_WN_TearOff_Enumerate, // enumerate; XPC_WN_TearOff_Resolve, // resolve; nullptr, // mayResolve; - nullptr, // convert; XPC_WN_TearOff_Finalize, // finalize; /* Optionally non-null members start here. */ diff --git a/js/xpconnect/wrappers/XrayWrapper.cpp b/js/xpconnect/wrappers/XrayWrapper.cpp index 6dcb101305..92fabf2d6b 100644 --- a/js/xpconnect/wrappers/XrayWrapper.cpp +++ b/js/xpconnect/wrappers/XrayWrapper.cpp @@ -894,7 +894,7 @@ ExpandoObjectFinalize(JSFreeOp* fop, JSObject* obj) const JSClass ExpandoObjectClass = { "XrayExpandoObject", JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_EXPANDO_COUNT), - nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, ExpandoObjectFinalize }; diff --git a/netwerk/base/ProxyAutoConfig.cpp b/netwerk/base/ProxyAutoConfig.cpp index fd3847648b..85c6d28468 100644 --- a/netwerk/base/ProxyAutoConfig.cpp +++ b/netwerk/base/ProxyAutoConfig.cpp @@ -670,7 +670,7 @@ const JSClass JSRuntimeWrapper::sGlobalClass = { JSCLASS_GLOBAL_FLAGS, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook }; diff --git a/toolkit/components/finalizationwitness/FinalizationWitnessService.cpp b/toolkit/components/finalizationwitness/FinalizationWitnessService.cpp index 9e7d3ff89d..3673e175da 100644 --- a/toolkit/components/finalizationwitness/FinalizationWitnessService.cpp +++ b/toolkit/components/finalizationwitness/FinalizationWitnessService.cpp @@ -124,7 +124,6 @@ static const JSClass sWitnessClass = { nullptr /* enumerate */, nullptr /* resolve */, nullptr /* mayResolve */, - nullptr /* convert */, Finalize /* finalize */ }; diff --git a/toolkit/devtools/server/DeserializedNode.cpp b/toolkit/devtools/server/DeserializedNode.cpp index 04430f1a5f..fbdf4703e0 100644 --- a/toolkit/devtools/server/DeserializedNode.cpp +++ b/toolkit/devtools/server/DeserializedNode.cpp @@ -97,6 +97,17 @@ DeserializedNode::getEdgeReferent(const DeserializedEdge& edge) return JS::ubi::Node(const_cast(&*ptr)); } +JS::ubi::StackFrame +DeserializedStackFrame::getParentStackFrame() const +{ + MOZ_ASSERT(parent.isSome()); + auto ptr = owner->frames.lookup(parent.ref()); + MOZ_ASSERT(ptr); + // See above comment in DeserializedNode::getEdgeReferent about why this + // const_cast is needed and safe. + return JS::ubi::StackFrame(const_cast(&*ptr)); +} + } // namespace devtools } // namespace mozilla @@ -180,5 +191,20 @@ Concrete::edges(JSContext* cx, bool) const return UniquePtr(range.release()); } +StackFrame +ConcreteStackFrame::parent() const +{ + return get().parent.isNothing() ? StackFrame() : get().getParentStackFrame(); +} + +bool +ConcreteStackFrame::constructSavedFrameStack( + JSContext* cx, + MutableHandleObject outSavedFrameStack) const +{ + StackFrame f(&get()); + return ConstructSavedFrameStackSlow(cx, f, outSavedFrameStack); +} + } // namespace ubi } // namespace JS diff --git a/toolkit/devtools/server/DeserializedNode.h b/toolkit/devtools/server/DeserializedNode.h index d2e53897ed..184705b43c 100644 --- a/toolkit/devtools/server/DeserializedNode.h +++ b/toolkit/devtools/server/DeserializedNode.h @@ -8,7 +8,7 @@ #include "js/UbiNode.h" #include "mozilla/devtools/CoreDump.pb.h" -#include "mozilla/MaybeOneOf.h" +#include "mozilla/Maybe.h" #include "mozilla/Move.h" #include "mozilla/UniquePtr.h" #include "mozilla/Vector.h" @@ -30,6 +30,7 @@ namespace devtools { class HeapSnapshot; using NodeId = uint64_t; +using StackFrameId = uint64_t; // A `DeserializedEdge` represents an edge in the heap graph pointing to the // node with id equal to `DeserializedEdge::referent` that we deserialized from @@ -57,24 +58,27 @@ struct DeserializedNode { using EdgeVector = Vector; using UniqueStringPtr = UniquePtr; - NodeId id; + NodeId id; // A borrowed reference to a string owned by this node's owning HeapSnapshot. - const char16_t* typeName; - uint64_t size; - EdgeVector edges; + const char16_t* typeName; + uint64_t size; + EdgeVector edges; + Maybe allocationStack; // A weak pointer to this node's owning `HeapSnapshot`. Safe without // AddRef'ing because this node's lifetime is equal to that of its owner. - HeapSnapshot* owner; + HeapSnapshot* owner; DeserializedNode(NodeId id, const char16_t* typeName, uint64_t size, EdgeVector&& edges, + Maybe allocationStack, HeapSnapshot& owner) : id(id) , typeName(typeName) , size(size) , edges(Move(edges)) + , allocationStack(allocationStack) , owner(&owner) { } virtual ~DeserializedNode() { } @@ -97,18 +101,24 @@ private: DeserializedNode& operator=(const DeserializedNode&) = delete; }; +static inline js::HashNumber +hashIdDerivedFromPtr(uint64_t id) +{ + // NodeIds and StackFrameIds are always 64 bits, but they are derived from + // the original referents' addresses, which could have been either 32 or 64 + // bits long. As such, NodeId and StackFrameId have little entropy in their + // bottom three bits, and may or may not have entropy in their upper 32 + // bits. This hash should manage both cases well. + id >>= 3; + return js::HashNumber((id >> 32) ^ id); +} + struct DeserializedNode::HashPolicy { using Lookup = NodeId; static js::HashNumber hash(const Lookup& lookup) { - // NodeIds are always 64 bits, but they are derived from the original - // referents' addresses, which could have been either 32 or 64 bits long. - // As such, a NodeId has little entropy in its bottom three bits, and may or - // may not have entropy in its upper 32 bits. This hash should manage both - // cases well. - uint64_t id = lookup >> 3; - return js::HashNumber((id >> 32) ^ id); + return hashIdDerivedFromPtr(lookup); } static bool match(const DeserializedNode& existing, const Lookup& lookup) { @@ -116,6 +126,76 @@ struct DeserializedNode::HashPolicy } }; +// A `DeserializedStackFrame` is a stack frame referred to by a thing in the +// heap graph that we deserialized from a core dump. +struct DeserializedStackFrame { + StackFrameId id; + Maybe parent; + uint32_t line; + uint32_t column; + // Borrowed references to strings owned by this DeserializedStackFrame's + // owning HeapSnapshot. + const char16_t* source; + const char16_t* functionDisplayName; + bool isSystem; + bool isSelfHosted; + // A weak pointer to this frame's owning `HeapSnapshot`. Safe without + // AddRef'ing because this frame's lifetime is equal to that of its owner. + HeapSnapshot* owner; + + explicit DeserializedStackFrame(StackFrameId id, + const Maybe& parent, + uint32_t line, + uint32_t column, + const char16_t* source, + const char16_t* functionDisplayName, + bool isSystem, + bool isSelfHosted, + HeapSnapshot& owner) + : id(id) + , parent(parent) + , line(line) + , column(column) + , source(source) + , functionDisplayName(functionDisplayName) + , isSystem(isSystem) + , isSelfHosted(isSelfHosted) + , owner(&owner) + { + MOZ_ASSERT(source); + } + + JS::ubi::StackFrame getParentStackFrame() const; + + struct HashPolicy; + +protected: + // This is exposed only for MockDeserializedStackFrame in the gtests. + explicit DeserializedStackFrame() + : id(0) + , parent(Nothing()) + , line(0) + , column(0) + , source(nullptr) + , functionDisplayName(nullptr) + , isSystem(false) + , isSelfHosted(false) + , owner(nullptr) + { }; +}; + +struct DeserializedStackFrame::HashPolicy { + using Lookup = StackFrameId; + + static js::HashNumber hash(const Lookup& lookup) { + return hashIdDerivedFromPtr(lookup); + } + + static bool match(const DeserializedStackFrame& existing, const Lookup& lookup) { + return existing.id == lookup; + } +}; + } // namespace devtools } // namespace mozilla @@ -123,6 +203,7 @@ namespace JS { namespace ubi { using mozilla::devtools::DeserializedNode; +using mozilla::devtools::DeserializedStackFrame; using mozilla::UniquePtr; template<> @@ -151,6 +232,42 @@ public: UniquePtr edges(JSContext* cx, bool) const override; }; +template<> +class ConcreteStackFrame : public BaseStackFrame +{ +protected: + explicit ConcreteStackFrame(DeserializedStackFrame* ptr) + : BaseStackFrame(ptr) + { } + + DeserializedStackFrame& get() const { + return *static_cast(ptr); + } + +public: + static void construct(void* storage, DeserializedStackFrame* ptr) { + new (storage) ConcreteStackFrame(ptr); + } + + uintptr_t identifier() const override { return get().id; } + uint32_t line() const override { return get().line; } + uint32_t column() const override { return get().column; } + bool isSystem() const override { return get().isSystem; } + bool isSelfHosted() const override { return get().isSelfHosted; } + void trace(JSTracer* trc) override { } + AtomOrTwoByteChars source() const override { + return AtomOrTwoByteChars(get().source); + } + AtomOrTwoByteChars functionDisplayName() const override { + return AtomOrTwoByteChars(get().functionDisplayName); + } + + StackFrame parent() const override; + bool constructSavedFrameStack(JSContext* cx, + MutableHandleObject outSavedFrameStack) + const override; +}; + } // namespace ubi } // namespace JS diff --git a/toolkit/devtools/server/HeapSnapshot.cpp b/toolkit/devtools/server/HeapSnapshot.cpp index 4568f74fa0..01c0371dfe 100644 --- a/toolkit/devtools/server/HeapSnapshot.cpp +++ b/toolkit/devtools/server/HeapSnapshot.cpp @@ -19,6 +19,7 @@ #include "mozilla/devtools/ZeroCopyNSIOutputStream.h" #include "mozilla/dom/ChromeUtils.h" #include "mozilla/dom/HeapSnapshotBinding.h" +#include "mozilla/RangedPtr.h" #include "mozilla/Telemetry.h" #include "mozilla/TimeStamp.h" #include "mozilla/UniquePtr.h" @@ -148,10 +149,99 @@ HeapSnapshot::saveNode(const protobuf::Node& node) edges.infallibleAppend(Move(edge)); } - DeserializedNode dn(id, typeName, size, Move(edges), *this); + Maybe allocationStack; + if (node.has_allocationstack()) { + StackFrameId id = 0; + if (!saveStackFrame(node.allocationstack(), id)) + return false; + allocationStack = Some(id); + } + + DeserializedNode dn(id, typeName, size, Move(edges), allocationStack, *this); return nodes.putNew(id, Move(dn)); } +bool +HeapSnapshot::saveStackFrame(const protobuf::StackFrame& frame, + StackFrameId& outFrameId) +{ + if (frame.has_ref()) { + // We should only get a reference to the previous frame if we have already + // seen the previous frame. + if (!frames.has(frame.ref())) + return false; + + outFrameId = frame.ref(); + return true; + } + + // Incomplete message. + if (!frame.has_data()) + return false; + + auto data = frame.data(); + + if (!data.has_id()) + return false; + StackFrameId id = data.id(); + + // This should be the first and only time we see this frame. + if (frames.has(id)) + return false; + + Maybe parent; + if (data.has_parent()) { + StackFrameId parentId = 0; + if (!saveStackFrame(data.parent(), parentId)) + return false; + parent = Some(parentId); + } + + if (!data.has_line()) + return false; + uint32_t line = data.line(); + + if (!data.has_column()) + return false; + uint32_t column = data.column(); + + auto duplicatedSource = reinterpret_cast( + data.source().data()); + size_t sourceLength = data.source().length() / sizeof(char16_t); + const char16_t* source = borrowUniqueString(duplicatedSource, sourceLength); + if (!source) + return false; + + const char16_t* functionDisplayName = nullptr; + if (data.has_functiondisplayname()) { + auto duplicatedName = reinterpret_cast( + data.functiondisplayname().data()); + size_t nameLength = data.functiondisplayname().length() / sizeof(char16_t); + const char16_t* functionDisplayName = borrowUniqueString(duplicatedName, + nameLength); + if (!functionDisplayName) + return false; + } + + if (!data.has_issystem()) + return false; + bool isSystem = data.issystem(); + + if (!data.has_isselfhosted()) + return false; + bool isSelfHosted = data.isselfhosted(); + + if (!frames.putNew(id, DeserializedStackFrame(id, parent, line, column, + source, functionDisplayName, + isSystem, isSelfHosted, *this))) + { + return false; + } + + outFrameId = id; + return true; +} + static inline bool StreamHasData(GzipInputStream& stream) { @@ -178,7 +268,7 @@ StreamHasData(GzipInputStream& stream) bool HeapSnapshot::init(const uint8_t* buffer, uint32_t size) { - if (!nodes.init() || !strings.init()) + if (!nodes.init() || !frames.init() || !strings.init()) return false; ArrayInputStream stream(buffer, size); @@ -381,8 +471,13 @@ EstablishBoundaries(JSContext* cx, // given `ZeroCopyOutputStream`. class MOZ_STACK_CLASS StreamWriter : public CoreDumpWriter { + using Set = js::HashSet; + JSContext* cx; - bool wantNames; + bool wantNames; + // The set of |JS::ubi::StackFrame::identifier()|s that have already been + // serialized and written to the core dump. + Set framesAlreadySerialized; ::google::protobuf::io::ZeroCopyOutputStream& stream; @@ -396,15 +491,79 @@ class MOZ_STACK_CLASS StreamWriter : public CoreDumpWriter return !codedStream.HadError(); } + protobuf::StackFrame* getProtobufStackFrame(JS::ubi::StackFrame& frame) { + MOZ_ASSERT(frame, + "null frames should be represented as the lack of a serialized " + "stack frame"); + + auto id = frame.identifier(); + auto protobufStackFrame = MakeUnique(); + if (!protobufStackFrame) + return nullptr; + + if (framesAlreadySerialized.has(id)) { + protobufStackFrame->set_ref(id); + return protobufStackFrame.release(); + } + + auto data = MakeUnique(); + if (!data) + return nullptr; + + data->set_id(id); + data->set_line(frame.line()); + data->set_column(frame.column()); + data->set_issystem(frame.isSystem()); + data->set_isselfhosted(frame.isSelfHosted()); + + auto source = MakeUnique(frame.sourceLength() * sizeof(char16_t), + '\0'); + if (!source) + return nullptr; + auto buf = const_cast(reinterpret_cast(source->data())); + frame.source(RangedPtr(buf, frame.sourceLength()), + frame.sourceLength()); + data->set_allocated_source(source.release()); + + auto nameLength = frame.functionDisplayNameLength(); + if (nameLength > 0) { + auto functionDisplayName = MakeUnique(nameLength * sizeof(char16_t), + '\0'); + if (!functionDisplayName) + return nullptr; + auto buf = const_cast(reinterpret_cast(functionDisplayName->data())); + frame.functionDisplayName(RangedPtr(buf, nameLength), nameLength); + data->set_allocated_functiondisplayname(functionDisplayName.release()); + } + + auto parent = frame.parent(); + if (parent) { + auto protobufParent = getProtobufStackFrame(parent); + if (!protobufParent) + return nullptr; + data->set_allocated_parent(protobufParent); + } + + protobufStackFrame->set_allocated_data(data.release()); + + if (!framesAlreadySerialized.put(id)) + return nullptr; + + return protobufStackFrame.release(); + } + public: StreamWriter(JSContext* cx, ::google::protobuf::io::ZeroCopyOutputStream& stream, bool wantNames) : cx(cx) , wantNames(wantNames) + , framesAlreadySerialized(cx) , stream(stream) { } + bool init() { return framesAlreadySerialized.init(); } + ~StreamWriter() override { } virtual bool writeMetadata(uint64_t timestamp) override { @@ -414,7 +573,7 @@ public: } virtual bool writeNode(const JS::ubi::Node& ubiNode, - EdgePolicy includeEdges) override { + EdgePolicy includeEdges) final { protobuf::Node protobufNode; protobufNode.set_id(ubiNode.identifier()); @@ -427,6 +586,14 @@ public: MOZ_ASSERT(mallocSizeOf); protobufNode.set_size(ubiNode.size(mallocSizeOf)); + if (ubiNode.hasAllocationStack()) { + auto ubiStackFrame = ubiNode.allocationStack(); + auto protoStackFrame = getProtobufStackFrame(ubiStackFrame); + if (NS_WARN_IF(!protoStackFrame)) + return false; + protobufNode.set_allocated_allocationstack(protoStackFrame); + } + if (includeEdges) { auto edges = ubiNode.edges(cx, wantNames); if (NS_WARN_IF(!edges)) @@ -592,6 +759,10 @@ ThreadSafeChromeUtils::SaveHeapSnapshot(GlobalObject& global, ::google::protobuf::io::GzipOutputStream gzipStream(&zeroCopyStream); StreamWriter writer(cx, gzipStream, wantNames); + if (NS_WARN_IF(!writer.init())) { + rv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } { Maybe maybeNoGC; diff --git a/toolkit/devtools/server/HeapSnapshot.h b/toolkit/devtools/server/HeapSnapshot.h index addfd85b2d..14b31171f6 100644 --- a/toolkit/devtools/server/HeapSnapshot.h +++ b/toolkit/devtools/server/HeapSnapshot.h @@ -60,11 +60,13 @@ class HeapSnapshot final : public nsISupports , public nsWrapperCache { friend struct DeserializedNode; + friend struct DeserializedStackFrame; explicit HeapSnapshot(JSContext* cx, nsISupports* aParent) : timestamp(Nothing()) , rootId(0) , nodes(cx) + , frames(cx) , strings(cx) , mParent(aParent) { @@ -80,6 +82,12 @@ class HeapSnapshot final : public nsISupports // `DeserializedNode`. bool saveNode(const protobuf::Node& node); + // Save the given `protobuf::StackFrame` message in this `HeapSnapshot` as a + // `DeserializedStackFrame`. The saved stack frame's id is returned via the + // out parameter. + bool saveStackFrame(const protobuf::StackFrame& frame, + StackFrameId& outFrameId); + // If present, a timestamp in the same units that `PR_Now` gives. Maybe timestamp; @@ -90,6 +98,11 @@ class HeapSnapshot final : public nsISupports using NodeSet = js::HashSet; NodeSet nodes; + // The set of stack frames in this deserialized heap graph, keyed by id. + using FrameSet = js::HashSet; + FrameSet frames; + // Core dump files have many duplicate strings: type names are repeated for // each node, and although in theory edge names are highly customizable for // specific edges, in practice they are also highly duplicated. Rather than diff --git a/toolkit/devtools/server/tests/gtest/DeserializedStackFrameUbiStackFrames.cpp b/toolkit/devtools/server/tests/gtest/DeserializedStackFrameUbiStackFrames.cpp new file mode 100644 index 0000000000..d016c2dbce --- /dev/null +++ b/toolkit/devtools/server/tests/gtest/DeserializedStackFrameUbiStackFrames.cpp @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* 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/. */ + +// Test that the `JS::ubi::StackFrame`s we create from +// `mozilla::devtools::DeserializedStackFrame` instances look and behave as we would +// like. + +#include "DevTools.h" +#include "js/TypeDecls.h" +#include "mozilla/devtools/DeserializedNode.h" + +using testing::Field; +using testing::ReturnRef; + +// A mock DeserializedStackFrame for testing. +struct MockDeserializedStackFrame : public DeserializedStackFrame +{ + MockDeserializedStackFrame() : DeserializedStackFrame() { } +}; + +DEF_TEST(DeserializedStackFrameUbiStackFrames, { + StackFrameId id = 1L << 42; + uint32_t line = 1337; + uint32_t column = 9; // 3 space tabs!? + const char16_t* source = MOZ_UTF16("my-javascript-file.js"); + const char16_t* functionDisplayName = MOZ_UTF16("myFunctionName"); + + MockDeserializedStackFrame mocked; + mocked.id = id; + mocked.line = line; + mocked.column = column; + mocked.source = source; + mocked.functionDisplayName = functionDisplayName; + + DeserializedStackFrame& deserialized = mocked; + JS::ubi::StackFrame ubiFrame(&deserialized); + + // Test the JS::ubi::StackFrame accessors. + + EXPECT_EQ(id, ubiFrame.identifier()); + EXPECT_EQ(JS::ubi::StackFrame(), ubiFrame.parent()); + EXPECT_EQ(line, ubiFrame.line()); + EXPECT_EQ(column, ubiFrame.column()); + EXPECT_EQ(JS::ubi::AtomOrTwoByteChars(source), ubiFrame.source()); + EXPECT_EQ(JS::ubi::AtomOrTwoByteChars(functionDisplayName), + ubiFrame.functionDisplayName()); + EXPECT_FALSE(ubiFrame.isSelfHosted()); + EXPECT_FALSE(ubiFrame.isSystem()); + + JS::RootedObject savedFrame(cx); + EXPECT_TRUE(ubiFrame.constructSavedFrameStack(cx, &savedFrame)); + + uint32_t frameLine; + ASSERT_EQ(JS::SavedFrameResult::Ok, JS::GetSavedFrameLine(cx, savedFrame, &frameLine)); + EXPECT_EQ(line, frameLine); + + uint32_t frameColumn; + ASSERT_EQ(JS::SavedFrameResult::Ok, JS::GetSavedFrameColumn(cx, savedFrame, &frameColumn)); + EXPECT_EQ(column, frameColumn); + + JS::RootedObject parent(cx); + ASSERT_EQ(JS::SavedFrameResult::Ok, JS::GetSavedFrameParent(cx, savedFrame, &parent)); + EXPECT_EQ(nullptr, parent); + + ASSERT_EQ(NS_strlen(source), 21U); + char16_t sourceBuf[21] = {}; + + // Test when the length is shorter than the string length. + auto written = ubiFrame.source(RangedPtr(sourceBuf), 3); + EXPECT_EQ(written, 3U); + for (size_t i = 0; i < 3; i++) { + EXPECT_EQ(sourceBuf[i], source[i]); + } + + written = ubiFrame.source(RangedPtr(sourceBuf), 21); + EXPECT_EQ(written, 21U); + for (size_t i = 0; i < 21; i++) { + EXPECT_EQ(sourceBuf[i], source[i]); + } + + ASSERT_EQ(NS_strlen(functionDisplayName), 14U); + char16_t nameBuf[14] = {}; + + written = ubiFrame.functionDisplayName(RangedPtr(nameBuf), 14); + EXPECT_EQ(written, 14U); + for (size_t i = 0; i < 14; i++) { + EXPECT_EQ(nameBuf[i], functionDisplayName[i]); + } +}); diff --git a/toolkit/devtools/server/tests/gtest/DevTools.h b/toolkit/devtools/server/tests/gtest/DevTools.h index d12c181341..a059aba542 100644 --- a/toolkit/devtools/server/tests/gtest/DevTools.h +++ b/toolkit/devtools/server/tests/gtest/DevTools.h @@ -18,6 +18,7 @@ #include "mozilla/CycleCollectedJSRuntime.h" #include "mozilla/Move.h" #include "mozilla/UniquePtr.h" +#include "js/Principals.h" #include "js/UbiNode.h" using namespace mozilla; diff --git a/toolkit/devtools/server/tests/gtest/moz.build b/toolkit/devtools/server/tests/gtest/moz.build index fc94405b4a..ca01405812 100644 --- a/toolkit/devtools/server/tests/gtest/moz.build +++ b/toolkit/devtools/server/tests/gtest/moz.build @@ -12,6 +12,7 @@ LOCAL_INCLUDES += [ UNIFIED_SOURCES = [ 'DeserializedNodeUbiNodes.cpp', + 'DeserializedStackFrameUbiStackFrames.cpp', 'DoesCrossZoneBoundaries.cpp', 'DoesntCrossZoneBoundaries.cpp', 'SerializesEdgeNames.cpp', diff --git a/xpcom/glue/tests/gtest/TestGCPostBarriers.cpp b/xpcom/glue/tests/gtest/TestGCPostBarriers.cpp index 236f895550..f3374d466d 100644 --- a/xpcom/glue/tests/gtest/TestGCPostBarriers.cpp +++ b/xpcom/glue/tests/gtest/TestGCPostBarriers.cpp @@ -88,7 +88,7 @@ CreateGlobalAndRunTest(JSRuntime* rt, JSContext* cx) "global", JSCLASS_GLOBAL_FLAGS, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook };