From 994061d7461d8ac2d562f51479df0f3bd3ad221d Mon Sep 17 00:00:00 2001 From: roytam1 Date: Mon, 8 Aug 2022 11:07:50 +0800 Subject: [PATCH] import changes from `dev' branch of rmottola/Arctic-Fox: - Bug 1196631 - Make JS::ubi::Node::size return 1 by default. r=sfink (9b34eb8a6b) - Bug 1191236 - Remove extract() methods used by operation forwarding on rooting types r=terrence (fb73375f55) - Bug 1191236 - Fix UCS canonicalization, r=jonco (fb5f57c838) - Bug 1196498 - Include objects' [[class]] names in heap snapshots; r=sfink (563e562e95) - Bug 1194418 - Use only JS::ubi::* interfaces in census analyses; r=sfink (a1374c3a49) - Bug 1194422 - Expose census traversals to SpiderMonkey embedders; r=sfink (7cd731fffc) - Bug 1139476 - Part 0: Add a takeCensus method to HeapSnapshot instances; r=sfink,bholley (6aac2ae0dd) - Bug 1139476 - Part 1: Port live heap census tests to offline heap snapshots; r=sfink (2cd8e13492) - Bug 1139476 - Part 2: Add test comparing live and offline census results; r=sfink (0db23ac1a0) - Bg 1198980 - Make JS::ubi::*::identifier be uint64_t instead of uintptr_t. r=sfink (902c041cb0) - Bug 1196634 - Part 0: Define a JS::ubi::CoarseType enum; r=sfink (4606fc2845) - Bug 1196634 - Part 1: Extend the protobuf format for coarseType; r=sfink (4110d46a2f) - Bug 1196634 - Part 2: Serialize and deserialize coarseType; r=sfink (530e023b48) - Bug 1196634 - Part 3: Use coarseType() instead of is in census; r=sfink (d077980d77) - Bug 1196634 - Part 4: Remove JS::ubi::Node::getCanonicalTypeName; r=sfink (4bd7131e4b) - Bug 1202048 - Root JSONParser explicitly; r=sfink (41a9034849) - Bug 1175523 - Update most (but not all) tests to use elem.srcObject over .mozSrcObject. r=pehrsons (22a6502d6d) - Bug 1201190 - Part 3: Mark every consumer of GUARD_OBJECT as MOZ_RAII, r=ehsan (f6c6381a15) - Bug 1204594 - Use MOZ_RAII to replace GUARD_OBJECT where possible in the GC; r=sfink (cec9b7f607) - Bug 1205054 - Remove isNullLike and other imprecise null checks; r=sfink (c12a6ed1d4) - Bug 1205454 - Consolidate the tagged pointer marking methods; r=sfink (7e8a823712) - js: more shared-build fixes (fdd3b957) --- .../base/timeline/AutoGlobalTimelineMarker.h | 2 +- docshell/base/timeline/AutoTimelineMarker.h | 2 +- dom/base/ScriptSettings.h | 8 +- dom/base/nsContentUtils.h | 2 +- dom/base/nsFrameLoader.cpp | 2 +- dom/bindings/BindingUtils.h | 8 +- dom/bindings/RootedDictionary.h | 4 +- dom/bindings/TypedArray.h | 8 +- dom/camera/test/camera_common.js | 6 +- dom/camera/test/test_bug1104913.html | 4 +- dom/camera/test/test_camera.html | 4 +- dom/camera/test/test_camera_2.html | 4 +- dom/camera/test/test_camera_3.html | 4 +- dom/canvas/test/captureStream_common.js | 2 +- dom/canvas/test/reftest/capturestream.html | 2 +- .../reftest/webgl-capturestream-test.html | 2 +- dom/canvas/test/test_capture.html | 6 +- .../test/webgl-mochitest/test_capture.html | 6 +- dom/media/MediaResource.h | 2 +- dom/media/test/manifest.js | 8 +- dom/media/test/test_bug879717.html | 2 +- ...mediarecorder_record_4ch_audiocontext.html | 2 +- ...est_mediarecorder_record_audiocontext.html | 2 +- ...test_mediatrack_consuming_mediastream.html | 2 +- .../test/test_multiple_mediastreamtracks.html | 2 +- dom/media/test/test_streams_autoplay.html | 2 +- .../test/test_streams_element_capture.html | 4 +- .../test_streams_element_capture_reset.html | 4 +- dom/media/test/test_streams_srcObject.html | 42 +- dom/media/tests/mochitest/blacksilence.js | 2 +- .../tests/mochitest/mediaStreamPlayback.js | 4 +- dom/media/tests/mochitest/pc.js | 4 +- ...ia_basicVideo_playAfterLoadedmetadata.html | 10 +- .../test_peerConnection_promiseSendOnly.html | 16 +- .../test_mediaStreamAudioDestinationNode.html | 2 +- dom/plugins/ipc/PluginModuleParent.cpp | 2 +- dom/svg/DOMSVGLength.cpp | 2 +- dom/svg/DOMSVGLengthList.cpp | 2 +- dom/svg/DOMSVGNumber.cpp | 2 +- dom/svg/DOMSVGNumberList.cpp | 2 +- dom/svg/DOMSVGPathSeg.cpp | 2 +- dom/svg/DOMSVGPathSegList.cpp | 2 +- dom/svg/DOMSVGPoint.cpp | 2 +- dom/svg/DOMSVGPointList.cpp | 2 +- dom/svg/DOMSVGStringList.cpp | 2 +- dom/svg/DOMSVGTransformList.cpp | 2 +- dom/svg/SVGSVGElement.h | 2 +- dom/svg/SVGTransform.cpp | 2 +- dom/webidl/HeapSnapshot.webidl | 41 + dom/workers/XMLHttpRequest.cpp | 2 +- editor/libeditor/nsEditorUtils.h | 16 +- ipc/glue/MessageChannel.cpp | 2 +- ipc/glue/Neutering.h | 4 +- js/ipc/CPOWTimer.h | 2 +- js/public/HeapAPI.h | 18 + js/public/Id.h | 2 +- js/public/RootingAPI.h | 30 +- js/public/TraceKind.h | 2 +- js/public/TraceableHashTable.h | 32 +- js/public/TraceableVector.h | 49 +- js/public/TracingAPI.h | 6 +- js/public/UbiNode.h | 100 +- js/public/UbiNodeCensus.h | 222 ++++ js/public/Value.h | 167 +-- js/src/builtin/Eval.cpp | 2 +- js/src/devtools/rootAnalysis/annotations.js | 19 +- js/src/ds/LifoAlloc.h | 2 +- js/src/gc/Barrier.h | 13 +- js/src/gc/GCInternals.h | 32 +- js/src/gc/GCRuntime.h | 2 +- js/src/gc/Heap.h | 3 +- js/src/gc/Marking.cpp | 163 +-- js/src/gc/Marking.h | 14 +- js/src/gc/Nursery.h | 1 + js/src/gc/RootMarking.cpp | 6 +- js/src/gc/Statistics.h | 29 +- js/src/gc/Tracer.cpp | 14 +- js/src/jit/Ion.cpp | 2 +- js/src/jit/IonCode.h | 12 +- js/src/jit/JitSpewer.cpp | 2 +- js/src/jsapi-tests/testUbiNode.cpp | 28 + js/src/jsapi.h | 121 +- js/src/jscntxt.h | 8 +- js/src/jscompartment.h | 8 +- js/src/jsfriendapi.h | 2 +- js/src/jsgc.cpp | 8 +- js/src/jsgc.h | 20 +- js/src/jsobj.cpp | 2 +- js/src/jsobj.h | 14 +- js/src/json.cpp | 2 +- js/src/jspubtd.h | 1 - js/src/jsscript.h | 34 +- js/src/jsutil.h | 2 +- js/src/moz.build | 3 + js/src/shell/js.cpp | 2 +- js/src/vm/Debugger.cpp | 6 +- js/src/vm/DebuggerMemory.cpp | 1072 +---------------- js/src/vm/HelperThreads.h | 4 +- js/src/vm/Interpreter.cpp | 9 +- js/src/vm/JSONParser.h | 43 +- js/src/vm/ObjectGroup.cpp | 7 +- js/src/vm/ObjectGroup.h | 112 +- js/src/vm/RegExpStatics.h | 2 +- js/src/vm/Runtime.h | 8 +- js/src/vm/SPSProfiler.h | 6 +- js/src/vm/SavedFrame.h | 8 +- js/src/vm/SavedStacks.cpp | 3 +- js/src/vm/SavedStacks.h | 2 +- js/src/vm/ScopeObject.h | 2 +- js/src/vm/Shape.h | 2 +- js/src/vm/String.cpp | 2 +- js/src/vm/TaggedProto.cpp | 20 + js/src/vm/TaggedProto.h | 130 ++ js/src/vm/TraceLogging.h | 2 +- js/src/vm/TraceLoggingGraph.cpp | 2 +- js/src/vm/TypeInference.h | 2 +- js/src/vm/UbiNode.cpp | 10 +- js/src/vm/UbiNodeCensus.cpp | 961 +++++++++++++++ js/xpconnect/src/xpcprivate.h | 4 +- layout/generic/nsFlexContainerFrame.cpp | 2 +- layout/generic/nsSelection.cpp | 2 +- layout/style/nsCSSParser.cpp | 4 +- layout/style/nsRuleProcessorData.h | 4 +- layout/svg/nsSVGClipPathFrame.h | 2 +- layout/svg/nsSVGFilterFrame.cpp | 2 +- layout/svg/nsSVGGradientFrame.cpp | 2 +- layout/svg/nsSVGMarkerFrame.h | 2 +- layout/svg/nsSVGMaskFrame.h | 2 +- layout/svg/nsSVGPatternFrame.cpp | 2 +- layout/svg/nsSVGUtils.h | 2 +- mfbt/ReentrancyGuard.h | 2 +- mfbt/ScopeExit.h | 2 +- mfbt/Scoped.h | 4 +- mozglue/build/WindowsDllBlocklist.h | 2 +- .../osfile/NativeOSFileInternals.cpp | 2 +- toolkit/components/telemetry/Telemetry.h | 4 +- toolkit/devtools/server/AutoMemMap.h | 2 +- toolkit/devtools/server/CoreDump.pb.cc | 99 +- toolkit/devtools/server/CoreDump.pb.h | 125 ++ toolkit/devtools/server/CoreDump.proto | 16 +- toolkit/devtools/server/DeserializedNode.cpp | 48 +- toolkit/devtools/server/DeserializedNode.h | 46 +- toolkit/devtools/server/HeapSnapshot.cpp | 108 +- toolkit/devtools/server/HeapSnapshot.h | 13 + .../tests/gtest/DeserializedNodeUbiNodes.cpp | 10 +- .../DeserializedStackFrameUbiStackFrames.cpp | 2 +- .../devtools/server/tests/gtest/DevTools.h | 2 +- toolkit/devtools/server/tests/unit/Census.jsm | 165 +++ toolkit/devtools/server/tests/unit/Match.jsm | 190 +++ .../unit/test_HeapSnapshot_takeCensus_01.js | 31 + .../unit/test_HeapSnapshot_takeCensus_02.js | 57 + .../unit/test_HeapSnapshot_takeCensus_03.js | 34 + .../unit/test_HeapSnapshot_takeCensus_04.js | 36 + .../unit/test_HeapSnapshot_takeCensus_05.js | 24 + .../unit/test_HeapSnapshot_takeCensus_06.js | 125 ++ .../unit/test_HeapSnapshot_takeCensus_07.js | 82 ++ .../unit/test_HeapSnapshot_takeCensus_08.js | 82 ++ .../unit/test_HeapSnapshot_takeCensus_09.js | 92 ++ .../unit/test_HeapSnapshot_takeCensus_10.js | 68 ++ .../unit/test_HeapSnapshot_takeCensus_11.js | 116 ++ .../devtools/server/tests/unit/xpcshell.ini | 13 + tools/profiler/public/GeckoProfilerImpl.h | 2 +- xpcom/glue/AutoRestore.h | 2 +- xpcom/glue/Mutex.h | 4 +- 164 files changed, 3573 insertions(+), 1960 deletions(-) create mode 100644 js/public/UbiNodeCensus.h create mode 100644 js/src/vm/TaggedProto.cpp create mode 100644 js/src/vm/TaggedProto.h create mode 100644 js/src/vm/UbiNodeCensus.cpp create mode 100644 toolkit/devtools/server/tests/unit/Census.jsm create mode 100644 toolkit/devtools/server/tests/unit/Match.jsm create mode 100644 toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_01.js create mode 100644 toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_02.js create mode 100644 toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_03.js create mode 100644 toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_04.js create mode 100644 toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_05.js create mode 100644 toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_06.js create mode 100644 toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_07.js create mode 100644 toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_08.js create mode 100644 toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_09.js create mode 100644 toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_10.js create mode 100644 toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_11.js diff --git a/docshell/base/timeline/AutoGlobalTimelineMarker.h b/docshell/base/timeline/AutoGlobalTimelineMarker.h index 7bb4e005b2..500964d4f0 100644 --- a/docshell/base/timeline/AutoGlobalTimelineMarker.h +++ b/docshell/base/timeline/AutoGlobalTimelineMarker.h @@ -29,7 +29,7 @@ namespace mozilla { // cc->Collect(); // ... // } -class MOZ_STACK_CLASS AutoGlobalTimelineMarker +class MOZ_RAII AutoGlobalTimelineMarker { MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER; diff --git a/docshell/base/timeline/AutoTimelineMarker.h b/docshell/base/timeline/AutoTimelineMarker.h index 0d99e8e644..e97ad6c756 100644 --- a/docshell/base/timeline/AutoTimelineMarker.h +++ b/docshell/base/timeline/AutoTimelineMarker.h @@ -28,7 +28,7 @@ namespace mozilla { // nsresult rv = ParseTheCSSFile(mFile); // ... // } -class MOZ_STACK_CLASS AutoTimelineMarker +class MOZ_RAII AutoTimelineMarker { MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER; diff --git a/dom/base/ScriptSettings.h b/dom/base/ScriptSettings.h index 80ef01fa15..3732f1241c 100644 --- a/dom/base/ScriptSettings.h +++ b/dom/base/ScriptSettings.h @@ -422,7 +422,7 @@ private: * passed as a parameter. AutoJSContext will take care of finding the most * appropriate JS context and release it when leaving the stack. */ -class MOZ_STACK_CLASS AutoJSContext { +class MOZ_RAII AutoJSContext { public: explicit AutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); operator JSContext*() const; @@ -444,7 +444,7 @@ protected: * Use ThreadsafeAutoJSContext when you want an AutoJSContext but might be * running on a worker thread. */ -class MOZ_STACK_CLASS ThreadsafeAutoJSContext { +class MOZ_RAII ThreadsafeAutoJSContext { public: explicit ThreadsafeAutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); operator JSContext*() const; @@ -462,7 +462,7 @@ private: * * Note - This is deprecated. Please use AutoJSAPI instead. */ -class MOZ_STACK_CLASS AutoSafeJSContext : public AutoJSContext { +class MOZ_RAII AutoSafeJSContext : public AutoJSContext { public: explicit AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); private: @@ -472,7 +472,7 @@ private: /** * Like AutoSafeJSContext but can be used safely on worker threads. */ -class MOZ_STACK_CLASS ThreadsafeAutoSafeJSContext { +class MOZ_RAII ThreadsafeAutoSafeJSContext { public: explicit ThreadsafeAutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); operator JSContext*() const; diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index bae1118d97..547848c454 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -2690,7 +2690,7 @@ private: #endif }; -class MOZ_STACK_CLASS nsAutoScriptBlocker { +class MOZ_RAII nsAutoScriptBlocker { public: explicit nsAutoScriptBlocker(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; diff --git a/dom/base/nsFrameLoader.cpp b/dom/base/nsFrameLoader.cpp index 9399abbdcb..01c085f063 100644 --- a/dom/base/nsFrameLoader.cpp +++ b/dom/base/nsFrameLoader.cpp @@ -632,7 +632,7 @@ AllDescendantsOfType(nsIDocShellTreeItem* aParentItem, int32_t aType) * A class that automatically sets mInShow to false when it goes * out of scope. */ -class MOZ_STACK_CLASS AutoResetInShow { +class MOZ_RAII AutoResetInShow { private: nsFrameLoader* mFrameLoader; MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h index bb3d8f1b3c..2ebb9affa6 100644 --- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -2285,7 +2285,7 @@ void DoTraceSequence(JSTracer* trc, InfallibleTArray& seq) // Rooter class for sequences; this is what we mostly use in the codegen template -class MOZ_STACK_CLASS SequenceRooter : private JS::CustomAutoRooter +class MOZ_RAII SequenceRooter : private JS::CustomAutoRooter { public: SequenceRooter(JSContext *aCx, FallibleTArray* aSequence @@ -2344,7 +2344,7 @@ public: // Rooter class for MozMap; this is what we mostly use in the codegen. template -class MOZ_STACK_CLASS MozMapRooter : private JS::CustomAutoRooter +class MOZ_RAII MozMapRooter : private JS::CustomAutoRooter { public: MozMapRooter(JSContext *aCx, MozMap* aMozMap @@ -2390,8 +2390,8 @@ private: }; template -class MOZ_STACK_CLASS RootedUnion : public T, - private JS::CustomAutoRooter +class MOZ_RAII RootedUnion : public T, + private JS::CustomAutoRooter { public: explicit RootedUnion(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : diff --git a/dom/bindings/RootedDictionary.h b/dom/bindings/RootedDictionary.h index a8f2a79762..73a6c730a1 100644 --- a/dom/bindings/RootedDictionary.h +++ b/dom/bindings/RootedDictionary.h @@ -15,7 +15,7 @@ namespace mozilla { namespace dom { template -class MOZ_STACK_CLASS RootedDictionary : public T, +class MOZ_RAII RootedDictionary : public T, private JS::CustomAutoRooter { public: @@ -32,7 +32,7 @@ public: }; template -class MOZ_STACK_CLASS NullableRootedDictionary : public Nullable, +class MOZ_RAII NullableRootedDictionary : public Nullable, private JS::CustomAutoRooter { public: diff --git a/dom/bindings/TypedArray.h b/dom/bindings/TypedArray.h index c8883b17af..9cacbcaa12 100644 --- a/dom/bindings/TypedArray.h +++ b/dom/bindings/TypedArray.h @@ -327,7 +327,7 @@ class TypedArrayCreator // A class for rooting an existing TypedArray struct template -class MOZ_STACK_CLASS TypedArrayRooter : private JS::CustomAutoRooter +class MOZ_RAII TypedArrayRooter : private JS::CustomAutoRooter { public: TypedArrayRooter(JSContext* cx, @@ -349,7 +349,7 @@ private: // And a specialization for dealing with nullable typed arrays template struct Nullable; template -class MOZ_STACK_CLASS TypedArrayRooter > : +class MOZ_RAII TypedArrayRooter > : private JS::CustomAutoRooter { public: @@ -373,8 +373,8 @@ private: // Class for easily setting up a rooted typed array object on the stack template -class MOZ_STACK_CLASS RootedTypedArray : public ArrayType, - private TypedArrayRooter +class MOZ_RAII RootedTypedArray : public ArrayType, + private TypedArrayRooter { public: explicit RootedTypedArray(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : diff --git a/dom/camera/test/camera_common.js b/dom/camera/test/camera_common.js index 145727d79d..edd7c6f62e 100644 --- a/dom/camera/test/camera_common.js +++ b/dom/camera/test/camera_common.js @@ -85,7 +85,7 @@ function CameraTestSuite() { var self = this; this._window.addEventListener('beforeunload', function() { if (isDefinedObj(self.viewfinder)) { - self.viewfinder.mozSrcObject = null; + self.viewfinder.srcObject = null; } self.hw = null; @@ -211,7 +211,7 @@ CameraTestSuite.prototype = { function postTest(pass) { ok(pass, test.name + ' finished'); var camera = self.camera; - self.viewfinder.mozSrcObject = null; + self.viewfinder.srcObject = null; self.camera = null; if (!isDefinedObj(camera)) { @@ -325,7 +325,7 @@ CameraTestSuite.prototype = { return; } - self.viewfinder.mozSrcObject = self.camera; + self.viewfinder.srcObject = self.camera; self.viewfinder.play(); self.camera.addEventListener('previewstatechange', onPreviewStateChange); }); diff --git a/dom/camera/test/test_bug1104913.html b/dom/camera/test/test_bug1104913.html index 51fd1686a5..ad15ec3f97 100644 --- a/dom/camera/test/test_bug1104913.html +++ b/dom/camera/test/test_bug1104913.html @@ -42,7 +42,7 @@ var Camera = { var camera = d.camera; var cfg = d.configuration; Camera.cameraObj = camera; - Camera.viewfinder.mozSrcObject = camera; + Camera.viewfinder.srcObject = camera; Camera.viewfinder.play(); // Check the default configuration @@ -66,7 +66,7 @@ var Camera = { SimpleTest.waitForExplicitFinish(); window.addEventListener('beforeunload', function() { - Camera.viewfinder.mozSrcObject = null; + Camera.viewfinder.srcObject = null; if (Camera.cameraObj) { Camera.cameraObj.release(); Camera.cameraObj = null; diff --git a/dom/camera/test/test_camera.html b/dom/camera/test/test_camera.html index 5a6caa4498..074e6417c5 100644 --- a/dom/camera/test/test_camera.html +++ b/dom/camera/test/test_camera.html @@ -232,7 +232,7 @@ var Camera = { Camera.cameraObj.addEventListener('configurationchanged', Camera.onConfigChange); Camera.cameraObj.addEventListener('shutter', Camera.shutter); Camera.cameraObj.addEventListener('picture', Camera.takePictureEvent.bind(Camera)); - Camera.viewfinder.mozSrcObject = d.camera; + Camera.viewfinder.srcObject = d.camera; Camera.viewfinder.play(); SimpleTest.expectAssertions(0); ok(true, "Camera Control object has been successfully initialized"); @@ -245,7 +245,7 @@ var Camera = { SimpleTest.waitForExplicitFinish(); window.addEventListener('beforeunload', function() { - Camera.viewfinder.mozSrcObject = null; + Camera.viewfinder.srcObject = null; Camera.cameraObj.release(); Camera.cameraObj = null; }); diff --git a/dom/camera/test/test_camera_2.html b/dom/camera/test/test_camera_2.html index db2f304305..225f9199fa 100644 --- a/dom/camera/test/test_camera_2.html +++ b/dom/camera/test/test_camera_2.html @@ -179,7 +179,7 @@ var Camera = { Camera.cameraObj = d.camera; Camera.cameraObj.addEventListener('previewstatechange', Camera.onPreviewStateChange); Camera.cameraObj.addEventListener('shutter', Camera.shutter); - Camera.viewfinder.mozSrcObject = d.camera; + Camera.viewfinder.srcObject = d.camera; Camera.viewfinder.play(); SimpleTest.expectAssertions(0); }; @@ -190,7 +190,7 @@ var Camera = { SimpleTest.waitForExplicitFinish(); window.addEventListener('beforeunload', function() { - Camera.viewfinder.mozSrcObject = null; + Camera.viewfinder.srcObject = null; Camera.cameraObj.release(); Camera.cameraObj = null; }); diff --git a/dom/camera/test/test_camera_3.html b/dom/camera/test/test_camera_3.html index fe9353dbe9..a817913a63 100644 --- a/dom/camera/test/test_camera_3.html +++ b/dom/camera/test/test_camera_3.html @@ -54,7 +54,7 @@ var Camera = { function onSuccess(d) { Camera.cameraObj = d.camera; Camera.cameraObj.addEventListener('previewstatechange', Camera.onPreviewStateChange); - Camera.viewfinder.mozSrcObject = d.camera; + Camera.viewfinder.srcObject = d.camera; Camera.viewfinder.play(); }; navigator.mozCameras.getCamera(whichCamera, options).then(onSuccess, onError); @@ -64,7 +64,7 @@ var Camera = { SimpleTest.waitForExplicitFinish(); window.addEventListener('beforeunload', function() { - Camera.viewfinder.mozSrcObject = null; + Camera.viewfinder.srcObject = null; Camera.cameraObj.release(); Camera.cameraObj = null; }); diff --git a/dom/canvas/test/captureStream_common.js b/dom/canvas/test/captureStream_common.js index ba8d16b114..91be55ed51 100644 --- a/dom/canvas/test/captureStream_common.js +++ b/dom/canvas/test/captureStream_common.js @@ -49,7 +49,7 @@ CaptureStreamTestHelper.prototype = { /* Request a frame from the stream played by |video|. */ requestFrame: function (video) { info("Requesting frame from " + video.id); - video.mozSrcObject.requestFrame(); + video.srcObject.requestFrame(); }, /* Tests the top left pixel of |video| against |refData|. Format [R,G,B,A]. */ diff --git a/dom/canvas/test/reftest/capturestream.html b/dom/canvas/test/reftest/capturestream.html index 06b590507f..b07ab394bc 100644 --- a/dom/canvas/test/reftest/capturestream.html +++ b/dom/canvas/test/reftest/capturestream.html @@ -19,7 +19,7 @@ function runTest() { context.fillRect(0, 0, canvas.width, canvas.height); var video = document.getElementById('video'); - video.mozSrcObject = canvas.captureStream(0); + video.srcObject = canvas.captureStream(0); video.play(); video.onloadeddata = finished; video.onerror = finished; diff --git a/dom/canvas/test/reftest/webgl-capturestream-test.html b/dom/canvas/test/reftest/webgl-capturestream-test.html index 9a86d55597..efd343702e 100644 --- a/dom/canvas/test/reftest/webgl-capturestream-test.html +++ b/dom/canvas/test/reftest/webgl-capturestream-test.html @@ -33,7 +33,7 @@ function runTest() { gl.clear(gl.COLOR_BUFFER_BIT); var video = document.getElementById('video'); - video.mozSrcObject = canvas.captureStream(0); + video.srcObject = canvas.captureStream(0); video.play(); video.onloadeddata = finished; video.onerror = finished; diff --git a/dom/canvas/test/test_capture.html b/dom/canvas/test/test_capture.html index f47e145d92..513ef4b172 100644 --- a/dom/canvas/test/test_capture.html +++ b/dom/canvas/test/test_capture.html @@ -19,9 +19,9 @@ function checkDrawColorInitialRed() { h.drawColor(c, h.red); - vauto.mozSrcObject = c.captureStream(); - vmanual.mozSrcObject = c.captureStream(0); - vrate.mozSrcObject = c.captureStream(10); + vauto.srcObject = c.captureStream(); + vmanual.srcObject = c.captureStream(0); + vrate.srcObject = c.captureStream(10); ok(h.testPixel(vauto, [0, 0, 0, 0], 0), "vauto hould not be drawn to before stable state"); ok(h.testPixel(vrate, [0, 0, 0, 0], 0), "vrate Should not be drawn to before stable state"); diff --git a/dom/canvas/test/webgl-mochitest/test_capture.html b/dom/canvas/test/webgl-mochitest/test_capture.html index 0889f8874a..fad4ac37a6 100644 --- a/dom/canvas/test/webgl-mochitest/test_capture.html +++ b/dom/canvas/test/webgl-mochitest/test_capture.html @@ -50,9 +50,9 @@ function checkClearColorInitialRed() { h.clearColor(c, h.red); - vauto.mozSrcObject = c.captureStream(); - vmanual.mozSrcObject = c.captureStream(0); - vrate.mozSrcObject = c.captureStream(10); + vauto.srcObject = c.captureStream(); + vmanual.srcObject = c.captureStream(0); + vrate.srcObject = c.captureStream(10); ok(h.testPixel(vauto, [0, 0, 0, 0], 0), "Should not be drawn to before stable state"); ok(h.testPixel(vrate, [0, 0, 0, 0], 0), "Should not be drawn to before stable state"); diff --git a/dom/media/MediaResource.h b/dom/media/MediaResource.h index e4e00617d8..ff0f6496d7 100644 --- a/dom/media/MediaResource.h +++ b/dom/media/MediaResource.h @@ -739,7 +739,7 @@ protected: * us. */ template -class MOZ_STACK_CLASS AutoPinned { +class MOZ_RAII AutoPinned { public: explicit AutoPinned(T* aResource MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : mResource(aResource) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; diff --git a/dom/media/test/manifest.js b/dom/media/test/manifest.js index 0c634b28be..92c934849d 100644 --- a/dom/media/test/manifest.js +++ b/dom/media/test/manifest.js @@ -700,8 +700,12 @@ function getMajorMimeType(mimetype) { // Force releasing decoder to avoid timeout in waiting for decoding resource. function removeNodeAndSource(n) { n.remove(); - // reset |mozSrcObject| first since it takes precedence over |src|. - n.mozSrcObject = null; + // Clearing srcObject and/or src will actually set them to some default + // URI that will fail to load, so make sure we don't produce a spurious + // bailing error. + n.onerror = null; + // reset |srcObject| first since it takes precedence over |src|. + n.srcObject = null; n.src = ""; while (n.firstChild) { n.removeChild(n.firstChild); diff --git a/dom/media/test/test_bug879717.html b/dom/media/test/test_bug879717.html index 35e68fb286..f9a6fcbbc0 100644 --- a/dom/media/test/test_bug879717.html +++ b/dom/media/test/test_bug879717.html @@ -109,7 +109,7 @@ var startTest = function(media, token) { v1.src = media.name; v2.src = media.name; - v3.mozSrcObject = v2.mozCaptureStreamUntilEnded(); + v3.srcObject = v2.mozCaptureStreamUntilEnded(); } manager.runTests(getPlayableVideos(gSmallTests), startTest); diff --git a/dom/media/test/test_mediarecorder_record_4ch_audiocontext.html b/dom/media/test/test_mediarecorder_record_4ch_audiocontext.html index f145f67361..fa61fe09ca 100644 --- a/dom/media/test/test_mediarecorder_record_4ch_audiocontext.html +++ b/dom/media/test/test_mediarecorder_record_4ch_audiocontext.html @@ -31,7 +31,7 @@ function startTest() { source.channelCountMode = 'explicit'; source.connect(dest); var elem = document.createElement('audio'); - elem.mozSrcObject = dest.stream; + elem.srcObject = dest.stream; mMediaStream = dest.stream; source.start(0); elem.play(); diff --git a/dom/media/test/test_mediarecorder_record_audiocontext.html b/dom/media/test/test_mediarecorder_record_audiocontext.html index f4a9a585f6..19b350f83a 100644 --- a/dom/media/test/test_mediarecorder_record_audiocontext.html +++ b/dom/media/test/test_mediarecorder_record_audiocontext.html @@ -25,7 +25,7 @@ function startTest() { var dest = context.createMediaStreamDestination(); source.connect(dest); var elem = document.createElement('audio'); - elem.mozSrcObject = dest.stream; + elem.srcObject = dest.stream; mMediaStream = dest.stream; source.start(0); elem.play(); diff --git a/dom/media/test/test_mediatrack_consuming_mediastream.html b/dom/media/test/test_mediatrack_consuming_mediastream.html index 54a12ee1ed..62021f4be4 100644 --- a/dom/media/test/test_mediatrack_consuming_mediastream.html +++ b/dom/media/test/test_mediatrack_consuming_mediastream.html @@ -128,7 +128,7 @@ function startTest() { } var steps = 0; - element.mozSrcObject = stream; + element.srcObject = stream; element.onplaying = onplaying; element.onended = onended; element.play(); diff --git a/dom/media/test/test_multiple_mediastreamtracks.html b/dom/media/test/test_multiple_mediastreamtracks.html index 11d67ae008..35a75ee486 100644 --- a/dom/media/test/test_multiple_mediastreamtracks.html +++ b/dom/media/test/test_multiple_mediastreamtracks.html @@ -21,7 +21,7 @@ function startTest() { }; mStream = stream; - element.mozSrcObject = mStream; + element.srcObject = mStream; element.play(); }) .catch(function(reason) { diff --git a/dom/media/test/test_streams_autoplay.html b/dom/media/test/test_streams_autoplay.html index 9b3560d8b9..2b8c85c198 100644 --- a/dom/media/test/test_streams_autoplay.html +++ b/dom/media/test/test_streams_autoplay.html @@ -20,7 +20,7 @@ if (media == null) { SimpleTest.finish(); } else { v1.src = media.name; - v2.mozSrcObject = v1.mozCaptureStream(); + v2.srcObject = v1.mozCaptureStream(); var onPlayingEventFired = false; v2.onplaying = function() { diff --git a/dom/media/test/test_streams_element_capture.html b/dom/media/test/test_streams_element_capture.html index 944d42b0d8..4ae3bb7460 100644 --- a/dom/media/test/test_streams_element_capture.html +++ b/dom/media/test/test_streams_element_capture.html @@ -31,8 +31,8 @@ function startTest(test, token) { v.src = test.name; var stream = v.mozCaptureStreamUntilEnded(); is(stream.currentTime, 0, test.name + " stream initial currentTime"); - vout.mozSrcObject = stream; - is(vout.mozSrcObject, stream, test.name + " set output element .srcObject correctly"); + vout.srcObject = stream; + is(vout.srcObject, stream, test.name + " set output element .srcObject correctly"); var checkEnded = function(test, vout, stream) { return function() { is(stream.currentTime, vout.currentTime, test.name + " stream final currentTime"); diff --git a/dom/media/test/test_streams_element_capture_reset.html b/dom/media/test/test_streams_element_capture_reset.html index 1ef454b2d5..0820c1bd62 100644 --- a/dom/media/test/test_streams_element_capture_reset.html +++ b/dom/media/test/test_streams_element_capture_reset.html @@ -17,8 +17,8 @@ SimpleTest.waitForExplicitFinish(); var v = document.getElementById('v'); var vout = document.getElementById('vout'); var vout_untilended = document.getElementById('vout_untilended'); -vout.mozSrcObject = v.mozCaptureStream(); -vout_untilended.mozSrcObject = v.mozCaptureStreamUntilEnded(); +vout.srcObject = v.mozCaptureStream(); +vout_untilended.srcObject = v.mozCaptureStreamUntilEnded(); function dumpEvent(event) { var v = event.target; diff --git a/dom/media/test/test_streams_srcObject.html b/dom/media/test/test_streams_srcObject.html index ed9c586678..a5a4205cd3 100644 --- a/dom/media/test/test_streams_srcObject.html +++ b/dom/media/test/test_streams_srcObject.html @@ -6,48 +6,56 @@ - - + + +
 
 
diff --git a/dom/media/tests/mochitest/blacksilence.js b/dom/media/tests/mochitest/blacksilence.js index 39f3045a81..c0f6b77fa3 100644 --- a/dom/media/tests/mochitest/blacksilence.js +++ b/dom/media/tests/mochitest/blacksilence.js @@ -92,7 +92,7 @@ function checkVideo(constraintApplied, stream, done) { var video = mkElement('video'); - video.mozSrcObject = stream; + video.srcObject = stream; var ready = false; video.onplaying = function() { diff --git a/dom/media/tests/mochitest/mediaStreamPlayback.js b/dom/media/tests/mochitest/mediaStreamPlayback.js index b6bfe6fd8b..80380c30e1 100644 --- a/dom/media/tests/mochitest/mediaStreamPlayback.js +++ b/dom/media/tests/mochitest/mediaStreamPlayback.js @@ -125,7 +125,7 @@ MediaStreamPlayback.prototype = { false); // Hooks up the media stream to the media element and starts playing it - this.mediaElement.mozSrcObject = this.mediaStream; + this.mediaElement.srcObject = this.mediaStream; this.mediaElement.play(); // If canplaythrough doesn't fire in enough time, we fail the test @@ -145,7 +145,7 @@ MediaStreamPlayback.prototype = { */ stopMediaElement : function() { this.mediaElement.pause(); - this.mediaElement.mozSrcObject = null; + this.mediaElement.srcObject = null; } } diff --git a/dom/media/tests/mochitest/pc.js b/dom/media/tests/mochitest/pc.js index 3277ec0870..0cdb6fe449 100644 --- a/dom/media/tests/mochitest/pc.js +++ b/dom/media/tests/mochitest/pc.js @@ -62,7 +62,7 @@ function MediaElementChecker(element) { // If time has passed, then track that and remove the timeupdate event // listener. - if(element.mozSrcObject && element.mozSrcObject.currentTime > 0 && + if(element.srcObject && element.srcObject.currentTime > 0 && element.currentTime > 0) { info('time passed for media element ' + elementId); this.timePassed = true; @@ -880,7 +880,7 @@ PeerConnectionWrapper.prototype = { var element = createMediaElement(type, this.label + '_' + side + this.streams.length); this.mediaCheckers.push(new MediaElementChecker(element)); - element.mozSrcObject = stream; + element.srcObject = stream; element.play(); // Store local media elements so that we can stop them when done. diff --git a/dom/media/tests/mochitest/test_getUserMedia_basicVideo_playAfterLoadedmetadata.html b/dom/media/tests/mochitest/test_getUserMedia_basicVideo_playAfterLoadedmetadata.html index 1d71a042c6..cffb047b82 100644 --- a/dom/media/tests/mochitest/test_getUserMedia_basicVideo_playAfterLoadedmetadata.html +++ b/dom/media/tests/mochitest/test_getUserMedia_basicVideo_playAfterLoadedmetadata.html @@ -18,13 +18,11 @@ var testVideo = createMediaElement('video', 'testVideo'); var constraints = {video: true}; - getUserMedia(constraints).then(aStream => { - checkMediaStreamTracks(constraints, aStream); - - var playback = new LocalMediaStreamPlayback(testVideo, aStream); + return getUserMedia(constraints).then(stream => { + var playback = new LocalMediaStreamPlayback(testVideo, stream); var video = playback.mediaElement; - video.mozSrcObject = aStream; + video.srcObject = stream; return new Promise(resolve => { ok(playback.mediaElement.paused, "Media element should be paused before play()ing"); @@ -34,7 +32,7 @@ resolve(); }); }); - }).then(() => SimpleTest.finish(), generateErrorCallback()); + }); }); diff --git a/dom/media/tests/mochitest/test_peerConnection_promiseSendOnly.html b/dom/media/tests/mochitest/test_peerConnection_promiseSendOnly.html index 963538bf0d..52f0f77e12 100644 --- a/dom/media/tests/mochitest/test_peerConnection_promiseSendOnly.html +++ b/dom/media/tests/mochitest/test_peerConnection_promiseSendOnly.html @@ -1,4 +1,4 @@ - + @@ -12,8 +12,8 @@ visible: true }); - var pc1 = new mozRTCPeerConnection(); - var pc2 = new mozRTCPeerConnection(); + var pc1 = new RTCPeerConnection(); + var pc2 = new RTCPeerConnection(); var pc2_haveRemoteOffer = new Promise(resolve => pc2.onsignalingstatechange = e => (e.target.signalingState == "have-remote-offer") && resolve()); @@ -27,7 +27,7 @@ var v1, v2; var delivered = new Promise(resolve => - pc2.onaddstream = e => resolve(v2.mozSrcObject = e.stream)); + pc2.onaddstream = e => resolve(v2.srcObject = e.stream)); runNetworkTest(function() { v1 = createMediaElement('video', 'v1'); @@ -37,16 +37,16 @@ is(v2.currentTime, 0, "v2.currentTime is zero at outset"); navigator.mediaDevices.getUserMedia({ fake: true, video: true, audio: true }) - .then(stream => pc1.addStream(v1.mozSrcObject = stream)) - .then(() => pc1.createOffer()) + .then(stream => pc1.addStream(v1.srcObject = stream)) + .then(() => pc1.createOffer({})) // check that createOffer accepts arg. .then(offer => pc1.setLocalDescription(offer)) .then(() => pc2.setRemoteDescription(pc1.localDescription)) - .then(() => pc2.createAnswer()) + .then(() => pc2.createAnswer({})) // check that createAnswer accepts arg. .then(answer => pc2.setLocalDescription(answer)) .then(() => pc1.setRemoteDescription(pc2.localDescription)) .then(() => delivered) // .then(() => canPlayThrough) // why doesn't this fire? - .then(() => waitUntil(() => v2.currentTime > 0 && v2.mozSrcObject.currentTime > 0)) + .then(() => waitUntil(() => v2.currentTime > 0 && v2.srcObject.currentTime > 0)) .then(() => ok(v2.currentTime > 0, "v2.currentTime is moving (" + v2.currentTime + ")")) .then(() => ok(true, "Connected.")) .catch(reason => ok(false, "unexpected failure: " + reason)) diff --git a/dom/media/webaudio/test/test_mediaStreamAudioDestinationNode.html b/dom/media/webaudio/test/test_mediaStreamAudioDestinationNode.html index edd333eaee..dbab11e6d9 100644 --- a/dom/media/webaudio/test/test_mediaStreamAudioDestinationNode.html +++ b/dom/media/webaudio/test/test_mediaStreamAudioDestinationNode.html @@ -27,7 +27,7 @@ addLoadEvent(function() { source.connect(dest); var elem = document.getElementById('audioelem'); - elem.mozSrcObject = dest.stream; + elem.srcObject = dest.stream; elem.onloadedmetadata = function() { ok(true, "got metadata event"); setTimeout(function() { diff --git a/dom/plugins/ipc/PluginModuleParent.cpp b/dom/plugins/ipc/PluginModuleParent.cpp index 020d4e768a..ec232616f5 100755 --- a/dom/plugins/ipc/PluginModuleParent.cpp +++ b/dom/plugins/ipc/PluginModuleParent.cpp @@ -310,7 +310,7 @@ public: return sIsLoadModuleOnStack; } - class MOZ_STACK_CLASS NotifyLoadingModule + class MOZ_RAII NotifyLoadingModule { public: explicit NotifyLoadingModule(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM) diff --git a/dom/svg/DOMSVGLength.cpp b/dom/svg/DOMSVGLength.cpp index e26d16a24c..def3c82fbc 100644 --- a/dom/svg/DOMSVGLength.cpp +++ b/dom/svg/DOMSVGLength.cpp @@ -64,7 +64,7 @@ NS_INTERFACE_MAP_END // Helper class: AutoChangeLengthNotifier // Stack-based helper class to pair calls to WillChangeLengthList and // DidChangeLengthList. -class MOZ_STACK_CLASS AutoChangeLengthNotifier +class MOZ_RAII AutoChangeLengthNotifier { public: explicit AutoChangeLengthNotifier(DOMSVGLength* aLength MOZ_GUARD_OBJECT_NOTIFIER_PARAM) diff --git a/dom/svg/DOMSVGLengthList.cpp b/dom/svg/DOMSVGLengthList.cpp index 1788d7a398..7d341d130e 100644 --- a/dom/svg/DOMSVGLengthList.cpp +++ b/dom/svg/DOMSVGLengthList.cpp @@ -79,7 +79,7 @@ DOMSVGLengthList::WrapObject(JSContext *cx, JS::Handle aGivenProto) // Helper class: AutoChangeLengthListNotifier // Stack-based helper class to pair calls to WillChangeLengthList and // DidChangeLengthList. -class MOZ_STACK_CLASS AutoChangeLengthListNotifier +class MOZ_RAII AutoChangeLengthListNotifier { public: explicit AutoChangeLengthListNotifier(DOMSVGLengthList* aLengthList MOZ_GUARD_OBJECT_NOTIFIER_PARAM) diff --git a/dom/svg/DOMSVGNumber.cpp b/dom/svg/DOMSVGNumber.cpp index d75fa96f6b..d9a014a0ca 100644 --- a/dom/svg/DOMSVGNumber.cpp +++ b/dom/svg/DOMSVGNumber.cpp @@ -53,7 +53,7 @@ NS_INTERFACE_MAP_END // Helper class: AutoChangeNumberNotifier // Stack-based helper class to pair calls to WillChangeNumberList and // DidChangeNumberList. -class MOZ_STACK_CLASS AutoChangeNumberNotifier +class MOZ_RAII AutoChangeNumberNotifier { public: explicit AutoChangeNumberNotifier(DOMSVGNumber* aNumber MOZ_GUARD_OBJECT_NOTIFIER_PARAM) diff --git a/dom/svg/DOMSVGNumberList.cpp b/dom/svg/DOMSVGNumberList.cpp index 9df233c85b..6aad17c206 100644 --- a/dom/svg/DOMSVGNumberList.cpp +++ b/dom/svg/DOMSVGNumberList.cpp @@ -80,7 +80,7 @@ DOMSVGNumberList::WrapObject(JSContext *cx, JS::Handle aGivenProto) // Helper class: AutoChangeNumberListNotifier // Stack-based helper class to pair calls to WillChangeNumberList and // DidChangeNumberList. -class MOZ_STACK_CLASS AutoChangeNumberListNotifier +class MOZ_RAII AutoChangeNumberListNotifier { public: explicit AutoChangeNumberListNotifier(DOMSVGNumberList* aNumberList MOZ_GUARD_OBJECT_NOTIFIER_PARAM) diff --git a/dom/svg/DOMSVGPathSeg.cpp b/dom/svg/DOMSVGPathSeg.cpp index 1b2f19e564..521420e8f4 100644 --- a/dom/svg/DOMSVGPathSeg.cpp +++ b/dom/svg/DOMSVGPathSeg.cpp @@ -45,7 +45,7 @@ NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DOMSVGPathSeg, Release) // Helper class: AutoChangePathSegNotifier // Stack-based helper class to pair calls to WillChangePathSegList // and DidChangePathSegList. -class MOZ_STACK_CLASS AutoChangePathSegNotifier +class MOZ_RAII AutoChangePathSegNotifier { public: explicit AutoChangePathSegNotifier(DOMSVGPathSeg* aPathSeg MOZ_GUARD_OBJECT_NOTIFIER_PARAM) diff --git a/dom/svg/DOMSVGPathSegList.cpp b/dom/svg/DOMSVGPathSegList.cpp index 0a0c28f730..78fa31c605 100644 --- a/dom/svg/DOMSVGPathSegList.cpp +++ b/dom/svg/DOMSVGPathSegList.cpp @@ -55,7 +55,7 @@ NS_INTERFACE_MAP_END // Helper class: AutoChangePathSegListNotifier // Stack-based helper class to pair calls to WillChangePathSegList and // DidChangePathSegList. -class MOZ_STACK_CLASS AutoChangePathSegListNotifier +class MOZ_RAII AutoChangePathSegListNotifier { public: explicit AutoChangePathSegListNotifier(DOMSVGPathSegList* aPathSegList MOZ_GUARD_OBJECT_NOTIFIER_PARAM) diff --git a/dom/svg/DOMSVGPoint.cpp b/dom/svg/DOMSVGPoint.cpp index 20da6ae04b..05d5754906 100644 --- a/dom/svg/DOMSVGPoint.cpp +++ b/dom/svg/DOMSVGPoint.cpp @@ -23,7 +23,7 @@ namespace mozilla { // Helper class: AutoChangePointNotifier // Stack-based helper class to pair calls to WillChangePointList and // DidChangePointList. -class MOZ_STACK_CLASS AutoChangePointNotifier +class MOZ_RAII AutoChangePointNotifier { public: explicit AutoChangePointNotifier(DOMSVGPoint* aPoint MOZ_GUARD_OBJECT_NOTIFIER_PARAM) diff --git a/dom/svg/DOMSVGPointList.cpp b/dom/svg/DOMSVGPointList.cpp index 9c52471e63..31315c028e 100644 --- a/dom/svg/DOMSVGPointList.cpp +++ b/dom/svg/DOMSVGPointList.cpp @@ -73,7 +73,7 @@ NS_INTERFACE_MAP_END // Helper class: AutoChangePointListNotifier // Stack-based helper class to pair calls to WillChangePointList and // DidChangePointList. -class MOZ_STACK_CLASS AutoChangePointListNotifier +class MOZ_RAII AutoChangePointListNotifier { public: explicit AutoChangePointListNotifier(DOMSVGPointList* aPointList MOZ_GUARD_OBJECT_NOTIFIER_PARAM) diff --git a/dom/svg/DOMSVGStringList.cpp b/dom/svg/DOMSVGStringList.cpp index 81004993d9..35e54213b0 100644 --- a/dom/svg/DOMSVGStringList.cpp +++ b/dom/svg/DOMSVGStringList.cpp @@ -43,7 +43,7 @@ NS_INTERFACE_MAP_END // Helper class: AutoChangeStringListNotifier // Stack-based helper class to pair calls to WillChangeStringListList and // DidChangeStringListList. -class MOZ_STACK_CLASS AutoChangeStringListNotifier +class MOZ_RAII AutoChangeStringListNotifier { public: explicit AutoChangeStringListNotifier(DOMSVGStringList* aStringList MOZ_GUARD_OBJECT_NOTIFIER_PARAM) diff --git a/dom/svg/DOMSVGTransformList.cpp b/dom/svg/DOMSVGTransformList.cpp index 87930c994d..b5554262d5 100644 --- a/dom/svg/DOMSVGTransformList.cpp +++ b/dom/svg/DOMSVGTransformList.cpp @@ -81,7 +81,7 @@ DOMSVGTransformList::WrapObject(JSContext *cx, JS::Handle aGivenProto // Helper class: AutoChangeTransformListNotifier // Stack-based helper class to pair calls to WillChangeTransformList and // DidChangeTransformList. -class MOZ_STACK_CLASS AutoChangeTransformListNotifier +class MOZ_RAII AutoChangeTransformListNotifier { public: explicit AutoChangeTransformListNotifier(DOMSVGTransformList* aTransformList MOZ_GUARD_OBJECT_NOTIFIER_PARAM) diff --git a/dom/svg/SVGSVGElement.h b/dom/svg/SVGSVGElement.h index f331e91e4c..fb75752b0b 100644 --- a/dom/svg/SVGSVGElement.h +++ b/dom/svg/SVGSVGElement.h @@ -407,7 +407,7 @@ private: // Helper class to automatically manage temporary changes to an SVG document's // state for rendering purposes. -class MOZ_STACK_CLASS AutoSVGRenderingState +class MOZ_RAII AutoSVGRenderingState { public: AutoSVGRenderingState(const Maybe& aSVGContext, diff --git a/dom/svg/SVGTransform.cpp b/dom/svg/SVGTransform.cpp index daa09644b1..e6574a86ca 100644 --- a/dom/svg/SVGTransform.cpp +++ b/dom/svg/SVGTransform.cpp @@ -71,7 +71,7 @@ SVGTransform::WrapObject(JSContext* aCx, JS::Handle aGivenProto) // Helper class: AutoChangeTransformNotifier // Stack-based helper class to pair calls to WillChangeTransformList // and DidChangeTransformList. -class MOZ_STACK_CLASS AutoChangeTransformNotifier +class MOZ_RAII AutoChangeTransformNotifier { public: explicit AutoChangeTransformNotifier(SVGTransform* aTransform MOZ_GUARD_OBJECT_NOTIFIER_PARAM) diff --git a/dom/webidl/HeapSnapshot.webidl b/dom/webidl/HeapSnapshot.webidl index 1f3b7eaed3..34c6ed0bc3 100644 --- a/dom/webidl/HeapSnapshot.webidl +++ b/dom/webidl/HeapSnapshot.webidl @@ -9,4 +9,45 @@ */ [ChromeOnly, Exposed=(Window,System,Worker)] interface HeapSnapshot { + /** + * Take a census of the heap snapshot. + * + * This is the same as |Debugger.Memory.prototype.takeCensus|, but operates on + * the offline heap snapshot's serialized heap graph rather than the live heap + * graph. The same optional configuration options that can be passed to that + * function can be passed here. + * + * The returned value is determined by the `"breakdown"` option used, and is + * usually a `Map`, `Object`, or `Array`. For example, the following breakdown + * + * { + * by: "coarseType", + * objects: { by: "objectClass" }, + * other: { by: "internalType" } + * } + * + * produces a result like this: + * + * { + * "objects": { + * "Function": { "count": 404, "bytes": 37328 }, + * "Object": { "count": 11, "bytes": 1264 }, + * "Debugger": { "count": 1, "bytes": 416 }, + * "ScriptSource": { "count": 1, "bytes": 64 }, + * // ... omitted for brevity... + * }, + * "scripts": { "count": 1, "bytes": 0 }, + * "strings": { "count": 701, "bytes": 49080 }, + * "other": { + * "js::Shape": { "count": 450, "bytes": 0 }, + * "js::BaseShape": { "count": 21, "bytes": 0 }, + * "js::ObjectGroup": { "count": 17, "bytes": 0 } + * } + * } + * + * See the `takeCensus` section of the `js/src/doc/Debugger/Debugger.Memory.md` + * file for detailed documentation. + */ + [Throws] + any takeCensus(object? options); }; diff --git a/dom/workers/XMLHttpRequest.cpp b/dom/workers/XMLHttpRequest.cpp index 852d5aee37..a3b649bf81 100644 --- a/dom/workers/XMLHttpRequest.cpp +++ b/dom/workers/XMLHttpRequest.cpp @@ -543,7 +543,7 @@ class EventRunnable final : public MainThreadProxyRunnable nsresult mResponseResult; public: - class StateDataAutoRooter : private JS::CustomAutoRooter + class MOZ_RAII StateDataAutoRooter : private JS::CustomAutoRooter { XMLHttpRequest::StateData* mStateData; MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER diff --git a/editor/libeditor/nsEditorUtils.h b/editor/libeditor/nsEditorUtils.h index 0d8248174e..ab2552f9ea 100644 --- a/editor/libeditor/nsEditorUtils.h +++ b/editor/libeditor/nsEditorUtils.h @@ -31,7 +31,7 @@ class Selection; * stack based helper class for batching a collection of txns inside a * placeholder txn. */ -class MOZ_STACK_CLASS nsAutoPlaceHolderBatch +class MOZ_RAII nsAutoPlaceHolderBatch { private: nsCOMPtr mEd; @@ -58,7 +58,7 @@ class MOZ_STACK_CLASS nsAutoPlaceHolderBatch * Note: I changed this to use placeholder batching so that we get * proper selection save/restore across undo/redo. */ -class MOZ_STACK_CLASS nsAutoEditBatch : public nsAutoPlaceHolderBatch +class MOZ_RAII nsAutoEditBatch : public nsAutoPlaceHolderBatch { MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER public: @@ -74,7 +74,7 @@ class MOZ_STACK_CLASS nsAutoEditBatch : public nsAutoPlaceHolderBatch * stack based helper class for saving/restoring selection. Note that this * assumes that the nodes involved are still around afterwards! */ -class MOZ_STACK_CLASS nsAutoSelectionReset +class MOZ_RAII nsAutoSelectionReset { private: /** ref-counted reference to the selection that we are supposed to restore */ @@ -96,7 +96,7 @@ class MOZ_STACK_CLASS nsAutoSelectionReset /*************************************************************************** * stack based helper class for StartOperation()/EndOperation() sandwich */ -class MOZ_STACK_CLASS nsAutoRules +class MOZ_RAII nsAutoRules { public: @@ -131,7 +131,7 @@ class MOZ_STACK_CLASS nsAutoRules * stack based helper class for turning off active selection adjustment * by low level transactions */ -class MOZ_STACK_CLASS nsAutoTxnsConserveSelection +class MOZ_RAII nsAutoTxnsConserveSelection { public: @@ -163,7 +163,7 @@ class MOZ_STACK_CLASS nsAutoTxnsConserveSelection /*************************************************************************** * stack based helper class for batching reflow and paint requests. */ -class MOZ_STACK_CLASS nsAutoUpdateViewBatch +class MOZ_RAII nsAutoUpdateViewBatch { public: @@ -197,7 +197,7 @@ class nsBoolDomIterFunctor virtual bool operator()(nsINode* aNode) const = 0; }; -class MOZ_STACK_CLASS nsDOMIterator +class MOZ_RAII nsDOMIterator { public: explicit nsDOMIterator(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); @@ -214,7 +214,7 @@ class MOZ_STACK_CLASS nsDOMIterator MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; -class MOZ_STACK_CLASS nsDOMSubtreeIterator : public nsDOMIterator +class MOZ_RAII nsDOMSubtreeIterator : public nsDOMIterator { public: explicit nsDOMSubtreeIterator(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); diff --git a/ipc/glue/MessageChannel.cpp b/ipc/glue/MessageChannel.cpp index c265addfcf..1d4004cc93 100644 --- a/ipc/glue/MessageChannel.cpp +++ b/ipc/glue/MessageChannel.cpp @@ -276,7 +276,7 @@ private: namespace { -class MOZ_STACK_CLASS MaybeScriptBlocker { +class MOZ_RAII MaybeScriptBlocker { public: explicit MaybeScriptBlocker(MessageChannel *aChannel, bool aBlock MOZ_GUARD_OBJECT_NOTIFIER_PARAM) diff --git a/ipc/glue/Neutering.h b/ipc/glue/Neutering.h index ea1a2bf5e9..70657c53c1 100644 --- a/ipc/glue/Neutering.h +++ b/ipc/glue/Neutering.h @@ -23,7 +23,7 @@ namespace ipc { * current thread will be neutered. It is safe to nest multiple instances of * this class. */ -class MOZ_STACK_CLASS NeuteredWindowRegion +class MOZ_RAII NeuteredWindowRegion { public: explicit NeuteredWindowRegion(bool aDoNeuter MOZ_GUARD_OBJECT_NOTIFIER_PARAM); @@ -46,7 +46,7 @@ private: * disabling neutering for the remainder of its enclosing block. * @see NeuteredWindowRegion */ -class MOZ_STACK_CLASS DeneuteredWindowRegion +class MOZ_RAII DeneuteredWindowRegion { public: DeneuteredWindowRegion(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); diff --git a/js/ipc/CPOWTimer.h b/js/ipc/CPOWTimer.h index 898806c64a..91dc66fd7f 100644 --- a/js/ipc/CPOWTimer.h +++ b/js/ipc/CPOWTimer.h @@ -20,7 +20,7 @@ * This stopwatch is active iff JSRuntime::stopwatch.isActive is set. * Upon destruction, update JSRuntime::stopwatch.data.totalCPOWTime. */ -class MOZ_STACK_CLASS CPOWTimer final { +class MOZ_RAII CPOWTimer final { public: explicit inline CPOWTimer(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM); ~CPOWTimer(); diff --git a/js/public/HeapAPI.h b/js/public/HeapAPI.h index 693e53348e..016d9f1953 100644 --- a/js/public/HeapAPI.h +++ b/js/public/HeapAPI.h @@ -246,6 +246,24 @@ operator!=(const GCCellPtr& ptr1, const GCCellPtr& ptr2) return !(ptr1 == ptr2); } +// Unwraps the given GCCellPtr and calls the given functor with a template +// argument of the actual type of the pointer. +template +auto +DispatchTyped(F f, GCCellPtr thing, Args&&... args) + -> decltype(f(static_cast(nullptr), mozilla::Forward(args)...)) +{ + switch (thing.kind()) { +#define JS_EXPAND_DEF(name, type, _) \ + case JS::TraceKind::name: \ + return f(&thing.as(), mozilla::Forward(args)...); + JS_FOR_EACH_TRACEKIND(JS_EXPAND_DEF); +#undef JS_EXPAND_DEF + default: + MOZ_CRASH("Invalid trace kind in DispatchTyped for GCCellPtr."); + } +} + } /* namespace JS */ namespace js { diff --git a/js/public/Id.h b/js/public/Id.h index ed8e597b48..51c8518350 100644 --- a/js/public/Id.h +++ b/js/public/Id.h @@ -178,7 +178,7 @@ template <> struct GCMethods // the pointer. If the jsid is not a GC type, calls F::defaultValue. template auto -DispatchIdTyped(F f, jsid& id, Args&&... args) +DispatchTyped(F f, jsid& id, Args&&... args) -> decltype(f(static_cast(nullptr), mozilla::Forward(args)...)) { if (JSID_IS_STRING(id)) diff --git a/js/public/RootingAPI.h b/js/public/RootingAPI.h index e8bc40a4f0..dc44e8498f 100644 --- a/js/public/RootingAPI.h +++ b/js/public/RootingAPI.h @@ -654,7 +654,7 @@ namespace JS { * specialization, define a RootedBase specialization containing them. */ template -class MOZ_STACK_CLASS Rooted : public js::RootedBase +class MOZ_RAII Rooted : public js::RootedBase { static_assert(!mozilla::IsConvertible::value, "Rooted takes pointer or Traceable types but not Traceable* type"); @@ -669,20 +669,16 @@ class MOZ_STACK_CLASS Rooted : public js::RootedBase public: template - explicit Rooted(const RootingContext& cx - MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + explicit Rooted(const RootingContext& cx) : ptr(js::GCMethods::initial()) { - MOZ_GUARD_OBJECT_NOTIFIER_INIT; registerWithRootLists(js::RootListsForRootingContext(cx)); } template - Rooted(const RootingContext& cx, S&& initial - MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + Rooted(const RootingContext& cx, S&& initial) : ptr(mozilla::Forward(initial)) { - MOZ_GUARD_OBJECT_NOTIFIER_INIT; registerWithRootLists(js::RootListsForRootingContext(cx)); } @@ -730,8 +726,6 @@ class MOZ_STACK_CLASS Rooted : public js::RootedBase T>::Type; MaybeWrapped ptr; - MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER - Rooted(const Rooted&) = delete; }; @@ -777,24 +771,14 @@ class HandleBase /* Interface substitute for Rooted which does not root the variable's memory. */ template -class FakeRooted : public RootedBase +class MOZ_RAII FakeRooted : public RootedBase { public: template - explicit FakeRooted(CX* cx - MOZ_GUARD_OBJECT_NOTIFIER_PARAM) - : ptr(GCMethods::initial()) - { - MOZ_GUARD_OBJECT_NOTIFIER_INIT; - } + explicit FakeRooted(CX* cx) : ptr(GCMethods::initial()) {} template - explicit FakeRooted(CX* cx, T initial - MOZ_GUARD_OBJECT_NOTIFIER_PARAM) - : ptr(initial) - { - MOZ_GUARD_OBJECT_NOTIFIER_INIT; - } + FakeRooted(CX* cx, T initial) : ptr(initial) {} DECLARE_POINTER_COMPARISON_OPS(T); DECLARE_POINTER_CONSTREF_OPS(T); @@ -809,8 +793,6 @@ class FakeRooted : public RootedBase ptr = value; } - MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER - FakeRooted(const FakeRooted&) = delete; }; diff --git a/js/public/TraceKind.h b/js/public/TraceKind.h index f4db1e20d8..c7e1a40dac 100644 --- a/js/public/TraceKind.h +++ b/js/public/TraceKind.h @@ -125,7 +125,7 @@ DispatchTraceKindTyped(F f, JS::TraceKind traceKind, Args&&... args) template auto DispatchTraceKindTyped(F f, void* thing, JS::TraceKind traceKind, Args&&... args) - -> decltype(f(reinterpret_cast(0), mozilla::Forward(args)...)) + -> decltype(f(static_cast(nullptr), mozilla::Forward(args)...)) { switch (traceKind) { #define JS_EXPAND_DEF(name, type, _) \ diff --git a/js/public/TraceableHashTable.h b/js/public/TraceableHashTable.h index 256e1f98f7..83f29dffc5 100644 --- a/js/public/TraceableHashTable.h +++ b/js/public/TraceableHashTable.h @@ -76,7 +76,7 @@ class TraceableHashMapOperations using Range = typename Map::Range; using Enum = typename Map::Enum; - const Map& map() const { return static_cast(this)->extract(); } + const Map& map() const { return static_cast(this)->get(); } public: bool initialized() const { return map().initialized(); } @@ -101,7 +101,7 @@ class MutableTraceableHashMapOperations using Range = typename Map::Range; using Enum = typename Map::Enum; - Map& map() { return static_cast(this)->extract(); } + Map& map() { return static_cast(this)->get(); } public: bool init(uint32_t len = 16) { return map().init(len); } @@ -140,40 +140,18 @@ class MutableTraceableHashMapOperations template class RootedBase> : public MutableTraceableHashMapOperations>, A,B,C,D,E,F> -{ - using Map = TraceableHashMap; - - friend class TraceableHashMapOperations, A,B,C,D,E,F>; - const Map& extract() const { return *static_cast*>(this)->address(); } - - friend class MutableTraceableHashMapOperations, A,B,C,D,E,F>; - Map& extract() { return *static_cast*>(this)->address(); } -}; +{}; template class MutableHandleBase> : public MutableTraceableHashMapOperations>, A,B,C,D,E,F> -{ - using Map = TraceableHashMap; - - friend class TraceableHashMapOperations, A,B,C,D,E,F>; - const Map& extract() const { - return *static_cast*>(this)->address(); - } - - friend class MutableTraceableHashMapOperations, A,B,C,D,E,F>; - Map& extract() { return *static_cast*>(this)->address(); } -}; +{}; template class HandleBase> : public TraceableHashMapOperations>, A,B,C,D,E,F> -{ - using Map = TraceableHashMap; - friend class TraceableHashMapOperations, A,B,C,D,E,F>; - const Map& extract() const { return *static_cast*>(this)->address(); } -}; +{}; } /* namespace js */ diff --git a/js/public/TraceableVector.h b/js/public/TraceableVector.h index a86f653ed8..53c66961ba 100644 --- a/js/public/TraceableVector.h +++ b/js/public/TraceableVector.h @@ -59,7 +59,7 @@ template ; - const Vec& vec() const { return static_cast(this)->extract(); } + const Vec& vec() const { return static_cast(this)->get(); } public: const AllocPolicy& allocPolicy() const { return vec().allocPolicy(); } @@ -81,8 +81,8 @@ class MutableTraceableVectorOperations : public TraceableVectorOperations { using Vec = TraceableVector; - const Vec& vec() const { return static_cast(this)->extract(); } - Vec& vec() { return static_cast(this)->extract(); } + const Vec& vec() const { return static_cast(this)->get(); } + Vec& vec() { return static_cast(this)->get(); } public: const AllocPolicy& allocPolicy() const { return vec().allocPolicy(); } @@ -144,59 +144,24 @@ class MutableTraceableVectorOperations template class RootedBase> : public MutableTraceableVectorOperations>, T,N,AP,TP> -{ - using Vec = TraceableVector; - - friend class TraceableVectorOperations, T,N,AP,TP>; - const Vec& extract() const { return *static_cast*>(this)->address(); } - - friend class MutableTraceableVectorOperations, T,N,AP,TP>; - Vec& extract() { return *static_cast*>(this)->address(); } -}; +{}; template class MutableHandleBase> : public MutableTraceableVectorOperations>, T,N,AP,TP> -{ - using Vec = TraceableVector; - - friend class TraceableVectorOperations, T,N,AP,TP>; - const Vec& extract() const { - return *static_cast*>(this)->address(); - } - - friend class MutableTraceableVectorOperations, T,N,AP,TP>; - Vec& extract() { return *static_cast*>(this)->address(); } -}; +{}; template class HandleBase> : public TraceableVectorOperations>, T,N,AP,TP> -{ - using Vec = TraceableVector; - - friend class TraceableVectorOperations, T,N,AP,TP>; - const Vec& extract() const { - return *static_cast*>(this)->address(); - } -}; +{}; template class PersistentRootedBase> : public MutableTraceableVectorOperations>, T,N,AP,TP> -{ - using Vec = TraceableVector; - - friend class TraceableVectorOperations, T,N,AP,TP>; - const Vec& extract() const { - return *static_cast*>(this)->address(); - } - - friend class MutableTraceableVectorOperations, T,N,AP,TP>; - Vec& extract() { return *static_cast*>(this)->address(); } -}; +{}; } // namespace js diff --git a/js/public/TracingAPI.h b/js/public/TracingAPI.h index b747f27c0b..57e3ed79e0 100644 --- a/js/public/TracingAPI.h +++ b/js/public/TracingAPI.h @@ -200,7 +200,7 @@ class JS_PUBLIC_API(CallbackTracer) : public JSTracer }; // Set the name portion of the tracer's context for the current edge. -class AutoTracingName +class MOZ_RAII AutoTracingName { CallbackTracer* trc_; const char* prior_; @@ -217,7 +217,7 @@ class AutoTracingName }; // Set the index portion of the tracer's context for the current range. -class AutoTracingIndex +class MOZ_RAII AutoTracingIndex { CallbackTracer* trc_; @@ -246,7 +246,7 @@ class AutoTracingIndex // Set a context callback for the trace callback to use, if it needs a detailed // edge description. -class AutoTracingDetails +class MOZ_RAII AutoTracingDetails { CallbackTracer* trc_; diff --git a/js/public/UbiNode.h b/js/public/UbiNode.h index 66ba815774..8bbf4b008e 100644 --- a/js/public/UbiNode.h +++ b/js/public/UbiNode.h @@ -201,7 +201,7 @@ class JS_FRIEND_API(BaseStackFrame) { // Get a unique identifier for this StackFrame. The identifier is not valid // across garbage collections. - virtual uintptr_t identifier() const { return reinterpret_cast(ptr); } + virtual uint64_t identifier() const { return reinterpret_cast(ptr); } // Get this frame's parent frame. virtual StackFrame parent() const = 0; @@ -376,7 +376,7 @@ class JS_FRIEND_API(StackFrame) : public JS::Traceable { // Methods that forward to virtual calls through BaseStackFrame. void trace(JSTracer* trc) { base()->trace(trc); } - uintptr_t identifier() const { return base()->identifier(); } + uint64_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(); } @@ -415,7 +415,7 @@ class JS_FRIEND_API(ConcreteStackFrame) : public BaseStackFrame { public: static void construct(void* storage, void*) { new (storage) ConcreteStackFrame(nullptr); } - uintptr_t identifier() const override { return 0; } + uint64_t identifier() const override { return 0; } void trace(JSTracer* trc) override { } bool constructSavedFrameStack(JSContext* cx, MutableHandleObject out) const override { out.set(nullptr); @@ -437,6 +437,50 @@ JS_FRIEND_API(bool) ConstructSavedFrameStackSlow(JSContext* cx, JS::ubi::StackFr /*** ubi::Node ************************************************************************************/ +// A concrete node specialization can claim its referent is a member of a +// particular "coarse type" which is less specific than the actual +// implementation type but generally more palatable for web developers. For +// example, JitCode can be considered to have a coarse type of "Script". This is +// used by some analyses for putting nodes into different buckets. The default, +// if a concrete specialization does not provide its own mapping to a CoarseType +// variant, is "Other". +// +// NB: the values associated with a particular enum variant must not change or +// be reused for new variants. Doing so will cause inspecting ubi::Nodes backed +// by an offline heap snapshot from an older SpiderMonkey/Firefox version to +// break. Consider this enum append only. +enum class CoarseType: uint32_t { + Other = 0, + Object = 1, + Script = 2, + String = 3, + + FIRST = Other, + LAST = String +}; + +inline uint32_t +CoarseTypeToUint32(CoarseType type) +{ + return static_cast(type); +} + +inline bool +Uint32IsValidCoarseType(uint32_t n) +{ + auto first = static_cast(CoarseType::FIRST); + auto last = static_cast(CoarseType::LAST); + MOZ_ASSERT(first < last); + return first <= n && n <= last; +} + +inline CoarseType +Uint32ToCoarseType(uint32_t n) +{ + MOZ_ASSERT(Uint32IsValidCoarseType(n)); + return static_cast(n); +} + // The base class implemented by each ubi::Node referent type. Subclasses must // not add data members to this class. class JS_FRIEND_API(Base) { @@ -470,14 +514,14 @@ class JS_FRIEND_API(Base) { // // This is probably suitable for use in serializations, as it is an integral // type. It may also help save memory when constructing HashSets of - // ubi::Nodes: since a uintptr_t will always be smaller than a ubi::Node, a - // HashSet will use less space per element than a - // HashSet. + // ubi::Nodes: since a uint64_t will always be smaller-or-equal-to the size + // of a ubi::Node, a HashSet may use less space per element + // than a HashSet. // // (Note that 'unique' only means 'up to equality on ubi::Node'; see the // caveats about multiple objects allocated at the same address for // 'ubi::Node::operator=='.) - typedef uintptr_t Id; + using Id = uint64_t; virtual Id identifier() const { return reinterpret_cast(ptr); } // Returns true if this node is pointing to something on the live heap, as @@ -485,6 +529,9 @@ class JS_FRIEND_API(Base) { // otherwise. virtual bool isLive() const { return true; }; + // Return the coarse-grained type-of-thing that this node represents. + virtual CoarseType coarseType() const { return CoarseType::Other; } + // Return a human-readable name for the referent's type. The result should // be statically allocated. (You can use MOZ_UTF16("strings") for this.) // @@ -495,8 +542,9 @@ class JS_FRIEND_API(Base) { // Return the size of this node, in bytes. Include any structures that this // node owns exclusively that are not exposed as their own ubi::Nodes. // |mallocSizeOf| should be a malloc block sizing function; see - // |mfbt/MemoryReporting.h. - virtual size_t size(mozilla::MallocSizeOf mallocSizeof) const { return 0; } + // |mfbt/MemoryReporting.h|. + using Size = uint64_t; + virtual Size size(mozilla::MallocSizeOf mallocSizeof) const { return 1; } // Return an EdgeRange that initially contains all the referent's outgoing // edges. The caller takes ownership of the EdgeRange. @@ -647,9 +695,13 @@ class JS_FRIEND_API(Node) { bool isLive() const { return base()->isLive(); } + // Get the canonical type name for the given type T. + template + static const char16_t* canonicalTypeName() { return Concrete::concreteTypeName; } + template bool is() const { - return base()->typeName() == Concrete::concreteTypeName; + return base()->typeName() == canonicalTypeName(); } template @@ -671,6 +723,7 @@ class JS_FRIEND_API(Node) { // not all!) JSObjects can be exposed. JS::Value exposeToJS() const; + CoarseType coarseType() const { return base()->coarseType(); } const char16_t* typeName() const { return base()->typeName(); } JS::Zone* zone() const { return base()->zone(); } JSCompartment* compartment() const { return base()->compartment(); } @@ -680,7 +733,8 @@ class JS_FRIEND_API(Node) { return base()->jsObjectConstructorName(cx, outName); } - size_t size(mozilla::MallocSizeOf mallocSizeof) const { + using Size = Base::Size; + Size size(mozilla::MallocSizeOf mallocSizeof) const { return base()->size(mallocSizeof); } @@ -693,7 +747,7 @@ class JS_FRIEND_API(Node) { return base()->allocationStack(); } - typedef Base::Id Id; + using Id = Base::Id; Id identifier() const { return base()->identifier(); } // A hash policy for ubi::Nodes. @@ -953,7 +1007,16 @@ class JS_FRIEND_API(TracerConcreteWithCompartment) : public TracerConcrete struct Concrete : TracerConcrete { }; -template<> struct Concrete : TracerConcreteWithCompartment { }; + +template<> struct Concrete : TracerConcreteWithCompartment { + CoarseType coarseType() const final { return CoarseType::Script; } + + protected: + explicit Concrete(JSScript *ptr) : TracerConcreteWithCompartment(ptr) { } + + public: + static void construct(void *storage, JSScript *ptr) { new (storage) Concrete(ptr); } +}; // The JSObject specialization. template<> @@ -961,11 +1024,13 @@ class JS_FRIEND_API(Concrete) : public TracerConcreteWithCompartment& outName) const override; - size_t size(mozilla::MallocSizeOf mallocSizeOf) const override; + Size size(mozilla::MallocSizeOf mallocSizeOf) const override; bool hasAllocationStack() const override; StackFrame allocationStack() const override; + CoarseType coarseType() const final { return CoarseType::Object; } + protected: explicit Concrete(JSObject* ptr) : TracerConcreteWithCompartment(ptr) { } @@ -977,7 +1042,9 @@ class JS_FRIEND_API(Concrete) : public TracerConcreteWithCompartment struct Concrete : TracerConcrete { - size_t size(mozilla::MallocSizeOf mallocSizeOf) const override; + Size size(mozilla::MallocSizeOf mallocSizeOf) const override; + + CoarseType coarseType() const final { return CoarseType::String; } protected: explicit Concrete(JSString *ptr) : TracerConcrete(ptr) { } @@ -990,10 +1057,11 @@ template<> struct Concrete : TracerConcrete { template<> class JS_FRIEND_API(Concrete) : public Base { const char16_t* typeName() const override; - size_t size(mozilla::MallocSizeOf mallocSizeOf) const override; + Size size(mozilla::MallocSizeOf mallocSizeOf) const override; UniquePtr edges(JSContext* cx, bool wantNames) const override; JS::Zone* zone() const override; JSCompartment* compartment() const override; + CoarseType coarseType() const final; explicit Concrete(void* ptr) : Base(ptr) { } diff --git a/js/public/UbiNodeCensus.h b/js/public/UbiNodeCensus.h new file mode 100644 index 0000000000..c7ed7fd9d2 --- /dev/null +++ b/js/public/UbiNodeCensus.h @@ -0,0 +1,222 @@ +/* -*- 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 js_UbiNodeCensus_h +#define js_UbiNodeCensus_h + +#include "mozilla/Move.h" + +#include "js/UbiNode.h" +#include "js/UbiNodeTraverse.h" + +// A census is a ubi::Node traversal that assigns each node to one or more +// buckets, and returns a report with the size of each bucket. +// +// We summarize the results of a census with counts broken down according to +// criteria selected by the API consumer code that is requesting the census. For +// example, the following breakdown might give an interesting overview of the +// heap: +// +// - all nodes +// - objects +// - objects with a specific [[Class]] * +// - strings +// - scripts +// - all other Node types +// - nodes with a specific ubi::Node::typeName * +// +// Obviously, the parts of this tree marked with * represent many separate +// counts, depending on how many distinct [[Class]] values and ubi::Node type +// names we encounter. +// +// The supported types of breakdowns are documented in +// js/src/doc/Debugger/Debugger.Memory.md. +// +// When we parse the 'breakdown' argument to takeCensus, we build a tree of +// CountType nodes. For example, for the breakdown shown in the +// Debugger.Memory.prototype.takeCensus, documentation: +// +// { +// by: "coarseType", +// objects: { by: "objectClass" }, +// other: { by: "internalType" } +// } +// +// we would build the following tree of CountType subclasses: +// +// ByCoarseType +// objects: ByObjectClass +// each class: SimpleCount +// scripts: SimpleCount +// strings: SimpleCount +// other: ByUbinodeType +// each type: SimpleCount +// +// The interior nodes are all breakdown types that categorize nodes according to +// one characteristic or another; and the leaf nodes are all SimpleType. +// +// Each CountType has its own concrete C++ type that holds the counts it +// produces. SimpleCount::Count just holds totals. ByObjectClass::Count has a +// hash table whose keys are object class names and whose values are counts of +// some other type (in the example above, SimpleCount). +// +// To keep actual count nodes small, they have no vtable. Instead, each count +// points to its CountType, which knows how to carry out all the operations we +// need on a Count. A CountType can produce new count nodes; process nodes as we +// visit them; build a JS object reporting the results; and destruct count +// nodes. + + +namespace JS { +namespace ubi { + +struct Census; + +class CountBase; + +struct JS_FRIEND_API(CountDeleter) { + void operator()(CountBase*); +}; + +using CountBasePtr = UniquePtr; + +// Abstract base class for CountType nodes. +struct JS_FRIEND_API(CountType) { + explicit CountType(Census& census) : census(census) { } + virtual ~CountType() { } + + // Destruct a count tree node that this type instance constructed. + virtual void destructCount(CountBase& count) = 0; + + // Return a fresh node for the count tree that categorizes nodes according + // to this type. Return a nullptr on OOM. + virtual CountBasePtr makeCount() = 0; + + // Trace |count| and all its children, for garbage collection. + virtual void traceCount(CountBase& count, JSTracer* trc) = 0; + + // Implement the 'count' method for counts returned by this CountType + // instance's 'newCount' method. + virtual bool count(CountBase& count, const Node& node) = 0; + + // Implement the 'report' method for counts returned by this CountType + // instance's 'newCount' method. + virtual bool report(CountBase& count, MutableHandleValue report) = 0; + + protected: + Census& census; +}; + +using CountTypePtr = UniquePtr>; + +// An abstract base class for count tree nodes. +class JS_FRIEND_API(CountBase) { + // In lieu of a vtable, each CountBase points to its type, which + // carries not only the implementations of the CountBase methods, but also + // additional parameters for the type's behavior, as specified in the + // breakdown argument passed to takeCensus. + CountType& type; + + protected: + ~CountBase() { } + + public: + explicit CountBase(CountType& type) : type(type), total_(0) { } + + // Categorize and count |node| as appropriate for this count's type. + bool count(const Node& node) { return type.count(*this, node); } + + // Construct a JavaScript object reporting the counts recorded in this + // count, and store it in |report|. Return true on success, or false on + // failure. + bool report(MutableHandleValue report) { return type.report(*this, report); } + + // Down-cast this CountBase to its true type, based on its 'type' member, + // and run its destructor. + void destruct() { return type.destructCount(*this); } + + // Trace this count for garbage collection. + void trace(JSTracer* trc) { type.traceCount(*this, trc); } + + size_t total_; +}; + +class RootedCount : JS::CustomAutoRooter { + CountBasePtr count; + + void trace(JSTracer* trc) override { count->trace(trc); } + + public: + RootedCount(JSContext* cx, CountBasePtr&& count) + : CustomAutoRooter(cx), + count(Move(count)) + { } + CountBase* operator->() const { return count.get(); } + explicit operator bool() const { return count.get(); } + operator CountBasePtr&() { return count; } +}; + +// Common data for a census traversal, shared across all CountType nodes. +struct JS_FRIEND_API(Census) { + JSContext* const cx; + // If the targetZones set is non-empty, then only consider nodes whose zone + // is an element of the set. If the targetZones set is empty, then nodes in + // all zones are considered. + JS::ZoneSet targetZones; + Zone* atomsZone; + + explicit Census(JSContext* cx) : cx(cx), atomsZone(nullptr) { } + + bool init(); + + // A 'new' work-alike that behaves like TempAllocPolicy: report OOM on this + // census's context, but don't charge the memory allocated to our context's + // GC pressure counters. + template + T* new_(Args&&... args) MOZ_HEAP_ALLOCATOR { + void* memory = js_malloc(sizeof(T)); + if (MOZ_UNLIKELY(!memory)) { + return nullptr; + } + return new(memory) T(mozilla::Forward(args)...); + } +}; + +// A BreadthFirst handler type that conducts a census, using a CountBase to +// categorize and count each node. +class JS_FRIEND_API(CensusHandler) { + Census& census; + CountBasePtr& rootCount; + + public: + CensusHandler(Census& census, CountBasePtr& rootCount) + : census(census), + rootCount(rootCount) + { } + + bool report(MutableHandleValue report) { + return rootCount->report(report); + } + + // This class needs to retain no per-node data. + class NodeData { }; + + bool operator() (BreadthFirst& traversal, + Node origin, const Edge& edge, + NodeData* referentData, bool first); +}; + +using CensusTraversal = BreadthFirst; + +// Examine the census options supplied by the API consumer, and use that to +// build a CountType tree. +JS_FRIEND_API(bool) ParseCensusOptions(JSContext* cx, Census& census, HandleObject options, + CountTypePtr& outResult); + +} // namespace ubi +} // namespace JS + +#endif // js_UbiNodeCensus_h diff --git a/js/public/Value.h b/js/public/Value.h index 9f7c44369c..0bbaf3d11c 100644 --- a/js/public/Value.h +++ b/js/public/Value.h @@ -1676,82 +1676,82 @@ template class MutableValueOperations; /* * A class designed for CRTP use in implementing the non-mutating parts of the * Value interface in Value-like classes. Outer must be a class inheriting - * ValueOperations with a visible extract() method returning the - * const Value* abstracted by Outer. + * ValueOperations with a visible get() method returning a const + * reference to the Value abstracted by Outer. */ template class ValueOperations { friend class MutableValueOperations; - const JS::Value * value() const { return static_cast(this)->extract(); } + const JS::Value& value() const { return static_cast(this)->get(); } public: - bool isUndefined() const { return value()->isUndefined(); } - bool isNull() const { return value()->isNull(); } - bool isBoolean() const { return value()->isBoolean(); } - bool isTrue() const { return value()->isTrue(); } - bool isFalse() const { return value()->isFalse(); } - bool isNumber() const { return value()->isNumber(); } - bool isInt32() const { return value()->isInt32(); } - bool isInt32(int32_t i32) const { return value()->isInt32(i32); } - bool isDouble() const { return value()->isDouble(); } - bool isString() const { return value()->isString(); } - bool isSymbol() const { return value()->isSymbol(); } - bool isObject() const { return value()->isObject(); } - bool isMagic() const { return value()->isMagic(); } - bool isMagic(JSWhyMagic why) const { return value()->isMagic(why); } - bool isMarkable() const { return value()->isMarkable(); } - bool isPrimitive() const { return value()->isPrimitive(); } - bool isGCThing() const { return value()->isGCThing(); } + bool isUndefined() const { return value().isUndefined(); } + bool isNull() const { return value().isNull(); } + bool isBoolean() const { return value().isBoolean(); } + bool isTrue() const { return value().isTrue(); } + bool isFalse() const { return value().isFalse(); } + bool isNumber() const { return value().isNumber(); } + bool isInt32() const { return value().isInt32(); } + bool isInt32(int32_t i32) const { return value().isInt32(i32); } + bool isDouble() const { return value().isDouble(); } + bool isString() const { return value().isString(); } + bool isSymbol() const { return value().isSymbol(); } + bool isObject() const { return value().isObject(); } + bool isMagic() const { return value().isMagic(); } + bool isMagic(JSWhyMagic why) const { return value().isMagic(why); } + bool isMarkable() const { return value().isMarkable(); } + bool isPrimitive() const { return value().isPrimitive(); } + bool isGCThing() const { return value().isGCThing(); } - bool isNullOrUndefined() const { return value()->isNullOrUndefined(); } - bool isObjectOrNull() const { return value()->isObjectOrNull(); } + bool isNullOrUndefined() const { return value().isNullOrUndefined(); } + bool isObjectOrNull() const { return value().isObjectOrNull(); } - bool toBoolean() const { return value()->toBoolean(); } - double toNumber() const { return value()->toNumber(); } - int32_t toInt32() const { return value()->toInt32(); } - double toDouble() const { return value()->toDouble(); } - JSString* toString() const { return value()->toString(); } - JS::Symbol* toSymbol() const { return value()->toSymbol(); } - JSObject& toObject() const { return value()->toObject(); } - JSObject* toObjectOrNull() const { return value()->toObjectOrNull(); } - gc::Cell* toGCThing() const { return value()->toGCThing(); } - JS::TraceKind traceKind() const { return value()->traceKind(); } - uint64_t asRawBits() const { return value()->asRawBits(); } + bool toBoolean() const { return value().toBoolean(); } + double toNumber() const { return value().toNumber(); } + int32_t toInt32() const { return value().toInt32(); } + double toDouble() const { return value().toDouble(); } + JSString* toString() const { return value().toString(); } + JS::Symbol* toSymbol() const { return value().toSymbol(); } + JSObject& toObject() const { return value().toObject(); } + JSObject* toObjectOrNull() const { return value().toObjectOrNull(); } + gc::Cell* toGCThing() const { return value().toGCThing(); } + JS::TraceKind traceKind() const { return value().traceKind(); } + uint64_t asRawBits() const { return value().asRawBits(); } - JSValueType extractNonDoubleType() const { return value()->extractNonDoubleType(); } - uint32_t toPrivateUint32() const { return value()->toPrivateUint32(); } + JSValueType extractNonDoubleType() const { return value().extractNonDoubleType(); } + uint32_t toPrivateUint32() const { return value().toPrivateUint32(); } - JSWhyMagic whyMagic() const { return value()->whyMagic(); } - uint32_t magicUint32() const { return value()->magicUint32(); } + JSWhyMagic whyMagic() const { return value().whyMagic(); } + uint32_t magicUint32() const { return value().magicUint32(); } }; /* * A class designed for CRTP use in implementing all the mutating parts of the * Value interface in Value-like classes. Outer must be a class inheriting - * MutableValueOperations with visible extractMutable() and extract() - * methods returning the const Value* and Value* abstracted by Outer. + * MutableValueOperations with visible get() methods returning const and + * non-const references to the Value abstracted by Outer. */ template class MutableValueOperations : public ValueOperations { - JS::Value * value() { return static_cast(this)->extractMutable(); } + JS::Value& value() { return static_cast(this)->get(); } public: - void setNull() { value()->setNull(); } - void setUndefined() { value()->setUndefined(); } - void setInt32(int32_t i) { value()->setInt32(i); } - void setDouble(double d) { value()->setDouble(d); } + void setNull() { value().setNull(); } + void setUndefined() { value().setUndefined(); } + void setInt32(int32_t i) { value().setInt32(i); } + void setDouble(double d) { value().setDouble(d); } void setNaN() { setDouble(JS::GenericNaN()); } - void setBoolean(bool b) { value()->setBoolean(b); } - void setMagic(JSWhyMagic why) { value()->setMagic(why); } - bool setNumber(uint32_t ui) { return value()->setNumber(ui); } - bool setNumber(double d) { return value()->setNumber(d); } - void setString(JSString* str) { this->value()->setString(str); } - void setSymbol(JS::Symbol* sym) { this->value()->setSymbol(sym); } - void setObject(JSObject& obj) { this->value()->setObject(obj); } - void setObjectOrNull(JSObject* arg) { this->value()->setObjectOrNull(arg); } + void setBoolean(bool b) { value().setBoolean(b); } + void setMagic(JSWhyMagic why) { value().setMagic(why); } + bool setNumber(uint32_t ui) { return value().setNumber(ui); } + bool setNumber(double d) { return value().setNumber(d); } + void setString(JSString* str) { this->value().setString(str); } + void setSymbol(JS::Symbol* sym) { this->value().setSymbol(sym); } + void setObject(JSObject& obj) { this->value().setObject(obj); } + void setObjectOrNull(JSObject* arg) { this->value().setObjectOrNull(arg); } }; /* @@ -1765,8 +1765,6 @@ class HeapBase : public ValueOperations > friend class ValueOperations; - const JS::Value * extract() const { return static_cast(this)->address(); } - void setBarriered(const JS::Value& v) { *static_cast*>(this) = v; } @@ -1812,72 +1810,21 @@ class HeapBase : public ValueOperations > } }; -/* - * Augment the generic Handle interface when T = Value with type-querying - * and value-extracting operations. - */ template <> class HandleBase : public ValueOperations > -{ - friend class ValueOperations >; - const JS::Value * extract() const { - return static_cast*>(this)->address(); - } -}; +{}; -/* - * Augment the generic MutableHandle interface when T = Value with - * type-querying, value-extracting, and mutating operations. - */ template <> class MutableHandleBase : public MutableValueOperations > -{ - friend class ValueOperations >; - const JS::Value * extract() const { - return static_cast*>(this)->address(); - } +{}; - friend class MutableValueOperations >; - JS::Value * extractMutable() { - return static_cast*>(this)->address(); - } -}; - -/* - * Augment the generic Rooted interface when T = Value with type-querying, - * value-extracting, and mutating operations. - */ template <> class RootedBase : public MutableValueOperations > -{ - friend class ValueOperations >; - const JS::Value * extract() const { - return static_cast*>(this)->address(); - } +{}; - friend class MutableValueOperations >; - JS::Value * extractMutable() { - return static_cast*>(this)->address(); - } -}; - -/* - * Augment the generic PersistentRooted interface when T = Value with type-querying, - * value-extracting, and mutating operations. - */ template <> class PersistentRootedBase : public MutableValueOperations> -{ - friend class ValueOperations>; - const JS::Value * extract() const { - return static_cast*>(this)->address(); - } - - friend class MutableValueOperations>; - JS::Value * extractMutable() { - return static_cast*>(this)->address(); - } -}; +{}; /* * If the Value is a GC pointer type, convert to that type and call |f| with @@ -1885,7 +1832,7 @@ class PersistentRootedBase : public MutableValueOperations auto -DispatchValueTyped(F f, const JS::Value& val, Args&&... args) +DispatchTyped(F f, const JS::Value& val, Args&&... args) -> decltype(f(static_cast(nullptr), mozilla::Forward(args)...)) { if (val.isString()) diff --git a/js/src/builtin/Eval.cpp b/js/src/builtin/Eval.cpp index 345a0f95d9..59fb111560 100644 --- a/js/src/builtin/Eval.cpp +++ b/js/src/builtin/Eval.cpp @@ -183,7 +183,7 @@ ParseEvalStringAsJSON(JSContext* cx, const mozilla::Range chars, Mu ? chars : mozilla::Range(chars.start().get() + 1U, len - 2); - JSONParser parser(cx, jsonChars, JSONParserBase::NoError); + Rooted> parser(cx, JSONParser(cx, jsonChars, JSONParserBase::NoError)); if (!parser.parse(rval)) return EvalJSON_Failure; diff --git a/js/src/devtools/rootAnalysis/annotations.js b/js/src/devtools/rootAnalysis/annotations.js index 1183f49909..eab8abc825 100644 --- a/js/src/devtools/rootAnalysis/annotations.js +++ b/js/src/devtools/rootAnalysis/annotations.js @@ -254,23 +254,8 @@ function ignoreGCFunction(mangled) function stripUCSAndNamespace(name) { - if (name.startsWith('struct ')) - name = name.substr(7); - if (name.startsWith('class ')) - name = name.substr(6); - if (name.startsWith('const ')) - name = name.substr(6); - if (name.startsWith('js::ctypes::')) - name = name.substr(12); - if (name.startsWith('js::')) - name = name.substr(4); - if (name.startsWith('JS::')) - name = name.substr(4); - if (name.startsWith('mozilla::dom::')) - name = name.substr(14); - if (name.startsWith('mozilla::')) - name = name.substr(9); - + name = name.replace(/(struct|class|union|const) /g, ""); + name = name.replace(/(js::ctypes::|js::|JS::|mozilla::dom::|mozilla::)/g, ""); return name; } diff --git a/js/src/ds/LifoAlloc.h b/js/src/ds/LifoAlloc.h index e87943b88e..cbe9ab91a3 100644 --- a/js/src/ds/LifoAlloc.h +++ b/js/src/ds/LifoAlloc.h @@ -484,7 +484,7 @@ class LifoAlloc }; }; -class LifoAllocScope +class MOZ_NON_TEMPORARY_CLASS LifoAllocScope { LifoAlloc* lifoAlloc; LifoAlloc::Mark mark; diff --git a/js/src/gc/Barrier.h b/js/src/gc/Barrier.h index 4ad997e52b..22adbad4ab 100644 --- a/js/src/gc/Barrier.h +++ b/js/src/gc/Barrier.h @@ -263,7 +263,7 @@ struct InternalGCMethods static bool isMarkableTaggedPointer(Value v) { return isMarkable(v); } static void preBarrier(Value v) { - DispatchValueTyped(PreBarrierFunctor(), v); + DispatchTyped(PreBarrierFunctor(), v); } static void postBarrier(Value* vp, const Value& prev, const Value& next) { @@ -288,7 +288,7 @@ struct InternalGCMethods } static void readBarrier(const Value& v) { - DispatchValueTyped(ReadBarrierFunctor(), v); + DispatchTyped(ReadBarrierFunctor(), v); } }; @@ -298,7 +298,7 @@ struct InternalGCMethods static bool isMarkable(jsid id) { return JSID_IS_STRING(id) || JSID_IS_SYMBOL(id); } static bool isMarkableTaggedPointer(jsid id) { return isMarkable(id); } - static void preBarrier(jsid id) { DispatchIdTyped(PreBarrierFunctor(), id); } + static void preBarrier(jsid id) { DispatchTyped(PreBarrierFunctor(), id); } static void postBarrier(jsid* idp, jsid prev, jsid next) {} }; @@ -342,12 +342,7 @@ class MOZ_NON_MEMMOVABLE BarrieredBase : public BarrieredBaseMixins template <> class BarrieredBaseMixins : public ValueOperations > -{ - friend class ValueOperations >; - const JS::Value * extract() const { - return static_cast*>(this)->unsafeGet(); - } -}; +{}; /* * PreBarriered only automatically handles pre-barriers. Post-barriers must diff --git a/js/src/gc/GCInternals.h b/js/src/gc/GCInternals.h index 0c07eace01..79cab1b3f3 100644 --- a/js/src/gc/GCInternals.h +++ b/js/src/gc/GCInternals.h @@ -22,7 +22,7 @@ namespace gc { void MarkPersistentRootedChains(JSTracer* trc); -class AutoCopyFreeListToArenas +class MOZ_RAII AutoCopyFreeListToArenas { JSRuntime* runtime; ZoneSelector selector; @@ -32,7 +32,7 @@ class AutoCopyFreeListToArenas ~AutoCopyFreeListToArenas(); }; -struct AutoFinishGC +struct MOZ_RAII AutoFinishGC { explicit AutoFinishGC(JSRuntime* rt); }; @@ -41,7 +41,7 @@ struct AutoFinishGC * This class should be used by any code that needs to exclusive access to the * heap in order to trace through it... */ -class AutoTraceSession +class MOZ_RAII AutoTraceSession { public: explicit AutoTraceSession(JSRuntime* rt, JS::HeapState state = JS::HeapState::Tracing); @@ -58,7 +58,7 @@ class AutoTraceSession JS::HeapState prevState; }; -struct AutoPrepareForTracing +struct MOZ_RAII AutoPrepareForTracing { AutoFinishGC finish; AutoTraceSession session; @@ -92,19 +92,16 @@ IsIncrementalGCSafe(JSRuntime* rt); #ifdef JS_GC_ZEAL -class AutoStopVerifyingBarriers +class MOZ_RAII AutoStopVerifyingBarriers { GCRuntime* gc; bool restartPreVerifier; - MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER public: - AutoStopVerifyingBarriers(JSRuntime* rt, bool isShutdown - MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + AutoStopVerifyingBarriers(JSRuntime* rt, bool isShutdown) : gc(&rt->gc) { restartPreVerifier = gc->endVerifyPreBarriers() && !isShutdown; - MOZ_GUARD_OBJECT_NOTIFIER_INIT; } ~AutoStopVerifyingBarriers() { @@ -127,7 +124,7 @@ class AutoStopVerifyingBarriers } }; #else -struct AutoStopVerifyingBarriers +struct MOZ_RAII AutoStopVerifyingBarriers { AutoStopVerifyingBarriers(JSRuntime*, bool) {} }; @@ -152,18 +149,15 @@ struct MovingTracer : JS::CallbackTracer #endif }; -class AutoMaybeStartBackgroundAllocation +class MOZ_RAII AutoMaybeStartBackgroundAllocation { private: JSRuntime* runtime; - MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER public: - explicit AutoMaybeStartBackgroundAllocation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM) + AutoMaybeStartBackgroundAllocation() : runtime(nullptr) - { - MOZ_GUARD_OBJECT_NOTIFIER_INIT; - } + {} void tryToStartBackgroundAllocation(JSRuntime* rt) { runtime = rt; @@ -176,15 +170,14 @@ class AutoMaybeStartBackgroundAllocation }; // In debug builds, set/unset the GC sweeping flag for the current thread. -struct AutoSetThreadIsSweeping +struct MOZ_RAII AutoSetThreadIsSweeping { #ifdef DEBUG - explicit AutoSetThreadIsSweeping(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM) + AutoSetThreadIsSweeping() : threadData_(js::TlsPerThreadData.get()) { MOZ_ASSERT(!threadData_->gcSweeping); threadData_->gcSweeping = true; - MOZ_GUARD_OBJECT_NOTIFIER_INIT; } ~AutoSetThreadIsSweeping() { @@ -194,7 +187,6 @@ struct AutoSetThreadIsSweeping private: PerThreadData* threadData_; - MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER #else AutoSetThreadIsSweeping() {} #endif diff --git a/js/src/gc/GCRuntime.h b/js/src/gc/GCRuntime.h index 59c6ed2c2b..7afe24d4da 100644 --- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -1314,7 +1314,7 @@ class GCRuntime }; /* Prevent compartments and zones from being collected during iteration. */ -class AutoEnterIteration { +class MOZ_RAII AutoEnterIteration { GCRuntime* gc; public: diff --git a/js/src/gc/Heap.h b/js/src/gc/Heap.h index 3b418c9385..a478ae8ee0 100644 --- a/js/src/gc/Heap.h +++ b/js/src/gc/Heap.h @@ -1473,7 +1473,8 @@ TenuredCell::readBarrier(TenuredCell* thing) TenuredCell::writeBarrierPre(TenuredCell* thing) { MOZ_ASSERT(!CurrentThreadIsIonCompiling()); - if (isNullLike(thing) || thing->shadowRuntimeFromAnyThread()->isHeapBusy()) + MOZ_ASSERT_IF(thing, !isNullLike(thing)); + if (!thing || thing->shadowRuntimeFromAnyThread()->isHeapBusy()) return; JS::shadow::Zone* shadowZone = thing->shadowZoneFromAnyThread(); diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index 25909e6c47..5c19e6657d 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -9,6 +9,7 @@ #include "mozilla/DebugOnly.h" #include "mozilla/IntegerRange.h" #include "mozilla/ReentrancyGuard.h" +#include "mozilla/ScopeExit.h" #include "mozilla/TypeTraits.h" #include "jsgc.h" @@ -182,7 +183,7 @@ IsOwnedByOtherRuntime(JSTracer* trc, T thing) template void -js::CheckTracedThing(JSTracer* trc, T thing) +js::CheckTracedThing(JSTracer* trc, T* thing) { #ifdef DEBUG MOZ_ASSERT(trc); @@ -252,23 +253,16 @@ struct CheckTracedFunctor : public VoidDefaultAdaptor { template void operator()(T* t, JSTracer* trc) { CheckTracedThing(trc, t); } }; +template +void +js::CheckTracedThing(JSTracer* trc, T thing) +{ + DispatchTyped(CheckTracedFunctor(), thing, trc); +} + namespace js { -template<> -void -CheckTracedThing(JSTracer* trc, Value val) -{ - DispatchValueTyped(CheckTracedFunctor(), val, trc); -} - -template <> -void -CheckTracedThing(JSTracer* trc, jsid id) -{ - DispatchIdTyped(CheckTracedFunctor(), id, trc); -} - #define IMPL_CHECK_TRACED_THING(_, type, __) \ - template void CheckTracedThing(JSTracer*, type*); + template void CheckTracedThing(JSTracer*, type*); JS_FOR_EACH_TRACEKIND(IMPL_CHECK_TRACED_THING); #undef IMPL_CHECK_TRACED_THING } // namespace js @@ -395,7 +389,8 @@ AssertRootMarkingPhase(JSTracer* trc) D(JS::Symbol*) \ D(js::ObjectGroup*) \ D(Value) \ - D(jsid) + D(jsid) \ + D(TaggedProto) // The second parameter to BaseGCType is derived automatically based on T. The // relation here is that for any T, the TraceKind will automatically, @@ -429,9 +424,7 @@ JS_FOR_EACH_TRACEKIND(IMPL_BASE_GC_TYPE); // share the definitions with Value and jsid. Thus, we need to strip the // pointer before sending the type to BaseGCType and re-add it on the other // side. As such: -template struct PtrBaseGCType {}; -template <> struct PtrBaseGCType { typedef Value type; }; -template <> struct PtrBaseGCType { typedef jsid type; }; +template struct PtrBaseGCType { typedef T type; }; template struct PtrBaseGCType { typedef typename BaseGCType::type* type; }; template @@ -443,6 +436,7 @@ ConvertToBase(T* thingp) template void DispatchToTracer(JSTracer* trc, T* thingp, const char* name); template T DoCallback(JS::CallbackTracer* trc, T* thingp, const char* name); +template void DoMarking(GCMarker* gcmarker, T* thing); template void DoMarking(GCMarker* gcmarker, T thing); template @@ -604,7 +598,8 @@ DispatchToTracer(JSTracer* trc, T* thingp, const char* name) static_assert( JS_FOR_EACH_TRACEKIND(IS_SAME_TYPE_OR) mozilla::IsSame::value || - mozilla::IsSame::value, + mozilla::IsSame::value || + mozilla::IsSame::value, "Only the base cell layout types are allowed into marking/tracing internals"); #undef IS_SAME_TYPE_OR if (trc->isMarkingTracer()) @@ -735,7 +730,7 @@ MustSkipMarking(JS::Symbol* sym) template void -DoMarking(GCMarker* gcmarker, T thing) +DoMarking(GCMarker* gcmarker, T* thing) { // Do per-type marking precondition checks. if (MustSkipMarking(thing)) @@ -753,18 +748,11 @@ struct DoMarkingFunctor : public VoidDefaultAdaptor { template void operator()(T* t, GCMarker* gcmarker) { DoMarking(gcmarker, t); } }; -template <> +template void -DoMarking(GCMarker* gcmarker, Value val) +DoMarking(GCMarker* gcmarker, T thing) { - DispatchValueTyped(DoMarkingFunctor(), val, gcmarker); -} - -template <> -void -DoMarking(GCMarker* gcmarker, jsid id) -{ - DispatchIdTyped(DoMarkingFunctor(), id, gcmarker); + DispatchTyped(DoMarkingFunctor(), thing, gcmarker); } // The simplest traversal calls out to the fully generic traceChildren function @@ -835,7 +823,7 @@ GCMarker::traverse(AccessorShape* thing) { template void -js::GCMarker::traverseEdge(S source, T target) +js::GCMarker::traverseEdge(S source, T* target) { // Atoms and Symbols do not have or mark their internal pointers, respectively. MOZ_ASSERT(!ThingIsPermanentAtomOrWellKnownSymbol(source)); @@ -861,18 +849,11 @@ template struct TraverseEdgeFunctor : public VoidDefaul } }; -template +template void -js::GCMarker::traverseEdge(S source, jsid id) +js::GCMarker::traverseEdge(S source, T thing) { - DispatchIdTyped(TraverseEdgeFunctor(), id, this, source); -} - -template -void -js::GCMarker::traverseEdge(S source, Value v) -{ - DispatchValueTyped(TraverseEdgeFunctor(), v, this, source); + DispatchTyped(TraverseEdgeFunctor(), thing, this, source); } template @@ -1084,7 +1065,7 @@ js::ObjectGroup::traceChildren(JSTracer* trc) } if (proto().isObject()) - TraceEdge(trc, &protoRaw(), "group_proto"); + TraceEdge(trc, &proto(), "group_proto"); if (newScript()) newScript()->trace(trc); @@ -1234,14 +1215,9 @@ bool GCMarker::drainMarkStack(SliceBudget& budget) { #ifdef DEBUG - struct AutoCheckCompartment { - bool& flag; - explicit AutoCheckCompartment(bool& comparmentCheckFlag) : flag(comparmentCheckFlag) { - MOZ_ASSERT(!flag); - flag = true; - } - ~AutoCheckCompartment() { flag = false; } - } acc(strictCompartmentChecking); + MOZ_ASSERT(!strictCompartmentChecking); + strictCompartmentChecking = true; + auto acc = mozilla::MakeScopeExit([&] {strictCompartmentChecking = false;}); #endif if (budget.isOverBudget()) @@ -1829,6 +1805,12 @@ GCMarker::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const /*** Tenuring Tracer *****************************************************************************/ namespace js { +template +void +TenuringTracer::traverse(T** tp) +{ +} + template <> void TenuringTracer::traverse(JSObject** objp) @@ -1840,27 +1822,20 @@ TenuringTracer::traverse(JSObject** objp) *objp = moveToTenured(*objp); } -template <> +template +struct TenuringTraversalFunctor : public IdentityDefaultAdaptor { + template S operator()(T* t, TenuringTracer* trc) { + trc->traverse(&t); + return js::gc::RewrapTaggedPointer::wrap(t); + } +}; + +template void -TenuringTracer::traverse(Value* valp) +TenuringTracer::traverse(T* thingp) { - if (!valp->isObject()) - return; - - JSObject *obj = &valp->toObject(); - traverse(&obj); - valp->setObject(*obj); + *thingp = DispatchTyped(TenuringTraversalFunctor(), *thingp, this); } - -template <> void js::TenuringTracer::traverse(js::BaseShape**) {} -template <> void js::TenuringTracer::traverse(js::jit::JitCode**) {} -template <> void js::TenuringTracer::traverse(JSScript**) {} -template <> void js::TenuringTracer::traverse(js::LazyScript**) {} -template <> void js::TenuringTracer::traverse(js::Shape**) {} -template <> void js::TenuringTracer::traverse(JSString**) {} -template <> void js::TenuringTracer::traverse(JS::Symbol**) {} -template <> void js::TenuringTracer::traverse(js::ObjectGroup**) {} -template <> void js::TenuringTracer::traverse(jsid*) {} } // namespace js template @@ -2233,13 +2208,13 @@ IsMarkedInternalCommon(T* thingp) template static bool -IsMarkedInternal(T* thingp) +IsMarkedInternal(T** thingp) { return IsMarkedInternalCommon(thingp); } -template -static bool +template <> +/* static */ bool IsMarkedInternal(JSObject** thingp) { if (IsInsideNursery(*thingp)) { @@ -2254,34 +2229,25 @@ template struct IsMarkedFunctor : public IdentityDefaultAdaptor { template S operator()(T* t, bool* rv) { *rv = IsMarkedInternal(&t); - return js::gc::RewrapValueOrId::wrap(t); + return js::gc::RewrapTaggedPointer::wrap(t); } }; -template <> -bool -IsMarkedInternal(Value* valuep) +template +static bool +IsMarkedInternal(T* thingp) { bool rv = true; - *valuep = DispatchValueTyped(IsMarkedFunctor(), *valuep, &rv); - return rv; -} - -template <> -bool -IsMarkedInternal(jsid* idp) -{ - bool rv = true; - *idp = DispatchIdTyped(IsMarkedFunctor(), *idp, &rv); + *thingp = DispatchTyped(IsMarkedFunctor(), *thingp, &rv); return rv; } template static bool -IsAboutToBeFinalizedInternal(T* thingp) +IsAboutToBeFinalizedInternal(T** thingp) { CheckIsMarkedThing(thingp); - T thing = *thingp; + T* thing = *thingp; JSRuntime* rt = thing->runtimeFromAnyThread(); /* Permanent atoms are never finalized by non-owning runtimes. */ @@ -2314,25 +2280,16 @@ template struct IsAboutToBeFinalizedFunctor : public IdentityDefaultAdaptor { template S operator()(T* t, bool* rv) { *rv = IsAboutToBeFinalizedInternal(&t); - return js::gc::RewrapValueOrId::wrap(t); + return js::gc::RewrapTaggedPointer::wrap(t); } }; -template <> -bool -IsAboutToBeFinalizedInternal(Value* valuep) +template +static bool +IsAboutToBeFinalizedInternal(T* thingp) { bool rv = false; - *valuep = DispatchValueTyped(IsAboutToBeFinalizedFunctor(), *valuep, &rv); - return rv; -} - -template <> -bool -IsAboutToBeFinalizedInternal(jsid* idp) -{ - bool rv = false; - *idp = DispatchIdTyped(IsAboutToBeFinalizedFunctor(), *idp, &rv); + *thingp = DispatchTyped(IsAboutToBeFinalizedFunctor(), *thingp, &rv); return rv; } diff --git a/js/src/gc/Marking.h b/js/src/gc/Marking.h index 01c0da2ad3..0377346310 100644 --- a/js/src/gc/Marking.h +++ b/js/src/gc/Marking.h @@ -20,6 +20,7 @@ #include "js/HeapAPI.h" #include "js/SliceBudget.h" #include "js/TracingAPI.h" +#include "vm/TaggedProto.h" class JSLinearString; class JSRope; @@ -182,10 +183,8 @@ class GCMarker : public JSTracer template void traverse(T thing); // Calls traverse on target after making additional assertions. + template void traverseEdge(S source, T* target); template void traverseEdge(S source, T target); - // C++ requires explicit declarations of partial template instantiations. - template void traverseEdge(S source, jsid target); - template void traverseEdge(S source, Value target); /* * Care must be taken changing the mark color from gray to black. The cycle @@ -453,9 +452,9 @@ class HashKeyRef : public BufferableRef // Wrap a GC thing pointer into a new Value or jsid. The type system enforces // that the thing pointer is a wrappable type. template -struct RewrapValueOrId {}; +struct RewrapTaggedPointer{}; #define DECLARE_REWRAP(S, T, method, prefix) \ - template <> struct RewrapValueOrId { \ + template <> struct RewrapTaggedPointer { \ static S wrap(T thing) { return method ( prefix thing ); } \ } DECLARE_REWRAP(JS::Value, JSObject*, JS::ObjectOrNullValue, ); @@ -463,12 +462,17 @@ DECLARE_REWRAP(JS::Value, JSString*, JS::StringValue, ); DECLARE_REWRAP(JS::Value, JS::Symbol*, JS::SymbolValue, ); DECLARE_REWRAP(jsid, JSString*, NON_INTEGER_ATOM_TO_JSID, (JSAtom*)); DECLARE_REWRAP(jsid, JS::Symbol*, SYMBOL_TO_JSID, ); +DECLARE_REWRAP(js::TaggedProto, JSObject*, js::TaggedProto, ); } /* namespace gc */ bool UnmarkGrayShapeRecursively(Shape* shape); +template +void +CheckTracedThing(JSTracer* trc, T* thing); + template void CheckTracedThing(JSTracer* trc, T thing); diff --git a/js/src/gc/Nursery.h b/js/src/gc/Nursery.h index de69f974ac..a13468e060 100644 --- a/js/src/gc/Nursery.h +++ b/js/src/gc/Nursery.h @@ -66,6 +66,7 @@ class TenuringTracer : public JSTracer const Nursery& nursery() const { return nursery_; } // Returns true if the pointer was updated. + template void traverse(T** thingp); template void traverse(T* thingp); void insertIntoFixupList(gc::RelocationOverlay* entry); diff --git a/js/src/gc/RootMarking.cpp b/js/src/gc/RootMarking.cpp index a816f032f3..e131fdbfff 100644 --- a/js/src/gc/RootMarking.cpp +++ b/js/src/gc/RootMarking.cpp @@ -157,10 +157,6 @@ AutoGCRooter::trace(JSTracer* trc) return; } - case JSONPARSER: - static_cast(this)->trace(trc); - return; - case CUSTOM: static_cast(this)->trace(trc); return; @@ -427,7 +423,7 @@ BufferGrayRootsTracer::onChild(const JS::GCCellPtr& thing) // objects and scripts. We rely on gray root buffering for this to work, // but we only need to worry about uncollected dead compartments during // incremental GCs (when we do gray root buffering). - DispatchTraceKindTyped(SetMaybeAliveFunctor(), tenured, thing.kind()); + DispatchTyped(SetMaybeAliveFunctor(), thing); if (!zone->gcGrayRoots.append(tenured)) bufferingGrayRootsFailed = true; diff --git a/js/src/gc/Statistics.h b/js/src/gc/Statistics.h index 5e6c1b143e..74adbb0417 100644 --- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -331,46 +331,37 @@ struct Statistics double computeMMU(int64_t resolution) const; }; -struct AutoGCSlice +struct MOZ_RAII AutoGCSlice { AutoGCSlice(Statistics& stats, const ZoneGCStats& zoneStats, JSGCInvocationKind gckind, - SliceBudget budget, JS::gcreason::Reason reason - MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + SliceBudget budget, JS::gcreason::Reason reason) : stats(stats) { - MOZ_GUARD_OBJECT_NOTIFIER_INIT; stats.beginSlice(zoneStats, gckind, budget, reason); } ~AutoGCSlice() { stats.endSlice(); } Statistics& stats; - MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; -struct AutoPhase +struct MOZ_RAII AutoPhase { - AutoPhase(Statistics& stats, Phase phase - MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + AutoPhase(Statistics& stats, Phase phase) : stats(stats), task(nullptr), phase(phase), enabled(true) { - MOZ_GUARD_OBJECT_NOTIFIER_INIT; stats.beginPhase(phase); } - AutoPhase(Statistics& stats, bool condition, Phase phase - MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + AutoPhase(Statistics& stats, bool condition, Phase phase) : stats(stats), task(nullptr), phase(phase), enabled(condition) { - MOZ_GUARD_OBJECT_NOTIFIER_INIT; if (enabled) stats.beginPhase(phase); } - AutoPhase(Statistics& stats, const GCParallelTask& task, Phase phase - MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + AutoPhase(Statistics& stats, const GCParallelTask& task, Phase phase) : stats(stats), task(&task), phase(phase), enabled(true) { - MOZ_GUARD_OBJECT_NOTIFIER_INIT; if (enabled) stats.beginPhase(phase); } @@ -388,16 +379,13 @@ struct AutoPhase const GCParallelTask* task; Phase phase; bool enabled; - MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; -struct AutoSCC +struct MOZ_RAII AutoSCC { - AutoSCC(Statistics& stats, unsigned scc - MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + AutoSCC(Statistics& stats, unsigned scc) : stats(stats), scc(scc) { - MOZ_GUARD_OBJECT_NOTIFIER_INIT; start = stats.beginSCC(); } ~AutoSCC() { @@ -407,7 +395,6 @@ struct AutoSCC Statistics& stats; unsigned scc; int64_t start; - MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; const char* ExplainInvocationKind(JSGCInvocationKind gckind); diff --git a/js/src/gc/Tracer.cpp b/js/src/gc/Tracer.cpp index 6a6f1659d8..68be97b6cd 100644 --- a/js/src/gc/Tracer.cpp +++ b/js/src/gc/Tracer.cpp @@ -59,7 +59,7 @@ JS_FOR_EACH_TRACEKIND(INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS); template struct DoCallbackFunctor : public IdentityDefaultAdaptor { template S operator()(T* t, JS::CallbackTracer* trc, const char* name) { - return js::gc::RewrapValueOrId::wrap(DoCallback(trc, &t, name)); + return js::gc::RewrapTaggedPointer::wrap(DoCallback(trc, &t, name)); } }; @@ -67,7 +67,7 @@ template <> Value DoCallback(JS::CallbackTracer* trc, Value* vp, const char* name) { - *vp = DispatchValueTyped(DoCallbackFunctor(), *vp, trc, name); + *vp = DispatchTyped(DoCallbackFunctor(), *vp, trc, name); return *vp; } @@ -75,10 +75,18 @@ template <> jsid DoCallback(JS::CallbackTracer* trc, jsid* idp, const char* name) { - *idp = DispatchIdTyped(DoCallbackFunctor(), *idp, trc, name); + *idp = DispatchTyped(DoCallbackFunctor(), *idp, trc, name); return *idp; } +template <> +TaggedProto +DoCallback(JS::CallbackTracer* trc, TaggedProto* protop, const char* name) +{ + *protop = DispatchTyped(DoCallbackFunctor(), *protop, trc, name); + return *protop; +} + void JS::CallbackTracer::getTracingEdgeName(char* buffer, size_t bufferSize) { diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index 0dc96e0725..8369637e1f 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -518,7 +518,7 @@ FinishAllOffThreadCompilations(JSCompartment* comp) } } -class AutoLazyLinkExitFrame +class MOZ_RAII AutoLazyLinkExitFrame { JitActivation* jitActivation_; MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER diff --git a/js/src/jit/IonCode.h b/js/src/jit/IonCode.h index f777c8d9f6..78766691fd 100644 --- a/js/src/jit/IonCode.h +++ b/js/src/jit/IonCode.h @@ -776,7 +776,17 @@ IsMarked(const jit::VMFunction*) // instances with no associated compartment. namespace JS { namespace ubi { -template<> struct Concrete : TracerConcrete { }; +template<> +struct Concrete : TracerConcrete { + CoarseType coarseType() const final { return CoarseType::Script; } + + protected: + explicit Concrete(js::jit::JitCode *ptr) : TracerConcrete(ptr) { } + + public: + static void construct(void *storage, js::jit::JitCode *ptr) { new (storage) Concrete(ptr); } +}; + } // namespace ubi } // namespace JS diff --git a/js/src/jit/JitSpewer.cpp b/js/src/jit/JitSpewer.cpp index 3797f26774..79be437566 100644 --- a/js/src/jit/JitSpewer.cpp +++ b/js/src/jit/JitSpewer.cpp @@ -75,7 +75,7 @@ class IonSpewer } }; -class AutoLockIonSpewerOutput +class MOZ_RAII AutoLockIonSpewerOutput { private: MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER diff --git a/js/src/jsapi-tests/testUbiNode.cpp b/js/src/jsapi-tests/testUbiNode.cpp index d99878bcac..8d3c8aba97 100644 --- a/js/src/jsapi-tests/testUbiNode.cpp +++ b/js/src/jsapi-tests/testUbiNode.cpp @@ -196,3 +196,31 @@ BEGIN_TEST(test_ubiStackFrame) return true; } END_TEST(test_ubiStackFrame) + +BEGIN_TEST(test_ubiCoarseType) +{ + // Test that our explicit coarseType() overrides work as expected. + + JSObject* obj = nullptr; + CHECK(JS::ubi::Node(obj).coarseType() == JS::ubi::CoarseType::Object); + + JSScript* script = nullptr; + CHECK(JS::ubi::Node(script).coarseType() == JS::ubi::CoarseType::Script); + + js::LazyScript* lazyScript = nullptr; + CHECK(JS::ubi::Node(lazyScript).coarseType() == JS::ubi::CoarseType::Script); + + js::jit::JitCode* jitCode = nullptr; + CHECK(JS::ubi::Node(jitCode).coarseType() == JS::ubi::CoarseType::Script); + + JSString* str = nullptr; + CHECK(JS::ubi::Node(str).coarseType() == JS::ubi::CoarseType::String); + + // Test that the default when coarseType() is not overridden is Other. + + JS::Symbol* sym = nullptr; + CHECK(JS::ubi::Node(sym).coarseType() == JS::ubi::CoarseType::Other); + + return true; +} +END_TEST(test_ubiCoarseType) diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 3219f856bb..48fc232316 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -67,7 +67,7 @@ class JS_PUBLIC_API(AutoCheckRequestDepth) /* AutoValueArray roots an internal fixed-size array of Values. */ template -class AutoValueArray : public AutoGCRooter +class MOZ_RAII AutoValueArray : public AutoGCRooter { const size_t length_; Value elements_[N]; @@ -99,7 +99,7 @@ class AutoValueArray : public AutoGCRooter }; template -class AutoVectorRooterBase : protected AutoGCRooter +class MOZ_RAII AutoVectorRooterBase : protected AutoGCRooter { typedef js::Vector VectorImpl; VectorImpl vector; @@ -196,7 +196,7 @@ class AutoVectorRooterBase : protected AutoGCRooter }; template -class MOZ_STACK_CLASS AutoVectorRooter : public AutoVectorRooterBase +class MOZ_RAII AutoVectorRooter : public AutoVectorRooterBase { public: explicit AutoVectorRooter(JSContext* cx @@ -226,7 +226,7 @@ using IdVector = js::TraceableVector; using ScriptVector = js::TraceableVector; template -class AutoHashMapRooter : protected AutoGCRooter +class MOZ_RAII AutoHashMapRooter : protected AutoGCRooter { private: typedef js::HashMap HashMapImpl; @@ -346,7 +346,7 @@ class AutoHashMapRooter : protected AutoGCRooter }; template -class AutoHashSetRooter : protected AutoGCRooter +class MOZ_RAII AutoHashSetRooter : protected AutoGCRooter { private: typedef js::HashSet HashSetImpl; @@ -453,7 +453,7 @@ class AutoHashSetRooter : protected AutoGCRooter /* * Custom rooting behavior for internal and external clients. */ -class JS_PUBLIC_API(CustomAutoRooter) : private AutoGCRooter +class MOZ_RAII JS_PUBLIC_API(CustomAutoRooter) : private AutoGCRooter { public: template @@ -1063,7 +1063,7 @@ AssertHeapIsIdle(JSContext* cx); } /* namespace js */ -class JSAutoRequest +class MOZ_RAII JSAutoRequest { public: explicit JSAutoRequest(JSContext* cx @@ -1402,7 +1402,7 @@ JS_RefreshCrossCompartmentWrappers(JSContext* cx, JS::Handle obj); * the corresponding JS_LeaveCompartment call. */ -class JS_PUBLIC_API(JSAutoCompartment) +class MOZ_RAII JS_PUBLIC_API(JSAutoCompartment) { JSContext* cx_; JSCompartment* oldCompartment_; @@ -1416,7 +1416,7 @@ class JS_PUBLIC_API(JSAutoCompartment) MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; -class JS_PUBLIC_API(JSAutoNullableCompartment) +class MOZ_RAII JS_PUBLIC_API(JSAutoNullableCompartment) { JSContext* cx_; JSCompartment* oldCompartment_; @@ -2456,20 +2456,20 @@ namespace JS { template class PropertyDescriptorOperations { - const JSPropertyDescriptor* desc() const { return static_cast(this)->extract(); } + const JSPropertyDescriptor& desc() const { return static_cast(this)->get(); } bool has(unsigned bit) const { MOZ_ASSERT(bit != 0); MOZ_ASSERT((bit & (bit - 1)) == 0); // only a single bit - return (desc()->attrs & bit) != 0; + return (desc().attrs & bit) != 0; } bool hasAny(unsigned bits) const { - return (desc()->attrs & bits) != 0; + return (desc().attrs & bits) != 0; } bool hasAll(unsigned bits) const { - return (desc()->attrs & bits) == bits; + return (desc().attrs & bits) == bits; } // Non-API attributes bit used internally for arguments objects. @@ -2480,7 +2480,7 @@ class PropertyDescriptorOperations // descriptors. It's complicated. bool isAccessorDescriptor() const { return hasAny(JSPROP_GETTER | JSPROP_SETTER); } bool isGenericDescriptor() const { - return (desc()->attrs& + return (desc().attrs& (JSPROP_GETTER | JSPROP_SETTER | JSPROP_IGNORE_READONLY | JSPROP_IGNORE_VALUE)) == (JSPROP_IGNORE_READONLY | JSPROP_IGNORE_VALUE); } @@ -2494,7 +2494,7 @@ class PropertyDescriptorOperations bool hasValue() const { return !isAccessorDescriptor() && !has(JSPROP_IGNORE_VALUE); } JS::HandleValue value() const { - return JS::HandleValue::fromMarkedLocation(&desc()->value); + return JS::HandleValue::fromMarkedLocation(&desc().value); } bool hasWritable() const { return !isAccessorDescriptor() && !has(JSPROP_IGNORE_READONLY); } @@ -2504,24 +2504,24 @@ class PropertyDescriptorOperations JS::HandleObject getterObject() const { MOZ_ASSERT(hasGetterObject()); return JS::HandleObject::fromMarkedLocation( - reinterpret_cast(&desc()->getter)); + reinterpret_cast(&desc().getter)); } bool hasSetterObject() const { return has(JSPROP_SETTER); } JS::HandleObject setterObject() const { MOZ_ASSERT(hasSetterObject()); return JS::HandleObject::fromMarkedLocation( - reinterpret_cast(&desc()->setter)); + reinterpret_cast(&desc().setter)); } - bool hasGetterOrSetter() const { return desc()->getter || desc()->setter; } + bool hasGetterOrSetter() const { return desc().getter || desc().setter; } bool isShared() const { return has(JSPROP_SHARED); } JS::HandleObject object() const { - return JS::HandleObject::fromMarkedLocation(&desc()->obj); + return JS::HandleObject::fromMarkedLocation(&desc().obj); } - unsigned attributes() const { return desc()->attrs; } - JSGetterOp getter() const { return desc()->getter; } - JSSetterOp setter() const { return desc()->setter; } + unsigned attributes() const { return desc().attrs; } + JSGetterOp getter() const { return desc().getter; } + JSSetterOp setter() const { return desc().setter; } void assertValid() const { #ifdef DEBUG @@ -2588,7 +2588,7 @@ class PropertyDescriptorOperations template class MutablePropertyDescriptorOperations : public PropertyDescriptorOperations { - JSPropertyDescriptor * desc() { return static_cast(this)->extractMutable(); } + JSPropertyDescriptor& desc() { return static_cast(this)->get(); } public: void clear() { @@ -2634,63 +2634,63 @@ class MutablePropertyDescriptorOperations : public PropertyDescriptorOperations< } JS::MutableHandleObject object() { - return JS::MutableHandleObject::fromMarkedLocation(&desc()->obj); + return JS::MutableHandleObject::fromMarkedLocation(&desc().obj); } - unsigned& attributesRef() { return desc()->attrs; } - JSGetterOp& getter() { return desc()->getter; } - JSSetterOp& setter() { return desc()->setter; } + unsigned& attributesRef() { return desc().attrs; } + JSGetterOp& getter() { return desc().getter; } + JSSetterOp& setter() { return desc().setter; } JS::MutableHandleValue value() { - return JS::MutableHandleValue::fromMarkedLocation(&desc()->value); + return JS::MutableHandleValue::fromMarkedLocation(&desc().value); } void setValue(JS::HandleValue v) { - MOZ_ASSERT(!(desc()->attrs & (JSPROP_GETTER | JSPROP_SETTER))); + MOZ_ASSERT(!(desc().attrs & (JSPROP_GETTER | JSPROP_SETTER))); attributesRef() &= ~JSPROP_IGNORE_VALUE; value().set(v); } void setConfigurable(bool configurable) { - setAttributes((desc()->attrs & ~(JSPROP_IGNORE_PERMANENT | JSPROP_PERMANENT)) | + setAttributes((desc().attrs & ~(JSPROP_IGNORE_PERMANENT | JSPROP_PERMANENT)) | (configurable ? 0 : JSPROP_PERMANENT)); } void setEnumerable(bool enumerable) { - setAttributes((desc()->attrs & ~(JSPROP_IGNORE_ENUMERATE | JSPROP_ENUMERATE)) | + setAttributes((desc().attrs & ~(JSPROP_IGNORE_ENUMERATE | JSPROP_ENUMERATE)) | (enumerable ? JSPROP_ENUMERATE : 0)); } void setWritable(bool writable) { - MOZ_ASSERT(!(desc()->attrs & (JSPROP_GETTER | JSPROP_SETTER))); - setAttributes((desc()->attrs & ~(JSPROP_IGNORE_READONLY | JSPROP_READONLY)) | + MOZ_ASSERT(!(desc().attrs & (JSPROP_GETTER | JSPROP_SETTER))); + setAttributes((desc().attrs & ~(JSPROP_IGNORE_READONLY | JSPROP_READONLY)) | (writable ? 0 : JSPROP_READONLY)); } - void setAttributes(unsigned attrs) { desc()->attrs = attrs; } + void setAttributes(unsigned attrs) { desc().attrs = attrs; } void setGetter(JSGetterOp op) { MOZ_ASSERT(op != JS_PropertyStub); - desc()->getter = op; + desc().getter = op; } void setSetter(JSSetterOp op) { MOZ_ASSERT(op != JS_StrictPropertyStub); - desc()->setter = op; + desc().setter = op; } void setGetterObject(JSObject* obj) { - desc()->getter = reinterpret_cast(obj); - desc()->attrs &= ~(JSPROP_IGNORE_VALUE | JSPROP_IGNORE_READONLY | JSPROP_READONLY); - desc()->attrs |= JSPROP_GETTER | JSPROP_SHARED; + desc().getter = reinterpret_cast(obj); + desc().attrs &= ~(JSPROP_IGNORE_VALUE | JSPROP_IGNORE_READONLY | JSPROP_READONLY); + desc().attrs |= JSPROP_GETTER | JSPROP_SHARED; } void setSetterObject(JSObject* obj) { - desc()->setter = reinterpret_cast(obj); - desc()->attrs &= ~(JSPROP_IGNORE_VALUE | JSPROP_IGNORE_READONLY | JSPROP_READONLY); - desc()->attrs |= JSPROP_SETTER | JSPROP_SHARED; + desc().setter = reinterpret_cast(obj); + desc().attrs &= ~(JSPROP_IGNORE_VALUE | JSPROP_IGNORE_READONLY | JSPROP_READONLY); + desc().attrs |= JSPROP_SETTER | JSPROP_SHARED; } JS::MutableHandleObject getterObject() { MOZ_ASSERT(this->hasGetterObject()); return JS::MutableHandleObject::fromMarkedLocation( - reinterpret_cast(&desc()->getter)); + reinterpret_cast(&desc().getter)); } JS::MutableHandleObject setterObject() { MOZ_ASSERT(this->hasSetterObject()); return JS::MutableHandleObject::fromMarkedLocation( - reinterpret_cast(&desc()->setter)); + reinterpret_cast(&desc().setter)); } }; @@ -2701,40 +2701,17 @@ namespace js { template <> class RootedBase : public JS::MutablePropertyDescriptorOperations> -{ - friend class JS::PropertyDescriptorOperations>; - friend class JS::MutablePropertyDescriptorOperations>; - const JSPropertyDescriptor* extract() const { - return static_cast*>(this)->address(); - } - JSPropertyDescriptor* extractMutable() { - return static_cast*>(this)->address(); - } -}; +{}; template <> class HandleBase : public JS::PropertyDescriptorOperations> -{ - friend class JS::PropertyDescriptorOperations>; - const JSPropertyDescriptor* extract() const { - return static_cast*>(this)->address(); - } -}; +{}; template <> class MutableHandleBase : public JS::MutablePropertyDescriptorOperations> -{ - friend class JS::PropertyDescriptorOperations>; - friend class JS::MutablePropertyDescriptorOperations>; - const JSPropertyDescriptor* extract() const { - return static_cast*>(this)->address(); - } - JSPropertyDescriptor* extractMutable() { - return static_cast*>(this)->address(); - } -}; +{}; } /* namespace js */ @@ -4275,7 +4252,7 @@ JS_GetStringEncodingLength(JSContext* cx, JSString* str); JS_PUBLIC_API(size_t) JS_EncodeStringToBuffer(JSContext* cx, JSString* str, char* buffer, size_t length); -class JSAutoByteString +class MOZ_RAII JSAutoByteString { public: JSAutoByteString(JSContext* cx, JSString* str @@ -5104,7 +5081,7 @@ HideScriptedCaller(JSContext* cx); extern JS_PUBLIC_API(void) UnhideScriptedCaller(JSContext* cx); -class AutoHideScriptedCaller +class MOZ_RAII AutoHideScriptedCaller { public: explicit AutoHideScriptedCaller(JSContext* cx diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index e9effb3354..d31f4d66e6 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -33,7 +33,7 @@ typedef HashSet ObjectSet; typedef HashSet ShapeSet; /* Detects cycles when traversing an object graph. */ -class AutoCycleDetector +class MOZ_RAII AutoCycleDetector { Generation hashsetGenerationAtInit; JSContext* cx; @@ -466,7 +466,7 @@ struct JSContext : public js::ExclusiveContext, namespace js { -struct AutoResolving { +struct MOZ_RAII AutoResolving { public: enum Kind { LOOKUP, @@ -670,7 +670,7 @@ using ShapeVector = js::TraceableVector; using StringVector = js::TraceableVector; /* AutoArrayRooter roots an external array of Values. */ -class AutoArrayRooter : private JS::AutoGCRooter +class MOZ_RAII AutoArrayRooter : private JS::AutoGCRooter { public: AutoArrayRooter(JSContext* cx, size_t len, Value* vec @@ -749,7 +749,7 @@ class AutoAssertNoException /* Exposed intrinsics for the JITs. */ bool intrinsic_IsSuspendedStarGenerator(JSContext* cx, unsigned argc, Value* vp); -class AutoLockForExclusiveAccess +class MOZ_RAII AutoLockForExclusiveAccess { JSRuntime* runtime; diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index 5d8081767c..e2370aeb8b 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -185,7 +185,7 @@ using NewObjectMetadataState = mozilla::Variant; -class MOZ_STACK_CLASS AutoSetNewObjectMetadata : private JS::CustomAutoRooter +class MOZ_RAII AutoSetNewObjectMetadata : private JS::CustomAutoRooter { MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER; @@ -769,7 +769,7 @@ ExclusiveContext::global() const return Handle::fromMarkedLocation(compartment_->global_.unsafeGet()); } -class AssertCompartmentUnchanged +class MOZ_RAII AssertCompartmentUnchanged { public: explicit AssertCompartmentUnchanged(JSContext* cx @@ -867,7 +867,7 @@ struct WrapperValue Value value; }; -class AutoWrapperVector : public JS::AutoVectorRooterBase +class MOZ_RAII AutoWrapperVector : public JS::AutoVectorRooterBase { public: explicit AutoWrapperVector(JSContext* cx @@ -880,7 +880,7 @@ class AutoWrapperVector : public JS::AutoVectorRooterBase MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; -class AutoWrapperRooter : private JS::AutoGCRooter { +class MOZ_RAII AutoWrapperRooter : private JS::AutoGCRooter { public: AutoWrapperRooter(JSContext* cx, WrapperValue v MOZ_GUARD_OBJECT_NOTIFIER_PARAM) diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index aa2b538ab0..b1de1add95 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -2668,7 +2668,7 @@ typedef void JS_FRIEND_API(void) SetCTypesActivityCallback(JSRuntime* rt, CTypesActivityCallback cb); -class JS_FRIEND_API(AutoCTypesActivityCallback) { +class MOZ_RAII JS_FRIEND_API(AutoCTypesActivityCallback) { private: JSContext* cx; CTypesActivityCallback callback; diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 18e4b18bab..cbb36587db 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -3788,7 +3788,7 @@ CompartmentCheckTracer::onChild(const JS::GCCellPtr& thing) { TenuredCell* tenured = TenuredCell::fromPointer(thing.asCell()); - JSCompartment* comp = DispatchTraceKindTyped(MaybeCompartmentFunctor(), tenured, thing.kind()); + JSCompartment* comp = DispatchTyped(MaybeCompartmentFunctor(), thing); if (comp && compartment) { MOZ_ASSERT(comp == compartment || runtime()->isAtomsCompartment(comp) || (srcKind == JS::TraceKind::Object && @@ -6971,11 +6971,9 @@ js::UninlinedIsInsideNursery(const gc::Cell* cell) } #ifdef DEBUG -AutoDisableProxyCheck::AutoDisableProxyCheck(JSRuntime* rt - MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) +AutoDisableProxyCheck::AutoDisableProxyCheck(JSRuntime* rt) : gc(rt->gc) { - MOZ_GUARD_OBJECT_NOTIFIER_INIT; gc.disableStrictProxyChecking(); } @@ -7328,7 +7326,7 @@ JS::IncrementalReferenceBarrier(GCCellPtr thing) if (!thing) return; - DispatchTraceKindTyped(IncrementalReferenceBarrierFunctor(), thing.asCell(), thing.kind()); + DispatchTyped(IncrementalReferenceBarrierFunctor(), thing); } JS_PUBLIC_API(void) diff --git a/js/src/jsgc.h b/js/src/jsgc.h index ce7e931fa1..40c3cb7249 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -1189,7 +1189,7 @@ struct IsForwardedFunctor : public BoolDefaultAdaptor { inline bool IsForwarded(const JS::Value& value) { - return DispatchValueTyped(IsForwardedFunctor(), value); + return DispatchTyped(IsForwardedFunctor(), value); } template @@ -1203,14 +1203,14 @@ Forwarded(T* t) struct ForwardedFunctor : public IdentityDefaultAdaptor { template inline Value operator()(T* t) { - return js::gc::RewrapValueOrId::wrap(Forwarded(t)); + return js::gc::RewrapTaggedPointer::wrap(Forwarded(t)); } }; inline Value Forwarded(const JS::Value& value) { - return DispatchValueTyped(ForwardedFunctor(), value); + return DispatchTyped(ForwardedFunctor(), value); } template @@ -1246,7 +1246,7 @@ struct CheckValueAfterMovingGCFunctor : public VoidDefaultAdaptor { inline void CheckValueAfterMovingGC(const JS::Value& value) { - DispatchValueTyped(CheckValueAfterMovingGCFunctor(), value); + DispatchTyped(CheckValueAfterMovingGCFunctor(), value); } #endif // JSGC_HASH_TABLE_CHECKS @@ -1300,7 +1300,7 @@ MaybeVerifyBarriers(JSContext* cx, bool always = false) * read the comment in vm/Runtime.h above |suppressGC| and take all appropriate * precautions before instantiating this class. */ -class AutoSuppressGC +class MOZ_RAII AutoSuppressGC { int32_t& suppressGC_; @@ -1350,24 +1350,22 @@ NewMemoryStatisticsObject(JSContext* cx); #ifdef DEBUG /* Use this to avoid assertions when manipulating the wrapper map. */ -class AutoDisableProxyCheck +class MOZ_RAII AutoDisableProxyCheck { - MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER; gc::GCRuntime& gc; public: - explicit AutoDisableProxyCheck(JSRuntime* rt - MOZ_GUARD_OBJECT_NOTIFIER_PARAM); + explicit AutoDisableProxyCheck(JSRuntime* rt); ~AutoDisableProxyCheck(); }; #else -struct AutoDisableProxyCheck +struct MOZ_RAII AutoDisableProxyCheck { explicit AutoDisableProxyCheck(JSRuntime* rt) {} }; #endif -struct AutoDisableCompactingGC +struct MOZ_RAII AutoDisableCompactingGC { explicit AutoDisableCompactingGC(JSRuntime* rt); ~AutoDisableCompactingGC(); diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index a584b9df04..cead38531c 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -3672,7 +3672,7 @@ JSObject::sizeOfIncludingThisInNursery() const return size; } -size_t +JS::ubi::Node::Size JS::ubi::Concrete::size(mozilla::MallocSizeOf mallocSizeOf) const { JSObject& obj = get(); diff --git a/js/src/jsobj.h b/js/src/jsobj.h index 8cb19a962d..7d0e7ebd35 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -618,14 +618,16 @@ struct JSObject_Slots16 : JSObject { void* data[3]; js::Value fslots[16]; }; /* static */ MOZ_ALWAYS_INLINE void JSObject::readBarrier(JSObject* obj) { - if (!isNullLike(obj) && obj->isTenured()) + MOZ_ASSERT_IF(obj, !isNullLike(obj)); + if (obj && obj->isTenured()) obj->asTenured().readBarrier(&obj->asTenured()); } /* static */ MOZ_ALWAYS_INLINE void JSObject::writeBarrierPre(JSObject* obj) { - if (!isNullLike(obj) && obj->isTenured()) + MOZ_ASSERT_IF(obj, !isNullLike(obj)); + if (obj && obj->isTenured()) obj->asTenured().writeBarrierPre(&obj->asTenured()); } @@ -633,13 +635,15 @@ JSObject::writeBarrierPre(JSObject* obj) JSObject::writeBarrierPost(void* cellp, JSObject* prev, JSObject* next) { MOZ_ASSERT(cellp); + MOZ_ASSERT_IF(next, !IsNullTaggedPointer(next)); + MOZ_ASSERT_IF(prev, !IsNullTaggedPointer(prev)); // If the target needs an entry, add it. js::gc::StoreBuffer* buffer; - if (!IsNullTaggedPointer(next) && (buffer = next->storeBuffer())) { + if (next && (buffer = next->storeBuffer())) { // If we know that the prev has already inserted an entry, we can skip // doing the lookup to add the new entry. - if (!IsNullTaggedPointer(prev) && prev->storeBuffer()) { + if (prev && prev->storeBuffer()) { buffer->assertHasCellEdge(static_cast(cellp)); return; } @@ -648,7 +652,7 @@ JSObject::writeBarrierPost(void* cellp, JSObject* prev, JSObject* next) } // Remove the prev entry if the new value does not need it. - if (!IsNullTaggedPointer(prev) && (buffer = prev->storeBuffer())) + if (prev && (buffer = prev->storeBuffer())) buffer->unputCellFromAnyThread(static_cast(cellp)); } diff --git a/js/src/json.cpp b/js/src/json.cpp index 0f54e80241..205a9fc120 100644 --- a/js/src/json.cpp +++ b/js/src/json.cpp @@ -822,7 +822,7 @@ js::ParseJSONWithReviver(JSContext* cx, const mozilla::Range chars, MutableHandleValue vp) { /* 15.12.2 steps 2-3. */ - JSONParser parser(cx, chars); + Rooted> parser(cx, JSONParser(cx, chars)); if (!parser.parse(vp)) return false; diff --git a/js/src/jspubtd.h b/js/src/jspubtd.h index e896fff0f6..7b9e2e2bfd 100644 --- a/js/src/jspubtd.h +++ b/js/src/jspubtd.h @@ -226,7 +226,6 @@ class JS_PUBLIC_API(AutoGCRooter) IONMASM = -19, /* js::jit::MacroAssembler */ WRAPVECTOR = -20, /* js::AutoWrapperVector */ WRAPPER = -21, /* js::AutoWrapperRooter */ - JSONPARSER = -25, /* js::JSONParser */ CUSTOM = -26 /* js::CustomAutoRooter */ }; diff --git a/js/src/jsscript.h b/js/src/jsscript.h index cf7c20e6af..54a067b79d 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -359,7 +359,7 @@ static_assert(sizeof(Bindings) % js::gc::CellSize == 0, template class BindingsOperations { - const Bindings& bindings() const { return static_cast(this)->extract(); } + const Bindings& bindings() const { return static_cast(this)->get(); } public: // Direct data access to the underlying bindings. @@ -423,7 +423,7 @@ class BindingsOperations template class MutableBindingsOperations : public BindingsOperations { - Bindings& bindings() { return static_cast(this)->extractMutable(); } + Bindings& bindings() { return static_cast(this)->get(); } public: void setCallObjShape(HandleShape shape) { bindings().callObjShape_ = shape; } @@ -448,27 +448,12 @@ class MutableBindingsOperations : public BindingsOperations template <> class HandleBase : public BindingsOperations> -{ - friend class BindingsOperations>; - const Bindings& extract() const { - return static_cast*>(this)->get(); - } -}; +{}; template <> class MutableHandleBase : public MutableBindingsOperations> -{ - friend class BindingsOperations>; - const Bindings& extract() const { - return static_cast*>(this)->get(); - } - - friend class MutableBindingsOperations>; - Bindings& extractMutable() { - return static_cast*>(this)->get(); - } -}; +{}; class ScriptCounts { @@ -2479,7 +2464,16 @@ CloneGlobalScript(JSContext* cx, Handle enclosingScope, HandleScri // with no associated compartment. namespace JS { namespace ubi { -template<> struct Concrete : TracerConcrete { }; +template<> +struct Concrete : TracerConcrete { + CoarseType coarseType() const final { return CoarseType::Script; } + + protected: + explicit Concrete(js::LazyScript *ptr) : TracerConcrete(ptr) { } + + public: + static void construct(void *storage, js::LazyScript *ptr) { new (storage) Concrete(ptr); } +}; } // namespace ubi } // namespace JS diff --git a/js/src/jsutil.h b/js/src/jsutil.h index 3e11534e45..e28cb39e08 100644 --- a/js/src/jsutil.h +++ b/js/src/jsutil.h @@ -165,7 +165,7 @@ ImplicitCast(U& u) } template -class AutoScopedAssign +class MOZ_RAII AutoScopedAssign { public: AutoScopedAssign(T* addr, const T& value diff --git a/js/src/moz.build b/js/src/moz.build index 8a34941c02..88603fb761 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -130,6 +130,7 @@ EXPORTS.js += [ '../public/TrackedOptimizationInfo.h', '../public/TypeDecls.h', '../public/UbiNode.h', + '../public/UbiNodeCensus.h', '../public/UbiNodeTraverse.h', '../public/Utility.h', '../public/Value.h', @@ -322,10 +323,12 @@ UNIFIED_SOURCES += [ 'vm/StringBuffer.cpp', 'vm/StructuredClone.cpp', 'vm/Symbol.cpp', + 'vm/TaggedProto.cpp', 'vm/Time.cpp', 'vm/TypedArrayObject.cpp', 'vm/TypeInference.cpp', 'vm/UbiNode.cpp', + 'vm/UbiNodeCensus.cpp', 'vm/UnboxedObject.cpp', 'vm/Unicode.cpp', 'vm/Value.cpp', diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 4abf85184f..8a9279c055 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -3422,7 +3422,7 @@ runOffThreadScript(JSContext* cx, unsigned argc, Value* vp) return JS_ExecuteScript(cx, script, args.rval()); } -struct FreeOnReturn +struct MOZ_RAII FreeOnReturn { JSContext* cx; const char* ptr; diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index ec25fae328..7ea941fdac 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -1827,7 +1827,7 @@ Debugger::slowPathPromiseHook(JSContext* cx, Hook hook, HandleObject promise) /*** Debugger code invalidation for observing execution ******************************************/ -class MOZ_STACK_CLASS ExecutionObservableCompartments : public Debugger::ExecutionObservableSet +class MOZ_RAII ExecutionObservableCompartments : public Debugger::ExecutionObservableSet { HashSet compartments_; HashSet zones_; @@ -1864,7 +1864,7 @@ class MOZ_STACK_CLASS ExecutionObservableCompartments : public Debugger::Executi // represents the stack frames that need to be bailed out or marked as // debuggees, and the scripts that need to be recompiled, taking inlining into // account. -class MOZ_STACK_CLASS ExecutionObservableFrame : public Debugger::ExecutionObservableSet +class MOZ_RAII ExecutionObservableFrame : public Debugger::ExecutionObservableSet { AbstractFramePtr frame_; @@ -1925,7 +1925,7 @@ class MOZ_STACK_CLASS ExecutionObservableFrame : public Debugger::ExecutionObser MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; -class MOZ_STACK_CLASS ExecutionObservableScript : public Debugger::ExecutionObservableSet +class MOZ_RAII ExecutionObservableScript : public Debugger::ExecutionObservableSet { RootedScript script_; diff --git a/js/src/vm/DebuggerMemory.cpp b/js/src/vm/DebuggerMemory.cpp index 117455ef6c..bbf3a666f1 100644 --- a/js/src/vm/DebuggerMemory.cpp +++ b/js/src/vm/DebuggerMemory.cpp @@ -22,7 +22,7 @@ #include "js/Debug.h" #include "js/TracingAPI.h" #include "js/UbiNode.h" -#include "js/UbiNodeTraverse.h" +#include "js/UbiNodeCensus.h" #include "js/Utility.h" #include "vm/Debugger.h" #include "vm/GlobalObject.h" @@ -492,60 +492,10 @@ JS::dbg::GetDebuggerMallocSizeOf(JSRuntime* rt) return rt->debuggerMallocSizeOf; } -namespace js { -namespace dbg { +using JS::ubi::Census; +using JS::ubi::CountTypePtr; +using JS::ubi::CountBasePtr; -// We want to summarize the results of a census with counts broken down -// according to criteria selected by the code that is requesting the census. For -// example, the following breakdown might give an interesting overview of the -// heap: -// -// - all nodes -// - objects -// - objects with a specific [[Class]] * -// - strings -// - scripts -// - all other Node types -// - nodes with a specific ubi::Node::typeName * -// -// Obviously, the parts of this tree marked with * represent many separate -// counts, depending on how many distinct [[Class]] values and ubi::Node type -// names we encounter. -// -// We parse the 'breakdown' argument to takeCensus and build a tree of CountType -// nodes of the sort shown above. For example, for the breakdown shown in the -// Debugger.Memory.prototype.takeCensus, documentation: -// -// { -// by: "coarseType", -// objects: { by: "objectClass" }, -// other: { by: "internalType" } -// } -// -// we would build the following tree of CountType subclasses: -// -// ByCoarseType -// objects: ByObjectClass -// each class: SimpleCount -// scripts: SimpleCount -// strings: SimpleCount -// other: ByUbinodeType -// each type: SimpleCount -// -// The interior nodes are all breakdown types that categorize nodes according to -// one characteristics or another; and the leaf nodes are all SimpleType. -// -// Each CountType has its own concrete C++ type that holds the counts it -// produces. SimpleCount::Count just holds totals. ByObjectClass::Count has a -// hash table whose keys are object class names and whose values are counts of -// some other type (in the example above, SimpleCount). -// -// To keep actual count nodes small, they have no vtable. Instead, each count -// points to its CountType, which knows how to carry out all the operations we -// need on a Count. A CountType can produce new count nodes; process nodes as we -// visit them; build a JS object reporting the results; and destruct count -// nodes. -// // The takeCensus function works in three phases: // // 1) We examine the 'breakdown' property of our 'options' argument, and @@ -557,978 +507,6 @@ namespace dbg { // // 3) We walk the tree of counts and produce JavaScript objects reporting the // accumulated results. - -// Common data for a census traversal, shared across all CountType nodes. -struct Census { - JSContext* const cx; - JS::ZoneSet debuggeeZones; - Zone* atomsZone; - - explicit Census(JSContext* cx) : cx(cx), atomsZone(nullptr) { } - - bool init() { - AutoLockForExclusiveAccess lock(cx); - atomsZone = cx->runtime()->atomsCompartment()->zone(); - return debuggeeZones.init(); - } - - // A 'new' work-alike that behaves like TempAllocPolicy: report OOM on this - // census's context, but don't charge the memory allocated to our context's - // GC pressure counters. - template - T* new_(Args&&... args) MOZ_HEAP_ALLOCATOR { - void* memory = js_malloc(sizeof(T)); - if (MOZ_UNLIKELY(!memory)) { - memory = static_cast(cx->onOutOfMemory(AllocFunction::Malloc, sizeof(T))); - if (!memory) - return nullptr; - } - return new(memory) T(Forward(args)...); - } -}; - -class CountBase; - -struct CountDeleter { - void operator()(CountBase*); -}; - -typedef UniquePtr CountBasePtr; - -// Abstract base class for CountType nodes. -struct CountType { - explicit CountType(Census& census) : census(census) { } - virtual ~CountType() { } - - // Return a fresh node for the count tree that categorizes nodes according - // to this type. Return a nullptr on OOM. - virtual CountBasePtr makeCount() = 0; - - // Trace |count| and all its children, for garbage collection. - virtual void traceCount(CountBase& count, JSTracer* trc) = 0; - - // Destruct a count tree node that this type instance constructed. - virtual void destructCount(CountBase& count) = 0; - - // Implement the 'count' method for counts returned by this CountType - // instance's 'newCount' method. - virtual bool count(CountBase& count, const Node& node) = 0; - - // Implement the 'report' method for counts returned by this CountType - // instance's 'newCount' method. - virtual bool report(CountBase& count, MutableHandleValue report) = 0; - - protected: - Census& census; -}; - -typedef UniquePtr> CountTypePtr; - -// An abstract base class for count tree nodes. -class CountBase { - // In lieu of a vtable, each CountBase points to its type, which - // carries not only the implementations of the CountBase methods, but also - // additional parameters for the type's behavior, as specified in the - // breakdown argument passed to takeCensus. - CountType& type; - - protected: - ~CountBase() { } - - public: - explicit CountBase(CountType& type) : type(type), total_(0) { } - - // Categorize and count |node| as appropriate for this count's type. - bool count(const Node& node) { return type.count(*this, node); } - - // Construct a JavaScript object reporting the counts recorded in this - // count, and store it in |report|. Return true on success, or false on - // failure. - bool report(MutableHandleValue report) { return type.report(*this, report); } - - // Down-cast this CountBase to its true type, based on its type, and run - // its destructor. - void destruct() { return type.destructCount(*this); } - - // Trace this count for garbage collection. - void trace(JSTracer* trc) { type.traceCount(*this, trc); } - - size_t total_; -}; - -class RootedCount : JS::CustomAutoRooter { - CountBasePtr count; - - void trace(JSTracer* trc) override { count->trace(trc); } - - public: - RootedCount(JSContext* cx, CountBasePtr&& count) - : CustomAutoRooter(cx), - count(Move(count)) - { } - CountBase* operator->() const { return count.get(); } - explicit operator bool() const { return count.get(); } - operator CountBasePtr&() { return count; } -}; - -void -CountDeleter::operator()(CountBase* ptr) -{ - if (!ptr) - return; - - // Downcast to our true type and destruct, as guided by our CountType - // pointer. - ptr->destruct(); - js_free(ptr); -} - -// The simplest type: just count everything. -class SimpleCount : public CountType { - - struct Count : CountBase { - size_t totalBytes_; - - explicit Count(SimpleCount& count) - : CountBase(count), - totalBytes_(0) - { } - }; - - UniquePtr label; - bool reportCount : 1; - bool reportBytes : 1; - - public: - SimpleCount(Census& census, - UniquePtr& label, - bool reportCount=true, - bool reportBytes=true) - : CountType(census), - label(Move(label)), - reportCount(reportCount), - reportBytes(reportBytes) - { } - - explicit SimpleCount(Census& census) - : CountType(census), - label(nullptr), - reportCount(true), - reportBytes(true) - { } - - CountBasePtr makeCount() override { - return CountBasePtr(census.new_(*this)); - } - - void traceCount(CountBase& countBase, JSTracer* trc) override { } - - void destructCount(CountBase& countBase) override { - Count& count = static_cast(countBase); - count.~Count(); - } - - bool count(CountBase& countBase, const Node& node) override { - Count& count = static_cast(countBase); - count.total_++; - if (reportBytes) - count.totalBytes_ += node.size(census.cx->runtime()->debuggerMallocSizeOf); - return true; - } - - bool report(CountBase& countBase, MutableHandleValue report) override { - Count& count = static_cast(countBase); - - RootedPlainObject obj(census.cx, NewBuiltinClassInstance(census.cx)); - if (!obj) - return false; - - RootedValue countValue(census.cx, NumberValue(count.total_)); - if (reportCount && !DefineProperty(census.cx, obj, census.cx->names().count, countValue)) - return false; - - RootedValue bytesValue(census.cx, NumberValue(count.totalBytes_)); - if (reportBytes && !DefineProperty(census.cx, obj, census.cx->names().bytes, bytesValue)) - return false; - - if (label) { - JSString* labelString = JS_NewUCStringCopyZ(census.cx, label.get()); - if (!labelString) - return false; - RootedValue labelValue(census.cx, StringValue(labelString)); - if (!DefineProperty(census.cx, obj, census.cx->names().label, labelValue)) - return false; - } - - report.setObject(*obj); - return true; - } -}; - -// A type that categorizes nodes by their JavaScript type --- 'objects', -// 'strings', 'scripts', and 'other' --- and then passes the nodes to child -// types. -// -// Implementation details of scripts like jitted code are counted under -// 'scripts'. -class ByCoarseType : public CountType { - CountTypePtr objects; - CountTypePtr scripts; - CountTypePtr strings; - CountTypePtr other; - - struct Count : CountBase { - Count(CountType& type, - CountBasePtr& objects, - CountBasePtr& scripts, - CountBasePtr& strings, - CountBasePtr& other) - : CountBase(type), - objects(Move(objects)), - scripts(Move(scripts)), - strings(Move(strings)), - other(Move(other)) - { } - - CountBasePtr objects; - CountBasePtr scripts; - CountBasePtr strings; - CountBasePtr other; - }; - - public: - ByCoarseType(Census& census, - CountTypePtr& objects, - CountTypePtr& scripts, - CountTypePtr& strings, - CountTypePtr& other) - : CountType(census), - objects(Move(objects)), - scripts(Move(scripts)), - strings(Move(strings)), - other(Move(other)) - { } - - CountBasePtr makeCount() override { - CountBasePtr objectsCount(objects->makeCount()); - CountBasePtr scriptsCount(scripts->makeCount()); - CountBasePtr stringsCount(strings->makeCount()); - CountBasePtr otherCount(other->makeCount()); - - if (!objectsCount || !scriptsCount || !stringsCount || !otherCount) - return CountBasePtr(nullptr); - - return CountBasePtr(census.new_(*this, - objectsCount, - scriptsCount, - stringsCount, - otherCount)); - } - - void traceCount(CountBase& countBase, JSTracer* trc) override { - Count& count = static_cast(countBase); - count.objects->trace(trc); - count.scripts->trace(trc); - count.strings->trace(trc); - count.other->trace(trc); - } - - void destructCount(CountBase& countBase) override { - Count& count = static_cast(countBase); - count.~Count(); - } - - bool count(CountBase& countBase, const Node& node) override { - Count& count = static_cast(countBase); - count.total_++; - - if (node.is()) - return count.objects->count(node); - if (node.is() || node.is() || node.is()) - return count.scripts->count(node); - if (node.is()) - return count.strings->count(node); - return count.other->count(node); - } - - bool report(CountBase& countBase, MutableHandleValue report) override { - Count& count = static_cast(countBase); - JSContext* cx = census.cx; - - RootedPlainObject obj(cx, NewBuiltinClassInstance(cx)); - if (!obj) - return false; - - RootedValue objectsReport(cx); - if (!count.objects->report(&objectsReport) || - !DefineProperty(cx, obj, cx->names().objects, objectsReport)) - return false; - - RootedValue scriptsReport(cx); - if (!count.scripts->report(&scriptsReport) || - !DefineProperty(cx, obj, cx->names().scripts, scriptsReport)) - return false; - - RootedValue stringsReport(cx); - if (!count.strings->report(&stringsReport) || - !DefineProperty(cx, obj, cx->names().strings, stringsReport)) - return false; - - RootedValue otherReport(cx); - if (!count.other->report(&otherReport) || - !DefineProperty(cx, obj, cx->names().other, otherReport)) - return false; - - report.setObject(*obj); - return true; - } -}; - -// Comparison function for sorting hash table entries by total count. The -// arguments are doubly indirect: they're pointers to elements in an array of -// pointers to table entries. -template -static int compareEntries(const void* lhsVoid, const void* rhsVoid) { - size_t lhs = (*static_cast(lhsVoid))->value()->total_; - size_t rhs = (*static_cast(rhsVoid))->value()->total_; - - // qsort sorts in "ascending" order, so we should describe entries with - // smaller counts as being "greater than" entries with larger counts. We - // don't want to just subtract the counts, as they're unsigned. - if (lhs < rhs) - return 1; - if (lhs > rhs) - return -1; - return 0; -} - -// A type that categorizes nodes that are JSObjects by their class name, -// and places all other nodes in an 'other' category. -class ByObjectClass : public CountType { - // A hash policy that compares C strings. - struct HashPolicy { - typedef const char* Lookup; - static js::HashNumber hash(Lookup l) { return mozilla::HashString(l); } - static bool match(const char* key, Lookup lookup) { - return strcmp(key, lookup) == 0; - } - }; - - // A table mapping class names to their counts. Note that we treat js::Class - // instances with the same name as equal keys. If you have several - // js::Classes with equal names (and we do; as of this writing there were - // six named "Object"), you will get several different js::Classes being - // counted in the same table entry. - typedef HashMap Table; - typedef Table::Entry Entry; - - struct Count : public CountBase { - Table table; - CountBasePtr other; - - Count(CountType& type, CountBasePtr& other) - : CountBase(type), - other(Move(other)) - { } - - bool init() { return table.init(); } - }; - - CountTypePtr classesType; - CountTypePtr otherType; - - public: - ByObjectClass(Census& census, - CountTypePtr& classesType, - CountTypePtr& otherType) - : CountType(census), - classesType(Move(classesType)), - otherType(Move(otherType)) - { } - - CountBasePtr makeCount() override { - CountBasePtr otherCount(otherType->makeCount()); - if (!otherCount) - return nullptr; - - UniquePtr count(census.new_(*this, otherCount)); - if (!count || !count->init()) - return nullptr; - - return CountBasePtr(count.release()); - } - - void traceCount(CountBase& countBase, JSTracer* trc) override { - Count& count = static_cast(countBase); - for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) - r.front().value()->trace(trc); - count.other->trace(trc); - } - - void destructCount(CountBase& countBase) override { - Count& count = static_cast(countBase); - count.~Count(); - } - - bool count(CountBase& countBase, const Node& node) override { - Count& count = static_cast(countBase); - count.total_++; - - const char* className = node.jsObjectClassName(); - if (!className) - return count.other->count(node); - - Table::AddPtr p = count.table.lookupForAdd(className); - if (!p) { - CountBasePtr classCount(classesType->makeCount()); - if (!classCount || !count.table.add(p, className, Move(classCount))) - return false; - } - return p->value()->count(node); - } - - bool report(CountBase& countBase, MutableHandleValue report) override { - Count& count = static_cast(countBase); - JSContext* cx = census.cx; - - // Build a vector of pointers to entries; sort by total; and then use - // that to build the result object. This makes the ordering of entries - // more interesting, and a little less non-deterministic. - mozilla::Vector entries; - if (!entries.reserve(count.table.count())) - return false; - for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) - entries.infallibleAppend(&r.front()); - qsort(entries.begin(), entries.length(), sizeof(*entries.begin()), compareEntries); - - // Now build the result by iterating over the sorted vector. - RootedPlainObject obj(cx, NewBuiltinClassInstance(cx)); - if (!obj) - return false; - for (Entry** entryPtr = entries.begin(); entryPtr < entries.end(); entryPtr++) { - Entry& entry = **entryPtr; - CountBasePtr& classCount = entry.value(); - RootedValue classReport(cx); - if (!classCount->report(&classReport)) - return false; - - const char* name = entry.key(); - MOZ_ASSERT(name); - JSAtom* atom = Atomize(cx, name, strlen(name)); - if (!atom) - return false; - RootedId entryId(cx, AtomToId(atom)); - -#ifdef DEBUG - // We have multiple js::Classes out there with the same name (for - // example, JSObject::class_, Debugger.Object, and CollatorClass are - // all "Object"), so let's make sure our hash table treats them all - // as equivalent. - bool has; - if (!HasOwnProperty(cx, obj, entryId, &has)) - return false; - if (has) { - fprintf(stderr, "already has own property '%s'\n", name); - MOZ_ASSERT(!has); - } -#endif - - if (!DefineProperty(cx, obj, entryId, classReport)) - return false; - } - - RootedValue otherReport(cx); - if (!count.other->report(&otherReport) || - !DefineProperty(cx, obj, cx->names().other, otherReport)) - return false; - - report.setObject(*obj); - return true; - } -}; - - -// A count type that categorizes nodes by their ubi::Node::typeName. -class ByUbinodeType : public CountType { - // Note that, because ubi::Node::typeName promises to return a specific - // pointer, not just any string whose contents are correct, we can use their - // addresses as hash table keys. - typedef HashMap, - SystemAllocPolicy> Table; - typedef Table::Entry Entry; - - struct Count: public CountBase { - Table table; - - explicit Count(CountType& type) : CountBase(type) { } - - bool init() { return table.init(); } - }; - - CountTypePtr entryType; - - public: - ByUbinodeType(Census& census, CountTypePtr& entryType) - : CountType(census), - entryType(Move(entryType)) - { } - - CountBasePtr makeCount() override { - UniquePtr count(census.new_(*this)); - if (!count || !count->init()) - return nullptr; - - return CountBasePtr(count.release()); - } - - void traceCount(CountBase& countBase, JSTracer* trc) override { - Count& count = static_cast(countBase); - for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) - r.front().value()->trace(trc); - } - - void destructCount(CountBase& countBase) override { - Count& count = static_cast(countBase); - count.~Count(); - } - - bool count(CountBase& countBase, const Node& node) override { - Count& count = static_cast(countBase); - count.total_++; - - const char16_t* key = node.typeName(); - Table::AddPtr p = count.table.lookupForAdd(key); - if (!p) { - CountBasePtr typesCount(entryType->makeCount()); - if (!typesCount || !count.table.add(p, key, Move(typesCount))) - return false; - } - return p->value()->count(node); - } - - bool report(CountBase& countBase, MutableHandleValue report) override { - Count& count = static_cast(countBase); - JSContext* cx = census.cx; - - // Build a vector of pointers to entries; sort by total; and then use - // that to build the result object. This makes the ordering of entries - // more interesting, and a little less non-deterministic. - mozilla::Vector entries; - if (!entries.reserve(count.table.count())) - return false; - for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) - entries.infallibleAppend(&r.front()); - qsort(entries.begin(), entries.length(), sizeof(*entries.begin()), compareEntries); - - // Now build the result by iterating over the sorted vector. - RootedPlainObject obj(cx, NewBuiltinClassInstance(cx)); - if (!obj) - return false; - for (Entry** entryPtr = entries.begin(); entryPtr < entries.end(); entryPtr++) { - Entry& entry = **entryPtr; - CountBasePtr& typeCount = entry.value(); - RootedValue typeReport(cx); - if (!typeCount->report(&typeReport)) - return false; - - const char16_t* name = entry.key(); - MOZ_ASSERT(name); - JSAtom* atom = AtomizeChars(cx, name, js_strlen(name)); - if (!atom) - return false; - RootedId entryId(cx, AtomToId(atom)); - - if (!DefineProperty(cx, obj, entryId, typeReport)) - return false; - } - - report.setObject(*obj); - return true; - } -}; - - -// A count type that categorizes nodes by the JS stack under which they were -// allocated. -class ByAllocationStack : public CountType { - typedef HashMap, - SystemAllocPolicy> Table; - typedef Table::Entry Entry; - - struct Count : public CountBase { - // NOTE: You may look up entries in this table by SavedFrame key only - // during traversal, NOT ONCE TRAVERSAL IS COMPLETE. Once traversal is - // complete, you may only iterate over it. - // - // In this hash table, keys are JSObjects, and we use JSObject identity - // (that is, address identity) as key identity. The normal way to - // support such a table is to make the trace function notice keys that - // have moved and re-key them in the table. However, our trace function - // does *not* rehash; the first GC may render the hash table - // unsearchable. - // - // This is as it should be: - // - // First, the heap traversal phase needs lookups by key to work. But no - // GC may ever occur during a traversal; this is enforced by the - // JS::ubi::BreadthFirst template. So the traceCount function doesn't - // need to do anything to help traversal; it never even runs then. - // - // Second, the report phase needs iteration over the table to work, but - // never looks up entries by key. GC may well occur during this phase: - // we allocate a Map object, and probably cross-compartment wrappers for - // SavedFrame instances as well. If a GC were to occur, it would call - // our traceCount function; if traceCount were to re-key, that would - // ruin the traversal in progress. - // - // So depending on the phase, we either don't need re-keying, or - // can't abide it. - Table table; - CountBasePtr noStack; - - Count(CountType& type, CountBasePtr& noStack) - : CountBase(type), - noStack(Move(noStack)) - { } - bool init() { return table.init(); } - }; - - CountTypePtr entryType; - CountTypePtr noStackType; - - public: - ByAllocationStack(Census& census, CountTypePtr& entryType, CountTypePtr& noStackType) - : CountType(census), - entryType(Move(entryType)), - noStackType(Move(noStackType)) - { } - - CountBasePtr makeCount() override { - CountBasePtr noStackCount(noStackType->makeCount()); - if (!noStackCount) - return nullptr; - - UniquePtr count(census.new_(*this, noStackCount)); - if (!count || !count->init()) - return nullptr; - return CountBasePtr(count.release()); - } - - void traceCount(CountBase& countBase, JSTracer* trc) override { - Count& count= static_cast(countBase); - for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) { - // Trace our child Counts. - r.front().value()->trace(trc); - - // Trace the SavedFrame that is this entry's key. Do not re-key if - // it has moved; see comments for ByAllocationStack::Count::table. - SavedFrame** keyPtr = const_cast(&r.front().key()); - TraceRoot(trc, keyPtr, "Debugger.Memory.prototype.census byAllocationStack count key"); - } - count.noStack->trace(trc); - } - - void destructCount(CountBase& countBase) override { - Count& count = static_cast(countBase); - count.~Count(); - } - - bool count(CountBase& countBase, const Node& node) override { - Count& count = static_cast(countBase); - count.total_++; - - SavedFrame* allocationStack = nullptr; - if (node.is()) { - JSObject* metadata = GetObjectMetadata(node.as()); - if (metadata && metadata->is()) - allocationStack = &metadata->as(); - } - // If any other types had allocation site data, we could retrieve it - // here. - - // If we do have an allocation stack for this node, include it in the - // count for that stack. - if (allocationStack) { - Table::AddPtr p = count.table.lookupForAdd(allocationStack); - if (!p) { - CountBasePtr stackCount(entryType->makeCount()); - if (!stackCount || !count.table.add(p, allocationStack, Move(stackCount))) - return false; - } - return p->value()->count(node); - } - - // Otherwise, count it in the "no stack" category. - return count.noStack->count(node); - } - - bool report(CountBase& countBase, MutableHandleValue report) override { - Count& count = static_cast(countBase); - JSContext* cx = census.cx; - -#ifdef DEBUG - // Check that nothing rehashes our table while we hold pointers into it. - Generation generation = count.table.generation(); -#endif - - // Build a vector of pointers to entries; sort by total; and then use - // that to build the result object. This makes the ordering of entries - // more interesting, and a little less non-deterministic. - mozilla::Vector entries; - if (!entries.reserve(count.table.count())) - return false; - for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) - entries.infallibleAppend(&r.front()); - qsort(entries.begin(), entries.length(), sizeof(*entries.begin()), compareEntries); - - // Now build the result by iterating over the sorted vector. - Rooted map(cx, MapObject::create(cx)); - if (!map) - return false; - for (Entry** entryPtr = entries.begin(); entryPtr < entries.end(); entryPtr++) { - Entry& entry = **entryPtr; - - MOZ_ASSERT(entry.key()); - RootedValue stack(cx, ObjectValue(*entry.key())); - if (!cx->compartment()->wrap(cx, &stack)) - return false; - - CountBasePtr& stackCount = entry.value(); - RootedValue stackReport(cx); - if (!stackCount->report(&stackReport)) - return false; - - if (!MapObject::set(cx, map, stack, stackReport)) - return false; - } - - if (count.noStack->total_ > 0) { - RootedValue noStackReport(cx); - if (!count.noStack->report(&noStackReport)) - return false; - RootedValue noStack(cx, StringValue(cx->names().noStack)); - if (!MapObject::set(cx, map, noStack, noStackReport)) - return false; - } - - MOZ_ASSERT(generation == count.table.generation()); - - report.setObject(*map); - return true; - } -}; - - -// A BreadthFirst handler type that conducts a census, using a CountBase to -// categorize and count each node. -class CensusHandler { - Census& census; - CountBasePtr& rootCount; - - public: - CensusHandler(Census& census, CountBasePtr& rootCount) - : census(census), - rootCount(rootCount) - { } - - bool report(MutableHandleValue report) { - return rootCount->report(report); - } - - // This class needs to retain no per-node data. - class NodeData { }; - - bool operator() (BreadthFirst& traversal, - Node origin, const Edge& edge, - NodeData* referentData, bool first) - { - // We're only interested in the first time we reach edge.referent, not - // in every edge arriving at that node. - if (!first) - return true; - - // Don't count nodes outside the debuggee zones. Do count things in the - // special atoms zone, but don't traverse their outgoing edges, on the - // assumption that they are shared resources that debuggee is using. - // Symbols are always allocated in the atoms zone, even if they were - // created for exactly one compartment and never shared; this rule will - // include such nodes in the count. - const Node& referent = edge.referent; - Zone* zone = referent.zone(); - - if (census.debuggeeZones.has(zone)) { - return rootCount->count(referent); - } - - if (zone == census.atomsZone) { - traversal.abandonReferent(); - return rootCount->count(referent); - } - - traversal.abandonReferent(); - return true; - } -}; - -typedef BreadthFirst CensusTraversal; - -static CountTypePtr ParseBreakdown(Census& census, HandleValue breakdownValue); - -static CountTypePtr -ParseChildBreakdown(Census& census, HandleObject breakdown, PropertyName* prop) -{ - JSContext* cx = census.cx; - RootedValue v(cx); - if (!GetProperty(cx, breakdown, breakdown, prop, &v)) - return nullptr; - return ParseBreakdown(census, v); -} - -static CountTypePtr -ParseBreakdown(Census& census, HandleValue breakdownValue) -{ - JSContext* cx = census.cx; - - if (breakdownValue.isUndefined()) { - // Construct the default type, { by: 'count' } - CountTypePtr simple(census.new_(census)); - return simple; - } - - RootedObject breakdown(cx, ToObject(cx, breakdownValue)); - if (!breakdown) - return nullptr; - - RootedValue byValue(cx); - if (!GetProperty(cx, breakdown, breakdown, cx->names().by, &byValue)) - return nullptr; - RootedString byString(cx, ToString(cx, byValue)); - if (!byString) - return nullptr; - RootedLinearString by(cx, byString->ensureLinear(cx)); - if (!by) - return nullptr; - - if (StringEqualsAscii(by, "count")) { - RootedValue countValue(cx), bytesValue(cx); - if (!GetProperty(cx, breakdown, breakdown, cx->names().count, &countValue) || - !GetProperty(cx, breakdown, breakdown, cx->names().bytes, &bytesValue)) - return nullptr; - - // Both 'count' and 'bytes' default to true if omitted, but ToBoolean - // naturally treats 'undefined' as false; fix this up. - if (countValue.isUndefined()) countValue.setBoolean(true); - if (bytesValue.isUndefined()) bytesValue.setBoolean(true); - - // Undocumented feature, for testing: { by: 'count' } breakdowns can have - // a 'label' property whose value is converted to a string and included as - // a 'label' property on the report object. - RootedValue label(cx); - if (!GetProperty(cx, breakdown, breakdown, cx->names().label, &label)) - return nullptr; - - UniquePtr labelUnique(nullptr); - if (!label.isUndefined()) { - RootedString labelString(cx, ToString(cx, label)); - if (!labelString) - return nullptr; - - JSFlatString* flat = labelString->ensureFlat(cx); - if (!flat) - return nullptr; - - AutoStableStringChars chars(cx); - if (!chars.initTwoByte(cx, flat)) - return nullptr; - - // Since flat strings are null-terminated, and AutoStableStringChars - // null- terminates if it needs to make a copy, we know that - // chars.twoByteChars() is null-terminated. - labelUnique = DuplicateString(cx, chars.twoByteChars()); - if (!labelUnique) - return nullptr; - } - - CountTypePtr simple(census.new_(census, - labelUnique, - ToBoolean(countValue), - ToBoolean(bytesValue))); - return simple; - } - - if (StringEqualsAscii(by, "objectClass")) { - CountTypePtr thenType(ParseChildBreakdown(census, breakdown, cx->names().then)); - if (!thenType) - return nullptr; - - CountTypePtr otherType(ParseChildBreakdown(census, breakdown, cx->names().other)); - if (!otherType) - return nullptr; - - return CountTypePtr(census.new_(census, thenType, otherType)); - } - - if (StringEqualsAscii(by, "coarseType")) { - CountTypePtr objectsType(ParseChildBreakdown(census, breakdown, cx->names().objects)); - if (!objectsType) - return nullptr; - CountTypePtr scriptsType(ParseChildBreakdown(census, breakdown, cx->names().scripts)); - if (!scriptsType) - return nullptr; - CountTypePtr stringsType(ParseChildBreakdown(census, breakdown, cx->names().strings)); - if (!stringsType) - return nullptr; - CountTypePtr otherType(ParseChildBreakdown(census, breakdown, cx->names().other)); - if (!otherType) - return nullptr; - - return CountTypePtr(census.new_(census, - objectsType, - scriptsType, - stringsType, - otherType)); - } - - if (StringEqualsAscii(by, "internalType")) { - CountTypePtr thenType(ParseChildBreakdown(census, breakdown, cx->names().then)); - if (!thenType) - return nullptr; - - return CountTypePtr(census.new_(census, thenType)); - } - - if (StringEqualsAscii(by, "allocationStack")) { - CountTypePtr thenType(ParseChildBreakdown(census, breakdown, cx->names().then)); - if (!thenType) - return nullptr; - CountTypePtr noStackType(ParseChildBreakdown(census, breakdown, cx->names().noStack)); - if (!noStackType) - return nullptr; - - return CountTypePtr(census.new_(census, thenType, noStackType)); - } - - // We didn't recognize the breakdown type; complain. - RootedString bySource(cx, ValueToSource(cx, byValue)); - if (!bySource) - return nullptr; - - JSAutoByteString byBytes(cx, bySource); - if (!byBytes) - return nullptr; - - JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_CENSUS_BREAKDOWN, - byBytes.ptr()); - return nullptr; -} - -} // namespace dbg -} // namespace js - -using dbg::Census; -using dbg::CountTypePtr; -using dbg::CountBasePtr; - bool DebuggerMemory::takeCensus(JSContext* cx, unsigned argc, Value* vp) { @@ -1543,52 +521,20 @@ DebuggerMemory::takeCensus(JSContext* cx, unsigned argc, Value* vp) if (args.get(0).isObject()) options = &args[0].toObject(); - { - RootedValue breakdown(cx, UndefinedValue()); - if (options && !GetProperty(cx, options, options, cx->names().breakdown, &breakdown)) - return false; - if (!breakdown.isUndefined()) { - rootType = ParseBreakdown(census, breakdown); - } else { - // Manually build the default takeCensus query: - // { by: "coarseType", - // objects: { by: "objectClass" }, - // other: { by: "internalType" } - // } - CountTypePtr byClass(census.new_(census)); - CountTypePtr byClassElse(census.new_(census)); - CountTypePtr objects(census.new_(census, - byClass, - byClassElse)); - - CountTypePtr scripts(census.new_(census)); - CountTypePtr strings(census.new_(census)); - - CountTypePtr byType(census.new_(census)); - CountTypePtr other(census.new_(census, byType)); - - rootType = CountTypePtr(census.new_(census, - objects, - scripts, - strings, - other)); - } - } - - if (!rootType) + if (!JS::ubi::ParseCensusOptions(cx, census, options, rootType)) return false; - dbg::RootedCount rootCount(cx, rootType->makeCount()); + JS::ubi::RootedCount rootCount(cx, rootType->makeCount()); if (!rootCount) return false; - dbg::CensusHandler handler(census, rootCount); + JS::ubi::CensusHandler handler(census, rootCount); Debugger* dbg = memory->getDebugger(); RootedObject dbgObj(cx, dbg->object); // Populate our target set of debuggee zones. for (WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty(); r.popFront()) { - if (!census.debuggeeZones.put(r.front()->zone())) + if (!census.targetZones.put(r.front()->zone())) return false; } @@ -1598,7 +544,7 @@ DebuggerMemory::takeCensus(JSContext* cx, unsigned argc, Value* vp) if (!rootList.init(dbgObj)) return false; - dbg::CensusTraversal traversal(cx, handler, maybeNoGC.ref()); + JS::ubi::CensusTraversal traversal(cx, handler, maybeNoGC.ref()); if (!traversal.init()) return false; traversal.wantNames = false; diff --git a/js/src/vm/HelperThreads.h b/js/src/vm/HelperThreads.h index 851900bf06..0ce3e90976 100644 --- a/js/src/vm/HelperThreads.h +++ b/js/src/vm/HelperThreads.h @@ -406,7 +406,7 @@ EnqueuePendingParseTasksAfterGC(JSRuntime* rt); bool StartOffThreadCompression(ExclusiveContext* cx, SourceCompressionTask* task); -class AutoLockHelperThreadState +class MOZ_RAII AutoLockHelperThreadState { MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER @@ -422,7 +422,7 @@ class AutoLockHelperThreadState } }; -class AutoUnlockHelperThreadState +class MOZ_RAII AutoUnlockHelperThreadState { MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 0592dd874d..ae3481c446 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -370,7 +370,7 @@ namespace js { // All mutable state is stored in `Runtime::stopwatch` (per-process // performance stats and logistics) and in `PerformanceGroup` (per // group performance stats). -class AutoStopwatch final +class MOZ_RAII AutoStopwatch final { // The context with which this object was initialized. // Non-null. @@ -1817,12 +1817,7 @@ class ReservedRooted : public ReservedRootedBase template <> class ReservedRootedBase : public ValueOperations> -{ - friend class ValueOperations>; - const Value* extract() const { - return static_cast*>(this)->address(); - } -}; +{}; static MOZ_NEVER_INLINE bool Interpret(JSContext* cx, RunState& state) diff --git a/js/src/vm/JSONParser.h b/js/src/vm/JSONParser.h index ddeddad637..687972392b 100644 --- a/js/src/vm/JSONParser.h +++ b/js/src/vm/JSONParser.h @@ -20,7 +20,7 @@ namespace js { // JSONParser base class. JSONParser is templatized to work on either Latin1 // or TwoByte input strings, JSONParserBase holds all state and methods that // can be shared between the two encodings. -class MOZ_STACK_CLASS JSONParserBase : private JS::AutoGCRooter +class MOZ_STACK_CLASS JSONParserBase { public: enum ErrorHandling { RaiseError, NoError }; @@ -108,8 +108,7 @@ class MOZ_STACK_CLASS JSONParserBase : private JS::AutoGCRooter #endif JSONParserBase(JSContext* cx, ErrorHandling errorHandling) - : JS::AutoGCRooter(cx, JSONPARSER), - cx(cx), + : cx(cx), errorHandling(errorHandling), stack(cx), freeElements(cx), @@ -120,6 +119,20 @@ class MOZ_STACK_CLASS JSONParserBase : private JS::AutoGCRooter {} ~JSONParserBase(); + // Allow move construction for use with Rooted. + JSONParserBase(JSONParserBase&& other) + : v(other.v), + cx(other.cx), + errorHandling(other.errorHandling), + stack(mozilla::Move(other.stack)), + freeElements(mozilla::Move(other.freeElements)), + freeProperties(mozilla::Move(other.freeProperties)) +#ifdef DEBUG + , lastToken(mozilla::Move(other.lastToken)) +#endif + {} + + Value numberValue() const { MOZ_ASSERT(lastToken == Number); MOZ_ASSERT(v.isNumber()); @@ -169,16 +182,16 @@ class MOZ_STACK_CLASS JSONParserBase : private JS::AutoGCRooter bool finishObject(MutableHandleValue vp, PropertyVector& properties); bool finishArray(MutableHandleValue vp, ElementVector& elements); - private: - friend void AutoGCRooter::trace(JSTracer* trc); void trace(JSTracer* trc); + private: JSONParserBase(const JSONParserBase& other) = delete; void operator=(const JSONParserBase& other) = delete; }; template -class MOZ_STACK_CLASS JSONParser : public JSONParserBase +class MOZ_STACK_CLASS JSONParser : public JSONParserBase, + public JS::Traceable { private: typedef mozilla::RangedPtr CharPtr; @@ -200,6 +213,14 @@ class MOZ_STACK_CLASS JSONParser : public JSONParserBase MOZ_ASSERT(current <= end); } + /* Allow move construction for use with Rooted. */ + JSONParser(JSONParser&& other) + : JSONParserBase(mozilla::Forward(other)), + current(other.current), + begin(other.begin), + end(other.end) + {} + /* * Parse the JSON data specified at construction time. If it parses * successfully, store the prescribed value in *vp and return true. If an @@ -212,6 +233,9 @@ class MOZ_STACK_CLASS JSONParser : public JSONParserBase */ bool parse(MutableHandleValue vp); + static void trace(JSONParser* parser, JSTracer* trc) { parser->trace(trc); } + void trace(JSTracer* trc) { JSONParserBase::trace(trc); } + private: template Token readString(); @@ -233,6 +257,13 @@ class MOZ_STACK_CLASS JSONParser : public JSONParserBase void operator=(const JSONParser& other) = delete; }; +template +struct RootedBase> { + bool parse(MutableHandleValue vp) { + return static_cast>*>(this)->get().parse(vp); + } +}; + } /* namespace js */ #endif /* vm_JSONParser_h */ diff --git a/js/src/vm/ObjectGroup.cpp b/js/src/vm/ObjectGroup.cpp index d5c0f1a69f..4ebf94f645 100644 --- a/js/src/vm/ObjectGroup.cpp +++ b/js/src/vm/ObjectGroup.cpp @@ -36,7 +36,7 @@ ObjectGroup::ObjectGroup(const Class* clasp, TaggedProto proto, JSCompartment* c MOZ_ASSERT_IF(proto.isObject(), !proto.toObject()->getClass()->ext.outerObject); this->clasp_ = clasp; - this->proto_ = proto.raw(); + this->proto_ = proto; this->compartment_ = comp; this->flags_ = initialFlags; @@ -58,8 +58,9 @@ ObjectGroup::finalize(FreeOp* fop) void ObjectGroup::setProtoUnchecked(TaggedProto proto) { - proto_ = proto.raw(); - MOZ_ASSERT_IF(proto_ && proto_->isNative(), proto_->isDelegate()); + proto_ = proto; + MOZ_ASSERT_IF(proto_.isObject() && proto_.toObject()->isNative(), + proto_.toObject()->isDelegate()); } void diff --git a/js/src/vm/ObjectGroup.h b/js/src/vm/ObjectGroup.h index d364f58682..a83fe0071e 100644 --- a/js/src/vm/ObjectGroup.h +++ b/js/src/vm/ObjectGroup.h @@ -12,6 +12,7 @@ #include "ds/IdValuePair.h" #include "gc/Barrier.h" +#include "vm/TaggedProto.h" #include "vm/TypeInference.h" namespace js { @@ -25,106 +26,6 @@ class HeapTypeSet; class AutoClearTypeInferenceStateOnOOM; class CompilerConstraintList; -// Information about an object prototype, which can be either a particular -// object, null, or a lazily generated object. The latter is only used by -// certain kinds of proxies. -class TaggedProto -{ - public: - static JSObject * const LazyProto; - - TaggedProto() : proto(nullptr) {} - explicit TaggedProto(JSObject* proto) : proto(proto) {} - - uintptr_t toWord() const { return uintptr_t(proto); } - - bool isLazy() const { - return proto == LazyProto; - } - bool isObject() const { - /* Skip nullptr and LazyProto. */ - return uintptr_t(proto) > uintptr_t(TaggedProto::LazyProto); - } - JSObject* toObject() const { - MOZ_ASSERT(isObject()); - return proto; - } - JSObject* toObjectOrNull() const { - MOZ_ASSERT(!proto || isObject()); - return proto; - } - JSObject* raw() const { return proto; } - - bool operator ==(const TaggedProto& other) { return proto == other.proto; } - bool operator !=(const TaggedProto& other) { return proto != other.proto; } - - private: - JSObject* proto; -}; - -template <> -struct RootKind -{ - static ThingRootKind rootKind() { return THING_ROOT_OBJECT; } -}; - -template <> struct GCMethods -{ - static TaggedProto initial() { return TaggedProto(); } -}; - -template <> struct GCMethods -{ - static TaggedProto initial() { return TaggedProto(); } -}; - -template -class TaggedProtoOperations -{ - const TaggedProto* value() const { - return static_cast(this)->extract(); - } - - public: - uintptr_t toWord() const { return value()->toWord(); } - inline bool isLazy() const { return value()->isLazy(); } - inline bool isObject() const { return value()->isObject(); } - inline JSObject* toObject() const { return value()->toObject(); } - inline JSObject* toObjectOrNull() const { return value()->toObjectOrNull(); } - JSObject* raw() const { return value()->raw(); } -}; - -template <> -class HandleBase : public TaggedProtoOperations > -{ - friend class TaggedProtoOperations >; - const TaggedProto * extract() const { - return static_cast*>(this)->address(); - } -}; - -template <> -class RootedBase : public TaggedProtoOperations > -{ - friend class TaggedProtoOperations >; - const TaggedProto* extract() const { - return static_cast*>(this)->address(); - } -}; - -// Since JSObject pointers are either nullptr or a valid object and since the -// object layout of TaggedProto is identical to a bare object pointer, we can -// safely treat a pointer to an already-rooted object (e.g. HandleObject) as a -// pointer to a TaggedProto. -inline Handle -AsTaggedProto(HandleObject obj) -{ - static_assert(sizeof(JSObject*) == sizeof(TaggedProto), - "TaggedProto must be binary compatible with JSObject"); - return Handle::fromMarkedLocation( - reinterpret_cast(obj.address())); -} - namespace gc { void MergeCompartments(JSCompartment* source, JSCompartment* target); } // namespace gc @@ -182,7 +83,7 @@ class ObjectGroup : public gc::TenuredCell const Class* clasp_; /* Prototype shared by objects in this group. */ - HeapPtrObject proto_; + HeapPtr proto_; /* Compartment shared by objects in this group. */ JSCompartment* compartment_; @@ -197,12 +98,13 @@ class ObjectGroup : public gc::TenuredCell clasp_ = clasp; } - TaggedProto proto() const { - return TaggedProto(proto_); + const HeapPtr& proto() const { + return proto_; } - // For use during marking, don't call otherwise. - HeapPtrObject& protoRaw() { return proto_; } + HeapPtr& proto() { + return proto_; + } void setProto(TaggedProto proto); void setProtoUnchecked(TaggedProto proto); diff --git a/js/src/vm/RegExpStatics.h b/js/src/vm/RegExpStatics.h index 1b268bd9fd..430e4b3527 100644 --- a/js/src/vm/RegExpStatics.h +++ b/js/src/vm/RegExpStatics.h @@ -186,7 +186,7 @@ class RegExpStatics } }; -class AutoRegExpStaticsBuffer : private JS::CustomAutoRooter +class MOZ_RAII AutoRegExpStaticsBuffer : private JS::CustomAutoRooter { public: explicit AutoRegExpStaticsBuffer(JSContext* cx diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index 0af27c9414..f55ae05797 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -1871,7 +1871,7 @@ FreeOp::freeLater(void* p) * Note that the lock may be temporarily released by use of AutoUnlockGC when * passed a non-const reference to this class. */ -class MOZ_STACK_CLASS AutoLockGC +class MOZ_RAII AutoLockGC { public: explicit AutoLockGC(JSRuntime* rt @@ -1910,7 +1910,7 @@ class MOZ_STACK_CLASS AutoLockGC AutoLockGC& operator=(const AutoLockGC&) = delete; }; -class MOZ_STACK_CLASS AutoUnlockGC +class MOZ_RAII AutoUnlockGC { public: explicit AutoUnlockGC(AutoLockGC& lock @@ -1933,7 +1933,7 @@ class MOZ_STACK_CLASS AutoUnlockGC AutoUnlockGC& operator=(const AutoUnlockGC&) = delete; }; -class MOZ_STACK_CLASS AutoKeepAtoms +class MOZ_RAII AutoKeepAtoms { PerThreadData* pt; MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER @@ -2106,7 +2106,7 @@ extern const JSSecurityCallbacks NullSecurityCallbacks; // Debugging RAII class which marks the current thread as performing an Ion // compilation, for use by CurrentThreadCan{Read,Write}CompilationData -class AutoEnterIonCompilation +class MOZ_RAII AutoEnterIonCompilation { public: explicit AutoEnterIonCompilation(bool safeForMinorGC diff --git a/js/src/vm/SPSProfiler.h b/js/src/vm/SPSProfiler.h index a7106084d0..a5e6bc78c6 100644 --- a/js/src/vm/SPSProfiler.h +++ b/js/src/vm/SPSProfiler.h @@ -227,7 +227,7 @@ class AutoSPSLock * This class is used to suppress profiler sampling during * critical sections where stack state is not valid. */ -class AutoSuppressProfilerSampling +class MOZ_RAII AutoSuppressProfilerSampling { public: explicit AutoSuppressProfilerSampling(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM); @@ -260,7 +260,7 @@ SPSProfiler::stringsReset() * that we're about to enter JS function calls. This is the only time in which a * valid stack pointer is pushed to the sampling stack. */ -class SPSEntryMarker +class MOZ_RAII SPSEntryMarker { public: explicit SPSEntryMarker(JSRuntime* rt, @@ -279,7 +279,7 @@ class SPSEntryMarker * being entered via OSR. It marks the current top pseudostack entry as * OSR-ed */ -class SPSBaselineOSRMarker +class MOZ_RAII SPSBaselineOSRMarker { public: explicit SPSBaselineOSRMarker(JSRuntime* rt, bool hasSPSFrame diff --git a/js/src/vm/SavedFrame.h b/js/src/vm/SavedFrame.h index dc49a9f207..e7bf087f43 100644 --- a/js/src/vm/SavedFrame.h +++ b/js/src/vm/SavedFrame.h @@ -260,9 +260,11 @@ class ConcreteStackFrame : public BaseStackFrame { } void trace(JSTracer* trc) override { - JSObject* obj = &get(); - js::TraceManuallyBarrieredEdge(trc, &obj, "ConcreteStackFrame::ptr"); - ptr = obj; + JSObject* prev = &get(); + JSObject* next = prev; + js::TraceRoot(trc, &next, "ConcreteStackFrame::ptr"); + if (next != prev) + ptr = next; } bool isSelfHosted() const override { return get().isSelfHosted(); } diff --git a/js/src/vm/SavedStacks.cpp b/js/src/vm/SavedStacks.cpp index a97ba9a529..ecae772c04 100644 --- a/js/src/vm/SavedStacks.cpp +++ b/js/src/vm/SavedStacks.cpp @@ -151,7 +151,8 @@ struct SavedFrame::Lookup { activation(activation) { MOZ_ASSERT(source); - MOZ_ASSERT(activation); + MOZ_ASSERT_IF(framePtr.isSome(), pc); + MOZ_ASSERT_IF(framePtr.isSome(), activation); } explicit Lookup(SavedFrame& savedFrame) diff --git a/js/src/vm/SavedStacks.h b/js/src/vm/SavedStacks.h index f74a8c659e..0ec94a4779 100644 --- a/js/src/vm/SavedStacks.h +++ b/js/src/vm/SavedStacks.h @@ -181,7 +181,7 @@ class SavedStacks { // Similar to mozilla::ReentrancyGuard, but instead of asserting against // reentrancy, just change the behavior of SavedStacks::saveCurrentStack to // return a nullptr SavedFrame. - struct MOZ_STACK_CLASS AutoReentrancyGuard { + struct MOZ_RAII AutoReentrancyGuard { MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER; SavedStacks& stacks; diff --git a/js/src/vm/ScopeObject.h b/js/src/vm/ScopeObject.h index cdc7ca04fa..b4828df9d0 100644 --- a/js/src/vm/ScopeObject.h +++ b/js/src/vm/ScopeObject.h @@ -846,7 +846,7 @@ CloneNestedScopeObject(JSContext* cx, HandleObject enclosingScope, Handle ssi_; RootedObject scope_; diff --git a/js/src/vm/Shape.h b/js/src/vm/Shape.h index 99203669a2..b17874ddfe 100644 --- a/js/src/vm/Shape.h +++ b/js/src/vm/Shape.h @@ -982,7 +982,7 @@ StackBaseShape::StackBaseShape(Shape* shape) compartment(shape->compartment()) {} -class AutoRooterGetterSetter +class MOZ_RAII AutoRooterGetterSetter { class Inner : private JS::CustomAutoRooter { diff --git a/js/src/vm/String.cpp b/js/src/vm/String.cpp index 0ceb9c6ba7..064834ff27 100644 --- a/js/src/vm/String.cpp +++ b/js/src/vm/String.cpp @@ -67,7 +67,7 @@ JSString::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) : mallocSizeOf(flat.rawTwoByteChars()); } -size_t +JS::ubi::Node::Size JS::ubi::Concrete::size(mozilla::MallocSizeOf mallocSizeOf) const { JSString &str = get(); diff --git a/js/src/vm/TaggedProto.cpp b/js/src/vm/TaggedProto.cpp new file mode 100644 index 0000000000..926cd472ab --- /dev/null +++ b/js/src/vm/TaggedProto.cpp @@ -0,0 +1,20 @@ +/* -*- 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/. */ + +/* static */ void +js::InternalGCMethods::preBarrier(TaggedProto& proto) +{ + InternalGCMethods::preBarrier(proto.toObjectOrNull()); +} + +/* static */ void +js::InternalGCMethods::postBarrier(TaggedProto* vp, TaggedProto prev, TaggedProto next) +{ + JSObject* prevObj = prev.isObject() ? prev.toObject() : nullptr; + JSObject* nextObj = next.isObject() ? next.toObject() : nullptr; + InternalGCMethods::postBarrier(reinterpret_cast(vp), prevObj, + nextObj); +} diff --git a/js/src/vm/TaggedProto.h b/js/src/vm/TaggedProto.h new file mode 100644 index 0000000000..7253880253 --- /dev/null +++ b/js/src/vm/TaggedProto.h @@ -0,0 +1,130 @@ +/* -*- 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_TaggedProto_h +#define vm_TaggedProto_h + +#include "gc/Tracer.h" + +namespace js { + +// Information about an object prototype, which can be either a particular +// object, null, or a lazily generated object. The latter is only used by +// certain kinds of proxies. +class TaggedProto : public JS::Traceable +{ + public: + static JSObject * const LazyProto; + + TaggedProto() : proto(nullptr) {} + explicit TaggedProto(JSObject* proto) : proto(proto) {} + + uintptr_t toWord() const { return uintptr_t(proto); } + + bool isLazy() const { + return proto == LazyProto; + } + bool isObject() const { + /* Skip nullptr and LazyProto. */ + return uintptr_t(proto) > uintptr_t(TaggedProto::LazyProto); + } + JSObject* toObject() const { + MOZ_ASSERT(isObject()); + return proto; + } + JSObject* toObjectOrNull() const { + MOZ_ASSERT(!proto || isObject()); + return proto; + } + JSObject* raw() const { return proto; } + + bool operator ==(const TaggedProto& other) const { return proto == other.proto; } + bool operator !=(const TaggedProto& other) const { return proto != other.proto; } + + static void trace(TaggedProto* protop, JSTracer* trc) { + TraceManuallyBarrieredEdge(trc, protop, "TaggedProto"); + } + + private: + JSObject* proto; +}; + +template <> struct GCMethods +{ + static TaggedProto initial() { return TaggedProto(); } +}; + +template <> struct InternalGCMethods +{ + static void preBarrier(TaggedProto& proto); + + static void postBarrier(TaggedProto* vp, TaggedProto prev, TaggedProto next); + + static bool isMarkableTaggedPointer(TaggedProto proto) { + return proto.isObject(); + } + + static bool isMarkable(TaggedProto proto) { + return proto.isObject(); + } +}; + +template +class TaggedProtoOperations +{ + const TaggedProto& value() const { + return static_cast(this)->get(); + } + + public: + uintptr_t toWord() const { return value().toWord(); } + inline bool isLazy() const { return value().isLazy(); } + inline bool isObject() const { return value().isObject(); } + inline JSObject* toObject() const { return value().toObject(); } + inline JSObject* toObjectOrNull() const { return value().toObjectOrNull(); } + JSObject* raw() const { return value().raw(); } +}; + +template <> +class HandleBase : public TaggedProtoOperations> +{}; + +template <> +class RootedBase : public TaggedProtoOperations> +{}; + +template <> +class BarrieredBaseMixins : public TaggedProtoOperations> +{}; + +// If the TaggedProto is a JSObject pointer, convert to that type and call |f| +// with the pointer. If the TaggedProto is lazy, calls F::defaultValue. +template +auto +DispatchTyped(F f, TaggedProto& proto, Args&&... args) + -> decltype(f(static_cast(nullptr), mozilla::Forward(args)...)) +{ + if (proto.isObject()) + return f(proto.toObject(), mozilla::Forward(args)...); + return F::defaultValue(proto); +} + +// Since JSObject pointers are either nullptr or a valid object and since the +// object layout of TaggedProto is identical to a bare object pointer, we can +// safely treat a pointer to an already-rooted object (e.g. HandleObject) as a +// pointer to a TaggedProto. +inline Handle +AsTaggedProto(HandleObject obj) +{ + static_assert(sizeof(JSObject*) == sizeof(TaggedProto), + "TaggedProto must be binary compatible with JSObject"); + return Handle::fromMarkedLocation( + reinterpret_cast(obj.address())); +} + +} // namespace js + +#endif // vm_TaggedProto_h diff --git a/js/src/vm/TraceLogging.h b/js/src/vm/TraceLogging.h index 1ac21a16a8..9ba78edcfb 100644 --- a/js/src/vm/TraceLogging.h +++ b/js/src/vm/TraceLogging.h @@ -439,7 +439,7 @@ inline void TraceLogStopEventPrivate(TraceLoggerThread* logger, uint32_t id) { } // Automatic logging at the start and end of function call. -class AutoTraceLog +class MOZ_RAII AutoTraceLog { #ifdef JS_TRACE_LOGGING TraceLoggerThread* logger; diff --git a/js/src/vm/TraceLoggingGraph.cpp b/js/src/vm/TraceLoggingGraph.cpp index d1b7f2e5db..d5a7b8b735 100644 --- a/js/src/vm/TraceLoggingGraph.cpp +++ b/js/src/vm/TraceLoggingGraph.cpp @@ -24,7 +24,7 @@ using mozilla::NativeEndian; TraceLoggerGraphState* traceLoggerGraphState = nullptr; -class AutoTraceLoggerGraphStateLock +class MOZ_RAII AutoTraceLoggerGraphStateLock { TraceLoggerGraphState* graph; diff --git a/js/src/vm/TypeInference.h b/js/src/vm/TypeInference.h index 396d1c9956..83111c85e1 100644 --- a/js/src/vm/TypeInference.h +++ b/js/src/vm/TypeInference.h @@ -23,6 +23,7 @@ #include "js/UbiNode.h" #include "js/Utility.h" #include "js/Vector.h" +#include "vm/TaggedProto.h" namespace js { @@ -32,7 +33,6 @@ namespace jit { class TempAllocator; } // namespace jit -class TaggedProto; struct TypeZone; class TypeConstraint; class TypeNewScript; diff --git a/js/src/vm/UbiNode.cpp b/js/src/vm/UbiNode.cpp index 0aa64a6015..264613c9c1 100644 --- a/js/src/vm/UbiNode.cpp +++ b/js/src/vm/UbiNode.cpp @@ -37,11 +37,12 @@ using mozilla::Some; using mozilla::RangedPtr; using mozilla::UniquePtr; -using JS::DispatchTraceKindTyped; +using JS::DispatchTyped; using JS::HandleValue; using JS::Value; using JS::ZoneSet; using JS::ubi::AtomOrTwoByteChars; +using JS::ubi::CoarseType; using JS::ubi::Concrete; using JS::ubi::Edge; using JS::ubi::EdgeRange; @@ -144,6 +145,7 @@ StackFrame::functionDisplayNameLength() } // All operations on null ubi::Nodes crash. +CoarseType Concrete::coarseType() 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"); } @@ -153,7 +155,7 @@ Concrete::edges(JSContext*, bool) const { MOZ_CRASH("null ubi::Node"); } -size_t +Node::Size Concrete::size(mozilla::MallocSizeOf mallocSizeof) const { MOZ_CRASH("null ubi::Node"); @@ -165,12 +167,12 @@ struct Node::ConstructFunctor : public js::BoolDefaultAdaptor { Node::Node(const JS::GCCellPtr &thing) { - DispatchTraceKindTyped(ConstructFunctor(), thing.asCell(), thing.kind(), this); + DispatchTyped(ConstructFunctor(), thing, this); } Node::Node(HandleValue value) { - if (!DispatchValueTyped(ConstructFunctor(), value, this)) + if (!DispatchTyped(ConstructFunctor(), value, this)) construct(nullptr); } diff --git a/js/src/vm/UbiNodeCensus.cpp b/js/src/vm/UbiNodeCensus.cpp new file mode 100644 index 0000000000..a462da35d0 --- /dev/null +++ b/js/src/vm/UbiNodeCensus.cpp @@ -0,0 +1,961 @@ +/* -*- 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 "js/UbiNodeCensus.h" + +namespace JS { +namespace ubi { + +void +CountDeleter::operator()(CountBase* ptr) +{ + if (!ptr) + return; + + // Downcast to our true type and destruct, as guided by our CountType + // pointer. + ptr->destruct(); + js_free(ptr); +} + +bool +Census::init() { + AutoLockForExclusiveAccess lock(cx); + atomsZone = cx->runtime()->atomsCompartment()->zone(); + return targetZones.init(); +} + + +/*** Count Types ***********************************************************************************/ + +// The simplest type: just count everything. +class SimpleCount : public CountType { + + struct Count : CountBase { + size_t totalBytes_; + + explicit Count(SimpleCount& count) + : CountBase(count), + totalBytes_(0) + { } + }; + + UniquePtr label; + bool reportCount : 1; + bool reportBytes : 1; + + public: + SimpleCount(Census& census, + UniquePtr& label, + bool reportCount=true, + bool reportBytes=true) + : CountType(census), + label(Move(label)), + reportCount(reportCount), + reportBytes(reportBytes) + { } + + explicit SimpleCount(Census& census) + : CountType(census), + label(nullptr), + reportCount(true), + reportBytes(true) + { } + + void destructCount(CountBase& countBase) override { + Count& count = static_cast(countBase); + count.~Count(); + } + + CountBasePtr makeCount() override { return CountBasePtr(census.new_(*this)); } + void traceCount(CountBase& countBase, JSTracer* trc) override { } + bool count(CountBase& countBase, const Node& node) override; + bool report(CountBase& countBase, MutableHandleValue report) override; +}; + +bool +SimpleCount::count(CountBase& countBase, const Node& node) +{ + Count& count = static_cast(countBase); + count.total_++; + if (reportBytes) + count.totalBytes_ += node.size(census.cx->runtime()->debuggerMallocSizeOf); + return true; +} + +bool +SimpleCount::report(CountBase& countBase, MutableHandleValue report) +{ + Count& count = static_cast(countBase); + + RootedPlainObject obj(census.cx, NewBuiltinClassInstance(census.cx)); + if (!obj) + return false; + + RootedValue countValue(census.cx, NumberValue(count.total_)); + if (reportCount && !DefineProperty(census.cx, obj, census.cx->names().count, countValue)) + return false; + + RootedValue bytesValue(census.cx, NumberValue(count.totalBytes_)); + if (reportBytes && !DefineProperty(census.cx, obj, census.cx->names().bytes, bytesValue)) + return false; + + if (label) { + JSString* labelString = JS_NewUCStringCopyZ(census.cx, label.get()); + if (!labelString) + return false; + RootedValue labelValue(census.cx, StringValue(labelString)); + if (!DefineProperty(census.cx, obj, census.cx->names().label, labelValue)) + return false; + } + + report.setObject(*obj); + return true; +} + + +// A type that categorizes nodes by their JavaScript type -- 'objects', +// 'strings', 'scripts', and 'other' -- and then passes the nodes to child +// types. +// +// Implementation details of scripts like jitted code are counted under +// 'scripts'. +class ByCoarseType : public CountType { + CountTypePtr objects; + CountTypePtr scripts; + CountTypePtr strings; + CountTypePtr other; + + struct Count : CountBase { + Count(CountType& type, + CountBasePtr& objects, + CountBasePtr& scripts, + CountBasePtr& strings, + CountBasePtr& other) + : CountBase(type), + objects(Move(objects)), + scripts(Move(scripts)), + strings(Move(strings)), + other(Move(other)) + { } + + CountBasePtr objects; + CountBasePtr scripts; + CountBasePtr strings; + CountBasePtr other; + }; + + public: + ByCoarseType(Census& census, + CountTypePtr& objects, + CountTypePtr& scripts, + CountTypePtr& strings, + CountTypePtr& other) + : CountType(census), + objects(Move(objects)), + scripts(Move(scripts)), + strings(Move(strings)), + other(Move(other)) + { } + + void destructCount(CountBase& countBase) override { + Count& count = static_cast(countBase); + count.~Count(); + } + + CountBasePtr makeCount() override; + void traceCount(CountBase& countBase, JSTracer* trc) override; + bool count(CountBase& countBase, const Node& node) override; + bool report(CountBase& countBase, MutableHandleValue report) override; +}; + +CountBasePtr +ByCoarseType::makeCount() +{ + CountBasePtr objectsCount(objects->makeCount()); + CountBasePtr scriptsCount(scripts->makeCount()); + CountBasePtr stringsCount(strings->makeCount()); + CountBasePtr otherCount(other->makeCount()); + + if (!objectsCount || !scriptsCount || !stringsCount || !otherCount) + return CountBasePtr(nullptr); + + return CountBasePtr(census.new_(*this, + objectsCount, + scriptsCount, + stringsCount, + otherCount)); +} + +void +ByCoarseType::traceCount(CountBase& countBase, JSTracer* trc) +{ + Count& count = static_cast(countBase); + count.objects->trace(trc); + count.scripts->trace(trc); + count.strings->trace(trc); + count.other->trace(trc); +} + +bool +ByCoarseType::count(CountBase& countBase, const Node& node) +{ + Count& count = static_cast(countBase); + count.total_++; + + switch (node.coarseType()) { + case JS::ubi::CoarseType::Object: + return count.objects->count(node); + case JS::ubi::CoarseType::Script: + return count.scripts->count(node); + case JS::ubi::CoarseType::String: + return count.strings->count(node); + case JS::ubi::CoarseType::Other: + return count.other->count(node); + default: + MOZ_CRASH("bad JS::ubi::CoarseType in JS::ubi::ByCoarseType::count"); + return false; + } +} + +bool +ByCoarseType::report(CountBase& countBase, MutableHandleValue report) +{ + Count& count = static_cast(countBase); + JSContext* cx = census.cx; + + RootedPlainObject obj(cx, NewBuiltinClassInstance(cx)); + if (!obj) + return false; + + RootedValue objectsReport(cx); + if (!count.objects->report(&objectsReport) || + !DefineProperty(cx, obj, cx->names().objects, objectsReport)) + return false; + + RootedValue scriptsReport(cx); + if (!count.scripts->report(&scriptsReport) || + !DefineProperty(cx, obj, cx->names().scripts, scriptsReport)) + return false; + + RootedValue stringsReport(cx); + if (!count.strings->report(&stringsReport) || + !DefineProperty(cx, obj, cx->names().strings, stringsReport)) + return false; + + RootedValue otherReport(cx); + if (!count.other->report(&otherReport) || + !DefineProperty(cx, obj, cx->names().other, otherReport)) + return false; + + report.setObject(*obj); + return true; +} + + +// Comparison function for sorting hash table entries by total count. The +// arguments are doubly indirect: they're pointers to elements in an array of +// pointers to table entries. +template +static int compareEntries(const void* lhsVoid, const void* rhsVoid) { + size_t lhs = (*static_cast(lhsVoid))->value()->total_; + size_t rhs = (*static_cast(rhsVoid))->value()->total_; + + // qsort sorts in "ascending" order, so we should describe entries with + // smaller counts as being "greater than" entries with larger counts. We + // don't want to just subtract the counts, as they're unsigned. + if (lhs < rhs) + return 1; + if (lhs > rhs) + return -1; + return 0; +} + + +// A type that categorizes nodes that are JSObjects by their class name, +// and places all other nodes in an 'other' category. +class ByObjectClass : public CountType { + // A hash policy that compares C strings. + struct HashPolicy { + using Lookup = const char*; + static js::HashNumber hash(Lookup l) { return mozilla::HashString(l); } + static bool match(const char* key, Lookup lookup) { + return strcmp(key, lookup) == 0; + } + }; + + // A table mapping class names to their counts. Note that we treat js::Class + // instances with the same name as equal keys. If you have several + // js::Classes with equal names (and we do; as of this writing there were + // six named "Object"), you will get several different js::Classes being + // counted in the same table entry. + using Table = HashMap; + using Entry = Table::Entry; + + struct Count : public CountBase { + Table table; + CountBasePtr other; + + Count(CountType& type, CountBasePtr& other) + : CountBase(type), + other(Move(other)) + { } + + bool init() { return table.init(); } + }; + + CountTypePtr classesType; + CountTypePtr otherType; + + public: + ByObjectClass(Census& census, + CountTypePtr& classesType, + CountTypePtr& otherType) + : CountType(census), + classesType(Move(classesType)), + otherType(Move(otherType)) + { } + + void destructCount(CountBase& countBase) override { + Count& count = static_cast(countBase); + count.~Count(); + } + + CountBasePtr makeCount() override; + void traceCount(CountBase& countBase, JSTracer* trc) override; + bool count(CountBase& countBase, const Node& node) override; + bool report(CountBase& countBase, MutableHandleValue report) override; +}; + +CountBasePtr +ByObjectClass::makeCount() +{ + CountBasePtr otherCount(otherType->makeCount()); + if (!otherCount) + return nullptr; + + UniquePtr count(census.new_(*this, otherCount)); + if (!count || !count->init()) + return nullptr; + + return CountBasePtr(count.release()); +} + +void +ByObjectClass::traceCount(CountBase& countBase, JSTracer* trc) +{ + Count& count = static_cast(countBase); + for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) + r.front().value()->trace(trc); + count.other->trace(trc); +} + +bool +ByObjectClass::count(CountBase& countBase, const Node& node) +{ + Count& count = static_cast(countBase); + count.total_++; + + const char* className = node.jsObjectClassName(); + if (!className) + return count.other->count(node); + + Table::AddPtr p = count.table.lookupForAdd(className); + if (!p) { + CountBasePtr classCount(classesType->makeCount()); + if (!classCount || !count.table.add(p, className, Move(classCount))) + return false; + } + return p->value()->count(node); +} + +bool +ByObjectClass::report(CountBase& countBase, MutableHandleValue report) +{ + Count& count = static_cast(countBase); + JSContext* cx = census.cx; + + // Build a vector of pointers to entries; sort by total; and then use + // that to build the result object. This makes the ordering of entries + // more interesting, and a little less non-deterministic. + mozilla::Vector entries; + if (!entries.reserve(count.table.count())) + return false; + for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) + entries.infallibleAppend(&r.front()); + qsort(entries.begin(), entries.length(), sizeof(*entries.begin()), compareEntries); + + // Now build the result by iterating over the sorted vector. + RootedPlainObject obj(cx, NewBuiltinClassInstance(cx)); + if (!obj) + return false; + for (Entry** entryPtr = entries.begin(); entryPtr < entries.end(); entryPtr++) { + Entry& entry = **entryPtr; + CountBasePtr& classCount = entry.value(); + RootedValue classReport(cx); + if (!classCount->report(&classReport)) + return false; + + const char* name = entry.key(); + MOZ_ASSERT(name); + JSAtom* atom = Atomize(cx, name, strlen(name)); + if (!atom) + return false; + RootedId entryId(cx, AtomToId(atom)); + +#ifdef DEBUG + // We have multiple js::Classes out there with the same name (for + // example, JSObject::class_, Debugger.Object, and CollatorClass are + // all "Object"), so let's make sure our hash table treats them all + // as equivalent. + bool has; + if (!HasOwnProperty(cx, obj, entryId, &has)) + return false; + if (has) { + fprintf(stderr, "already has own property '%s'\n", name); + MOZ_ASSERT(!has); + } +#endif + + if (!DefineProperty(cx, obj, entryId, classReport)) + return false; + } + + RootedValue otherReport(cx); + if (!count.other->report(&otherReport) || + !DefineProperty(cx, obj, cx->names().other, otherReport)) + return false; + + report.setObject(*obj); + return true; +} + + +// A count type that categorizes nodes by their ubi::Node::typeName. +class ByUbinodeType : public CountType { + // Note that, because ubi::Node::typeName promises to return a specific + // pointer, not just any string whose contents are correct, we can use their + // addresses as hash table keys. + using Table = HashMap, + SystemAllocPolicy>; + using Entry = Table::Entry; + + struct Count: public CountBase { + Table table; + + explicit Count(CountType& type) : CountBase(type) { } + + bool init() { return table.init(); } + }; + + CountTypePtr entryType; + + public: + ByUbinodeType(Census& census, CountTypePtr& entryType) + : CountType(census), + entryType(Move(entryType)) + { } + + void destructCount(CountBase& countBase) override { + Count& count = static_cast(countBase); + count.~Count(); + } + + CountBasePtr makeCount() override; + void traceCount(CountBase& countBase, JSTracer* trc) override; + bool count(CountBase& countBase, const Node& node) override; + bool report(CountBase& countBase, MutableHandleValue report) override; +}; + +CountBasePtr +ByUbinodeType::makeCount() +{ + UniquePtr count(census.new_(*this)); + if (!count || !count->init()) + return nullptr; + + return CountBasePtr(count.release()); +} + +void +ByUbinodeType::traceCount(CountBase& countBase, JSTracer* trc) +{ + Count& count = static_cast(countBase); + for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) + r.front().value()->trace(trc); +} + +bool +ByUbinodeType::count(CountBase& countBase, const Node& node) +{ + Count& count = static_cast(countBase); + count.total_++; + + const char16_t* key = node.typeName(); + MOZ_ASSERT(key); + Table::AddPtr p = count.table.lookupForAdd(key); + if (!p) { + CountBasePtr typesCount(entryType->makeCount()); + if (!typesCount || !count.table.add(p, key, Move(typesCount))) + return false; + } + return p->value()->count(node); +} + +bool +ByUbinodeType::report(CountBase& countBase, MutableHandleValue report) +{ + Count& count = static_cast(countBase); + JSContext* cx = census.cx; + + // Build a vector of pointers to entries; sort by total; and then use + // that to build the result object. This makes the ordering of entries + // more interesting, and a little less non-deterministic. + mozilla::Vector entries; + if (!entries.reserve(count.table.count())) + return false; + for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) + entries.infallibleAppend(&r.front()); + qsort(entries.begin(), entries.length(), sizeof(*entries.begin()), compareEntries); + + // Now build the result by iterating over the sorted vector. + RootedPlainObject obj(cx, NewBuiltinClassInstance(cx)); + if (!obj) + return false; + for (Entry** entryPtr = entries.begin(); entryPtr < entries.end(); entryPtr++) { + Entry& entry = **entryPtr; + CountBasePtr& typeCount = entry.value(); + RootedValue typeReport(cx); + if (!typeCount->report(&typeReport)) + return false; + + const char16_t* name = entry.key(); + MOZ_ASSERT(name); + JSAtom* atom = AtomizeChars(cx, name, js_strlen(name)); + if (!atom) + return false; + RootedId entryId(cx, AtomToId(atom)); + + if (!DefineProperty(cx, obj, entryId, typeReport)) + return false; + } + + report.setObject(*obj); + return true; +} + + +// A count type that categorizes nodes by the JS stack under which they were +// allocated. +class ByAllocationStack : public CountType { + using Table = HashMap, + SystemAllocPolicy>; + using Entry = Table::Entry; + + struct Count : public CountBase { + // NOTE: You may look up entries in this table by JS::ubi::StackFrame + // key only during traversal, NOT ONCE TRAVERSAL IS COMPLETE. Once + // traversal is complete, you may only iterate over it. + // + // In this hash table, keys are JSObjects (with some indirection), and + // we use JSObject identity (that is, address identity) as key + // identity. The normal way to support such a table is to make the trace + // function notice keys that have moved and re-key them in the + // table. However, our trace function does *not* rehash; the first GC + // may render the hash table unsearchable. + // + // This is as it should be: + // + // First, the heap traversal phase needs lookups by key to work. But no + // GC may ever occur during a traversal; this is enforced by the + // JS::ubi::BreadthFirst template. So the traceCount function doesn't + // need to do anything to help traversal; it never even runs then. + // + // Second, the report phase needs iteration over the table to work, but + // never looks up entries by key. GC may well occur during this phase: + // we allocate a Map object, and probably cross-compartment wrappers for + // SavedFrame instances as well. If a GC were to occur, it would call + // our traceCount function; if traceCount were to re-key, that would + // ruin the traversal in progress. + // + // So depending on the phase, we either don't need re-keying, or + // can't abide it. + Table table; + CountBasePtr noStack; + + Count(CountType& type, CountBasePtr& noStack) + : CountBase(type), + noStack(Move(noStack)) + { } + bool init() { return table.init(); } + }; + + CountTypePtr entryType; + CountTypePtr noStackType; + + public: + ByAllocationStack(Census& census, CountTypePtr& entryType, CountTypePtr& noStackType) + : CountType(census), + entryType(Move(entryType)), + noStackType(Move(noStackType)) + { } + + void destructCount(CountBase& countBase) override { + Count& count = static_cast(countBase); + count.~Count(); + } + + CountBasePtr makeCount() override; + void traceCount(CountBase& countBase, JSTracer* trc) override; + bool count(CountBase& countBase, const Node& node) override; + bool report(CountBase& countBase, MutableHandleValue report) override; +}; + +CountBasePtr +ByAllocationStack::makeCount() +{ + CountBasePtr noStackCount(noStackType->makeCount()); + if (!noStackCount) + return nullptr; + + UniquePtr count(census.new_(*this, noStackCount)); + if (!count || !count->init()) + return nullptr; + return CountBasePtr(count.release()); +} + +void +ByAllocationStack::traceCount(CountBase& countBase, JSTracer* trc) +{ + Count& count = static_cast(countBase); + for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) { + // Trace our child Counts. + r.front().value()->trace(trc); + + // Trace the StackFrame that is this entry's key. Do not re-key if + // it has moved; see comments for ByAllocationStack::Count::table. + const StackFrame* key = &r.front().key(); + auto& k = *const_cast(key); + k.trace(trc); + } + count.noStack->trace(trc); +} + +bool +ByAllocationStack::count(CountBase& countBase, const Node& node) +{ + Count& count = static_cast(countBase); + count.total_++; + + // If we do have an allocation stack for this node, include it in the + // count for that stack. + if (node.hasAllocationStack()) { + auto allocationStack = node.allocationStack(); + auto p = count.table.lookupForAdd(allocationStack); + if (!p) { + CountBasePtr stackCount(entryType->makeCount()); + if (!stackCount || !count.table.add(p, allocationStack, Move(stackCount))) + return false; + } + MOZ_ASSERT(p); + return p->value()->count(node); + } + + // Otherwise, count it in the "no stack" category. + return count.noStack->count(node); +} + +bool +ByAllocationStack::report(CountBase& countBase, MutableHandleValue report) +{ + Count& count = static_cast(countBase); + JSContext* cx = census.cx; + +#ifdef DEBUG + // Check that nothing rehashes our table while we hold pointers into it. + uint32_t generation = count.table.generation(); +#endif + + // Build a vector of pointers to entries; sort by total; and then use + // that to build the result object. This makes the ordering of entries + // more interesting, and a little less non-deterministic. + mozilla::Vector entries; + if (!entries.reserve(count.table.count())) + return false; + for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) + entries.infallibleAppend(&r.front()); + qsort(entries.begin(), entries.length(), sizeof(*entries.begin()), compareEntries); + + // Now build the result by iterating over the sorted vector. + Rooted map(cx, MapObject::create(cx)); + if (!map) + return false; + for (Entry** entryPtr = entries.begin(); entryPtr < entries.end(); entryPtr++) { + Entry& entry = **entryPtr; + MOZ_ASSERT(entry.key()); + + RootedObject stack(cx); + if (!entry.key().constructSavedFrameStack(cx, &stack) || + !cx->compartment()->wrap(cx, &stack)) + { + return false; + } + RootedValue stackVal(cx, ObjectValue(*stack)); + + CountBasePtr& stackCount = entry.value(); + RootedValue stackReport(cx); + if (!stackCount->report(&stackReport)) + return false; + + if (!MapObject::set(cx, map, stackVal, stackReport)) + return false; + } + + if (count.noStack->total_ > 0) { + RootedValue noStackReport(cx); + if (!count.noStack->report(&noStackReport)) + return false; + RootedValue noStack(cx, StringValue(cx->names().noStack)); + if (!MapObject::set(cx, map, noStack, noStackReport)) + return false; + } + + MOZ_ASSERT(generation == count.table.generation()); + + report.setObject(*map); + return true; +} + + +/*** Census Handler *******************************************************************************/ + +bool +CensusHandler::operator() (BreadthFirst& traversal, + Node origin, const Edge& edge, + NodeData* referentData, bool first) +{ + // We're only interested in the first time we reach edge.referent, not + // in every edge arriving at that node. + if (!first) + return true; + + // Don't count nodes outside the debuggee zones. Do count things in the + // special atoms zone, but don't traverse their outgoing edges, on the + // assumption that they are shared resources that debuggee is using. + // Symbols are always allocated in the atoms zone, even if they were + // created for exactly one compartment and never shared; this rule will + // include such nodes in the count. + const Node& referent = edge.referent; + Zone* zone = referent.zone(); + + if (census.targetZones.count() == 0 || census.targetZones.has(zone)) + return rootCount->count(referent); + + if (zone == census.atomsZone) { + traversal.abandonReferent(); + return rootCount->count(referent); + } + + traversal.abandonReferent(); + return true; +} + + +/*** Parsing Breakdowns ***************************************************************************/ + +static CountTypePtr ParseBreakdown(Census& census, HandleValue breakdownValue); + +static CountTypePtr +ParseChildBreakdown(Census& census, HandleObject breakdown, PropertyName* prop) +{ + JSContext* cx = census.cx; + RootedValue v(cx); + if (!GetProperty(cx, breakdown, breakdown, prop, &v)) + return nullptr; + return ParseBreakdown(census, v); +} + +static CountTypePtr +ParseBreakdown(Census& census, HandleValue breakdownValue) +{ + JSContext* cx = census.cx; + + if (breakdownValue.isUndefined()) { + // Construct the default type, { by: 'count' } + CountTypePtr simple(census.new_(census)); + return simple; + } + + RootedObject breakdown(cx, ToObject(cx, breakdownValue)); + if (!breakdown) + return nullptr; + + RootedValue byValue(cx); + if (!GetProperty(cx, breakdown, breakdown, cx->names().by, &byValue)) + return nullptr; + RootedString byString(cx, ToString(cx, byValue)); + if (!byString) + return nullptr; + RootedLinearString by(cx, byString->ensureLinear(cx)); + if (!by) + return nullptr; + + if (StringEqualsAscii(by, "count")) { + RootedValue countValue(cx), bytesValue(cx); + if (!GetProperty(cx, breakdown, breakdown, cx->names().count, &countValue) || + !GetProperty(cx, breakdown, breakdown, cx->names().bytes, &bytesValue)) + return nullptr; + + // Both 'count' and 'bytes' default to true if omitted, but ToBoolean + // naturally treats 'undefined' as false; fix this up. + if (countValue.isUndefined()) countValue.setBoolean(true); + if (bytesValue.isUndefined()) bytesValue.setBoolean(true); + + // Undocumented feature, for testing: { by: 'count' } breakdowns can have + // a 'label' property whose value is converted to a string and included as + // a 'label' property on the report object. + RootedValue label(cx); + if (!GetProperty(cx, breakdown, breakdown, cx->names().label, &label)) + return nullptr; + + UniquePtr labelUnique(nullptr); + if (!label.isUndefined()) { + RootedString labelString(cx, ToString(cx, label)); + if (!labelString) + return nullptr; + + JSFlatString* flat = labelString->ensureFlat(cx); + if (!flat) + return nullptr; + + AutoStableStringChars chars(cx); + if (!chars.initTwoByte(cx, flat)) + return nullptr; + + // Since flat strings are null-terminated, and AutoStableStringChars + // null- terminates if it needs to make a copy, we know that + // chars.twoByteChars() is null-terminated. + labelUnique = DuplicateString(cx, chars.twoByteChars()); + if (!labelUnique) + return nullptr; + } + + CountTypePtr simple(census.new_(census, + labelUnique, + ToBoolean(countValue), + ToBoolean(bytesValue))); + return simple; + } + + if (StringEqualsAscii(by, "objectClass")) { + CountTypePtr thenType(ParseChildBreakdown(census, breakdown, cx->names().then)); + if (!thenType) + return nullptr; + + CountTypePtr otherType(ParseChildBreakdown(census, breakdown, cx->names().other)); + if (!otherType) + return nullptr; + + return CountTypePtr(census.new_(census, thenType, otherType)); + } + + if (StringEqualsAscii(by, "coarseType")) { + CountTypePtr objectsType(ParseChildBreakdown(census, breakdown, cx->names().objects)); + if (!objectsType) + return nullptr; + CountTypePtr scriptsType(ParseChildBreakdown(census, breakdown, cx->names().scripts)); + if (!scriptsType) + return nullptr; + CountTypePtr stringsType(ParseChildBreakdown(census, breakdown, cx->names().strings)); + if (!stringsType) + return nullptr; + CountTypePtr otherType(ParseChildBreakdown(census, breakdown, cx->names().other)); + if (!otherType) + return nullptr; + + return CountTypePtr(census.new_(census, + objectsType, + scriptsType, + stringsType, + otherType)); + } + + if (StringEqualsAscii(by, "internalType")) { + CountTypePtr thenType(ParseChildBreakdown(census, breakdown, cx->names().then)); + if (!thenType) + return nullptr; + + return CountTypePtr(census.new_(census, thenType)); + } + + if (StringEqualsAscii(by, "allocationStack")) { + CountTypePtr thenType(ParseChildBreakdown(census, breakdown, cx->names().then)); + if (!thenType) + return nullptr; + CountTypePtr noStackType(ParseChildBreakdown(census, breakdown, cx->names().noStack)); + if (!noStackType) + return nullptr; + + return CountTypePtr(census.new_(census, thenType, noStackType)); + } + + // We didn't recognize the breakdown type; complain. + RootedString bySource(cx, ValueToSource(cx, byValue)); + if (!bySource) + return nullptr; + + JSAutoByteString byBytes(cx, bySource); + if (!byBytes) + return nullptr; + + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_CENSUS_BREAKDOWN, + byBytes.ptr()); + return nullptr; +} + +// Get the default census breakdown: +// +// { by: "coarseType", +// objects: { by: "objectClass" }, +// other: { by: "internalType" } +// } +static CountTypePtr +GetDefaultBreakdown(Census& census) +{ + CountTypePtr byClass(census.new_(census)); + CountTypePtr byClassElse(census.new_(census)); + CountTypePtr objects(census.new_(census, + byClass, + byClassElse)); + + CountTypePtr scripts(census.new_(census)); + CountTypePtr strings(census.new_(census)); + + CountTypePtr byType(census.new_(census)); + CountTypePtr other(census.new_(census, byType)); + + return CountTypePtr(census.new_(census, + objects, + scripts, + strings, + other)); +} + +bool +ParseCensusOptions(JSContext* cx, Census& census, HandleObject options, CountTypePtr& outResult) +{ + RootedValue breakdown(cx, UndefinedValue()); + if (options && !GetProperty(cx, options, options, cx->names().breakdown, &breakdown)) + return false; + + outResult = breakdown.isUndefined() + ? GetDefaultBreakdown(census) + : ParseBreakdown(census, breakdown); + return !!outResult; +} + +} // namespace ubi +} // namespace JS diff --git a/js/xpconnect/src/xpcprivate.h b/js/xpconnect/src/xpcprivate.h index bb300ece00..081a57983c 100644 --- a/js/xpconnect/src/xpcprivate.h +++ b/js/xpconnect/src/xpcprivate.h @@ -3044,7 +3044,7 @@ private: /****************************************************************************** * Handles pre/post script processing. */ -class MOZ_STACK_CLASS AutoScriptEvaluate +class MOZ_RAII AutoScriptEvaluate { public: /** @@ -3081,7 +3081,7 @@ private: }; /***************************************************************************/ -class MOZ_STACK_CLASS AutoResolveName +class MOZ_RAII AutoResolveName { public: AutoResolveName(XPCCallContext& ccx, JS::HandleId name diff --git a/layout/generic/nsFlexContainerFrame.cpp b/layout/generic/nsFlexContainerFrame.cpp index 15b97f5ba0..1ec53ba441 100644 --- a/layout/generic/nsFlexContainerFrame.cpp +++ b/layout/generic/nsFlexContainerFrame.cpp @@ -3686,7 +3686,7 @@ nsFlexContainerFrame::Reflow(nsPresContext* aPresContext, // RAII class to clean up a list of FlexLines. // Specifically, this removes each line from the list, deletes all the // FlexItems in its list, and deletes the FlexLine. -class MOZ_STACK_CLASS AutoFlexLineListClearer +class MOZ_RAII AutoFlexLineListClearer { public: explicit AutoFlexLineListClearer(LinkedList& aLines diff --git a/layout/generic/nsSelection.cpp b/layout/generic/nsSelection.cpp index 90f816ffb3..cd392398cf 100644 --- a/layout/generic/nsSelection.cpp +++ b/layout/generic/nsSelection.cpp @@ -335,7 +335,7 @@ IsValidSelectionPoint(nsFrameSelection *aFrameSel, nsINode *aNode) } namespace mozilla { -struct MOZ_STACK_CLASS AutoPrepareFocusRange +struct MOZ_RAII AutoPrepareFocusRange { AutoPrepareFocusRange(Selection* aSelection, bool aContinueSelection, diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 3a22612317..331ac10331 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -428,7 +428,7 @@ protected: * state (in a CSSParserInputState object), and it restores the parser to * that state when destructed, unless "DoNotRestore()" has been called. */ - class MOZ_STACK_CLASS nsAutoCSSParserInputStateRestorer { + class MOZ_RAII nsAutoCSSParserInputStateRestorer { public: explicit nsAutoCSSParserInputStateRestorer(CSSParserImpl* aParser MOZ_GUARD_OBJECT_NOTIFIER_PARAM) @@ -476,7 +476,7 @@ protected: * XXXdholbert we could also change this & report errors, if needed. Might * want to customize the error reporting somehow though. */ - class MOZ_STACK_CLASS nsAutoScannerChanger { + class MOZ_RAII nsAutoScannerChanger { public: nsAutoScannerChanger(CSSParserImpl* aParser, const nsAString& aStringToScan diff --git a/layout/style/nsRuleProcessorData.h b/layout/style/nsRuleProcessorData.h index b7f9cc3ea1..b8516776cc 100644 --- a/layout/style/nsRuleProcessorData.h +++ b/layout/style/nsRuleProcessorData.h @@ -225,7 +225,7 @@ struct MOZ_STACK_CLASS TreeMatchContext { } /* Helper class for maintaining the ancestor state */ - class MOZ_STACK_CLASS AutoAncestorPusher { + class MOZ_RAII AutoAncestorPusher { public: explicit AutoAncestorPusher(TreeMatchContext& aTreeMatchContext MOZ_GUARD_OBJECT_NOTIFIER_PARAM) @@ -295,7 +295,7 @@ struct MOZ_STACK_CLASS TreeMatchContext { * in cases where we may or may not want to be skipping flex/grid-item * style fixup for a particular chunk of code). */ - class MOZ_STACK_CLASS AutoParentDisplayBasedStyleFixupSkipper { + class MOZ_RAII AutoParentDisplayBasedStyleFixupSkipper { public: explicit AutoParentDisplayBasedStyleFixupSkipper(TreeMatchContext& aTreeMatchContext, bool aSkipParentDisplayBasedStyleFixup = true diff --git a/layout/svg/nsSVGClipPathFrame.h b/layout/svg/nsSVGClipPathFrame.h index 8dad2aff79..2674a140ff 100644 --- a/layout/svg/nsSVGClipPathFrame.h +++ b/layout/svg/nsSVGClipPathFrame.h @@ -104,7 +104,7 @@ public: // automatically sets and clears the mInUse flag on the clip path frame // (to prevent nasty reference loops). It's easy to mess this up // and break things, so this helper makes the code far more robust. - class MOZ_STACK_CLASS AutoClipPathReferencer + class MOZ_RAII AutoClipPathReferencer { public: explicit AutoClipPathReferencer(nsSVGClipPathFrame *aFrame diff --git a/layout/svg/nsSVGFilterFrame.cpp b/layout/svg/nsSVGFilterFrame.cpp index f21d0e137f..741e91c374 100644 --- a/layout/svg/nsSVGFilterFrame.cpp +++ b/layout/svg/nsSVGFilterFrame.cpp @@ -27,7 +27,7 @@ NS_NewSVGFilterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) NS_IMPL_FRAMEARENA_HELPERS(nsSVGFilterFrame) -class MOZ_STACK_CLASS nsSVGFilterFrame::AutoFilterReferencer +class MOZ_RAII nsSVGFilterFrame::AutoFilterReferencer { public: explicit AutoFilterReferencer(nsSVGFilterFrame *aFrame MOZ_GUARD_OBJECT_NOTIFIER_PARAM) diff --git a/layout/svg/nsSVGGradientFrame.cpp b/layout/svg/nsSVGGradientFrame.cpp index 528acaeaec..360c594efa 100644 --- a/layout/svg/nsSVGGradientFrame.cpp +++ b/layout/svg/nsSVGGradientFrame.cpp @@ -24,7 +24,7 @@ using namespace mozilla::dom; //---------------------------------------------------------------------- // Helper classes -class MOZ_STACK_CLASS nsSVGGradientFrame::AutoGradientReferencer +class MOZ_RAII nsSVGGradientFrame::AutoGradientReferencer { public: explicit AutoGradientReferencer(nsSVGGradientFrame *aFrame diff --git a/layout/svg/nsSVGMarkerFrame.h b/layout/svg/nsSVGMarkerFrame.h index 0e31c37140..d23fb9f7cb 100644 --- a/layout/svg/nsSVGMarkerFrame.h +++ b/layout/svg/nsSVGMarkerFrame.h @@ -110,7 +110,7 @@ private: // prevent nasty reference loops) as well as the reference to the marked // frame and its coordinate context. It's easy to mess this up // and break things, so this helper makes the code far more robust. - class MOZ_STACK_CLASS AutoMarkerReferencer + class MOZ_RAII AutoMarkerReferencer { public: AutoMarkerReferencer(nsSVGMarkerFrame *aFrame, diff --git a/layout/svg/nsSVGMaskFrame.h b/layout/svg/nsSVGMaskFrame.h index a93c96898d..f3e4cfbef1 100644 --- a/layout/svg/nsSVGMaskFrame.h +++ b/layout/svg/nsSVGMaskFrame.h @@ -100,7 +100,7 @@ private: // automatically sets and clears the mInUse flag on the mask frame // (to prevent nasty reference loops). It's easy to mess this up // and break things, so this helper makes the code far more robust. - class MOZ_STACK_CLASS AutoMaskReferencer + class MOZ_RAII AutoMaskReferencer { public: explicit AutoMaskReferencer(nsSVGMaskFrame *aFrame diff --git a/layout/svg/nsSVGPatternFrame.cpp b/layout/svg/nsSVGPatternFrame.cpp index 39b5ecd5ca..47e1b205e2 100644 --- a/layout/svg/nsSVGPatternFrame.cpp +++ b/layout/svg/nsSVGPatternFrame.cpp @@ -32,7 +32,7 @@ using namespace mozilla::gfx; //---------------------------------------------------------------------- // Helper classes -class MOZ_STACK_CLASS nsSVGPatternFrame::AutoPatternReferencer +class MOZ_RAII nsSVGPatternFrame::AutoPatternReferencer { public: explicit AutoPatternReferencer(nsSVGPatternFrame *aFrame diff --git a/layout/svg/nsSVGUtils.h b/layout/svg/nsSVGUtils.h index 9ffbd5768c..8c982a768a 100644 --- a/layout/svg/nsSVGUtils.h +++ b/layout/svg/nsSVGUtils.h @@ -137,7 +137,7 @@ private: // GRRR WINDOWS HATE HATE HATE #undef CLIP_MASK -class MOZ_STACK_CLASS SVGAutoRenderState +class MOZ_RAII SVGAutoRenderState { typedef mozilla::gfx::DrawTarget DrawTarget; diff --git a/mfbt/ReentrancyGuard.h b/mfbt/ReentrancyGuard.h index d5142073c1..9963974e73 100644 --- a/mfbt/ReentrancyGuard.h +++ b/mfbt/ReentrancyGuard.h @@ -16,7 +16,7 @@ namespace mozilla { /* Useful for implementing containers that assert non-reentrancy */ -class ReentrancyGuard +class MOZ_RAII ReentrancyGuard { MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER #ifdef DEBUG diff --git a/mfbt/ScopeExit.h b/mfbt/ScopeExit.h index ff38bf4774..7aff82d8a1 100644 --- a/mfbt/ScopeExit.h +++ b/mfbt/ScopeExit.h @@ -86,7 +86,7 @@ namespace mozilla { template -class ScopeExit { +class MOZ_STACK_CLASS ScopeExit { ExitFunction mExitFunction; bool mExecuteOnDestruction; MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER diff --git a/mfbt/Scoped.h b/mfbt/Scoped.h index 7855cde950..de8a166f06 100644 --- a/mfbt/Scoped.h +++ b/mfbt/Scoped.h @@ -77,7 +77,7 @@ namespace mozilla { * } */ template -class Scoped +class MOZ_NON_TEMPORARY_CLASS Scoped { public: typedef typename Traits::type Resource; @@ -187,7 +187,7 @@ private: */ #define SCOPED_TEMPLATE(name, Traits) \ template \ -struct name : public mozilla::Scoped > \ +struct MOZ_NON_TEMPORARY_CLASS name : public mozilla::Scoped > \ { \ typedef mozilla::Scoped > Super; \ typedef typename Super::Resource Resource; \ diff --git a/mozglue/build/WindowsDllBlocklist.h b/mozglue/build/WindowsDllBlocklist.h index e33ec908fe..b66704764f 100644 --- a/mozglue/build/WindowsDllBlocklist.h +++ b/mozglue/build/WindowsDllBlocklist.h @@ -18,7 +18,7 @@ NS_IMPORT void DllBlocklist_Initialize(); NS_IMPORT void DllBlocklist_SetInXPCOMLoadOnMainThread(bool inXPCOMLoadOnMainThread); NS_IMPORT void DllBlocklist_WriteNotes(HANDLE file); -class AutoSetXPCOMLoadOnMainThread +class MOZ_RAII AutoSetXPCOMLoadOnMainThread { public: AutoSetXPCOMLoadOnMainThread(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM) { diff --git a/toolkit/components/osfile/NativeOSFileInternals.cpp b/toolkit/components/osfile/NativeOSFileInternals.cpp index 5f9282a1bd..b6036e98e4 100644 --- a/toolkit/components/osfile/NativeOSFileInternals.cpp +++ b/toolkit/components/osfile/NativeOSFileInternals.cpp @@ -91,7 +91,7 @@ struct ScopedArrayBufferContentsTraits { } }; -struct ScopedArrayBufferContents: public Scoped { +struct MOZ_NON_TEMPORARY_CLASS ScopedArrayBufferContents: public Scoped { explicit ScopedArrayBufferContents(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM): Scoped(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_TO_PARENT) { } diff --git a/toolkit/components/telemetry/Telemetry.h b/toolkit/components/telemetry/Telemetry.h index 13e2f7a718..2f0400d287 100644 --- a/toolkit/components/telemetry/Telemetry.h +++ b/toolkit/components/telemetry/Telemetry.h @@ -118,7 +118,7 @@ struct AccumulateDelta_impl template -class AutoTimer { +class MOZ_RAII AutoTimer { public: explicit AutoTimer(TimeStamp aStart = TimeStamp::Now() MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : start(aStart) @@ -148,7 +148,7 @@ private: }; template -class AutoCounter { +class MOZ_RAII AutoCounter { public: explicit AutoCounter(uint32_t counterStart = 0 MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : counter(counterStart) diff --git a/toolkit/devtools/server/AutoMemMap.h b/toolkit/devtools/server/AutoMemMap.h index e6943a4122..a086a4590b 100644 --- a/toolkit/devtools/server/AutoMemMap.h +++ b/toolkit/devtools/server/AutoMemMap.h @@ -28,7 +28,7 @@ namespace devtools { // doStuffWithMappedMemory(mm.address()); // } // // The memory is automatically unmapped when the AutoMemMap leaves scope. -class MOZ_STACK_CLASS AutoMemMap +class MOZ_RAII AutoMemMap { MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER; diff --git a/toolkit/devtools/server/CoreDump.pb.cc b/toolkit/devtools/server/CoreDump.pb.cc index 44c9c29638..168feed16c 100644 --- a/toolkit/devtools/server/CoreDump.pb.cc +++ b/toolkit/devtools/server/CoreDump.pb.cc @@ -108,12 +108,14 @@ void protobuf_AssignDesc_CoreDump_2eproto() { ::google::protobuf::MessageFactory::generated_factory(), sizeof(StackFrame_Data)); Node_descriptor_ = file->message_type(2); - static const int Node_offsets_[5] = { + static const int Node_offsets_[7] = { GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, id_), GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, typename__), GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, size_), GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, edges_), GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, allocationstack_), + GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, jsobjectclassname_), + GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, coarsetype_), }; Node_reflection_ = new ::google::protobuf::internal::GeneratedMessageReflection( @@ -198,12 +200,13 @@ void protobuf_AddDesc_CoreDump_2eproto() { "\022\014\n\004line\030\003 \001(\r\022\016\n\006column\030\004 \001(\r\022\016\n\006source" "\030\005 \001(\014\022\033\n\023functionDisplayName\030\006 \001(\014\022\020\n\010i" "sSystem\030\007 \001(\010\022\024\n\014isSelfHosted\030\010 \001(\010B\020\n\016S" - "tackFrameType\"\242\001\n\004Node\022\n\n\002id\030\001 \001(\004\022\020\n\010ty" + "tackFrameType\"\324\001\n\004Node\022\n\n\002id\030\001 \001(\004\022\020\n\010ty" "peName\030\002 \001(\014\022\014\n\004size\030\003 \001(\004\022.\n\005edges\030\004 \003(" "\0132\037.mozilla.devtools.protobuf.Edge\022>\n\017al" "locationStack\030\005 \001(\0132%.mozilla.devtools.p" - "rotobuf.StackFrame\"&\n\004Edge\022\020\n\010referent\030\001" - " \001(\004\022\014\n\004name\030\002 \001(\014", 578); + "rotobuf.StackFrame\022\031\n\021jsObjectClassName\030" + "\006 \001(\014\022\025\n\ncoarseType\030\007 \001(\r:\0010\"&\n\004Edge\022\020\n\010" + "referent\030\001 \001(\004\022\014\n\004name\030\002 \001(\014", 628); ::google::protobuf::MessageFactory::InternalRegisterGeneratedFile( "CoreDump.proto", &protobuf_RegisterTypes); Metadata::default_instance_ = new Metadata(); @@ -1276,6 +1279,8 @@ const int Node::kTypeNameFieldNumber; const int Node::kSizeFieldNumber; const int Node::kEdgesFieldNumber; const int Node::kAllocationStackFieldNumber; +const int Node::kJsObjectClassNameFieldNumber; +const int Node::kCoarseTypeFieldNumber; #endif // !_MSC_VER Node::Node() @@ -1302,6 +1307,8 @@ void Node::SharedCtor() { typename__ = const_cast< ::std::string*>(&::google::protobuf::internal::GetEmptyStringAlreadyInited()); size_ = GOOGLE_ULONGLONG(0); allocationstack_ = NULL; + jsobjectclassname_ = const_cast< ::std::string*>(&::google::protobuf::internal::GetEmptyStringAlreadyInited()); + coarsetype_ = 0u; ::memset(_has_bits_, 0, sizeof(_has_bits_)); } @@ -1314,6 +1321,9 @@ void Node::SharedDtor() { if (typename__ != &::google::protobuf::internal::GetEmptyStringAlreadyInited()) { delete typename__; } + if (jsobjectclassname_ != &::google::protobuf::internal::GetEmptyStringAlreadyInited()) { + delete jsobjectclassname_; + } if (this != default_instance_) { delete allocationstack_; } @@ -1341,7 +1351,7 @@ Node* Node::New() const { } void Node::Clear() { - if (_has_bits_[0 / 32] & 23) { + if (_has_bits_[0 / 32] & 119) { id_ = GOOGLE_ULONGLONG(0); if (has_typename_()) { if (typename__ != &::google::protobuf::internal::GetEmptyStringAlreadyInited()) { @@ -1352,6 +1362,12 @@ void Node::Clear() { if (has_allocationstack()) { if (allocationstack_ != NULL) allocationstack_->::mozilla::devtools::protobuf::StackFrame::Clear(); } + if (has_jsobjectclassname()) { + if (jsobjectclassname_ != &::google::protobuf::internal::GetEmptyStringAlreadyInited()) { + jsobjectclassname_->clear(); + } + } + coarsetype_ = 0u; } edges_.Clear(); ::memset(_has_bits_, 0, sizeof(_has_bits_)); @@ -1433,6 +1449,34 @@ bool Node::MergePartialFromCodedStream( } else { goto handle_unusual; } + if (input->ExpectTag(50)) goto parse_jsObjectClassName; + break; + } + + // optional bytes jsObjectClassName = 6; + case 6: { + if (tag == 50) { + parse_jsObjectClassName: + DO_(::google::protobuf::internal::WireFormatLite::ReadBytes( + input, this->mutable_jsobjectclassname())); + } else { + goto handle_unusual; + } + if (input->ExpectTag(56)) goto parse_coarseType; + break; + } + + // optional uint32 coarseType = 7 [default = 0]; + case 7: { + if (tag == 56) { + parse_coarseType: + DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive< + ::google::protobuf::uint32, ::google::protobuf::internal::WireFormatLite::TYPE_UINT32>( + input, &coarsetype_))); + set_has_coarsetype(); + } else { + goto handle_unusual; + } if (input->ExpectAtEnd()) goto success; break; } @@ -1490,6 +1534,17 @@ void Node::SerializeWithCachedSizes( 5, this->allocationstack(), output); } + // optional bytes jsObjectClassName = 6; + if (has_jsobjectclassname()) { + ::google::protobuf::internal::WireFormatLite::WriteBytesMaybeAliased( + 6, this->jsobjectclassname(), output); + } + + // optional uint32 coarseType = 7 [default = 0]; + if (has_coarsetype()) { + ::google::protobuf::internal::WireFormatLite::WriteUInt32(7, this->coarsetype(), output); + } + if (!unknown_fields().empty()) { ::google::protobuf::internal::WireFormat::SerializeUnknownFields( unknown_fields(), output); @@ -1531,6 +1586,18 @@ void Node::SerializeWithCachedSizes( 5, this->allocationstack(), target); } + // optional bytes jsObjectClassName = 6; + if (has_jsobjectclassname()) { + target = + ::google::protobuf::internal::WireFormatLite::WriteBytesToArray( + 6, this->jsobjectclassname(), target); + } + + // optional uint32 coarseType = 7 [default = 0]; + if (has_coarsetype()) { + target = ::google::protobuf::internal::WireFormatLite::WriteUInt32ToArray(7, this->coarsetype(), target); + } + if (!unknown_fields().empty()) { target = ::google::protobuf::internal::WireFormat::SerializeUnknownFieldsToArray( unknown_fields(), target); @@ -1571,6 +1638,20 @@ int Node::ByteSize() const { this->allocationstack()); } + // optional bytes jsObjectClassName = 6; + if (has_jsobjectclassname()) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::BytesSize( + this->jsobjectclassname()); + } + + // optional uint32 coarseType = 7 [default = 0]; + if (has_coarsetype()) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::UInt32Size( + this->coarsetype()); + } + } // repeated .mozilla.devtools.protobuf.Edge edges = 4; total_size += 1 * this->edges_size(); @@ -1619,6 +1700,12 @@ void Node::MergeFrom(const Node& from) { if (from.has_allocationstack()) { mutable_allocationstack()->::mozilla::devtools::protobuf::StackFrame::MergeFrom(from.allocationstack()); } + if (from.has_jsobjectclassname()) { + set_jsobjectclassname(from.jsobjectclassname()); + } + if (from.has_coarsetype()) { + set_coarsetype(from.coarsetype()); + } } mutable_unknown_fields()->MergeFrom(from.unknown_fields()); } @@ -1647,6 +1734,8 @@ void Node::Swap(Node* other) { std::swap(size_, other->size_); edges_.Swap(&other->edges_); std::swap(allocationstack_, other->allocationstack_); + std::swap(jsobjectclassname_, other->jsobjectclassname_); + std::swap(coarsetype_, other->coarsetype_); std::swap(_has_bits_[0], other->_has_bits_[0]); _unknown_fields_.Swap(&other->_unknown_fields_); std::swap(_cached_size_, other->_cached_size_); diff --git a/toolkit/devtools/server/CoreDump.pb.h b/toolkit/devtools/server/CoreDump.pb.h index b894a5985d..d63895100d 100644 --- a/toolkit/devtools/server/CoreDump.pb.h +++ b/toolkit/devtools/server/CoreDump.pb.h @@ -489,6 +489,25 @@ class Node : public ::google::protobuf::Message { inline ::mozilla::devtools::protobuf::StackFrame* release_allocationstack(); inline void set_allocated_allocationstack(::mozilla::devtools::protobuf::StackFrame* allocationstack); + // optional bytes jsObjectClassName = 6; + inline bool has_jsobjectclassname() const; + inline void clear_jsobjectclassname(); + static const int kJsObjectClassNameFieldNumber = 6; + inline const ::std::string& jsobjectclassname() const; + inline void set_jsobjectclassname(const ::std::string& value); + inline void set_jsobjectclassname(const char* value); + inline void set_jsobjectclassname(const void* value, size_t size); + inline ::std::string* mutable_jsobjectclassname(); + inline ::std::string* release_jsobjectclassname(); + inline void set_allocated_jsobjectclassname(::std::string* jsobjectclassname); + + // optional uint32 coarseType = 7 [default = 0]; + inline bool has_coarsetype() const; + inline void clear_coarsetype(); + static const int kCoarseTypeFieldNumber = 7; + inline ::google::protobuf::uint32 coarsetype() const; + inline void set_coarsetype(::google::protobuf::uint32 value); + // @@protoc_insertion_point(class_scope:mozilla.devtools.protobuf.Node) private: inline void set_has_id(); @@ -499,6 +518,10 @@ class Node : public ::google::protobuf::Message { inline void clear_has_size(); inline void set_has_allocationstack(); inline void clear_has_allocationstack(); + inline void set_has_jsobjectclassname(); + inline void clear_has_jsobjectclassname(); + inline void set_has_coarsetype(); + inline void clear_has_coarsetype(); ::google::protobuf::UnknownFieldSet _unknown_fields_; @@ -509,6 +532,8 @@ class Node : public ::google::protobuf::Message { ::google::protobuf::uint64 size_; ::google::protobuf::RepeatedPtrField< ::mozilla::devtools::protobuf::Edge > edges_; ::mozilla::devtools::protobuf::StackFrame* allocationstack_; + ::std::string* jsobjectclassname_; + ::google::protobuf::uint32 coarsetype_; friend void protobuf_AddDesc_CoreDump_2eproto(); friend void protobuf_AssignDesc_CoreDump_2eproto(); friend void protobuf_ShutdownFile_CoreDump_2eproto(); @@ -1240,6 +1265,106 @@ inline void Node::set_allocated_allocationstack(::mozilla::devtools::protobuf::S // @@protoc_insertion_point(field_set_allocated:mozilla.devtools.protobuf.Node.allocationStack) } +// optional bytes jsObjectClassName = 6; +inline bool Node::has_jsobjectclassname() const { + return (_has_bits_[0] & 0x00000020u) != 0; +} +inline void Node::set_has_jsobjectclassname() { + _has_bits_[0] |= 0x00000020u; +} +inline void Node::clear_has_jsobjectclassname() { + _has_bits_[0] &= ~0x00000020u; +} +inline void Node::clear_jsobjectclassname() { + if (jsobjectclassname_ != &::google::protobuf::internal::GetEmptyStringAlreadyInited()) { + jsobjectclassname_->clear(); + } + clear_has_jsobjectclassname(); +} +inline const ::std::string& Node::jsobjectclassname() const { + // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.jsObjectClassName) + return *jsobjectclassname_; +} +inline void Node::set_jsobjectclassname(const ::std::string& value) { + set_has_jsobjectclassname(); + if (jsobjectclassname_ == &::google::protobuf::internal::GetEmptyStringAlreadyInited()) { + jsobjectclassname_ = new ::std::string; + } + jsobjectclassname_->assign(value); + // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.jsObjectClassName) +} +inline void Node::set_jsobjectclassname(const char* value) { + set_has_jsobjectclassname(); + if (jsobjectclassname_ == &::google::protobuf::internal::GetEmptyStringAlreadyInited()) { + jsobjectclassname_ = new ::std::string; + } + jsobjectclassname_->assign(value); + // @@protoc_insertion_point(field_set_char:mozilla.devtools.protobuf.Node.jsObjectClassName) +} +inline void Node::set_jsobjectclassname(const void* value, size_t size) { + set_has_jsobjectclassname(); + if (jsobjectclassname_ == &::google::protobuf::internal::GetEmptyStringAlreadyInited()) { + jsobjectclassname_ = new ::std::string; + } + jsobjectclassname_->assign(reinterpret_cast(value), size); + // @@protoc_insertion_point(field_set_pointer:mozilla.devtools.protobuf.Node.jsObjectClassName) +} +inline ::std::string* Node::mutable_jsobjectclassname() { + set_has_jsobjectclassname(); + if (jsobjectclassname_ == &::google::protobuf::internal::GetEmptyStringAlreadyInited()) { + jsobjectclassname_ = new ::std::string; + } + // @@protoc_insertion_point(field_mutable:mozilla.devtools.protobuf.Node.jsObjectClassName) + return jsobjectclassname_; +} +inline ::std::string* Node::release_jsobjectclassname() { + clear_has_jsobjectclassname(); + if (jsobjectclassname_ == &::google::protobuf::internal::GetEmptyStringAlreadyInited()) { + return NULL; + } else { + ::std::string* temp = jsobjectclassname_; + jsobjectclassname_ = const_cast< ::std::string*>(&::google::protobuf::internal::GetEmptyStringAlreadyInited()); + return temp; + } +} +inline void Node::set_allocated_jsobjectclassname(::std::string* jsobjectclassname) { + if (jsobjectclassname_ != &::google::protobuf::internal::GetEmptyStringAlreadyInited()) { + delete jsobjectclassname_; + } + if (jsobjectclassname) { + set_has_jsobjectclassname(); + jsobjectclassname_ = jsobjectclassname; + } else { + clear_has_jsobjectclassname(); + jsobjectclassname_ = const_cast< ::std::string*>(&::google::protobuf::internal::GetEmptyStringAlreadyInited()); + } + // @@protoc_insertion_point(field_set_allocated:mozilla.devtools.protobuf.Node.jsObjectClassName) +} + +// optional uint32 coarseType = 7 [default = 0]; +inline bool Node::has_coarsetype() const { + return (_has_bits_[0] & 0x00000040u) != 0; +} +inline void Node::set_has_coarsetype() { + _has_bits_[0] |= 0x00000040u; +} +inline void Node::clear_has_coarsetype() { + _has_bits_[0] &= ~0x00000040u; +} +inline void Node::clear_coarsetype() { + coarsetype_ = 0u; + clear_has_coarsetype(); +} +inline ::google::protobuf::uint32 Node::coarsetype() const { + // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.coarseType) + return coarsetype_; +} +inline void Node::set_coarsetype(::google::protobuf::uint32 value) { + set_has_coarsetype(); + coarsetype_ = value; + // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.coarseType) +} + // ------------------------------------------------------------------- // Edge diff --git a/toolkit/devtools/server/CoreDump.proto b/toolkit/devtools/server/CoreDump.proto index b58a1406ac..a97ac364a5 100644 --- a/toolkit/devtools/server/CoreDump.proto +++ b/toolkit/devtools/server/CoreDump.proto @@ -80,12 +80,16 @@ message StackFrame { // A serialized version of `JS::ubi::Node` and its outgoing edges. message Node { - optional uint64 id = 1; + optional uint64 id = 1; // char16_t[] - optional bytes typeName = 2; - optional uint64 size = 3; - repeated Edge edges = 4; - optional StackFrame allocationStack = 5; + optional bytes typeName = 2; + optional uint64 size = 3; + repeated Edge edges = 4; + optional StackFrame allocationStack = 5; + // char[] + optional bytes jsObjectClassName = 6; + // JS::ubi::CoarseType. Defaults to Other. + optional uint32 coarseType = 7 [default = 0]; } // A serialized edge from the heap graph. @@ -93,4 +97,4 @@ message Edge { optional uint64 referent = 1; // char16_t[] optional bytes name = 2; -} \ No newline at end of file +} diff --git a/toolkit/devtools/server/DeserializedNode.cpp b/toolkit/devtools/server/DeserializedNode.cpp index fbdf4703e0..3cc2918cb1 100644 --- a/toolkit/devtools/server/DeserializedNode.cpp +++ b/toolkit/devtools/server/DeserializedNode.cpp @@ -49,39 +49,6 @@ DeserializedEdge::init(const protobuf::Edge& edge, HeapSnapshot& owner) return true; } -DeserializedNode::DeserializedNode(DeserializedNode&& rhs) -{ - id = rhs.id; - rhs.id = 0; - - typeName = rhs.typeName; - rhs.typeName = nullptr; - - size = rhs.size; - rhs.size = 0; - - edges = Move(rhs.edges); - - owner = rhs.owner; - rhs.owner = nullptr; -} - -DeserializedNode& DeserializedNode::operator=(DeserializedNode&& rhs) -{ - MOZ_ASSERT(&rhs != this); - this->~DeserializedNode(); - new(this) DeserializedNode(Move(rhs)); - return *this; -} - -DeserializedNode::DeserializedNode(NodeId id, const char16_t* typeName, uint64_t size) - : id(id) - , typeName(typeName) - , size(size) - , edges() - , owner(nullptr) -{ } - JS::ubi::Node DeserializedNode::getEdgeReferent(const DeserializedEdge& edge) { @@ -125,7 +92,7 @@ Concrete::typeName() const return get().typeName; } -size_t +Node::Size Concrete::size(mozilla::MallocSizeOf mallocSizeof) const { return get().size; @@ -179,6 +146,19 @@ public: } }; +StackFrame +Concrete::allocationStack() const +{ + MOZ_ASSERT(hasAllocationStack()); + auto id = get().allocationStack.ref(); + auto ptr = get().owner->frames.lookup(id); + 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)); +} + + UniquePtr Concrete::edges(JSContext* cx, bool) const { diff --git a/toolkit/devtools/server/DeserializedNode.h b/toolkit/devtools/server/DeserializedNode.h index 184705b43c..964c4a2fff 100644 --- a/toolkit/devtools/server/DeserializedNode.h +++ b/toolkit/devtools/server/DeserializedNode.h @@ -59,32 +59,54 @@ struct DeserializedNode { using UniqueStringPtr = UniquePtr; NodeId id; + JS::ubi::CoarseType coarseType; // A borrowed reference to a string owned by this node's owning HeapSnapshot. const char16_t* typeName; uint64_t size; EdgeVector edges; Maybe allocationStack; + UniquePtr jsObjectClassName; // 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; DeserializedNode(NodeId id, + JS::ubi::CoarseType coarseType, const char16_t* typeName, uint64_t size, EdgeVector&& edges, Maybe allocationStack, + UniquePtr&& className, HeapSnapshot& owner) : id(id) + , coarseType(coarseType) , typeName(typeName) , size(size) , edges(Move(edges)) , allocationStack(allocationStack) + , jsObjectClassName(Move(className)) , owner(&owner) { } virtual ~DeserializedNode() { } - DeserializedNode(DeserializedNode&& rhs); - DeserializedNode& operator=(DeserializedNode&& rhs); + DeserializedNode(DeserializedNode&& rhs) + : id(rhs.id) + , coarseType(rhs.coarseType) + , typeName(rhs.typeName) + , size(rhs.size) + , edges(Move(rhs.edges)) + , allocationStack(rhs.allocationStack) + , jsObjectClassName(Move(rhs.jsObjectClassName)) + , owner(rhs.owner) + { } + + DeserializedNode& operator=(DeserializedNode&& rhs) + { + MOZ_ASSERT(&rhs != this); + this->~DeserializedNode(); + new(this) DeserializedNode(Move(rhs)); + return *this; + } // Get a borrowed reference to the given edge's referent. This method is // virtual to provide a hook for gmock and gtest. @@ -94,7 +116,16 @@ struct DeserializedNode { protected: // This is only for use with `MockDeserializedNode` in testing. - DeserializedNode(NodeId id, const char16_t* typeName, uint64_t size); + DeserializedNode(NodeId id, const char16_t* typeName, uint64_t size) + : id(id) + , coarseType(JS::ubi::CoarseType::Other) + , typeName(typeName) + , size(size) + , edges() + , allocationStack(Nothing()) + , jsObjectClassName(nullptr) + , owner(nullptr) + { } private: DeserializedNode(const DeserializedNode&) = delete; @@ -222,10 +253,15 @@ public: new (storage) Concrete(ptr); } + CoarseType coarseType() const final { return get().coarseType; } Id identifier() const override { return get().id; } bool isLive() const override { return false; } const char16_t* typeName() const override; - size_t size(mozilla::MallocSizeOf mallocSizeof) const override; + Node::Size size(mozilla::MallocSizeOf mallocSizeof) const override; + const char* jsObjectClassName() const override { return get().jsObjectClassName.get(); } + + bool hasAllocationStack() const override { return get().allocationStack.isSome(); } + StackFrame allocationStack() const override; // We ignore the `bool wantNames` parameter because we can't control whether // the core dump was serialized with edge names or not. @@ -249,7 +285,7 @@ public: new (storage) ConcreteStackFrame(ptr); } - uintptr_t identifier() const override { return get().id; } + uint64_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; } diff --git a/toolkit/devtools/server/HeapSnapshot.cpp b/toolkit/devtools/server/HeapSnapshot.cpp index 01c0371dfe..04c28a1c7e 100644 --- a/toolkit/devtools/server/HeapSnapshot.cpp +++ b/toolkit/devtools/server/HeapSnapshot.cpp @@ -11,6 +11,7 @@ #include "js/Debug.h" #include "js/TypeDecls.h" +#include "js/UbiNodeCensus.h" #include "js/UbiNodeTraverse.h" #include "mozilla/Attributes.h" #include "mozilla/devtools/AutoMemMap.h" @@ -73,6 +74,8 @@ HeapSnapshot::WrapObject(JSContext* aCx, HandleObject aGivenProto) return HeapSnapshotBinding::Wrap(aCx, this, aGivenProto); } +/*** Reading Heap Snapshots ***********************************************************************/ + /* static */ already_AddRefed HeapSnapshot::Create(JSContext* cx, GlobalObject& global, @@ -123,14 +126,21 @@ HeapSnapshot::saveNode(const protobuf::Node& node) return false; NodeId id = node.id(); + // Should only deserialize each node once. + if (nodes.has(id)) + return false; + + if (!JS::ubi::Uint32IsValidCoarseType(node.coarsetype())) + return false; + auto coarseType = JS::ubi::Uint32ToCoarseType(node.coarsetype()); + if (!node.has_typename_()) return false; - const auto* duplicatedTypeName = reinterpret_cast( - node.typename_().c_str()); - const char16_t* typeName = borrowUniqueString( - duplicatedTypeName, - node.typename_().length() / sizeof(char16_t)); + auto duplicatedTypeName = reinterpret_cast( + node.typename_().data()); + auto length = node.typename_().length() / sizeof(char16_t); + auto typeName = borrowUniqueString(duplicatedTypeName, length); if (!typeName) return false; @@ -154,11 +164,25 @@ HeapSnapshot::saveNode(const protobuf::Node& node) StackFrameId id = 0; if (!saveStackFrame(node.allocationstack(), id)) return false; - allocationStack = Some(id); + allocationStack.emplace(id); + } + MOZ_ASSERT(allocationStack.isSome() == node.has_allocationstack()); + + UniquePtr jsObjectClassName; + if (node.has_jsobjectclassname()) { + auto length = node.jsobjectclassname().length(); + jsObjectClassName.reset(static_cast(malloc(length + 1))); + if (!jsObjectClassName) + return false; + strncpy(jsObjectClassName.get(), node.jsobjectclassname().data(), + length); + jsObjectClassName.get()[length] = '\0'; } - DeserializedNode dn(id, typeName, size, Move(edges), allocationStack, *this); - return nodes.putNew(id, Move(dn)); + return nodes.putNew(id, DeserializedNode(id, coarseType, typeName, size, + Move(edges), allocationStack, + Move(jsObjectClassName), + *this)); } bool @@ -213,15 +237,16 @@ HeapSnapshot::saveStackFrame(const protobuf::StackFrame& frame, return false; const char16_t* functionDisplayName = nullptr; - if (data.has_functiondisplayname()) { + if (data.has_functiondisplayname() && data.functiondisplayname().length() > 0) { auto duplicatedName = reinterpret_cast( data.functiondisplayname().data()); size_t nameLength = data.functiondisplayname().length() / sizeof(char16_t); - const char16_t* functionDisplayName = borrowUniqueString(duplicatedName, - nameLength); + functionDisplayName = borrowUniqueString(duplicatedName, nameLength); if (!functionDisplayName) return false; } + MOZ_ASSERT(!!functionDisplayName == (data.has_functiondisplayname() && + data.functiondisplayname().length() > 0)); if (!data.has_issystem()) return false; @@ -327,6 +352,60 @@ HeapSnapshot::borrowUniqueString(const char16_t* duplicateString, size_t length) return ptr->get(); } +/*** Heap Snapshot Analyses ***********************************************************************/ + +void +HeapSnapshot::TakeCensus(JSContext* cx, JS::HandleObject options, + JS::MutableHandleValue rval, ErrorResult& rv) +{ + JS::ubi::Census census(cx); + if (NS_WARN_IF(!census.init())) { + rv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + + JS::ubi::CountTypePtr rootType; + if (NS_WARN_IF(!JS::ubi::ParseCensusOptions(cx, census, options, rootType))) { + rv.Throw(NS_ERROR_UNEXPECTED); + return; + } + + JS::ubi::RootedCount rootCount(cx, rootType->makeCount()); + if (NS_WARN_IF(!rootCount)) { + rv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + + JS::ubi::CensusHandler handler(census, rootCount); + + { + JS::AutoCheckCannotGC nogc; + + JS::ubi::CensusTraversal traversal(cx, handler, nogc); + if (NS_WARN_IF(!traversal.init())) { + rv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + + if (NS_WARN_IF(!traversal.addStart(getRoot()))) { + rv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + + if (NS_WARN_IF(!traversal.traverse())) { + rv.Throw(NS_ERROR_UNEXPECTED); + return; + } + } + + if (NS_WARN_IF(!handler.report(rval))) { + rv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } +} + +/*** Saving Heap Snapshots ************************************************************************/ + // If we are only taking a snapshot of the heap affected by the given set of // globals, find the set of zones the globals are allocated within. Returns // false on OOM failure. @@ -577,6 +656,8 @@ public: protobuf::Node protobufNode; protobufNode.set_id(ubiNode.identifier()); + protobufNode.set_coarsetype(JS::ubi::CoarseTypeToUint32(ubiNode.coarseType())); + const char16_t* typeName = ubiNode.typeName(); size_t length = NS_strlen(typeName) * sizeof(char16_t); protobufNode.set_typename_(typeName, length); @@ -594,6 +675,11 @@ public: protobufNode.set_allocated_allocationstack(protoStackFrame); } + if (auto className = ubiNode.jsObjectClassName()) { + size_t length = strlen(className); + protobufNode.set_jsobjectclassname(className, length); + } + if (includeEdges) { auto edges = ubiNode.edges(cx, wantNames); if (NS_WARN_IF(!edges)) diff --git a/toolkit/devtools/server/HeapSnapshot.h b/toolkit/devtools/server/HeapSnapshot.h index 14b31171f6..0ed6156508 100644 --- a/toolkit/devtools/server/HeapSnapshot.h +++ b/toolkit/devtools/server/HeapSnapshot.h @@ -61,6 +61,7 @@ class HeapSnapshot final : public nsISupports { friend struct DeserializedNode; friend struct DeserializedStackFrame; + friend struct JS::ubi::Concrete; explicit HeapSnapshot(JSContext* cx, nsISupports* aParent) : timestamp(Nothing()) @@ -138,6 +139,18 @@ public: const char16_t* borrowUniqueString(const char16_t* duplicateString, size_t length); + + // Get the root node of this heap snapshot's graph. + JS::ubi::Node getRoot() { + MOZ_ASSERT(nodes.initialized()); + auto p = nodes.lookup(rootId); + MOZ_ASSERT(p); + const DeserializedNode& node = *p; + return JS::ubi::Node(const_cast(&node)); + } + + void TakeCensus(JSContext* cx, JS::HandleObject options, + JS::MutableHandleValue rval, ErrorResult& rv); }; // A `CoreDumpWriter` is given the data we wish to save in a core dump and diff --git a/toolkit/devtools/server/tests/gtest/DeserializedNodeUbiNodes.cpp b/toolkit/devtools/server/tests/gtest/DeserializedNodeUbiNodes.cpp index 743c90caa2..8ddd45f789 100644 --- a/toolkit/devtools/server/tests/gtest/DeserializedNodeUbiNodes.cpp +++ b/toolkit/devtools/server/tests/gtest/DeserializedNodeUbiNodes.cpp @@ -38,10 +38,14 @@ size_t fakeMallocSizeOf(const void*) { DEF_TEST(DeserializedNodeUbiNodes, { const char16_t* typeName = MOZ_UTF16("TestTypeName"); + const char* className = "MyObjectClassName"; - NodeId id = 1L << 33; - uint64_t size = 1L << 60; + NodeId id = uint64_t(1) << 33; + uint64_t size = uint64_t(1) << 60; MockDeserializedNode mocked(id, typeName, size); + mocked.jsObjectClassName = mozilla::UniquePtr(strdup(className)); + ASSERT_TRUE(!!mocked.jsObjectClassName); + mocked.coarseType = JS::ubi::CoarseType::Script; DeserializedNode& deserialized = mocked; JS::ubi::Node ubi(&deserialized); @@ -50,8 +54,10 @@ DEF_TEST(DeserializedNodeUbiNodes, { EXPECT_EQ(size, ubi.size(fakeMallocSizeOf)); EXPECT_EQ(typeName, ubi.typeName()); + EXPECT_EQ(JS::ubi::CoarseType::Script, ubi.coarseType()); EXPECT_EQ(id, ubi.identifier()); EXPECT_FALSE(ubi.isLive()); + EXPECT_EQ(strcmp(ubi.jsObjectClassName(), className), 0); // Test the ubi::Node's edges. diff --git a/toolkit/devtools/server/tests/gtest/DeserializedStackFrameUbiStackFrames.cpp b/toolkit/devtools/server/tests/gtest/DeserializedStackFrameUbiStackFrames.cpp index d016c2dbce..ac251cdaf4 100644 --- a/toolkit/devtools/server/tests/gtest/DeserializedStackFrameUbiStackFrames.cpp +++ b/toolkit/devtools/server/tests/gtest/DeserializedStackFrameUbiStackFrames.cpp @@ -21,7 +21,7 @@ struct MockDeserializedStackFrame : public DeserializedStackFrame }; DEF_TEST(DeserializedStackFrameUbiStackFrames, { - StackFrameId id = 1L << 42; + StackFrameId id = uint64_t(1) << 42; uint32_t line = 1337; uint32_t column = 9; // 3 space tabs!? const char16_t* source = MOZ_UTF16("my-javascript-file.js"); diff --git a/toolkit/devtools/server/tests/gtest/DevTools.h b/toolkit/devtools/server/tests/gtest/DevTools.h index a059aba542..cda9d724ff 100644 --- a/toolkit/devtools/server/tests/gtest/DevTools.h +++ b/toolkit/devtools/server/tests/gtest/DevTools.h @@ -186,7 +186,7 @@ class Concrete : public Base return UniquePtr(js_new(cx, get().edges)); } - size_t size(mozilla::MallocSizeOf) const override { + Size size(mozilla::MallocSizeOf) const override { return get().size; } diff --git a/toolkit/devtools/server/tests/unit/Census.jsm b/toolkit/devtools/server/tests/unit/Census.jsm new file mode 100644 index 0000000000..6a408ace0d --- /dev/null +++ b/toolkit/devtools/server/tests/unit/Census.jsm @@ -0,0 +1,165 @@ +// Functions for checking results returned by +// Debugger.Memory.prototype.takeCensus and +// HeapSnapshot.prototype.takeCensus. Adapted from js/src/jit-test/lib/census.js. + +this.EXPORTED_SYMBOLS = ["Census"]; + +this.Census = (function () { + const Census = {}; + + function dumpn(msg) { + dump("DBG-TEST: Census.jsm: " + msg + "\n"); + } + + // Census.walkCensus(subject, name, walker) + // + // Use |walker| to check |subject|, a census object of the sort returned by + // Debugger.Memory.prototype.takeCensus: a tree of objects with integers at the + // leaves. Use |name| as the name for |subject| in diagnostic messages. Return + // the number of leaves of |subject| we visited. + // + // A walker is an object with three methods: + // + // - enter(prop): Return the walker we should use to check the property of the + // subject census named |prop|. This is for recursing into the subobjects of + // the subject. + // + // - done(): Called after we have called 'enter' on every property of the + // subject. + // + // - check(value): Check |value|, a leaf in the subject. + // + // Walker methods are expected to simply throw if a node we visit doesn't look + // right. + Census.walkCensus = (subject, name, walker) => walk(subject, name, walker, 0); + function walk(subject, name, walker, count) { + if (typeof subject === 'object') { + dumpn(name); + for (let prop in subject) { + count = walk(subject[prop], + name + "[" + uneval(prop) + "]", + walker.enter(prop), + count); + } + walker.done(); + } else { + dumpn(name + " = " + uneval(subject)); + walker.check(subject); + count++; + } + + return count; + } + + // A walker that doesn't check anything. + Census.walkAnything = { + enter: () => Census.walkAnything, + done: () => undefined, + check: () => undefined + }; + + // A walker that requires all leaves to be zeros. + Census.assertAllZeros = { + enter: () => Census.assertAllZeros, + done: () => undefined, + check: elt => { if (elt !== 0) throw new Error("Census mismatch: expected zero, found " + elt); } + }; + + function expectedObject() { + throw new Error("Census mismatch: subject has leaf where basis has nested object"); + } + + function expectedLeaf() { + throw new Error("Census mismatch: subject has nested object where basis has leaf"); + } + + // Return a function that, given a 'basis' census, returns a census walker that + // compares the subject census against the basis. The returned walker calls the + // given |compare|, |missing|, and |extra| functions as follows: + // + // - compare(subjectLeaf, basisLeaf): Check a leaf of the subject against the + // corresponding leaf of the basis. + // + // - missing(prop, value): Called when the subject is missing a property named + // |prop| which is present in the basis with value |value|. + // + // - extra(prop): Called when the subject has a property named |prop|, but the + // basis has no such property. This should return a walker that can check + // the subject's value. + function makeBasisChecker({compare, missing, extra}) { + return function makeWalker(basis) { + if (typeof basis === 'object') { + var unvisited = new Set(Object.getOwnPropertyNames(basis)); + return { + enter: prop => { + unvisited.delete(prop); + if (prop in basis) { + return makeWalker(basis[prop]); + } else { + return extra(prop); + } + }, + + done: () => unvisited.forEach(prop => missing(prop, basis[prop])), + check: expectedObject + }; + } else { + return { + enter: expectedLeaf, + done: expectedLeaf, + check: elt => compare(elt, basis) + }; + } + }; + } + + function missingProp(prop) { + throw new Error("Census mismatch: subject lacks property present in basis: " + prop); + } + + function extraProp(prop) { + throw new Error("Census mismatch: subject has property not present in basis: " + prop); + } + + // Return a walker that checks that the subject census has counts all equal to + // |basis|. + Census.assertAllEqual = makeBasisChecker({ + compare: (a, b) => { if (a !== b) throw new Error("Census mismatch: expected " + a + " got " + b)}, + missing: missingProp, + extra: extraProp + }); + + function ok(val) { + if (!val) { + throw new Error("Census mismatch: expected truthy, got " + val); + } + } + + // Return a walker that checks that the subject census has at least as many + // items of each category as |basis|. + Census.assertAllNotLessThan = makeBasisChecker({ + compare: (subject, basis) => ok(subject >= basis), + missing: missingProp, + extra: () => Census.walkAnything + }); + + // Return a walker that checks that the subject census has at most as many + // items of each category as |basis|. + Census.assertAllNotMoreThan = makeBasisChecker({ + compare: (subject, basis) => ok(subject <= basis), + missing: missingProp, + extra: () => Census.walkAnything + }); + + // Return a walker that checks that the subject census has within |fudge| + // items of each category of the count in |basis|. + Census.assertAllWithin = function (fudge, basis) { + return makeBasisChecker({ + compare: (subject, basis) => ok(Math.abs(subject - basis) <= fudge), + missing: missingProp, + extra: () => Census.walkAnything + })(basis); + } + + return Census; +}()); diff --git a/toolkit/devtools/server/tests/unit/Match.jsm b/toolkit/devtools/server/tests/unit/Match.jsm new file mode 100644 index 0000000000..c29e6484e7 --- /dev/null +++ b/toolkit/devtools/server/tests/unit/Match.jsm @@ -0,0 +1,190 @@ +// A little pattern-matching library. +// +// Ported from js/src/tests/js1_8_5/reflect-parse/Match.js for use with devtools +// server xpcshell tests. + +this.EXPORTED_SYMBOLS = ["Match"]; + +this.Match = (function() { + + function Pattern(template) { + // act like a constructor even as a function + if (!(this instanceof Pattern)) + return new Pattern(template); + + this.template = template; + } + + Pattern.prototype = { + match: function(act) { + return match(act, this.template); + }, + + matches: function(act) { + try { + return this.match(act); + } + catch (e if e instanceof MatchError) { + return false; + } + }, + + assert: function(act, message) { + try { + return this.match(act); + } + catch (e if e instanceof MatchError) { + throw new Error((message || "failed match") + ": " + e.message); + } + }, + + toString: () => "[object Pattern]" + }; + + Pattern.ANY = new Pattern; + Pattern.ANY.template = Pattern.ANY; + + Pattern.NUMBER = new Pattern; + Pattern.NUMBER.match = function (act) { + if (typeof act !== 'number') { + throw new MatchError("Expected number, got: " + quote(act)); + } + } + + Pattern.NATURAL = new Pattern + Pattern.NATURAL.match = function (act) { + if (typeof act !== 'number' || act !== Math.floor(act) || act < 0) { + throw new MatchError("Expected natural number, got: " + quote(act)); + } + } + + var quote = uneval; + + function MatchError(msg) { + this.message = msg; + } + + MatchError.prototype = { + toString: function() { + return "match error: " + this.message; + } + }; + + function isAtom(x) { + return (typeof x === "number") || + (typeof x === "string") || + (typeof x === "boolean") || + (x === null) || + (typeof x === "object" && x instanceof RegExp); + } + + function isObject(x) { + return (x !== null) && (typeof x === "object"); + } + + function isFunction(x) { + return typeof x === "function"; + } + + function isArrayLike(x) { + return isObject(x) && ("length" in x); + } + + function matchAtom(act, exp) { + if ((typeof exp) === "number" && isNaN(exp)) { + if ((typeof act) !== "number" || !isNaN(act)) + throw new MatchError("expected NaN, got: " + quote(act)); + return true; + } + + if (exp === null) { + if (act !== null) + throw new MatchError("expected null, got: " + quote(act)); + return true; + } + + if (exp instanceof RegExp) { + if (!(act instanceof RegExp) || exp.source !== act.source) + throw new MatchError("expected " + quote(exp) + ", got: " + quote(act)); + return true; + } + + switch (typeof exp) { + case "string": + if (act !== exp) + throw new MatchError("expected " + quote(exp) + ", got " + quote(act)); + return true; + case "boolean": + case "number": + if (exp !== act) + throw new MatchError("expected " + exp + ", got " + quote(act)); + return true; + } + + throw new Error("bad pattern: " + exp.toSource()); + } + + function matchObject(act, exp) { + if (!isObject(act)) + throw new MatchError("expected object, got " + quote(act)); + + for (var key in exp) { + if (!(key in act)) + throw new MatchError("expected property " + quote(key) + " not found in " + quote(act)); + match(act[key], exp[key]); + } + + return true; + } + + function matchFunction(act, exp) { + if (!isFunction(act)) + throw new MatchError("expected function, got " + quote(act)); + + if (act !== exp) + throw new MatchError("expected function: " + exp + + "\nbut got different function: " + act); + } + + function matchArray(act, exp) { + if (!isObject(act) || !("length" in act)) + throw new MatchError("expected array-like object, got " + quote(act)); + + var length = exp.length; + if (act.length !== exp.length) + throw new MatchError("expected array-like object of length " + length + ", got " + quote(act)); + + for (var i = 0; i < length; i++) { + if (i in exp) { + if (!(i in act)) + throw new MatchError("expected array property " + i + " not found in " + quote(act)); + match(act[i], exp[i]); + } + } + + return true; + } + + function match(act, exp) { + if (exp === Pattern.ANY) + return true; + + if (exp instanceof Pattern) + return exp.match(act); + + if (isAtom(exp)) + return matchAtom(act, exp); + + if (isArrayLike(exp)) + return matchArray(act, exp); + + if (isFunction(exp)) + return matchFunction(act, exp); + + return matchObject(act, exp); + } + + return { Pattern: Pattern, + MatchError: MatchError }; + +})(); diff --git a/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_01.js b/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_01.js new file mode 100644 index 0000000000..5bd954ab12 --- /dev/null +++ b/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_01.js @@ -0,0 +1,31 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// HeapSnapshot.prototype.takeCensus returns a value of an appropriate +// shape. Ported from js/src/jit-tests/debug/Memory-takeCensus-01.js + +function run_test() { + var dbg = new Debugger; + + function checkProperties(census) { + equal(typeof census, 'object'); + for (prop of Object.getOwnPropertyNames(census)) { + var desc = Object.getOwnPropertyDescriptor(census, prop); + equal(desc.enumerable, true); + equal(desc.configurable, true); + equal(desc.writable, true); + if (typeof desc.value === 'object') + checkProperties(desc.value); + else + equal(typeof desc.value, 'number'); + } + } + + checkProperties(saveHeapSnapshotAndTakeCensus(dbg)); + + var g = newGlobal(); + dbg.addDebuggee(g); + checkProperties(saveHeapSnapshotAndTakeCensus(dbg)); + + do_test_finished(); +} diff --git a/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_02.js b/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_02.js new file mode 100644 index 0000000000..28a7278614 --- /dev/null +++ b/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_02.js @@ -0,0 +1,57 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// HeapSnapshot.prototype.takeCensus behaves plausibly as we allocate objects. +// +// Exact object counts vary in ways we can't predict. For example, +// BaselineScripts can hold onto "template objects", which exist only to hold +// the shape and type for newly created objects. When BaselineScripts are +// discarded, these template objects go with them. +// +// So instead of expecting precise counts, we expect counts that are at least as +// many as we would expect given the object graph we've built. +// +// Ported from js/src/jit-tests/debug/Memory-takeCensus-02.js + +function run_test() { + // A Debugger with no debuggees had better not find anything. + var dbg = new Debugger; + var census0 = saveHeapSnapshotAndTakeCensus(dbg); + Census.walkCensus(census0, "census0", Census.assertAllZeros); + + function newGlobalWithDefs() { + var g = newGlobal(); + g.eval(` + function times(n, fn) { + var a=[]; + for (var i = 0; i ({}));'); + g.eval('var rxs = times(200, () => /foo/);'); + g.eval('var ars = times(400, () => []);'); + g.eval('var fns = times(800, () => () => {});'); + + var census1 = dbg.memory.takeCensus(dbg); + Census.walkCensus(census1, "census1", + Census.assertAllNotLessThan( + { 'objects': + { 'Object': { count: 100 }, + 'RegExp': { count: 200 }, + 'Array': { count: 400 }, + 'Function': { count: 800 } + } + })); + + do_test_finished(); +} diff --git a/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_03.js b/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_03.js new file mode 100644 index 0000000000..25f2c37912 --- /dev/null +++ b/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_03.js @@ -0,0 +1,34 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// HeapSnapshot.prototype.takeCensus behaves plausibly as we add and remove +// debuggees. +// +// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-03.js + +function run_test() { + var dbg = new Debugger; + + var census0 = saveHeapSnapshotAndTakeCensus(dbg); + Census.walkCensus(census0, "census0", Census.assertAllZeros); + + var g1 = newGlobal(); + dbg.addDebuggee(g1); + var census1 = saveHeapSnapshotAndTakeCensus(dbg); + Census.walkCensus(census1, "census1", Census.assertAllNotLessThan(census0)); + + var g2 = newGlobal(); + dbg.addDebuggee(g2); + var census2 = saveHeapSnapshotAndTakeCensus(dbg); + Census.walkCensus(census2, "census2", Census.assertAllNotLessThan(census1)); + + dbg.removeDebuggee(g2); + var census3 = saveHeapSnapshotAndTakeCensus(dbg); + Census.walkCensus(census3, "census3", Census.assertAllEqual(census1)); + + dbg.removeDebuggee(g1); + var census4 = saveHeapSnapshotAndTakeCensus(dbg); + Census.walkCensus(census4, "census4", Census.assertAllEqual(census0)); + + do_test_finished(); +} diff --git a/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_04.js b/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_04.js new file mode 100644 index 0000000000..799844cde0 --- /dev/null +++ b/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_04.js @@ -0,0 +1,36 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that HeapSnapshot.prototype.takeCensus finds GC roots that are on the +// stack. +// +// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-04.js + +function run_test() { + var g = newGlobal(); + var dbg = new Debugger(g); + + g.eval(` +function withAllocationMarkerOnStack(f) { + (function () { + var onStack = allocationMarker(); + f(); + }()); +} +`); + + equal("AllocationMarker" in saveHeapSnapshotAndTakeCensus(dbg).objects, false, + "There shouldn't exist any allocation markers in the census."); + + var allocationMarkerCount; + g.withAllocationMarkerOnStack(() => { + const census = saveHeapSnapshotAndTakeCensus(dbg); + allocationMarkerCount = census.objects.AllocationMarker.count; + }); + + equal(allocationMarkerCount, 1, + "Should have one allocation marker in the census, because there " + + "was one on the stack."); + + do_test_finished(); +} diff --git a/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_05.js b/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_05.js new file mode 100644 index 0000000000..da6067624a --- /dev/null +++ b/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_05.js @@ -0,0 +1,24 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that HeapSnapshot.prototype.takeCensus finds cross compartment +// wrapper GC roots. +// +// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-05.js + +function run_test() { + var g = newGlobal(); + var dbg = new Debugger(g); + + equal("AllocationMarker" in saveHeapSnapshotAndTakeCensus(dbg).objects, false, + "No allocation markers should exist in the census."); + + this.ccw = g.allocationMarker(); + + const census = saveHeapSnapshotAndTakeCensus(dbg); + equal(census.objects.AllocationMarker.count, 1, + "Should have one allocation marker in the census, because there " + + "is one cross-compartment wrapper referring to it."); + + do_test_finished(); +} diff --git a/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_06.js b/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_06.js new file mode 100644 index 0000000000..258e1b867c --- /dev/null +++ b/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_06.js @@ -0,0 +1,125 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Check HeapSnapshot.prototype.takeCensus handling of 'breakdown' argument. +// +// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-06.js + +function run_test() { + var Pattern = Match.Pattern; + + var g = newGlobal(); + var dbg = new Debugger(g); + + Pattern({ count: Pattern.NATURAL, + bytes: Pattern.NATURAL }) + .assert(saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: 'count' } })); + + let census = saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: 'count', count: false, bytes: false } }); + equal('count' in census, false); + equal('bytes' in census, false); + + census = saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: 'count', count: true, bytes: false } }); + equal('count' in census, true); + equal('bytes' in census, false); + + census = saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: 'count', count: false, bytes: true } }); + equal('count' in census, false); + equal('bytes' in census, true); + + census = saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: 'count', count: true, bytes: true } }); + equal('count' in census, true); + equal('bytes' in census, true); + + + // Pattern doesn't mind objects with extra properties, so we'll restrict this + // list to the object classes we're pretty sure are going to stick around for + // the forseeable future. + Pattern({ + Function: { count: Pattern.NATURAL }, + Object: { count: Pattern.NATURAL }, + Debugger: { count: Pattern.NATURAL }, + Sandbox: { count: Pattern.NATURAL }, + + // The below are all Debugger prototype objects. + Source: { count: Pattern.NATURAL }, + Environment: { count: Pattern.NATURAL }, + Script: { count: Pattern.NATURAL }, + Memory: { count: Pattern.NATURAL }, + Frame: { count: Pattern.NATURAL } + }) + .assert(saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: 'objectClass' } })); + + Pattern({ + objects: { count: Pattern.NATURAL }, + scripts: { count: Pattern.NATURAL }, + strings: { count: Pattern.NATURAL }, + other: { count: Pattern.NATURAL } + }) + .assert(saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: 'coarseType' } })); + + // As for { by: 'objectClass' }, restrict our pattern to the types + // we predict will stick around for a long time. + Pattern({ + JSString: { count: Pattern.NATURAL }, + 'js::Shape': { count: Pattern.NATURAL }, + JSObject: { count: Pattern.NATURAL }, + JSScript: { count: Pattern.NATURAL } + }) + .assert(saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: 'internalType' } })); + + + // Nested breakdowns. + + let coarseTypePattern = { + objects: { count: Pattern.NATURAL }, + scripts: { count: Pattern.NATURAL }, + strings: { count: Pattern.NATURAL }, + other: { count: Pattern.NATURAL } + }; + + Pattern({ + JSString: coarseTypePattern, + 'js::Shape': coarseTypePattern, + JSObject: coarseTypePattern, + JSScript: coarseTypePattern, + }) + .assert(saveHeapSnapshotAndTakeCensus(dbg, { + breakdown: { by: 'internalType', + then: { by: 'coarseType' } + } + })); + + Pattern({ + Function: { count: Pattern.NATURAL }, + Object: { count: Pattern.NATURAL }, + Debugger: { count: Pattern.NATURAL }, + Sandbox: { count: Pattern.NATURAL }, + other: coarseTypePattern + }) + .assert(saveHeapSnapshotAndTakeCensus(dbg, { + breakdown: { + by: 'objectClass', + then: { by: 'count' }, + other: { by: 'coarseType' } + } + })); + + Pattern({ + objects: { count: Pattern.NATURAL, label: "object" }, + scripts: { count: Pattern.NATURAL, label: "scripts" }, + strings: { count: Pattern.NATURAL, label: "strings" }, + other: { count: Pattern.NATURAL, label: "other" } + }) + .assert(saveHeapSnapshotAndTakeCensus(dbg, { + breakdown: { + by: 'coarseType', + objects: { by: 'count', label: 'object' }, + scripts: { by: 'count', label: 'scripts' }, + strings: { by: 'count', label: 'strings' }, + other: { by: 'count', label: 'other' } + } + })); + + do_test_finished(); +} diff --git a/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_07.js b/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_07.js new file mode 100644 index 0000000000..f1d0398fd2 --- /dev/null +++ b/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_07.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// HeapSnapshot.prototype.takeCensus breakdown: check error handling on property +// gets. +// +// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-07.js + +function run_test() { + var g = newGlobal(); + var dbg = new Debugger(g); + + assertThrowsValue(() => { + saveHeapSnapshotAndTakeCensus(dbg, { + breakdown: { get by() { throw "ಠ_ಠ" } } + }); + }, "ಠ_ಠ"); + + + + assertThrowsValue(() => { + saveHeapSnapshotAndTakeCensus(dbg, { + breakdown: { by: 'count', get count() { throw "ಠ_ಠ" } } + }); + }, "ಠ_ಠ"); + + assertThrowsValue(() => { + saveHeapSnapshotAndTakeCensus(dbg, { + breakdown: { by: 'count', get bytes() { throw "ಠ_ಠ" } } + }); + }, "ಠ_ಠ"); + + + + assertThrowsValue(() => { + saveHeapSnapshotAndTakeCensus(dbg, { + breakdown: { by: 'objectClass', get then() { throw "ಠ_ಠ" } } + }); + }, "ಠ_ಠ"); + + assertThrowsValue(() => { + saveHeapSnapshotAndTakeCensus(dbg, { + breakdown: { by: 'objectClass', get other() { throw "ಠ_ಠ" } } + }); + }, "ಠ_ಠ"); + + + + assertThrowsValue(() => { + saveHeapSnapshotAndTakeCensus(dbg, { + breakdown: { by: 'coarseType', get objects() { throw "ಠ_ಠ" } } + }); + }, "ಠ_ಠ"); + + assertThrowsValue(() => { + saveHeapSnapshotAndTakeCensus(dbg, { + breakdown: { by: 'coarseType', get scripts() { throw "ಠ_ಠ" } } + }); + }, "ಠ_ಠ"); + + assertThrowsValue(() => { + saveHeapSnapshotAndTakeCensus(dbg, { + breakdown: { by: 'coarseType', get strings() { throw "ಠ_ಠ" } } + }); + }, "ಠ_ಠ"); + + assertThrowsValue(() => { + saveHeapSnapshotAndTakeCensus(dbg, { + breakdown: { by: 'coarseType', get other() { throw "ಠ_ಠ" } } + }); + }, "ಠ_ಠ"); + + + + assertThrowsValue(() => { + saveHeapSnapshotAndTakeCensus(dbg, { + breakdown: { by: 'internalType', get then() { throw "ಠ_ಠ" } } + }); + }, "ಠ_ಠ"); + + do_test_finished(); +} diff --git a/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_08.js b/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_08.js new file mode 100644 index 0000000000..4407d0d54a --- /dev/null +++ b/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_08.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// HeapSnapshot.prototype.takeCensus: test by: 'count' breakdown +// +// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-08.js + +function run_test() { + let g = newGlobal(); + let dbg = new Debugger(g); + + g.eval(` + var stuff = []; + function add(n, c) { + for (let i = 0; i < n; i++) + stuff.push(c()); + } + + let count = 0; + + function obj() { return { count: count++ }; } + obj.factor = 1; + + // This creates a closure (a function JSObject) that has captured + // a Call object. So each call creates two items. + function fun() { let v = count; return () => { return v; } } + fun.factor = 2; + + function str() { return 'perambulator' + count++; } + str.factor = 1; + + // Eval a fresh text each time, allocating: + // - a fresh ScriptSourceObject + // - a new JSScripts, not an eval cache hits + // - a fresh prototype object + // - a fresh Call object, since the eval makes 'ev' heavyweight + // - the new function itself + function ev() { + return eval(\`(function () { return \${ count++ } })\`); + } + ev.factor = 5; + + // A new object (1) with a new shape (2) with a new atom (3) + function shape() { return { [ 'theobroma' + count++ ]: count }; } + shape.factor = 3; + `); + + let baseline = 0; + function countIncreasedByAtLeast(n) { + let oldBaseline = baseline; + + // Since a census counts only reachable objects, one might assume that calling + // GC here would have no effect on the census results. But GC also throws away + // JIT code and any objects it might be holding (template objects, say); + // takeCensus reaches those. Shake everything loose that we can, to make the + // census approximate reachability a bit more closely, and make our results a + // bit more predictable. + gc(g, 'shrinking'); + + baseline = saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: 'count' } }).count; + return baseline >= oldBaseline + n; + } + + countIncreasedByAtLeast(0); + + g.add(100, g.obj); + ok(countIncreasedByAtLeast(g.obj.factor * 100)); + + g.add(100, g.fun); + ok(countIncreasedByAtLeast(g.fun.factor * 100)); + + g.add(100, g.str); + ok(countIncreasedByAtLeast(g.str.factor * 100)); + + g.add(100, g.ev); + ok(countIncreasedByAtLeast(g.ev.factor * 100)); + + g.add(100, g.shape); + ok(countIncreasedByAtLeast(g.shape.factor * 100)); + + do_test_finished(); +} diff --git a/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_09.js b/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_09.js new file mode 100644 index 0000000000..ad02e9a927 --- /dev/null +++ b/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_09.js @@ -0,0 +1,92 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// HeapSnapshot.prototype.takeCensus: by: allocationStack breakdown +// +// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-09.js + +function run_test() { + var g = newGlobal(); + var dbg = new Debugger(g); + + g.eval(` // 1 + var log = []; // 2 + function f() { log.push(allocationMarker()); } // 3 + function g() { f(); } // 4 + function h() { f(); } // 5 + `); // 6 + + // Create one allocationMarker with tracking turned off, + // so it will have no associated stack. + g.f(); + + dbg.memory.allocationSamplingProbability = 1; + + for ([func, n] of [[g.f, 20], [g.g, 10], [g.h, 5]]) { + for (let i = 0; i < n; i++) { + dbg.memory.trackingAllocationSites = true; + // All allocations of allocationMarker occur with this line as the oldest + // stack frame. + func(); + dbg.memory.trackingAllocationSites = false; + } + } + + let census = saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: 'objectClass', + then: { by: 'allocationStack', + then: { by: 'count', + label: 'haz stack' + }, + noStack: { by: 'count', + label: 'no haz stack' + } + } + } + }); + + let map = census.AllocationMarker; + ok(map instanceof Map, "Should be a Map instance"); + equal(map.size, 4, "Should have 4 allocation stacks (including the lack of a stack)") + + // Gather the stacks we are expecting to appear as keys, and + // check that there are no unexpected keys. + let stacks = { }; + + map.forEach((v, k) => { + if (k === 'noStack') { + // No need to save this key. + } else if (k.functionDisplayName === 'f' && + k.parent.functionDisplayName === 'run_test') { + stacks.f = k; + } else if (k.functionDisplayName === 'f' && + k.parent.functionDisplayName === 'g' && + k.parent.parent.functionDisplayName === 'run_test') { + stacks.fg = k; + } else if (k.functionDisplayName === 'f' && + k.parent.functionDisplayName === 'h' && + k.parent.parent.functionDisplayName === 'run_test') { + stacks.fh = k; + } else { + dumpn("Unexpected allocation stack:") + k.toString().split(/\n/g).forEach(s => dumpn(s)); + ok(false); + } + }); + + equal(map.get('noStack').label, 'no haz stack'); + equal(map.get('noStack').count, 1); + + ok(stacks.f); + equal(map.get(stacks.f).label, 'haz stack'); + equal(map.get(stacks.f).count, 20); + + ok(stacks.fg); + equal(map.get(stacks.fg).label, 'haz stack'); + equal(map.get(stacks.fg).count, 10); + + ok(stacks.fh); + equal(map.get(stacks.fh).label, 'haz stack'); + equal(map.get(stacks.fh).count, 5); + + do_test_finished(); +} diff --git a/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_10.js b/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_10.js new file mode 100644 index 0000000000..63e3dd1f8c --- /dev/null +++ b/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_10.js @@ -0,0 +1,68 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Check byte counts produced by takeCensus. +// +// Ported from js/src/jit-test/tests/debug/Memory-take Census-10.js + +function run_test() { + let g = newGlobal(); + let dbg = new Debugger(g); + + let sizeOfAM = byteSize(allocationMarker()); + + // Allocate a single allocation marker, and check that we can find it. + g.eval('var hold = allocationMarker();'); + let census = saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: 'objectClass' } }); + equal(census.AllocationMarker.count, 1); + equal(census.AllocationMarker.bytes, sizeOfAM); + g.hold = null; + + g.eval(` // 1 + var objs = []; // 2 + function fnerd() { // 3 + objs.push(allocationMarker()); // 4 + for (let i = 0; i < 10; i++) // 5 + objs.push(allocationMarker()); // 6 + } // 7 + `); // 8 + + dbg.memory.allocationSamplingProbability = 1; + dbg.memory.trackingAllocationSites = true; + g.fnerd(); + dbg.memory.trackingAllocationSites = false; + + census = saveHeapSnapshotAndTakeCensus(dbg, { + breakdown: { by: 'objectClass', + then: { by: 'allocationStack' } + } + }); + + let seen = 0; + census.AllocationMarker.forEach((v, k) => { + equal(k.functionDisplayName, 'fnerd'); + switch (k.line) { + case 4: + equal(v.count, 1); + equal(v.bytes, sizeOfAM); + seen++; + break; + + case 6: + equal(v.count, 10); + equal(v.bytes, 10 * sizeOfAM); + seen++; + break; + + default: + dumpn("Unexpected stack:"); + k.toString().split(/\n/g).forEach(s => dumpn(s)); + ok(false); + break; + } + }); + + equal(seen, 2); + + do_test_finished(); +} diff --git a/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_11.js b/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_11.js new file mode 100644 index 0000000000..33d42c4762 --- /dev/null +++ b/toolkit/devtools/server/tests/unit/test_HeapSnapshot_takeCensus_11.js @@ -0,0 +1,116 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that Debugger.Memory.prototype.takeCensus and +// HeapSnapshot.prototype.takeCensus return the same data for the same heap +// graph. + +function doLiveAndOfflineCensus(g, dbg, opts) { + dbg.memory.allocationSamplingProbability = 1; + dbg.memory.trackingAllocationSites = true; + g.eval(` // 1 + (function unsafeAtAnySpeed() { // 2 + for (var i = 0; i < 100; i++) { // 3 + this.markers.push(allocationMarker()); // 4 + } // 5 + }()); // 6 + `); // 7 + dbg.memory.trackingAllocationSites = false; + + return { + live: dbg.memory.takeCensus(opts), + offline: saveHeapSnapshotAndTakeCensus(dbg, opts) + }; +} + +function run_test() { + var g = newGlobal(); + var dbg = new Debugger(g); + + g.eval('this.markers = []'); + const markerSize = byteSize(allocationMarker()); + + // First, test that we get the same counts and sizes as we allocate and retain + // more things. + + let prevCount = 0; + let prevBytes = 0; + + for (var i = 0; i < 10; i++) { + const { live, offline } = doLiveAndOfflineCensus(g, dbg, { + breakdown: { by: 'objectClass', + then: { by: 'count'} } + }); + + equal(live.AllocationMarker.count, offline.AllocationMarker.count); + equal(live.AllocationMarker.bytes, offline.AllocationMarker.bytes); + equal(live.AllocationMarker.count, prevCount + 100); + equal(live.AllocationMarker.bytes, prevBytes + 100 * markerSize); + + prevCount = live.AllocationMarker.count; + prevBytes = live.AllocationMarker.bytes; + } + + // Second, test that the reported allocation stacks and counts and sizes at + // those allocation stacks match up. + + const { live, offline } = doLiveAndOfflineCensus(g, dbg, { + breakdown: { by: 'objectClass', + then: { by: 'allocationStack'} } + }); + + equal(live.AllocationMarker.size, offline.AllocationMarker.size); + // One stack with the loop further above, and another stack featuring the call + // right above. + equal(live.AllocationMarker.size, 2); + + // Note that because SavedFrame stacks reconstructed from an offline heap + // snapshot don't have the same principals as SavedFrame stacks captured from + // a live stack, the live and offline allocation stacks won't be identity + // equal, but should be structurally the same. + + const liveEntries = []; + live.AllocationMarker.forEach((v, k) => { + dumpn("Allocation stack:"); + k.toString().split(/\n/g).forEach(s => dumpn(s)); + + equal(k.functionDisplayName, 'unsafeAtAnySpeed'); + equal(k.line, 4); + + liveEntries.push([k.toString(), v]); + }); + + const offlineEntries = []; + offline.AllocationMarker.forEach((v, k) => { + dumpn("Allocation stack:"); + k.toString().split(/\n/g).forEach(s => dumpn(s)); + + equal(k.functionDisplayName, 'unsafeAtAnySpeed'); + equal(k.line, 4); + + offlineEntries.push([k.toString(), v]); + }); + + const sortEntries = (a, b) => { + if (a[0] < b[0]) { + return -1; + } else if (a[0] > b[0]) { + return 1; + } else { + return 0; + } + }; + liveEntries.sort(sortEntries); + offlineEntries.sort(sortEntries); + + equal(liveEntries.length, live.AllocationMarker.size); + equal(liveEntries.length, offlineEntries.length); + + for (let i = 0; i < liveEntries.length; i++) { + equal(liveEntries[i][0], offlineEntries[i][0]); + equal(liveEntries[i][1].count, offlineEntries[i][1].count); + equal(liveEntries[i][1].bytes, offlineEntries[i][1].bytes); + } + + do_test_finished(); +} diff --git a/toolkit/devtools/server/tests/unit/xpcshell.ini b/toolkit/devtools/server/tests/unit/xpcshell.ini index db72e69de9..f7e4e84e60 100644 --- a/toolkit/devtools/server/tests/unit/xpcshell.ini +++ b/toolkit/devtools/server/tests/unit/xpcshell.ini @@ -4,6 +4,8 @@ tail = skip-if = toolkit == 'android' || toolkit == 'gonk' support-files = + Census.jsm + Match.jsm source-map-data/sourcemapped.coffee source-map-data/sourcemapped.map post_init_global_actors.js @@ -41,6 +43,17 @@ support-files = [test_getyoungestframe.js] [test_nsjsinspector.js] [test_SaveHeapSnapshot.js] +[test_HeapSnapshot_takeCensus_01.js] +[test_HeapSnapshot_takeCensus_02.js] +[test_HeapSnapshot_takeCensus_03.js] +[test_HeapSnapshot_takeCensus_04.js] +[test_HeapSnapshot_takeCensus_05.js] +[test_HeapSnapshot_takeCensus_06.js] +[test_HeapSnapshot_takeCensus_07.js] +[test_HeapSnapshot_takeCensus_08.js] +[test_HeapSnapshot_takeCensus_09.js] +[test_HeapSnapshot_takeCensus_10.js] +[test_HeapSnapshot_takeCensus_11.js] [test_ReadHeapSnapshot.js] [test_ReadHeapSnapshot_worker.js] [test_dbgactor.js] diff --git a/tools/profiler/public/GeckoProfilerImpl.h b/tools/profiler/public/GeckoProfilerImpl.h index e27d5c2014..011a0941c9 100644 --- a/tools/profiler/public/GeckoProfilerImpl.h +++ b/tools/profiler/public/GeckoProfilerImpl.h @@ -383,7 +383,7 @@ static inline void profiler_tracing(const char* aCategory, const char* aInfo, namespace mozilla { -class MOZ_STACK_CLASS GeckoProfilerTracingRAII { +class MOZ_RAII GeckoProfilerTracingRAII { public: GeckoProfilerTracingRAII(const char* aCategory, const char* aInfo, mozilla::UniquePtr aBacktrace diff --git a/xpcom/glue/AutoRestore.h b/xpcom/glue/AutoRestore.h index 2a87229846..9ea790035c 100644 --- a/xpcom/glue/AutoRestore.h +++ b/xpcom/glue/AutoRestore.h @@ -27,7 +27,7 @@ namespace mozilla { * } */ template -class MOZ_STACK_CLASS AutoRestore +class MOZ_RAII AutoRestore { private: T& mLocation; diff --git a/xpcom/glue/Mutex.h b/xpcom/glue/Mutex.h index a5ed05f36a..734dbf3cf8 100644 --- a/xpcom/glue/Mutex.h +++ b/xpcom/glue/Mutex.h @@ -145,7 +145,7 @@ private: * MUCH PREFERRED to bare calls to Mutex.Lock and Unlock. */ template -class MOZ_STACK_CLASS BaseAutoLock +class MOZ_RAII BaseAutoLock { public: /** @@ -190,7 +190,7 @@ typedef BaseAutoLock OffTheBooksMutexAutoLock; * MUCH PREFERRED to bare calls to Mutex.Unlock and Lock. */ template -class MOZ_STACK_CLASS BaseAutoUnlock +class MOZ_RAII BaseAutoUnlock { public: explicit BaseAutoUnlock(T& aLock MOZ_GUARD_OBJECT_NOTIFIER_PARAM)