diff --git a/browser/config/mozconfigs/win32/mingwclang b/browser/config/mozconfigs/win32/mingwclang index 29331cddeec2..8585c11a57fe 100644 --- a/browser/config/mozconfigs/win32/mingwclang +++ b/browser/config/mozconfigs/win32/mingwclang @@ -54,7 +54,7 @@ AR=llvm-ar RANLIB=llvm-ranlib # For Stylo -BINDGEN_CFLAGS="-I$TOOLTOOL_DIR/clang/i686-w64-mingw32/include/c++/v1 -I$TOOLTOOL_DIR/clang/lib/clang/7.0.0/include -I$TOOLTOOL_DIR/clang/i686-w64-mingw32/include" +BINDGEN_CFLAGS="-I$TOOLTOOL_DIR/clang/i686-w64-mingw32/include/c++/v1 -I$TOOLTOOL_DIR/clang/lib/clang/8.0.0/include -I$TOOLTOOL_DIR/clang/i686-w64-mingw32/include" # Since we use windres from binutils without the rest of tools (like cpp), we need to # explicitly specify clang as a preprocessor. diff --git a/browser/config/mozconfigs/win64/mingwclang b/browser/config/mozconfigs/win64/mingwclang index 5e9446e0acdc..420f94e2d45d 100755 --- a/browser/config/mozconfigs/win64/mingwclang +++ b/browser/config/mozconfigs/win64/mingwclang @@ -54,7 +54,7 @@ AR=llvm-ar RANLIB=llvm-ranlib # For Stylo -BINDGEN_CFLAGS="-I$TOOLTOOL_DIR/clang/x86_64-w64-mingw32/include/c++/v1 -I$TOOLTOOL_DIR/clang/lib/clang/7.0.0/include -I$TOOLTOOL_DIR/clang/x86_64-w64-mingw32/include" +BINDGEN_CFLAGS="-I$TOOLTOOL_DIR/clang/x86_64-w64-mingw32/include/c++/v1 -I$TOOLTOOL_DIR/clang/lib/clang/8.0.0/include -I$TOOLTOOL_DIR/clang/x86_64-w64-mingw32/include" # Since we use windres from binutils without the rest of tools (like cpp), we need to # explicitly specify clang as a preprocessor. diff --git a/build/build-clang/clang-7-mingw.json b/build/build-clang/clang-trunk-mingw.json similarity index 58% rename from build/build-clang/clang-7-mingw.json rename to build/build-clang/clang-trunk-mingw.json index 8ad9ad5b3649..f0f93cb8f473 100644 --- a/build/build-clang/clang-7-mingw.json +++ b/build/build-clang/clang-trunk-mingw.json @@ -4,12 +4,12 @@ "build_libcxx": true, "build_type": "Release", "assertions": false, - "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_700/final", - "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_700/final", - "lld_repo": "https://llvm.org/svn/llvm-project/lld/tags/RELEASE_700/final", - "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_700/final", - "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_700/final", - "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_700/final", + "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/trunk", + "clang_repo": "https://llvm.org/svn/llvm-project/cfe/trunk", + "lld_repo": "https://llvm.org/svn/llvm-project/lld/trunk", + "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/trunk", + "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/trunk", + "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/trunk", "python_path": "/usr/bin/python2.7", "gcc_dir": "/builds/worker/workspace/build/src/gcc", "cc": "/builds/worker/workspace/build/src/gcc/bin/gcc", diff --git a/devtools/server/actors/replay/debugger.js b/devtools/server/actors/replay/debugger.js index 1c7e5e75fb1d..9ee241a26cd8 100644 --- a/devtools/server/actors/replay/debugger.js +++ b/devtools/server/actors/replay/debugger.js @@ -62,7 +62,11 @@ ReplayDebugger.prototype = { replayResumeBackward() { RecordReplayControl.resume(/* forward = */ false); }, replayResumeForward() { RecordReplayControl.resume(/* forward = */ true); }, replayTimeWarp: RecordReplayControl.timeWarp, - replayPause: RecordReplayControl.pause, + + replayPause() { + RecordReplayControl.pause(); + this._repaint(); + }, addDebuggee() {}, removeAllDebuggees() {}, @@ -90,6 +94,18 @@ ReplayDebugger.prototype = { return this._sendRequest(request); }, + // Update graphics according to the current state of the child process. This + // should be done anytime we pause and allow the user to interact with the + // debugger. + _repaint() { + const rv = this._sendRequestAllowDiverge({ type: "repaint" }); + if ("width" in rv && "height" in rv) { + RecordReplayControl.hadRepaint(rv.width, rv.height); + } else { + RecordReplayControl.hadRepaintFailure(); + } + }, + _setBreakpoint(handler, position, data) { const id = RecordReplayControl.setBreakpoint(handler, position); this._breakpoints.push({id, position, data}); @@ -163,10 +179,24 @@ ReplayDebugger.prototype = { return this._scripts[data.id]; }, - findScripts() { - // Note: Debugger's findScripts() method takes a query argument, which - // we ignore here. - const data = this._sendRequest({ type: "findScripts" }); + _convertScriptQuery(query) { + // Make a copy of the query, converting properties referring to debugger + // things into their associated ids. + const rv = Object.assign({}, query); + if ("global" in query) { + rv.global = query.global._data.id; + } + if ("source" in query) { + rv.source = query.source._data.id; + } + return rv; + }, + + findScripts(query) { + const data = this._sendRequest({ + type: "findScripts", + query: this._convertScriptQuery(query) + }); return data.map(script => this._addScript(script)); }, @@ -315,7 +345,8 @@ ReplayDebugger.prototype = { get onEnterFrame() { return this._breakpointKindGetter("EnterFrame"); }, set onEnterFrame(handler) { this._breakpointKindSetter("EnterFrame", handler, - () => handler.call(this, this.getNewestFrame())); + () => { this._repaint(); + handler.call(this, this.getNewestFrame()); }); }, get replayingOnPopFrame() { @@ -326,7 +357,8 @@ ReplayDebugger.prototype = { set replayingOnPopFrame(handler) { if (handler) { - this._setBreakpoint(() => handler.call(this, this.getNewestFrame()), + this._setBreakpoint(() => { this._repaint(); + handler.call(this, this.getNewestFrame()); }, { kind: "OnPop" }, handler); } else { this._clearMatchingBreakpoints(({position}) => { @@ -340,7 +372,8 @@ ReplayDebugger.prototype = { }, set replayingOnForcedPause(handler) { this._breakpointKindSetter("ForcedPause", handler, - () => handler.call(this, this.getNewestFrame())); + () => { this._repaint(); + handler.call(this, this.getNewestFrame()); }); }, getNewConsoleMessage() { @@ -388,7 +421,8 @@ ReplayDebuggerScript.prototype = { getPredecessorOffsets(pc) { return this._forward("getPredecessorOffsets", pc); }, setBreakpoint(offset, handler) { - this._dbg._setBreakpoint(() => { handler.hit(this._dbg.getNewestFrame()); }, + this._dbg._setBreakpoint(() => { this._dbg._repaint(); + handler.hit(this._dbg.getNewestFrame()); }, { kind: "Break", script: this._data.id, offset }, handler); }, @@ -505,7 +539,8 @@ ReplayDebuggerFrame.prototype = { this._clearOnStepBreakpoints(); offsets.forEach(offset => { this._dbg._setBreakpoint( - () => handler.call(this._dbg.getNewestFrame()), + () => { this._dbg._repaint(); + handler.call(this._dbg.getNewestFrame()); }, { kind: "OnStep", script: this._data.script, offset, @@ -523,6 +558,7 @@ ReplayDebuggerFrame.prototype = { set onPop(handler) { if (handler) { this._dbg._setBreakpoint(() => { + this._dbg._repaint(); const result = this._dbg._sendRequest({ type: "popFrameResult" }); handler.call(this._dbg.getNewestFrame(), this._dbg._convertCompletionValue(result)); @@ -604,7 +640,8 @@ ReplayDebuggerObject.prototype = { getOwnPropertyDescriptor(name) { this._ensureProperties(); - return this._properties[name]; + const desc = this._properties[name]; + return desc ? this._convertPropertyDescriptor(desc) : null; }, _ensureProperties() { @@ -614,21 +651,24 @@ ReplayDebuggerObject.prototype = { id: this._data.id }); this._properties = {}; - properties.forEach(({name, desc}) => { - if ("value" in desc) { - desc.value = this._dbg._convertValue(desc.value); - } - if ("get" in desc) { - desc.get = this._dbg._getObject(desc.get); - } - if ("set" in desc) { - desc.set = this._dbg._getObject(desc.set); - } - this._properties[name] = desc; - }); + properties.forEach(({name, desc}) => { this._properties[name] = desc; }); } }, + _convertPropertyDescriptor(desc) { + const rv = Object.assign({}, desc); + if ("value" in desc) { + rv.value = this._dbg._convertValue(desc.value); + } + if ("get" in desc) { + rv.get = this._dbg._getObject(desc.get); + } + if ("set" in desc) { + rv.set = this._dbg._getObject(desc.set); + } + return rv; + }, + get allocationSite() { NYI(); }, get errorMessageName() { NYI(); }, get errorNotes() { NYI(); }, diff --git a/devtools/server/actors/replay/graphics.js b/devtools/server/actors/replay/graphics.js index d546746a288f..b7c69173b877 100644 --- a/devtools/server/actors/replay/graphics.js +++ b/devtools/server/actors/replay/graphics.js @@ -14,7 +14,7 @@ ChromeUtils.import("resource://gre/modules/Services.jsm"); -function updateWindow(window, buffer, width, height) { +function updateWindow(window, buffer, width, height, hadFailure) { // Make sure the window has a canvas filling the screen. let canvas = window.middlemanCanvas; if (!canvas) { @@ -36,10 +36,20 @@ function updateWindow(window, buffer, width, height) { `scale(${ 1 / scale }) translate(-${ width / scale }px, -${ height / scale }px)`; } + const cx = canvas.getContext("2d"); + const graphicsData = new Uint8Array(buffer); - const imageData = canvas.getContext("2d").getImageData(0, 0, width, height); + const imageData = cx.getImageData(0, 0, width, height); imageData.data.set(graphicsData); - canvas.getContext("2d").putImageData(imageData, 0, 0); + cx.putImageData(imageData, 0, 0); + + // Indicate to the user when repainting failed and we are showing old painted + // graphics instead of the most up-to-date graphics. + if (hadFailure) { + cx.fillStyle = "red"; + cx.font = "48px serif"; + cx.fillText("PAINT FAILURE", 10, 50); + } // Make recording/replaying tabs easier to differentiate from other tabs. window.document.title = "RECORD/REPLAY"; @@ -48,11 +58,11 @@ function updateWindow(window, buffer, width, height) { // Entry point for when we have some new graphics data from the child process // to draw. // eslint-disable-next-line no-unused-vars -function Update(buffer, width, height) { +function Update(buffer, width, height, hadFailure) { try { // Paint to all windows we can find. Hopefully there is only one. for (const window of Services.ww.getWindowEnumerator()) { - updateWindow(window, buffer, width, height); + updateWindow(window, buffer, width, height, hadFailure); } } catch (e) { dump("Middleman Graphics Update Exception: " + e + "\n"); diff --git a/devtools/server/actors/replay/replay.js b/devtools/server/actors/replay/replay.js index 73fc5682f3eb..59cbbfbb0ac7 100644 --- a/devtools/server/actors/replay/replay.js +++ b/devtools/server/actors/replay/replay.js @@ -523,10 +523,30 @@ function forwardToScript(name) { const gRequestHandlers = { + repaint() { + if (!RecordReplayControl.maybeDivergeFromRecording()) { + return {}; + } + return RecordReplayControl.repaint(); + }, + findScripts(request) { + const query = Object.assign({}, request.query); + if ("global" in query) { + query.global = gPausedObjects.getObject(query.global); + } + if ("source" in query) { + query.source = gScriptSources.getObject(query.source); + if (!query.source) { + return []; + } + } + const scripts = dbg.findScripts(query); const rv = []; - gScripts.forEach((id) => { - rv.push(getScriptData(id)); + scripts.forEach(script => { + if (considerScript(script)) { + rv.push(getScriptData(gScripts.getId(script))); + } }); return rv; }, diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp index 5cb9bc2f4669..e0a80265e31b 100644 --- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -1226,10 +1226,10 @@ TabChild::RecvShow(const ScreenIntSize& aSize, } // We have now done enough initialization for the record/replay system to - // create checkpoints. Try to create the initial checkpoint now, in case this - // process never paints later on (the usual place where checkpoints occur). + // create checkpoints. Create a checkpoint now, in case this process never + // paints later on (the usual place where checkpoints occur). if (recordreplay::IsRecordingOrReplaying()) { - recordreplay::child::MaybeCreateInitialCheckpoint(); + recordreplay::child::CreateCheckpoint(); } return IPC_OK(); diff --git a/gfx/2d/NativeFontResourceDWrite.cpp b/gfx/2d/NativeFontResourceDWrite.cpp index c89d7efcda0b..e734323178c7 100644 --- a/gfx/2d/NativeFontResourceDWrite.cpp +++ b/gfx/2d/NativeFontResourceDWrite.cpp @@ -272,8 +272,8 @@ NativeFontResourceDWrite::Create(uint8_t *aFontData, uint32_t aDataLength, } RefPtr fontResource = - new NativeFontResourceDWrite(factory, fontFile.forget(), faceType, - numberOfFaces, aNeedsCairo); + new NativeFontResourceDWrite(factory, fontFile.forget(), ffsRef.forget(), + faceType, numberOfFaces, aNeedsCairo); return fontResource.forget(); } diff --git a/gfx/2d/NativeFontResourceDWrite.h b/gfx/2d/NativeFontResourceDWrite.h index d93170ba67dd..43ad5317d71b 100644 --- a/gfx/2d/NativeFontResourceDWrite.h +++ b/gfx/2d/NativeFontResourceDWrite.h @@ -40,14 +40,20 @@ public: private: NativeFontResourceDWrite(IDWriteFactory *aFactory, already_AddRefed aFontFile, + already_AddRefed aFontFileStream, DWRITE_FONT_FACE_TYPE aFaceType, uint32_t aNumberOfFaces, bool aNeedsCairo) - : mFactory(aFactory), mFontFile(aFontFile), mFaceType(aFaceType) - , mNumberOfFaces(aNumberOfFaces), mNeedsCairo(aNeedsCairo) + : mFactory(aFactory) + , mFontFile(aFontFile) + , mFontFileStream(aFontFileStream) + , mFaceType(aFaceType) + , mNumberOfFaces(aNumberOfFaces) + , mNeedsCairo(aNeedsCairo) {} IDWriteFactory *mFactory; RefPtr mFontFile; + RefPtr mFontFileStream; DWRITE_FONT_FACE_TYPE mFaceType; uint32_t mNumberOfFaces; bool mNeedsCairo; diff --git a/gfx/layers/ipc/ShadowLayers.cpp b/gfx/layers/ipc/ShadowLayers.cpp index 6df38537a682..df9b8732b64c 100644 --- a/gfx/layers/ipc/ShadowLayers.cpp +++ b/gfx/layers/ipc/ShadowLayers.cpp @@ -771,10 +771,6 @@ ShadowLayerForwarder::EndTransaction(const nsIntRegion& aRegionToClear, // finish. If it does we don't have to delay messages at all. GetCompositorBridgeChild()->PostponeMessagesIfAsyncPainting(); - if (recordreplay::IsRecordingOrReplaying()) { - recordreplay::child::NotifyPaintStart(); - } - MOZ_LAYERS_LOG(("[LayersForwarder] sending transaction...")); RenderTraceScope rendertrace3("Forward Transaction", "000093"); if (!mShadowManager->SendUpdate(info)) { @@ -782,15 +778,15 @@ ShadowLayerForwarder::EndTransaction(const nsIntRegion& aRegionToClear, return false; } - if (recordreplay::IsRecordingOrReplaying()) { - recordreplay::child::WaitForPaintToComplete(); - } - if (startTime) { mPaintTiming.sendMs() = (TimeStamp::Now() - startTime.value()).ToMilliseconds(); mShadowManager->SendRecordPaintTimes(mPaintTiming); } + if (recordreplay::IsRecordingOrReplaying()) { + recordreplay::child::NotifyPaintStart(); + } + *aSent = true; mIsFirstPaint = false; mFocusTarget = FocusTarget(); diff --git a/gfx/webrender/src/renderer.rs b/gfx/webrender/src/renderer.rs index 66bec87ef77e..143564f67348 100644 --- a/gfx/webrender/src/renderer.rs +++ b/gfx/webrender/src/renderer.rs @@ -4624,6 +4624,7 @@ impl DebugServer { // that we can use in wrench reftests to ensure that // tests are batching and/or allocating on render // targets as we expect them to. +#[repr(C)] pub struct RendererStats { pub total_draw_calls: usize, pub alpha_target_count: usize, diff --git a/gfx/webrender/src/texture_cache.rs b/gfx/webrender/src/texture_cache.rs index 3a53c944d774..7f93ec42a8f8 100644 --- a/gfx/webrender/src/texture_cache.rs +++ b/gfx/webrender/src/texture_cache.rs @@ -19,14 +19,6 @@ use std::cmp; use std::mem; use std::rc::Rc; -// The fixed number of layers for the shared texture cache. -// There is one array texture per image format, allocated lazily. -const TEXTURE_ARRAY_LAYERS_LINEAR: usize = 4; -const TEXTURE_ARRAY_LAYERS_NEAREST: usize = 1; - -// The dimensions of each layer in the texture cache. -const TEXTURE_LAYER_DIMENSIONS: u32 = 2048; - // The size of each region (page) in a texture layer. const TEXTURE_REGION_DIMENSIONS: u32 = 512; @@ -238,25 +230,44 @@ impl TextureCache { pub fn new(max_texture_size: u32) -> Self { TextureCache { max_texture_size, + // Used primarily for cached shadow masks. There can be lots of + // these on some pages like francine, but most pages don't use it + // much. array_a8_linear: TextureArray::new( ImageFormat::R8, TextureFilter::Linear, - TEXTURE_ARRAY_LAYERS_LINEAR, + 1024, + 1, ), + // Used for experimental hdr yuv texture support, but not used in + // production Firefox. array_a16_linear: TextureArray::new( ImageFormat::R16, TextureFilter::Linear, - TEXTURE_ARRAY_LAYERS_LINEAR, + 1024, + 1, ), + // The primary cache for images, glyphs, etc. array_rgba8_linear: TextureArray::new( ImageFormat::BGRA8, TextureFilter::Linear, - TEXTURE_ARRAY_LAYERS_LINEAR, + 2048, + 4, ), + // Used for image-rendering: crisp. This is mostly favicons, which + // are small. Some other images use it too, but those tend to be + // larger than 512x512 and thus don't use the shared cache anyway. + // + // Ideally we'd use 512 as the dimensions here, since we don't really + // need more. But once a page gets something of a given size bucket + // assigned to it, all further allocations need to be of that size. + // So using 1024 gives us 4 buckets instead of 1, which in practice + // is probably enough. array_rgba8_nearest: TextureArray::new( ImageFormat::BGRA8, TextureFilter::Nearest, - TEXTURE_ARRAY_LAYERS_NEAREST, + 1024, + 1, ), next_id: CacheTextureId(1), pending_updates: TextureUpdateList::new(), @@ -746,8 +757,8 @@ impl TextureCache { let update_op = TextureUpdate { id: texture_id, op: TextureUpdateOp::Create { - width: TEXTURE_LAYER_DIMENSIONS, - height: TEXTURE_LAYER_DIMENSIONS, + width: texture_array.dimensions, + height: texture_array.dimensions, format: descriptor.format, filter: texture_array.filter, // TODO(gw): Creating a render target here is only used @@ -1081,6 +1092,7 @@ impl TextureRegion { #[cfg_attr(feature = "replay", derive(Deserialize))] struct TextureArray { filter: TextureFilter, + dimensions: u32, layer_count: usize, format: ImageFormat, is_allocated: bool, @@ -1092,11 +1104,13 @@ impl TextureArray { fn new( format: ImageFormat, filter: TextureFilter, - layer_count: usize + dimensions: u32, + layer_count: usize, ) -> Self { TextureArray { format, filter, + dimensions, layer_count, is_allocated: false, regions: Vec::new(), @@ -1112,8 +1126,8 @@ impl TextureArray { fn update_profile(&self, counter: &mut ResourceProfileCounter) { if self.is_allocated { - let size = self.layer_count as u32 * TEXTURE_LAYER_DIMENSIONS * - TEXTURE_LAYER_DIMENSIONS * self.format.bytes_per_pixel(); + let size = self.layer_count as u32 * self.dimensions * + self.dimensions * self.format.bytes_per_pixel(); counter.set(self.layer_count as usize, size as usize); } else { counter.set(0, 0); @@ -1132,8 +1146,8 @@ impl TextureArray { // This means that very rarely used image formats can be // added but won't allocate a cache if never used. if !self.is_allocated { - debug_assert!(TEXTURE_LAYER_DIMENSIONS % TEXTURE_REGION_DIMENSIONS == 0); - let regions_per_axis = TEXTURE_LAYER_DIMENSIONS / TEXTURE_REGION_DIMENSIONS; + debug_assert!(self.dimensions % TEXTURE_REGION_DIMENSIONS == 0); + let regions_per_axis = self.dimensions / TEXTURE_REGION_DIMENSIONS; for layer_index in 0 .. self.layer_count { for y in 0 .. regions_per_axis { for x in 0 .. regions_per_axis { diff --git a/gfx/webrender_bindings/revision.txt b/gfx/webrender_bindings/revision.txt index b04fb7b84fd0..b06999e5f49d 100644 --- a/gfx/webrender_bindings/revision.txt +++ b/gfx/webrender_bindings/revision.txt @@ -1 +1 @@ -74f265e447d2927c27d4320c676779956d39eaf0 +b648c76e2dc2cbcbd635322cdf94ab9d5320e0c1 diff --git a/ipc/chromium/src/base/message_pump_default.cc b/ipc/chromium/src/base/message_pump_default.cc index 076b21d66b60..af2cecd9c70c 100644 --- a/ipc/chromium/src/base/message_pump_default.cc +++ b/ipc/chromium/src/base/message_pump_default.cc @@ -60,6 +60,19 @@ void MessagePumpDefault::Run(Delegate* delegate) { AUTO_PROFILER_LABEL("MessagePumpDefault::Run:Wait", IDLE); { AUTO_PROFILER_THREAD_SLEEP; + // Some threads operating a message pump (i.e. the compositor thread) + // can diverge from Web Replay recordings, after which the thread needs + // to be explicitly notified in order to coordinate with the + // record/replay checkpoint system. + if (mozilla::recordreplay::IsRecordingOrReplaying()) { + // Workaround static analysis. Message pumps for which the lambda + // below might be called will not be destroyed. + void* thiz = this; + + mozilla::recordreplay::MaybeWaitForCheckpointSave(); + mozilla::recordreplay::NotifyUnrecordedWait([=]() { ((MessagePumpDefault*)thiz)->ScheduleWork(); }, + /* aOnlyWhenDiverged = */ true); + } event_.Wait(); } } else { diff --git a/ipc/glue/MessageChannel.cpp b/ipc/glue/MessageChannel.cpp index 275dea9371e6..19abf41b3619 100644 --- a/ipc/glue/MessageChannel.cpp +++ b/ipc/glue/MessageChannel.cpp @@ -995,6 +995,17 @@ MessageChannel::Echo(Message* aMsg) bool MessageChannel::Send(Message* aMsg) { + if (recordreplay::HasDivergedFromRecording() && + recordreplay::child::SuppressMessageAfterDiverge(aMsg)) + { + // Only certain IPDL messages are allowed to be sent in a replaying + // process after it has diverged from the recording, to avoid + // deadlocking with threads that remain idle. The browser remains + // paused after diverging from the recording, and other IPDL messages + // do not need to be sent. + return true; + } + if (aMsg->size() >= kMinTelemetryMessageSize) { Telemetry::Accumulate(Telemetry::IPC_MESSAGE_SIZE2, aMsg->size()); } diff --git a/js/src/jit-test/tests/wasm/passive-segs-nonboundary.js b/js/src/jit-test/tests/wasm/passive-segs-nonboundary.js index 3e175b2f519b..1c7bf502d4dc 100644 --- a/js/src/jit-test/tests/wasm/passive-segs-nonboundary.js +++ b/js/src/jit-test/tests/wasm/passive-segs-nonboundary.js @@ -356,21 +356,21 @@ function checkMiscPrefixed(opcode, expect_failure) { [funcBody( {locals:[], body:[0x41, 0x0, 0x41, 0x0, 0x41, 0x0, // 3 x const.i32 0 - MiscPrefix, opcode]})])]); + MiscPrefix, ...opcode]})])]); if (expect_failure) { assertErrorMessage(() => new WebAssembly.Module(binary), WebAssembly.CompileError, /unrecognized opcode/); } else { - assertEq(true, WebAssembly.validate(binary)); + assertEq(WebAssembly.validate(binary), true); } } //----------------------------------------------------------- // Verification cases for memory.copy/fill opcode encodings -checkMiscPrefixed(0x0a, false); // memory.copy -checkMiscPrefixed(0x0b, false); // memory.fill -checkMiscPrefixed(0x0f, true); // table.copy+1, which is currently unassigned +checkMiscPrefixed([0x0a, 0x00], false); // memory.copy, flags=0 +checkMiscPrefixed([0x0b, 0x00], false); // memory.fill, flags=0 +checkMiscPrefixed([0x0f], true); // table.copy+1, which is currently unassigned //----------------------------------------------------------- // Verification cases for memory.copy/fill arguments diff --git a/js/src/vm/HelperThreads.cpp b/js/src/vm/HelperThreads.cpp index 94ccbf0a48fa..e0fdec9ce7b6 100644 --- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -162,7 +162,8 @@ MaybeWaitForRecordReplayCheckpointSave(AutoLockHelperThreadState& locked) // Now that we are holding the helper thread state lock again, // notify the record/replay system that we might block soon. - mozilla::recordreplay::NotifyUnrecordedWait(HelperThread::WakeupAll); + mozilla::recordreplay::NotifyUnrecordedWait(HelperThread::WakeupAll, + /* aOnlyWhenDiverged = */ false); } } diff --git a/js/src/vm/TypedArrayObject-inl.h b/js/src/vm/TypedArrayObject-inl.h index ed433abb9acd..b48112febe32 100644 --- a/js/src/vm/TypedArrayObject-inl.h +++ b/js/src/vm/TypedArrayObject-inl.h @@ -279,17 +279,10 @@ class ElementSpecific return true; } - // Inhibit unaligned accesses on ARM (bug 1097253, a compiler bug). -#if defined(__arm__) && MOZ_IS_GCC -# define JS_VOLATILE_ARM volatile -#else -# define JS_VOLATILE_ARM -#endif - SharedMem data = Ops::extract(source); switch (source->type()) { case Scalar::Int8: { - SharedMem src = data.cast(); + SharedMem src = data.cast(); for (uint32_t i = 0; i < count; ++i) { Ops::store(dest++, ConvertNumber(Ops::load(src++))); } @@ -297,49 +290,49 @@ class ElementSpecific } case Scalar::Uint8: case Scalar::Uint8Clamped: { - SharedMem src = data.cast(); + SharedMem src = data.cast(); for (uint32_t i = 0; i < count; ++i) { Ops::store(dest++, ConvertNumber(Ops::load(src++))); } break; } case Scalar::Int16: { - SharedMem src = data.cast(); + SharedMem src = data.cast(); for (uint32_t i = 0; i < count; ++i) { Ops::store(dest++, ConvertNumber(Ops::load(src++))); } break; } case Scalar::Uint16: { - SharedMem src = data.cast(); + SharedMem src = data.cast(); for (uint32_t i = 0; i < count; ++i) { Ops::store(dest++, ConvertNumber(Ops::load(src++))); } break; } case Scalar::Int32: { - SharedMem src = data.cast(); + SharedMem src = data.cast(); for (uint32_t i = 0; i < count; ++i) { Ops::store(dest++, ConvertNumber(Ops::load(src++))); } break; } case Scalar::Uint32: { - SharedMem src = data.cast(); + SharedMem src = data.cast(); for (uint32_t i = 0; i < count; ++i) { Ops::store(dest++, ConvertNumber(Ops::load(src++))); } break; } case Scalar::Float32: { - SharedMem src = data.cast(); + SharedMem src = data.cast(); for (uint32_t i = 0; i < count; ++i) { Ops::store(dest++, ConvertNumber(Ops::load(src++))); } break; } case Scalar::Float64: { - SharedMem src = data.cast(); + SharedMem src = data.cast(); for (uint32_t i = 0; i < count; ++i) { Ops::store(dest++, ConvertNumber(Ops::load(src++))); } @@ -349,8 +342,6 @@ class ElementSpecific MOZ_CRASH("setFromTypedArray with a typed array with bogus type"); } -#undef JS_VOLATILE_ARM - return true; } diff --git a/js/src/wasm/WasmBaselineCompile.cpp b/js/src/wasm/WasmBaselineCompile.cpp index 1d5a135393e0..0eb673d4c3f3 100644 --- a/js/src/wasm/WasmBaselineCompile.cpp +++ b/js/src/wasm/WasmBaselineCompile.cpp @@ -9239,8 +9239,15 @@ BaseCompiler::emitInstanceCall(uint32_t lineOrBytecode, const MIRTypeVector& sig popValueStackBy(numArgs); - // Note, a number of clients of emitInstanceCall currently assume that the - // following operation does not destroy ReturnReg. + // Note, many clients of emitInstanceCall currently assume that pushing the + // result here does not destroy ReturnReg. + // + // Furthermore, clients assume that even if retType == ExprType::Void, the + // callee may have returned a status result and left it in ReturnReg for us + // to find, and that that register will not be destroyed here (or above). + // In this case the callee will have a C++ declaration stating that there is + // a return value. Examples include memory and table operations that are + // implemented as callouts. pushReturnedIfNonVoid(baselineCall, retType); } @@ -10080,14 +10087,7 @@ BaseCompiler::emitStructNarrow() return true; } - // Null pointers are just passed through. - - Label done; - Label doTest; RegPtr rp = popRef(); - masm.branchTestPtr(Assembler::NonZero, rp, rp, &doTest); - pushRef(NULLREF_VALUE); - masm.jump(&done); // AnyRef -> (ref T) must first unbox; leaves rp or null @@ -10097,15 +10097,11 @@ BaseCompiler::emitStructNarrow() const StructType& outputStruct = env_.types[outputType.refTypeIndex()].structType(); - masm.bind(&doTest); - pushI32(mustUnboxAnyref); pushI32(outputStruct.moduleIndex_); pushRef(rp); emitInstanceCall(lineOrBytecode, SigPIIP_, ExprType::AnyRef, SymbolicAddress::StructNarrow); - masm.bind(&done); - return true; } diff --git a/js/src/wasm/WasmInstance.cpp b/js/src/wasm/WasmInstance.cpp index c49bf81bf112..ae32a6585e99 100644 --- a/js/src/wasm/WasmInstance.cpp +++ b/js/src/wasm/WasmInstance.cpp @@ -714,13 +714,18 @@ Instance::structNew(Instance* instance, uint32_t typeIndex) /* static */ void* Instance::structNarrow(Instance* instance, uint32_t mustUnboxAnyref, uint32_t outputTypeIndex, - void* nonnullPtr) + void* maybeNullPtr) { JSContext* cx = TlsContext.get(); Rooted obj(cx); Rooted typeDescr(cx); + if (maybeNullPtr == nullptr) { + return maybeNullPtr; + } + + void* nonnullPtr = maybeNullPtr; if (mustUnboxAnyref) { Rooted no(cx, static_cast(nonnullPtr)); if (!no->is()) { diff --git a/js/src/wasm/WasmInstance.h b/js/src/wasm/WasmInstance.h index 5b98b6f35b1f..c9d4c5c2a64f 100644 --- a/js/src/wasm/WasmInstance.h +++ b/js/src/wasm/WasmInstance.h @@ -192,7 +192,8 @@ class Instance uint32_t srcOffset, uint32_t len, uint32_t segIndex); static void postBarrier(Instance* instance, gc::Cell** location); static void* structNew(Instance* instance, uint32_t typeIndex); - static void* structNarrow(Instance* instance, uint32_t mustUnboxAnyref, uint32_t outputTypeIndex, void* ptr); + static void* structNarrow(Instance* instance, uint32_t mustUnboxAnyref, uint32_t outputTypeIndex, + void* maybeNullPtr); }; typedef UniquePtr UniqueInstance; diff --git a/js/src/wasm/WasmOpIter.h b/js/src/wasm/WasmOpIter.h index f4e983bc178d..cc4f43d7639a 100644 --- a/js/src/wasm/WasmOpIter.h +++ b/js/src/wasm/WasmOpIter.h @@ -2055,6 +2055,14 @@ OpIter::readMemOrTableCopy(bool isMem, Value* dst, Value* src, Value* le } } + uint8_t memOrTableFlags; + if (!readFixedU8(&memOrTableFlags)) { + return fail(isMem ? "unable to read memory flags" : "unable to read table flags"); + } + if (memOrTableFlags != 0) { + return fail(isMem ? "memory flags must be zero" : "table flags must be zero"); + } + if (!popWithType(ValType::I32, len)) { return false; } @@ -2114,6 +2122,14 @@ OpIter::readMemFill(Value* start, Value* val, Value* len) return fail("can't touch memory without memory"); } + uint8_t memoryFlags; + if (!readFixedU8(&memoryFlags)) { + return fail("unable to read memory flags"); + } + if (memoryFlags != 0) { + return fail("memory flags must be zero"); + } + if (!popWithType(ValType::I32, len)) { return false; } @@ -2158,6 +2174,14 @@ OpIter::readMemOrTableInit(bool isMem, uint32_t* segIndex, return false; } + uint8_t memOrTableFlags; + if (!readFixedU8(&memOrTableFlags)) { + return fail(isMem ? "unable to read memory flags" : "unable to read table flags"); + } + if (memOrTableFlags != 0) { + return fail(isMem ? "memory flags must be zero" : "table flags must be zero"); + } + if (!readVarU32(segIndex)) { return false; } diff --git a/js/src/wasm/WasmTextToBinary.cpp b/js/src/wasm/WasmTextToBinary.cpp index b3e213d2f105..2d4aed305dd7 100644 --- a/js/src/wasm/WasmTextToBinary.cpp +++ b/js/src/wasm/WasmTextToBinary.cpp @@ -6290,13 +6290,16 @@ EncodeWake(Encoder& e, AstWake& s) } #ifdef ENABLE_WASM_BULKMEM_OPS +static const uint8_t DEFAULT_MEM_TABLE_FLAGS = 0; + static bool EncodeMemOrTableCopy(Encoder& e, AstMemOrTableCopy& s) { return EncodeExpr(e, s.dest()) && EncodeExpr(e, s.src()) && EncodeExpr(e, s.len()) && - e.writeOp(s.isMem() ? MiscOp::MemCopy : MiscOp::TableCopy); + e.writeOp(s.isMem() ? MiscOp::MemCopy : MiscOp::TableCopy) && + e.writeFixedU8(DEFAULT_MEM_TABLE_FLAGS); } static bool @@ -6312,7 +6315,8 @@ EncodeMemFill(Encoder& e, AstMemFill& s) return EncodeExpr(e, s.start()) && EncodeExpr(e, s.val()) && EncodeExpr(e, s.len()) && - e.writeOp(MiscOp::MemFill); + e.writeOp(MiscOp::MemFill) && + e.writeFixedU8(DEFAULT_MEM_TABLE_FLAGS); } static bool @@ -6322,6 +6326,7 @@ EncodeMemOrTableInit(Encoder& e, AstMemOrTableInit& s) EncodeExpr(e, s.src()) && EncodeExpr(e, s.len()) && e.writeOp(s.isMem() ? MiscOp::MemInit : MiscOp::TableInit) && + e.writeFixedU8(DEFAULT_MEM_TABLE_FLAGS) && e.writeVarU32(s.segIndex()); } #endif diff --git a/layout/ipc/VsyncChild.cpp b/layout/ipc/VsyncChild.cpp index d0b12c01b86c..b7299b58e7b8 100644 --- a/layout/ipc/VsyncChild.cpp +++ b/layout/ipc/VsyncChild.cpp @@ -67,14 +67,12 @@ VsyncChild::RecvNotify(const TimeStamp& aVsyncTimestamp) MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!mIsShutdown); - // Ignore Vsync messages sent to a recording/replaying process. Vsyncs are - // triggered at the top of the main thread's event loop instead. - if (recordreplay::IsRecordingOrReplaying()) { - return IPC_OK(); - } - SchedulerGroup::MarkVsyncRan(); if (mObservingVsync && mObserver) { + if (recordreplay::IsRecordingOrReplaying() && !recordreplay::child::OnVsync()) { + return IPC_OK(); + } + mObserver->NotifyVsync(aVsyncTimestamp); } return IPC_OK(); diff --git a/mfbt/RecordReplay.cpp b/mfbt/RecordReplay.cpp index 1e1aaeefa084..8be2d3394f72 100644 --- a/mfbt/RecordReplay.cpp +++ b/mfbt/RecordReplay.cpp @@ -51,7 +51,8 @@ namespace recordreplay { Macro(InternalRecordReplayBytes, \ (void* aData, size_t aSize), (aData, aSize)) \ Macro(NotifyUnrecordedWait, \ - (const std::function& aCallback), (aCallback)) \ + (const std::function& aCallback, bool aOnlyWhenDiverged), \ + (aCallback, aOnlyWhenDiverged)) \ Macro(MaybeWaitForCheckpointSave, (), ()) \ Macro(InternalInvalidateRecording, (const char* aWhy), (aWhy)) \ Macro(InternalDestroyPLDHashTableCallbacks, \ diff --git a/mfbt/RecordReplay.h b/mfbt/RecordReplay.h index ef80f44d3da0..bf0334d8aefb 100644 --- a/mfbt/RecordReplay.h +++ b/mfbt/RecordReplay.h @@ -258,12 +258,16 @@ static inline bool HasDivergedFromRecording(); // The callback passed to NotifyUnrecordedWait will be invoked at most once // by the main thread whenever the main thread is waiting for other threads to // become idle, and at most once after the call to NotifyUnrecordedWait if the -// main thread is already waiting for other threads to become idle. +// main thread is already waiting for other threads to become idle. If +// aOnlyWhenDiverged is specified, the callback will only be invoked if the +// thread has diverged from the recording (causing all resources to be treated +// as unrecorded). // // The callback should poke the thread so that it is no longer blocked on the // resource. The thread must call MaybeWaitForCheckpointSave before blocking // again. -MFBT_API void NotifyUnrecordedWait(const std::function& aCallback); +MFBT_API void NotifyUnrecordedWait(const std::function& aCallback, + bool aOnlyWhenDiverged); MFBT_API void MaybeWaitForCheckpointSave(); // API for debugging inconsistent behavior between recording and replay. diff --git a/modules/libmar/sign/mar_sign.c b/modules/libmar/sign/mar_sign.c index ccefd75dde69..43230cfc4dca 100644 --- a/modules/libmar/sign/mar_sign.c +++ b/modules/libmar/sign/mar_sign.c @@ -534,6 +534,7 @@ extract_signature(const char *src, uint32_t sigIndex, const char * dest) for (i = 0; i <= sigIndex; i++) { /* Avoid leaking while skipping signatures */ free(extractedSignature); + extractedSignature = NULL; /* skip past the signature algorithm ID */ if (fseeko(fpSrc, sizeof(uint32_t), SEEK_CUR)) { diff --git a/taskcluster/ci/build/windows.yml b/taskcluster/ci/build/windows.yml index a5e279bf57e0..7d70b4f4b975 100755 --- a/taskcluster/ci/build/windows.yml +++ b/taskcluster/ci/build/windows.yml @@ -919,7 +919,7 @@ win32-mingwclang/opt: - linux64-sccache - linux64-cbindgen - linux64-node - - linux64-clang-7-mingw-x86 + - linux64-clang-trunk-mingw-x86 - linux64-mingw32-nsis - linux64-mingw32-fxc2 @@ -955,7 +955,7 @@ win32-mingwclang/debug: - linux64-sccache - linux64-cbindgen - linux64-node - - linux64-clang-7-mingw-x86 + - linux64-clang-trunk-mingw-x86 - linux64-mingw32-nsis - linux64-mingw32-fxc2 @@ -990,7 +990,7 @@ win64-mingwclang/opt: - linux64-sccache - linux64-cbindgen - linux64-node - - linux64-clang-7-mingw-x64 + - linux64-clang-trunk-mingw-x64 - linux64-mingw32-nsis - linux64-mingw32-fxc2 @@ -1026,6 +1026,6 @@ win64-mingwclang/debug: - linux64-sccache - linux64-cbindgen - linux64-node - - linux64-clang-7-mingw-x64 + - linux64-clang-trunk-mingw-x64 - linux64-mingw32-nsis - linux64-mingw32-fxc2 diff --git a/taskcluster/ci/release-mark-as-started/kind.yml b/taskcluster/ci/release-mark-as-started/kind.yml new file mode 100644 index 000000000000..1ca751ae78dd --- /dev/null +++ b/taskcluster/ci/release-mark-as-started/kind.yml @@ -0,0 +1,51 @@ +# 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/. + +loader: taskgraph.loader.transform:loader + +transforms: + - taskgraph.transforms.release_mark_as_started:transforms + - taskgraph.transforms.task:transforms + +job-defaults: + description: mark release as started in Ship-It v1 + worker-type: + by-project: + mozilla-beta: scriptworker-prov-v1/shipit-v1 + mozilla-release: scriptworker-prov-v1/shipit-v1 + mozilla-esr60: scriptworker-prov-v1/shipit-v1 + default: scriptworker-prov-v1/shipit-dev + worker: + implementation: shipit-started + scopes: + by-project: + mozilla-beta: + - project:releng:ship-it:server:production + - project:releng:ship-it:action:mark-as-started + mozilla-release: + - project:releng:ship-it:server:production + - project:releng:ship-it:action:mark-as-started + mozilla-esr60: + - project:releng:ship-it:server:production + - project:releng:ship-it:action:mark-as-started + default: + - project:releng:ship-it:server:staging + - project:releng:ship-it:action:mark-as-started + run-on-projects: [] + shipping-phase: promote + locales-file: browser/locales/l10n-changesets.json + +jobs: + fennec: + name: release-fennec_mark_as_started + shipping-product: fennec + locales-file: mobile/locales/l10n-changesets.json + + firefox: + name: release-firefox_mark_as_started + shipping-product: firefox + + devedition: + name: release-devedition_mark_as_started + shipping-product: devedition diff --git a/taskcluster/ci/toolchain/linux.yml b/taskcluster/ci/toolchain/linux.yml index bc3d886bc960..be43d973f47f 100755 --- a/taskcluster/ci/toolchain/linux.yml +++ b/taskcluster/ci/toolchain/linux.yml @@ -70,49 +70,49 @@ linux64-clang-7: toolchains: - linux64-gcc-4.9 -linux64-clang-7-mingw-x86: - description: "MinGW-Clang 7 x86 toolchain build" +linux64-clang-trunk-mingw-x86: + description: "MinGW-Clang Trunk x86 toolchain build" treeherder: kind: build platform: toolchains/opt - symbol: TMW(clang7-32) + symbol: TMW(clang-x86) tier: 2 worker-type: aws-provisioner-v1/gecko-{level}-b-linux-xlarge worker: max-run-time: 7200 run: using: toolchain-script - script: build-clang-7-mingw.sh + script: build-clang-trunk-mingw.sh arguments: [ 'x86' ] resources: - 'build/build-clang/build-clang.py' - - 'build/build-clang/clang-7-mingw.json' + - 'build/build-clang/clang-trunk-mingw.json' - 'taskcluster/scripts/misc/tooltool-download.sh' toolchain-artifact: public/build/clangmingw.tar.xz toolchains: - linux64-gcc-4.9 -linux64-clang-7-mingw-x64: - description: "MinGW-Clang 7 Pre x64 toolchain build" +linux64-clang-trunk-mingw-x64: + description: "MinGW-Clang Trunk x64 toolchain build" treeherder: kind: build platform: toolchains/opt - symbol: TMW(clang7-64) + symbol: TMW(clang-x64) tier: 2 worker-type: aws-provisioner-v1/gecko-{level}-b-linux-xlarge worker: max-run-time: 7200 run: using: toolchain-script - script: build-clang-7-mingw.sh + script: build-clang-trunk-mingw.sh arguments: [ 'x64' ] resources: - 'build/build-clang/build-clang.py' - - 'build/build-clang/clang-7-mingw.json' + - 'build/build-clang/clang-trunk-mingw.json' - 'taskcluster/scripts/misc/tooltool-download.sh' toolchain-artifact: public/build/clangmingw.tar.xz toolchains: diff --git a/taskcluster/docs/kinds.rst b/taskcluster/docs/kinds.rst index 3813e9ad5c83..6f70e2c8d501 100644 --- a/taskcluster/docs/kinds.rst +++ b/taskcluster/docs/kinds.rst @@ -295,6 +295,10 @@ release-mark-as-shipped ----------------------- Marks releases as shipped in Ship-It v1 +release-mark-as-started +----------------------- +Marks releases as started in Ship-It v1 + release-bouncer-aliases ----------------------- Update Bouncer's (download.mozilla.org) "latest" aliases. diff --git a/taskcluster/scripts/misc/build-clang-7-mingw.sh b/taskcluster/scripts/misc/build-clang-trunk-mingw.sh similarity index 98% rename from taskcluster/scripts/misc/build-clang-7-mingw.sh rename to taskcluster/scripts/misc/build-clang-trunk-mingw.sh index 014e880c5893..d8645b8d584f 100755 --- a/taskcluster/scripts/misc/build-clang-7-mingw.sh +++ b/taskcluster/scripts/misc/build-clang-trunk-mingw.sh @@ -30,7 +30,6 @@ INSTALL_DIR=$TOOLCHAIN_DIR/build/stage3/clang CROSS_PREFIX_DIR=$INSTALL_DIR/$machine-w64-mingw32 SRC_DIR=$TOOLCHAIN_DIR/src -CLANG_VERSION=7.0.0 make_flags="-j$(nproc)" mingw_version=cfd85ebed773810429bf2164c3a985895b7dbfe3 @@ -137,6 +136,7 @@ build_mingw() { } build_compiler_rt() { + CLANG_VERSION=$(basename $(dirname $(dirname $(dirname $($CC --print-libgcc-file-name -rtlib=compiler-rt))))) mkdir compiler-rt pushd compiler-rt cmake \ @@ -292,7 +292,7 @@ set +x cd build/build-clang # |mach python| sets up a virtualenv for us! -../../mach python ./build-clang.py -c clang-7-mingw.json --skip-tar +../../mach python ./build-clang.py -c clang-trunk-mingw.json --skip-tar set -x diff --git a/taskcluster/taskgraph/transforms/release_mark_as_started.py b/taskcluster/taskgraph/transforms/release_mark_as_started.py new file mode 100644 index 000000000000..a39ae6ac5f80 --- /dev/null +++ b/taskcluster/taskgraph/transforms/release_mark_as_started.py @@ -0,0 +1,57 @@ + +# 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/. +""" +Add from parameters.yml into Balrog publishing tasks. +""" + +from __future__ import absolute_import, print_function, unicode_literals + +import json + +from taskgraph.transforms.base import TransformSequence +from taskgraph.transforms.l10n import parse_locales_file +from taskgraph.util.schema import resolve_keyed_by +from taskgraph.util.scriptworker import get_release_config + +transforms = TransformSequence() + + +@transforms.add +def make_task_description(config, jobs): + release_config = get_release_config(config) + for job in jobs: + resolve_keyed_by( + job, 'worker-type', item_name=job['name'], project=config.params['project'] + ) + resolve_keyed_by( + job, 'scopes', item_name=job['name'], project=config.params['project'] + ) + + job['worker']['release-name'] = '{product}-{version}-build{build_number}'.format( + product=job['shipping-product'].capitalize(), + version=release_config['version'], + build_number=release_config['build_number'] + ) + job['worker']['product'] = job['shipping-product'] + branch = config.params['head_repository'].split('https://hg.mozilla.org/')[1] + job['worker']['branch'] = branch + + # locales files has different structure between mobile and desktop + locales_file = job['locales-file'] + all_locales = {} + + if job['shipping-product'] == 'fennec': + with open(locales_file, mode='r') as f: + all_locales = json.dumps(json.load(f)) + else: + all_locales = "\n".join([ + "{} {}".format(locale, revision) + for locale, revision in sorted(parse_locales_file(job['locales-file']).items()) + ]) + + job['worker']['locales'] = all_locales + del job['locales-file'] + + yield job diff --git a/taskcluster/taskgraph/transforms/task.py b/taskcluster/taskgraph/transforms/task.py index 46ec9113b54d..bf694fbd165e 100644 --- a/taskcluster/taskgraph/transforms/task.py +++ b/taskcluster/taskgraph/transforms/task.py @@ -616,6 +616,12 @@ task_description_schema = Schema({ }, { Required('implementation'): 'shipit-shipped', Required('release-name'): basestring, + }, { + Required('implementation'): 'shipit-started', + Required('release-name'): basestring, + Required('product'): basestring, + Required('branch'): basestring, + Required('locales'): basestring, }, { Required('implementation'): 'treescript', Required('tags'): [Any('buildN', 'release', None)], @@ -1251,6 +1257,23 @@ def build_ship_it_shipped_payload(config, task, task_def): } +@payload_builder('shipit-started') +def build_ship_it_started_payload(config, task, task_def): + worker = task['worker'] + release_config = get_release_config(config) + + task_def['payload'] = { + 'release_name': worker['release-name'], + 'product': worker['product'], + 'version': release_config['version'], + 'build_number': release_config['build_number'], + 'branch': worker['branch'], + 'revision': get_branch_rev(config), + 'partials': release_config.get('partial_versions', ""), + 'l10n_changesets': worker['locales'], + } + + @payload_builder('sign-and-push-addons') def build_sign_and_push_addons_payload(config, task, task_def): worker = task['worker'] diff --git a/taskcluster/taskgraph/util/scriptworker.py b/taskcluster/taskgraph/util/scriptworker.py index 18859bfda5ea..196d6fff708a 100644 --- a/taskcluster/taskgraph/util/scriptworker.py +++ b/taskcluster/taskgraph/util/scriptworker.py @@ -337,6 +337,7 @@ def get_release_config(config): 'release-secondary-update-verify-config', 'release-balrog-submit-toplevel', 'release-secondary-balrog-submit-toplevel', + 'release-mark-as-started' ): partial_updates = json.loads(partial_updates) release_config['partial_versions'] = ', '.join([ diff --git a/toolkit/recordreplay/BufferStream.h b/toolkit/recordreplay/BufferStream.h new file mode 100644 index 000000000000..5722e4ccfd54 --- /dev/null +++ b/toolkit/recordreplay/BufferStream.h @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 mozilla_recordreplay_BufferStream_h +#define mozilla_recordreplay_BufferStream_h + +#include "InfallibleVector.h" + +namespace mozilla { +namespace recordreplay { + +// BufferStream provides similar functionality to Stream in File.h, allowing +// reading or writing to a stream of data backed by an in memory buffer instead +// of data stored on disk. +class BufferStream +{ + InfallibleVector* mOutput; + + const char* mInput; + size_t mInputSize; + +public: + BufferStream(const char* aInput, size_t aInputSize) + : mOutput(nullptr), mInput(aInput), mInputSize(aInputSize) + {} + + explicit BufferStream(InfallibleVector* aOutput) + : mOutput(aOutput), mInput(nullptr), mInputSize(0) + {} + + void WriteBytes(const void* aData, size_t aSize) { + MOZ_RELEASE_ASSERT(mOutput); + mOutput->append((char*) aData, aSize); + } + + void WriteScalar(size_t aValue) { + WriteBytes(&aValue, sizeof(aValue)); + } + + void ReadBytes(void* aData, size_t aSize) { + if (aSize) { + MOZ_RELEASE_ASSERT(mInput); + MOZ_RELEASE_ASSERT(aSize <= mInputSize); + memcpy(aData, mInput, aSize); + mInput += aSize; + mInputSize -= aSize; + } + } + + size_t ReadScalar() { + size_t rv; + ReadBytes(&rv, sizeof(rv)); + return rv; + } + + bool IsEmpty() { + MOZ_RELEASE_ASSERT(mInput); + return mInputSize == 0; + } +}; + +} // namespace recordreplay +} // namespace mozilla + +#endif // mozilla_recordreplay_BufferStream_h diff --git a/toolkit/recordreplay/Callback.cpp b/toolkit/recordreplay/Callback.cpp index 5b2d9dd3e819..5cc0124dc330 100644 --- a/toolkit/recordreplay/Callback.cpp +++ b/toolkit/recordreplay/Callback.cpp @@ -49,6 +49,9 @@ BeginCallback(size_t aCallbackId) } thread->SetPassThrough(false); + RecordingEventSection res(thread); + MOZ_RELEASE_ASSERT(res.CanAccessEvents()); + thread->Events().RecordOrReplayThreadEvent(ThreadEvent::ExecuteCallback); thread->Events().WriteScalar(aCallbackId); } @@ -73,7 +76,8 @@ SaveOrRestoreCallbackData(void** aData) MOZ_RELEASE_ASSERT(gCallbackData); Thread* thread = Thread::Current(); - MOZ_RELEASE_ASSERT(thread->CanAccessRecording()); + RecordingEventSection res(thread); + MOZ_RELEASE_ASSERT(res.CanAccessEvents()); thread->Events().RecordOrReplayThreadEvent(ThreadEvent::RestoreCallbackData); @@ -102,7 +106,8 @@ void PassThroughThreadEventsAllowCallbacks(const std::function& aFn) { Thread* thread = Thread::Current(); - MOZ_RELEASE_ASSERT(thread->CanAccessRecording()); + RecordingEventSection res(thread); + MOZ_RELEASE_ASSERT(res.CanAccessEvents()); if (IsRecording()) { if (thread->IsMainThread()) { diff --git a/toolkit/recordreplay/File.cpp b/toolkit/recordreplay/File.cpp index da2db9d46476..0f59cf3ee222 100644 --- a/toolkit/recordreplay/File.cpp +++ b/toolkit/recordreplay/File.cpp @@ -47,13 +47,7 @@ Stream::ReadBytes(void* aData, size_t aSize) } MOZ_RELEASE_ASSERT(mBufferPos == mBufferLength); - - // If we try to read off the end of a stream then we must have hit the end - // of the replay for this thread. - while (mChunkIndex == mChunks.length()) { - MOZ_RELEASE_ASSERT(mName == StreamName::Event); - HitEndOfRecording(); - } + MOZ_RELEASE_ASSERT(mChunkIndex < mChunks.length()); const StreamChunkLocation& chunk = mChunks[mChunkIndex++]; @@ -89,6 +83,7 @@ void Stream::WriteBytes(const void* aData, size_t aSize) { MOZ_RELEASE_ASSERT(mFile->OpenForWriting()); + MOZ_RELEASE_ASSERT(mName != StreamName::Event || mInRecordingEventSection); // Prevent the entire file from being flushed while we write this data. AutoReadSpinLock streamLock(mFile->mStreamLock); diff --git a/toolkit/recordreplay/File.h b/toolkit/recordreplay/File.h index 65c27ff7b767..f55f9a1972d2 100644 --- a/toolkit/recordreplay/File.h +++ b/toolkit/recordreplay/File.h @@ -60,10 +60,12 @@ enum class StreamName }; class File; +class RecordingEventSection; class Stream { friend class File; + friend class RecordingEventSection; // File this stream belongs to. File* mFile; @@ -117,6 +119,9 @@ class Stream // flushed. size_t mFlushedChunks; + // Whether there is a RecordingEventSection instance active for this stream. + bool mInRecordingEventSection; + Stream(File* aFile, StreamName aName, size_t aNameIndex) : mFile(aFile) , mName(aName) @@ -133,6 +138,7 @@ class Stream , mLastEvent((ThreadEvent) 0) , mChunkIndex(0) , mFlushedChunks(0) + , mInRecordingEventSection(false) {} public: @@ -204,6 +210,7 @@ public: }; friend class Stream; + friend class RecordingEventSection; private: // Open file handle, or 0 if closed. diff --git a/toolkit/recordreplay/Lock.cpp b/toolkit/recordreplay/Lock.cpp index 02bf3bdc3248..85a236d0939e 100644 --- a/toolkit/recordreplay/Lock.cpp +++ b/toolkit/recordreplay/Lock.cpp @@ -6,8 +6,6 @@ #include "Lock.h" -#include "mozilla/StaticMutex.h" - #include "ChunkAllocator.h" #include "InfallibleVector.h" #include "SpinLock.h" @@ -69,13 +67,12 @@ static ReadWriteSpinLock gLocksLock; Lock::New(void* aNativeLock) { Thread* thread = Thread::Current(); - if (!thread || thread->PassThroughEvents() || HasDivergedFromRecording()) { + RecordingEventSection res(thread); + if (!res.CanAccessEvents()) { Destroy(aNativeLock); // Clean up any old lock, as below. return; } - MOZ_RELEASE_ASSERT(thread->CanAccessRecording()); - thread->Events().RecordOrReplayThreadEvent(ThreadEvent::CreateLock); size_t id; @@ -152,7 +149,11 @@ void Lock::Enter() { Thread* thread = Thread::Current(); - MOZ_RELEASE_ASSERT(thread->CanAccessRecording()); + + RecordingEventSection res(thread); + if (!res.CanAccessEvents()) { + return; + } // Include an event in each thread's record when a lock acquire begins. This // is not required by the replay but is used to check that lock acquire order @@ -165,8 +166,9 @@ Lock::Enter() if (IsRecording()) { acquires->mAcquires->WriteScalar(thread->Id()); } else { - // Wait until this thread is next in line to acquire the lock. - while (thread->Id() != acquires->mNextOwner) { + // Wait until this thread is next in line to acquire the lock, or until it + // has been instructed to diverge from the recording. + while (thread->Id() != acquires->mNextOwner && !thread->MaybeDivergeFromRecording()) { Thread::Wait(); } } @@ -175,10 +177,11 @@ Lock::Enter() void Lock::Exit() { - if (IsReplaying()) { + Thread* thread = Thread::Current(); + if (IsReplaying() && !thread->HasDivergedFromRecording()) { // Notify the next owner before releasing the lock. LockAcquires* acquires = gLockAcquires.Get(mId); - acquires->ReadAndNotifyNextOwner(Thread::Current()); + acquires->ReadAndNotifyNextOwner(thread); } } @@ -188,9 +191,7 @@ struct AtomicLock : public detail::MutexImpl using detail::MutexImpl::unlock; }; -// Lock which is held during code sections that run atomically. This is a -// PRLock instead of an OffTheBooksMutex because the latter performs atomic -// operations during initialization. +// Lock which is held during code sections that run atomically. static AtomicLock* gAtomicLock = nullptr; /* static */ void diff --git a/toolkit/recordreplay/MiddlemanCall.cpp b/toolkit/recordreplay/MiddlemanCall.cpp new file mode 100644 index 000000000000..23bb91e01f7f --- /dev/null +++ b/toolkit/recordreplay/MiddlemanCall.cpp @@ -0,0 +1,426 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "MiddlemanCall.h" + +#include + +namespace mozilla { +namespace recordreplay { + +// In a replaying or middleman process, all middleman calls that have been +// encountered, indexed by their ID. +static StaticInfallibleVector gMiddlemanCalls; + +// In a replaying or middleman process, association between values produced by +// a middleman call and the call itself. +typedef std::unordered_map MiddlemanCallMap; +static MiddlemanCallMap* gMiddlemanCallMap; + +// In a middleman process, any buffers allocated for performed calls. +static StaticInfallibleVector gAllocatedBuffers; + +// Lock protecting middleman call state. +static Monitor* gMonitor; + +void +InitializeMiddlemanCalls() +{ + MOZ_RELEASE_ASSERT(IsRecordingOrReplaying() || IsMiddleman()); + + gMiddlemanCallMap = new MiddlemanCallMap(); + gMonitor = new Monitor(); +} + +// Apply the ReplayInput phase to aCall and any calls it depends on that have +// not been sent to the middleman yet, filling aOutgoingCalls with the set of +// such calls. +static bool +GatherDependentCalls(InfallibleVector& aOutgoingCalls, MiddlemanCall* aCall) +{ + MOZ_RELEASE_ASSERT(!aCall->mSent); + aCall->mSent = true; + + CallArguments arguments; + aCall->mArguments.CopyTo(&arguments); + + InfallibleVector dependentCalls; + + MiddlemanCallContext cx(aCall, &arguments, MiddlemanCallPhase::ReplayInput); + cx.mDependentCalls = &dependentCalls; + gRedirections[aCall->mCallId].mMiddlemanCall(cx); + if (cx.mFailed) { + return false; + } + + for (MiddlemanCall* dependent : dependentCalls) { + if (!dependent->mSent && !GatherDependentCalls(aOutgoingCalls, dependent)) { + return false; + } + } + + aOutgoingCalls.append(aCall); + return true; +} + +bool +SendCallToMiddleman(size_t aCallId, CallArguments* aArguments, bool aDiverged) +{ + MOZ_RELEASE_ASSERT(IsReplaying()); + + const Redirection& redirection = gRedirections[aCallId]; + MOZ_RELEASE_ASSERT(redirection.mMiddlemanCall); + + Maybe lock; + lock.emplace(*gMonitor); + + // Allocate and fill in a new MiddlemanCall. + size_t id = gMiddlemanCalls.length(); + MiddlemanCall* newCall = new MiddlemanCall(); + gMiddlemanCalls.emplaceBack(newCall); + newCall->mId = id; + newCall->mCallId = aCallId; + newCall->mArguments.CopyFrom(aArguments); + + // Perform the ReplayPreface phase on the new call. + { + MiddlemanCallContext cx(newCall, aArguments, MiddlemanCallPhase::ReplayPreface); + redirection.mMiddlemanCall(cx); + if (cx.mFailed) { + delete newCall; + gMiddlemanCalls.popBack(); + return false; + } + } + + // Other phases will not run if we have not diverged from the recording. + // Any outputs for the call have been handled by the SaveOutput hook. + if (!aDiverged) { + return true; + } + + // Perform the ReplayInput phase on the new call and any others it depends on. + InfallibleVector outgoingCalls; + if (!GatherDependentCalls(outgoingCalls, newCall)) { + for (MiddlemanCall* call : outgoingCalls) { + call->mSent = false; + } + return false; + } + + // Encode all calls we are sending to the middleman. + InfallibleVector inputData; + BufferStream inputStream(&inputData); + for (MiddlemanCall* call : outgoingCalls) { + call->EncodeInput(inputStream); + } + + // Perform the calls synchronously in the middleman. + InfallibleVector outputData; + if (!child::SendMiddlemanCallRequest(inputData.begin(), inputData.length(), &outputData)) { + // This thread is not allowed to perform middleman calls anymore. Release + // the lock and block until the process rewinds. + lock.reset(); + Thread::WaitForever(); + } + + // Decode outputs for the calls just sent, and perform the ReplayOutput phase + // on any older dependent calls we sent. + BufferStream outputStream(outputData.begin(), outputData.length()); + for (MiddlemanCall* call : outgoingCalls) { + call->DecodeOutput(outputStream); + + if (call != newCall) { + CallArguments oldArguments; + call->mArguments.CopyTo(&oldArguments); + MiddlemanCallContext cx(call, &oldArguments, MiddlemanCallPhase::ReplayOutput); + cx.mReplayOutputIsOld = true; + gRedirections[call->mCallId].mMiddlemanCall(cx); + } + } + + // Perform the ReplayOutput phase to fill in outputs for the current call. + newCall->mArguments.CopyTo(aArguments); + MiddlemanCallContext cx(newCall, aArguments, MiddlemanCallPhase::ReplayOutput); + redirection.mMiddlemanCall(cx); + return true; +} + +void +ProcessMiddlemanCall(const char* aInputData, size_t aInputSize, + InfallibleVector* aOutputData) +{ + MOZ_RELEASE_ASSERT(IsMiddleman()); + + BufferStream inputStream(aInputData, aInputSize); + BufferStream outputStream(aOutputData); + + while (!inputStream.IsEmpty()) { + MiddlemanCall* call = new MiddlemanCall(); + call->DecodeInput(inputStream); + + const Redirection& redirection = gRedirections[call->mCallId]; + MOZ_RELEASE_ASSERT(gRedirections[call->mCallId].mMiddlemanCall); + + CallArguments arguments; + call->mArguments.CopyTo(&arguments); + + { + MiddlemanCallContext cx(call, &arguments, MiddlemanCallPhase::MiddlemanInput); + redirection.mMiddlemanCall(cx); + } + + RecordReplayInvokeCall(call->mCallId, &arguments); + + { + MiddlemanCallContext cx(call, &arguments, MiddlemanCallPhase::MiddlemanOutput); + redirection.mMiddlemanCall(cx); + } + + call->mArguments.CopyFrom(&arguments); + call->EncodeOutput(outputStream); + + while (call->mId >= gMiddlemanCalls.length()) { + gMiddlemanCalls.emplaceBack(nullptr); + } + MOZ_RELEASE_ASSERT(!gMiddlemanCalls[call->mId]); + gMiddlemanCalls[call->mId] = call; + } +} + +void* +MiddlemanCallContext::AllocateBytes(size_t aSize) +{ + void* rv = malloc(aSize); + + // In a middleman process, any buffers we allocate live until the calls are + // reset. In a replaying process, the buffers will either live forever + // (if they are allocated in the ReplayPreface phase, to match the lifetime + // of the MiddlemanCall itself) or will be recovered when we rewind after we + // are done with our divergence from the recording (any other phase). + if (IsMiddleman()) { + gAllocatedBuffers.append(rv); + } + + return rv; +} + +void +ResetMiddlemanCalls() +{ + MOZ_RELEASE_ASSERT(IsMiddleman()); + + for (MiddlemanCall* call : gMiddlemanCalls) { + if (call) { + CallArguments arguments; + call->mArguments.CopyTo(&arguments); + + MiddlemanCallContext cx(call, &arguments, MiddlemanCallPhase::MiddlemanRelease); + gRedirections[call->mCallId].mMiddlemanCall(cx); + + delete call; + } + } + + gMiddlemanCalls.clear(); + for (auto buffer : gAllocatedBuffers) { + free(buffer); + } + gAllocatedBuffers.clear(); +} + +/////////////////////////////////////////////////////////////////////////////// +// System Values +/////////////////////////////////////////////////////////////////////////////// + +static void +AddMiddlemanCallValue(const void* aThing, MiddlemanCall* aCall) +{ + gMiddlemanCallMap->erase(aThing); + gMiddlemanCallMap->insert(MiddlemanCallMap::value_type(aThing, aCall)); +} + +static MiddlemanCall* +LookupMiddlemanCall(const void* aThing) +{ + MiddlemanCallMap::const_iterator iter = gMiddlemanCallMap->find(aThing); + if (iter != gMiddlemanCallMap->end()) { + return iter->second; + } + return nullptr; +} + +static const void* +GetMiddlemanCallValue(size_t aId) +{ + MOZ_RELEASE_ASSERT(IsMiddleman()); + MOZ_RELEASE_ASSERT(aId < gMiddlemanCalls.length() && + gMiddlemanCalls[aId] && + gMiddlemanCalls[aId]->mMiddlemanValue.isSome()); + return gMiddlemanCalls[aId]->mMiddlemanValue.ref(); +} + +bool +Middleman_SystemInput(MiddlemanCallContext& aCx, const void** aThingPtr) +{ + MOZ_RELEASE_ASSERT(aCx.AccessPreface()); + + if (!*aThingPtr) { + // Null values are handled by the normal argument copying logic. + return true; + } + + Maybe callId; + if (aCx.mPhase == MiddlemanCallPhase::ReplayPreface) { + // Determine any middleman call this object came from, before the pointer + // has a chance to be clobbered by another call between this and the + // ReplayInput phase. + MiddlemanCall* call = LookupMiddlemanCall(*aThingPtr); + if (call) { + callId.emplace(call->mId); + } + } + aCx.ReadOrWritePrefaceBytes(&callId, sizeof(callId)); + + switch (aCx.mPhase) { + case MiddlemanCallPhase::ReplayPreface: + return true; + case MiddlemanCallPhase::ReplayInput: + if (callId.isSome()) { + aCx.WriteInputScalar(callId.ref()); + aCx.mDependentCalls->append(gMiddlemanCalls[callId.ref()]); + return true; + } + return false; + case MiddlemanCallPhase::MiddlemanInput: + if (callId.isSome()) { + size_t callIndex = aCx.ReadInputScalar(); + *aThingPtr = GetMiddlemanCallValue(callIndex); + return true; + } + return false; + default: + MOZ_CRASH("Bad phase"); + } +} + +// Pointer system values are preserved during the replay so that null tests +// and equality tests work as expected. We additionally mangle the +// pointers here by setting one of the two highest bits, depending on whether +// the pointer came from the recording or from the middleman. This avoids +// accidentally conflating pointers that happen to have the same value but +// which originate from different processes. +static const void* +MangleSystemValue(const void* aValue, bool aFromRecording) +{ + return (const void*) ((size_t)aValue | (1ULL << (aFromRecording ? 63 : 62))); +} + +void +Middleman_SystemOutput(MiddlemanCallContext& aCx, const void** aOutput, bool aUpdating) +{ + if (!*aOutput) { + return; + } + + switch (aCx.mPhase) { + case MiddlemanCallPhase::ReplayPreface: + if (!HasDivergedFromRecording()) { + // If we haven't diverged from the recording, use the output value saved + // in the recording. + if (!aUpdating) { + *aOutput = MangleSystemValue(*aOutput, true); + } + aCx.mCall->SetRecordingValue(*aOutput); + AddMiddlemanCallValue(*aOutput, aCx.mCall); + } + break; + case MiddlemanCallPhase::MiddlemanOutput: + aCx.mCall->SetMiddlemanValue(*aOutput); + AddMiddlemanCallValue(*aOutput, aCx.mCall); + break; + case MiddlemanCallPhase::ReplayOutput: { + if (!aUpdating) { + *aOutput = MangleSystemValue(*aOutput, false); + } + aCx.mCall->SetMiddlemanValue(*aOutput); + + // Associate the value produced by the middleman with this call. If the + // call previously went through the ReplayPreface phase when we did not + // diverge from the recording, we will associate values from both the + // recording and middleman processes with this call. If a call made after + // diverging produced the same value as a call made before diverging, use + // the value saved in the recording for the first call, so that equality + // tests on the value work as expected. + MiddlemanCall* previousCall = LookupMiddlemanCall(*aOutput); + if (previousCall) { + if (previousCall->mRecordingValue.isSome()) { + *aOutput = previousCall->mRecordingValue.ref(); + } + } else { + AddMiddlemanCallValue(*aOutput, aCx.mCall); + } + break; + } + default: + return; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// MiddlemanCall +/////////////////////////////////////////////////////////////////////////////// + +void +MiddlemanCall::EncodeInput(BufferStream& aStream) const +{ + aStream.WriteScalar(mId); + aStream.WriteScalar(mCallId); + aStream.WriteBytes(&mArguments, sizeof(CallRegisterArguments)); + aStream.WriteScalar(mPreface.length()); + aStream.WriteBytes(mPreface.begin(), mPreface.length()); + aStream.WriteScalar(mInput.length()); + aStream.WriteBytes(mInput.begin(), mInput.length()); +} + +void +MiddlemanCall::DecodeInput(BufferStream& aStream) +{ + mId = aStream.ReadScalar(); + mCallId = aStream.ReadScalar(); + aStream.ReadBytes(&mArguments, sizeof(CallRegisterArguments)); + size_t prefaceLength = aStream.ReadScalar(); + mPreface.appendN(0, prefaceLength); + aStream.ReadBytes(mPreface.begin(), prefaceLength); + size_t inputLength = aStream.ReadScalar(); + mInput.appendN(0, inputLength); + aStream.ReadBytes(mInput.begin(), inputLength); +} + +void +MiddlemanCall::EncodeOutput(BufferStream& aStream) const +{ + aStream.WriteBytes(&mArguments, sizeof(CallRegisterArguments)); + aStream.WriteScalar(mOutput.length()); + aStream.WriteBytes(mOutput.begin(), mOutput.length()); +} + +void +MiddlemanCall::DecodeOutput(BufferStream& aStream) +{ + // Only update the return value when decoding arguments, so that we don't + // clobber the call's arguments with any changes made in the middleman. + CallRegisterArguments newArguments; + aStream.ReadBytes(&newArguments, sizeof(CallRegisterArguments)); + mArguments.CopyRvalFrom(&newArguments); + + size_t outputLength = aStream.ReadScalar(); + mOutput.appendN(0, outputLength); + aStream.ReadBytes(mOutput.begin(), outputLength); +} + +} // namespace recordreplay +} // namespace mozilla diff --git a/toolkit/recordreplay/MiddlemanCall.h b/toolkit/recordreplay/MiddlemanCall.h new file mode 100644 index 000000000000..b7236081b46c --- /dev/null +++ b/toolkit/recordreplay/MiddlemanCall.h @@ -0,0 +1,464 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 mozilla_recordreplay_MiddlemanCall_h +#define mozilla_recordreplay_MiddlemanCall_h + +#include "BufferStream.h" +#include "ProcessRedirect.h" +#include "mozilla/Maybe.h" + +namespace mozilla { +namespace recordreplay { + +// Middleman Calls Overview +// +// With few exceptions, replaying processes do not interact with the underlying +// system or call the actual versions of redirected system library functions. +// This is problematic after diverging from the recording, as then the diverged +// thread cannot interact with its recording either. +// +// Middleman calls are used in a replaying process after diverging from the +// recording to perform calls in the middleman process instead. Inputs are +// gathered and serialized in the replaying process, then sent to the middleman +// process. The middleman calls the function, and its outputs are serialized +// for reading by the replaying process. +// +// Calls that might need to be sent to the middleman are processed in phases, +// per the MiddlemanCallPhase enum below. The timeline of a middleman call is +// as follows: +// +// - Any redirection with a middleman call hook can potentially be sent to the +// middleman. In a replaying process, whenever such a call is encountered, +// the hook is invoked in the ReplayPreface phase to capture any input data +// that must be examined at the time of the call itself. +// +// - If the thread has not diverged from the recording, the call is remembered +// but no further action is necessary yet. +// +// - If the thread has diverged from the recording, the call needs to go +// through the remaining phases. The ReplayInput phase captures any +// additional inputs to the call, potentially including values produced by +// other middleman calls. +// +// - The transitive closure of these call dependencies is produced, and all +// calls found go through the ReplayInput phase. The resulting data is sent +// to the middleman process, which goes through the MiddlemanInput phase +// to decode those inputs. +// +// - The middleman performs each of the calls it has been given, and their +// outputs are encoded in the MiddlemanOutput phase. These outputs are sent +// to the replaying process in a response and decoded in the ReplayOutput +// phase, which can then resume execution. +// +// - The replaying process holds onto information about calls it has sent until +// it rewinds to a point before it diverged from the recording. This rewind +// will --- without any special action required --- wipe out information on +// all calls sent to the middleman, and retain any data gathered in the +// ReplayPreface phase for calls that were made prior to the rewind target. +// +// - Information about calls and all resources held are retained in the +// middleman process are retained until a replaying process asks for them to +// be reset, which happens any time the replaying process first diverges from +// the recording. The MiddlemanRelease phase is used to release any system +// resources held. + +// Ways of processing calls that can be sent to the middleman. +enum class MiddlemanCallPhase +{ + // When replaying, a call is being performed that might need to be sent to + // the middleman later. + ReplayPreface, + + // A call for which inputs have been gathered is now being sent to the + // middleman. This is separate from ReplayPreface because capturing inputs + // might need to dereference pointers that could be bogus values originating + // from the recording. Waiting to dereference these pointers until we know + // the call needs to be sent to the middleman avoids needing to understand + // the inputs to all call sites of general purpose redirections such as + // CFArrayCreate. + ReplayInput, + + // In the middleman process, a call from the replaying process is being + // performed. + MiddlemanInput, + + // In the middleman process, a call from the replaying process was just + // performed, and its outputs need to be saved. + MiddlemanOutput, + + // Back in the replaying process, the outputs from a call have been received + // from the middleman. + ReplayOutput, + + // In the middleman process, release any system resources held after this + // call. + MiddlemanRelease, +}; + +struct MiddlemanCall +{ + // Unique ID for this call. + size_t mId; + + // ID of the redirection being invoked. + size_t mCallId; + + // All register arguments and return values are preserved when sending the + // call back and forth between processes. + CallRegisterArguments mArguments; + + // Written in ReplayPrefaceInput, read in ReplayInput and MiddlemanInput. + InfallibleVector mPreface; + + // Written in ReplayInput, read in MiddlemanInput. + InfallibleVector mInput; + + // Written in MiddlemanOutput, read in ReplayOutput. + InfallibleVector mOutput; + + // In a replaying process, whether this call has been sent to the middleman. + bool mSent; + + // In a replaying process, any value associated with this call that was + // included in the recording, when the call was made before diverging from + // the recording. + Maybe mRecordingValue; + + // In a replaying or middleman process, any value associated with this call + // that was produced by the middleman itself. + Maybe mMiddlemanValue; + + MiddlemanCall() + : mId(0), mCallId(0), mSent(false) + {} + + void EncodeInput(BufferStream& aStream) const; + void DecodeInput(BufferStream& aStream); + + void EncodeOutput(BufferStream& aStream) const; + void DecodeOutput(BufferStream& aStream); + + void SetRecordingValue(const void* aValue) { + MOZ_RELEASE_ASSERT(mRecordingValue.isNothing()); + mRecordingValue.emplace(aValue); + } + + void SetMiddlemanValue(const void* aValue) { + MOZ_RELEASE_ASSERT(mMiddlemanValue.isNothing()); + mMiddlemanValue.emplace(aValue); + } +}; + +// Information needed to process one of the phases of a middleman call, +// in either the replaying or middleman process. +struct MiddlemanCallContext +{ + // Call being operated on. + MiddlemanCall* mCall; + + // Complete arguments and return value information for the call. + CallArguments* mArguments; + + // Current processing phase. + MiddlemanCallPhase mPhase; + + // During the ReplayPreface or ReplayInput phases, whether capturing input + // data has failed. In such cases the call cannot be sent to the middleman + // and, if the thread has diverged from the recording, an unhandled + // divergence and associated rewind will occur. + bool mFailed; + + // During the ReplayInput phase, this can be used to fill in any middleman + // calls whose output the current one depends on. + InfallibleVector* mDependentCalls; + + // Streams of data that can be accessed during the various phases. Streams + // need to be read or written from at the same points in the phases which use + // them, so that callbacks operating on these streams can be composed without + // issues. + + // The preface is written during ReplayPreface, and read during both + // ReplayInput and MiddlemanInput. + Maybe mPrefaceStream; + + // Inputs are written during ReplayInput, and read during MiddlemanInput. + Maybe mInputStream; + + // Outputs are written during MiddlemanOutput, and read during ReplayOutput. + Maybe mOutputStream; + + // During the ReplayOutput phase, this is set if the call was made sometime + // in the past and pointers referred to in the arguments may no longer be + // valid. + bool mReplayOutputIsOld; + + MiddlemanCallContext(MiddlemanCall* aCall, CallArguments* aArguments, MiddlemanCallPhase aPhase) + : mCall(aCall), mArguments(aArguments), mPhase(aPhase), + mFailed(false), mDependentCalls(nullptr), mReplayOutputIsOld(false) + { + switch (mPhase) { + case MiddlemanCallPhase::ReplayPreface: + mPrefaceStream.emplace(&mCall->mPreface); + break; + case MiddlemanCallPhase::ReplayInput: + mPrefaceStream.emplace(mCall->mPreface.begin(), mCall->mPreface.length()); + mInputStream.emplace(&mCall->mInput); + break; + case MiddlemanCallPhase::MiddlemanInput: + mPrefaceStream.emplace(mCall->mPreface.begin(), mCall->mPreface.length()); + mInputStream.emplace(mCall->mInput.begin(), mCall->mInput.length()); + break; + case MiddlemanCallPhase::MiddlemanOutput: + mOutputStream.emplace(&mCall->mOutput); + break; + case MiddlemanCallPhase::ReplayOutput: + mOutputStream.emplace(mCall->mOutput.begin(), mCall->mOutput.length()); + break; + case MiddlemanCallPhase::MiddlemanRelease: + break; + } + } + + void MarkAsFailed() { + MOZ_RELEASE_ASSERT(mPhase == MiddlemanCallPhase::ReplayPreface || + mPhase == MiddlemanCallPhase::ReplayInput); + mFailed = true; + } + + void WriteInputBytes(const void* aBuffer, size_t aSize) { + MOZ_RELEASE_ASSERT(mPhase == MiddlemanCallPhase::ReplayInput); + mInputStream.ref().WriteBytes(aBuffer, aSize); + } + + void WriteInputScalar(size_t aValue) { + MOZ_RELEASE_ASSERT(mPhase == MiddlemanCallPhase::ReplayInput); + mInputStream.ref().WriteScalar(aValue); + } + + void ReadInputBytes(void* aBuffer, size_t aSize) { + MOZ_RELEASE_ASSERT(mPhase == MiddlemanCallPhase::MiddlemanInput); + mInputStream.ref().ReadBytes(aBuffer, aSize); + } + + size_t ReadInputScalar() { + MOZ_RELEASE_ASSERT(mPhase == MiddlemanCallPhase::MiddlemanInput); + return mInputStream.ref().ReadScalar(); + } + + bool AccessInput() { + return mInputStream.isSome(); + } + + void ReadOrWriteInputBytes(void* aBuffer, size_t aSize) { + switch (mPhase) { + case MiddlemanCallPhase::ReplayInput: + WriteInputBytes(aBuffer, aSize); + break; + case MiddlemanCallPhase::MiddlemanInput: + ReadInputBytes(aBuffer, aSize); + break; + default: + MOZ_CRASH(); + } + } + + bool AccessPreface() { + return mPrefaceStream.isSome(); + } + + void ReadOrWritePrefaceBytes(void* aBuffer, size_t aSize) { + switch (mPhase) { + case MiddlemanCallPhase::ReplayPreface: + mPrefaceStream.ref().WriteBytes(aBuffer, aSize); + break; + case MiddlemanCallPhase::ReplayInput: + case MiddlemanCallPhase::MiddlemanInput: + mPrefaceStream.ref().ReadBytes(aBuffer, aSize); + break; + default: + MOZ_CRASH(); + } + } + + void ReadOrWritePrefaceBuffer(void** aBufferPtr, size_t aSize) { + switch (mPhase) { + case MiddlemanCallPhase::ReplayPreface: + mPrefaceStream.ref().WriteBytes(*aBufferPtr, aSize); + break; + case MiddlemanCallPhase::ReplayInput: + case MiddlemanCallPhase::MiddlemanInput: + *aBufferPtr = AllocateBytes(aSize); + mPrefaceStream.ref().ReadBytes(*aBufferPtr, aSize); + break; + default: + MOZ_CRASH(); + } + } + + bool AccessOutput() { + return mOutputStream.isSome(); + } + + void ReadOrWriteOutputBytes(void* aBuffer, size_t aSize) { + switch (mPhase) { + case MiddlemanCallPhase::MiddlemanOutput: + mOutputStream.ref().WriteBytes(aBuffer, aSize); + break; + case MiddlemanCallPhase::ReplayOutput: + mOutputStream.ref().ReadBytes(aBuffer, aSize); + break; + default: + MOZ_CRASH(); + } + } + + void ReadOrWriteOutputBuffer(void** aBuffer, size_t aSize) { + if (*aBuffer) { + if (mPhase == MiddlemanCallPhase::MiddlemanInput || mReplayOutputIsOld) { + *aBuffer = AllocateBytes(aSize); + } + + if (AccessOutput()) { + ReadOrWriteOutputBytes(*aBuffer, aSize); + } + } + } + + // Allocate some memory associated with the call, which will be released in + // the replaying process on a rewind and in the middleman process when the + // call state is reset. + void* AllocateBytes(size_t aSize); +}; + +// Notify the system about a call to a redirection with a middleman call hook. +// aDiverged is set if the current thread has diverged from the recording and +// any outputs for the call must be filled in; otherwise, they already have +// been filled in using data from the recording. Returns false if the call was +// unable to be processed. +bool SendCallToMiddleman(size_t aCallId, CallArguments* aArguments, bool aDiverged); + +// In the middleman process, perform one or more calls encoded in aInputData +// and encode their outputs to aOutputData. +void ProcessMiddlemanCall(const char* aInputData, size_t aInputSize, + InfallibleVector* aOutputData); + +// In the middleman process, reset all call state. +void ResetMiddlemanCalls(); + +/////////////////////////////////////////////////////////////////////////////// +// Middleman Call Helpers +/////////////////////////////////////////////////////////////////////////////// + +// Capture the contents of an input buffer at BufferArg with element count at CountArg. +template +static inline void +Middleman_Buffer(MiddlemanCallContext& aCx) +{ + if (aCx.AccessPreface()) { + auto& buffer = aCx.mArguments->Arg(); + auto byteSize = aCx.mArguments->Arg() * sizeof(ElemType); + aCx.ReadOrWritePrefaceBuffer(&buffer, byteSize); + } +} + +// Capture the contents of a fixed size input buffer. +template +static inline void +Middleman_BufferFixedSize(MiddlemanCallContext& aCx) +{ + if (aCx.AccessPreface()) { + auto& buffer = aCx.mArguments->Arg(); + if (buffer) { + aCx.ReadOrWritePrefaceBuffer(&buffer, ByteSize); + } + } +} + +// Capture a C string argument. +template +static inline void +Middleman_CString(MiddlemanCallContext& aCx) +{ + if (aCx.AccessPreface()) { + auto& buffer = aCx.mArguments->Arg(); + size_t len = (aCx.mPhase == MiddlemanCallPhase::ReplayPreface) ? strlen(buffer) + 1 : 0; + aCx.ReadOrWritePrefaceBytes(&len, sizeof(len)); + aCx.ReadOrWritePrefaceBuffer((void**) &buffer, len); + } +} + +// Capture the data written to an output buffer at BufferArg with element count at CountArg. +template +static inline void +Middleman_WriteBuffer(MiddlemanCallContext& aCx) +{ + auto& buffer = aCx.mArguments->Arg(); + auto count = aCx.mArguments->Arg(); + aCx.ReadOrWriteOutputBuffer(&buffer, count * sizeof(ElemType)); +} + +// Capture the data written to a fixed size output buffer. +template +static inline void +Middleman_WriteBufferFixedSize(MiddlemanCallContext& aCx) +{ + auto& buffer = aCx.mArguments->Arg(); + aCx.ReadOrWriteOutputBuffer(&buffer, ByteSize); +} + +// Capture return values that are too large for register storage. +template +static inline void +Middleman_OversizeRval(MiddlemanCallContext& aCx) +{ + Middleman_WriteBufferFixedSize<0, ByteSize>(aCx); +} + +// Capture a byte count of stack argument data. +template +static inline void +Middleman_StackArgumentData(MiddlemanCallContext& aCx) +{ + if (aCx.AccessPreface()) { + auto stack = aCx.mArguments->StackAddress<0>(); + aCx.ReadOrWritePrefaceBytes(stack, ByteSize); + } +} + +static inline void +Middleman_NoOp(MiddlemanCallContext& aCx) +{ +} + +template +static inline void +Middleman_Compose(MiddlemanCallContext& aCx) +{ + Fn0(aCx); + Fn1(aCx); + Fn2(aCx); + Fn3(aCx); + Fn4(aCx); +} + +// Helper for capturing inputs that are produced by other middleman calls. +// Returns false in the ReplayInput or MiddlemanInput phases if the input +// system value could not be found. +bool Middleman_SystemInput(MiddlemanCallContext& aCx, const void** aThingPtr); + +// Helper for capturing output system values that might be consumed by other +// middleman calls. +void Middleman_SystemOutput(MiddlemanCallContext& aCx, const void** aOutput, bool aUpdating = false); + +} // namespace recordreplay +} // namespace mozilla + +#endif // mozilla_recordreplay_MiddlemanCall_h diff --git a/toolkit/recordreplay/ProcessRecordReplay.cpp b/toolkit/recordreplay/ProcessRecordReplay.cpp index 2e26dfee60cb..c5ad8dbe35e2 100644 --- a/toolkit/recordreplay/ProcessRecordReplay.cpp +++ b/toolkit/recordreplay/ProcessRecordReplay.cpp @@ -51,6 +51,9 @@ char* gRecordingFilename; // Current process ID. static int gPid; +// ID of the process which produced the recording. +static int gRecordingPid; + // Whether to spew record/replay messages to stderr. static bool gSpewEnabled; @@ -111,6 +114,7 @@ RecordReplayInterface_Initialize(int aArgc, char* aArgv[]) EarlyInitializeRedirections(); if (!IsRecordingOrReplaying()) { + InitializeMiddlemanCalls(); return; } @@ -144,6 +148,7 @@ RecordReplayInterface_Initialize(int aArgc, char* aArgv[]) Thread::SpawnAllThreads(); InitializeCountdownThread(); SetupDirtyMemoryHandler(); + InitializeMiddlemanCalls(); // Don't create a stylo thread pool when recording or replaying. putenv((char*) "STYLO_THREADS=1"); @@ -152,6 +157,7 @@ RecordReplayInterface_Initialize(int aArgc, char* aArgv[]) Lock::InitializeLocks(); InitializeRewindState(); + gRecordingPid = RecordReplayValue(gPid); gInitialized = true; } @@ -160,12 +166,11 @@ MOZ_EXPORT size_t RecordReplayInterface_InternalRecordReplayValue(size_t aValue) { Thread* thread = Thread::Current(); - if (thread->PassThroughEvents()) { + RecordingEventSection res(thread); + if (!res.CanAccessEvents()) { return aValue; } - EnsureNotDivergedFromRecording(); - MOZ_RELEASE_ASSERT(thread->CanAccessRecording()); thread->Events().RecordOrReplayThreadEvent(ThreadEvent::Value); thread->Events().RecordOrReplayValue(&aValue); return aValue; @@ -175,12 +180,11 @@ MOZ_EXPORT void RecordReplayInterface_InternalRecordReplayBytes(void* aData, size_t aSize) { Thread* thread = Thread::Current(); - if (thread->PassThroughEvents()) { + RecordingEventSection res(thread); + if (!res.CanAccessEvents()) { return; } - EnsureNotDivergedFromRecording(); - MOZ_RELEASE_ASSERT(thread->CanAccessRecording()); thread->Events().RecordOrReplayThreadEvent(ThreadEvent::Bytes); thread->Events().CheckInput(aSize); thread->Events().RecordOrReplayBytes(aData, aSize); @@ -325,6 +329,12 @@ ThreadEventName(ThreadEvent aEvent) return gRedirections[callId].mName; } +int +GetRecordingPid() +{ + return gRecordingPid; +} + /////////////////////////////////////////////////////////////////////////////// // Record/Replay Assertions /////////////////////////////////////////////////////////////////////////////// @@ -335,10 +345,10 @@ MOZ_EXPORT void RecordReplayInterface_InternalRecordReplayAssert(const char* aFormat, va_list aArgs) { Thread* thread = Thread::Current(); - if (thread->PassThroughEvents() || thread->HasDivergedFromRecording()) { + RecordingEventSection res(thread); + if (!res.CanAccessEvents()) { return; } - MOZ_RELEASE_ASSERT(thread->CanAccessRecording()); // Add the asserted string to the recording. char text[1024]; @@ -352,10 +362,10 @@ MOZ_EXPORT void RecordReplayInterface_InternalRecordReplayAssertBytes(const void* aData, size_t aSize) { Thread* thread = Thread::Current(); - if (thread->PassThroughEvents() || thread->HasDivergedFromRecording()) { + RecordingEventSection res(thread); + if (!res.CanAccessEvents()) { return; } - MOZ_RELEASE_ASSERT(thread->CanAccessRecording()); thread->Events().RecordOrReplayThreadEvent(ThreadEvent::AssertBytes); thread->Events().CheckInput(aData, aSize); diff --git a/toolkit/recordreplay/ProcessRecordReplay.h b/toolkit/recordreplay/ProcessRecordReplay.h index 8e3a63ef7e97..3902d9ddd8bd 100644 --- a/toolkit/recordreplay/ProcessRecordReplay.h +++ b/toolkit/recordreplay/ProcessRecordReplay.h @@ -232,6 +232,9 @@ MOZ_MakeRecordReplayPrinter(PrintSpew, true) #undef MOZ_MakeRecordReplayPrinter +// Get the ID of the process that produced the recording. +int GetRecordingPid(); + /////////////////////////////////////////////////////////////////////////////// // Profiling /////////////////////////////////////////////////////////////////////////////// diff --git a/toolkit/recordreplay/ProcessRedirect.cpp b/toolkit/recordreplay/ProcessRedirect.cpp index 9aa859099b67..8a1669d7640e 100644 --- a/toolkit/recordreplay/ProcessRedirect.cpp +++ b/toolkit/recordreplay/ProcessRedirect.cpp @@ -7,6 +7,7 @@ #include "ProcessRedirect.h" #include "InfallibleVector.h" +#include "MiddlemanCall.h" #include "mozilla/Sprintf.h" #include @@ -27,6 +28,24 @@ namespace recordreplay { // Redirection Skeleton /////////////////////////////////////////////////////////////////////////////// +static bool +CallPreambleHook(PreambleFn aPreamble, size_t aCallId, CallArguments* aArguments) +{ + PreambleResult result = aPreamble(aArguments); + switch (result) { + case PreambleResult::Veto: + return true; + case PreambleResult::PassThrough: { + AutoEnsurePassThroughThreadEvents pt; + RecordReplayInvokeCall(aCallId, aArguments); + return true; + } + case PreambleResult::Redirect: + return false; + } + Unreachable(); +} + extern "C" { __attribute__((used)) int @@ -37,42 +56,64 @@ RecordReplayInterceptCall(int aCallId, CallArguments* aArguments) // Call the preamble to see if this call needs special handling, even when // events have been passed through. if (redirection.mPreamble) { - PreambleResult result = redirection.mPreamble(aArguments); - switch (result) { - case PreambleResult::Veto: + if (CallPreambleHook(redirection.mPreamble, aCallId, aArguments)) { return 0; - case PreambleResult::PassThrough: { - AutoEnsurePassThroughThreadEvents pt; - RecordReplayInvokeCall(aCallId, aArguments); - return 0; - } - case PreambleResult::Redirect: - break; } } Thread* thread = Thread::Current(); + Maybe res; + res.emplace(thread); - // When events are passed through, invoke the call with the original stack - // and register state. - if (!thread || thread->PassThroughEvents()) { - // RecordReplayRedirectCall will load the function to call from the - // return value slot. - aArguments->Rval() = redirection.mOriginalFunction; - return 1; + if (!res.ref().CanAccessEvents()) { + // When events are passed through, invoke the call with the original stack + // and register state. + if (!thread || thread->PassThroughEvents()) { + // RecordReplayRedirectCall will load the function to call from the + // return value slot. + aArguments->Rval() = redirection.mOriginalFunction; + return 1; + } + + MOZ_RELEASE_ASSERT(thread->HasDivergedFromRecording()); + + // After we have diverged from the recording, we can't access the thread's + // recording anymore. + + // If the redirection has a middleman preamble hook, call it to see if it + // can handle this call. The middleman preamble hook is separate from the + // normal preamble hook because entering the RecordingEventSection can + // cause the current thread to diverge from the recording; testing for + // HasDivergedFromRecording() does not work reliably in the normal preamble. + if (redirection.mMiddlemanPreamble) { + if (CallPreambleHook(redirection.mMiddlemanPreamble, aCallId, aArguments)) { + return 0; + } + } + + // If the redirection has a middleman call hook, try to perform the call in + // the middleman instead. + if (redirection.mMiddlemanCall) { + if (SendCallToMiddleman(aCallId, aArguments, /* aPopulateOutput = */ true)) { + return 0; + } + } + + // Calling any redirection which performs the standard steps will cause + // debugger operations that have diverged from the recording to fail. + EnsureNotDivergedFromRecording(); + Unreachable(); } - // Calling any redirection which performs the standard steps will cause - // debugger operations that have diverged from the recording to fail. - EnsureNotDivergedFromRecording(); - - MOZ_RELEASE_ASSERT(thread->CanAccessRecording()); - if (IsRecording()) { // Call the original function, passing through events while we do so. + // Destroy the RecordingEventSection so that we don't prevent the file + // from being flushed in case we end up blocking. + res.reset(); thread->SetPassThrough(true); RecordReplayInvokeCall(aCallId, aArguments); thread->SetPassThrough(false); + res.emplace(thread); } // Save any system error in case we want to record/replay it. @@ -86,6 +127,13 @@ RecordReplayInterceptCall(int aCallId, CallArguments* aArguments) redirection.mSaveOutput(thread->Events(), aArguments, &error); } + // Save information about any potential middleman calls encountered if we + // haven't diverged from the recording, in case we diverge and later calls + // access data produced by this one. + if (IsReplaying() && redirection.mMiddlemanCall) { + (void) SendCallToMiddleman(aCallId, aArguments, /* aDiverged = */ false); + } + RestoreError(error); return 0; } diff --git a/toolkit/recordreplay/ProcessRedirect.h b/toolkit/recordreplay/ProcessRedirect.h index e44708104af4..cd55712d2b95 100644 --- a/toolkit/recordreplay/ProcessRedirect.h +++ b/toolkit/recordreplay/ProcessRedirect.h @@ -13,7 +13,6 @@ #include "ProcessRecordReplay.h" #include "ProcessRewind.h" #include "Thread.h" -#include "ipc/Channel.h" #include "mozilla/Assertions.h" #include "mozilla/Atomics.h" @@ -74,18 +73,11 @@ namespace recordreplay { // Function Redirections /////////////////////////////////////////////////////////////////////////////// -// Capture the arguments that can be passed to a redirection, and provide -// storage to specify the redirection's return value. We only need to capture -// enough argument data here for calls made directly from Gecko code, -// i.e. where events are not passed through. Calls made while events are passed -// through are performed with the same stack and register state as when they -// were initially invoked. -// -// Arguments and return value indexes refer to the register contents as passed -// to the function originally. For functions with complex or floating point -// arguments and return values, the right index to use might be different than -// expected, per the requirements of the System V x64 ABI. -struct CallArguments +struct CallArguments; + +// All argument and return value data that is stored in registers and whose +// values are preserved when calling a redirected function. +struct CallRegisterArguments { protected: size_t arg0; // 0 @@ -101,6 +93,28 @@ protected: size_t rval1; // 80 double floatrval0; // 88 double floatrval1; // 96 + // Size: 104 + +public: + void CopyFrom(const CallRegisterArguments* aArguments); + void CopyTo(CallRegisterArguments* aArguments) const; + void CopyRvalFrom(const CallRegisterArguments* aArguments); +}; + +// Capture the arguments that can be passed to a redirection, and provide +// storage to specify the redirection's return value. We only need to capture +// enough argument data here for calls made directly from Gecko code, +// i.e. where events are not passed through. Calls made while events are passed +// through are performed with the same stack and register state as when they +// were initially invoked. +// +// Arguments and return value indexes refer to the register contents as passed +// to the function originally. For functions with complex or floating point +// arguments and return values, the right index to use might be different than +// expected, per the requirements of the System V x64 ABI. +struct CallArguments : public CallRegisterArguments +{ +protected: size_t stack[64]; // 104 // Size: 616 @@ -121,6 +135,12 @@ public: } } + template + size_t* StackAddress() { + static_assert(Offset % sizeof(size_t) == 0, "Bad stack offset"); + return &stack[Offset / sizeof(size_t)]; + } + template T& Rval() { static_assert(sizeof(T) == sizeof(size_t), "Size must match"); @@ -142,6 +162,27 @@ public: } }; +inline void +CallRegisterArguments::CopyFrom(const CallRegisterArguments* aArguments) +{ + memcpy(this, aArguments, sizeof(CallRegisterArguments)); +} + +inline void +CallRegisterArguments::CopyTo(CallRegisterArguments* aArguments) const +{ + memcpy(aArguments, this, sizeof(CallRegisterArguments)); +} + +inline void +CallRegisterArguments::CopyRvalFrom(const CallRegisterArguments* aArguments) +{ + rval0 = aArguments->rval0; + rval1 = aArguments->rval1; + floatrval0 = aArguments->floatrval0; + floatrval1 = aArguments->floatrval1; +} + // Generic type for a system error code. typedef ssize_t ErrorType; @@ -164,6 +205,15 @@ enum class PreambleResult { PassThrough }; +// Signature for a function that is called on entry to a redirection and can +// modify its behavior. +typedef PreambleResult (*PreambleFn)(CallArguments* aArguments); + +// Signature for a function that conveys data about a call to or from the +// middleman process. +struct MiddlemanCallContext; +typedef void (*MiddlemanCallFn)(MiddlemanCallContext& aCx); + // Information about a system library API function which is being redirected. struct Redirection { @@ -187,7 +237,15 @@ struct Redirection SaveOutputFn mSaveOutput; // If specified, will be called upon entry to the redirected call. - PreambleResult (*mPreamble)(CallArguments* aArguments); + PreambleFn mPreamble; + + // If specified, will be called while replaying and diverged from the + // recording to perform this call in the middleman process. + MiddlemanCallFn mMiddlemanCall; + + // Additional preamble that is only called while replaying and diverged from + // the recording. + PreambleFn mMiddlemanPreamble; }; // All platform specific redirections, indexed by the call event. @@ -468,15 +526,6 @@ RR_WriteBufferViaRval(Stream& aEvents, CallArguments* aArguments, ErrorType* aEr aEvents.RecordOrReplayBytes(buf, rval + Offset); } -// Insert an atomic access while recording/replaying so that calls to this -// function replay in the same order they occurred in while recording. This is -// used for functions that are used in inter-thread synchronization. -static inline void -RR_OrderCall(Stream& aEvents, CallArguments* aArguments, ErrorType* aError) -{ - AutoOrderedAtomicAccess(); -} - // Record/replay a scalar return value. static inline void RR_ScalarRval(Stream& aEvents, CallArguments* aArguments, ErrorType* aError) @@ -520,7 +569,7 @@ template static inline PreambleResult Preamble_Veto(CallArguments* aArguments) { - aArguments->Rval() = 0; + aArguments->Rval() = ReturnValue; return PreambleResult::Veto; } @@ -531,7 +580,7 @@ Preamble_VetoIfNotPassedThrough(CallArguments* aArguments) if (AreThreadEventsPassedThrough()) { return PreambleResult::PassThrough; } - aArguments->Rval() = 0; + aArguments->Rval() = ReturnValue; return PreambleResult::Veto; } diff --git a/toolkit/recordreplay/ProcessRedirectDarwin.cpp b/toolkit/recordreplay/ProcessRedirectDarwin.cpp index 91e4453edeef..67dfc0ec8ab8 100644 --- a/toolkit/recordreplay/ProcessRedirectDarwin.cpp +++ b/toolkit/recordreplay/ProcessRedirectDarwin.cpp @@ -13,6 +13,7 @@ #include "MemorySnapshot.h" #include "ProcessRecordReplay.h" #include "ProcessRewind.h" +#include "base/eintr_wrapper.h" #include #include @@ -78,26 +79,31 @@ namespace recordreplay { MACRO(gettimeofday, RR_SaveRvalHadErrorNegative, \ RR_WriteOptionalBufferFixedSize<1, sizeof(struct timezone)>>>, \ - Preamble_gettimeofday) \ + nullptr, nullptr, Preamble_PassThrough) \ MACRO(getuid, RR_ScalarRval) \ MACRO(geteuid, RR_ScalarRval) \ MACRO(getgid, RR_ScalarRval) \ MACRO(getegid, RR_ScalarRval) \ MACRO(issetugid, RR_ScalarRval) \ MACRO(__gettid, RR_ScalarRval) \ - MACRO(getpid, RR_ScalarRval) \ + MACRO(getpid, nullptr, Preamble_getpid) \ MACRO(fcntl, RR_SaveRvalHadErrorNegative, Preamble_fcntl) \ MACRO(getattrlist, RR_SaveRvalHadErrorNegative>) \ MACRO(fstat$INODE64, \ - RR_SaveRvalHadErrorNegative>) \ + RR_SaveRvalHadErrorNegative>, \ + nullptr, nullptr, Preamble_SetError) \ MACRO(lstat$INODE64, \ - RR_SaveRvalHadErrorNegative>) \ + RR_SaveRvalHadErrorNegative>, \ + nullptr, nullptr, Preamble_SetError) \ MACRO(stat$INODE64, \ - RR_SaveRvalHadErrorNegative>) \ + RR_SaveRvalHadErrorNegative>, \ + nullptr, nullptr, Preamble_SetError) \ MACRO(statfs$INODE64, \ - RR_SaveRvalHadErrorNegative>) \ + RR_SaveRvalHadErrorNegative>, \ + nullptr, nullptr, Preamble_SetError) \ MACRO(fstatfs$INODE64, \ - RR_SaveRvalHadErrorNegative>) \ + RR_SaveRvalHadErrorNegative>, \ + nullptr, nullptr, Preamble_SetError) \ MACRO(readlink, RR_SaveRvalHadErrorNegative>) \ MACRO(__getdirentries64, RR_SaveRvalHadErrorNegative, \ @@ -114,7 +120,8 @@ namespace recordreplay { RR_SaveRvalHadErrorNegative>) \ MACRO(__setrlimit, RR_SaveRvalHadErrorNegative) \ MACRO(sigprocmask, \ - RR_SaveRvalHadErrorNegative>) \ + RR_SaveRvalHadErrorNegative>, \ + nullptr, nullptr, Preamble_PassThrough) \ MACRO(sigaltstack, \ RR_SaveRvalHadErrorNegative>) \ MACRO(sigaction, \ @@ -145,7 +152,7 @@ namespace recordreplay { MACRO(pthread_cond_wait, nullptr, Preamble_pthread_cond_wait) \ MACRO(pthread_cond_timedwait, nullptr, Preamble_pthread_cond_timedwait) \ MACRO(pthread_cond_timedwait_relative_np, nullptr, Preamble_pthread_cond_timedwait_relative_np) \ - MACRO(pthread_create, nullptr, Preamble_pthread_create) \ + MACRO(pthread_create, nullptr, Preamble_pthread_create, nullptr, Preamble_SetError) \ MACRO(pthread_join, nullptr, Preamble_pthread_join) \ MACRO(pthread_mutex_init, nullptr, Preamble_pthread_mutex_init) \ MACRO(pthread_mutex_destroy, nullptr, Preamble_pthread_mutex_destroy) \ @@ -162,7 +169,7 @@ namespace recordreplay { MACRO(fseek, RR_SaveRvalHadErrorNegative) \ MACRO(ftell, RR_SaveRvalHadErrorNegative) \ MACRO(fwrite, RR_ScalarRval) \ - MACRO(getenv, RR_CStringRval) \ + MACRO(getenv, RR_CStringRval, nullptr, nullptr, Preamble_Veto<0>) \ MACRO(localtime_r, RR_SaveRvalHadErrorZero, \ RR_RvalIsArgument<1>>>) \ @@ -175,8 +182,9 @@ namespace recordreplay { MACRO(setlocale, RR_CStringRval) \ MACRO(strftime, RR_Compose>) \ MACRO(arc4random, RR_ScalarRval) \ - MACRO(mach_absolute_time, RR_ScalarRval, Preamble_mach_absolute_time) \ - MACRO(mach_msg, RR_Compose>) \ + MACRO(mach_absolute_time, RR_ScalarRval, Preamble_mach_absolute_time, \ + nullptr, Preamble_PassThrough) \ + MACRO(mach_msg, RR_Compose>) \ MACRO(mach_timebase_info, \ RR_Compose>) \ MACRO(mach_vm_allocate, nullptr, Preamble_mach_vm_allocate) \ @@ -209,15 +217,17 @@ namespace recordreplay { MACRO(method_exchangeImplementations) \ MACRO(objc_autoreleasePoolPop) \ MACRO(objc_autoreleasePoolPush, RR_ScalarRval) \ - MACRO(objc_msgSend, nullptr, Preamble_objc_msgSend) \ + MACRO(objc_msgSend, RR_objc_msgSend, Preamble_objc_msgSend, \ + Middleman_objc_msgSend, MiddlemanPreamble_objc_msgSend) \ /* Cocoa functions */ \ MACRO(AcquireFirstMatchingEventInQueue, RR_ScalarRval) \ MACRO(CFArrayAppendValue) \ - MACRO(CFArrayCreate, RR_ScalarRval) \ - MACRO(CFArrayGetCount, RR_ScalarRval) \ - MACRO(CFArrayGetValueAtIndex, RR_ScalarRval) \ + MACRO(CFArrayCreate, RR_ScalarRval, nullptr, Middleman_CFArrayCreate) \ + MACRO(CFArrayGetCount, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \ + MACRO(CFArrayGetValueAtIndex, RR_ScalarRval, nullptr, Middleman_CFArrayGetValueAtIndex) \ MACRO(CFArrayRemoveValueAtIndex) \ - MACRO(CFAttributedStringCreate, RR_ScalarRval) \ + MACRO(CFAttributedStringCreate, RR_ScalarRval, nullptr, \ + Middleman_Compose, Middleman_CFTypeArg<2>, Middleman_CreateCFTypeRval>) \ MACRO(CFBundleCopyExecutableURL, RR_ScalarRval) \ MACRO(CFBundleCopyInfoDictionaryForURL, RR_ScalarRval) \ MACRO(CFBundleCreate, RR_ScalarRval) \ @@ -228,20 +238,32 @@ namespace recordreplay { MACRO(CFBundleGetInfoDictionary, RR_ScalarRval) \ MACRO(CFBundleGetMainBundle, RR_ScalarRval) \ MACRO(CFBundleGetValueForInfoDictionaryKey, RR_ScalarRval) \ - MACRO(CFDataGetBytePtr, RR_CFDataGetBytePtr) \ - MACRO(CFDataGetLength, RR_ScalarRval) \ + MACRO(CFDataGetBytePtr, RR_CFDataGetBytePtr, nullptr, Middleman_CFDataGetBytePtr) \ + MACRO(CFDataGetLength, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \ MACRO(CFDateFormatterCreate, RR_ScalarRval) \ MACRO(CFDateFormatterGetFormat, RR_ScalarRval) \ - MACRO(CFDictionaryAddValue) \ - MACRO(CFDictionaryCreate, RR_ScalarRval) \ - MACRO(CFDictionaryCreateMutable, RR_ScalarRval) \ - MACRO(CFDictionaryCreateMutableCopy, RR_ScalarRval) \ - MACRO(CFDictionaryGetValue, RR_ScalarRval) \ + MACRO(CFDictionaryAddValue, nullptr, nullptr, \ + Middleman_Compose, \ + Middleman_CFTypeArg<1>, \ + Middleman_CFTypeArg<2>>) \ + MACRO(CFDictionaryCreate, RR_ScalarRval, nullptr, Middleman_CFDictionaryCreate) \ + MACRO(CFDictionaryCreateMutable, RR_ScalarRval, nullptr, Middleman_CreateCFTypeRval) \ + MACRO(CFDictionaryCreateMutableCopy, RR_ScalarRval, nullptr, \ + Middleman_Compose, Middleman_CreateCFTypeRval>) \ + MACRO(CFDictionaryGetValue, RR_ScalarRval, nullptr, \ + Middleman_Compose, Middleman_CFTypeArg<1>, Middleman_CFTypeRval>) \ MACRO(CFDictionaryGetValueIfPresent, \ - RR_Compose>) \ - MACRO(CFDictionaryReplaceValue) \ - MACRO(CFEqual, RR_ScalarRval) \ - MACRO(CFGetTypeID, RR_ScalarRval) \ + RR_Compose>, nullptr, \ + Middleman_Compose, \ + Middleman_CFTypeArg<1>, \ + Middleman_CFTypeOutputArg<2>>) \ + MACRO(CFDictionaryReplaceValue, nullptr, nullptr, \ + Middleman_Compose, \ + Middleman_CFTypeArg<1>, \ + Middleman_CFTypeArg<2>>) \ + MACRO(CFEqual, RR_ScalarRval, nullptr, \ + Middleman_Compose, Middleman_CFTypeArg<1>>) \ + MACRO(CFGetTypeID, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \ MACRO(CFLocaleCopyCurrent, RR_ScalarRval) \ MACRO(CFLocaleCopyPreferredLanguages, RR_ScalarRval) \ MACRO(CFLocaleCreate, RR_ScalarRval) \ @@ -249,9 +271,10 @@ namespace recordreplay { MACRO(CFNotificationCenterAddObserver, nullptr, Preamble_CFNotificationCenterAddObserver) \ MACRO(CFNotificationCenterGetLocalCenter, RR_ScalarRval) \ MACRO(CFNotificationCenterRemoveObserver) \ - MACRO(CFNumberCreate, RR_ScalarRval) \ - MACRO(CFNumberGetValue, RR_Compose) \ - MACRO(CFNumberIsFloatType, RR_ScalarRval) \ + MACRO(CFNumberCreate, RR_ScalarRval, nullptr, Middleman_CFNumberCreate) \ + MACRO(CFNumberGetValue, RR_Compose, nullptr, \ + Middleman_CFNumberGetValue) \ + MACRO(CFNumberIsFloatType, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \ MACRO(CFPreferencesAppValueIsForced, RR_ScalarRval) \ MACRO(CFPreferencesCopyAppValue, RR_ScalarRval) \ MACRO(CFPreferencesCopyValue, RR_ScalarRval) \ @@ -260,8 +283,10 @@ namespace recordreplay { MACRO(CFReadStreamClose) \ MACRO(CFReadStreamCreateWithFile, RR_ScalarRval) \ MACRO(CFReadStreamOpen, RR_ScalarRval) \ - MACRO(CFRelease, RR_ScalarRval) \ - MACRO(CFRetain, RR_ScalarRval) \ + /* Don't handle release/retain calls explicitly in the middleman, all resources */ \ + /* will be cleaned up when its calls are reset. */ \ + MACRO(CFRelease, RR_ScalarRval, nullptr, nullptr, Preamble_Veto<0>) \ + MACRO(CFRetain, RR_ScalarRval, nullptr, nullptr, MiddlemanPreamble_CFRetain) \ MACRO(CFRunLoopAddSource) \ MACRO(CFRunLoopGetCurrent, RR_ScalarRval) \ MACRO(CFRunLoopRemoveSource) \ @@ -269,12 +294,14 @@ namespace recordreplay { MACRO(CFRunLoopSourceSignal) \ MACRO(CFRunLoopWakeUp) \ MACRO(CFStringAppendCharacters) \ - MACRO(CFStringCompare, RR_ScalarRval) \ + MACRO(CFStringCompare, RR_ScalarRval, nullptr, \ + Middleman_Compose, Middleman_CFTypeArg<1>>) \ MACRO(CFStringCreateArrayBySeparatingStrings, RR_ScalarRval) \ MACRO(CFStringCreateMutable, RR_ScalarRval) \ MACRO(CFStringCreateWithBytes, RR_ScalarRval) \ MACRO(CFStringCreateWithBytesNoCopy, RR_ScalarRval) \ - MACRO(CFStringCreateWithCharactersNoCopy, RR_ScalarRval) \ + MACRO(CFStringCreateWithCharactersNoCopy, RR_ScalarRval, nullptr, \ + Middleman_Compose, Middleman_CreateCFTypeRval>) \ MACRO(CFStringCreateWithCString, RR_ScalarRval) \ MACRO(CFStringCreateWithFormat, RR_ScalarRval) \ /* Argument indexes are off by one here as the CFRange argument uses two slots. */ \ @@ -284,11 +311,12 @@ namespace recordreplay { RR_WriteOptionalBufferFixedSize<8, sizeof(CFIndex)>>) \ /* Argument indexes are off by one here as the CFRange argument uses two slots. */ \ /* We also need to specify the argument register with the range's length here. */ \ - MACRO(CFStringGetCharacters, RR_WriteBuffer<3, 2, UniChar>) \ + MACRO(CFStringGetCharacters, RR_WriteBuffer<3, 2, UniChar>, nullptr, \ + Middleman_Compose, Middleman_WriteBuffer<3, 2, UniChar>>) \ MACRO(CFStringGetCString, RR_Compose>) \ MACRO(CFStringGetCStringPtr, nullptr, Preamble_VetoIfNotPassedThrough<0>) \ MACRO(CFStringGetIntValue, RR_ScalarRval) \ - MACRO(CFStringGetLength, RR_ScalarRval) \ + MACRO(CFStringGetLength, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \ MACRO(CFStringGetMaximumSizeForEncoding, RR_ScalarRval) \ MACRO(CFStringHasPrefix, RR_ScalarRval) \ MACRO(CFStringTokenizerAdvanceToNextToken, RR_ScalarRval) \ @@ -306,64 +334,91 @@ namespace recordreplay { MACRO(CGAffineTransformConcat, RR_OversizeRval) \ MACRO(CGBitmapContextCreateImage, RR_ScalarRval) \ MACRO(CGBitmapContextCreateWithData, \ - RR_Compose) \ + RR_Compose, nullptr, \ + Middleman_CGBitmapContextCreateWithData) \ MACRO(CGBitmapContextGetBytesPerRow, RR_ScalarRval) \ MACRO(CGBitmapContextGetHeight, RR_ScalarRval) \ MACRO(CGBitmapContextGetWidth, RR_ScalarRval) \ MACRO(CGColorRelease, RR_ScalarRval) \ MACRO(CGColorSpaceCopyICCProfile, RR_ScalarRval) \ - MACRO(CGColorSpaceCreateDeviceRGB, RR_ScalarRval) \ + MACRO(CGColorSpaceCreateDeviceRGB, RR_ScalarRval, nullptr, Middleman_CreateCFTypeRval) \ MACRO(CGColorSpaceCreatePattern, RR_ScalarRval) \ MACRO(CGColorSpaceRelease, RR_ScalarRval) \ MACRO(CGContextBeginTransparencyLayerWithRect) \ - MACRO(CGContextClipToRects, RR_ScalarRval) \ - MACRO(CGContextConcatCTM) \ + MACRO(CGContextClipToRects, RR_ScalarRval, nullptr, \ + Middleman_Compose, Middleman_Buffer<1, 2, CGRect>>) \ + MACRO(CGContextConcatCTM, nullptr, nullptr, \ + Middleman_Compose, \ + Middleman_StackArgumentData>) \ MACRO(CGContextDrawImage, RR_FlushCGContext<0>) \ MACRO(CGContextEndTransparencyLayer) \ - MACRO(CGContextFillRect, RR_FlushCGContext<0>) \ + MACRO(CGContextFillRect, RR_FlushCGContext<0>, nullptr, \ + Middleman_Compose, \ + Middleman_StackArgumentData, \ + Middleman_FlushCGContext<0>>) \ MACRO(CGContextGetClipBoundingBox, RR_OversizeRval) \ MACRO(CGContextGetCTM, RR_OversizeRval) \ MACRO(CGContextGetType, RR_ScalarRval) \ MACRO(CGContextGetUserSpaceToDeviceSpaceTransform, RR_OversizeRval) \ - MACRO(CGContextRestoreGState, nullptr, Preamble_CGContextRestoreGState) \ - MACRO(CGContextSaveGState) \ - MACRO(CGContextSetAllowsFontSubpixelPositioning) \ - MACRO(CGContextSetAllowsFontSubpixelQuantization) \ - MACRO(CGContextSetAlpha) \ - MACRO(CGContextSetBaseCTM) \ - MACRO(CGContextSetCTM) \ - MACRO(CGContextSetGrayFillColor) \ + MACRO(CGContextRestoreGState, nullptr, Preamble_CGContextRestoreGState, \ + Middleman_UpdateCFTypeArg<0>) \ + MACRO(CGContextSaveGState, nullptr, nullptr, Middleman_UpdateCFTypeArg<0>) \ + MACRO(CGContextSetAllowsFontSubpixelPositioning, nullptr, nullptr, \ + Middleman_UpdateCFTypeArg<0>) \ + MACRO(CGContextSetAllowsFontSubpixelQuantization, nullptr, nullptr, \ + Middleman_UpdateCFTypeArg<0>) \ + MACRO(CGContextSetAlpha, nullptr, nullptr, Middleman_UpdateCFTypeArg<0>) \ + MACRO(CGContextSetBaseCTM, nullptr, nullptr, \ + Middleman_Compose, \ + Middleman_StackArgumentData>) \ + MACRO(CGContextSetCTM, nullptr, nullptr, \ + Middleman_Compose, \ + Middleman_StackArgumentData>) \ + MACRO(CGContextSetGrayFillColor, nullptr, nullptr, Middleman_UpdateCFTypeArg<0>) \ MACRO(CGContextSetRGBFillColor) \ - MACRO(CGContextSetShouldAntialias) \ - MACRO(CGContextSetShouldSmoothFonts) \ - MACRO(CGContextSetShouldSubpixelPositionFonts) \ - MACRO(CGContextSetShouldSubpixelQuantizeFonts) \ - MACRO(CGContextSetTextDrawingMode) \ - MACRO(CGContextSetTextMatrix) \ - MACRO(CGContextScaleCTM) \ - MACRO(CGContextTranslateCTM) \ + MACRO(CGContextSetShouldAntialias, nullptr, nullptr, Middleman_UpdateCFTypeArg<0>) \ + MACRO(CGContextSetShouldSmoothFonts, nullptr, nullptr, Middleman_UpdateCFTypeArg<0>) \ + MACRO(CGContextSetShouldSubpixelPositionFonts, nullptr, nullptr, Middleman_UpdateCFTypeArg<0>) \ + MACRO(CGContextSetShouldSubpixelQuantizeFonts, nullptr, nullptr, Middleman_UpdateCFTypeArg<0>) \ + MACRO(CGContextSetTextDrawingMode, nullptr, nullptr, Middleman_UpdateCFTypeArg<0>) \ + MACRO(CGContextSetTextMatrix, nullptr, nullptr, \ + Middleman_Compose, \ + Middleman_StackArgumentData>) \ + MACRO(CGContextScaleCTM, nullptr, nullptr, Middleman_UpdateCFTypeArg<0>) \ + MACRO(CGContextTranslateCTM, nullptr, nullptr, Middleman_UpdateCFTypeArg<0>) \ MACRO(CGDataProviderCreateWithData, RR_Compose) \ MACRO(CGDataProviderRelease) \ MACRO(CGDisplayCopyColorSpace, RR_ScalarRval) \ MACRO(CGDisplayIOServicePort, RR_ScalarRval) \ MACRO(CGEventSourceCounterForEventType, RR_ScalarRval) \ - MACRO(CGFontCopyTableForTag, RR_ScalarRval) \ - MACRO(CGFontCopyTableTags, RR_ScalarRval) \ - MACRO(CGFontCopyVariations, RR_ScalarRval) \ + MACRO(CGFontCopyTableForTag, RR_ScalarRval, nullptr, \ + Middleman_Compose, Middleman_CreateCFTypeRval>) \ + MACRO(CGFontCopyTableTags, RR_ScalarRval, nullptr, \ + Middleman_Compose, Middleman_CreateCFTypeRval>) \ + MACRO(CGFontCopyVariations, RR_ScalarRval, nullptr, \ + Middleman_Compose, Middleman_CreateCFTypeRval>) \ MACRO(CGFontCreateCopyWithVariations, RR_ScalarRval) \ MACRO(CGFontCreateWithDataProvider, RR_ScalarRval) \ - MACRO(CGFontCreateWithFontName, RR_ScalarRval) \ + MACRO(CGFontCreateWithFontName, RR_ScalarRval, nullptr, \ + Middleman_Compose, Middleman_CreateCFTypeRval>) \ MACRO(CGFontCreateWithPlatformFont, RR_ScalarRval) \ - MACRO(CGFontGetAscent, RR_ScalarRval) \ - MACRO(CGFontGetCapHeight, RR_ScalarRval) \ - MACRO(CGFontGetDescent, RR_ScalarRval) \ - MACRO(CGFontGetFontBBox, RR_OversizeRval) \ - MACRO(CGFontGetGlyphAdvances, RR_Compose>) \ - MACRO(CGFontGetGlyphBBoxes, RR_Compose>) \ + MACRO(CGFontGetAscent, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \ + MACRO(CGFontGetCapHeight, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \ + MACRO(CGFontGetDescent, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \ + MACRO(CGFontGetFontBBox, RR_OversizeRval, nullptr, \ + Middleman_Compose, Middleman_OversizeRval>) \ + MACRO(CGFontGetGlyphAdvances, RR_Compose>, nullptr, \ + Middleman_Compose, \ + Middleman_Buffer<1, 2, CGGlyph>, \ + Middleman_WriteBuffer<3, 2, int>>) \ + MACRO(CGFontGetGlyphBBoxes, RR_Compose>, nullptr, \ + Middleman_Compose, \ + Middleman_Buffer<1, 2, CGGlyph>, \ + Middleman_WriteBuffer<3, 2, CGRect>>) \ MACRO(CGFontGetGlyphPath, RR_ScalarRval) \ - MACRO(CGFontGetLeading, RR_ScalarRval) \ - MACRO(CGFontGetUnitsPerEm, RR_ScalarRval) \ - MACRO(CGFontGetXHeight, RR_ScalarRval) \ + MACRO(CGFontGetLeading, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \ + MACRO(CGFontGetUnitsPerEm, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \ + MACRO(CGFontGetXHeight, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \ MACRO(CGImageGetHeight, RR_ScalarRval) \ MACRO(CGImageGetWidth, RR_ScalarRval) \ MACRO(CGImageRelease, RR_ScalarRval) \ @@ -377,59 +432,110 @@ namespace recordreplay { MACRO(CGPathIsEmpty, RR_ScalarRval) \ MACRO(CGSSetDebugOptions, RR_ScalarRval) \ MACRO(CGSShutdownServerConnections) \ - MACRO(CTFontCopyFamilyName, RR_ScalarRval) \ - MACRO(CTFontCopyFeatures, RR_ScalarRval) \ - MACRO(CTFontCopyFontDescriptor, RR_ScalarRval) \ - MACRO(CTFontCopyGraphicsFont, RR_ScalarRval) \ - MACRO(CTFontCopyTable, RR_ScalarRval) \ + MACRO(CTFontCopyFamilyName, RR_ScalarRval, nullptr, \ + Middleman_Compose, Middleman_CreateCFTypeRval>) \ + MACRO(CTFontCopyFeatures, RR_ScalarRval, nullptr, \ + Middleman_Compose, Middleman_CreateCFTypeRval>) \ + MACRO(CTFontCopyFontDescriptor, RR_ScalarRval, nullptr, \ + Middleman_Compose, Middleman_CreateCFTypeRval>) \ + MACRO(CTFontCopyGraphicsFont, RR_ScalarRval, nullptr, \ + Middleman_Compose, Middleman_CreateCFTypeRval>) \ + MACRO(CTFontCopyTable, RR_ScalarRval, nullptr, \ + Middleman_Compose, Middleman_CreateCFTypeRval>) \ MACRO(CTFontCopyVariationAxes, RR_ScalarRval) \ - MACRO(CTFontCreateForString, RR_ScalarRval) \ + MACRO(CTFontCreateForString, RR_ScalarRval, nullptr, \ + Middleman_Compose, Middleman_CFTypeArg<1>, Middleman_CreateCFTypeRval>) \ MACRO(CTFontCreatePathForGlyph, RR_ScalarRval) \ - MACRO(CTFontCreateWithFontDescriptor, RR_ScalarRval) \ - MACRO(CTFontCreateWithGraphicsFont, RR_ScalarRval) \ - MACRO(CTFontCreateWithName, RR_ScalarRval) \ - MACRO(CTFontDescriptorCopyAttribute, RR_ScalarRval) \ - MACRO(CTFontDescriptorCreateCopyWithAttributes, RR_ScalarRval) \ - MACRO(CTFontDescriptorCreateMatchingFontDescriptors, RR_ScalarRval) \ - MACRO(CTFontDescriptorCreateWithAttributes, RR_ScalarRval) \ - MACRO(CTFontDrawGlyphs, RR_FlushCGContext<4>) \ + MACRO(CTFontCreateWithFontDescriptor, RR_ScalarRval, nullptr, \ + Middleman_Compose, \ + Middleman_BufferFixedSize<1, sizeof(CGAffineTransform)>, \ + Middleman_CreateCFTypeRval>) \ + MACRO(CTFontCreateWithGraphicsFont, RR_ScalarRval, nullptr, \ + Middleman_Compose, \ + Middleman_BufferFixedSize<1, sizeof(CGAffineTransform)>, \ + Middleman_CFTypeArg<2>, \ + Middleman_CreateCFTypeRval>) \ + MACRO(CTFontCreateWithName, RR_ScalarRval, nullptr, \ + Middleman_Compose, \ + Middleman_BufferFixedSize<1, sizeof(CGAffineTransform)>, \ + Middleman_CreateCFTypeRval>) \ + MACRO(CTFontDescriptorCopyAttribute, RR_ScalarRval, nullptr, \ + Middleman_Compose, Middleman_CFTypeArg<1>, Middleman_CreateCFTypeRval>) \ + MACRO(CTFontDescriptorCreateCopyWithAttributes, RR_ScalarRval, nullptr, \ + Middleman_Compose, Middleman_CFTypeArg<1>, Middleman_CreateCFTypeRval>) \ + MACRO(CTFontDescriptorCreateMatchingFontDescriptors, RR_ScalarRval, nullptr, \ + Middleman_Compose, Middleman_CFTypeArg<1>, Middleman_CreateCFTypeRval>) \ + MACRO(CTFontDescriptorCreateWithAttributes, RR_ScalarRval, nullptr, \ + Middleman_Compose, Middleman_CreateCFTypeRval>) \ + MACRO(CTFontDrawGlyphs, RR_FlushCGContext<4>, nullptr, \ + Middleman_Compose, \ + Middleman_CFTypeArg<4>, \ + Middleman_Buffer<1, 3, CGGlyph>, \ + Middleman_Buffer<2, 3, CGPoint>, \ + Middleman_FlushCGContext<4>>) \ MACRO(CTFontGetAdvancesForGlyphs, \ - RR_Compose>) \ - MACRO(CTFontGetAscent, RR_FloatRval) \ - MACRO(CTFontGetBoundingBox, RR_OversizeRval) \ + RR_Compose>, nullptr, \ + Middleman_Compose, \ + Middleman_Buffer<2, 4, CGGlyph>, \ + Middleman_WriteBuffer<3, 4, CGSize>>) \ + MACRO(CTFontGetAscent, RR_FloatRval, nullptr, Middleman_CFTypeArg<0>) \ + MACRO(CTFontGetBoundingBox, RR_OversizeRval, nullptr, \ + Middleman_Compose, Middleman_OversizeRval>) \ MACRO(CTFontGetBoundingRectsForGlyphs, \ /* Argument indexes here are off by one due to the oversize rval. */ \ - RR_Compose, RR_WriteOptionalBuffer<4, 5, CGRect>>) \ - MACRO(CTFontGetCapHeight, RR_FloatRval) \ - MACRO(CTFontGetDescent, RR_FloatRval) \ - MACRO(CTFontGetGlyphCount, RR_ScalarRval) \ + RR_Compose, \ + RR_WriteOptionalBuffer<4, 5, CGRect>>, nullptr, \ + Middleman_Compose, \ + Middleman_Buffer<3, 5, CGGlyph>, \ + Middleman_OversizeRval, \ + Middleman_WriteBuffer<4, 5, CGRect>>) \ + MACRO(CTFontGetCapHeight, RR_FloatRval, nullptr, Middleman_CFTypeArg<0>) \ + MACRO(CTFontGetDescent, RR_FloatRval, nullptr, Middleman_CFTypeArg<0>) \ + MACRO(CTFontGetGlyphCount, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \ MACRO(CTFontGetGlyphsForCharacters, \ - RR_Compose>) \ - MACRO(CTFontGetLeading, RR_FloatRval) \ - MACRO(CTFontGetSize, RR_FloatRval) \ - MACRO(CTFontGetSymbolicTraits, RR_ScalarRval) \ - MACRO(CTFontGetUnderlinePosition, RR_FloatRval) \ - MACRO(CTFontGetUnderlineThickness, RR_FloatRval) \ - MACRO(CTFontGetUnitsPerEm, RR_ScalarRval) \ - MACRO(CTFontGetXHeight, RR_FloatRval) \ + RR_Compose>, nullptr, \ + Middleman_Compose, \ + Middleman_Buffer<1, 3, UniChar>, \ + Middleman_WriteBuffer<2, 3, CGGlyph>>) \ + MACRO(CTFontGetLeading, RR_FloatRval, nullptr, Middleman_CFTypeArg<0>) \ + MACRO(CTFontGetSize, RR_FloatRval, nullptr, Middleman_CFTypeArg<0>) \ + MACRO(CTFontGetSymbolicTraits, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \ + MACRO(CTFontGetUnderlinePosition, RR_FloatRval, nullptr, Middleman_CFTypeArg<0>) \ + MACRO(CTFontGetUnderlineThickness, RR_FloatRval, nullptr, Middleman_CFTypeArg<0>) \ + MACRO(CTFontGetUnitsPerEm, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \ + MACRO(CTFontGetXHeight, RR_FloatRval, nullptr, Middleman_CFTypeArg<0>) \ MACRO(CTFontManagerCopyAvailableFontFamilyNames, RR_ScalarRval) \ MACRO(CTFontManagerRegisterFontsForURLs, RR_ScalarRval) \ MACRO(CTFontManagerSetAutoActivationSetting) \ - MACRO(CTLineCreateWithAttributedString, RR_ScalarRval) \ - MACRO(CTLineGetGlyphRuns, RR_ScalarRval) \ - MACRO(CTRunGetAttributes, RR_ScalarRval) \ - MACRO(CTRunGetGlyphCount, RR_ScalarRval) \ - MACRO(CTRunGetGlyphsPtr, RR_CTRunGetElements) \ - MACRO(CTRunGetPositionsPtr, RR_CTRunGetElements) \ - MACRO(CTRunGetStringIndicesPtr, RR_CTRunGetElements) \ - MACRO(CTRunGetStringRange, RR_ComplexScalarRval) \ + MACRO(CTLineCreateWithAttributedString, RR_ScalarRval, nullptr, \ + Middleman_Compose, Middleman_CreateCFTypeRval>) \ + MACRO(CTLineGetGlyphRuns, RR_ScalarRval, nullptr, \ + Middleman_Compose, Middleman_CFTypeRval>) \ + MACRO(CTRunGetAttributes, RR_ScalarRval, nullptr, \ + Middleman_Compose, Middleman_CFTypeRval>) \ + MACRO(CTRunGetGlyphCount, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \ + MACRO(CTRunGetGlyphsPtr, RR_CTRunGetElements, nullptr, \ + Middleman_CTRunGetElements) \ + MACRO(CTRunGetPositionsPtr, RR_CTRunGetElements, nullptr, \ + Middleman_CTRunGetElements) \ + MACRO(CTRunGetStringIndicesPtr, RR_CTRunGetElements, nullptr, \ + Middleman_CTRunGetElements) \ + MACRO(CTRunGetStringRange, RR_ComplexScalarRval, nullptr, Middleman_CFTypeArg<0>) \ /* Argument indexes are off by one here as the CFRange argument uses two slots. */ \ - MACRO(CTRunGetTypographicBounds, RR_Compose< \ - RR_FloatRval, \ - RR_WriteOptionalBufferFixedSize<3, sizeof(CGFloat)>, \ - RR_WriteOptionalBufferFixedSize<4, sizeof(CGFloat)>, \ - RR_WriteOptionalBufferFixedSize<5, sizeof(CGFloat)>>) \ - MACRO(CUIDraw) \ + MACRO(CTRunGetTypographicBounds, \ + RR_Compose, \ + RR_WriteOptionalBufferFixedSize<4, sizeof(CGFloat)>, \ + RR_WriteOptionalBufferFixedSize<5, sizeof(CGFloat)>>, nullptr, \ + Middleman_Compose, \ + Middleman_WriteBufferFixedSize<3, sizeof(CGFloat)>, \ + Middleman_WriteBufferFixedSize<4, sizeof(CGFloat)>, \ + Middleman_WriteBufferFixedSize<5, sizeof(CGFloat)>>) \ + MACRO(CUIDraw, nullptr, nullptr, \ + Middleman_Compose, \ + Middleman_CFTypeArg<1>, \ + Middleman_CFTypeArg<2>, \ + Middleman_StackArgumentData>) \ MACRO(FSCompareFSRefs, RR_ScalarRval) \ MACRO(FSGetVolumeInfo, RR_Compose< \ RR_ScalarRval, \ @@ -469,11 +575,12 @@ namespace recordreplay { RR_ScalarRval, \ RR_WriteOptionalBufferFixedSize<4, sizeof(FSRef)>, \ RR_WriteOptionalBufferFixedSize<5, sizeof(CFURLRef)>>) \ - MACRO(LSGetApplicationForURL, RR_Compose< \ + MACRO(LSGetApplicationForURL, RR_Compose< \ RR_ScalarRval, \ RR_WriteOptionalBufferFixedSize<2, sizeof(FSRef)>, \ RR_WriteOptionalBufferFixedSize<3, sizeof(CFURLRef)>>) \ - MACRO(NSClassFromString, RR_ScalarRval) \ + MACRO(NSClassFromString, RR_ScalarRval, nullptr, \ + Middleman_Compose, Middleman_CFTypeRval>) \ MACRO(NSRectFill) \ MACRO(NSSearchPathForDirectoriesInDomains, RR_ScalarRval) \ MACRO(NSSetFocusRingStyle, RR_ScalarRval) \ @@ -578,6 +685,203 @@ ReplayInvokeCallback(size_t aCallbackId) } } +/////////////////////////////////////////////////////////////////////////////// +// Middleman Call Helpers +/////////////////////////////////////////////////////////////////////////////// + +static bool +TestObjCObjectClass(id aObj, const char* aName) +{ + Class cls = object_getClass(aObj); + while (cls) { + const char* className = class_getName(cls); + if (!strcmp(className, aName)) { + return true; + } + cls = class_getSuperclass(cls); + } + return false; +} + +// Inputs that originate from static data in the replaying process itself +// rather than from previous middleman calls. +enum class ObjCInputKind { + StaticClass, + ConstantString, +}; + +// Capture an Objective C or CoreFoundation input to a call, which may come +// either from another middleman call, or from static data in the replaying +// process. +static void +Middleman_ObjCInput(MiddlemanCallContext& aCx, id* aThingPtr) +{ + MOZ_RELEASE_ASSERT(aCx.AccessPreface()); + + if (Middleman_SystemInput(aCx, (const void**) aThingPtr)) { + // This value came from a previous middleman call. + return; + } + + MOZ_RELEASE_ASSERT(aCx.AccessInput()); + + if (aCx.mPhase == MiddlemanCallPhase::ReplayInput) { + // Try to determine where this object came from. + + // List of the Objective C classes which messages might be sent to directly. + static const char* gStaticClasses[] = { + "NSAutoreleasePool", + "NSColor", + "NSDictionary", + "NSFont", + "NSFontManager", + "NSNumber", + "NSString", + "NSWindow", + }; + + // Watch for messages sent to particular classes. + for (const char* className : gStaticClasses) { + Class cls = objc_lookUpClass(className); + if (cls == (Class) *aThingPtr) { + aCx.WriteInputScalar((size_t) ObjCInputKind::StaticClass); + size_t len = strlen(className) + 1; + aCx.WriteInputScalar(len); + aCx.WriteInputBytes(className, len); + return; + } + } + + // Watch for constant compile time strings baked into the generated code or + // stored in system libraries. We can crash here if the object came from + // e.g. a replayed pointer from the recording, as can happen if not enough + // redirections have middleman call hooks. We could do better here to make + // sure the pointer looks like it could be a constant string, but it seems + // better and simpler to crash more reliably here than mask problems due to + // missing middleman call hooks. + if (TestObjCObjectClass(*aThingPtr, "NSString")) { + AutoPassThroughThreadEvents pt; + CFIndex len = CFStringGetLength((CFStringRef)*aThingPtr); + InfallibleVector buffer; + buffer.appendN(0, len); + CFStringGetCharacters((CFStringRef)*aThingPtr, { 0, len }, buffer.begin()); + aCx.WriteInputScalar((size_t) ObjCInputKind::ConstantString); + aCx.WriteInputScalar(len); + aCx.WriteInputBytes(buffer.begin(), len * sizeof(UniChar)); + return; + } + + aCx.MarkAsFailed(); + return; + } + + switch ((ObjCInputKind) aCx.ReadInputScalar()) { + case ObjCInputKind::StaticClass: { + size_t len = aCx.ReadInputScalar(); + UniquePtr className(new char[len]); + aCx.ReadInputBytes(className.get(), len); + *aThingPtr = (id) objc_lookUpClass(className.get()); + break; + } + case ObjCInputKind::ConstantString: { + size_t len = aCx.ReadInputScalar(); + UniquePtr contents(new UniChar[len]); + aCx.ReadInputBytes(contents.get(), len * sizeof(UniChar)); + *aThingPtr = (id) CFStringCreateWithCharacters(kCFAllocatorDefault, contents.get(), len); + break; + } + default: + MOZ_CRASH(); + } +} + +template +static void +Middleman_CFTypeArg(MiddlemanCallContext& aCx) +{ + if (aCx.AccessPreface()) { + auto& object = aCx.mArguments->Arg(); + Middleman_ObjCInput(aCx, &object); + } +} + +static void +Middleman_CFTypeOutput(MiddlemanCallContext& aCx, CFTypeRef* aOutput, bool aOwnsReference) +{ + Middleman_SystemOutput(aCx, (const void**) aOutput); + + if (*aOutput) { + switch (aCx.mPhase) { + case MiddlemanCallPhase::MiddlemanOutput: + if (!aOwnsReference) { + CFRetain(*aOutput); + } + break; + case MiddlemanCallPhase::MiddlemanRelease: + CFRelease(*aOutput); + break; + default: + break; + } + } +} + +// For APIs using the 'Get' rule: no reference is held on the returned value. +static void +Middleman_CFTypeRval(MiddlemanCallContext& aCx) +{ + auto& rval = aCx.mArguments->Rval(); + Middleman_CFTypeOutput(aCx, &rval, /* aOwnsReference = */ false); +} + +// For APIs using the 'Create' rule: a reference is held on the returned +// value which must be released. +static void +Middleman_CreateCFTypeRval(MiddlemanCallContext& aCx) +{ + auto& rval = aCx.mArguments->Rval(); + Middleman_CFTypeOutput(aCx, &rval, /* aOwnsReference = */ true); +} + +template +static void +Middleman_CFTypeOutputArg(MiddlemanCallContext& aCx) +{ + Middleman_WriteBufferFixedSize(aCx); + + auto arg = aCx.mArguments->Arg(); + Middleman_CFTypeOutput(aCx, arg, /* aOwnsReference = */ false); +} + +// For APIs whose result will be released by the middleman's autorelease pool. +static void +Middleman_AutoreleaseCFTypeRval(MiddlemanCallContext& aCx) +{ + auto& rval = aCx.mArguments->Rval(); + Middleman_SystemOutput(aCx, &rval); +} + +// For functions which have an input CFType value and also have side effects on +// that value, this associates the call with its own input value so that this +// will be treated as a dependent for any future calls using the value. +template +static void +Middleman_UpdateCFTypeArg(MiddlemanCallContext& aCx) +{ + auto arg = aCx.mArguments->Arg(); + + Middleman_CFTypeArg(aCx); + Middleman_SystemOutput(aCx, &arg, /* aUpdating = */ true); +} + +static PreambleResult +Preamble_SetError(CallArguments* aArguments) +{ + aArguments->Rval() = -1; + errno = EAGAIN; + return PreambleResult::Veto; +} + /////////////////////////////////////////////////////////////////////////////// // system call redirections /////////////////////////////////////////////////////////////////////////////// @@ -702,13 +1006,13 @@ RR_getsockopt(Stream& aEvents, CallArguments* aArguments, ErrorType* aError) } static PreambleResult -Preamble_gettimeofday(CallArguments* aArguments) +Preamble_getpid(CallArguments* aArguments) { - // If we have diverged from the recording, just get the actual current time - // rather than causing the current debugger operation to fail. This function - // is frequently called via e.g. JS natives which the debugger will execute. - if (HasDivergedFromRecording()) { - return PreambleResult::PassThrough; + if (!AreThreadEventsPassedThrough()) { + // Don't involve the recording with getpid calls, so that this can be used + // after diverging from the recording. + aArguments->Rval() = GetRecordingPid(); + return PreambleResult::Veto; } return PreambleResult::Redirect; } @@ -1155,26 +1459,6 @@ Preamble_PL_HashTableDestroy(CallArguments* aArguments) // Objective C redirections /////////////////////////////////////////////////////////////////////////////// -static bool -TestObjCObjectClass(id aObj, const char* aName) -{ - // When recording we can test to see what the class of an object is, but we - // have to record the result of the test because the object "pointers" we - // have when replaying are not valid. - bool found = false; - if (IsRecording()) { - Class cls = object_getClass(aObj); - while (cls) { - if (!strcmp(class_getName(cls), aName)) { - found = true; - break; - } - cls = class_getSuperclass(cls); - } - } - return RecordReplayValue(found); -} - // From Foundation.h, which has lots of Objective C declarations and can't be // included here :( struct NSFastEnumerationState @@ -1192,86 +1476,288 @@ static unsigned long gNeverChange; static PreambleResult Preamble_objc_msgSend(CallArguments* aArguments) { - Thread* thread = Thread::Current(); - if (!thread || thread->PassThroughEvents()) { - return PreambleResult::Redirect; - } - EnsureNotDivergedFromRecording(); + if (!AreThreadEventsPassedThrough()) { + auto message = aArguments->Arg<1, const char*>(); + // Watch for some top level NSApplication messages that can cause Gecko + // events to be processed. + if (!strcmp(message, "run") || + !strcmp(message, "nextEventMatchingMask:untilDate:inMode:dequeue:")) + { + PassThroughThreadEventsAllowCallbacks([&]() { + RecordReplayInvokeCall(CallEvent_objc_msgSend, aArguments); + }); + RecordReplayBytes(&aArguments->Rval(), sizeof(size_t)); + return PreambleResult::Veto; + } + } + return PreambleResult::Redirect; +} + +static void +RR_objc_msgSend(Stream& aEvents, CallArguments* aArguments, ErrorType* aError) +{ auto& object = aArguments->Arg<0, id>(); auto& message = aArguments->Arg<1, const char*>(); - thread->Events().RecordOrReplayThreadEvent(CallIdToThreadEvent(CallEvent_objc_msgSend)); - thread->Events().CheckInput(message); + aEvents.CheckInput(message); - bool handled = false; - - // Watch for some top level NSApplication messages that can cause Gecko - // events to be processed. - if ((!strcmp(message, "run") || - !strcmp(message, "nextEventMatchingMask:untilDate:inMode:dequeue:")) && - TestObjCObjectClass(object, "NSApplication")) - { - PassThroughThreadEventsAllowCallbacks([&]() { - RecordReplayInvokeCall(CallEvent_objc_msgSend, aArguments); - }); - handled = true; - } - - // Other messages are performed as normal. - if (!handled && IsRecording()) { - AutoPassThroughThreadEvents pt; - RecordReplayInvokeCall(CallEvent_objc_msgSend, aArguments); - } - - RecordReplayBytes(&aArguments->Rval(), sizeof(size_t)); - RecordReplayBytes(&aArguments->FloatRval(), sizeof(double)); + aEvents.RecordOrReplayValue(&aArguments->Rval()); + aEvents.RecordOrReplayBytes(&aArguments->FloatRval(), sizeof(double)); // Do some extra recording on messages that return additional state. - if (!strcmp(message, "countByEnumeratingWithState:objects:count:") && - TestObjCObjectClass(object, "NSArray")) - { + if (!strcmp(message, "countByEnumeratingWithState:objects:count:")) { auto& state = aArguments->Arg<2, NSFastEnumerationState*>(); auto& rval = aArguments->Rval(); if (IsReplaying()) { state->itemsPtr = NewLeakyArray(rval); state->mutationsPtr = &gNeverChange; } - RecordReplayBytes(state->itemsPtr, rval * sizeof(id)); + aEvents.RecordOrReplayBytes(state->itemsPtr, rval * sizeof(id)); } - if (!strcmp(message, "getCharacters:") && - TestObjCObjectClass(object, "NSString")) - { + if (!strcmp(message, "getCharacters:")) { size_t len = 0; if (IsRecording()) { AutoPassThroughThreadEvents pt; len = CFStringGetLength((CFStringRef) object); } - len = RecordReplayValue(len); - RecordReplayBytes(aArguments->Arg<2, void*>(), len * sizeof(wchar_t)); + aEvents.RecordOrReplayValue(&len); + aEvents.RecordOrReplayBytes(aArguments->Arg<2, void*>(), len * sizeof(wchar_t)); } - if ((!strcmp(message, "UTF8String") || - !strcmp(message, "cStringUsingEncoding:")) && - TestObjCObjectClass(object, "NSString")) - { + if (!strcmp(message, "UTF8String") || !strcmp(message, "cStringUsingEncoding:")) { auto& rval = aArguments->Rval(); - size_t len = RecordReplayValue(IsRecording() ? strlen(rval) : 0); + size_t len = IsRecording() ? strlen(rval) : 0; + aEvents.RecordOrReplayValue(&len); if (IsReplaying()) { rval = NewLeakyArray(len + 1); } - RecordReplayBytes(rval, len + 1); + aEvents.RecordOrReplayBytes(rval, len + 1); + } +} + +static PreambleResult +MiddlemanPreamble_objc_msgSend(CallArguments* aArguments) +{ + auto message = aArguments->Arg<1, const char*>(); + + // Ignore uses of NSAutoreleasePool after diverging from the recording. + // These are not performed in the middleman because the middleman has its + // own autorelease pool, and because the middleman can process calls from + // multiple threads which will cause these messages to behave differently. + if (!strcmp(message, "alloc") || + !strcmp(message, "drain") || + !strcmp(message, "init") || + !strcmp(message, "release")) { + // Fake a return value in case the caller null checks it. + aArguments->Rval() = 1; + return PreambleResult::Veto; } - return PreambleResult::Veto; + // Other messages will be handled by Middleman_objc_msgSend. + return PreambleResult::Redirect; +} + +static void +Middleman_PerformSelector(MiddlemanCallContext& aCx) +{ + Middleman_CString<2>(aCx); + Middleman_CFTypeArg<3>(aCx); + + // The behavior of performSelector:withObject: varies depending on the + // selector used, so use a whitelist here. + if (aCx.mPhase == MiddlemanCallPhase::ReplayPreface) { + auto str = aCx.mArguments->Arg<2, const char*>(); + if (strcmp(str, "appearanceNamed:")) { + aCx.MarkAsFailed(); + return; + } + } + + Middleman_AutoreleaseCFTypeRval(aCx); +} + +static void +Middleman_DictionaryWithObjects(MiddlemanCallContext& aCx) +{ + Middleman_Buffer<2, 4, const void*>(aCx); + Middleman_Buffer<3, 4, const void*>(aCx); + + if (aCx.AccessPreface()) { + auto objects = aCx.mArguments->Arg<2, const void**>(); + auto keys = aCx.mArguments->Arg<3, const void**>(); + auto count = aCx.mArguments->Arg<4, CFIndex>(); + + for (CFIndex i = 0; i < count; i++) { + Middleman_ObjCInput(aCx, (id*) &objects[i]); + Middleman_ObjCInput(aCx, (id*) &keys[i]); + } + } + + Middleman_AutoreleaseCFTypeRval(aCx); +} + +static void +Middleman_NSStringGetCharacters(MiddlemanCallContext& aCx) +{ + auto string = aCx.mArguments->Arg<0, CFStringRef>(); + auto& buffer = aCx.mArguments->Arg<2, void*>(); + + if (aCx.mPhase == MiddlemanCallPhase::MiddlemanInput) { + size_t len = CFStringGetLength(string); + buffer = aCx.AllocateBytes(len * sizeof(UniChar)); + } + + if (aCx.AccessOutput()) { + size_t len = + (aCx.mPhase == MiddlemanCallPhase::MiddlemanOutput) ? CFStringGetLength(string) : 0; + aCx.ReadOrWriteOutputBytes(&len, sizeof(len)); + if (aCx.mReplayOutputIsOld) { + buffer = aCx.AllocateBytes(len * sizeof(UniChar)); + } + aCx.ReadOrWriteOutputBytes(buffer, len * sizeof(UniChar)); + } +} + +struct ObjCMessageInfo +{ + const char* mMessage; + MiddlemanCallFn mMiddlemanCall; +}; + +// All Objective C messages that can be called in the middleman, and hooks for +// capturing any inputs and outputs other than the object and message. +static ObjCMessageInfo gObjCMiddlemanCallMessages[] = { + { "performSelector:withObject:", Middleman_PerformSelector }, + { "respondsToSelector:", Middleman_CString<2> }, + + // NSArray + { "count" }, + { "objectAtIndex:", Middleman_AutoreleaseCFTypeRval }, + + // NSColor + { "currentControlTint" }, + + // NSDictionary + { "dictionaryWithObjects:forKeys:count:", Middleman_DictionaryWithObjects }, + + // NSFont + { "boldSystemFontOfSize:", Middleman_AutoreleaseCFTypeRval }, + { "controlContentFontOfSize:", Middleman_AutoreleaseCFTypeRval }, + { "familyName", Middleman_AutoreleaseCFTypeRval }, + { "fontDescriptor", Middleman_AutoreleaseCFTypeRval }, + { "menuBarFontOfSize:", Middleman_AutoreleaseCFTypeRval }, + { "pointSize" }, + { "smallSystemFontSize" }, + { "systemFontOfSize:", Middleman_AutoreleaseCFTypeRval }, + { "toolTipsFontOfSize:", Middleman_AutoreleaseCFTypeRval }, + { "userFontOfSize:", Middleman_AutoreleaseCFTypeRval }, + + // NSFontManager + { "availableMembersOfFontFamily:", Middleman_Compose, Middleman_AutoreleaseCFTypeRval> }, + { "sharedFontManager", Middleman_AutoreleaseCFTypeRval }, + + // NSNumber + { "numberWithBool:", Middleman_AutoreleaseCFTypeRval }, + { "unsignedIntValue" }, + + // NSString + { "getCharacters:", Middleman_NSStringGetCharacters }, + { "hasSuffix:", Middleman_CFTypeArg<2> }, + { "isEqualToString:", Middleman_CFTypeArg<2> }, + { "length" }, + { "rangeOfString:options:", Middleman_CFTypeArg<2> }, + { "stringWithCharacters:length:", + Middleman_Compose, Middleman_AutoreleaseCFTypeRval> }, + + // NSWindow + { "coreUIRenderer", Middleman_AutoreleaseCFTypeRval }, + + // UIFontDescriptor + { "symbolicTraits" }, +}; + +static void +Middleman_objc_msgSend(MiddlemanCallContext& aCx) +{ + auto& object = aCx.mArguments->Arg<0, id>(); + auto message = aCx.mArguments->Arg<1, const char*>(); + + for (const ObjCMessageInfo& info : gObjCMiddlemanCallMessages) { + if (!strcmp(info.mMessage, message)) { + if (aCx.AccessPreface()) { + Middleman_ObjCInput(aCx, &object); + } + if (info.mMiddlemanCall && !aCx.mFailed) { + info.mMiddlemanCall(aCx); + } + return; + } + } + + if (aCx.mPhase == MiddlemanCallPhase::ReplayPreface) { + aCx.MarkAsFailed(); + } } /////////////////////////////////////////////////////////////////////////////// // Cocoa redirections /////////////////////////////////////////////////////////////////////////////// +static void +Middleman_CFArrayCreate(MiddlemanCallContext& aCx) +{ + Middleman_Buffer<1, 2, const void*>(aCx); + + if (aCx.AccessPreface()) { + auto values = aCx.mArguments->Arg<1, const void**>(); + auto numValues = aCx.mArguments->Arg<2, CFIndex>(); + auto& callbacks = aCx.mArguments->Arg<3, const CFArrayCallBacks*>(); + + // For now we only support creating arrays with CFType elements. + if (aCx.mPhase == MiddlemanCallPhase::MiddlemanInput) { + callbacks = &kCFTypeArrayCallBacks; + } else { + MOZ_RELEASE_ASSERT(callbacks == &kCFTypeArrayCallBacks); + } + + for (CFIndex i = 0; i < numValues; i++) { + Middleman_ObjCInput(aCx, (id*) &values[i]); + } + } + + Middleman_CreateCFTypeRval(aCx); +} + +static void +Middleman_CFArrayGetValueAtIndex(MiddlemanCallContext& aCx) +{ + Middleman_CFTypeArg<0>(aCx); + + auto array = aCx.mArguments->Arg<0, id>(); + + // We can't probe the array to see what callbacks it uses, so look at where + // it came from to see whether its elements should be treated as CFTypes. + MiddlemanCall* call = LookupMiddlemanCall(array); + bool isCFTypeRval = false; + if (call) { + switch (call->mCallId) { + case CallEvent_CTLineGetGlyphRuns: + case CallEvent_CTFontDescriptorCreateMatchingFontDescriptors: + isCFTypeRval = true; + break; + default: + break; + } + } + + if (isCFTypeRval) { + Middleman_CFTypeRval(aCx); + } +} + static void RR_CFDataGetBytePtr(Stream& aEvents, CallArguments* aArguments, ErrorType* aError) { @@ -1288,6 +1774,55 @@ RR_CFDataGetBytePtr(Stream& aEvents, CallArguments* aArguments, ErrorType* aErro aEvents.RecordOrReplayBytes(rval, len); } +static void +Middleman_CFDataGetBytePtr(MiddlemanCallContext& aCx) +{ + Middleman_CFTypeArg<0>(aCx); + + auto data = aCx.mArguments->Arg<0, CFDataRef>(); + auto& buffer = aCx.mArguments->Rval(); + + if (aCx.AccessOutput()) { + size_t len = (aCx.mPhase == MiddlemanCallPhase::MiddlemanOutput) ? CFDataGetLength(data) : 0; + aCx.ReadOrWriteOutputBytes(&len, sizeof(len)); + if (aCx.mPhase == MiddlemanCallPhase::ReplayOutput) { + buffer = aCx.AllocateBytes(len); + } + aCx.ReadOrWriteOutputBytes(buffer, len); + } +} + +static void +Middleman_CFDictionaryCreate(MiddlemanCallContext& aCx) +{ + Middleman_Buffer<1, 3, const void*>(aCx); + Middleman_Buffer<2, 3, const void*>(aCx); + + if (aCx.AccessPreface()) { + auto keys = aCx.mArguments->Arg<1, const void**>(); + auto values = aCx.mArguments->Arg<2, const void**>(); + auto numValues = aCx.mArguments->Arg<3, CFIndex>(); + auto& keyCallbacks = aCx.mArguments->Arg<4, const CFDictionaryKeyCallBacks*>(); + auto& valueCallbacks = aCx.mArguments->Arg<5, const CFDictionaryValueCallBacks*>(); + + // For now we only support creating dictionaries with CFType keys and values. + if (aCx.mPhase == MiddlemanCallPhase::MiddlemanInput) { + keyCallbacks = &kCFTypeDictionaryKeyCallBacks; + valueCallbacks = &kCFTypeDictionaryValueCallBacks; + } else { + MOZ_RELEASE_ASSERT(keyCallbacks == &kCFTypeDictionaryKeyCallBacks); + MOZ_RELEASE_ASSERT(valueCallbacks == &kCFTypeDictionaryValueCallBacks); + } + + for (CFIndex i = 0; i < numValues; i++) { + Middleman_ObjCInput(aCx, (id*) &keys[i]); + Middleman_ObjCInput(aCx, (id*) &values[i]); + } + } + + Middleman_CreateCFTypeRval(aCx); +} + static void DummyCFNotificationCallback(CFNotificationCenterRef aCenter, void* aObserver, CFStringRef aName, const void* aObject, CFDictionaryRef aUserInfo) @@ -1330,6 +1865,18 @@ CFNumberTypeBytes(CFNumberType aType) } } +static void +Middleman_CFNumberCreate(MiddlemanCallContext& aCx) +{ + if (aCx.AccessPreface()) { + auto numberType = aCx.mArguments->Arg<1, CFNumberType>(); + auto& valuePtr = aCx.mArguments->Arg<2, void*>(); + aCx.ReadOrWritePrefaceBuffer(&valuePtr, CFNumberTypeBytes(numberType)); + } + + Middleman_CreateCFTypeRval(aCx); +} + static void RR_CFNumberGetValue(Stream& aEvents, CallArguments* aArguments, ErrorType* aError) { @@ -1340,6 +1887,23 @@ RR_CFNumberGetValue(Stream& aEvents, CallArguments* aArguments, ErrorType* aErro aEvents.RecordOrReplayBytes(value, CFNumberTypeBytes(type)); } +static void +Middleman_CFNumberGetValue(MiddlemanCallContext& aCx) +{ + Middleman_CFTypeArg<0>(aCx); + + auto& buffer = aCx.mArguments->Arg<2, void*>(); + auto type = aCx.mArguments->Arg<1, CFNumberType>(); + aCx.ReadOrWriteOutputBuffer(&buffer, CFNumberTypeBytes(type)); +} + +static PreambleResult +MiddlemanPreamble_CFRetain(CallArguments* aArguments) +{ + aArguments->Rval() = aArguments->Arg<0, size_t>(); + return PreambleResult::Veto; +} + static PreambleResult Preamble_CFRunLoopSourceCreate(CallArguments* aArguments) { @@ -1380,7 +1944,35 @@ RR_CGBitmapContextCreateWithData(Stream& aEvents, CallArguments* aArguments, Err auto& rval = aArguments->Rval(); MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread()); - gContextData.emplaceBack(rval, data, height * bytesPerRow); + + // When replaying, Middleman_CGBitmapContextCreateWithData will take care of + // updating gContextData with the right context pointer (after being mangled + // in Middleman_SystemOutput). + if (IsRecording()) { + gContextData.emplaceBack(rval, data, height * bytesPerRow); + } +} + +static void +Middleman_CGBitmapContextCreateWithData(MiddlemanCallContext& aCx) +{ + Middleman_CFTypeArg<5>(aCx); + Middleman_StackArgumentData<3 * sizeof(size_t)>(aCx); + Middleman_CreateCFTypeRval(aCx); + + auto& data = aCx.mArguments->Arg<0, void*>(); + auto height = aCx.mArguments->Arg<2, size_t>(); + auto bytesPerRow = aCx.mArguments->Arg<4, size_t>(); + auto rval = aCx.mArguments->Rval(); + + if (aCx.mPhase == MiddlemanCallPhase::MiddlemanInput) { + data = aCx.AllocateBytes(height * bytesPerRow); + } + + if ((aCx.mPhase == MiddlemanCallPhase::ReplayPreface && !HasDivergedFromRecording()) || + (aCx.AccessOutput() && !aCx.mReplayOutputIsOld)) { + gContextData.emplaceBack(rval, data, height * bytesPerRow); + } } template @@ -1391,11 +1983,42 @@ RR_FlushCGContext(Stream& aEvents, CallArguments* aArguments, ErrorType* aError) for (int i = gContextData.length() - 1; i >= 0; i--) { if (context == gContextData[i].mContext) { - RecordReplayBytes(gContextData[i].mData, gContextData[i].mDataSize); + aEvents.RecordOrReplayBytes(gContextData[i].mData, gContextData[i].mDataSize); return; } } - MOZ_CRASH(); + MOZ_CRASH("RR_FlushCGContext"); +} + +template +static void +Middleman_FlushCGContext(MiddlemanCallContext& aCx) +{ + auto context = aCx.mArguments->Arg(); + + // Update the contents of the target buffer in the middleman process to match + // the current contents in the replaying process. + if (aCx.AccessInput()) { + for (int i = gContextData.length() - 1; i >= 0; i--) { + if (context == gContextData[i].mContext) { + aCx.ReadOrWriteInputBytes(gContextData[i].mData, gContextData[i].mDataSize); + return; + } + } + MOZ_CRASH("Middleman_FlushCGContext"); + } + + // After performing the call, the buffer in the replaying process is updated + // to match any data written by the middleman. + if (aCx.AccessOutput()) { + for (int i = gContextData.length() - 1; i >= 0; i--) { + if (context == gContextData[i].mContext) { + aCx.ReadOrWriteOutputBytes(gContextData[i].mData, gContextData[i].mDataSize); + return; + } + } + MOZ_CRASH("Middleman_FlushCGContext"); + } } static PreambleResult @@ -1470,6 +2093,32 @@ RR_CTRunGetElements(Stream& aEvents, CallArguments* aArguments, ErrorType* aErro aEvents.RecordOrReplayBytes(rval, count * sizeof(ElemType)); } +template +static void +Middleman_CTRunGetElements(MiddlemanCallContext& aCx) +{ + Middleman_CFTypeArg<0>(aCx); + + if (aCx.AccessOutput()) { + auto run = aCx.mArguments->Arg<0, CTRunRef>(); + auto& rval = aCx.mArguments->Rval(); + + size_t count = 0; + if (IsMiddleman()) { + count = CTRunGetGlyphCount(run); + if (!rval) { + rval = (ElemType*) aCx.AllocateBytes(count * sizeof(ElemType)); + GetElemsFn(run, CFRangeMake(0, 0), rval); + } + } + aCx.ReadOrWriteOutputBytes(&count, sizeof(count)); + if (IsReplaying()) { + rval = (ElemType*) aCx.AllocateBytes(count * sizeof(ElemType)); + } + aCx.ReadOrWriteOutputBytes(rval, count * sizeof(ElemType)); + } +} + static PreambleResult Preamble_OSSpinLockLock(CallArguments* aArguments) { @@ -1545,15 +2194,6 @@ DirectUnprotectMemory(void* aAddress, size_t aSize, bool aExecutable, MOZ_RELEASE_ASSERT(aIgnoreFailures || rv == 0); } -// From chromium/src/base/eintr_wrapper.h -#define HANDLE_EINTR(x) ({ \ - typeof(x) __eintr_result__; \ - do { \ - __eintr_result__ = x; \ - } while (__eintr_result__ == -1 && errno == EINTR); \ - __eintr_result__;\ -}) - void DirectSeekFile(FileHandle aFd, uint64_t aOffset) { diff --git a/toolkit/recordreplay/ProcessRewind.cpp b/toolkit/recordreplay/ProcessRewind.cpp index 683e2879cde2..91bc29ef1b0b 100644 --- a/toolkit/recordreplay/ProcessRewind.cpp +++ b/toolkit/recordreplay/ProcessRewind.cpp @@ -8,6 +8,7 @@ #include "nsString.h" #include "ipc/ChildInternal.h" +#include "ipc/ParentInternal.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/StaticMutex.h" #include "InfallibleVector.h" @@ -125,6 +126,7 @@ NewCheckpoint(bool aTemporary) { MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread()); MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough()); + MOZ_RELEASE_ASSERT(!HasDivergedFromRecording()); MOZ_RELEASE_ASSERT(IsReplaying() || !aTemporary); navigation::BeforeCheckpoint(); @@ -192,7 +194,13 @@ DivergeFromRecording() Thread* thread = Thread::Current(); MOZ_RELEASE_ASSERT(thread->IsMainThread()); - thread->DivergeFromRecording(); + + if (!thread->HasDivergedFromRecording()) { + // Reset middleman call state whenever we first diverge from the recording. + child::SendResetMiddlemanCalls(); + + thread->DivergeFromRecording(); + } gUnhandledDivergeAllowed = true; } @@ -218,9 +226,18 @@ DisallowUnhandledDivergeFromRecording() void EnsureNotDivergedFromRecording() { + // If we have diverged from the recording and encounter an operation we can't + // handle, rewind to the last checkpoint. AssertEventsAreNotPassedThrough(); if (HasDivergedFromRecording()) { MOZ_RELEASE_ASSERT(gUnhandledDivergeAllowed); + + // Crash instead of rewinding in the painting stress mode, for finding + // areas where middleman calls do not cover all painting logic. + if (parent::InRepaintStressMode()) { + MOZ_CRASH("Recording divergence in repaint stress mode"); + } + PrintSpew("Unhandled recording divergence, restoring checkpoint...\n"); RestoreCheckpointAndResume(gRewindInfo->mSavedCheckpoints.back().mCheckpoint); Unreachable(); diff --git a/toolkit/recordreplay/Thread.cpp b/toolkit/recordreplay/Thread.cpp index 7fbdcaf7706a..21f9aadcd85f 100644 --- a/toolkit/recordreplay/Thread.cpp +++ b/toolkit/recordreplay/Thread.cpp @@ -234,10 +234,12 @@ Thread::SpawnThread(Thread* aThread) /* static */ NativeThreadId Thread::StartThread(Callback aStart, void* aArgument, bool aNeedsJoin) { - EnsureNotDivergedFromRecording(); - Thread* thread = Thread::Current(); - MOZ_RELEASE_ASSERT(thread->CanAccessRecording()); + RecordingEventSection res(thread); + if (!res.CanAccessEvents()) { + EnsureNotDivergedFromRecording(); + Unreachable(); + } MonitorAutoLock lock(*gMonitor); @@ -387,7 +389,15 @@ Thread::WaitForIdleThreads() Thread* thread = GetById(i); if (!thread->mIdle) { done = false; - if (thread->mUnrecordedWaitCallback && !thread->mUnrecordedWaitNotified) { + + // Check if there is a callback we can invoke to get this thread to + // make progress. The mUnrecordedWaitOnlyWhenDiverged flag is used to + // avoid perturbing the behavior of threads that may or may not be + // waiting on an unrecorded resource, depending on whether they have + // diverged from the recording yet. + if (thread->mUnrecordedWaitCallback && !thread->mUnrecordedWaitNotified && + (!thread->mUnrecordedWaitOnlyWhenDiverged || + thread->WillDivergeFromRecordingSoon())) { // Set this flag before releasing the idle lock. Otherwise it's // possible the thread could call NotifyUnrecordedWait while we // aren't holding the lock, and we would set the flag afterwards @@ -395,10 +405,11 @@ Thread::WaitForIdleThreads() thread->mUnrecordedWaitNotified = true; // Release the idle lock here to avoid any risk of deadlock. + std::function callback = thread->mUnrecordedWaitCallback; { MonitorAutoUnlock unlock(*gMonitor); AutoPassThroughThreadEvents pt; - thread->mUnrecordedWaitCallback(); + callback(); } // Releasing the global lock means that we need to start over @@ -436,7 +447,7 @@ Thread::ResumeIdleThreads() } void -Thread::NotifyUnrecordedWait(const std::function& aCallback) +Thread::NotifyUnrecordedWait(const std::function& aCallback, bool aOnlyWhenDiverged) { MonitorAutoLock lock(*gMonitor); if (mUnrecordedWaitCallback) { @@ -449,6 +460,7 @@ Thread::NotifyUnrecordedWait(const std::function& aCallback) } mUnrecordedWaitCallback = aCallback; + mUnrecordedWaitOnlyWhenDiverged = aOnlyWhenDiverged; // The main thread might be able to make progress now by calling the routine // if it is waiting for idle replay threads. @@ -470,9 +482,10 @@ Thread::MaybeWaitForCheckpointSave() extern "C" { MOZ_EXPORT void -RecordReplayInterface_NotifyUnrecordedWait(const std::function& aCallback) +RecordReplayInterface_NotifyUnrecordedWait(const std::function& aCallback, + bool aOnlyWhenDiverged) { - Thread::Current()->NotifyUnrecordedWait(aCallback); + Thread::Current()->NotifyUnrecordedWait(aCallback, aOnlyWhenDiverged); } MOZ_EXPORT void diff --git a/toolkit/recordreplay/Thread.h b/toolkit/recordreplay/Thread.h index d65a09abf57b..569a18036483 100644 --- a/toolkit/recordreplay/Thread.h +++ b/toolkit/recordreplay/Thread.h @@ -98,6 +98,10 @@ private: // recorded events cannot be accessed. bool mDivergedFromRecording; + // Whether this thread should diverge from the recording at the next + // opportunity. This can be set from any thread. + Atomic mShouldDivergeFromRecording; + // Start routine and argument which the thread is currently executing. This // is cleared after the routine finishes and another start routine may be // assigned to the thread. mNeedsJoin specifies whether the thread must be @@ -130,6 +134,7 @@ private: // and whether the callback has been invoked yet while the main thread is // waiting for threads to become idle. Protected by the thread monitor. std::function mUnrecordedWaitCallback; + bool mUnrecordedWaitOnlyWhenDiverged; bool mUnrecordedWaitNotified; public: @@ -179,6 +184,26 @@ public: return mDivergedFromRecording; } + // Mark this thread as needing to diverge from the recording soon, and wake + // it up in case it can make progress now. The mShouldDivergeFromRecording + // flag is separate from mDivergedFromRecording so that the thread can only + // begin diverging from the recording at calls to MaybeDivergeFromRecording. + void SetShouldDivergeFromRecording() { + MOZ_RELEASE_ASSERT(CurrentIsMainThread()); + mShouldDivergeFromRecording = true; + Notify(mId); + } + bool WillDivergeFromRecordingSoon() { + MOZ_RELEASE_ASSERT(CurrentIsMainThread()); + return mShouldDivergeFromRecording; + } + bool MaybeDivergeFromRecording() { + if (mShouldDivergeFromRecording) { + mDivergedFromRecording = true; + } + return mDivergedFromRecording; + } + // Return whether this thread may read or write to its recorded event stream. bool CanAccessRecording() const { return !PassThroughEvents() && !AreEventsDisallowed() && !HasDivergedFromRecording(); @@ -253,7 +278,8 @@ public: static void WaitForeverNoIdle(); // See RecordReplay.h. - void NotifyUnrecordedWait(const std::function& aCallback); + void NotifyUnrecordedWait(const std::function& aCallback, + bool aOnlyWhenDiverged); static void MaybeWaitForCheckpointSave(); // Wait for all other threads to enter the idle state necessary for saving @@ -290,6 +316,60 @@ public: } }; +// Mark a region of code where a thread's event stream can be accessed. +// This class has several properties: +// +// - When recording, all writes to the thread's event stream occur atomically +// within the class: the end of the stream cannot be hit at an intermediate +// point. +// +// - When replaying, this checks for the end of the stream, and blocks the +// thread if necessary. +// +// - When replaying, this is a point where the thread can begin diverging from +// the recording. Checks for divergence should occur after the constructor +// finishes. +class MOZ_RAII RecordingEventSection +{ + Thread* mThread; + +public: + explicit RecordingEventSection(Thread* aThread) + : mThread(aThread) + { + if (!aThread || !aThread->CanAccessRecording()) { + return; + } + if (IsRecording()) { + MOZ_RELEASE_ASSERT(!aThread->Events().mInRecordingEventSection); + aThread->Events().mFile->mStreamLock.ReadLock(); + aThread->Events().mInRecordingEventSection = true; + } else { + while (!aThread->MaybeDivergeFromRecording() && aThread->Events().AtEnd()) { + HitEndOfRecording(); + } + } + } + + ~RecordingEventSection() { + if (!mThread || !mThread->CanAccessRecording()) { + return; + } + if (IsRecording()) { + mThread->Events().mFile->mStreamLock.ReadUnlock(); + mThread->Events().mInRecordingEventSection = false; + } + } + + bool CanAccessEvents() { + if (!mThread || mThread->PassThroughEvents() || mThread->HasDivergedFromRecording()) { + return false; + } + MOZ_RELEASE_ASSERT(mThread->CanAccessRecording()); + return true; + } +}; + } // namespace recordreplay } // namespace mozilla diff --git a/toolkit/recordreplay/Trigger.cpp b/toolkit/recordreplay/Trigger.cpp index c9b298f57dd7..0ab4032587cb 100644 --- a/toolkit/recordreplay/Trigger.cpp +++ b/toolkit/recordreplay/Trigger.cpp @@ -59,6 +59,7 @@ MOZ_EXPORT void RecordReplayInterface_RegisterTrigger(void* aObj, const std::function& aCallback) { MOZ_RELEASE_ASSERT(aObj); + MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough()); Thread* thread = Thread::Current(); if (thread->HasDivergedFromRecording()) { @@ -84,6 +85,9 @@ RecordReplayInterface_RegisterTrigger(void* aObj, const std::function& a } } + RecordingEventSection res(thread); + MOZ_RELEASE_ASSERT(res.CanAccessEvents()); + thread->Events().RecordOrReplayThreadEvent(ThreadEvent::RegisterTrigger); thread->Events().CheckInput(id); } @@ -157,7 +161,10 @@ MOZ_EXPORT void RecordReplayInterface_ExecuteTriggers() { Thread* thread = Thread::Current(); - MOZ_RELEASE_ASSERT(thread->CanAccessRecording()); + RecordingEventSection res(thread); + if (!res.CanAccessEvents()) { + return; + } if (IsRecording()) { // Invoke the callbacks for any triggers waiting for execution, including diff --git a/toolkit/recordreplay/ipc/Channel.cpp b/toolkit/recordreplay/ipc/Channel.cpp index 10dc588bb1e9..b258a74cce9d 100644 --- a/toolkit/recordreplay/ipc/Channel.cpp +++ b/toolkit/recordreplay/ipc/Channel.cpp @@ -12,6 +12,7 @@ #include "MainThreadUtils.h" #include "nsXULAppAPI.h" +#include "base/eintr_wrapper.h" #include "base/process_util.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/ipc/FileDescriptor.h" @@ -65,6 +66,7 @@ Channel::Channel(size_t aId, bool aMiddlemanRecording, const MessageHandler& aHa , mInitialized(false) , mConnectionFd(0) , mFd(0) + , mMessageBuffer(nullptr) , mMessageBytes(0) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); @@ -148,7 +150,9 @@ Channel::ThreadMain(void* aChannelArg) void Channel::SendMessage(const Message& aMsg) { - MOZ_RELEASE_ASSERT(NS_IsMainThread() || aMsg.mType == MessageType::FatalError); + MOZ_RELEASE_ASSERT(NS_IsMainThread() || + aMsg.mType == MessageType::FatalError || + aMsg.mType == MessageType::MiddlemanCallRequest); // Block until the channel is initialized. if (!mInitialized) { @@ -179,27 +183,29 @@ Channel::SendMessage(const Message& aMsg) Message* Channel::WaitForMessage() { - if (!mMessageBuffer.length()) { - mMessageBuffer.appendN(0, PageSize); + if (!mMessageBuffer) { + mMessageBuffer = (MessageBuffer*) AllocateMemory(sizeof(MessageBuffer), MemoryKind::Generic); + mMessageBuffer->appendN(0, PageSize); } size_t messageSize = 0; while (true) { if (mMessageBytes >= sizeof(Message)) { - Message* msg = (Message*) mMessageBuffer.begin(); + Message* msg = (Message*) mMessageBuffer->begin(); messageSize = msg->mSize; + MOZ_RELEASE_ASSERT(messageSize >= sizeof(Message)); if (mMessageBytes >= messageSize) { break; } } // Make sure the buffer is large enough for the entire incoming message. - if (messageSize > mMessageBuffer.length()) { - mMessageBuffer.appendN(0, messageSize - mMessageBuffer.length()); + if (messageSize > mMessageBuffer->length()) { + mMessageBuffer->appendN(0, messageSize - mMessageBuffer->length()); } - ssize_t nbytes = HANDLE_EINTR(recv(mFd, &mMessageBuffer[mMessageBytes], - mMessageBuffer.length() - mMessageBytes, 0)); + ssize_t nbytes = HANDLE_EINTR(recv(mFd, &mMessageBuffer->begin()[mMessageBytes], + mMessageBuffer->length() - mMessageBytes, 0)); if (nbytes < 0) { MOZ_RELEASE_ASSERT(errno == EAGAIN); continue; @@ -215,12 +221,12 @@ Channel::WaitForMessage() mMessageBytes += nbytes; } - Message* res = ((Message*)mMessageBuffer.begin())->Clone(); + Message* res = ((Message*)mMessageBuffer->begin())->Clone(); // Remove the message we just received from the incoming buffer. size_t remaining = mMessageBytes - messageSize; if (remaining) { - memmove(mMessageBuffer.begin(), &mMessageBuffer[messageSize], remaining); + memmove(mMessageBuffer->begin(), &mMessageBuffer->begin()[messageSize], remaining); } mMessageBytes = remaining; diff --git a/toolkit/recordreplay/ipc/Channel.h b/toolkit/recordreplay/ipc/Channel.h index daf01420ebbe..08c6079cd0bd 100644 --- a/toolkit/recordreplay/ipc/Channel.h +++ b/toolkit/recordreplay/ipc/Channel.h @@ -98,6 +98,9 @@ namespace recordreplay { /* Set whether to save a particular checkpoint. */ \ _Macro(SetSaveCheckpoint) \ \ + /* Respond to a MiddlemanCallRequest message. */ \ + _Macro(MiddlemanCallResponse) \ + \ /* Messages sent from the child process to the middleman. */ \ \ /* Sent in response to a FlushRecording, telling the middleman that the flush */ \ @@ -119,6 +122,13 @@ namespace recordreplay { /* Send a response to a DebuggerRequest message. */ \ _Macro(DebuggerResponse) \ \ + /* Call a system function from the middleman process which the child has */ \ + /* encountered after diverging from the recording. */ \ + _Macro(MiddlemanCallRequest) \ + \ + /* Reset all information generated by previous MiddlemanCallRequest messages. */ \ + _Macro(ResetMiddlemanCalls) \ + \ /* Notify that the 'AlwaysMarkMajorCheckpoints' directive was invoked. */ \ _Macro(AlwaysMarkMajorCheckpoints) @@ -164,6 +174,7 @@ public: bool CanBeSentWhileUnpaused() const { return mType == MessageType::CreateCheckpoint || mType == MessageType::SetDebuggerRunsInMiddleman + || mType == MessageType::MiddlemanCallResponse || mType == MessageType::Terminate; } @@ -367,11 +378,15 @@ static const gfx::SurfaceFormat gSurfaceFormat = gfx::SurfaceFormat::R8G8B8X8; struct PaintMessage : public Message { + // Checkpoint whose state is being painted. + uint32_t mCheckpointId; + uint32_t mWidth; uint32_t mHeight; - PaintMessage(uint32_t aWidth, uint32_t aHeight) + PaintMessage(uint32_t aCheckpointId, uint32_t aWidth, uint32_t aHeight) : Message(MessageType::Paint, sizeof(*this)) + , mCheckpointId(aCheckpointId) , mWidth(aWidth) , mHeight(aHeight) {} @@ -418,6 +433,29 @@ struct HitBreakpointMessage : public Message typedef EmptyMessage AlwaysMarkMajorCheckpointsMessage; +template +struct BinaryMessage : public Message +{ + explicit BinaryMessage(uint32_t aSize) + : Message(Type, aSize) + {} + + const char* BinaryData() const { return Data, char>(); } + size_t BinaryDataSize() const { return DataSize, char>(); } + + static BinaryMessage* + New(const char* aData, size_t aDataSize) { + BinaryMessage* res = NewWithData, char>(aDataSize); + MOZ_RELEASE_ASSERT(res->BinaryDataSize() == aDataSize); + PodCopy(res->Data, char>(), aData, aDataSize); + return res; + } +}; + +typedef BinaryMessage MiddlemanCallRequestMessage; +typedef BinaryMessage MiddlemanCallResponseMessage; +typedef EmptyMessage ResetMiddlemanCallsMessage; + class Channel { public: @@ -445,7 +483,8 @@ private: Monitor mMonitor; // Buffer for message data received from the other side of the channel. - InfallibleVector> mMessageBuffer; + typedef InfallibleVector> MessageBuffer; + MessageBuffer* mMessageBuffer; // The number of bytes of data already in the message buffer. size_t mMessageBytes; diff --git a/toolkit/recordreplay/ipc/ChildIPC.cpp b/toolkit/recordreplay/ipc/ChildIPC.cpp index 7e6381f8f450..b1c3e73b740e 100644 --- a/toolkit/recordreplay/ipc/ChildIPC.cpp +++ b/toolkit/recordreplay/ipc/ChildIPC.cpp @@ -16,7 +16,10 @@ #include "ipc/Channel.h" #include "mac/handler/exception_handler.h" #include "mozilla/dom/ContentChild.h" +#include "mozilla/layers/CompositorBridgeChild.h" #include "mozilla/layers/ImageDataSerializer.h" +#include "mozilla/layers/ImageDataSerializer.h" +#include "mozilla/layers/LayerTransactionChild.h" #include "mozilla/Sprintf.h" #include "mozilla/VsyncDispatcher.h" @@ -64,6 +67,9 @@ static IntroductionMessage* gIntroductionMessage; // When recording, whether developer tools server code runs in the middleman. static bool gDebuggerRunsInMiddleman; +// Any response received to the last MiddlemanCallRequest message. +static MiddlemanCallResponseMessage* gCallResponseMessage; + // Processing routine for incoming channel messages. static void ChannelMessageHandler(Message* aMsg) @@ -159,6 +165,14 @@ ChannelMessageHandler(Message* aMsg) }); break; } + case MessageType::MiddlemanCallResponse: { + MonitorAutoLock lock(*gMonitor); + MOZ_RELEASE_ASSERT(!gCallResponseMessage); + gCallResponseMessage = (MiddlemanCallResponseMessage*) aMsg; + aMsg = nullptr; // Avoid freeing the message below. + gMonitor->NotifyAll(); + break; + } default: MOZ_CRASH(); } @@ -187,6 +201,7 @@ ListenForCheckpointThreadMain(void*) } } +// Shared memory block for graphics data. void* gGraphicsShmem; void @@ -325,9 +340,11 @@ DebuggerRunsInMiddleman() } void -MaybeCreateInitialCheckpoint() +CreateCheckpoint() { - NewCheckpoint(/* aTemporary = */ false); + if (!HasDivergedFromRecording()) { + NewCheckpoint(/* aTemporary = */ false); + } } void @@ -398,7 +415,7 @@ SetVsyncObserver(VsyncObserver* aObserver) gVsyncObserver = aObserver; } -void +static void NotifyVsyncObserver() { if (gVsyncObserver) { @@ -406,30 +423,68 @@ NotifyVsyncObserver() } } +// Whether an update has been sent to the compositor for a normal paint, and we +// haven't reached PaintFromMainThread yet. This is used to preserve the +// invariant that there can be at most one paint performed between two +// checkpoints, other than repaints triggered by the debugger. +static bool gHasActivePaint; + +bool +OnVsync() +{ + // In the repainting stress mode, we create a new checkpoint on every vsync + // message received from the UI process. When we notify the parent about the + // new checkpoint it will trigger a repaint to make sure that all layout and + // painting activity can occur when diverged from the recording. + if (parent::InRepaintStressMode()) { + CreateCheckpoint(); + } + + // After a paint starts, ignore incoming vsyncs until the paint completes. + return !gHasActivePaint; +} + /////////////////////////////////////////////////////////////////////////////// // Painting /////////////////////////////////////////////////////////////////////////////// -// Graphics memory is only written on the compositor thread and read on the -// main thread and by the middleman. The gPendingPaint flag is used to -// synchronize access, so that data is not read until the paint has completed. -static Maybe gPaintMessage; -static bool gPendingPaint; - -// Target buffer for the draw target created by the child process widget. +// Target buffer for the draw target created by the child process widget, which +// the compositor thread writes to. static void* gDrawTargetBuffer; static size_t gDrawTargetBufferSize; +// Dimensions of the last paint which the compositor performed. +static size_t gPaintWidth, gPaintHeight; + +// How many updates have been sent to the compositor thread and haven't been +// processed yet. This can briefly become negative if the main thread sends an +// update and the compositor processes it before the main thread reaches +// NotifyPaintStart. Outside of this window, the compositor can only write to +// gDrawTargetBuffer or update gPaintWidth/gPaintHeight if this is non-zero. +static Atomic gNumPendingPaints; + +// ID of the compositor thread. +static Atomic gCompositorThreadId; + already_AddRefed DrawTargetForRemoteDrawing(LayoutDeviceIntSize aSize) { MOZ_RELEASE_ASSERT(!NS_IsMainThread()); + // Keep track of the compositor thread ID. + size_t threadId = Thread::Current()->Id(); + if (gCompositorThreadId) { + MOZ_RELEASE_ASSERT(threadId == gCompositorThreadId); + } else { + gCompositorThreadId = threadId; + } + if (aSize.IsEmpty()) { return nullptr; } - gPaintMessage = Some(PaintMessage(aSize.width, aSize.height)); + gPaintWidth = aSize.width; + gPaintHeight = aSize.height; gfx::IntSize size(aSize.width, aSize.height); size_t bufferSize = layers::ImageDataSerializer::ComputeRGBBufferSize(size, gSurfaceFormat); @@ -458,42 +513,135 @@ NotifyPaintStart() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); - NewCheckpoint(/* aTemporary = */ false); + // A new paint cannot be triggered until the last one finishes and has been + // sent to the middleman. + MOZ_RELEASE_ASSERT(HasDivergedFromRecording() || !gHasActivePaint); - gPendingPaint = true; + gNumPendingPaints++; + gHasActivePaint = true; + + CreateCheckpoint(); } -void -WaitForPaintToComplete() +static void +PaintFromMainThread() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); - MonitorAutoLock lock(*gMonitor); - while (gPendingPaint) { - gMonitor->Wait(); - } - if (IsActiveChild() && gPaintMessage.isSome()) { + // There cannot not be any other in flight paints. + MOZ_RELEASE_ASSERT(!gNumPendingPaints); + + // Clear the active flag now that we have completed the paint. + MOZ_RELEASE_ASSERT(gHasActivePaint); + gHasActivePaint = false; + + if (IsActiveChild() && gDrawTargetBuffer) { memcpy(gGraphicsShmem, gDrawTargetBuffer, gDrawTargetBufferSize); - gChannel->SendMessage(gPaintMessage.ref()); + gChannel->SendMessage(PaintMessage(navigation::LastNormalCheckpoint(), + gPaintWidth, gPaintHeight)); } } void NotifyPaintComplete() { - MOZ_RELEASE_ASSERT(!NS_IsMainThread()); + MOZ_RELEASE_ASSERT(Thread::Current()->Id() == gCompositorThreadId); - MonitorAutoLock lock(*gMonitor); - MOZ_RELEASE_ASSERT(gPendingPaint); - gPendingPaint = false; - gMonitor->Notify(); + // Notify the main thread in case it is waiting for this paint to complete. + { + MonitorAutoLock lock(*gMonitor); + if (--gNumPendingPaints == 0) { + gMonitor->Notify(); + } + } + + // Notify the middleman about the completed paint from the main thread. + NS_DispatchToMainThread(NewRunnableFunction("PaintFromMainThread", PaintFromMainThread)); +} + +void +Repaint(size_t* aWidth, size_t* aHeight) +{ + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + MOZ_RELEASE_ASSERT(HasDivergedFromRecording()); + + // Don't try to repaint if the first normal paint hasn't occurred yet. + if (!gCompositorThreadId) { + *aWidth = 0; + *aHeight = 0; + return; + } + + // Ignore the request to repaint if the compositor thread has already + // diverged from the recording. In this case we have already done a repaint + // and the last graphics we sent will still be correct. + Thread* compositorThread = Thread::GetById(gCompositorThreadId); + if (!compositorThread->WillDivergeFromRecordingSoon()) { + // Create an artifical vsync to see if graphics have changed since the last + // paint and a new paint is needed. + NotifyVsyncObserver(); + + if (gNumPendingPaints) { + // Allow the compositor to diverge from the recording so it can perform + // any paint we just triggered, or finish any in flight paint that that + // existed at the point we are paused at. + Thread::GetById(gCompositorThreadId)->SetShouldDivergeFromRecording(); + + // Wait for the compositor to finish all in flight paints, including any + // one we just triggered. + MonitorAutoLock lock(*gMonitor); + while (gNumPendingPaints) { + gMonitor->Wait(); + } + } + } + + if (gDrawTargetBuffer) { + memcpy(gGraphicsShmem, gDrawTargetBuffer, gDrawTargetBufferSize); + *aWidth = gPaintWidth; + *aHeight = gPaintHeight; + } else { + *aWidth = 0; + *aHeight = 0; + } +} + +static bool +CompositorCanPerformMiddlemanCalls() +{ + // After repainting finishes the compositor is not allowed to send call + // requests to the middleman anymore. + return !!gNumPendingPaints; +} + +bool +SuppressMessageAfterDiverge(IPC::Message* aMsg) +{ + MOZ_RELEASE_ASSERT(HasDivergedFromRecording()); + + // Only messages necessary for compositing can be sent after the sending + // thread has diverged from the recording. Sending other messages can risk + // deadlocking when a necessary lock is held by an idle thread (we probably + // need a more robust way to deal with this problem). + + IPC::Message::msgid_t type = aMsg->type(); + if (type >= layers::PLayerTransaction::PLayerTransactionStart && + type <= layers::PLayerTransaction::PLayerTransactionEnd) { + return false; + } + + if (type == layers::PCompositorBridge::Msg_PTextureConstructor__ID) { + return false; + } + + return true; } /////////////////////////////////////////////////////////////////////////////// // Checkpoint Messages /////////////////////////////////////////////////////////////////////////////// -// When recording, the time when the last HitCheckpoint message was sent. +// The time when the last HitCheckpoint message was sent. static double gLastCheckpointTime; // When recording and we are idle, the time when we became idle. @@ -534,7 +682,7 @@ HitCheckpoint(size_t aId, bool aRecordingEndpoint) } /////////////////////////////////////////////////////////////////////////////// -// Debugger Messages +// Message Helpers /////////////////////////////////////////////////////////////////////////////// void @@ -558,6 +706,52 @@ HitBreakpoint(bool aRecordingEndpoint, const uint32_t* aBreakpoints, size_t aNum }); } +bool +SendMiddlemanCallRequest(const char* aInputData, size_t aInputSize, + InfallibleVector* aOutputData) +{ + Thread* thread = Thread::Current(); + + // Middleman calls can only be made from the main and compositor threads. + // These two threads cannot simultaneously send call requests or other + // messages, as doing so will race both here and in Channel::SendMessage. + // CompositorCanPerformMiddlemanCalls() ensures that the main thread is + // not actively sending messages at times when the compositor performs + // middleman calls. + MOZ_RELEASE_ASSERT(thread->IsMainThread() || thread->Id() == gCompositorThreadId); + + if (thread->Id() == gCompositorThreadId && !CompositorCanPerformMiddlemanCalls()) { + return false; + } + + MonitorAutoLock lock(*gMonitor); + + MOZ_RELEASE_ASSERT(!gCallResponseMessage); + + MiddlemanCallRequestMessage* msg = MiddlemanCallRequestMessage::New(aInputData, aInputSize); + gChannel->SendMessage(*msg); + free(msg); + + while (!gCallResponseMessage) { + gMonitor->Wait(); + } + + aOutputData->append(gCallResponseMessage->BinaryData(), gCallResponseMessage->BinaryDataSize()); + + free(gCallResponseMessage); + gCallResponseMessage = nullptr; + + gMonitor->Notify(); + return true; +} + +void +SendResetMiddlemanCalls() +{ + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + gChannel->SendMessage(ResetMiddlemanCallsMessage()); +} + } // namespace child } // namespace recordreplay } // namespace mozilla diff --git a/toolkit/recordreplay/ipc/ChildIPC.h b/toolkit/recordreplay/ipc/ChildIPC.h index 4b0242ff5087..a09c51161e1c 100644 --- a/toolkit/recordreplay/ipc/ChildIPC.h +++ b/toolkit/recordreplay/ipc/ChildIPC.h @@ -11,6 +11,8 @@ #include "mozilla/gfx/2D.h" #include "Units.h" +namespace IPC { class Message; } + namespace mozilla { class VsyncObserver; @@ -29,42 +31,35 @@ void InitRecordingOrReplayingProcess(int* aArgc, char*** aArgv); base::ProcessId MiddlemanProcessId(); base::ProcessId ParentProcessId(); -// Create a normal checkpoint, if no such checkpoint has been created yet. -void MaybeCreateInitialCheckpoint(); +// Create a normal checkpoint, if execution has not diverged from the recording. +void CreateCheckpoint(); /////////////////////////////////////////////////////////////////////////////// // Painting Coordination /////////////////////////////////////////////////////////////////////////////// -// In child processes, paints do not occur in response to vsyncs from the UI -// process: when a page is updating rapidly these events occur sporadically and -// cause the tab's graphics to not accurately reflect the tab's state at that -// point in time. When viewing a normal tab this is no problem because the tab -// will be painted with the correct graphics momentarily, but when the tab can -// be rewound and paused this behavior is visible. -// -// This API is used to trigger artificial vsyncs whenever the page is updated. -// SetVsyncObserver is used to tell the child code about any singleton vsync -// observer that currently exists, and NotifyVsyncObserver is used to trigger -// a vsync on that observer at predictable points, e.g. the top of the main -// thread's event loop. +// Tell the child code about any singleton vsync observer that currently +// exists. This is used to trigger artifical vsyncs that paint the current +// graphics when paused. void SetVsyncObserver(VsyncObserver* aObserver); -void NotifyVsyncObserver(); -// Similarly to the vsync handling above, in order to ensure that the tab's -// graphics accurately reflect its state, we want to perform paints -// synchronously after a vsync has occurred. When a paint is about to happen, -// the main thread calls NotifyPaintStart, and after the compositor thread has -// been informed about the update the main thread calls WaitForPaintToComplete -// to block until the compositor thread has finished painting and called -// NotifyPaintComplete. +// Called before processing incoming vsyncs from the UI process. Returns false +// if the vsync should be ignored. +bool OnVsync(); + +// Tell the child code about any ongoing painting activity. When a paint is +// about to happen, the main thread calls NotifyPaintStart, and when the +// compositor thread finishes the paint it calls NotifyPaintComplete. void NotifyPaintStart(); -void WaitForPaintToComplete(); void NotifyPaintComplete(); // Get a draw target which the compositor thread can paint to. already_AddRefed DrawTargetForRemoteDrawing(LayoutDeviceIntSize aSize); +// Called to ignore IPDL messages sent after diverging from the recording, +// except for those needed for compositing. +bool SuppressMessageAfterDiverge(IPC::Message* aMsg); + } // namespace child } // namespace recordreplay } // namespace mozilla diff --git a/toolkit/recordreplay/ipc/ChildInternal.h b/toolkit/recordreplay/ipc/ChildInternal.h index 77ab177c350c..b6ec9ccb8400 100644 --- a/toolkit/recordreplay/ipc/ChildInternal.h +++ b/toolkit/recordreplay/ipc/ChildInternal.h @@ -7,8 +7,10 @@ #ifndef mozilla_recordreplay_ChildInternal_h #define mozilla_recordreplay_ChildInternal_h +#include "Channel.h" #include "ChildIPC.h" #include "JSControl.h" +#include "MiddlemanCall.h" #include "Monitor.h" namespace mozilla { @@ -62,6 +64,10 @@ js::ExecutionPoint CurrentExecutionPoint(const js::BreakpointPosition& aPosition // executing into an ExecutionPoint. js::ExecutionPoint TimeWarpTargetExecutionPoint(ProgressCounter aTarget); +// Synchronously paint the current contents into the graphics shared memory +// object, returning the size of the painted area via aWidth/aHeight. +void Repaint(size_t* aWidth, size_t* aHeight); + // Called when running forward, immediately before hitting a normal or // temporary checkpoint. void BeforeCheckpoint(); @@ -70,6 +76,9 @@ void BeforeCheckpoint(); // when running forward or immediately after rewinding. void AfterCheckpoint(const CheckpointId& aCheckpoint); +// Get the ID of the last normal checkpoint. +size_t LastNormalCheckpoint(); + } // namespace navigation namespace child { @@ -116,6 +125,11 @@ void EndIdleTime(); // Whether the middleman runs developer tools server code. bool DebuggerRunsInMiddleman(); +// Send messages operating on middleman calls. +bool SendMiddlemanCallRequest(const char* aInputData, size_t aInputSize, + InfallibleVector* aOutputData); +void SendResetMiddlemanCalls(); + } // namespace child } // namespace recordreplay diff --git a/toolkit/recordreplay/ipc/ChildNavigation.cpp b/toolkit/recordreplay/ipc/ChildNavigation.cpp index 6a6977c6aeb2..97dd6d586589 100644 --- a/toolkit/recordreplay/ipc/ChildNavigation.cpp +++ b/toolkit/recordreplay/ipc/ChildNavigation.cpp @@ -698,13 +698,15 @@ PausedPhase::MaybeDivergeFromRecording() return false; } + size_t index = mRequestIndex; + if (!EnsureTemporaryCheckpoint()) { // One of the premature exit cases was hit in EnsureTemporaryCheckpoint. // Don't allow any operations that can diverge from the recording. return false; } - if (mRequests[mRequestIndex].mUnhandledDivergence) { + if (mRequests[index].mUnhandledDivergence) { // We tried to process this request before and had an unhandled divergence. // Disallow the request handler from doing anything that might diverge from // the recording. @@ -1109,6 +1111,12 @@ AfterCheckpoint(const CheckpointId& aCheckpoint) gNavigation->AfterCheckpoint(aCheckpoint); } +size_t +LastNormalCheckpoint() +{ + return gNavigation->LastCheckpoint().mNormal; +} + void DebuggerRequest(js::CharBuffer* aRequestBuffer) { @@ -1190,9 +1198,13 @@ extern "C" { MOZ_EXPORT ProgressCounter RecordReplayInterface_NewTimeWarpTarget() { + if (AreThreadEventsDisallowed()) { + return 0; + } + // NewTimeWarpTarget() must be called at consistent points between recording // and replaying. - recordreplay::RecordReplayAssert("NewTimeWarpTarget"); + RecordReplayAssert("NewTimeWarpTarget"); if (!gNavigation) { return 0; diff --git a/toolkit/recordreplay/ipc/ChildProcess.cpp b/toolkit/recordreplay/ipc/ChildProcess.cpp index 598603527de8..c8e72f5f48c9 100644 --- a/toolkit/recordreplay/ipc/ChildProcess.cpp +++ b/toolkit/recordreplay/ipc/ChildProcess.cpp @@ -295,6 +295,7 @@ ChildProcessInfo::SendMessage(const Message& aMsg) case MessageType::RunToPoint: case MessageType::DebuggerRequest: case MessageType::SetBreakpoint: + case MessageType::MiddlemanCallResponse: mMessages.emplaceBack(aMsg.Clone()); break; default: @@ -407,8 +408,11 @@ ChildProcessInfo::OnIncomingRecoveryMessage(const Message& aMsg) } case MessageType::HitBreakpoint: case MessageType::DebuggerResponse: + case MessageType::MiddlemanCallRequest: SendNextRecoveryMessage(); break; + case MessageType::ResetMiddlemanCalls: + break; default: MOZ_CRASH("Unexpected message during recovery"); } diff --git a/toolkit/recordreplay/ipc/DisabledIPC.cpp b/toolkit/recordreplay/ipc/DisabledIPC.cpp index eb591ec2ef8b..46685fc1b64e 100644 --- a/toolkit/recordreplay/ipc/DisabledIPC.cpp +++ b/toolkit/recordreplay/ipc/DisabledIPC.cpp @@ -40,7 +40,7 @@ ParentProcessId() MOZ_CRASH(); } -void MaybeCreateInitialCheckpoint() +void CreateCheckpoint() { MOZ_CRASH(); } @@ -51,8 +51,8 @@ SetVsyncObserver(VsyncObserver* aObserver) MOZ_CRASH(); } -void -NotifyVsyncObserver() +bool +OnVsync() { MOZ_CRASH(); } @@ -69,14 +69,14 @@ NotifyPaintComplete() MOZ_CRASH(); } -void -WaitForPaintToComplete() +already_AddRefed +DrawTargetForRemoteDrawing(LayoutDeviceIntSize aSize) { MOZ_CRASH(); } -already_AddRefed -DrawTargetForRemoteDrawing(LayoutDeviceIntSize aSize) +bool +SuppressMessageAfterDiverge(IPC::Message* aMsg) { MOZ_CRASH(); } diff --git a/toolkit/recordreplay/ipc/JSControl.cpp b/toolkit/recordreplay/ipc/JSControl.cpp index 143b37bd1bf0..135f3b80c006 100644 --- a/toolkit/recordreplay/ipc/JSControl.cpp +++ b/toolkit/recordreplay/ipc/JSControl.cpp @@ -401,6 +401,37 @@ Middleman_MaybeSwitchToReplayingChild(JSContext* aCx, unsigned aArgc, Value* aVp return true; } +static bool +Middleman_HadRepaint(JSContext* aCx, unsigned aArgc, Value* aVp) +{ + CallArgs args = CallArgsFromVp(aArgc, aVp); + + if (!args.get(0).isNumber() || !args.get(1).isNumber()) { + JS_ReportErrorASCII(aCx, "Bad width/height"); + return false; + } + + size_t width = args.get(0).toNumber(); + size_t height = args.get(1).toNumber(); + + PaintMessage message(CheckpointId::Invalid, width, height); + parent::UpdateGraphicsInUIProcess(&message); + + args.rval().setUndefined(); + return true; +} + +static bool +Middleman_HadRepaintFailure(JSContext* aCx, unsigned aArgc, Value* aVp) +{ + CallArgs args = CallArgsFromVp(aArgc, aVp); + + parent::UpdateGraphicsInUIProcess(nullptr); + + args.rval().setUndefined(); + return true; +} + /////////////////////////////////////////////////////////////////////////////// // Devtools Sandbox /////////////////////////////////////////////////////////////////////////////// @@ -823,6 +854,26 @@ RecordReplay_TimeWarpTargetExecutionPoint(JSContext* aCx, unsigned aArgc, Value* return true; } +static bool +RecordReplay_Repaint(JSContext* aCx, unsigned aArgc, Value* aVp) +{ + CallArgs args = CallArgsFromVp(aArgc, aVp); + + size_t width, height; + child::Repaint(&width, &height); + + RootedObject obj(aCx, JS_NewObject(aCx, nullptr)); + if (!obj || + !JS_DefineProperty(aCx, obj, "width", (double) width, JSPROP_ENUMERATE) || + !JS_DefineProperty(aCx, obj, "height", (double) height, JSPROP_ENUMERATE)) + { + return false; + } + + args.rval().setObject(*obj); + return true; +} + static bool RecordReplay_Dump(JSContext* aCx, unsigned aArgc, Value* aVp) { @@ -859,6 +910,8 @@ static const JSFunctionSpec gMiddlemanMethods[] = { JS_FN("setBreakpoint", Middleman_SetBreakpoint, 2, 0), JS_FN("clearBreakpoint", Middleman_ClearBreakpoint, 1, 0), JS_FN("maybeSwitchToReplayingChild", Middleman_MaybeSwitchToReplayingChild, 0, 0), + JS_FN("hadRepaint", Middleman_HadRepaint, 2, 0), + JS_FN("hadRepaintFailure", Middleman_HadRepaintFailure, 0, 0), JS_FS_END }; @@ -870,6 +923,7 @@ static const JSFunctionSpec gRecordReplayMethods[] = { JS_FN("getContent", RecordReplay_GetContent, 1, 0), JS_FN("currentExecutionPoint", RecordReplay_CurrentExecutionPoint, 1, 0), JS_FN("timeWarpTargetExecutionPoint", RecordReplay_TimeWarpTargetExecutionPoint, 1, 0), + JS_FN("repaint", RecordReplay_Repaint, 0, 0), JS_FN("dump", RecordReplay_Dump, 1, 0), JS_FS_END }; diff --git a/toolkit/recordreplay/ipc/ParentForwarding.cpp b/toolkit/recordreplay/ipc/ParentForwarding.cpp index 43e1923d10a0..217df24fb7b8 100644 --- a/toolkit/recordreplay/ipc/ParentForwarding.cpp +++ b/toolkit/recordreplay/ipc/ParentForwarding.cpp @@ -77,11 +77,6 @@ HandleMessageInMiddleman(ipc::Side aSide, const IPC::Message& aMessage) // Preferences are initialized via the SetXPCOMProcessAttributes message. PreferencesLoaded(); } - if (type == dom::PBrowser::Msg_RenderLayers__ID) { - // Graphics are being loaded or unloaded for a tab, so update what we are - // showing to the UI process according to the last paint performed. - UpdateGraphicsInUIProcess(nullptr); - } return false; } @@ -115,6 +110,13 @@ HandleMessageInMiddleman(ipc::Side aSide, const IPC::Message& aMessage) static bool AlwaysForwardMessage(const IPC::Message& aMessage) { + // Always forward messages in repaint stress mode, as the active child is + // almost always a replaying child and lost messages make it hard to load + // pages completely. + if (InRepaintStressMode()) { + return true; + } + IPC::Message::msgid_t type = aMessage.type(); // Forward close messages so that the tab shuts down properly even if it is diff --git a/toolkit/recordreplay/ipc/ParentGraphics.cpp b/toolkit/recordreplay/ipc/ParentGraphics.cpp index 63d85be6528a..d02aebd8d132 100644 --- a/toolkit/recordreplay/ipc/ParentGraphics.cpp +++ b/toolkit/recordreplay/ipc/ParentGraphics.cpp @@ -69,8 +69,6 @@ SendGraphicsMemoryToChild() MOZ_RELEASE_ASSERT(kr == KERN_SUCCESS); } -static Maybe gLastPaint; - // Global object for the sandbox used to paint graphics data in this process. static JS::PersistentRootedObject* gGraphicsSandbox; @@ -109,17 +107,47 @@ InitGraphicsSandbox() // Buffer used to transform graphics memory, if necessary. static void* gBufferMemory; +// The dimensions of the data in the graphics shmem buffer. +static size_t gLastPaintWidth, gLastPaintHeight; + +// Explicit Paint messages received from the child need to be handled with +// care to make sure we show correct graphics. Each Paint message is for the +// the process state at the most recent checkpoint in the past. When running +// (forwards or backwards) between the checkpoint and the Paint message, +// we could pause at a breakpoint and repaint the graphics at that point, +// reflecting the process state at a point later than at the checkpoint. +// In this case the Paint message's graphics will be stale. To avoid showing +// its graphics, we wait until both the Paint and the checkpoint itself have +// been hit, with no intervening repaint. + +// The last explicit paint message received from the child, if there has not +// been an intervening repaint. +static UniquePtr gLastExplicitPaint; + +// The last checkpoint the child reached, if there has not been an intervening +// repaint. +static size_t gLastCheckpoint; + void UpdateGraphicsInUIProcess(const PaintMessage* aMsg) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (aMsg) { - gLastPaint = Some(*aMsg); - } else if (!gLastPaint.isSome()) { + gLastPaintWidth = aMsg->mWidth; + gLastPaintHeight = aMsg->mHeight; + } + + if (!gLastPaintWidth || !gLastPaintHeight) { return; } + bool hadFailure = !aMsg; + + // Clear out the last explicit paint information. This may delete aMsg. + gLastExplicitPaint = nullptr; + gLastCheckpoint = CheckpointId::Invalid; + // Make sure there is a sandbox which is running the graphics JS module. if (!gGraphicsSandbox) { InitGraphicsSandbox(); @@ -128,8 +156,8 @@ UpdateGraphicsInUIProcess(const PaintMessage* aMsg) AutoSafeJSContext cx; JSAutoRealm ar(cx, *gGraphicsSandbox); - size_t width = gLastPaint.ref().mWidth; - size_t height = gLastPaint.ref().mHeight; + size_t width = gLastPaintWidth; + size_t height = gLastPaintHeight; size_t stride = layers::ImageDataSerializer::ComputeRGBStride(gSurfaceFormat, width); // Make sure the width and height are appropriately sized. @@ -160,10 +188,11 @@ UpdateGraphicsInUIProcess(const PaintMessage* aMsg) JS_NewArrayBufferWithExternalContents(cx, width * height * 4, memory); MOZ_RELEASE_ASSERT(bufferObject); - JS::AutoValueArray<3> args(cx); + JS::AutoValueArray<4> args(cx); args[0].setObject(*bufferObject); args[1].setInt32(width); args[2].setInt32(height); + args[3].setBoolean(hadFailure); // Call into the graphics module to update the canvas it manages. RootedValue rval(cx); @@ -172,6 +201,41 @@ UpdateGraphicsInUIProcess(const PaintMessage* aMsg) } } +static void +MaybeTriggerExplicitPaint() +{ + if (gLastExplicitPaint && gLastExplicitPaint->mCheckpointId == gLastCheckpoint) { + UpdateGraphicsInUIProcess(gLastExplicitPaint.get()); + } +} + +void +MaybeUpdateGraphicsAtPaint(const PaintMessage& aMsg) +{ + gLastExplicitPaint.reset(new PaintMessage(aMsg)); + MaybeTriggerExplicitPaint(); +} + +void +MaybeUpdateGraphicsAtCheckpoint(size_t aCheckpointId) +{ + gLastCheckpoint = aCheckpointId; + MaybeTriggerExplicitPaint(); +} + +bool +InRepaintStressMode() +{ + static bool checked = false; + static bool rv; + if (!checked) { + AutoEnsurePassThroughThreadEvents pt; + rv = TestEnv("MOZ_RECORD_REPLAY_REPAINT_STRESS"); + checked = true; + } + return rv; +} + } // namespace parent } // namespace recordreplay } // namespace mozilla diff --git a/toolkit/recordreplay/ipc/ParentIPC.cpp b/toolkit/recordreplay/ipc/ParentIPC.cpp index c70eebd9791b..1017a65c6991 100644 --- a/toolkit/recordreplay/ipc/ParentIPC.cpp +++ b/toolkit/recordreplay/ipc/ParentIPC.cpp @@ -226,6 +226,7 @@ static void RecvHitBreakpoint(const HitBreakpointMessage& aMsg); static void RecvDebuggerResponse(const DebuggerResponseMessage& aMsg); static void RecvRecordingFlushed(); static void RecvAlwaysMarkMajorCheckpoints(); +static void RecvMiddlemanCallRequest(const MiddlemanCallRequestMessage& aMsg); // The role taken by the active child. class ChildRoleActive final : public ChildRole @@ -250,7 +251,7 @@ public: void OnIncomingMessage(const Message& aMsg) override { switch (aMsg.mType) { case MessageType::Paint: - UpdateGraphicsInUIProcess((const PaintMessage*) &aMsg); + MaybeUpdateGraphicsAtPaint((const PaintMessage&) aMsg); break; case MessageType::HitCheckpoint: RecvHitCheckpoint((const HitCheckpointMessage&) aMsg); @@ -267,6 +268,12 @@ public: case MessageType::AlwaysMarkMajorCheckpoints: RecvAlwaysMarkMajorCheckpoints(); break; + case MessageType::MiddlemanCallRequest: + RecvMiddlemanCallRequest((const MiddlemanCallRequestMessage&) aMsg); + break; + case MessageType::ResetMiddlemanCalls: + ResetMiddlemanCalls(); + break; default: MOZ_CRASH("Unexpected message"); } @@ -970,11 +977,45 @@ static bool gResumeForwardOrBackward = false; // Hit any breakpoints installed for forced pauses. static void HitForcedPauseBreakpoints(bool aRecordingBoundary); +static void +MaybeSendRepaintMessage() +{ + // In repaint stress mode, we want to trigger a repaint at every checkpoint, + // so before resuming after the child pauses at each checkpoint, send it a + // repaint message. There might not be a debugger open, so manually craft the + // same message which the debugger would send to trigger a repaint and parse + // the result. + if (InRepaintStressMode()) { + MaybeSwitchToReplayingChild(); + + const char16_t contents[] = u"{\"type\":\"repaint\"}"; + + js::CharBuffer request, response; + request.append(contents, ArrayLength(contents) - 1); + SendRequest(request, &response); + + AutoSafeJSContext cx; + JS::RootedValue value(cx); + if (JS_ParseJSON(cx, response.begin(), response.length(), &value)) { + MOZ_RELEASE_ASSERT(value.isObject()); + JS::RootedObject obj(cx, &value.toObject()); + RootedValue width(cx), height(cx); + if (JS_GetProperty(cx, obj, "width", &width) && width.isNumber() && width.toNumber() && + JS_GetProperty(cx, obj, "height", &height) && height.isNumber() && height.toNumber()) { + PaintMessage message(CheckpointId::Invalid, width.toNumber(), height.toNumber()); + UpdateGraphicsInUIProcess(&message); + } + } + } +} + void Resume(bool aForward) { gActiveChild->WaitUntilPaused(); + MaybeSendRepaintMessage(); + // Set the preferred direction of travel. gResumeForwardOrBackward = false; gChildExecuteForward = aForward; @@ -1125,6 +1166,7 @@ static void RecvHitCheckpoint(const HitCheckpointMessage& aMsg) { UpdateCheckpointTimes(aMsg); + MaybeUpdateGraphicsAtCheckpoint(aMsg.mCheckpointId); // Resume either forwards or backwards. Break the resume off into a separate // runnable, to avoid starving any code already on the stack and waiting for @@ -1199,6 +1241,18 @@ HitForcedPauseBreakpoints(bool aRecordingBoundary) } } +static void +RecvMiddlemanCallRequest(const MiddlemanCallRequestMessage& aMsg) +{ + InfallibleVector outputData; + ProcessMiddlemanCall(aMsg.BinaryData(), aMsg.BinaryDataSize(), &outputData); + + MiddlemanCallResponseMessage* response = + MiddlemanCallResponseMessage::New(outputData.begin(), outputData.length()); + gActiveChild->SendMessage(*response); + free(response); +} + } // namespace parent } // namespace recordreplay } // namespace mozilla diff --git a/toolkit/recordreplay/ipc/ParentInternal.h b/toolkit/recordreplay/ipc/ParentInternal.h index d10dec307fcf..6c4b3531a707 100644 --- a/toolkit/recordreplay/ipc/ParentInternal.h +++ b/toolkit/recordreplay/ipc/ParentInternal.h @@ -86,9 +86,15 @@ void InitializeGraphicsMemory(); void SendGraphicsMemoryToChild(); // Update the graphics painted in the UI process, per painting data received -// from a child process, or null for the last paint performed. +// from a child process, or null if a repaint was triggered and failed due to +// an unhandled recording divergence. void UpdateGraphicsInUIProcess(const PaintMessage* aMsg); +// If necessary, update graphics after the active child sends a paint message +// or reaches a checkpoint. +void MaybeUpdateGraphicsAtPaint(const PaintMessage& aMsg); +void MaybeUpdateGraphicsAtCheckpoint(size_t aCheckpointId); + // ID for the mach message sent from a child process to the middleman to // request a port for the graphics shmem. static const int32_t GraphicsHandshakeMessageId = 42; @@ -100,6 +106,13 @@ static const int32_t GraphicsMemoryMessageId = 43; // Fixed size of the graphics shared memory buffer. static const size_t GraphicsMemorySize = 4096 * 4096 * 4; +// Return whether the environment variable activating repaint stress mode is +// set. This makes various changes in both the middleman and child processes to +// trigger a child to diverge from the recording and repaint on every vsync, +// making sure that repainting can handle all the system interactions that +// occur while painting the current tab. +bool InRepaintStressMode(); + /////////////////////////////////////////////////////////////////////////////// // Child Processes /////////////////////////////////////////////////////////////////////////////// diff --git a/toolkit/recordreplay/moz.build b/toolkit/recordreplay/moz.build index a1dbdb0ca386..8dd56716b4d5 100644 --- a/toolkit/recordreplay/moz.build +++ b/toolkit/recordreplay/moz.build @@ -26,6 +26,7 @@ if CONFIG['OS_ARCH'] == 'Darwin' and CONFIG['NIGHTLY_BUILD']: 'ipc/ParentIPC.cpp', 'Lock.cpp', 'MemorySnapshot.cpp', + 'MiddlemanCall.cpp', 'ProcessRecordReplay.cpp', 'ProcessRedirectDarwin.cpp', 'ProcessRewind.cpp', diff --git a/xpcom/threads/ThreadEventTarget.cpp b/xpcom/threads/ThreadEventTarget.cpp index 02a2d38807c5..466e7a7b3648 100644 --- a/xpcom/threads/ThreadEventTarget.cpp +++ b/xpcom/threads/ThreadEventTarget.cpp @@ -134,6 +134,14 @@ ThreadEventTarget::Dispatch(already_AddRefed aEvent, uint32_t aFlag return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; } + // Don't dispatch runnables to other threads when replaying and diverged from + // the recording, to avoid deadlocking with other idle threads. The browser + // remains paused after diverging from the recording, and threads will not + // run their event loops. + if (recordreplay::HasDivergedFromRecording()) { + return NS_ERROR_FAILURE; + } + #ifdef MOZ_TASK_TRACER nsCOMPtr tracedRunnable = CreateTracedRunnable(event.take()); (static_cast(tracedRunnable.get()))->DispatchTask(); diff --git a/xpcom/threads/nsThread.cpp b/xpcom/threads/nsThread.cpp index 93bbeb448b4f..4f7d75b8619c 100644 --- a/xpcom/threads/nsThread.cpp +++ b/xpcom/threads/nsThread.cpp @@ -1123,13 +1123,6 @@ nsThread::ProcessNextEvent(bool aMayWait, bool* aResult) return NS_ERROR_NOT_SAME_THREAD; } - // When recording or replaying, vsync observers are notified whenever - // processing events on the main thread. Waiting for explicit vsync messages - // from the UI process can result in paints happening at unexpected times. - if (recordreplay::IsRecordingOrReplaying() && mIsMainThread == MAIN_THREAD) { - recordreplay::child::NotifyVsyncObserver(); - } - // The toplevel event loop normally blocks waiting for the next event, but // if we're trying to shut this thread down, we must exit the event loop when // the event queue is empty.