From bc8ce423556d5c7ccfdfabb9f6dc768c8fdc5dc9 Mon Sep 17 00:00:00 2001 From: roytam1 Date: Tue, 25 Jul 2023 10:32:23 +0800 Subject: [PATCH] import changes from `dev' branch of rmottola/Arctic-Fox: - Bug 1226200: Don't assume a TCPSocket has only one managee (and rename LoneManagedOrNull) r=jdm (aa2d0fcc14) - Bug 1227300, Part 1 - Add an alert notification component. r=MattN,wchen (37758ce9ff) - Bug 1230700. Make Notification::ShowInternal explicitly suppress the exception from GetPermissionInternal. r=smaug (9e288cf5ae) - Bug 1225336 - Add telemetry about web notification display/messages. r=wchen,kitcambridge p=vladan# Please enter the commit message for your changes. Lines starting (b2e481691a) - Bug 1219030 - Collect notification management telemetry. r=wchen,MattN; p=ally (c0ba425b4e) - Bug 1212611 - Use system notification for website notifications in Android. r=mfinkle (84985bcf01) - Bug 1227300, Part 2 - Implement showAlert. r=MattN,wchen (33eedc7e91) - Bug 1214305 - Part 0: Ensure site security service is initialized before trying to use DataStorage via IPC. (8bd73f43b0) - Bug 1137681 - Make user agent docshell overrides affect network requests. r=jduell (e8dabb8338) - Bug 1233245 - Propagate the interception information in the non-e10s case for all HTTP redirects, not just the internal ones; r=jdm (6922fddcf8) - Bug 1226444 - Use helper function to identify preloads. r=sicking (d110669f73) - Bug 1214305 - Part 1: Refactor the logic for querying whether a connection should go through a secure upgrade into NS_ShouldSecureUpgrade; r=mcmanus (2d04c78290) - Bug 1137681 - Per-tab user agent emulation. r=bz (4ff70db690) - Bug 1227300, Part 3 - Implement showAlert for the OS X alerts backend. r=mstange (5eb05d0728) - Bug 1227300, Part 4 - Implement showAlert for the libnotify alerts backend. r=karlt (0942fa2764) - Bug 1227300, Part 5 - Implement showAlert for the B2G alerts backend. r=mhenretty (e39581aea5) - Bug 1227300, Part 6 - Use showAlert to display web notifications. r=wchen (ab79eaa0c8) - domBug 1227300, Part 7 - Update test interfaces. a=testonly (387cb62772) - Bug 1214305 - Part 3: Add a nsIInterceptedChannel.secureUpgradedChannelURI helper; r=jdm (811d25bd58) - Bug 1214305 - Part 4: Use the secure upgraded channel URI in ServiceWorkerManager::PrepareFetchEvent; r=jdm (0c44bf527c) - Bug 1214305 - Part 5: Use the secure upgraded channel URI in FetchEventRunnable::Init; r=jdm (a30f239261) - Bug 1214305 - Part 6: Use a non-IPC redirect for synthesized upgraded responses to ensure the response URL is correctly propagated; r=mcmanus (8ba8a5728c) - Bug 1214305 - Part 7: Decide in the child process whether an intercepted channel should go through a secure upgrade; r=mcmanus (93e27decae) - Bug 1198397 - Add a test for interception of requests upgraded through the CSP upgrade-insecure-requests directive; r=jdm (21eb14eb34) - Bug 1214305 - Part 8: Enable secure upgrade service worker tests on e10s; r=jdm (c14f5fb504) - Bug 1214305 - Part 10: Clean up global DataStorage references in the child process; r=keeler (ef0b52d049) - Bug 1224771 - Close all web notifications when the originating tab is closed. r=wchen (da295b4ba7) - Bug 1214305 - Part 2: Refactor the logic for obtaining the secure upgraded URI into HttpBaseChannel; r=mcmanus (3346078285) - Bug 1237151 (part 1) - Remove ignored qualifiers in dom/media/gmp/. r=cpearce. (18134820a6) - Bug 1237151 (part 2) - Remove ignored qualifiers in WebRTC-relate code. r=jesup. (effe5bd694) - Bug 1118820 part 1 (style system part) - [css-grid] Implement the 'auto-fill' and 'auto-fit' keywords in the repeat() function. r=dholbert (5db1a577db) - Bug 1118820 part 2a - [css-grid] Add a LineNameMap class that lets us lookup line names with a dynamic number of 'repeat(auto-fill/auto-fit)' tracks taken into account. r=dholbert (3ecf4b53f0) - Bug 1118820 part 2b - [css-grid] Move the static functions FindLine/RFindLine/FindNamedLine into the LineNameMap class (idempotent patch). r=dholbert (efb12c594b) - Bug 1118820 part 2c+2d - [css-grid] Modify the LineNameMap::FindLine/RFindLine/FindNamedLine methods to take line names associated with 'repeat(auto-fill/auto-fit)' tracks into account. Instantiate and pass around a LineNameMap object instead of an array of line name arrays. r=dholbert (a3db750297) - Bug 1118820 part 3a - [css-grid] Modify TrackSizingFunctions to take a dynamic number of 'repeat(auto-fill/auto-fit)' tracks taking into account. r=dholbert (9aa6033332) - Bug 1118820 part 3b - [css-grid] Implement the CalculateRepeatFillCount method that calculates the number of 'repeat(auto-fill/auto-fit)' tracks to use for the given sizes. r=dholbert (cf75fab8a8) - Bug 1118820 part 4 - [css-grid] Provide the sizes to use for CalculateRepeatFillCount. r=dholbert (8184c00dba) - Bug 1118820 part 5 - [css-grid] Remove any empty 'repeat(auto-fit)' tracks at the end of its range and adjust affected grid area line numbers accordingly. r=dholbert (c02ba6a6e3) - Bug 1229165 - [css-grid] Reftests for min/max-width/height properties on the grid container. (725097f878) - Bug 1229999 - [css-grid] Reftest. (51b5bc9535) - Bug 1237151 (part 3) - Remove ignored qualifiers in all remaining code. r=froydnj. (69917ebbad) - more bits of Bug 1178892 - Split the profiler into Core & Gecko files (41bb127b9c) - Bug 1199841 - Restructure private browsing to remember status after OnStopRequest r=jdm (16c2c1044d) - Bug 1233845 - Report an interception error and cancel the HTTP channel when encountering a known topcrash situation. r=ehsan (5d08075110) - Bug 664163 - Fix Get(Local|Remote)(Address|Port) in HttpChannelChild. r=jduell (cbf70af4e8) - Bug 1229177 - Show the tracking protection shield for fetch and XHR requests. r=jduell (5833b3e872) - Bug 1220678 Don't crash when DivertToParent() is called on an intercepted channel. r=jdm (5d7ff6ecc8) - Bug 1220681 P1 Make HttpChannelChild::DivertToParent() work with synthetic responses. r=jdm (67715703ef) - Bug 1169819 Add browser chrome test to validate SW force refresh. r=ehsan (c0b6b3e874) - Bug 1220681 P2 Test synthetic responses that trigger downloads. r=ehsan (c701f3ddb1) - Bug 1220681 P3 Delay diversion on parent side until response head has been synthesized. r=jdm (7c697aacee) - Bug 1220681 P4 Automatically suspend the parent channel after synthesizing the response for diverison. r=jdm (27f31bcb35) - Bug 1220681 P5 Don't double suspend parent channel during synthesized divert to parent. r=jdm (62081d4b56) - Bug 1220681 P6 Use clients.claim() in browser_download.js to avoid worker unregister race. r=jdm (c8de291727) - Bug 1240161 - Remove "only-if-cached" from RequestCache; r=bkelly (283486f584) - Bug 1184550 - Add a mochitest for the Request constructor that tests that the body is set to used after being fetched and then fails on the second fetch with the same Request. r=bkelly (db4a967203) - Bug 1205495 - Correctly use the requests's body and redirect mode in reroute.js; r=nsm (a36a96624a) - Bug 1189656 - Fix fetch-request-fallback test paths. r=bkelly (366c884179) - Bug 1219085 - Import the fetch-request-redirect.https.html test from Blink; r=jdm (bf6b484d82) - Bug 1209081 - Part 1: Implement the "navigate" value for RequestMode; r=bkelly (a22f19d94b) - Bug 1209081 - Part 2: Upgrade the saved Requests in the DOM Cache to reflect the "navigate" RequestMode if they represent navigation content policy types; r=bkelly (60d1da23bb) - Bug 1219469 - Part 1: Revert the error reporting added in bug 1233845; r=jdm (dcc022b9d3) - Bug 1219469 - Part 2: Make HttpChannelParentListener be the controller; r=jdm,jduell (9c5f0dfbe0) - Bug 1229369 - Intercept redirected network fetches that have their request mode set to manual; r=jdm (fb21d86ed2) --- b2g/components/AlertsService.js | 39 +- docshell/base/nsDocShell.cpp | 38 + docshell/base/nsDocShell.h | 1 + docshell/base/nsIDocShell.idl | 9 +- .../base/timeline/AbstractTimelineMarker.h | 4 +- docshell/test/browser/browser.ini | 1 + docshell/test/browser/browser_ua_emulation.js | 52 + dom/base/Navigator.cpp | 25 +- dom/base/Navigator.h | 4 + dom/base/nsDocument.cpp | 3 + dom/bindings/Errors.msg | 1 + dom/cache/DBSchema.cpp | 75 +- dom/cache/test/xpcshell/head.js | 3 + dom/cache/test/xpcshell/schema_15_profile.zip | Bin 2577 -> 3111 bytes dom/cache/test/xpcshell/test_migration.js | 2 + dom/fetch/FetchDriver.cpp | 3 +- dom/fetch/InternalRequest.cpp | 14 +- dom/fetch/Request.cpp | 5 + dom/gamepad/cocoa/CocoaGamepad.cpp | 2 +- dom/inputport/InputPortData.cpp | 2 +- dom/inputport/InputPortData.h | 2 +- dom/ipc/ContentBridgeChild.cpp | 2 +- dom/ipc/ContentBridgeParent.cpp | 2 +- dom/ipc/ContentChild.cpp | 4 +- dom/ipc/ContentParent.cpp | 34 +- dom/ipc/ContentParent.h | 8 +- dom/ipc/CrashReporterChild.cpp | 2 +- dom/ipc/PContent.ipdl | 13 +- dom/ipc/TabParent.cpp | 2 +- .../en-US/chrome/layout/css.properties | 3 + dom/media/android/AndroidMediaDecoder.h | 2 +- dom/media/gmp/GMPContentParent.h | 2 +- dom/media/gmp/GMPDecryptorParent.h | 2 +- dom/media/gmp/GMPDecryptorProxy.h | 2 +- dom/media/gmp/GMPEncryptedBufferDataImpl.cpp | 2 +- dom/media/gmp/GMPEncryptedBufferDataImpl.h | 2 +- dom/media/gmp/GMPParent.cpp | 4 +- dom/media/gmp/GMPParent.h | 2 +- dom/media/gmp/GMPService.h | 2 +- dom/media/gmp/GMPVideoDecoderParent.h | 2 +- dom/media/gmp/GMPVideoDecoderProxy.h | 2 +- dom/media/gmp/GMPVideoEncoderParent.h | 2 +- dom/media/gmp/GMPVideoEncoderProxy.h | 2 +- dom/media/gmp/gmp-api/gmp-decryption.h | 2 +- dom/media/omx/OMXCodecWrapper.cpp | 6 +- dom/media/webrtc/MediaEngine.h | 2 +- dom/media/webrtc/MediaEngineDefault.h | 4 +- dom/media/webrtc/MediaEngineGonkVideoSource.h | 2 +- .../webrtc/MediaEngineRemoteVideoSource.h | 2 +- dom/media/webrtc/MediaEngineTabVideoSource.h | 2 +- dom/media/webrtc/MediaEngineWebRTC.h | 4 +- dom/network/TCPServerSocketParent.cpp | 4 +- dom/network/TCPSocketParent.cpp | 8 +- dom/notification/DesktopNotification.cpp | 25 +- dom/notification/Notification.cpp | 351 ++- dom/notification/Notification.h | 43 +- dom/plugins/ipc/PluginMessageUtils.h | 2 +- dom/plugins/ipc/PluginModuleParent.cpp | 2 +- .../provider/MulticastDNSDeviceProvider.h | 4 +- dom/smil/nsSMILCSSValueType.cpp | 2 +- dom/svg/SVGTransform.h | 2 +- dom/tests/mochitest/fetch/reroute.js | 19 +- dom/tests/mochitest/fetch/test_request.js | 24 + .../mochitest/notification/MockServices.js | 22 +- .../notification_common.js | 22 +- .../test_notification_tag.html | 12 +- .../test_system_principal.xul | 10 +- dom/tests/mochitest/webapps/head.js | 3 + dom/webidl/Navigator.webidl | 2 +- dom/webidl/Request.webidl | 4 +- dom/workers/ServiceWorkerEvents.cpp | 3 +- dom/workers/ServiceWorkerManager.cpp | 4 +- dom/workers/ServiceWorkerPrivate.cpp | 2 +- dom/workers/moz.build | 4 + dom/workers/test/serviceworkers/browser.ini | 10 + .../browser_base_force_refresh.html | 30 + .../browser_cached_force_refresh.html | 17 + .../test/serviceworkers/browser_download.js | 82 + .../serviceworkers/browser_force_refresh.js | 54 + .../test/serviceworkers/download_window.html | 46 + .../test/serviceworkers/download_worker.js | 28 + .../fetch/upgrade-insecure/embedder.html | 10 + .../upgrade-insecure/embedder.html^headers^ | 1 + .../fetch/upgrade-insecure/image-20px.png | Bin 0 -> 87 bytes .../fetch/upgrade-insecure/image-40px.png | Bin 0 -> 123 bytes .../fetch/upgrade-insecure/image.html | 13 + .../fetch/upgrade-insecure/realindex.html | 4 + .../fetch/upgrade-insecure/register.html | 14 + .../fetch/upgrade-insecure/unregister.html | 12 + .../upgrade-insecure/upgrade-insecure_test.js | 11 + .../force_refresh_browser_worker.js | 22 + dom/workers/test/serviceworkers/mochitest.ini | 11 +- .../test_csp_upgrade-insecure_intercept.html | 56 + .../unixshared/nsPrintingPromptService.cpp | 2 +- gfx/layers/Layers.cpp | 2 +- gfx/layers/Layers.h | 4 +- gfx/thebes/gfxUtils.cpp | 4 +- ipc/chromium/src/chrome/common/ipc_message.h | 2 +- .../src/chrome/common/ipc_message_utils.h | 2 +- ipc/glue/ProtocolUtils.h | 15 +- ipc/ipdl/ipdl/lower.py | 2 +- ipc/ipdl/test/cxx/TestMultiMgrs.cpp | 4 +- layout/base/nsPresContext.h | 16 +- layout/build/nsLayoutCID.h | 3 + layout/build/nsLayoutModule.cpp | 7 + layout/generic/nsGridContainerFrame.cpp | 653 +++-- layout/generic/nsGridContainerFrame.h | 121 +- .../css-grid/grid-clamping-001-ref.html | 68 + .../reftests/css-grid/grid-clamping-001.html | 78 + .../css-grid/grid-clamping-002-ref.html | 68 + .../reftests/css-grid/grid-clamping-002.html | 75 + ...ontainer-min-max-width-height-001-ref.html | 56 + ...id-container-min-max-width-height-001.html | 55 + .../grid-placement-auto-implicit-001-ref.html | 8 +- .../grid-repeat-auto-fill-fit-001-ref.html | 193 ++ .../grid-repeat-auto-fill-fit-001.html | 179 ++ .../grid-repeat-auto-fill-fit-002-ref.html | 190 ++ .../grid-repeat-auto-fill-fit-002.html | 182 ++ .../grid-repeat-auto-fill-fit-003-ref.html | 189 ++ .../grid-repeat-auto-fill-fit-003.html | 181 ++ .../grid-repeat-auto-fill-fit-004-ref.html | 191 ++ .../grid-repeat-auto-fill-fit-004.html | 183 ++ .../grid-repeat-auto-fill-fit-005-ref.html | 381 +++ .../grid-repeat-auto-fill-fit-005.html | 377 +++ .../grid-repeat-auto-fill-fit-006-ref.html | 184 ++ .../grid-repeat-auto-fill-fit-006.html | 213 ++ layout/reftests/css-grid/reftest.list | 9 + layout/style/nsCSSKeywordList.h | 2 + layout/style/nsCSSParser.cpp | 205 +- layout/style/nsCSSValue.cpp | 39 + layout/style/nsComputedDOMStyle.cpp | 162 +- layout/style/nsComputedDOMStyle.h | 11 +- layout/style/nsRuleNode.cpp | 81 +- layout/style/nsStyleConsts.h | 4 + layout/style/nsStyleStruct.h | 126 +- layout/style/test/property_database.js | 42 +- .../src/media-conduit/MediaConduitInterface.h | 2 +- .../src/media-conduit/WebrtcGmpVideoCodec.h | 8 +- .../WebrtcMediaCodecVP8VideoCodec.h | 4 +- .../media-conduit/WebrtcOMXH264VideoCodec.h | 4 +- .../src/mediapipeline/MediaPipeline.h | 2 +- media/webrtc/signaling/src/sdp/Sdp.h | 2 +- .../webrtc/signaling/test/FakeMediaStreams.h | 2 +- modules/libjar/InterceptedJARChannel.cpp | 6 + mozglue/linker/Mappable.h | 6 +- mozglue/linker/SeekableZStream.h | 6 +- netwerk/base/PrivateBrowsingChannel.h | 17 +- netwerk/base/nsBaseChannel.cpp | 4 +- .../base/nsINetworkInterceptController.idl | 8 +- netwerk/base/nsNetUtil.cpp | 108 +- netwerk/base/nsNetUtil.h | 10 + netwerk/cache2/CacheObserver.cpp | 6 +- netwerk/cache2/CacheObserver.h | 36 +- netwerk/cache2/CacheStorageService.cpp | 2 +- netwerk/cache2/CacheStorageService.h | 2 +- netwerk/ipc/NeckoChannelParams.ipdlh | 1 + netwerk/protocol/http/HttpBaseChannel.cpp | 88 +- netwerk/protocol/http/HttpBaseChannel.h | 7 +- netwerk/protocol/http/HttpChannelChild.cpp | 205 +- netwerk/protocol/http/HttpChannelChild.h | 20 +- netwerk/protocol/http/HttpChannelParent.cpp | 247 +- netwerk/protocol/http/HttpChannelParent.h | 45 +- .../http/HttpChannelParentListener.cpp | 172 +- .../protocol/http/HttpChannelParentListener.h | 33 + netwerk/protocol/http/InterceptedChannel.cpp | 46 +- netwerk/protocol/http/InterceptedChannel.h | 11 +- netwerk/protocol/http/nsHttpChannel.cpp | 160 +- netwerk/protocol/http/nsIHttpChannelChild.idl | 5 +- .../protocol/http/nsIHttpChannelInternal.idl | 3 +- .../protocol/wyciwyg/WyciwygChannelChild.cpp | 3 + netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp | 4 +- netwerk/test/unit/test_getHost.js | 75 + netwerk/test/unit/test_traceable_channel.js | 2 - netwerk/test/unit/xpcshell.ini | 1 + netwerk/test/unit_ipc/test_getHost_wrap.js | 3 + netwerk/test/unit_ipc/xpcshell.ini | 1 + parser/htmlparser/nsParser.cpp | 2 +- security/manager/ssl/DataStorage.cpp | 45 +- storage/mozStorageBindingParamsArray.h | 2 +- .../web-platform/mozilla/meta/MANIFEST.json | 7 + .../fetch-request-fallback.https.html.ini | 6 - .../fetch-request-redirect.https.html.ini | 3 + .../fetch-request-fallback.https.html | 21 +- .../fetch-request-redirect.https.html | 176 ++ .../navigation-redirect.https.html | 449 ++++ .../request-end-to-end.https.html | 2 + .../service-worker/resources/dummy.html | 2 + .../fetch-request-fallback-worker.js | 1 + .../fetch-request-redirect-iframe.html | 35 + .../resources/fetch-rewrite-worker.js | 5 +- .../resources/request-end-to-end-worker.js | 1 + .../service-worker/resources/silence.oga | 0 .../service-worker/resources/square.png | Bin 0 -> 18299 bytes .../components/alerts/AlertNotification.cpp | 125 + toolkit/components/alerts/AlertNotification.h | 44 + .../alerts/AlertNotificationIPCSerializer.h | 119 + toolkit/components/alerts/moz.build | 6 + toolkit/components/alerts/nsAlertsService.cpp | 99 +- .../components/alerts/nsIAlertsService.idl | 89 +- toolkit/components/alerts/nsXULAlerts.cpp | 14 + .../alerts/resources/content/alert.js | 2 + toolkit/components/build/nsToolkitCompsCID.h | 3 + .../components/build/nsToolkitCompsModule.cpp | 5 + .../osfile/NativeOSFileInternals.cpp | 2 +- toolkit/components/telemetry/Histograms.json | 66 + .../mozapps/update/updater/automounter_gonk.h | 2 +- toolkit/system/gnome/nsAlertsIconListener.cpp | 50 +- toolkit/system/gnome/nsAlertsIconListener.h | 10 +- .../system/gnome/nsSystemAlertsService.cpp | 20 +- tools/profiler/lul/AutoObjectMapper.cpp | 206 ++ tools/profiler/lul/AutoObjectMapper.h | 115 + tools/profiler/lul/LulCommon.cpp | 114 + tools/profiler/lul/LulCommonExt.h | 554 +++++ tools/profiler/lul/LulDwarf.cpp | 2181 +++++++++++++++++ tools/profiler/lul/LulDwarfExt.h | 1287 ++++++++++ tools/profiler/lul/LulDwarfInt.h | 194 ++ tools/profiler/lul/LulDwarfSummariser.cpp | 358 +++ tools/profiler/lul/LulDwarfSummariser.h | 65 + tools/profiler/lul/LulElf.cpp | 914 +++++++ tools/profiler/lul/LulElfExt.h | 68 + tools/profiler/lul/LulElfInt.h | 234 ++ tools/profiler/lul/LulMain.cpp | 1944 +++++++++++++++ tools/profiler/lul/LulMain.h | 397 +++ tools/profiler/lul/LulMainInt.h | 393 +++ tools/profiler/lul/LulPlatformMacros.h | 53 + .../profiler/{ => lul}/platform-linux-lul.cpp | 0 tools/profiler/moz.build | 6 +- tools/profiler/public/ProfilerMarkers.h | 4 +- widget/cocoa/OSXNotificationCenter.mm | 80 +- widget/gtk/nsPrintDialogGTK.cpp | 4 +- xpcom/build/PoisonIOInterposerBase.cpp | 5 +- xpcom/ds/nsIAtom.idl | 4 +- xpcom/glue/PLDHashTable.h | 2 +- xpcom/glue/nsTArray.h | 19 +- 234 files changed, 16875 insertions(+), 1077 deletions(-) create mode 100644 docshell/test/browser/browser_ua_emulation.js create mode 100644 dom/workers/test/serviceworkers/browser.ini create mode 100644 dom/workers/test/serviceworkers/browser_base_force_refresh.html create mode 100644 dom/workers/test/serviceworkers/browser_cached_force_refresh.html create mode 100644 dom/workers/test/serviceworkers/browser_download.js create mode 100644 dom/workers/test/serviceworkers/browser_force_refresh.js create mode 100644 dom/workers/test/serviceworkers/download_window.html create mode 100644 dom/workers/test/serviceworkers/download_worker.js create mode 100644 dom/workers/test/serviceworkers/fetch/upgrade-insecure/embedder.html create mode 100644 dom/workers/test/serviceworkers/fetch/upgrade-insecure/embedder.html^headers^ create mode 100644 dom/workers/test/serviceworkers/fetch/upgrade-insecure/image-20px.png create mode 100644 dom/workers/test/serviceworkers/fetch/upgrade-insecure/image-40px.png create mode 100644 dom/workers/test/serviceworkers/fetch/upgrade-insecure/image.html create mode 100644 dom/workers/test/serviceworkers/fetch/upgrade-insecure/realindex.html create mode 100644 dom/workers/test/serviceworkers/fetch/upgrade-insecure/register.html create mode 100644 dom/workers/test/serviceworkers/fetch/upgrade-insecure/unregister.html create mode 100644 dom/workers/test/serviceworkers/fetch/upgrade-insecure/upgrade-insecure_test.js create mode 100644 dom/workers/test/serviceworkers/force_refresh_browser_worker.js create mode 100644 dom/workers/test/serviceworkers/test_csp_upgrade-insecure_intercept.html create mode 100644 layout/reftests/css-grid/grid-clamping-001-ref.html create mode 100644 layout/reftests/css-grid/grid-clamping-001.html create mode 100644 layout/reftests/css-grid/grid-clamping-002-ref.html create mode 100644 layout/reftests/css-grid/grid-clamping-002.html create mode 100644 layout/reftests/css-grid/grid-container-min-max-width-height-001-ref.html create mode 100644 layout/reftests/css-grid/grid-container-min-max-width-height-001.html create mode 100644 layout/reftests/css-grid/grid-repeat-auto-fill-fit-001-ref.html create mode 100644 layout/reftests/css-grid/grid-repeat-auto-fill-fit-001.html create mode 100644 layout/reftests/css-grid/grid-repeat-auto-fill-fit-002-ref.html create mode 100644 layout/reftests/css-grid/grid-repeat-auto-fill-fit-002.html create mode 100644 layout/reftests/css-grid/grid-repeat-auto-fill-fit-003-ref.html create mode 100644 layout/reftests/css-grid/grid-repeat-auto-fill-fit-003.html create mode 100644 layout/reftests/css-grid/grid-repeat-auto-fill-fit-004-ref.html create mode 100644 layout/reftests/css-grid/grid-repeat-auto-fill-fit-004.html create mode 100644 layout/reftests/css-grid/grid-repeat-auto-fill-fit-005-ref.html create mode 100644 layout/reftests/css-grid/grid-repeat-auto-fill-fit-005.html create mode 100644 layout/reftests/css-grid/grid-repeat-auto-fill-fit-006-ref.html create mode 100644 layout/reftests/css-grid/grid-repeat-auto-fill-fit-006.html create mode 100644 netwerk/test/unit/test_getHost.js create mode 100644 netwerk/test/unit_ipc/test_getHost_wrap.js delete mode 100644 testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-request-fallback.https.html.ini create mode 100644 testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-request-redirect.https.html.ini create mode 100644 testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-request-redirect.https.html create mode 100644 testing/web-platform/mozilla/tests/service-workers/service-worker/navigation-redirect.https.html create mode 100644 testing/web-platform/mozilla/tests/service-workers/service-worker/resources/dummy.html create mode 100644 testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-request-redirect-iframe.html create mode 100644 testing/web-platform/mozilla/tests/service-workers/service-worker/resources/silence.oga create mode 100644 testing/web-platform/mozilla/tests/service-workers/service-worker/resources/square.png create mode 100644 toolkit/components/alerts/AlertNotification.cpp create mode 100644 toolkit/components/alerts/AlertNotification.h create mode 100644 toolkit/components/alerts/AlertNotificationIPCSerializer.h create mode 100644 tools/profiler/lul/AutoObjectMapper.cpp create mode 100644 tools/profiler/lul/AutoObjectMapper.h create mode 100644 tools/profiler/lul/LulCommon.cpp create mode 100644 tools/profiler/lul/LulCommonExt.h create mode 100644 tools/profiler/lul/LulDwarf.cpp create mode 100644 tools/profiler/lul/LulDwarfExt.h create mode 100644 tools/profiler/lul/LulDwarfInt.h create mode 100644 tools/profiler/lul/LulDwarfSummariser.cpp create mode 100644 tools/profiler/lul/LulDwarfSummariser.h create mode 100644 tools/profiler/lul/LulElf.cpp create mode 100644 tools/profiler/lul/LulElfExt.h create mode 100644 tools/profiler/lul/LulElfInt.h create mode 100644 tools/profiler/lul/LulMain.cpp create mode 100644 tools/profiler/lul/LulMain.h create mode 100644 tools/profiler/lul/LulMainInt.h create mode 100644 tools/profiler/lul/LulPlatformMacros.h rename tools/profiler/{ => lul}/platform-linux-lul.cpp (100%) diff --git a/b2g/components/AlertsService.js b/b2g/components/AlertsService.js index 6025098ffc..f41ba10662 100644 --- a/b2g/components/AlertsService.js +++ b/b2g/components/AlertsService.js @@ -66,23 +66,36 @@ AlertsService.prototype = { }, // nsIAlertsService + showAlert: function(aAlert, aAlertListener) { + if (!aAlert) { + return; + } + cpmm.sendAsyncMessage(kMessageAlertNotificationSend, { + imageURL: aAlert.imageURL, + title: aAlert.title, + text: aAlert.text, + clickable: aAlert.textClickable, + cookie: aAlert.cookie, + listener: aAlertListener, + id: aAlert.name, + dir: aAlert.dir, + lang: aAlert.lang, + dataStr: aAlert.data, + inPrivateBrowsing: aAlert.inPrivateBrowsing + }); + }, + showAlertNotification: function(aImageUrl, aTitle, aText, aTextClickable, aCookie, aAlertListener, aName, aBidi, aLang, aDataStr, aPrincipal, aInPrivateBrowsing) { - cpmm.sendAsyncMessage(kMessageAlertNotificationSend, { - imageURL: aImageUrl, - title: aTitle, - text: aText, - clickable: aTextClickable, - cookie: aCookie, - listener: aAlertListener, - id: aName, - dir: aBidi, - lang: aLang, - dataStr: aDataStr, - inPrivateBrowsing: aInPrivateBrowsing - }); + let alert = Cc["@mozilla.org/alert-notification;1"]. + createInstance(Ci.nsIAlertNotification); + + alert.init(aName, aImageUrl, aTitle, aText, aTextClickable, aCookie, + aBidi, aLang, aDataStr, aPrincipal, aInPrivateBrowsing); + + this.showAlert(alert, aAlertListener); }, closeAlert: function(aName) { diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 2132c0f161..7873f5b5b8 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -27,6 +27,7 @@ #include "mozilla/StartupTimeline.h" #include "mozilla/Telemetry.h" #include "mozilla/unused.h" +#include "Navigator.h" #include "URIUtils.h" #include "nsIContent.h" @@ -3128,6 +3129,38 @@ nsDocShell::NameEquals(const char16_t* aName, bool* aResult) return NS_OK; } +NS_IMETHODIMP +nsDocShell::GetCustomUserAgent(nsAString& aCustomUserAgent) +{ + aCustomUserAgent = mCustomUserAgent; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetCustomUserAgent(const nsAString& aCustomUserAgent) +{ + mCustomUserAgent = aCustomUserAgent; + RefPtr win = mScriptGlobal ? + mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr; + if (win) { + ErrorResult ignored; + Navigator* navigator = win->GetNavigator(ignored); + ignored.SuppressException(); + if (navigator) { + navigator->ClearUserAgentCache(); + } + } + + uint32_t childCount = mChildList.Length(); + for (uint32_t i = 0; i < childCount; ++i) { + nsCOMPtr childShell = do_QueryInterface(ChildAt(i)); + if (childShell) { + childShell->SetCustomUserAgent(aCustomUserAgent); + } + } + return NS_OK; +} + /* virtual */ int32_t nsDocShell::ItemType() { @@ -3255,6 +3288,7 @@ nsDocShell::SetDocLoaderParent(nsDocLoader* aParent) // If parent is another docshell, we inherit all their flags for // allowing plugins, scripting etc. bool value; + nsString customUserAgent; nsCOMPtr parentAsDocShell(do_QueryInterface(parent)); if (parentAsDocShell) { if (NS_SUCCEEDED(parentAsDocShell->GetAllowPlugins(&value))) { @@ -3284,6 +3318,10 @@ nsDocShell::SetDocLoaderParent(nsDocLoader* aParent) if (parentAsDocShell->GetIsPrerendered()) { SetIsPrerendered(true); } + if (NS_SUCCEEDED(parentAsDocShell->GetCustomUserAgent(customUserAgent)) && + !customUserAgent.IsEmpty()) { + SetCustomUserAgent(customUserAgent); + } if (NS_FAILED(parentAsDocShell->GetAllowDNSPrefetch(&value))) { value = false; } diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h index 3169436a6a..a68a40f386 100644 --- a/docshell/base/nsDocShell.h +++ b/docshell/base/nsDocShell.h @@ -797,6 +797,7 @@ protected: nsIntRect mBounds; nsString mName; nsString mTitle; + nsString mCustomUserAgent; /** * Content-Type Hint of the most-recently initiated load. Used for diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl index 546e2ca33e..2645e2aa3a 100644 --- a/docshell/base/nsIDocShell.idl +++ b/docshell/base/nsIDocShell.idl @@ -46,7 +46,7 @@ interface nsITabParent; typedef unsigned long nsLoadFlags; -[scriptable, builtinclass, uuid(63adb599-6dc9-4746-972e-c22e9018020b)] +[scriptable, builtinclass, uuid(bc3524bd-023c-4fc8-ace1-472bc999fb12)] interface nsIDocShell : nsIDocShellTreeItem { /** @@ -232,6 +232,11 @@ interface nsIDocShell : nsIDocShellTreeItem */ attribute nsIDOMEventTarget chromeEventHandler; + /** + * This allows chrome to set a custom User agent on a specific docshell + */ + attribute DOMString customUserAgent; + /** * Whether to allow plugin execution */ @@ -245,7 +250,7 @@ interface nsIDocShell : nsIDocShellTreeItem /** * Attribute stating if refresh based redirects can be allowed */ - attribute boolean allowMetaRedirects; + attribute boolean allowMetaRedirects; /** * Attribute stating if it should allow subframes (framesets/iframes) or not diff --git a/docshell/base/timeline/AbstractTimelineMarker.h b/docshell/base/timeline/AbstractTimelineMarker.h index 16c953c52f..5fde6631da 100644 --- a/docshell/base/timeline/AbstractTimelineMarker.h +++ b/docshell/base/timeline/AbstractTimelineMarker.h @@ -49,8 +49,8 @@ public: DOMHighResTimeStamp GetTime() const { return mTime; } MarkerTracingType GetTracingType() const { return mTracingType; } - const uint8_t GetProcessType() const { return mProcessType; }; - const bool IsOffMainThread() const { return mIsOffMainThread; }; + uint8_t GetProcessType() const { return mProcessType; }; + bool IsOffMainThread() const { return mIsOffMainThread; }; private: const char* mName; diff --git a/docshell/test/browser/browser.ini b/docshell/test/browser/browser.ini index 6ee3774c91..7a41d4dde5 100644 --- a/docshell/test/browser/browser.ini +++ b/docshell/test/browser/browser.ini @@ -106,6 +106,7 @@ skip-if = e10s # Bug ?????? - event handler checks event.target is the content d [browser_onbeforeunload_navigation.js] skip-if = e10s [browser_search_notification.js] +[browser_ua_emulation.js] [browser_timelineMarkers-01.js] [browser_timelineMarkers-02.js] [browser_timelineMarkers-03.js] diff --git a/docshell/test/browser/browser_ua_emulation.js b/docshell/test/browser/browser_ua_emulation.js new file mode 100644 index 0000000000..db46944532 --- /dev/null +++ b/docshell/test/browser/browser_ua_emulation.js @@ -0,0 +1,52 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the docShell UA emulation works +add_task(function*() { + yield openUrl("data:text/html;charset=utf-8,"); + + let docshell = content.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + is(docshell.customUserAgent, "", "There should initially be no customUserAgent"); + + docshell.customUserAgent = "foo"; + is(content.navigator.userAgent, "foo", "The user agent should be changed to foo"); + + let frameWin = content.document.querySelector("#test-iframe").contentWindow; + is(frameWin.navigator.userAgent, "foo", "The UA should be passed on to frames."); + + let newFrame = content.document.createElement("iframe"); + content.document.body.appendChild(newFrame); + + let newFrameWin = newFrame.contentWindow; + is(newFrameWin.navigator.userAgent, "foo", "Newly created frames should use the new UA"); + + newFrameWin.location.reload(); + yield waitForEvent(newFrameWin, "load"); + + is(newFrameWin.navigator.userAgent, "foo", "New UA should persist across reloads"); + gBrowser.removeCurrentTab(); +}); + +function waitForEvent(target, event) { + return new Promise(function(resolve) { + target.addEventListener(event, resolve); + }); +} + +function openUrl(url) { + return new Promise(function(resolve, reject) { + window.focus(); + + let tab = window.gBrowser.selectedTab = window.gBrowser.addTab(url); + let linkedBrowser = tab.linkedBrowser; + + linkedBrowser.addEventListener("load", function onload() { + linkedBrowser.removeEventListener("load", onload, true); + resolve(tab); + }, true); + }); +} diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp index e4dc5e405c..fb3680054b 100644 --- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp @@ -356,11 +356,22 @@ Navigator::GetUserAgent(nsAString& aUserAgent) nsCOMPtr codebaseURI; nsCOMPtr window; - if (mWindow && mWindow->GetDocShell()) { + if (mWindow) { window = mWindow; - nsIDocument* doc = mWindow->GetExtantDoc(); - if (doc) { - doc->NodePrincipal()->GetURI(getter_AddRefs(codebaseURI)); + nsIDocShell* docshell = window->GetDocShell(); + nsString customUserAgent; + if (docshell) { + docshell->GetCustomUserAgent(customUserAgent); + + if (!customUserAgent.IsEmpty()) { + aUserAgent = customUserAgent; + return NS_OK; + } + + nsIDocument* doc = mWindow->GetExtantDoc(); + if (doc) { + doc->NodePrincipal()->GetURI(getter_AddRefs(codebaseURI)); + } } } @@ -2693,6 +2704,12 @@ Navigator::AppName(nsAString& aAppName, bool aUsePrefOverriddenValue) aAppName.AssignLiteral("Netscape"); } +void +Navigator::ClearUserAgentCache() +{ + NavigatorBinding::ClearCachedUserAgentValue(this); +} + nsresult Navigator::GetUserAgent(nsPIDOMWindow* aWindow, nsIURI* aURI, bool aIsCallerChrome, diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h index 4ec458f63f..84f69ac876 100644 --- a/dom/base/Navigator.h +++ b/dom/base/Navigator.h @@ -179,6 +179,10 @@ public: bool aIsCallerChrome, nsAString& aUserAgent); + // Clears the user agent cache by calling: + // NavigatorBinding::ClearCachedUserAgentValue(this); + void ClearUserAgentCache(); + already_AddRefed GetDataStores(const nsAString& aName, const nsAString& aOwner, ErrorResult& aRv); diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index 8d69317dcf..c2c7ae242e 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -2573,7 +2573,10 @@ nsDocument::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel, if (sameTypeParent) { mUpgradeInsecureRequests = sameTypeParent->GetDocument()->GetUpgradeInsecureRequests(); + // if the parent document makes use of upgrade-insecure-requests + // then subdocument preloads should always be upgraded. mUpgradeInsecurePreloads = + mUpgradeInsecureRequests || sameTypeParent->GetDocument()->GetUpgradeInsecurePreloads(); } } diff --git a/dom/bindings/Errors.msg b/dom/bindings/Errors.msg index acc06573bb..d79e12f3fd 100644 --- a/dom/bindings/Errors.msg +++ b/dom/bindings/Errors.msg @@ -64,6 +64,7 @@ MSG_DEF(MSG_INVALID_HEADER_SEQUENCE, 0, JSEXN_TYPEERR, "Headers require name/val MSG_DEF(MSG_PERMISSION_DENIED_TO_PASS_ARG, 1, JSEXN_TYPEERR, "Permission denied to pass cross-origin object as {0}.") MSG_DEF(MSG_MISSING_REQUIRED_DICTIONARY_MEMBER, 1, JSEXN_TYPEERR, "Missing required {0}.") MSG_DEF(MSG_INVALID_REQUEST_METHOD, 1, JSEXN_TYPEERR, "Invalid request method {0}.") +MSG_DEF(MSG_INVALID_REQUEST_MODE, 1, JSEXN_TYPEERR, "Invalid request mode {0}.") MSG_DEF(MSG_FETCH_BODY_CONSUMED_ERROR, 0, JSEXN_TYPEERR, "Body has already been consumed.") MSG_DEF(MSG_RESPONSE_INVALID_STATUSTEXT_ERROR, 0, JSEXN_TYPEERR, "Response statusText may not contain newline or carriage return.") MSG_DEF(MSG_FETCH_FAILED, 0, JSEXN_TYPEERR, "NetworkError when attempting to fetch resource.") diff --git a/dom/cache/DBSchema.cpp b/dom/cache/DBSchema.cpp index e75ff68093..4e427877cb 100644 --- a/dom/cache/DBSchema.cpp +++ b/dom/cache/DBSchema.cpp @@ -37,7 +37,7 @@ const int32_t kFirstShippedSchemaVersion = 15; namespace { // Update this whenever the DB schema is changed. -const int32_t kLatestSchemaVersion = 17; +const int32_t kLatestSchemaVersion = 19; // --------- // The following constants define the SQL schema. These are defined in the @@ -192,7 +192,8 @@ static_assert(int(HeadersGuardEnum::None) == 0 && static_assert(int(RequestMode::Same_origin) == 0 && int(RequestMode::No_cors) == 1 && int(RequestMode::Cors) == 2 && - int(RequestMode::EndGuard_) == 3, + int(RequestMode::Navigate) == 3 && + int(RequestMode::EndGuard_) == 4, "RequestMode values are as expected"); static_assert(int(RequestCredentials::Omit) == 0 && int(RequestCredentials::Same_origin) == 1 && @@ -204,8 +205,7 @@ static_assert(int(RequestCache::Default) == 0 && int(RequestCache::Reload) == 2 && int(RequestCache::No_cache) == 3 && int(RequestCache::Force_cache) == 4 && - int(RequestCache::Only_if_cached) == 5 && - int(RequestCache::EndGuard_) == 6, + int(RequestCache::EndGuard_) == 5, "RequestCache values are as expected"); static_assert(int(RequestRedirect::Follow) == 0 && int(RequestRedirect::Error) == 1 && @@ -2411,11 +2411,15 @@ struct Migration // the version by a single increment. Don't skip versions. nsresult MigrateFrom15To16(mozIStorageConnection* aConn); nsresult MigrateFrom16To17(mozIStorageConnection* aConn); +nsresult MigrateFrom17To18(mozIStorageConnection* aConn); +nsresult MigrateFrom18To19(mozIStorageConnection* aConn); // Configure migration functions to run for the given starting version. Migration sMigrationList[] = { Migration(15, MigrateFrom15To16), Migration(16, MigrateFrom16To17), + Migration(17, MigrateFrom17To18), + Migration(18, MigrateFrom18To19), }; uint32_t sMigrationListLength = sizeof(sMigrationList) / sizeof(Migration); @@ -2653,6 +2657,69 @@ MigrateFrom16To17(mozIStorageConnection* aConn) return rv; } +nsresult +MigrateFrom17To18(mozIStorageConnection* aConn) +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aConn); + + mozStorageTransaction trans(aConn, true, + mozIStorageConnection::TRANSACTION_IMMEDIATE); + + // This migration is needed in order to remove "only-if-cached" RequestCache + // values from the database. This enum value was removed from the spec in + // https://github.com/whatwg/fetch/issues/39 but we unfortunately happily + // accepted this value in the Request constructor. + // + // There is no good value to upgrade this to, so we just stick to "default". + + static_assert(int(RequestCache::Default) == 0, + "This is where the 0 below comes from!"); + nsresult rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "UPDATE entries SET request_cache = 0 " + "WHERE request_cache = 5;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = aConn->SetSchemaVersion(18); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + return rv; +} + +nsresult +MigrateFrom18To19(mozIStorageConnection* aConn) +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aConn); + + mozStorageTransaction trans(aConn, true, + mozIStorageConnection::TRANSACTION_IMMEDIATE); + + // This migration is needed in order to update the RequestMode values for + // Request objects corresponding to a navigation content policy type to + // "navigate". + + static_assert(int(nsIContentPolicy::TYPE_DOCUMENT) == 6 && + int(nsIContentPolicy::TYPE_SUBDOCUMENT) == 7 && + int(nsIContentPolicy::TYPE_INTERNAL_FRAME) == 28 && + int(nsIContentPolicy::TYPE_INTERNAL_IFRAME) == 29 && + int(nsIContentPolicy::TYPE_REFRESH) == 8 && + int(RequestMode::Navigate) == 3, + "This is where the numbers below come from!"); + nsresult rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "UPDATE entries SET request_mode = 3 " + "WHERE request_contentpolicytype IN (6, 7, 28, 29, 8);" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = aConn->SetSchemaVersion(19); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + return rv; +} + + } // anonymous namespace } // namespace db diff --git a/dom/cache/test/xpcshell/head.js b/dom/cache/test/xpcshell/head.js index 3d51929b35..fb09774499 100644 --- a/dom/cache/test/xpcshell/head.js +++ b/dom/cache/test/xpcshell/head.js @@ -18,6 +18,9 @@ var sts = Cc['@mozilla.org/network/stream-transport-service;1'] var hash = Cc['@mozilla.org/security/hash;1'] .createInstance(Ci.nsICryptoHash); +var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); +prefs.setBoolPref("dom.requestcache.enabled", true); + // Expose Cache and Fetch symbols on the global Cu.importGlobalProperties(['caches', 'fetch']); diff --git a/dom/cache/test/xpcshell/schema_15_profile.zip b/dom/cache/test/xpcshell/schema_15_profile.zip index 6d742275b30921a721dddbcbbd12439d3081bdce..32cc8f2eeb144a881f345e1468a155a0f959a5db 100644 GIT binary patch literal 3111 zcmWIWW@h1H0D-DrJr6JgN^mg9Fcg>M7bT{r>W7AKGB7V!nGtqm@w~9o3T_5QmamKq z3@jj}0XR(%Kr$gEH7&6;r-Vq87#I|hox&iF#iZnnqWoN_(**yBGT?BU2m=!X2hcD@ z2B%-e^TmKHSs+%xZJ=ImYDr>BVo4&{(;$;U7|qkk2~)j)-PPXM)7aRkYq?m%-dwbk z6)7kR>WxtXQx>;<$%)AsP;YDjhAv)jWB`p#KfEUV132Uv8MFu)3pTu1uedNLvm_Pl zp(Q{&)?vot+qdm~f#l%y7MbPDvXjjjwWH`1v?u2x!X_oZLgr0w(9 zn}5kwsk8CvKk?VdBxj$9bJ~1~pG7G@uUucA#%6x${?hZjYi8Qdu6<_C#L)2H?e$zo zV`H}`n=3et1Nn+$FlFHHWPp9Z@>NOx^>N&3|Ds7m>oOr zs{NflFIw~6St*Y<&-I?(mhRrXw)5@ds-orFefNj0sdk-vA$Em@qw379yX}6zJiD*p zMD0F_HnVlcbq2x<@7#=AKI4s~*6!O;(^oy3`i*^Cu=0PunHvs%taumk;l!-Ib;qWs zzWja3e$BMEg5S5i{IQfTea_r@)v7mDZ+}hoY~TFqN>kp_J-aR~m5N*YDsRp$m*{IVO6MMPKUb`vW zT^1i|wLk8g5Oewc1->FZ=e}4^HTjcucdgl+s2PW3a7l5ocBY zM3+VSmXYeKtv>y^nAtK-=BbR#*3|3S*RD3c+Qc9H_bq3#X;zwvCHvcj0p;J-t0(*V zrnOx@y2LNl-r&rc7b`AWNry?ko%Uj%dZ6Q$cPIR!iu<`wb65Vo#9p>9A@y+g_wfD9 z;G*rg#}CJUC+0ipgLw~Fyd+ot`1PNEp*WadFlkBeyo)7Ynj1f`|69(<2^Lhpsc`*2 zm^G*3;`jgNt#M$^0sFOAvwOOXzaMWuetvl`<6(hg1+PkzLwWdlmcPH<+7;A0J2<%L zaAf|KC#u(@=X3pUZ}Ar08aMmro5#!T>ps4@!*9Ot*W1JC-G|T3x*lSFH!Ad6=&Jqt zs{3N&-c){EXT8}hRxkea)b-QWKUwy4%bB|~_S(!YPS^84e`ejz%}=w9=kv5(|G%GW zFW4~+$?tRGt7>fa{$I;n_n#jmcHpFV?f?JPFrTgQk=Xk@e`)Ocbt$n=ucSF^?OeD_ z%5-^D{rR;PbqZJ49&fGQ`5NfGdhse?6$1q7M?B8|Jo20A;`9IZoG@En@h&cZP*&w^ z(xS~2C>V6*%l?NtzKGYUSbZu>hM1s14FT2Vn9J8gCGOL`f%Or z+!`6FIXU?X<@rT9DFNP$OmfV)ni&!xKLCLM!&^rXjc9bRLK+=tbuT{C1Q?J_b3!r= z(EuTADte@eqnO%*#Z-7>1ZX&x<^sgYxSAp`Co?cCX}pWuaHNI_K3gEcjM)Hz*#Zh? zUtrFH1v4yQpk9DAM}Ss zA0`N(iBos>*~b1j3L1;1pg&+Rhm!3_`G&*r!9NMvL35kCS2_d#kOUqEc#4pa`18+i`)uOFpmUgu+vRcrB> z+P-&myc>VxrZWOxcW)wuMc$_x+q2BL7Z>@!2hMVI2Pz{fm26(9brf~zM5^fNNdUDi zx~O1bRD53{HIcE~^T&K~t_HZvGDFO@_TcOFNYzr-?{d|z_XtX&I<5Tv#Iak457@+I zho6iCt&?`gqsp7LM@_V>#Nc%|D;A$gt*^S#0HG3V0?G@xWE-g&knl60> zw<=ozbU6F4EwPN6no|WObqDh6Vh2OJ8YGW>7a2-I+sg*uz*GDTT?r1ni`14p%R*NF z%Rz}ht*XwbtDr3=Cug0gxQ%sqgOMOEvrw3VySlgcKB3@gd9S)<`5}RtDRix2)q^A| zv6IUmNp|*aK=E7lApEN9u*X#z-w0##p8QJ68pyt$D~^7QL@k4CA2xK0L$Sa;9Ithz z?6XkUnwTvdln)f`;GLIF_~)@8u;`gdmE_O`ZQZOoTsYu(6^r$OC~#Y6Zc| zwS!@`S z)U|4))iOSzptkSL(H>jh^zc%|8&!+UbVQ(5XK*8T^g*u6Sj_6TBc6{+ozfZ-HAQtA zVGut73y%3lbv0dl1jPw*);5KD1LsL-<$1P}pm+OPwT+_Js1Di+b8b zDtg)%&AQY(dNc1%$qS@5n-l`gAvdvos`$wC;mi7E^--%{`=U(D)9wY@?rcbHFYh*R z4|4a4*iKl|nTLOC_a|w2QX66{g%nV3r%8imt>!a4oGjS;2R)U7>aU!_EiM{@s9qaO zzHsoGWs~j^fvQO8GLVyxt$)`4vWIsZvKd6Z%?S>}k3Q4P!o`viOle=q-AA{>bpz=w z#6$_llfc6UlzOuh%^rJkR~)?ed_LRKenX>922*GY3YK=mY~oLoEOz+U4qt#aIk~+; zd0w$XGh>?;gS+f#22a}94V++S;fdcVCOMciwI!xjFg(j~zHRJreYdq3h;spjOkRFThE>>*P~*&=@qo^Jf%Vb5 z<}a8b4%DWC{a6S6_T6#B^BRfu?PRZJ{H`4Pmd2_^dhf}L=UNQOU0pZ(dq>h*ZbO3# zDFjKPWK34wU1X!85EIaSse6yZ?rh0;XQWuB5Ew8-&ydMb-HPYH(&U|4;-S(cQBxB0 z8uJ;`ULXF1!tO!(i0|NPh(A;NwvF$Xklu~R1oWwJ#I10+d|K+R8!D55r#{&{+fg$t zrg@)srmSfq7SlOvP4O|O){k1dudf;YMJ

J@R+XiGfT1FTxHas zNb;h7q9V^k8GIqi!~{N@1G)ND^{<|gE#GzISKw%yFi=l3JRWX@H^rM6nc>WEMw?+9 zVMZ9}Ml&eP69dD;p<%{&!eO-E2P+t>udYP&o?n4jR;zoz@`knBTZ?i#J0C|!j!j6o zlcVZ{fzA6yLuqz*os91?pl_SJH@DYL(?0FClQaOxjMXAM}wgM1Pd8)A-3< j{5WH$q)&vV@+zUe= diff --git a/dom/cache/test/xpcshell/test_migration.js b/dom/cache/test/xpcshell/test_migration.js index 1b4b217aa8..31acc4cb7b 100644 --- a/dom/cache/test/xpcshell/test_migration.js +++ b/dom/cache/test/xpcshell/test_migration.js @@ -20,6 +20,8 @@ function run_test() { requestList.forEach(function(request) { ok(request, 'each request in list should be non-null'); ok(request.redirect === 'follow', 'request.redirect should default to "follow"'); + ok(request.cache === 'default', 'request.cache should have been updated to "default"' + request.cache); + ok(request.mode === 'navigate', 'request.mode should have been updated to "navigate"'); }); return Promise.all(requestList.map(function(request) { return cache.match(request); diff --git a/dom/fetch/FetchDriver.cpp b/dom/fetch/FetchDriver.cpp index a5e17ccb48..887e9748e4 100644 --- a/dom/fetch/FetchDriver.cpp +++ b/dom/fetch/FetchDriver.cpp @@ -161,7 +161,8 @@ FetchDriver::HttpFetch() nsSecurityFlags secFlags = nsILoadInfo::SEC_ABOUT_BLANK_INHERITS; if (mRequest->Mode() == RequestMode::Cors) { secFlags |= nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS; - } else if (mRequest->Mode() == RequestMode::Same_origin) { + } else if (mRequest->Mode() == RequestMode::Same_origin || + mRequest->Mode() == RequestMode::Navigate) { secFlags |= nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS; } else if (mRequest->Mode() == RequestMode::No_cors) { secFlags |= nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS; diff --git a/dom/fetch/InternalRequest.cpp b/dom/fetch/InternalRequest.cpp index 9085645c60..04b3d6c530 100644 --- a/dom/fetch/InternalRequest.cpp +++ b/dom/fetch/InternalRequest.cpp @@ -276,14 +276,13 @@ InternalRequest::MapChannelToRequestMode(nsIChannel* aChannel) nsCOMPtr loadInfo; MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aChannel->GetLoadInfo(getter_AddRefs(loadInfo)))); - // RequestMode deviates from our internal security mode for navigations. - // While navigations normally allow cross origin we must set a same-origin - // RequestMode to get the correct service worker interception restrictions - // in place. - // TODO: remove the worker override once securityMode is fully implemented (bug 1189945) nsContentPolicyType contentPolicy = loadInfo->InternalContentPolicyType(); - if (IsNavigationContentPolicy(contentPolicy) || - IsWorkerContentPolicy(contentPolicy)) { + if (IsNavigationContentPolicy(contentPolicy)) { + return RequestMode::Navigate; + } + + // TODO: remove the worker override once securityMode is fully implemented (bug 1189945) + if (IsWorkerContentPolicy(contentPolicy)) { return RequestMode::Same_origin; } @@ -320,6 +319,7 @@ InternalRequest::MapChannelToRequestMode(nsIChannel* aChannel) uint32_t corsMode; MOZ_ALWAYS_TRUE(NS_SUCCEEDED(httpChannel->GetCorsMode(&corsMode))); + MOZ_ASSERT(corsMode != nsIHttpChannelInternal::CORS_MODE_NAVIGATE); // This cast is valid due to static asserts in ServiceWorkerManager.cpp. return static_cast(corsMode); diff --git a/dom/fetch/Request.cpp b/dom/fetch/Request.cpp index 36eea47eae..05c1afeba8 100644 --- a/dom/fetch/Request.cpp +++ b/dom/fetch/Request.cpp @@ -284,6 +284,11 @@ Request::Constructor(const GlobalObject& aGlobal, aInit.mCredentials.WasPassed() ? aInit.mCredentials.Value() : fallbackCredentials; + if (mode == RequestMode::Navigate) { + aRv.ThrowTypeError(NS_LITERAL_STRING("navigate")); + return nullptr; + } + if (mode != RequestMode::EndGuard_) { request->ClearCreatedByFetchEvent(); request->SetMode(mode); diff --git a/dom/gamepad/cocoa/CocoaGamepad.cpp b/dom/gamepad/cocoa/CocoaGamepad.cpp index ecf1158d1d..897b62cdb1 100644 --- a/dom/gamepad/cocoa/CocoaGamepad.cpp +++ b/dom/gamepad/cocoa/CocoaGamepad.cpp @@ -93,7 +93,7 @@ class Gamepad { // Index given by our superclass. uint32_t mSuperIndex; - const bool isDpad(IOHIDElementRef element) const + bool isDpad(IOHIDElementRef element) const { return element == mDpad; } diff --git a/dom/inputport/InputPortData.cpp b/dom/inputport/InputPortData.cpp index 9fba2b7a7b..887dc1d37b 100644 --- a/dom/inputport/InputPortData.cpp +++ b/dom/inputport/InputPortData.cpp @@ -103,7 +103,7 @@ InputPortData::GetId() const return mId; } -const InputPortType +InputPortType InputPortData::GetType() const { return ToInputPortType(mType); diff --git a/dom/inputport/InputPortData.h b/dom/inputport/InputPortData.h index e29a56ac8b..a9deba745a 100644 --- a/dom/inputport/InputPortData.h +++ b/dom/inputport/InputPortData.h @@ -33,7 +33,7 @@ public: const nsString& GetId() const; - const InputPortType GetType() const; + InputPortType GetType() const; private: ~InputPortData(); diff --git a/dom/ipc/ContentBridgeChild.cpp b/dom/ipc/ContentBridgeChild.cpp index 3684e88e96..67d583d826 100644 --- a/dom/ipc/ContentBridgeChild.cpp +++ b/dom/ipc/ContentBridgeChild.cpp @@ -98,7 +98,7 @@ ContentBridgeChild::SendPBrowserConstructor(PBrowserChild* aActor, jsipc::CPOWManager* ContentBridgeChild::GetCPOWManager() { - if (PJavaScriptChild* c = LoneManagedOrNull(ManagedPJavaScriptChild())) { + if (PJavaScriptChild* c = LoneManagedOrNullAsserts(ManagedPJavaScriptChild())) { return CPOWManagerFor(c); } return CPOWManagerFor(SendPJavaScriptConstructor()); diff --git a/dom/ipc/ContentBridgeParent.cpp b/dom/ipc/ContentBridgeParent.cpp index 2b5de0629a..b4672f02e3 100644 --- a/dom/ipc/ContentBridgeParent.cpp +++ b/dom/ipc/ContentBridgeParent.cpp @@ -180,7 +180,7 @@ ContentBridgeParent::NotifyTabDestroyed() jsipc::CPOWManager* ContentBridgeParent::GetCPOWManager() { - if (PJavaScriptParent* p = LoneManagedOrNull(ManagedPJavaScriptParent())) { + if (PJavaScriptParent* p = LoneManagedOrNullAsserts(ManagedPJavaScriptParent())) { return CPOWManagerFor(p); } return nullptr; diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index c8400c21c7..341cc347e2 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -1718,7 +1718,7 @@ ContentChild::DeallocPTestShellChild(PTestShellChild* shell) jsipc::CPOWManager* ContentChild::GetCPOWManager() { - if (PJavaScriptChild* c = LoneManagedOrNull(ManagedPJavaScriptChild())) { + if (PJavaScriptChild* c = LoneManagedOrNullAsserts(ManagedPJavaScriptChild())) { return CPOWManagerFor(c); } return CPOWManagerFor(SendPJavaScriptConstructor()); @@ -2221,7 +2221,7 @@ ContentChild::ProcessingError(Result aCode, const char* aReason) } #if defined(MOZ_CRASHREPORTER) && !defined(MOZ_B2G) - if (PCrashReporterChild* c = LoneManagedOrNull(ManagedPCrashReporterChild())) { + if (PCrashReporterChild* c = LoneManagedOrNullAsserts(ManagedPCrashReporterChild())) { CrashReporterChild* crashReporter = static_cast(c); nsDependentCString reason(aReason); diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index a8491cc670..05a1eb9df6 100755 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -2152,7 +2152,7 @@ ContentParent::ActorDestroy(ActorDestroyReason why) // There's a window in which child processes can crash // after IPC is established, but before a crash reporter // is created. - if (PCrashReporterParent* p = LoneManagedOrNull(ManagedPCrashReporterParent())) { + if (PCrashReporterParent* p = LoneManagedOrNullAsserts(ManagedPCrashReporterParent())) { CrashReporterParent* crashReporter = static_cast(p); @@ -2333,7 +2333,7 @@ ContentParent::NotifyTabDestroyed(const TabId& aTabId, jsipc::CPOWManager* ContentParent::GetCPOWManager() { - if (PJavaScriptParent* p = LoneManagedOrNull(ManagedPJavaScriptParent())) { + if (PJavaScriptParent* p = LoneManagedOrNullAsserts(ManagedPJavaScriptParent())) { return CPOWManagerFor(p); } return nullptr; @@ -2354,7 +2354,7 @@ ContentParent::DestroyTestShell(TestShellParent* aTestShell) TestShellParent* ContentParent::GetTestShellSingleton() { - PTestShellParent* p = LoneManagedOrNull(ManagedPTestShellParent()); + PTestShellParent* p = LoneManagedOrNullAsserts(ManagedPTestShellParent()); return static_cast(p); } @@ -2741,6 +2741,9 @@ bool ContentParent::RecvReadDataStorageArray(const nsString& aFilename, InfallibleTArray* aValues) { + // Ensure the SSS is initialized before we try to use its storage. + nsCOMPtr sss = do_GetService("@mozilla.org/ssservice;1"); + RefPtr storage = DataStorage::Get(aFilename); storage->GetAll(aValues); return true; @@ -3635,7 +3638,7 @@ ContentParent::KillHard(const char* aReason) // We're about to kill the child process associated with this content. // Something has gone wrong to get us here, so we generate a minidump // of the parent and child for submission to the crash server. - if (PCrashReporterParent* p = LoneManagedOrNull(ManagedPCrashReporterParent())) { + if (PCrashReporterParent* p = LoneManagedOrNullAsserts(ManagedPCrashReporterParent())) { CrashReporterParent* crashReporter = static_cast(p); // GeneratePairedMinidump creates two minidumps for us - the main @@ -4395,23 +4398,24 @@ ContentParent::HasNotificationPermission(const IPC::Principal& aPrincipal) } bool -ContentParent::RecvShowAlertNotification(const nsString& aImageUrl, const nsString& aTitle, - const nsString& aText, const bool& aTextClickable, - const nsString& aCookie, const nsString& aName, - const nsString& aBidi, const nsString& aLang, - const nsString& aData, - const IPC::Principal& aPrincipal, - const bool& aInPrivateBrowsing) +ContentParent::RecvShowAlert(const AlertNotificationType& aAlert) { - if (!HasNotificationPermission(aPrincipal)) { + nsCOMPtr alert(dont_AddRef(aAlert)); + if (NS_WARN_IF(!alert)) { + return true; + } + + nsCOMPtr principal; + nsresult rv = alert->GetPrincipal(getter_AddRefs(principal)); + if (NS_WARN_IF(NS_FAILED(rv)) || + !HasNotificationPermission(IPC::Principal(principal))) { + return true; } nsCOMPtr sysAlerts(do_GetService(NS_ALERTSERVICE_CONTRACTID)); if (sysAlerts) { - sysAlerts->ShowAlertNotification(aImageUrl, aTitle, aText, aTextClickable, - aCookie, this, aName, aBidi, aLang, - aData, aPrincipal, aInPrivateBrowsing); + sysAlerts->ShowAlert(alert, this); } return true; } diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h index 778b3cd735..41fe68b081 100644 --- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -780,13 +780,7 @@ private: bool HasNotificationPermission(const IPC::Principal& aPrincipal); - virtual bool RecvShowAlertNotification(const nsString& aImageUrl, const nsString& aTitle, - const nsString& aText, const bool& aTextClickable, - const nsString& aCookie, const nsString& aName, - const nsString& aBidi, const nsString& aLang, - const nsString& aData, - const IPC::Principal& aPrincipal, - const bool& aInPrivateBrowsing) override; + virtual bool RecvShowAlert(const AlertNotificationType& aAlert) override; virtual bool RecvCloseAlert(const nsString& aName, const IPC::Principal& aPrincipal) override; diff --git a/dom/ipc/CrashReporterChild.cpp b/dom/ipc/CrashReporterChild.cpp index 27d3dee2dd..8174452e70 100644 --- a/dom/ipc/CrashReporterChild.cpp +++ b/dom/ipc/CrashReporterChild.cpp @@ -35,7 +35,7 @@ CrashReporterChild::GetCrashReporter() if (!reporters) { return nullptr; } - return LoneManagedOrNull(*reporters); + return LoneManagedOrNullAsserts(*reporters); } } // namespace dom diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl index 9dbe11cd56..1bc91cbdd8 100644 --- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -74,6 +74,7 @@ include "mozilla/dom/PContentBridgeParent.h"; include "mozilla/dom/quota/SerializationHelpers.h"; using GeoPosition from "nsGeoPositionIPCSerialiser.h"; +using AlertNotificationType from "mozilla/AlertNotificationIPCSerializer.h"; using struct ChromePackage from "mozilla/chrome/RegistryMessageUtils.h"; using struct SubstitutionMapping from "mozilla/chrome/RegistryMessageUtils.h"; @@ -864,17 +865,7 @@ parent: CpowEntry[] aCpows, Principal aPrincipal) returns (StructuredCloneData[] retval); - ShowAlertNotification(nsString imageUrl, - nsString title, - nsString text, - bool textClickable, - nsString cookie, - nsString name, - nsString bidi, - nsString lang, - nsString data, - Principal principal, - bool inPrivateBrowsing); + ShowAlert(AlertNotificationType alert); CloseAlert(nsString name, Principal principal); diff --git a/dom/ipc/TabParent.cpp b/dom/ipc/TabParent.cpp index 74322b124c..ad440b1f74 100644 --- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -2324,7 +2324,7 @@ TabParent::GetTabIdFrom(nsIDocShell *docShell) RenderFrameParent* TabParent::GetRenderFrame() { - PRenderFrameParent* p = LoneManagedOrNull(ManagedPRenderFrameParent()); + PRenderFrameParent* p = LoneManagedOrNullAsserts(ManagedPRenderFrameParent()); return static_cast(p); } diff --git a/dom/locales/en-US/chrome/layout/css.properties b/dom/locales/en-US/chrome/layout/css.properties index 5f8dde727e..3304f9629a 100644 --- a/dom/locales/en-US/chrome/layout/css.properties +++ b/dom/locales/en-US/chrome/layout/css.properties @@ -172,3 +172,6 @@ PEExpectedVariableName=Expected identifier for variable name but found '%1$S'. PEExpectedVariableFallback=Expected variable reference fallback after ','. PEExpectedVariableCommaOrCloseParen=Expected ',' or ')' after variable name in variable reference but found '%1$S'. PESubgridNotSupported=Support for the 'subgrid' keyword of CSS Grid is not enabled. +PEMoreThanOneGridRepeatAutoFillInNameList=Only one repeat(auto-fill, ...) is allowed in a name list for a subgrid. +PEMoreThanOneGridRepeatAutoFillFitInTrackList=Only one repeat(auto-fill, ...) or repeat(auto-fit, ...) is allowed in a track list. +PEMoreThanOneGridRepeatTrackSize=Only one track size is allowed inside repeat(auto-fit/auto-fill, ...). diff --git a/dom/media/android/AndroidMediaDecoder.h b/dom/media/android/AndroidMediaDecoder.h index aa82abc041..b38524f93d 100644 --- a/dom/media/android/AndroidMediaDecoder.h +++ b/dom/media/android/AndroidMediaDecoder.h @@ -17,7 +17,7 @@ class AndroidMediaDecoder : public MediaDecoder public: AndroidMediaDecoder(MediaDecoderOwner* aOwner, const nsACString& aType); - const nsresult GetContentType(nsACString& aType) const { + nsresult GetContentType(nsACString& aType) const { aType = mType; return NS_OK; } diff --git a/dom/media/gmp/GMPContentParent.h b/dom/media/gmp/GMPContentParent.h index c2e2f0be13..07aab7e8bd 100644 --- a/dom/media/gmp/GMPContentParent.h +++ b/dom/media/gmp/GMPContentParent.h @@ -56,7 +56,7 @@ public: { mPluginId = aPluginId; } - const uint32_t GetPluginId() + uint32_t GetPluginId() const { return mPluginId; } diff --git a/dom/media/gmp/GMPDecryptorParent.h b/dom/media/gmp/GMPDecryptorParent.h index 1a79207088..15377e17bf 100644 --- a/dom/media/gmp/GMPDecryptorParent.h +++ b/dom/media/gmp/GMPDecryptorParent.h @@ -29,7 +29,7 @@ public: // GMPDecryptorProxy - virtual const uint32_t GetPluginId() const override { return mPluginId; } + virtual uint32_t GetPluginId() const override { return mPluginId; } virtual nsresult Init(GMPDecryptorProxyCallback* aCallback) override; diff --git a/dom/media/gmp/GMPDecryptorProxy.h b/dom/media/gmp/GMPDecryptorProxy.h index 56b89629c9..099259e056 100644 --- a/dom/media/gmp/GMPDecryptorProxy.h +++ b/dom/media/gmp/GMPDecryptorProxy.h @@ -59,7 +59,7 @@ class GMPDecryptorProxy { public: ~GMPDecryptorProxy() {} - virtual const uint32_t GetPluginId() const = 0; + virtual uint32_t GetPluginId() const = 0; virtual nsresult Init(GMPDecryptorProxyCallback* aCallback) = 0; diff --git a/dom/media/gmp/GMPEncryptedBufferDataImpl.cpp b/dom/media/gmp/GMPEncryptedBufferDataImpl.cpp index c72c7d86e6..206d7b42a3 100644 --- a/dom/media/gmp/GMPEncryptedBufferDataImpl.cpp +++ b/dom/media/gmp/GMPEncryptedBufferDataImpl.cpp @@ -99,7 +99,7 @@ GMPStringListImpl::GMPStringListImpl(const nsTArray& aStrings) { } -const uint32_t +uint32_t GMPStringListImpl::Size() const { return mStrings.Length(); diff --git a/dom/media/gmp/GMPEncryptedBufferDataImpl.h b/dom/media/gmp/GMPEncryptedBufferDataImpl.h index c79fcde794..023079be7b 100644 --- a/dom/media/gmp/GMPEncryptedBufferDataImpl.h +++ b/dom/media/gmp/GMPEncryptedBufferDataImpl.h @@ -19,7 +19,7 @@ class GMPStringListImpl : public GMPStringList { public: explicit GMPStringListImpl(const nsTArray& aStrings); - virtual const uint32_t Size() const override; + virtual uint32_t Size() const override; virtual void StringAt(uint32_t aIndex, const char** aOutString, uint32_t *aOutLength) const override; virtual ~GMPStringListImpl() override; diff --git a/dom/media/gmp/GMPParent.cpp b/dom/media/gmp/GMPParent.cpp index 9154ca090a..b7e7032205 100644 --- a/dom/media/gmp/GMPParent.cpp +++ b/dom/media/gmp/GMPParent.cpp @@ -582,7 +582,7 @@ void GMPParent::GetCrashID(nsString& aResult) { CrashReporterParent* cr = - static_cast(LoneManagedOrNull(ManagedPCrashReporterParent())); + static_cast(LoneManagedOrNullAsserts(ManagedPCrashReporterParent())); if (NS_WARN_IF(!cr)) { return; } @@ -936,7 +936,7 @@ GMPParent::GetVersion() const return mVersion; } -const uint32_t +uint32_t GMPParent::GetPluginId() const { return mPluginId; diff --git a/dom/media/gmp/GMPParent.h b/dom/media/gmp/GMPParent.h index 794b1a3b32..ff6372da9a 100644 --- a/dom/media/gmp/GMPParent.h +++ b/dom/media/gmp/GMPParent.h @@ -122,7 +122,7 @@ public: const nsCString& GetDisplayName() const; const nsCString& GetVersion() const; - const uint32_t GetPluginId() const; + uint32_t GetPluginId() const; nsString GetPluginBaseName() const; // Returns true if a plugin can be or is being used across multiple NodeIds. diff --git a/dom/media/gmp/GMPService.h b/dom/media/gmp/GMPService.h index 0dac2d8a75..8997cf60db 100644 --- a/dom/media/gmp/GMPService.h +++ b/dom/media/gmp/GMPService.h @@ -105,7 +105,7 @@ protected: nsIDocument* aDocument); void Run(const nsACString& aPluginName); bool IsStillValid(); - const uint32_t GetPluginId() const { return mPluginId; } + uint32_t GetPluginId() const { return mPluginId; } private: virtual ~GMPCrashCallback() { MOZ_ASSERT(NS_IsMainThread()); } diff --git a/dom/media/gmp/GMPVideoDecoderParent.h b/dom/media/gmp/GMPVideoDecoderParent.h index 202b763e5b..4b0ebaf466 100644 --- a/dom/media/gmp/GMPVideoDecoderParent.h +++ b/dom/media/gmp/GMPVideoDecoderParent.h @@ -45,7 +45,7 @@ public: int64_t aRenderTimeMs = -1) override; virtual nsresult Reset() override; virtual nsresult Drain() override; - virtual const uint32_t GetPluginId() const override { return mPluginId; } + virtual uint32_t GetPluginId() const override { return mPluginId; } virtual const nsCString& GetDisplayName() const override; // GMPSharedMemManager diff --git a/dom/media/gmp/GMPVideoDecoderProxy.h b/dom/media/gmp/GMPVideoDecoderProxy.h index e2ba4b9d34..6043f5cd25 100644 --- a/dom/media/gmp/GMPVideoDecoderProxy.h +++ b/dom/media/gmp/GMPVideoDecoderProxy.h @@ -46,7 +46,7 @@ public: int64_t aRenderTimeMs = -1) = 0; virtual nsresult Reset() = 0; virtual nsresult Drain() = 0; - virtual const uint32_t GetPluginId() const = 0; + virtual uint32_t GetPluginId() const = 0; // Call to tell GMP/plugin the consumer will no longer use this // interface/codec. diff --git a/dom/media/gmp/GMPVideoEncoderParent.h b/dom/media/gmp/GMPVideoEncoderParent.h index fcdc00cccf..8f6a3f6fa9 100644 --- a/dom/media/gmp/GMPVideoEncoderParent.h +++ b/dom/media/gmp/GMPVideoEncoderParent.h @@ -45,7 +45,7 @@ public: virtual GMPErr SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT) override; virtual GMPErr SetRates(uint32_t aNewBitRate, uint32_t aFrameRate) override; virtual GMPErr SetPeriodicKeyFrames(bool aEnable) override; - virtual const uint32_t GetPluginId() const override { return mPluginId; } + virtual uint32_t GetPluginId() const override { return mPluginId; } // GMPSharedMemManager virtual bool Alloc(size_t aSize, Shmem::SharedMemory::SharedMemoryType aType, Shmem* aMem) override diff --git a/dom/media/gmp/GMPVideoEncoderProxy.h b/dom/media/gmp/GMPVideoEncoderProxy.h index 6990b1b7f9..655b1e9ae3 100644 --- a/dom/media/gmp/GMPVideoEncoderProxy.h +++ b/dom/media/gmp/GMPVideoEncoderProxy.h @@ -46,7 +46,7 @@ public: virtual GMPErr SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT) = 0; virtual GMPErr SetRates(uint32_t aNewBitRate, uint32_t aFrameRate) = 0; virtual GMPErr SetPeriodicKeyFrames(bool aEnable) = 0; - virtual const uint32_t GetPluginId() const = 0; + virtual uint32_t GetPluginId() const = 0; // Call to tell GMP/plugin the consumer will no longer use this // interface/codec. diff --git a/dom/media/gmp/gmp-api/gmp-decryption.h b/dom/media/gmp/gmp-api/gmp-decryption.h index 1b2225de52..05efbce72f 100644 --- a/dom/media/gmp/gmp-api/gmp-decryption.h +++ b/dom/media/gmp/gmp-api/gmp-decryption.h @@ -21,7 +21,7 @@ class GMPStringList { public: - virtual const uint32_t Size() const = 0; + virtual uint32_t Size() const = 0; virtual void StringAt(uint32_t aIndex, const char** aOutString, uint32_t* aOutLength) const = 0; diff --git a/dom/media/omx/OMXCodecWrapper.cpp b/dom/media/omx/OMXCodecWrapper.cpp index df43dd8a42..3266b47437 100644 --- a/dom/media/omx/OMXCodecWrapper.cpp +++ b/dom/media/omx/OMXCodecWrapper.cpp @@ -713,7 +713,7 @@ public: private: uint8_t* GetPointer() { return mData + mOffset; } - const size_t AvailableSize() { return mCapicity - mOffset; } + size_t AvailableSize() const { return mCapicity - mOffset; } void IncreaseOffset(size_t aValue) { @@ -722,12 +722,12 @@ private: mOffset += aValue; } - bool IsEmpty() + bool IsEmpty() const { return (mOffset == 0); } - const size_t GetCapacity() + size_t GetCapacity() const { return mCapicity; } diff --git a/dom/media/webrtc/MediaEngine.h b/dom/media/webrtc/MediaEngine.h index ccfda26777..e65eb40970 100644 --- a/dom/media/webrtc/MediaEngine.h +++ b/dom/media/webrtc/MediaEngine.h @@ -134,7 +134,7 @@ public: virtual bool IsFake() = 0; /* Returns the type of media source (camera, microphone, screen, window, etc) */ - virtual const dom::MediaSourceEnum GetMediaSource() = 0; + virtual dom::MediaSourceEnum GetMediaSource() const = 0; // Callback interface for TakePhoto(). Either PhotoComplete() or PhotoError() // should be called. diff --git a/dom/media/webrtc/MediaEngineDefault.h b/dom/media/webrtc/MediaEngineDefault.h index 8e1d5c9a84..cc34f9a9a0 100644 --- a/dom/media/webrtc/MediaEngineDefault.h +++ b/dom/media/webrtc/MediaEngineDefault.h @@ -69,7 +69,7 @@ public: return true; } - virtual const dom::MediaSourceEnum GetMediaSource() override { + virtual dom::MediaSourceEnum GetMediaSource() const override { return dom::MediaSourceEnum::Camera; } @@ -139,7 +139,7 @@ public: return true; } - virtual const dom::MediaSourceEnum GetMediaSource() override { + virtual dom::MediaSourceEnum GetMediaSource() const override { return dom::MediaSourceEnum::Microphone; } diff --git a/dom/media/webrtc/MediaEngineGonkVideoSource.h b/dom/media/webrtc/MediaEngineGonkVideoSource.h index 433667189e..42df3f157d 100644 --- a/dom/media/webrtc/MediaEngineGonkVideoSource.h +++ b/dom/media/webrtc/MediaEngineGonkVideoSource.h @@ -73,7 +73,7 @@ public: SourceMediaStream* aSource, TrackID aId, StreamTime aDesiredTime) override; - virtual const dom::MediaSourceEnum GetMediaSource() override { + virtual dom::MediaSourceEnum GetMediaSource() const override { return dom::MediaSourceEnum::Camera; } diff --git a/dom/media/webrtc/MediaEngineRemoteVideoSource.h b/dom/media/webrtc/MediaEngineRemoteVideoSource.h index ab0b3cccc5..9981cedeea 100644 --- a/dom/media/webrtc/MediaEngineRemoteVideoSource.h +++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.h @@ -84,7 +84,7 @@ public: SourceMediaStream* aSource, TrackID aId, StreamTime aDesiredTime) override; - virtual const dom::MediaSourceEnum GetMediaSource() override { + virtual dom::MediaSourceEnum GetMediaSource() const override { return mMediaSource; } diff --git a/dom/media/webrtc/MediaEngineTabVideoSource.h b/dom/media/webrtc/MediaEngineTabVideoSource.h index 8a0d3f17f3..331a31a0ee 100644 --- a/dom/media/webrtc/MediaEngineTabVideoSource.h +++ b/dom/media/webrtc/MediaEngineTabVideoSource.h @@ -34,7 +34,7 @@ class MediaEngineTabVideoSource : public MediaEngineVideoSource, nsIDOMEventList const nsString& aDeviceId) override; virtual nsresult Config(bool, uint32_t, bool, uint32_t, bool, uint32_t, int32_t) override; virtual bool IsFake() override; - virtual const dom::MediaSourceEnum GetMediaSource() override { + virtual dom::MediaSourceEnum GetMediaSource() const override { return dom::MediaSourceEnum::Browser; } virtual uint32_t GetBestFitnessDistance( diff --git a/dom/media/webrtc/MediaEngineWebRTC.h b/dom/media/webrtc/MediaEngineWebRTC.h index f0843f9406..bb3e304b3f 100644 --- a/dom/media/webrtc/MediaEngineWebRTC.h +++ b/dom/media/webrtc/MediaEngineWebRTC.h @@ -98,7 +98,7 @@ public: void NotifyPull(MediaStreamGraph* aGraph, SourceMediaStream* aSource, TrackID aID, StreamTime aDesiredTime) override {} - const dom::MediaSourceEnum GetMediaSource() override + dom::MediaSourceEnum GetMediaSource() const override { return dom::MediaSourceEnum::AudioCapture; } @@ -178,7 +178,7 @@ public: return false; } - virtual const dom::MediaSourceEnum GetMediaSource() override { + virtual dom::MediaSourceEnum GetMediaSource() const override { return dom::MediaSourceEnum::Microphone; } diff --git a/dom/network/TCPServerSocketParent.cpp b/dom/network/TCPServerSocketParent.cpp index b2b873a216..39d689ae4f 100644 --- a/dom/network/TCPServerSocketParent.cpp +++ b/dom/network/TCPServerSocketParent.cpp @@ -66,7 +66,7 @@ uint32_t TCPServerSocketParent::GetAppId() { const PContentParent *content = Manager()->Manager(); - if (PBrowserParent* browser = LoneManagedOrNull(content->ManagedPBrowserParent())) { + if (PBrowserParent* browser = SingleManagedOrNull(content->ManagedPBrowserParent())) { TabParent *tab = TabParent::GetFrom(browser); return tab->OwnAppId(); } else { @@ -78,7 +78,7 @@ bool TCPServerSocketParent::GetInBrowser() { const PContentParent *content = Manager()->Manager(); - if (PBrowserParent* browser = LoneManagedOrNull(content->ManagedPBrowserParent())) { + if (PBrowserParent* browser = SingleManagedOrNull(content->ManagedPBrowserParent())) { TabParent *tab = TabParent::GetFrom(browser); return tab->IsBrowserElement(); } else { diff --git a/dom/network/TCPSocketParent.cpp b/dom/network/TCPSocketParent.cpp index 08cd0dfef5..eca8b03b20 100644 --- a/dom/network/TCPSocketParent.cpp +++ b/dom/network/TCPSocketParent.cpp @@ -68,7 +68,7 @@ uint32_t TCPSocketParent::GetAppId() { const PContentParent *content = Manager()->Manager(); - if (PBrowserParent* browser = LoneManagedOrNull(content->ManagedPBrowserParent())) { + if (PBrowserParent* browser = SingleManagedOrNull(content->ManagedPBrowserParent())) { TabParent *tab = TabParent::GetFrom(browser); return tab->OwnAppId(); } else { @@ -80,7 +80,7 @@ bool TCPSocketParent::GetInBrowser() { const PContentParent *content = Manager()->Manager(); - if (PBrowserParent* browser = LoneManagedOrNull(content->ManagedPBrowserParent())) { + if (PBrowserParent* browser = SingleManagedOrNull(content->ManagedPBrowserParent())) { TabParent *tab = TabParent::GetFrom(browser); return tab->IsBrowserElement(); } else { @@ -223,7 +223,9 @@ TCPSocketParent::RecvOpenBind(const nsCString& aRemoteHost, uint32_t appId = nsIScriptSecurityManager::NO_APP_ID; bool inBrowser = false; const PContentParent *content = Manager()->Manager(); - if (PBrowserParent* browser = LoneManagedOrNull(content->ManagedPBrowserParent())) { + if (PBrowserParent* browser = SingleManagedOrNull(content->ManagedPBrowserParent())) { + // appId's are for B2G only currently, where managees.Count() == 1 + // This is not guaranteed currently in Desktop, so skip this there. TabParent *tab = TabParent::GetFrom(browser); appId = tab->OwnAppId(); inBrowser = tab->IsBrowserElement(); diff --git a/dom/notification/DesktopNotification.cpp b/dom/notification/DesktopNotification.cpp index a25f192593..5b3c5c1c5e 100644 --- a/dom/notification/DesktopNotification.cpp +++ b/dom/notification/DesktopNotification.cpp @@ -7,6 +7,7 @@ #include "mozilla/dom/DesktopNotificationBinding.h" #include "mozilla/dom/AppNotificationServiceOptionsBinding.h" #include "mozilla/dom/ToJSValue.h" +#include "nsComponentManagerUtils.h" #include "nsContentPermissionHelper.h" #include "nsXULAppAPI.h" #include "mozilla/dom/PBrowserChild.h" @@ -114,16 +115,20 @@ DesktopNotification::PostDesktopNotification() nsIPrincipal* principal = doc->NodePrincipal(); nsCOMPtr loadContext = doc->GetLoadContext(); bool inPrivateBrowsing = loadContext && loadContext->UsePrivateBrowsing(); - return alerts->ShowAlertNotification(mIconURL, mTitle, mDescription, - true, - uniqueName, - mObserver, - uniqueName, - NS_LITERAL_STRING("auto"), - EmptyString(), - EmptyString(), - principal, - inPrivateBrowsing); + nsCOMPtr alert = + do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID); + NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE); + nsresult rv = alert->Init(uniqueName, mIconURL, mTitle, + mDescription, + true, + uniqueName, + NS_LITERAL_STRING("auto"), + EmptyString(), + EmptyString(), + principal, + inPrivateBrowsing); + NS_ENSURE_SUCCESS(rv, rv); + return alerts->ShowAlert(alert, mObserver); } DesktopNotification::DesktopNotification(const nsAString & title, diff --git a/dom/notification/Notification.cpp b/dom/notification/Notification.cpp index 76e67cb99c..c05a93a044 100644 --- a/dom/notification/Notification.cpp +++ b/dom/notification/Notification.cpp @@ -10,6 +10,7 @@ #include "mozilla/OwningNonNull.h" #include "mozilla/Preferences.h" #include "mozilla/Services.h" +#include "mozilla/Telemetry.h" #include "mozilla/unused.h" #include "mozilla/dom/AppNotificationServiceOptionsBinding.h" @@ -21,8 +22,11 @@ #include "mozilla/dom/PromiseWorkerProxy.h" #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h" +#include "nsAlertsUtils.h" +#include "nsComponentManagerUtils.h" #include "nsContentPermissionHelper.h" #include "nsContentUtils.h" +#include "nsCRTGlue.h" #include "nsDOMJSUtils.h" #include "nsGlobalWindow.h" #include "nsIAlertsService.h" @@ -32,8 +36,10 @@ #include "nsILoadContext.h" #include "nsINotificationStorage.h" #include "nsIPermissionManager.h" +#include "nsIPermission.h" #include "nsIScriptSecurityManager.h" #include "nsIServiceWorkerManager.h" +#include "nsISimpleEnumerator.h" #include "nsIUUIDGenerator.h" #include "nsIXPConnect.h" #include "nsNetUtil.h" @@ -659,6 +665,195 @@ NotificationPermissionRequest::GetTypes(nsIArray** aTypes) aTypes); } +NS_IMPL_ISUPPORTS(NotificationTelemetryService, nsIObserver) + +NotificationTelemetryService::NotificationTelemetryService() + : mDNDRecorded(false) +{} + +NotificationTelemetryService::~NotificationTelemetryService() +{ + Unused << NS_WARN_IF(NS_FAILED(RemovePermissionChangeObserver())); +} + +/* static */ already_AddRefed +NotificationTelemetryService::GetInstance() +{ + nsCOMPtr telemetrySupports = + do_GetService(NOTIFICATIONTELEMETRYSERVICE_CONTRACTID); + if (!telemetrySupports) { + return nullptr; + } + RefPtr telemetry = + static_cast(telemetrySupports.get()); + return telemetry.forget(); +} + +nsresult +NotificationTelemetryService::Init() +{ + nsresult rv = AddPermissionChangeObserver(); + NS_ENSURE_SUCCESS(rv, rv); + + RecordPermissions(); + + return NS_OK; +} + +nsresult +NotificationTelemetryService::RemovePermissionChangeObserver() +{ + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (!obs) { + return NS_ERROR_OUT_OF_MEMORY; + } + return obs->RemoveObserver(this, "perm-changed"); +} + +nsresult +NotificationTelemetryService::AddPermissionChangeObserver() +{ + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (!obs) { + return NS_ERROR_OUT_OF_MEMORY; + } + return obs->AddObserver(this, "perm-changed", false); +} + +void +NotificationTelemetryService::RecordPermissions() +{ + if (!Telemetry::CanRecordBase() || !Telemetry::CanRecordExtended()) { + return; + } + + nsCOMPtr permissionManager = + services::GetPermissionManager(); + if (!permissionManager) { + return; + } + + nsCOMPtr enumerator; + nsresult rv = permissionManager->GetEnumerator(getter_AddRefs(enumerator)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + for (;;) { + bool hasMoreElements; + nsresult rv = enumerator->HasMoreElements(&hasMoreElements); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + if (!hasMoreElements) { + break; + } + nsCOMPtr supportsPermission; + rv = enumerator->GetNext(getter_AddRefs(supportsPermission)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + uint32_t capability; + if (!GetNotificationPermission(supportsPermission, &capability)) { + continue; + } + if (capability == nsIPermissionManager::DENY_ACTION) { + Telemetry::Accumulate(Telemetry::WEB_NOTIFICATION_PERMISSIONS, 0); + } else if (capability == nsIPermissionManager::ALLOW_ACTION) { + Telemetry::Accumulate(Telemetry::WEB_NOTIFICATION_PERMISSIONS, 1); + } + } +} + +bool +NotificationTelemetryService::GetNotificationPermission(nsISupports* aSupports, + uint32_t* aCapability) +{ + nsCOMPtr permission = do_QueryInterface(aSupports); + if (!permission) { + return false; + } + nsAutoCString type; + permission->GetType(type); + if (!type.Equals("desktop-notification")) { + return false; + } + permission->GetCapability(aCapability); + return true; +} + +void +NotificationTelemetryService::RecordDNDSupported() +{ + if (mDNDRecorded) { + return; + } + + nsCOMPtr alertService = + do_GetService(NS_ALERTSERVICE_CONTRACTID); + if (!alertService) { + return; + } + + nsCOMPtr alertServiceDND = + do_QueryInterface(alertService); + if (!alertServiceDND) { + return; + } + + mDNDRecorded = true; + bool isEnabled; + nsresult rv = alertServiceDND->GetManualDoNotDisturb(&isEnabled); + if (NS_FAILED(rv)) { + return; + } + + Telemetry::Accumulate( + Telemetry::ALERTS_SERVICE_DND_SUPPORTED_FLAG, true); +} + +nsresult +NotificationTelemetryService::RecordSender(nsIPrincipal* aPrincipal) +{ + if (!Telemetry::CanRecordBase() || !Telemetry::CanRecordExtended() || + !nsAlertsUtils::IsActionablePrincipal(aPrincipal)) { + return NS_OK; + } + nsAutoString origin; + nsresult rv = Notification::GetOrigin(aPrincipal, origin); + if (NS_FAILED(rv)) { + return rv; + } + if (!mOrigins.Contains(origin)) { + mOrigins.PutEntry(origin); + Telemetry::Accumulate(Telemetry::WEB_NOTIFICATION_SENDERS, 1); + } + return NS_OK; +} + +NS_IMETHODIMP +NotificationTelemetryService::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + uint32_t capability; + if (strcmp("perm-changed", aTopic) || + !NS_strcmp(MOZ_UTF16("cleared"), aData) || + !GetNotificationPermission(aSubject, &capability)) { + return NS_OK; + } + if (!NS_strcmp(MOZ_UTF16("deleted"), aData)) { + if (capability == nsIPermissionManager::DENY_ACTION) { + Telemetry::Accumulate( + Telemetry::WEB_NOTIFICATION_PERMISSION_REMOVED, 0); + } else if (capability == nsIPermissionManager::ALLOW_ACTION) { + Telemetry::Accumulate( + Telemetry::WEB_NOTIFICATION_PERMISSION_REMOVED, 1); + } + } + return NS_OK; +} + // Observer that the alert service calls to do common tasks and/or dispatch to the // specific observer for the context e.g. main thread, worker, or service worker. class NotificationObserver final : public nsIObserver @@ -666,11 +861,14 @@ class NotificationObserver final : public nsIObserver public: nsCOMPtr mObserver; nsCOMPtr mPrincipal; + bool mInPrivateBrowsing; NS_DECL_ISUPPORTS NS_DECL_NSIOBSERVER - NotificationObserver(nsIObserver* aObserver, nsIPrincipal* aPrincipal) - : mObserver(aObserver), mPrincipal(aPrincipal) + NotificationObserver(nsIObserver* aObserver, nsIPrincipal* aPrincipal, + bool aInPrivateBrowsing) + : mObserver(aObserver), mPrincipal(aPrincipal), + mInPrivateBrowsing(aInPrivateBrowsing) { AssertIsOnMainThread(); MOZ_ASSERT(mObserver); @@ -783,6 +981,23 @@ Notification::Notification(nsIGlobalObject* aGlobal, const nsAString& aID, } } +nsresult +Notification::Init() +{ + if (!mWorkerPrivate) { + nsCOMPtr obs = mozilla::services::GetObserverService(); + NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE); + + nsresult rv = obs->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true); + NS_ENSURE_SUCCESS(rv, rv); + + rv = obs->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, true); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + void Notification::SetAlertName() { @@ -952,6 +1167,7 @@ Notification::CreateInternal(nsIGlobalObject* aGlobal, const nsAString& aTitle, const NotificationOptions& aOptions) { + nsresult rv; nsString id; if (!aID.IsEmpty()) { id = aID; @@ -960,7 +1176,7 @@ Notification::CreateInternal(nsIGlobalObject* aGlobal, do_GetService("@mozilla.org/uuid-generator;1"); NS_ENSURE_TRUE(uuidgen, nullptr); nsID uuid; - nsresult rv = uuidgen->GenerateUUIDInPlace(&uuid); + rv = uuidgen->GenerateUUIDInPlace(&uuid); NS_ENSURE_SUCCESS(rv, nullptr); char buffer[NSID_LENGTH]; @@ -976,6 +1192,8 @@ Notification::CreateInternal(nsIGlobalObject* aGlobal, aOptions.mTag, aOptions.mIcon, aOptions.mMozbehavior); + rv = notification->Init(); + NS_ENSURE_SUCCESS(rv, nullptr); return notification.forget(); } @@ -1004,6 +1222,8 @@ NS_IMPL_ADDREF_INHERITED(Notification, DOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(Notification, DOMEventTargetHelper) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Notification) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) nsIPrincipal* @@ -1161,6 +1381,7 @@ NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic, AssertIsOnMainThread(); if (!strcmp("alertdisablecallback", aTopic)) { + Telemetry::Accumulate(Telemetry::WEB_NOTIFICATION_MENU, 1); if (XRE_IsParentProcess()) { return Notification::RemovePermission(mPrincipal); } @@ -1170,7 +1391,10 @@ NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic, ContentChild::GetSingleton()->SendDisableNotifications( IPC::Principal(mPrincipal)); return NS_OK; + } else if (!strcmp("alertclickcallback", aTopic)) { + Telemetry::Accumulate(Telemetry::WEB_NOTIFICATION_CLICKED, 1); } else if (!strcmp("alertsettingscallback", aTopic)) { + Telemetry::Accumulate(Telemetry::WEB_NOTIFICATION_MENU, 2); if (XRE_IsParentProcess()) { return Notification::OpenSettings(mPrincipal); } @@ -1181,7 +1405,23 @@ NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic, return NS_OK; } else if (!strcmp("alertshow", aTopic) || !strcmp("alertfinished", aTopic)) { + RefPtr telemetry = + NotificationTelemetryService::GetInstance(); + if (telemetry) { + // Record whether "do not disturb" is supported after the first + // notification, to account for falling back to XUL alerts. + telemetry->RecordDNDSupported(); + if (!mInPrivateBrowsing) { + // Ignore senders in private windows. + Unused << NS_WARN_IF(NS_FAILED(telemetry->RecordSender(mPrincipal))); + } + } Unused << NS_WARN_IF(NS_FAILED(AdjustPushQuota(aTopic))); + + if (!strcmp("alertshow", aTopic)) { + // Record notifications actually shown (e.g. don't count if DND is on). + Telemetry::Accumulate(Telemetry::WEB_NOTIFICATION_SHOWN, 1); + } } return mObserver->Observe(aSubject, aTopic, aData); @@ -1416,6 +1656,30 @@ ServiceWorkerNotificationObserver::Observe(nsISupports* aSubject, return NS_OK; } +bool +Notification::IsInPrivateBrowsing() +{ + nsIDocument* doc = mWorkerPrivate ? mWorkerPrivate->GetDocument() + : GetOwner()->GetExtantDoc(); + if (doc) { + nsCOMPtr loadContext = doc->GetLoadContext(); + return loadContext && loadContext->UsePrivateBrowsing(); + } + + if (mWorkerPrivate) { + // Not all workers may have a document, but with Bug 1107516 fixed, they + // should all have a loadcontext. + nsCOMPtr loadGroup = mWorkerPrivate->GetLoadGroup(); + nsCOMPtr loadContext; + NS_QueryNotificationCallbacks(nullptr, loadGroup, NS_GET_IID(nsILoadContext), + getter_AddRefs(loadContext)); + return loadContext && loadContext->UsePrivateBrowsing(); + } + + //XXXnsm Should this default to true? + return false; +} + void Notification::ShowInternal() { @@ -1446,6 +1710,9 @@ Notification::ShowInternal() } else { permission = GetPermissionInternal(GetOwner(), result); } + // We rely on GetPermissionInternal returning Denied on all failure codepaths. + MOZ_ASSERT_IF(result.Failed(), permission == NotificationPermission::Denied); + result.SuppressException(); if (permission != NotificationPermission::Granted || !alertService) { if (mWorkerPrivate) { RefPtr r = @@ -1487,7 +1754,8 @@ Notification::ShowInternal() } MOZ_ASSERT(observer); nsCOMPtr alertObserver = new NotificationObserver(observer, - GetPrincipal()); + GetPrincipal(), + IsInPrivateBrowsing()); #ifdef MOZ_B2G @@ -1538,30 +1806,23 @@ Notification::ShowInternal() // nsIObserver. Thus the cookie must be unique to differentiate observers. nsString uniqueCookie = NS_LITERAL_STRING("notification:"); uniqueCookie.AppendInt(sCount++); - //XXXnsm Should this default to true? - bool inPrivateBrowsing = false; - nsIDocument* doc = mWorkerPrivate ? mWorkerPrivate->GetDocument() - : GetOwner()->GetExtantDoc(); - if (doc) { - nsCOMPtr loadContext = doc->GetLoadContext(); - inPrivateBrowsing = loadContext && loadContext->UsePrivateBrowsing(); - } else if (mWorkerPrivate) { - // Not all workers may have a document, but with Bug 1107516 fixed, they - // should all have a loadcontext. - nsCOMPtr loadGroup = mWorkerPrivate->GetLoadGroup(); - nsCOMPtr loadContext; - NS_QueryNotificationCallbacks(nullptr, loadGroup, NS_GET_IID(nsILoadContext), - getter_AddRefs(loadContext)); - inPrivateBrowsing = loadContext && loadContext->UsePrivateBrowsing(); - } + bool inPrivateBrowsing = IsInPrivateBrowsing(); nsAutoString alertName; GetAlertName(alertName); - alertService->ShowAlertNotification(iconUrl, mTitle, mBody, true, - uniqueCookie, alertObserver, alertName, - DirectionToString(mDir), mLang, - mDataAsBase64, GetPrincipal(), - inPrivateBrowsing); + nsCOMPtr alert = + do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID); + NS_ENSURE_TRUE_VOID(alert); + rv = alert->Init(alertName, iconUrl, mTitle, mBody, + true, + uniqueCookie, + DirectionToString(mDir), + mLang, + mDataAsBase64, + GetPrincipal(), + inPrivateBrowsing); + NS_ENSURE_SUCCESS_VOID(rv); + alertService->ShowAlert(alert, alertObserver); } /* static */ bool @@ -2478,6 +2739,46 @@ Notification::OpenSettings(nsIPrincipal* aPrincipal) return NS_OK; } +NS_IMETHODIMP +Notification::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + AssertIsOnMainThread(); + + if (!strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) || + !strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC)) { + + nsCOMPtr window = GetOwner(); + if (SameCOMIdentity(aSubject, window)) { + nsCOMPtr obs = + mozilla::services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC); + obs->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC); + } + + uint16_t appStatus = nsIPrincipal::APP_STATUS_NOT_INSTALLED; + uint32_t appId = nsIScriptSecurityManager::UNKNOWN_APP_ID; + + nsCOMPtr doc = window->GetExtantDoc(); + nsCOMPtr nodePrincipal = doc ? doc->NodePrincipal() : + nullptr; + if (nodePrincipal) { + appStatus = nodePrincipal->GetAppStatus(); + appId = nodePrincipal->GetAppId(); + } + + if (appStatus == nsIPrincipal::APP_STATUS_NOT_INSTALLED || + appId == nsIScriptSecurityManager::NO_APP_ID || + appId == nsIScriptSecurityManager::UNKNOWN_APP_ID) { + CloseInternal(); + } + } + } + + return NS_OK; +} + } // namespace dom } // namespace mozilla diff --git a/dom/notification/Notification.h b/dom/notification/Notification.h index 4bcb8c63e3..856ec50dc2 100644 --- a/dom/notification/Notification.h +++ b/dom/notification/Notification.h @@ -15,6 +15,12 @@ #include "nsIObserver.h" #include "nsCycleCollectionParticipant.h" +#include "nsHashKeys.h" +#include "nsTHashtable.h" +#include "nsWeakReference.h" + +#define NOTIFICATIONTELEMETRYSERVICE_CONTRACTID \ + "@mozilla.org/notificationTelemetryService;1" class nsIPrincipal; class nsIVariant; @@ -44,6 +50,35 @@ public: Notify(JSContext* aCx, workers::Status aStatus) override; }; +// Records telemetry probes at application startup, when a notification is +// shown, and when the notification permission is revoked for a site. +class NotificationTelemetryService final : public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + NotificationTelemetryService(); + + static already_AddRefed GetInstance(); + + nsresult Init(); + void RecordDNDSupported(); + void RecordPermissions(); + nsresult RecordSender(nsIPrincipal* aPrincipal); + +private: + virtual ~NotificationTelemetryService(); + + nsresult AddPermissionChangeObserver(); + nsresult RemovePermissionChangeObserver(); + + bool GetNotificationPermission(nsISupports* aSupports, + uint32_t* aCapability); + + bool mDNDRecorded; + nsTHashtable mOrigins; +}; /* * Notifications on workers introduce some lifetime issues. The property we @@ -98,6 +133,8 @@ public: * */ class Notification : public DOMEventTargetHelper + , public nsIObserver + , public nsSupportsWeakReference { friend class CloseNotificationRunnable; friend class NotificationTask; @@ -107,6 +144,7 @@ class Notification : public DOMEventTargetHelper friend class ServiceWorkerNotificationObserver; friend class WorkerGetRunnable; friend class WorkerNotificationObserver; + friend class NotificationTelemetryService; public: IMPL_EVENT_HANDLER(click) @@ -116,6 +154,7 @@ public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(Notification, DOMEventTargetHelper) + NS_DECL_NSIOBSERVER static bool PrefEnabled(JSContext* aCx, JSObject* aObj); // Returns if Notification.get() is allowed for the current global. @@ -290,6 +329,8 @@ protected: const nsAString& aTitle, const NotificationOptions& aOptions); + nsresult Init(); + bool IsInPrivateBrowsing(); void ShowInternal(); void CloseInternal(); @@ -308,7 +349,7 @@ protected: } } - static const NotificationDirection StringToDirection(const nsAString& aDirection) + static NotificationDirection StringToDirection(const nsAString& aDirection) { if (aDirection.EqualsLiteral("ltr")) { return NotificationDirection::Ltr; diff --git a/dom/plugins/ipc/PluginMessageUtils.h b/dom/plugins/ipc/PluginMessageUtils.h index 89e7e81941..1e5f1a5f28 100644 --- a/dom/plugins/ipc/PluginMessageUtils.h +++ b/dom/plugins/ipc/PluginMessageUtils.h @@ -117,7 +117,7 @@ typedef mozilla::null_t DXGISharedSurfaceHandle; // XXX maybe not the best place for these. better one? #define VARSTR(v_) case v_: return #v_ -inline const char* const +inline const char* NPPVariableToString(NPPVariable aVar) { switch (aVar) { diff --git a/dom/plugins/ipc/PluginModuleParent.cpp b/dom/plugins/ipc/PluginModuleParent.cpp index 8554db6f0f..b3a111e094 100755 --- a/dom/plugins/ipc/PluginModuleParent.cpp +++ b/dom/plugins/ipc/PluginModuleParent.cpp @@ -1514,7 +1514,7 @@ PluginModuleChromeParent::OnHangUIContinue() CrashReporterParent* PluginModuleChromeParent::CrashReporter() { - return static_cast(LoneManagedOrNull(ManagedPCrashReporterParent())); + return static_cast(LoneManagedOrNullAsserts(ManagedPCrashReporterParent())); } #ifdef MOZ_CRASHREPORTER_INJECTOR diff --git a/dom/presentation/provider/MulticastDNSDeviceProvider.h b/dom/presentation/provider/MulticastDNSDeviceProvider.h index b390068364..7c60197f23 100644 --- a/dom/presentation/provider/MulticastDNSDeviceProvider.h +++ b/dom/presentation/provider/MulticastDNSDeviceProvider.h @@ -86,12 +86,12 @@ private: return mAddress; } - const uint16_t Port() const + uint16_t Port() const { return mPort; } - const DeviceState State() const + DeviceState State() const { return mState; } diff --git a/dom/smil/nsSMILCSSValueType.cpp b/dom/smil/nsSMILCSSValueType.cpp index 7e7feae34f..9c089bd25b 100644 --- a/dom/smil/nsSMILCSSValueType.cpp +++ b/dom/smil/nsSMILCSSValueType.cpp @@ -72,7 +72,7 @@ GetZeroValueForUnit(StyleAnimationValue::Unit aUnit) // with a eUnit_Float value. (See comment below.) // // Returns true on success, or false. -static const bool +static bool FinalizeStyleAnimationValues(const StyleAnimationValue*& aValue1, const StyleAnimationValue*& aValue2) { diff --git a/dom/svg/SVGTransform.h b/dom/svg/SVGTransform.h index 7c4dc14fb1..49c0b03ed1 100644 --- a/dom/svg/SVGTransform.h +++ b/dom/svg/SVGTransform.h @@ -134,7 +134,7 @@ protected: // Interface for SVGMatrix's use friend class dom::SVGMatrix; - const bool IsAnimVal() const { + bool IsAnimVal() const { return mIsAnimValItem; } const gfxMatrix& Matrixgfx() const { diff --git a/dom/tests/mochitest/fetch/reroute.js b/dom/tests/mochitest/fetch/reroute.js index e117c12e99..2c9bcd35f1 100644 --- a/dom/tests/mochitest/fetch/reroute.js +++ b/dom/tests/mochitest/fetch/reroute.js @@ -5,13 +5,18 @@ onfetch = function(e) { var url = e.request.url.substring(0, e.request.url.indexOf('?')); url += '?headers=' + ({ 'Referer': self.location.href }).toSource(); - e.respondWith(fetch(url, { - method: e.request.method, - headers: e.request.headers, - body: e.request.body, - mode: e.request.mode, - credentials: e.request.credentials, - cache: e.request.cache, + e.respondWith(e.request.text().then(function(text) { + var body = text === '' ? undefined : text; + var mode = e.request.mode == 'navigate' ? 'same-origin' : e.request.mode; + return fetch(url, { + method: e.request.method, + headers: e.request.headers, + body: body, + mode: mode, + credentials: e.request.credentials, + redirect: e.request.redirect, + cache: e.request.cache, + }); })); return; } diff --git a/dom/tests/mochitest/fetch/test_request.js b/dom/tests/mochitest/fetch/test_request.js index ec88a752f3..33c56e1a1e 100644 --- a/dom/tests/mochitest/fetch/test_request.js +++ b/dom/tests/mochitest/fetch/test_request.js @@ -124,6 +124,19 @@ function testBug1109574() { var r3 = new Request(r1); } +// Bug 1184550 - Request constructor should always throw if used flag is set, +// even if body is null +function testBug1184550() { + var req = new Request("", { method: 'post', body: "Test" }); + fetch(req); + ok(req.bodyUsed, "Request body should be used immediately after fetch()"); + return fetch(req).then(function(resp) { + ok(false, "Second fetch with same request should fail."); + }).catch(function(err) { + is(err.name, 'TypeError', "Second fetch with same request should fail."); + }); +} + function testHeaderGuard() { var headers = { "Cookie": "Custom cookie", @@ -138,6 +151,15 @@ function testHeaderGuard() { ok(!r2.headers.has("Non-Simple-Header"), "no-cors Request header should have guard request-no-cors and prevent setting non-simple header."); } +function testMode() { + try { + var req = new Request("http://example.com", {mode: "navigate"}); + ok(false, "Creating a Request with navigate RequestMode should throw a TypeError"); + } catch(e) { + is(e.name, "TypeError", "Creating a Request with navigate RequestMode should throw a TypeError"); + } +} + function testMethod() { // These get normalized. var allowed = ["delete", "get", "head", "options", "post", "put" ]; @@ -498,8 +520,10 @@ function runTest() { testUrlFragment(); testUrlCredentials(); testUrlMalformed(); + testMode(); testMethod(); testBug1109574(); + testBug1184550(); testHeaderGuard(); testModeCorsPreflightEnumValue(); testBug1154268(); diff --git a/dom/tests/mochitest/notification/MockServices.js b/dom/tests/mochitest/notification/MockServices.js index b47194b5c6..ff8c5da0a0 100644 --- a/dom/tests/mochitest/notification/MockServices.js +++ b/dom/tests/mochitest/notification/MockServices.js @@ -29,26 +29,34 @@ var MockServices = (function () { }); var mockAlertsService = { - showAlertNotification: function(imageUrl, title, text, textClickable, - cookie, alertListener, name) { + showAlert: function(alert, alertListener) { var listener = SpecialPowers.wrap(alertListener); - activeAlertNotifications[name] = { + activeAlertNotifications[alert.name] = { listener: listener, - cookie: cookie, - title: title + cookie: alert.cookie, + title: alert.title }; // fake async alert show event if (listener) { setTimeout(function () { - listener.observe(null, "alertshow", cookie); + listener.observe(null, "alertshow", alert.cookie); }, 100); setTimeout(function () { - listener.observe(null, "alertclickcallback", cookie); + listener.observe(null, "alertclickcallback", alert.cookie); }, 100); } }, + showAlertNotification: function(imageUrl, title, text, textClickable, + cookie, alertListener, name) { + this.showAlert({ + name: name, + cookie: cookie, + title: title + }, alertListener); + }, + showAppNotification: function(aImageUrl, aTitle, aText, aAlertListener, aDetails) { var listener = aAlertListener || (activeAlertNotifications[aDetails.id] ? activeAlertNotifications[aDetails.id].listener : undefined); activeAppNotifications[aDetails.id] = { diff --git a/dom/tests/mochitest/notification/desktop-notification/notification_common.js b/dom/tests/mochitest/notification/desktop-notification/notification_common.js index 47d9d22da9..3aae3cef4b 100644 --- a/dom/tests/mochitest/notification/desktop-notification/notification_common.js +++ b/dom/tests/mochitest/notification/desktop-notification/notification_common.js @@ -8,17 +8,23 @@ var registrar = SpecialPowers.wrap(SpecialPowers.Components).manager. QueryInterface(SpecialPowers.Ci.nsIComponentRegistrar); var mockAlertsService = { + showAlert: function(alert, alertListener) { + // probably should do this async.... + SpecialPowers.wrap(alertListener).observe(null, "alertshow", alert.cookie); + + if (SpecialPowers.getBoolPref("notification.prompt.testing.click_on_notification") == true) { + SpecialPowers.wrap(alertListener).observe(null, "alertclickcallback", alert.cookie); + } + + SpecialPowers.wrap(alertListener).observe(null, "alertfinished", alert.cookie); + }, + showAlertNotification: function(imageUrl, title, text, textClickable, cookie, alertListener, name, bidi, lang, data) { - // probably should do this async.... - SpecialPowers.wrap(alertListener).observe(null, "alertshow", cookie); - - if (SpecialPowers.getBoolPref("notification.prompt.testing.click_on_notification") == true) { - SpecialPowers.wrap(alertListener).observe(null, "alertclickcallback", cookie); - } - - SpecialPowers.wrap(alertListener).observe(null, "alertfinished", cookie); + return this.showAlert({ + cookie: cookie + }, alertListener); }, showAppNotification: function(imageUrl, title, text, alertListener, details) { diff --git a/dom/tests/mochitest/notification/desktop-notification/test_notification_tag.html b/dom/tests/mochitest/notification/desktop-notification/test_notification_tag.html index 086ff0a7b5..37bcb16c02 100644 --- a/dom/tests/mochitest/notification/desktop-notification/test_notification_tag.html +++ b/dom/tests/mochitest/notification/desktop-notification/test_notification_tag.html @@ -23,15 +23,19 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=782211 const ALERTS_SERVICE_CONTRACT_ID = "@mozilla.org/alerts-service;1"; var mockAlertsService = { - showAlertNotification: function(imageUrl, title, text, textClickable, - cookie, alertListener, name, dir, - lang, data) { - notificationsCreated.push(name); + showAlert: function(alert, alertListener) { + notificationsCreated.push(alert.name); if (notificationsCreated.length == 3) { checkNotifications(); } }, + showAlertNotification: function(imageUrl, title, text, textClickable, + cookie, alertListener, name, dir, + lang, data) { + this.showAlert({ name: name }); + }, + QueryInterface: function(aIID) { if (SpecialPowers.wrap(aIID).equals(SpecialPowers.Ci.nsISupports) || SpecialPowers.wrap(aIID).equals(SpecialPowers.Ci.nsIAlertsService)) { diff --git a/dom/tests/mochitest/notification/desktop-notification/test_system_principal.xul b/dom/tests/mochitest/notification/desktop-notification/test_system_principal.xul index 52312a7663..1690bc101b 100644 --- a/dom/tests/mochitest/notification/desktop-notification/test_system_principal.xul +++ b/dom/tests/mochitest/notification/desktop-notification/test_system_principal.xul @@ -23,13 +23,17 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=874090 const ALERTS_SERVICE_CONTRACT_ID = "@mozilla.org/alerts-service;1"; var mockAlertsService = { - showAlertNotification: function(imageUrl, title, text, textClickable, - cookie, alertListener, name, dir, lang, data) { - ok(true, "System principal was granted permission and is able to call showAlertNotification."); + showAlert: function(alert, alertListener) { + ok(true, "System principal was granted permission and is able to call showAlert."); unregisterMock(); SimpleTest.finish(); }, + showAlertNotification: function(imageUrl, title, text, textClickable, + cookie, alertListener, name, dir, lang, data) { + this.showAlert(); + }, + QueryInterface: function(aIID) { if (aIID.equals(Components.interfaces.nsISupports) || aIID.equals(Components.interfaces.nsIAlertsService)) { diff --git a/dom/tests/mochitest/webapps/head.js b/dom/tests/mochitest/webapps/head.js index f34f756471..16c3090f7b 100644 --- a/dom/tests/mochitest/webapps/head.js +++ b/dom/tests/mochitest/webapps/head.js @@ -117,6 +117,9 @@ var AlertsService = { "", ALERTS_SERVICE_CONTRACT_ID, null); }, + showAlert: function() { + }, + showAlertNotification: function() { }, }; diff --git a/dom/webidl/Navigator.webidl b/dom/webidl/Navigator.webidl index 48875ce803..66d185e4d3 100644 --- a/dom/webidl/Navigator.webidl +++ b/dom/webidl/Navigator.webidl @@ -41,7 +41,7 @@ interface NavigatorID { readonly attribute DOMString appVersion; [Constant, Cached] readonly attribute DOMString platform; - [Constant, Cached, Throws=Workers] + [Pure, Cached, Throws=Workers] readonly attribute DOMString userAgent; [Constant, Cached] readonly attribute DOMString product; // constant "Gecko" diff --git a/dom/webidl/Request.webidl b/dom/webidl/Request.webidl index 1ab355f3ac..bfe2a1b36b 100644 --- a/dom/webidl/Request.webidl +++ b/dom/webidl/Request.webidl @@ -55,7 +55,7 @@ enum RequestContext { "xslt" }; -enum RequestMode { "same-origin", "no-cors", "cors" }; +enum RequestMode { "same-origin", "no-cors", "cors", "navigate" }; enum RequestCredentials { "omit", "same-origin", "include" }; -enum RequestCache { "default", "no-store", "reload", "no-cache", "force-cache", "only-if-cached" }; +enum RequestCache { "default", "no-store", "reload", "no-cache", "force-cache" }; enum RequestRedirect { "follow", "error", "manual" }; diff --git a/dom/workers/ServiceWorkerEvents.cpp b/dom/workers/ServiceWorkerEvents.cpp index 54cf1af2d4..8af3911708 100644 --- a/dom/workers/ServiceWorkerEvents.cpp +++ b/dom/workers/ServiceWorkerEvents.cpp @@ -563,7 +563,8 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle aValu return; } - MOZ_ASSERT_IF(mIsClientRequest, mRequestMode == RequestMode::Same_origin); + MOZ_ASSERT_IF(mIsClientRequest, mRequestMode == RequestMode::Same_origin || + mRequestMode == RequestMode::Navigate); if (response->Type() == ResponseType::Opaque && mRequestMode != RequestMode::No_cors) { uint32_t mode = static_cast(mRequestMode); diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp index 85be059ab6..0b987f15ca 100644 --- a/dom/workers/ServiceWorkerManager.cpp +++ b/dom/workers/ServiceWorkerManager.cpp @@ -105,6 +105,8 @@ static_assert(nsIHttpChannelInternal::CORS_MODE_NO_CORS == static_cast "RequestMode enumeration value should match Necko CORS mode value."); static_assert(nsIHttpChannelInternal::CORS_MODE_CORS == static_cast(RequestMode::Cors), "RequestMode enumeration value should match Necko CORS mode value."); +static_assert(nsIHttpChannelInternal::CORS_MODE_NAVIGATE == static_cast(RequestMode::Navigate), + "RequestMode enumeration value should match Necko CORS mode value."); static_assert(nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW == static_cast(RequestRedirect::Follow), "RequestRedirect enumeration value should make Necko Redirect mode value."); @@ -3466,7 +3468,7 @@ ServiceWorkerManager::PrepareFetchEvent(const OriginAttributes& aOriginAttribute documentId = aDocumentIdForTopLevelNavigation; nsCOMPtr uri; - aRv = internalChannel->GetURI(getter_AddRefs(uri)); + aRv = aChannel->GetSecureUpgradedChannelURI(getter_AddRefs(uri)); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } diff --git a/dom/workers/ServiceWorkerPrivate.cpp b/dom/workers/ServiceWorkerPrivate.cpp index 1ea25ac8d2..813823d9d8 100644 --- a/dom/workers/ServiceWorkerPrivate.cpp +++ b/dom/workers/ServiceWorkerPrivate.cpp @@ -1052,7 +1052,7 @@ public: NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr uri; - rv = channel->GetURI(getter_AddRefs(uri)); + rv = mInterceptedChannel->GetSecureUpgradedChannelURI(getter_AddRefs(uri)); NS_ENSURE_SUCCESS(rv, rv); rv = uri->GetSpec(mSpec); diff --git a/dom/workers/moz.build b/dom/workers/moz.build index f33cb1714f..d905e1f60a 100644 --- a/dom/workers/moz.build +++ b/dom/workers/moz.build @@ -121,6 +121,10 @@ MOCHITEST_CHROME_MANIFESTS += [ 'test/serviceworkers/chrome.ini' ] +BROWSER_CHROME_MANIFESTS += [ + 'test/serviceworkers/browser.ini', +] + XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini'] TEST_DIRS += ['test/gtest'] diff --git a/dom/workers/test/serviceworkers/browser.ini b/dom/workers/test/serviceworkers/browser.ini new file mode 100644 index 0000000000..106bf096cf --- /dev/null +++ b/dom/workers/test/serviceworkers/browser.ini @@ -0,0 +1,10 @@ +[DEFAULT] +support-files = + browser_base_force_refresh.html + browser_cached_force_refresh.html + download_window.html + download_worker.js + force_refresh_browser_worker.js + +[browser_force_refresh.js] +[browser_download.js] diff --git a/dom/workers/test/serviceworkers/browser_base_force_refresh.html b/dom/workers/test/serviceworkers/browser_base_force_refresh.html new file mode 100644 index 0000000000..1b0d2defe2 --- /dev/null +++ b/dom/workers/test/serviceworkers/browser_base_force_refresh.html @@ -0,0 +1,30 @@ + + + + + + + + + diff --git a/dom/workers/test/serviceworkers/browser_cached_force_refresh.html b/dom/workers/test/serviceworkers/browser_cached_force_refresh.html new file mode 100644 index 0000000000..4b550a3d23 --- /dev/null +++ b/dom/workers/test/serviceworkers/browser_cached_force_refresh.html @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/dom/workers/test/serviceworkers/browser_download.js b/dom/workers/test/serviceworkers/browser_download.js new file mode 100644 index 0000000000..667b32b26f --- /dev/null +++ b/dom/workers/test/serviceworkers/browser_download.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +Cu.import('resource://gre/modules/Services.jsm'); +var Downloads = Cu.import("resource://gre/modules/Downloads.jsm", {}).Downloads; +Cu.import('resource://gre/modules/NetUtil.jsm'); + +var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", + "http://mochi.test:8888/") + +function getFile(aFilename) { + if (aFilename.startsWith('file:')) { + var url = NetUtil.newURI(aFilename).QueryInterface(Ci.nsIFileURL); + return url.file.clone(); + } + + var file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile); + file.initWithPath(aFilename); + return file; +} + +function windowObserver(win, topic) { + if (topic !== 'domwindowopened') { + return; + } + + win.addEventListener('load', function onLoadWindow() { + win.removeEventListener('load', onLoadWindow, false); + if (win.document.documentURI === + 'chrome://mozapps/content/downloads/unknownContentType.xul') { + executeSoon(function() { + var button = win.document.documentElement.getButton('accept'); + button.disabled = false; + win.document.documentElement.acceptDialog(); + }); + } + }, false); +} + +function test() { + waitForExplicitFinish(); + + Services.ww.registerNotification(windowObserver); + + SpecialPowers.pushPrefEnv({'set': [['dom.serviceWorkers.enabled', true], + ['dom.serviceWorkers.exemptFromPerDomainMax', true], + ['dom.serviceWorkers.testing.enabled', true], + ['dom.serviceWorkers.interception.enabled', true]]}, + function() { + var url = gTestRoot + 'download_window.html'; + var tab = gBrowser.addTab(); + gBrowser.selectedTab = tab; + + Downloads.getList(Downloads.ALL).then(function(downloadList) { + var downloadListener; + + function downloadVerifier(aDownload) { + if (aDownload.succeeded) { + var file = getFile(aDownload.target.path); + ok(file.exists(), 'download completed'); + is(file.fileSize, 33, 'downloaded file has correct size'); + file.remove(false); + + downloadList.removeView(downloadListener); + gBrowser.removeTab(tab); + Services.ww.unregisterNotification(windowObserver); + + executeSoon(finish); + } + } + + downloadListener = { + onDownloadAdded: downloadVerifier, + onDownloadChanged: downloadVerifier + }; + + return downloadList.addView(downloadListener); + }).then(function() { + gBrowser.loadURI(url); + }); + }); +} diff --git a/dom/workers/test/serviceworkers/browser_force_refresh.js b/dom/workers/test/serviceworkers/browser_force_refresh.js new file mode 100644 index 0000000000..ff51774ffc --- /dev/null +++ b/dom/workers/test/serviceworkers/browser_force_refresh.js @@ -0,0 +1,54 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", + "http://mochi.test:8888/") + +function refresh() { + EventUtils.synthesizeKey('R', { accelKey: true }); +} + +function forceRefresh() { + EventUtils.synthesizeKey('R', { accelKey: true, shiftKey: true }); +} + +function test() { + waitForExplicitFinish(); + SpecialPowers.pushPrefEnv({'set': [['dom.serviceWorkers.enabled', true], + ['dom.serviceWorkers.exemptFromPerDomainMax', true], + ['dom.serviceWorkers.testing.enabled', true], + ['dom.caches.enabled', true]]}, + function() { + var url = gTestRoot + 'browser_base_force_refresh.html'; + var tab = gBrowser.addTab(url); + gBrowser.selectedTab = tab; + + var cachedLoad = false; + + function eventHandler(event) { + if (event.type === 'base-load') { + if (cachedLoad) { + gBrowser.removeTab(tab); + executeSoon(finish); + } + } else if (event.type === 'base-register') { + ok(!cachedLoad, 'cached load should not occur before base register'); + refresh(); + } else if (event.type === 'base-sw-ready') { + ok(!cachedLoad, 'cached load should not occur before base ready'); + refresh(); + } else if (event.type === 'cached-load') { + ok(!cachedLoad, 'cached load should not occur twice'); + cachedLoad = true; + forceRefresh(); + } + + return; + } + + addEventListener('base-load', eventHandler, true, true); + addEventListener('base-register', eventHandler, true, true); + addEventListener('base-sw-ready', eventHandler, true, true); + addEventListener('cached-load', eventHandler, true, true); + }); +} diff --git a/dom/workers/test/serviceworkers/download_window.html b/dom/workers/test/serviceworkers/download_window.html new file mode 100644 index 0000000000..335ed29fc2 --- /dev/null +++ b/dom/workers/test/serviceworkers/download_window.html @@ -0,0 +1,46 @@ + + + + + + + + + diff --git a/dom/workers/test/serviceworkers/download_worker.js b/dom/workers/test/serviceworkers/download_worker.js new file mode 100644 index 0000000000..810641cd8a --- /dev/null +++ b/dom/workers/test/serviceworkers/download_worker.js @@ -0,0 +1,28 @@ +addEventListener('install', function(evt) { + evt.waitUntil(self.skipWaiting()); +}); + +addEventListener('activate', function(evt) { + // We claim the current clients in order to ensure that we have an + // active client when we call unregister in the fetch handler. Otherwise + // the unregister() can kill the current worker before returning a + // response. + evt.waitUntil(clients.claim()); +}); + +addEventListener('fetch', function(evt) { + // This worker may live long enough to receive a fetch event from the next + // test. Just pass such requests through to the network. + if (evt.request.url.indexOf('fake_download') === -1) { + return; + } + + // We should only get a single download fetch event. Automatically unregister. + evt.respondWith(registration.unregister().then(function() { + return new Response('service worker generated download', { + headers: { + 'Content-Disposition': 'attachment; filename="fake_download.bin"' + } + }); + })); +}); diff --git a/dom/workers/test/serviceworkers/fetch/upgrade-insecure/embedder.html b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/embedder.html new file mode 100644 index 0000000000..6098a45dd4 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/embedder.html @@ -0,0 +1,10 @@ + + + diff --git a/dom/workers/test/serviceworkers/fetch/upgrade-insecure/embedder.html^headers^ b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/embedder.html^headers^ new file mode 100644 index 0000000000..602d9dc38d --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/embedder.html^headers^ @@ -0,0 +1 @@ +Content-Security-Policy: upgrade-insecure-requests diff --git a/dom/workers/test/serviceworkers/fetch/upgrade-insecure/image-20px.png b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/image-20px.png new file mode 100644 index 0000000000000000000000000000000000000000..ae6a8a6b88403959c75efce931b0bf4293efc956 GIT binary patch literal 87 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE;=oTrOph(&MmkMjpU%%3$Q@ydZf kW_Mm0(}F7pCT1xxd^;`67yW*X5Ktw9r>mdKI;Vst0D!m{_W%F@ literal 0 HcmV?d00001 diff --git a/dom/workers/test/serviceworkers/fetch/upgrade-insecure/image-40px.png b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/image-40px.png new file mode 100644 index 0000000000000000000000000000000000000000..fe391dc8a2d797360651fe8cf77161a3fc891194 GIT binary patch literal 123 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU1|)m_?Z^dEPM$7~ArY-_&utWBP~c%$*z@jL z8rOSWjTbX5i}z1sXJLKaupmKJKx7SbQ&Xu!zy>}Ju4{~r2dxw|BEXUllDQ? + diff --git a/dom/workers/test/serviceworkers/fetch/upgrade-insecure/realindex.html b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/realindex.html new file mode 100644 index 0000000000..aaa255aad3 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/realindex.html @@ -0,0 +1,4 @@ + + diff --git a/dom/workers/test/serviceworkers/fetch/upgrade-insecure/register.html b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/register.html new file mode 100644 index 0000000000..6309b9b218 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/register.html @@ -0,0 +1,14 @@ + + diff --git a/dom/workers/test/serviceworkers/fetch/upgrade-insecure/unregister.html b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/unregister.html new file mode 100644 index 0000000000..1f13508fa7 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/unregister.html @@ -0,0 +1,12 @@ + + diff --git a/dom/workers/test/serviceworkers/fetch/upgrade-insecure/upgrade-insecure_test.js b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/upgrade-insecure_test.js new file mode 100644 index 0000000000..ab54164ed2 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/upgrade-insecure_test.js @@ -0,0 +1,11 @@ +self.addEventListener("fetch", function(event) { + if (event.request.url.indexOf("index.html") >= 0) { + event.respondWith(fetch("realindex.html")); + } else if (event.request.url.indexOf("image-20px.png") >= 0) { + if (event.request.url.indexOf("https://") == 0) { + event.respondWith(fetch("image-40px.png")); + } else { + event.respondWith(Response.error()); + } + } +}); diff --git a/dom/workers/test/serviceworkers/force_refresh_browser_worker.js b/dom/workers/test/serviceworkers/force_refresh_browser_worker.js new file mode 100644 index 0000000000..0fe91ed95a --- /dev/null +++ b/dom/workers/test/serviceworkers/force_refresh_browser_worker.js @@ -0,0 +1,22 @@ +var name = 'browserRefresherCache'; + +self.addEventListener('install', function(event) { + event.waitUntil( + Promise.all([caches.open(name), + fetch('./browser_cached_force_refresh.html')]).then(function(results) { + var cache = results[0]; + var response = results[1]; + return cache.put('./browser_base_force_refresh.html', response); + }) + ); +}); + +self.addEventListener('fetch', function (event) { + event.respondWith( + caches.open(name).then(function(cache) { + return cache.match(event.request); + }).then(function(response) { + return response || fetch(event.request); + }) + ); +}); diff --git a/dom/workers/test/serviceworkers/mochitest.ini b/dom/workers/test/serviceworkers/mochitest.ini index 8ed9664396..9a677b390a 100644 --- a/dom/workers/test/serviceworkers/mochitest.ini +++ b/dom/workers/test/serviceworkers/mochitest.ini @@ -98,6 +98,15 @@ support-files = fetch/sandbox/register.html fetch/sandbox/unregister.html fetch/sandbox/sandbox_test.js + fetch/upgrade-insecure/upgrade-insecure_test.js + fetch/upgrade-insecure/embedder.html + fetch/upgrade-insecure/embedder.html^headers^ + fetch/upgrade-insecure/image.html + fetch/upgrade-insecure/image-20px.png + fetch/upgrade-insecure/image-40px.png + fetch/upgrade-insecure/realindex.html + fetch/upgrade-insecure/register.html + fetch/upgrade-insecure/unregister.html match_all_properties_worker.js match_all_clients/match_all_controlled.html test_serviceworker_interfaces.js @@ -274,7 +283,7 @@ skip-if = toolkit == "android" || toolkit == "gonk" [test_file_blob_upload.html] [test_unresolved_fetch_interception.html] [test_hsts_upgrade_intercept.html] -skip-if = e10s # Bug 1214305 +[test_csp_upgrade-insecure_intercept.html] [test_serviceworker_header.html] [test_openWindow.html] skip-if = toolkit == "android" || toolkit == "gonk" diff --git a/dom/workers/test/serviceworkers/test_csp_upgrade-insecure_intercept.html b/dom/workers/test/serviceworkers/test_csp_upgrade-insecure_intercept.html new file mode 100644 index 0000000000..45d02a409a --- /dev/null +++ b/dom/workers/test/serviceworkers/test_csp_upgrade-insecure_intercept.html @@ -0,0 +1,56 @@ + + + + + Test that a CSP upgraded request can be intercepted by a service worker + + + + +

+
+ +
+

+
+
+
+
diff --git a/embedding/components/printingui/unixshared/nsPrintingPromptService.cpp b/embedding/components/printingui/unixshared/nsPrintingPromptService.cpp
index 1cdce8c50b..bdf3ab733d 100644
--- a/embedding/components/printingui/unixshared/nsPrintingPromptService.cpp
+++ b/embedding/components/printingui/unixshared/nsPrintingPromptService.cpp
@@ -43,7 +43,7 @@ public:
       return CallCreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID, &mBlock);
     }
     nsIDialogParamBlock * operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN { return mBlock; }
-    operator nsIDialogParamBlock * const ()  { return mBlock; }
+    operator nsIDialogParamBlock * () const { return mBlock; }
 
 private:
     nsIDialogParamBlock *mBlock;
diff --git a/gfx/layers/Layers.cpp b/gfx/layers/Layers.cpp
index 6992daee85..8e932e8b30 100644
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -941,7 +941,7 @@ Layer::ApplyPendingUpdatesForThisTransaction()
   }
 }
 
-const float
+float
 Layer::GetLocalOpacity()
 {
   float opacity = mOpacity;
diff --git a/gfx/layers/Layers.h b/gfx/layers/Layers.h
index eb85e37e4e..c43a2d701d 100644
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -1354,7 +1354,7 @@ public:
    * Returns the local opacity for this layer: either mOpacity or,
    * for shadow layers, GetShadowOpacity()
    */
-  const float GetLocalOpacity();
+  float GetLocalOpacity();
 
   /**
    * DRAWING PHASE ONLY
@@ -1637,7 +1637,7 @@ public:
    * marked as needed to be recomposited.
    */
   const nsIntRegion& GetInvalidRegion() { return mInvalidRegion; }
-  const void AddInvalidRegion(const nsIntRegion& aRegion) {
+  void AddInvalidRegion(const nsIntRegion& aRegion) {
     mInvalidRegion.Or(mInvalidRegion, aRegion);
   }
 
diff --git a/gfx/thebes/gfxUtils.cpp b/gfx/thebes/gfxUtils.cpp
index e9a722ffeb..6425377c27 100644
--- a/gfx/thebes/gfxUtils.cpp
+++ b/gfx/thebes/gfxUtils.cpp
@@ -92,11 +92,11 @@ void mozilla_dump_image(void* bytes, int width, int height, int bytepp,
 
 }
 
-static const uint8_t PremultiplyValue(uint8_t a, uint8_t v) {
+static uint8_t PremultiplyValue(uint8_t a, uint8_t v) {
     return gfxUtils::sPremultiplyTable[a*256+v];
 }
 
-static const uint8_t UnpremultiplyValue(uint8_t a, uint8_t v) {
+static uint8_t UnpremultiplyValue(uint8_t a, uint8_t v) {
     return gfxUtils::sUnpremultiplyTable[a*256+v];
 }
 
diff --git a/ipc/chromium/src/chrome/common/ipc_message.h b/ipc/chromium/src/chrome/common/ipc_message.h
index 27f6e519da..4cf6c9e201 100644
--- a/ipc/chromium/src/chrome/common/ipc_message.h
+++ b/ipc/chromium/src/chrome/common/ipc_message.h
@@ -194,7 +194,7 @@ class Message : public Pickle {
     header()->seqno = aSeqno;
   }
 
-  const char* const name() const {
+  const char* name() const {
     return name_;
   }
 
diff --git a/ipc/chromium/src/chrome/common/ipc_message_utils.h b/ipc/chromium/src/chrome/common/ipc_message_utils.h
index e3373b8a3e..b063867af8 100644
--- a/ipc/chromium/src/chrome/common/ipc_message_utils.h
+++ b/ipc/chromium/src/chrome/common/ipc_message_utils.h
@@ -53,7 +53,7 @@ class MessageIterator {
       NOTREACHED();
     return val;
   }
-  const void NextData(const char** data, int* length) const {
+  void NextData(const char** data, int* length) const {
     if (!msg_.ReadData(&iter_, data, length)) {
       NOTREACHED();
     }
diff --git a/ipc/glue/ProtocolUtils.h b/ipc/glue/ProtocolUtils.h
index 50b2d4ca3b..1a30d03ab7 100644
--- a/ipc/glue/ProtocolUtils.h
+++ b/ipc/glue/ProtocolUtils.h
@@ -336,7 +336,7 @@ using ManagedContainer = nsTHashtable>;
 
 template
 Protocol*
-LoneManagedOrNull(const ManagedContainer& aManagees)
+LoneManagedOrNullAsserts(const ManagedContainer& aManagees)
 {
     if (aManagees.IsEmpty()) {
         return nullptr;
@@ -345,6 +345,19 @@ LoneManagedOrNull(const ManagedContainer& aManagees)
     return aManagees.ConstIter().Get()->GetKey();
 }
 
+// appId's are for B2G only currently, where managees.Count() == 1. This is
+// not guaranteed currently in Desktop, so for paths used for desktop,
+// don't assert there's one managee.
+template
+Protocol*
+SingleManagedOrNull(const ManagedContainer& aManagees)
+{
+    if (aManagees.Count() != 1) {
+        return nullptr;
+    }
+    return aManagees.ConstIter().Get()->GetKey();
+}
+
 } // namespace mozilla
 
 
diff --git a/ipc/ipdl/ipdl/lower.py b/ipc/ipdl/ipdl/lower.py
index e48a4fe996..b1b1795997 100644
--- a/ipc/ipdl/ipdl/lower.py
+++ b/ipc/ipdl/ipdl/lower.py
@@ -574,7 +574,7 @@ def _cxxConstPtrToType(ipdltype, side):
         t.ptrconstptr = 1
         return t
     t.const = 1
-    t.ptrconst = 1
+    t.ptr = 1
     return t
 
 def _allocMethod(ptype, side):
diff --git a/ipc/ipdl/test/cxx/TestMultiMgrs.cpp b/ipc/ipdl/test/cxx/TestMultiMgrs.cpp
index ab9daac173..422b3fea33 100644
--- a/ipc/ipdl/test/cxx/TestMultiMgrs.cpp
+++ b/ipc/ipdl/test/cxx/TestMultiMgrs.cpp
@@ -78,10 +78,10 @@ TestMultiMgrsChild::RecvCheck()
 
     TestMultiMgrsLeftChild* leftie =
         static_cast(
-            LoneManagedOrNull(ManagedPTestMultiMgrsLeftChild()));
+            LoneManagedOrNullAsserts(ManagedPTestMultiMgrsLeftChild()));
     TestMultiMgrsRightChild* rightie =
         static_cast(
-            LoneManagedOrNull(ManagedPTestMultiMgrsRightChild()));
+            LoneManagedOrNullAsserts(ManagedPTestMultiMgrsRightChild()));
 
     if (!leftie->HasChild(mBottomL))
         fail("leftie didn't have a child it was supposed to!");
diff --git a/layout/base/nsPresContext.h b/layout/base/nsPresContext.h
index a66253850f..7192d6702e 100644
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -403,18 +403,18 @@ public:
   /**
    * Get the default colors
    */
-  const nscolor DefaultColor() const { return mDefaultColor; }
-  const nscolor DefaultBackgroundColor() const { return mBackgroundColor; }
-  const nscolor DefaultLinkColor() const { return mLinkColor; }
-  const nscolor DefaultActiveLinkColor() const { return mActiveLinkColor; }
-  const nscolor DefaultVisitedLinkColor() const { return mVisitedLinkColor; }
-  const nscolor FocusBackgroundColor() const { return mFocusBackgroundColor; }
-  const nscolor FocusTextColor() const { return mFocusTextColor; }
+  nscolor DefaultColor() const { return mDefaultColor; }
+  nscolor DefaultBackgroundColor() const { return mBackgroundColor; }
+  nscolor DefaultLinkColor() const { return mLinkColor; }
+  nscolor DefaultActiveLinkColor() const { return mActiveLinkColor; }
+  nscolor DefaultVisitedLinkColor() const { return mVisitedLinkColor; }
+  nscolor FocusBackgroundColor() const { return mFocusBackgroundColor; }
+  nscolor FocusTextColor() const { return mFocusTextColor; }
 
   /**
    * Body text color, for use in quirks mode only.
    */
-  const nscolor BodyTextColor() const { return mBodyTextColor; }
+  nscolor BodyTextColor() const { return mBodyTextColor; }
   void SetBodyTextColor(nscolor aColor) { mBodyTextColor = aColor; }
 
   bool GetUseFocusColors() const { return mUseFocusColors; }
diff --git a/layout/build/nsLayoutCID.h b/layout/build/nsLayoutCID.h
index 5e7d639b86..a3cd7c8ddb 100644
--- a/layout/build/nsLayoutCID.h
+++ b/layout/build/nsLayoutCID.h
@@ -82,4 +82,7 @@
 #define SERVICEWORKERMANAGER_CID \
 { 0xc74bde32, 0xbcc7, 0x4840, { 0x84, 0x30, 0xc7, 0x33, 0x35, 0x1b, 0x21, 0x2a } }
 
+#define NOTIFICATIONTELEMETRYSERVICE_CID \
+{ 0x5995b782, 0x6a0e, 0x4066, { 0xaa, 0xc5, 0x27, 0x6f, 0x0a, 0x9a, 0xd8, 0xcf } }
+
 #endif /* nsLayoutCID_h__ */
diff --git a/layout/build/nsLayoutModule.cpp b/layout/build/nsLayoutModule.cpp
index cb668f60c4..9d94d3a86d 100644
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -87,6 +87,7 @@
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/dom/workers/ServiceWorkerManager.h"
 #include "mozilla/dom/workers/WorkerDebuggerManager.h"
+#include "mozilla/dom/Notification.h"
 #include "mozilla/OSFileConstants.h"
 #include "mozilla/Services.h"
 
@@ -273,6 +274,7 @@ using mozilla::dom::UDPSocketChild;
 using mozilla::dom::time::TimeService;
 using mozilla::net::StreamingProtocolControllerService;
 using mozilla::gmp::GeckoMediaPluginService;
+using mozilla::dom::NotificationTelemetryService;
 
 // Transformiix
 /* 5d5d92cd-6bf8-11d9-bf4a-000a95dc234c */
@@ -400,6 +402,7 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(InputPortData)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIPresentationService,
                                          NS_CreatePresentationService)
 NS_GENERIC_FACTORY_CONSTRUCTOR(PresentationSessionTransport)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(NotificationTelemetryService, Init)
 //-----------------------------------------------------------------------------
 
 static bool gInitialized = false;
@@ -762,6 +765,7 @@ NS_DEFINE_NAMED_CID(NS_TEXTEDITOR_CID);
 NS_DEFINE_NAMED_CID(DOMREQUEST_SERVICE_CID);
 NS_DEFINE_NAMED_CID(QUOTA_MANAGER_CID);
 NS_DEFINE_NAMED_CID(SERVICEWORKERMANAGER_CID);
+NS_DEFINE_NAMED_CID(NOTIFICATIONTELEMETRYSERVICE_CID);
 NS_DEFINE_NAMED_CID(WORKERDEBUGGERMANAGER_CID);
 #ifdef MOZ_WIDGET_GONK
 NS_DEFINE_NAMED_CID(SYSTEMWORKERMANAGER_CID);
@@ -1070,6 +1074,7 @@ static const mozilla::Module::CIDEntry kLayoutCIDs[] = {
   { &kDOMREQUEST_SERVICE_CID, false, nullptr, DOMRequestServiceConstructor },
   { &kQUOTA_MANAGER_CID, false, nullptr, QuotaManagerConstructor },
   { &kSERVICEWORKERMANAGER_CID, false, nullptr, ServiceWorkerManagerConstructor },
+  { &kNOTIFICATIONTELEMETRYSERVICE_CID, false, nullptr, NotificationTelemetryServiceConstructor },
   { &kWORKERDEBUGGERMANAGER_CID, true, nullptr, WorkerDebuggerManagerConstructor },
 #ifdef MOZ_WIDGET_GONK
   { &kSYSTEMWORKERMANAGER_CID, true, nullptr, SystemWorkerManagerConstructor },
@@ -1238,6 +1243,7 @@ static const mozilla::Module::ContractIDEntry kLayoutContracts[] = {
   { DOMREQUEST_SERVICE_CONTRACTID, &kDOMREQUEST_SERVICE_CID },
   { QUOTA_MANAGER_CONTRACTID, &kQUOTA_MANAGER_CID },
   { SERVICEWORKERMANAGER_CONTRACTID, &kSERVICEWORKERMANAGER_CID },
+  { NOTIFICATIONTELEMETRYSERVICE_CONTRACTID, &kNOTIFICATIONTELEMETRYSERVICE_CID },
   { WORKERDEBUGGERMANAGER_CONTRACTID, &kWORKERDEBUGGERMANAGER_CID },
 #ifdef MOZ_WIDGET_GONK
   { SYSTEMWORKERMANAGER_CONTRACTID, &kSYSTEMWORKERMANAGER_CID },
@@ -1352,6 +1358,7 @@ static const mozilla::Module::CategoryEntry kLayoutCategories[] = {
 #endif
   { "profile-after-change", "PresentationDeviceManager", PRESENTATION_DEVICE_MANAGER_CONTRACTID },
   { "profile-after-change", "PresentationService", PRESENTATION_SERVICE_CONTRACTID },
+  { "profile-after-change", "Notification Telemetry Service", NOTIFICATIONTELEMETRYSERVICE_CONTRACTID },
   { nullptr }
 };
 
diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp
index ac31646c40..2c0bbc0675 100644
--- a/layout/generic/nsGridContainerFrame.cpp
+++ b/layout/generic/nsGridContainerFrame.cpp
@@ -193,17 +193,302 @@ private:
 #endif
 };
 
+/**
+ * Utility class to find line names.  It provides an interface to lookup line
+ * names with a dynamic number of repeat(auto-fill/fit) tracks taken into
+ * account.
+ */
+class MOZ_STACK_CLASS nsGridContainerFrame::LineNameMap
+{
+public:
+  /**
+   * Create a LineNameMap.
+   * @param aGridTemplate is the grid-template-rows/columns data for this axis
+   * @param aNumRepeatTracks the number of actual tracks associated with
+   *   a repeat(auto-fill/fit) track (zero or more), or zero if there is no
+   *   specified repeat(auto-fill/fit) track
+   */
+  LineNameMap(const nsStyleGridTemplate& aGridTemplate,
+              uint32_t                   aNumRepeatTracks)
+    : mLineNameLists(aGridTemplate.mLineNameLists)
+    , mRepeatAutoLineNameListBefore(aGridTemplate.mRepeatAutoLineNameListBefore)
+    , mRepeatAutoLineNameListAfter(aGridTemplate.mRepeatAutoLineNameListAfter)
+    , mRepeatAutoStart(aGridTemplate.HasRepeatAuto() ?
+                         aGridTemplate.mRepeatAutoIndex : 0)
+    , mRepeatAutoEnd(mRepeatAutoStart + aNumRepeatTracks)
+    , mRepeatEndDelta(aGridTemplate.HasRepeatAuto() ?
+                        int32_t(aNumRepeatTracks) - 1 :
+                        0)
+    , mTemplateLinesEnd(mLineNameLists.Length() + mRepeatEndDelta)
+    , mHasRepeatAuto(aGridTemplate.HasRepeatAuto())
+  {
+    MOZ_ASSERT(mHasRepeatAuto || aNumRepeatTracks == 0);
+    MOZ_ASSERT(mRepeatAutoStart <= mLineNameLists.Length());
+    MOZ_ASSERT(!mHasRepeatAuto || mLineNameLists.Length() >= 2);
+  }
+
+  /**
+   * Find the aNth occurrence of aName, searching forward if aNth is positive,
+   * and in reverse if aNth is negative (aNth == 0 is invalid), starting from
+   * aFromIndex (not inclusive), and return a 1-based line number.
+   * Also take into account there is an unconditional match at aImplicitLine
+   * unless it's zero.
+   * Return zero if aNth occurrences can't be found.  In that case, aNth has
+   * been decremented with the number of occurrences that were found (if any).
+   *
+   * E.g. to search for "A 2" forward from the start of the grid: aName is "A"
+   * aNth is 2 and aFromIndex is zero.  To search for "A -2", aNth is -2 and
+   * aFromIndex is ExplicitGridEnd + 1 (which is the line "before" the last
+   * line when we're searching in reverse).  For "span A 2", aNth is 2 when
+   * used on a grid-[row|column]-end property and -2 for a *-start property,
+   * and aFromIndex is the line (which we should skip) on the opposite property.
+   */
+  uint32_t FindNamedLine(const nsString& aName, int32_t* aNth,
+                         uint32_t aFromIndex, uint32_t aImplicitLine) const
+  {
+    MOZ_ASSERT(aNth && *aNth != 0);
+    if (*aNth > 0) {
+      return FindLine(aName, aNth, aFromIndex, aImplicitLine);
+    }
+    int32_t nth = -*aNth;
+    int32_t line = RFindLine(aName, &nth, aFromIndex, aImplicitLine);
+    *aNth = -nth;
+    return line;
+  }
+
+private:
+  /**
+   * @see FindNamedLine, this function searches forward.
+   */
+  uint32_t FindLine(const nsString& aName, int32_t* aNth,
+                    uint32_t aFromIndex, uint32_t aImplicitLine) const
+  {
+    MOZ_ASSERT(aNth && *aNth > 0);
+    int32_t nth = *aNth;
+    const uint32_t end = mTemplateLinesEnd;
+    uint32_t line;
+    uint32_t i = aFromIndex;
+    for (; i < end; i = line) {
+      line = i + 1;
+      if (line == aImplicitLine || Contains(i, aName)) {
+        if (--nth == 0) {
+          return line;
+        }
+      }
+    }
+    if (aImplicitLine > i) {
+      // aImplicitLine is after the lines we searched above so it's last.
+      // (grid-template-areas has more tracks than grid-template-[rows|columns])
+      if (--nth == 0) {
+        return aImplicitLine;
+      }
+    }
+    MOZ_ASSERT(nth > 0, "should have returned a valid line above already");
+    *aNth = nth;
+    return 0;
+  }
+
+  /**
+   * @see FindNamedLine, this function searches in reverse.
+   */
+  uint32_t RFindLine(const nsString& aName, int32_t* aNth,
+                     uint32_t aFromIndex, uint32_t aImplicitLine) const
+  {
+    MOZ_ASSERT(aNth && *aNth > 0);
+    if (MOZ_UNLIKELY(aFromIndex == 0)) {
+      return 0; // There are no named lines beyond the start of the explicit grid.
+    }
+    --aFromIndex; // (shift aFromIndex so we can treat it as inclusive)
+    int32_t nth = *aNth;
+    // The implicit line may be beyond the explicit grid so we match
+    // this line first if it's within the mTemplateLinesEnd..aFromIndex range.
+    const uint32_t end = mTemplateLinesEnd;
+    if (aImplicitLine > end && aImplicitLine < aFromIndex) {
+      if (--nth == 0) {
+        return aImplicitLine;
+      }
+    }
+    for (uint32_t i = std::min(aFromIndex, end); i; --i) {
+      if (i == aImplicitLine || Contains(i - 1, aName)) {
+        if (--nth == 0) {
+          return i;
+        }
+      }
+    }
+    MOZ_ASSERT(nth > 0, "should have returned a valid line above already");
+    *aNth = nth;
+    return 0;
+  }
+
+  // Return true if aName exists at aIndex.
+  bool Contains(uint32_t aIndex, const nsString& aName) const
+  {
+    if (!mHasRepeatAuto) {
+      return mLineNameLists[aIndex].Contains(aName);
+    }
+    if (aIndex < mRepeatAutoEnd && aIndex >= mRepeatAutoStart &&
+        mRepeatAutoLineNameListBefore.Contains(aName)) {
+      return true;
+    }
+    if (aIndex <= mRepeatAutoEnd && aIndex > mRepeatAutoStart &&
+        mRepeatAutoLineNameListAfter.Contains(aName)) {
+      return true;
+    }
+    if (aIndex <= mRepeatAutoStart) {
+      return mLineNameLists[aIndex].Contains(aName) ||
+             (aIndex == mRepeatAutoEnd &&
+              mLineNameLists[aIndex + 1].Contains(aName));
+    }
+    return aIndex >= mRepeatAutoEnd &&
+           mLineNameLists[aIndex - mRepeatEndDelta].Contains(aName);
+  }
+
+  // Some style data references, for easy access.
+  const nsTArray>& mLineNameLists;
+  const nsTArray& mRepeatAutoLineNameListBefore;
+  const nsTArray& mRepeatAutoLineNameListAfter;
+  // The index of the repeat(auto-fill/fit) track, or zero if there is none.
+  const uint32_t mRepeatAutoStart;
+  // The (hypothetical) index of the last such repeat() track.
+  const uint32_t mRepeatAutoEnd;
+  // The difference between mTemplateLinesEnd and mLineNameLists.Length().
+  const int32_t mRepeatEndDelta;
+  // The end of the line name lists with repeat(auto-fill/fit) tracks accounted
+  // for.  It is equal to mLineNameLists.Length() when a repeat() track
+  // generates one track (making mRepeatEndDelta == 0).
+  const uint32_t mTemplateLinesEnd;
+  // True if there is a specified repeat(auto-fill/fit) track.
+  const bool mHasRepeatAuto;
+};
+
 /**
  * Encapsulates CSS track-sizing functions.
  */
 struct MOZ_STACK_CLASS nsGridContainerFrame::TrackSizingFunctions
 {
+  TrackSizingFunctions(const nsStyleGridTemplate& aGridTemplate,
+                       const nsStyleCoord&        aAutoMinSizing,
+                       const nsStyleCoord&        aAutoMaxSizing)
+    : mMinSizingFunctions(aGridTemplate.mMinTrackSizingFunctions)
+    , mMaxSizingFunctions(aGridTemplate.mMaxTrackSizingFunctions)
+    , mAutoMinSizing(aAutoMinSizing)
+    , mAutoMaxSizing(aAutoMaxSizing)
+    , mExplicitGridOffset(0)
+    , mRepeatAutoStart(aGridTemplate.HasRepeatAuto() ?
+                         aGridTemplate.mRepeatAutoIndex : 0)
+    , mRepeatAutoEnd(mRepeatAutoStart)
+    , mRepeatEndDelta(0)
+    , mHasRepeatAuto(aGridTemplate.HasRepeatAuto())
+  {
+    MOZ_ASSERT(mMinSizingFunctions.Length() == mMaxSizingFunctions.Length());
+    MOZ_ASSERT(!mHasRepeatAuto ||
+               (mMinSizingFunctions.Length() >= 1 &&
+                mRepeatAutoStart < mMinSizingFunctions.Length()));
+  }
+
+  /**
+   * Initialize the number of auto-fill/fit tracks to use and return that.
+   * (zero if no auto-fill/fit track was specified)
+   */
+  uint32_t InitRepeatTracks(nscoord aGridGap, nscoord aMinSize, nscoord aSize,
+                            nscoord aMaxSize)
+  {
+    uint32_t repeatTracks =
+      CalculateRepeatFillCount(aGridGap, aMinSize, aSize, aMaxSize);
+    SetNumRepeatTracks(repeatTracks);
+    return repeatTracks;
+  }
+
+  uint32_t CalculateRepeatFillCount(nscoord aGridGap,
+                                    nscoord aMinSize,
+                                    nscoord aSize,
+                                    nscoord aMaxSize) const
+  {
+    if (!mHasRepeatAuto) {
+      return 0;
+    }
+    // Spec quotes are from https://drafts.csswg.org/css-grid/#repeat-notation
+    const uint32_t numTracks = mMinSizingFunctions.Length();
+    MOZ_ASSERT(numTracks >= 1, "expected at least the repeat() track");
+    nscoord maxFill = aSize != NS_UNCONSTRAINEDSIZE ? aSize : aMaxSize;
+    if (maxFill == NS_UNCONSTRAINEDSIZE && aMinSize == NS_UNCONSTRAINEDSIZE) {
+      // "Otherwise, the specified track list repeats only once."
+      return 1;
+    }
+    nscoord repeatTrackSize = 0;
+    // Note that the repeat() track size is included in |sum| in this loop.
+    nscoord sum = 0;
+    for (uint32_t i = 0; i < numTracks; ++i) {
+      // "The  variant ... requires definite minimum track sizes"
+      // "... treating each track as its max track sizing function if that is
+      //  definite or as its minimum track sizing function otherwise"
+      // https://drafts.csswg.org/css-grid/#valdef-repeat-auto-fill
+      const auto& maxCoord = mMaxSizingFunctions[i];
+      const auto* coord = &maxCoord;
+      if (!coord->IsCoordPercentCalcUnit()) {
+        coord = &mMinSizingFunctions[i];
+        if (!coord->IsCoordPercentCalcUnit()) {
+          return 1;
+        }
+      }
+      nscoord trackSize = nsRuleNode::ComputeCoordPercentCalc(*coord, aSize);
+      if (i == mRepeatAutoStart) {
+        // Use a minimum 1px for the repeat() track-size.
+        if (trackSize < AppUnitsPerCSSPixel()) {
+          trackSize = AppUnitsPerCSSPixel();
+        }
+        repeatTrackSize = trackSize;
+      }
+      sum += trackSize;
+    }
+    if (numTracks > 1) {
+      // Add grid-gaps for all the tracks including the repeat() track.
+      sum += aGridGap * (numTracks - 1);
+    }
+    nscoord available = maxFill != NS_UNCONSTRAINEDSIZE ? maxFill : aMinSize;
+    nscoord spaceToFill = available - sum;
+    if (spaceToFill <= 0) {
+      // "if any number of repetitions would overflow, then 1 repetition"
+      return 1;
+    }
+    // Calculate the max number of tracks that fits without overflow.
+    uint32_t numRepeatTracks = (spaceToFill / (repeatTrackSize + aGridGap)) + 1;
+    if (maxFill == NS_UNCONSTRAINEDSIZE) {
+      // "Otherwise, if the grid container has a definite min size in
+      // the relevant axis, the number of repetitions is the largest possible
+      // positive integer that fulfills that minimum requirement."
+      ++numRepeatTracks; // one more to ensure the grid is at least min-size
+    }
+    // Clamp the number of repeat tracks so that the last line <= kMaxLine.
+    // (note that |numTracks| already includes one repeat() track)
+    const uint32_t maxRepeatTracks = nsStyleGridLine::kMaxLine - numTracks;
+    return std::min(numRepeatTracks, maxRepeatTracks);
+  }
+
+  /**
+   * Compute the explicit grid end line number (in a zero-based grid).
+   * @param aGridTemplateAreasEnd 'grid-template-areas' end line in this axis
+   */
+  uint32_t ComputeExplicitGridEnd(uint32_t aGridTemplateAreasEnd)
+  {
+    uint32_t end = NumExplicitTracks() + 1;
+    end = std::max(end, aGridTemplateAreasEnd);
+    end = std::min(end, uint32_t(nsStyleGridLine::kMaxLine));
+    return end;
+  }
+
   const nsStyleCoord& MinSizingFor(uint32_t aTrackIndex) const
   {
     if (MOZ_UNLIKELY(aTrackIndex < mExplicitGridOffset)) {
       return mAutoMinSizing;
     }
     uint32_t index = aTrackIndex - mExplicitGridOffset;
+    if (index >= mRepeatAutoStart) {
+      if (index < mRepeatAutoEnd) {
+        return mMinSizingFunctions[mRepeatAutoStart];
+      }
+      index -= mRepeatEndDelta;
+    }
     return index < mMinSizingFunctions.Length() ?
       mMinSizingFunctions[index] : mAutoMinSizing;
   }
@@ -213,15 +498,47 @@ struct MOZ_STACK_CLASS nsGridContainerFrame::TrackSizingFunctions
       return mAutoMaxSizing;
     }
     uint32_t index = aTrackIndex - mExplicitGridOffset;
+    if (index >= mRepeatAutoStart) {
+      if (index < mRepeatAutoEnd) {
+        return mMaxSizingFunctions[mRepeatAutoStart];
+      }
+      index -= mRepeatEndDelta;
+    }
     return index < mMaxSizingFunctions.Length() ?
       mMaxSizingFunctions[index] : mAutoMaxSizing;
   }
+  uint32_t NumExplicitTracks() const
+  {
+    return mMinSizingFunctions.Length() + mRepeatEndDelta;
+  }
+  uint32_t NumRepeatTracks() const
+  {
+    return mRepeatAutoEnd - mRepeatAutoStart;
+  }
+  void SetNumRepeatTracks(uint32_t aNumRepeatTracks)
+  {
+    MOZ_ASSERT(mHasRepeatAuto || aNumRepeatTracks == 0);
+    mRepeatAutoEnd = mRepeatAutoStart + aNumRepeatTracks;
+    mRepeatEndDelta = mHasRepeatAuto ?
+                        int32_t(aNumRepeatTracks) - 1 :
+                        0;
+  }
 
+  // Some style data references, for easy access.
   const nsTArray& mMinSizingFunctions;
   const nsTArray& mMaxSizingFunctions;
   const nsStyleCoord& mAutoMinSizing;
   const nsStyleCoord& mAutoMaxSizing;
+  // Offset from the start of the implicit grid to the first explicit track.
   uint32_t mExplicitGridOffset;
+  // The index of the repeat(auto-fill/fit) track, or zero if there is none.
+  const uint32_t mRepeatAutoStart;
+  // The (hypothetical) index of the last such repeat() track.
+  uint32_t mRepeatAutoEnd;
+  // The difference between mExplicitGridEnd and mMinSizingFunctions.Length().
+  int32_t mRepeatEndDelta;
+  // True if there is a specified repeat(auto-fill/fit) track.
+  const bool mHasRepeatAuto;
 };
 
 /**
@@ -657,18 +974,12 @@ private:
     , mGridStyle(aGridStyle)
     , mCols(eLogicalAxisInline)
     , mRows(eLogicalAxisBlock)
-    , mColFunctions({
-        mGridStyle->mGridTemplateColumns.mMinTrackSizingFunctions,
-        mGridStyle->mGridTemplateColumns.mMaxTrackSizingFunctions,
-        mGridStyle->mGridAutoColumnsMin,
-        mGridStyle->mGridAutoColumnsMax,
-      })
-    , mRowFunctions({
-        mGridStyle->mGridTemplateRows.mMinTrackSizingFunctions,
-        mGridStyle->mGridTemplateRows.mMaxTrackSizingFunctions,
-        mGridStyle->mGridAutoRowsMin,
-        mGridStyle->mGridAutoRowsMax,
-      })
+    , mColFunctions(mGridStyle->mGridTemplateColumns,
+                    mGridStyle->mGridAutoColumnsMin,
+                    mGridStyle->mGridAutoColumnsMax)
+    , mRowFunctions(mGridStyle->mGridTemplateRows,
+                    mGridStyle->mGridAutoRowsMin,
+                    mGridStyle->mGridAutoRowsMax)
     , mReflowState(aReflowState)
     , mRenderingContext(aRenderingContext)
     , mWM(aWM)
@@ -682,104 +993,6 @@ bool IsMinContent(const nsStyleCoord& aCoord)
          aCoord.GetIntValue() == NS_STYLE_GRID_TRACK_BREADTH_MIN_CONTENT;
 }
 
-/**
- * @see FindNamedLine, this function searches forward.
- */
-static uint32_t
-FindLine(const nsString& aName, int32_t* aNth,
-         uint32_t aFromIndex, uint32_t aImplicitLine,
-         const nsTArray>& aNameList)
-{
-  MOZ_ASSERT(aNth && *aNth > 0);
-  int32_t nth = *aNth;
-  const uint32_t len = aNameList.Length();
-  uint32_t line;
-  uint32_t i = aFromIndex;
-  for (; i < len; i = line) {
-    line = i + 1;
-    if (line == aImplicitLine || aNameList[i].Contains(aName)) {
-      if (--nth == 0) {
-        return line;
-      }
-    }
-  }
-  if (aImplicitLine > i) {
-    // aImplicitLine is after the lines we searched above so it's last.
-    // (grid-template-areas has more tracks than grid-template-[rows|columns])
-    if (--nth == 0) {
-      return aImplicitLine;
-    }
-  }
-  MOZ_ASSERT(nth > 0, "should have returned a valid line above already");
-  *aNth = nth;
-  return 0;
-}
-
-/**
- * @see FindNamedLine, this function searches in reverse.
- */
-static uint32_t
-RFindLine(const nsString& aName, int32_t* aNth,
-          uint32_t aFromIndex, uint32_t aImplicitLine,
-          const nsTArray>& aNameList)
-{
-  MOZ_ASSERT(aNth && *aNth > 0);
-  if (MOZ_UNLIKELY(aFromIndex == 0)) {
-    return 0; // There are no named lines beyond the start of the explicit grid.
-  }
-  --aFromIndex; // (shift aFromIndex so we can treat it as inclusive)
-  int32_t nth = *aNth;
-  const uint32_t len = aNameList.Length();
-  // The implicit line may be beyond the length of aNameList so we match this
-  // line first if it's within the len..aFromIndex range.
-  if (aImplicitLine > len && aImplicitLine < aFromIndex) {
-    if (--nth == 0) {
-      return aImplicitLine;
-    }
-  }
-  for (uint32_t i = std::min(aFromIndex, len); i; --i) {
-    if (i == aImplicitLine || aNameList[i - 1].Contains(aName)) {
-      if (--nth == 0) {
-        return i;
-      }
-    }
-  }
-  MOZ_ASSERT(nth > 0, "should have returned a valid line above already");
-  *aNth = nth;
-  return 0;
-}
-
-/**
- * Find the aNth occurrence of aName, searching forward if aNth is positive,
- * and in reverse if aNth is negative (aNth == 0 is invalid), starting from
- * aFromIndex (not inclusive), and return a 1-based line number.
- * Also take into account there is an unconditional match at aImplicitLine
- * unless it's zero.
- * Return zero if aNth occurrences can't be found.  In that case, aNth has
- * been decremented with the number of occurrences that were found (if any).
- *
- * E.g. to search for "A 2" forward from the start of the grid: aName is "A"
- * aNth is 2 and aFromIndex is zero.  To search for "A -2", aNth is -2 and
- * aFromIndex is ExplicitGridEnd + 1 (which is the line "before" the last
- * line when we're searching in reverse).  For "span A 2", aNth is 2 when
- * used on a grid-[row|column]-end property and -2 for a *-start property,
- * and aFromIndex is the line (which we should skip) on the opposite property.
- */
-static uint32_t
-FindNamedLine(const nsString& aName, int32_t* aNth,
-              uint32_t aFromIndex, uint32_t aImplicitLine,
-              const nsTArray>& aNameList)
-{
-  MOZ_ASSERT(aNth && *aNth != 0);
-  if (*aNth > 0) {
-    return ::FindLine(aName, aNth, aFromIndex, aImplicitLine, aNameList);
-  }
-  int32_t nth = -*aNth;
-  int32_t line = ::RFindLine(aName, &nth, aFromIndex, aImplicitLine, aNameList);
-  *aNth = -nth;
-  return line;
-}
-
 /**
  * A convenience method to lookup a name in 'grid-template-areas'.
  * @param aStyle the StylePosition() for the grid container
@@ -1264,7 +1477,7 @@ nsGridContainerFrame::ResolveLine(
   const nsStyleGridLine& aLine,
   int32_t aNth,
   uint32_t aFromIndex,
-  const nsTArray>& aLineNameList,
+  const LineNameMap& aNameMap,
   uint32_t GridNamedArea::* aAreaStart,
   uint32_t GridNamedArea::* aAreaEnd,
   uint32_t aExplicitGridEnd,
@@ -1297,8 +1510,8 @@ nsGridContainerFrame::ResolveLine(
           lineName.AppendLiteral("-end");
           implicitLine = area ? area->*aAreaEnd : 0;
         }
-        line = ::FindNamedLine(lineName, &aNth, aFromIndex, implicitLine,
-                               aLineNameList);
+        line = aNameMap.FindNamedLine(lineName, &aNth, aFromIndex,
+                                      implicitLine);
       }
     }
 
@@ -1320,8 +1533,8 @@ nsGridContainerFrame::ResolveLine(
           implicitLine = area->*areaEdge;
         }
       }
-      line = ::FindNamedLine(aLine.mLineName, &aNth, aFromIndex, implicitLine,
-                             aLineNameList);
+      line = aNameMap.FindNamedLine(aLine.mLineName, &aNth, aFromIndex,
+                                    implicitLine);
     }
 
     if (line == 0) {
@@ -1348,7 +1561,7 @@ nsGridContainerFrame::LinePair
 nsGridContainerFrame::ResolveLineRangeHelper(
   const nsStyleGridLine& aStart,
   const nsStyleGridLine& aEnd,
-  const nsTArray>& aLineNameList,
+  const LineNameMap& aNameMap,
   uint32_t GridNamedArea::* aAreaStart,
   uint32_t GridNamedArea::* aAreaEnd,
   uint32_t aExplicitGridEnd,
@@ -1370,7 +1583,7 @@ nsGridContainerFrame::ResolveLineRangeHelper(
     }
 
     uint32_t from = aEnd.mInteger < 0 ? aExplicitGridEnd + 1: 0;
-    auto end = ResolveLine(aEnd, aEnd.mInteger, from, aLineNameList, aAreaStart,
+    auto end = ResolveLine(aEnd, aEnd.mInteger, from, aNameMap, aAreaStart,
                            aAreaEnd, aExplicitGridEnd, eLineRangeSideEnd,
                            aStyle);
     int32_t span = aStart.mInteger == 0 ? 1 : aStart.mInteger;
@@ -1380,7 +1593,7 @@ nsGridContainerFrame::ResolveLineRangeHelper(
       int32_t start = std::max(end - span, nsStyleGridLine::kMinLine);
       return LinePair(start, end);
     }
-    auto start = ResolveLine(aStart, -span, end, aLineNameList, aAreaStart,
+    auto start = ResolveLine(aStart, -span, end, aNameMap, aAreaStart,
                              aAreaEnd, aExplicitGridEnd, eLineRangeSideStart,
                              aStyle);
     return LinePair(start, end);
@@ -1404,7 +1617,7 @@ nsGridContainerFrame::ResolveLineRangeHelper(
     }
   } else {
     uint32_t from = aStart.mInteger < 0 ? aExplicitGridEnd + 1: 0;
-    start = ResolveLine(aStart, aStart.mInteger, from, aLineNameList,
+    start = ResolveLine(aStart, aStart.mInteger, from, aNameMap,
                         aAreaStart, aAreaEnd, aExplicitGridEnd,
                         eLineRangeSideStart, aStyle);
     if (aEnd.IsAuto()) {
@@ -1434,7 +1647,7 @@ nsGridContainerFrame::ResolveLineRangeHelper(
   } else {
     from = aEnd.mInteger < 0 ? aExplicitGridEnd + 1: 0;
   }
-  auto end = ResolveLine(aEnd, nth, from, aLineNameList, aAreaStart,
+  auto end = ResolveLine(aEnd, nth, from, aNameMap, aAreaStart,
                          aAreaEnd, aExplicitGridEnd, eLineRangeSideEnd, aStyle);
   if (start == int32_t(kAutoLine)) {
     // auto / definite line
@@ -1447,13 +1660,13 @@ nsGridContainerFrame::LineRange
 nsGridContainerFrame::ResolveLineRange(
   const nsStyleGridLine& aStart,
   const nsStyleGridLine& aEnd,
-  const nsTArray>& aLineNameList,
+  const LineNameMap& aNameMap,
   uint32_t GridNamedArea::* aAreaStart,
   uint32_t GridNamedArea::* aAreaEnd,
   uint32_t aExplicitGridEnd,
   const nsStylePosition* aStyle)
 {
-  LinePair r = ResolveLineRangeHelper(aStart, aEnd, aLineNameList, aAreaStart,
+  LinePair r = ResolveLineRangeHelper(aStart, aEnd, aNameMap, aAreaStart,
                                       aAreaEnd, aExplicitGridEnd, aStyle);
   MOZ_ASSERT(r.second != int32_t(kAutoLine));
 
@@ -1478,16 +1691,18 @@ nsGridContainerFrame::ResolveLineRange(
 
 nsGridContainerFrame::GridArea
 nsGridContainerFrame::PlaceDefinite(nsIFrame* aChild,
+                                    const LineNameMap& aColLineNameMap,
+                                    const LineNameMap& aRowLineNameMap,
                                     const nsStylePosition* aStyle)
 {
   const nsStylePosition* itemStyle = aChild->StylePosition();
   return GridArea(
     ResolveLineRange(itemStyle->mGridColumnStart, itemStyle->mGridColumnEnd,
-                     aStyle->mGridTemplateColumns.mLineNameLists,
+                     aColLineNameMap,
                      &GridNamedArea::mColumnStart, &GridNamedArea::mColumnEnd,
                      mExplicitGridColEnd, aStyle),
     ResolveLineRange(itemStyle->mGridRowStart, itemStyle->mGridRowEnd,
-                     aStyle->mGridTemplateRows.mLineNameLists,
+                     aRowLineNameMap,
                      &GridNamedArea::mRowStart, &GridNamedArea::mRowEnd,
                      mExplicitGridRowEnd, aStyle));
 }
@@ -1496,7 +1711,7 @@ nsGridContainerFrame::LineRange
 nsGridContainerFrame::ResolveAbsPosLineRange(
   const nsStyleGridLine& aStart,
   const nsStyleGridLine& aEnd,
-  const nsTArray>& aLineNameList,
+  const LineNameMap& aNameMap,
   uint32_t GridNamedArea::* aAreaStart,
   uint32_t GridNamedArea::* aAreaEnd,
   uint32_t aExplicitGridEnd,
@@ -1510,7 +1725,7 @@ nsGridContainerFrame::ResolveAbsPosLineRange(
     }
     uint32_t from = aEnd.mInteger < 0 ? aExplicitGridEnd + 1: 0;
     int32_t end =
-      ResolveLine(aEnd, aEnd.mInteger, from, aLineNameList, aAreaStart,
+      ResolveLine(aEnd, aEnd.mInteger, from, aNameMap, aAreaStart,
                   aAreaEnd, aExplicitGridEnd, eLineRangeSideEnd, aStyle);
     if (aEnd.mHasSpan) {
       ++end;
@@ -1523,7 +1738,7 @@ nsGridContainerFrame::ResolveAbsPosLineRange(
   if (aEnd.IsAuto()) {
     uint32_t from = aStart.mInteger < 0 ? aExplicitGridEnd + 1: 0;
     int32_t start =
-      ResolveLine(aStart, aStart.mInteger, from, aLineNameList, aAreaStart,
+      ResolveLine(aStart, aStart.mInteger, from, aNameMap, aAreaStart,
                   aAreaEnd, aExplicitGridEnd, eLineRangeSideStart, aStyle);
     if (aStart.mHasSpan) {
       start = std::max(aGridEnd - start, aGridStart);
@@ -1532,7 +1747,7 @@ nsGridContainerFrame::ResolveAbsPosLineRange(
     return LineRange(start, kAutoLine);
   }
 
-  LineRange r = ResolveLineRange(aStart, aEnd, aLineNameList, aAreaStart,
+  LineRange r = ResolveLineRange(aStart, aEnd, aNameMap, aAreaStart,
                                  aAreaEnd, aExplicitGridEnd, aStyle);
   if (r.IsAuto()) {
     MOZ_ASSERT(aStart.mHasSpan && aEnd.mHasSpan, "span / span is the only case "
@@ -1584,6 +1799,8 @@ nsGridContainerFrame::FindAutoCol(uint32_t aStartCol, uint32_t aLockedRow,
 
 nsGridContainerFrame::GridArea
 nsGridContainerFrame::PlaceAbsPos(nsIFrame* aChild,
+                                  const LineNameMap& aColLineNameMap,
+                                  const LineNameMap& aRowLineNameMap,
                                   const nsStylePosition* aStyle)
 {
   const nsStylePosition* itemStyle = aChild->StylePosition();
@@ -1592,14 +1809,14 @@ nsGridContainerFrame::PlaceAbsPos(nsIFrame* aChild,
   return GridArea(
     ResolveAbsPosLineRange(itemStyle->mGridColumnStart,
                            itemStyle->mGridColumnEnd,
-                           aStyle->mGridTemplateColumns.mLineNameLists,
+                           aColLineNameMap,
                            &GridNamedArea::mColumnStart,
                            &GridNamedArea::mColumnEnd,
                            mExplicitGridColEnd, gridColStart, mGridColEnd,
                            aStyle),
     ResolveAbsPosLineRange(itemStyle->mGridRowStart,
                            itemStyle->mGridRowEnd,
-                           aStyle->mGridTemplateRows.mLineNameLists,
+                           aRowLineNameMap,
                            &GridNamedArea::mRowStart,
                            &GridNamedArea::mRowEnd,
                            mExplicitGridRowEnd, gridRowStart, mGridRowEnd,
@@ -1704,31 +1921,39 @@ nsGridContainerFrame::PlaceAutoAutoInColOrder(uint32_t aStartCol,
 }
 
 void
-nsGridContainerFrame::InitializeGridBounds(const nsStylePosition* aStyle)
-{
-  // http://dev.w3.org/csswg/css-grid/#grid-definition
-  // Note that this is for a grid with a 1,1 origin.  We'll change that
-  // to a 0,0 based grid after placing definite lines.
-  uint32_t colEnd = aStyle->mGridTemplateColumns.mLineNameLists.Length();
-  uint32_t rowEnd = aStyle->mGridTemplateRows.mLineNameLists.Length();
-  auto areas = aStyle->mGridTemplateAreas.get();
-  mExplicitGridColEnd = std::max(colEnd, areas ? areas->mNColumns + 1 : 1);
-  mExplicitGridRowEnd = std::max(rowEnd, areas ? areas->NRows() + 1 : 1);
-  mExplicitGridColEnd =
-    std::min(mExplicitGridColEnd, uint32_t(nsStyleGridLine::kMaxLine));
-  mExplicitGridRowEnd =
-    std::min(mExplicitGridRowEnd, uint32_t(nsStyleGridLine::kMaxLine));
-  mGridColEnd = mExplicitGridColEnd;
-  mGridRowEnd = mExplicitGridRowEnd;
-}
-
-void
-nsGridContainerFrame::PlaceGridItems(GridReflowState& aState)
+nsGridContainerFrame::PlaceGridItems(GridReflowState& aState,
+                                     const LogicalSize& aComputedMinSize,
+                                     const LogicalSize& aComputedSize,
+                                     const LogicalSize& aComputedMaxSize)
 {
   const nsStylePosition* const gridStyle = aState.mGridStyle;
-
   mCellMap.ClearOccupied();
-  InitializeGridBounds(gridStyle);
+
+  // http://dev.w3.org/csswg/css-grid/#grid-definition
+  // Initialize the end lines of the Explicit Grid (mExplicitGridCol[Row]End).
+  // This is determined by the larger of the number of rows/columns defined
+  // by 'grid-template-areas' and the 'grid-template-rows'/'-columns', plus one.
+  // Also initialize the Implicit Grid (mGridCol[Row]End) to the same values.
+  // Note that this is for a grid with a 1,1 origin.  We'll change that
+  // to a 0,0 based grid after placing definite lines.
+  auto areas = gridStyle->mGridTemplateAreas.get();
+  uint32_t numRepeatCols = aState.mColFunctions.InitRepeatTracks(
+                             gridStyle->mGridColumnGap,
+                             aComputedMinSize.ISize(aState.mWM),
+                             aComputedSize.ISize(aState.mWM),
+                             aComputedMaxSize.ISize(aState.mWM));
+  mGridColEnd = mExplicitGridColEnd =
+    aState.mColFunctions.ComputeExplicitGridEnd(areas ? areas->mNColumns + 1 : 1);
+  LineNameMap colLineNameMap(gridStyle->mGridTemplateColumns, numRepeatCols);
+
+  uint32_t numRepeatRows = aState.mRowFunctions.InitRepeatTracks(
+                             gridStyle->mGridRowGap,
+                             aComputedMinSize.BSize(aState.mWM),
+                             aComputedSize.BSize(aState.mWM),
+                             aComputedMaxSize.BSize(aState.mWM));
+  mGridRowEnd = mExplicitGridRowEnd =
+    aState.mRowFunctions.ComputeExplicitGridEnd(areas ? areas->NRows() + 1 : 1);
+  LineNameMap rowLineNameMap(gridStyle->mGridTemplateRows, numRepeatRows);
 
   // http://dev.w3.org/csswg/css-grid/#line-placement
   // Resolve definite positions per spec chap 9.2.
@@ -1738,7 +1963,10 @@ nsGridContainerFrame::PlaceGridItems(GridReflowState& aState)
   for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
     nsIFrame* child = *aState.mIter;
     GridItemInfo* info =
-      mGridItems.AppendElement(GridItemInfo(PlaceDefinite(child, gridStyle)));
+      mGridItems.AppendElement(GridItemInfo(PlaceDefinite(child,
+                                                          colLineNameMap,
+                                                          rowLineNameMap,
+                                                          gridStyle)));
 #ifdef DEBUG
     MOZ_ASSERT(aState.mIter.GridItemIndex() == mGridItems.Length() - 1,
                "GridItemIndex() is broken");
@@ -1891,7 +2119,10 @@ nsGridContainerFrame::PlaceGridItems(GridReflowState& aState)
     for (nsFrameList::Enumerator e(children); !e.AtEnd(); e.Next(), ++i) {
       nsIFrame* child = e.get();
       GridItemInfo* info =
-        mAbsPosItems.AppendElement(GridItemInfo(PlaceAbsPos(child, gridStyle)));
+        mAbsPosItems.AppendElement(GridItemInfo(PlaceAbsPos(child,
+                                                            colLineNameMap,
+                                                            rowLineNameMap,
+                                                            gridStyle)));
 #ifdef DEBUG
       info->mFrame = child;
 #endif
@@ -1910,6 +2141,58 @@ nsGridContainerFrame::PlaceGridItems(GridReflowState& aState)
       }
     }
   }
+
+  // Count empty 'auto-fit' tracks at the end of the repeat() range.
+  uint32_t numEmptyCols = 0;
+  if (aState.mColFunctions.mHasRepeatAuto &&
+      !gridStyle->mGridTemplateColumns.mIsAutoFill &&
+      aState.mColFunctions.NumRepeatTracks() > 0) {
+    for (int32_t start = aState.mColFunctions.mRepeatAutoStart,
+                   col = aState.mColFunctions.mRepeatAutoEnd - 1;
+         col >= start && mCellMap.IsEmptyCol(col);
+         --col) {
+      ++numEmptyCols;
+    }
+  }
+  uint32_t numEmptyRows = 0;
+  if (aState.mRowFunctions.mHasRepeatAuto &&
+      !gridStyle->mGridTemplateRows.mIsAutoFill &&
+      aState.mRowFunctions.NumRepeatTracks() > 0) {
+    for (int32_t start = aState.mRowFunctions.mRepeatAutoStart,
+                   row = aState.mRowFunctions.mRepeatAutoEnd - 1;
+         row >= start && mCellMap.IsEmptyRow(row);
+         --row) {
+      ++numEmptyRows;
+    }
+  }
+  // Remove the empty 'auto-fit' tracks we found above, if any.
+  if (numEmptyCols || numEmptyRows) {
+    // Adjust the line numbers in the grid areas.
+    const uint32_t firstRemovedCol =
+      aState.mColFunctions.mRepeatAutoEnd - numEmptyCols;
+    const uint32_t firstRemovedRow =
+      aState.mRowFunctions.mRepeatAutoEnd - numEmptyRows;
+    for (auto& item : mGridItems) {
+      GridArea& area = item.mArea;
+      area.mCols.AdjustForRemovedTracks(firstRemovedCol, numEmptyCols);
+      area.mRows.AdjustForRemovedTracks(firstRemovedRow, numEmptyRows);
+    }
+    for (auto& item : mAbsPosItems) {
+      GridArea& area = item.mArea;
+      area.mCols.AdjustAbsPosForRemovedTracks(firstRemovedCol, numEmptyCols);
+      area.mRows.AdjustAbsPosForRemovedTracks(firstRemovedRow, numEmptyRows);
+    }
+    // Adjust the grid size.
+    mGridColEnd -= numEmptyCols;
+    mExplicitGridColEnd -= numEmptyCols;
+    mGridRowEnd -= numEmptyRows;
+    mExplicitGridRowEnd -= numEmptyRows;
+    // Adjust the track mapping to unmap the removed tracks.
+    auto finalColRepeatCount = aState.mColFunctions.NumRepeatTracks() - numEmptyCols;
+    aState.mColFunctions.SetNumRepeatTracks(finalColRepeatCount);
+    auto finalRowRepeatCount = aState.mRowFunctions.NumRepeatTracks() - numEmptyRows;
+    aState.mRowFunctions.SetNumRepeatTracks(finalRowRepeatCount);
+  }
 }
 
 void
@@ -1963,42 +2246,19 @@ nsGridContainerFrame::Tracks::Initialize(
   uint32_t                    aNumTracks,
   nscoord                     aContentBoxSize)
 {
+  MOZ_ASSERT(aNumTracks >= aFunctions.mExplicitGridOffset +
+                             aFunctions.NumExplicitTracks());
   mSizes.SetLength(aNumTracks);
   PodZero(mSizes.Elements(), mSizes.Length());
   nscoord percentageBasis = aContentBoxSize;
   if (percentageBasis == NS_UNCONSTRAINEDSIZE) {
     percentageBasis = 0;
   }
-
-  const uint32_t explicitGridOffset = aFunctions.mExplicitGridOffset;
-  MOZ_ASSERT(mSizes.Length() >=
-               explicitGridOffset + aFunctions.mMinSizingFunctions.Length());
-  MOZ_ASSERT(aFunctions.mMinSizingFunctions.Length() ==
-               aFunctions.mMaxSizingFunctions.Length());
-  // First we initialize the implicit tracks before the explicit grid starts.
-  uint32_t i = 0;
-  uint32_t sentinel = std::min(explicitGridOffset, mSizes.Length());
-  for (; i < sentinel; ++i) {
+  for (uint32_t i = 0, len = mSizes.Length(); i < len; ++i) {
     mSizes[i].Initialize(percentageBasis,
-                         aFunctions.mAutoMinSizing,
-                         aFunctions.mAutoMaxSizing);
+                         aFunctions.MinSizingFor(i),
+                         aFunctions.MaxSizingFor(i));
   }
-  // Now initialize the explicit grid tracks.
-  sentinel = std::min(i + aFunctions.mMinSizingFunctions.Length(),
-                                mSizes.Length());
-  for (uint32_t j = 0; i < sentinel; ++i, ++j) {
-    mSizes[i].Initialize(percentageBasis,
-                         aFunctions.mMinSizingFunctions[j],
-                         aFunctions.mMaxSizingFunctions[j]);
-  }
-  // Finally, initialize the implicit tracks that comes after the explicit grid.
-  sentinel = mSizes.Length();
-  for (; i < sentinel; ++i) {
-    mSizes[i].Initialize(percentageBasis,
-                         aFunctions.mAutoMinSizing,
-                         aFunctions.mAutoMaxSizing);
-  }
-
   mGridGap = aGridGap;
   MOZ_ASSERT(mGridGap >= nscoord(0), "negative grid gap");
 }
@@ -3023,14 +3283,32 @@ nsGridContainerFrame::Reflow(nsPresContext*           aPresContext,
   InitImplicitNamedAreas(stylePos);
   GridReflowState gridReflowState(this, aReflowState);
   mIsNormalFlowInCSSOrder = gridReflowState.mIter.ItemsAreAlreadyInOrder();
-  PlaceGridItems(gridReflowState);
-
   const nscoord computedBSize = aReflowState.ComputedBSize();
   const nscoord computedISize = aReflowState.ComputedISize();
   const WritingMode& wm = gridReflowState.mWM;
+  LogicalSize computedSize(wm, computedISize, computedBSize);
+
+  // ComputedMinSize is zero rather than NS_UNCONSTRAINEDSIZE when indefinite
+  // (unfortunately) so we have to check the style data and parent reflow state
+  // to determine if it's indefinite.
+  LogicalSize computedMinSize(aReflowState.ComputedMinSize());
+  const nsHTMLReflowState* cbState = aReflowState.mCBReflowState;
+  if (!stylePos->MinISize(wm).IsCoordPercentCalcUnit() ||
+      (stylePos->MinISize(wm).HasPercent() && cbState &&
+       cbState->ComputedSize(wm).ISize(wm) == NS_UNCONSTRAINEDSIZE)) {
+    computedMinSize.ISize(wm) = NS_UNCONSTRAINEDSIZE;
+  }
+  if (!stylePos->MinBSize(wm).IsCoordPercentCalcUnit() ||
+      (stylePos->MinBSize(wm).HasPercent() && cbState &&
+       cbState->ComputedSize(wm).BSize(wm) == NS_UNCONSTRAINEDSIZE)) {
+    computedMinSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
+  }
+
+  PlaceGridItems(gridReflowState, computedMinSize, computedSize,
+                 aReflowState.ComputedMaxSize());
+
   gridReflowState.mIter.Reset();
-  CalculateTrackSizes(gridReflowState,
-                      LogicalSize(wm, computedISize, computedBSize),
+  CalculateTrackSizes(gridReflowState, computedSize,
                       nsLayoutUtils::PREF_ISIZE);
 
   // FIXME bug 1229180: Instead of doing this on every reflow, we should only
@@ -3089,7 +3367,8 @@ nsGridContainerFrame::IntrinsicISize(nsRenderingContext* aRenderingContext,
   // http://dev.w3.org/csswg/css-grid/#intrinsic-sizes
   GridReflowState state(this, *aRenderingContext);
   InitImplicitNamedAreas(state.mGridStyle); // XXX optimize
-  PlaceGridItems(state);                    // XXX optimize
+  LogicalSize indefinite(state.mWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+  PlaceGridItems(state, indefinite, indefinite, indefinite);  // XXX optimize
   if (mGridColEnd == 0) {
     return 0;
   }
diff --git a/layout/generic/nsGridContainerFrame.h b/layout/generic/nsGridContainerFrame.h
index d886dd8e50..6038941c39 100644
--- a/layout/generic/nsGridContainerFrame.h
+++ b/layout/generic/nsGridContainerFrame.h
@@ -115,6 +115,7 @@ protected:
   struct TrackSizingFunctions;
   struct Tracks;
   struct GridReflowState;
+  class LineNameMap;
   friend nsContainerFrame* NS_NewGridContainerFrame(nsIPresShell* aPresShell,
                                                     nsStyleContext* aContext);
   explicit nsGridContainerFrame(nsStyleContext* aContext)
@@ -187,6 +188,55 @@ protected:
         mEnd = translatedMax;
       }
     }
+    /**
+     * Translate the lines to account for (empty) removed tracks.  This method
+     * is only for grid items and should only be called after placement.
+     */
+    void AdjustForRemovedTracks(uint32_t aFirstRemovedTrack,
+                                uint32_t aNumRemovedTracks)
+    {
+      MOZ_ASSERT(mStart != kAutoLine, "invalid resolved line for a grid item");
+      MOZ_ASSERT(mEnd != kAutoLine, "invalid resolved line for a grid item");
+      if (mStart >= aFirstRemovedTrack) {
+        MOZ_ASSERT(mStart >= aFirstRemovedTrack + aNumRemovedTracks,
+                   "can't start in a removed range of tracks - those tracks "
+                   "are supposed to be empty");
+        mStart -= aNumRemovedTracks;
+        mEnd -= aNumRemovedTracks;
+      } else {
+        MOZ_ASSERT(mEnd <= aFirstRemovedTrack, "can't span into a removed "
+                   "range of tracks - those tracks are supposed to be empty");
+      }
+    }
+    /**
+     * Translate the lines to account for (empty) removed tracks.  This method
+     * is only for abs.pos. children and should only be called after placement.
+     * Same as for in-flow items, but we don't touch 'auto' lines here and we
+     * also need to adjust areas that span into the removed range.
+     */
+    void AdjustAbsPosForRemovedTracks(uint32_t aFirstRemovedTrack,
+                                      uint32_t aNumRemovedTracks)
+    {
+      if (mStart != nsGridContainerFrame::kAutoLine &&
+          mStart > aFirstRemovedTrack) {
+        if (mStart < aFirstRemovedTrack + aNumRemovedTracks) {
+          mStart = aFirstRemovedTrack;
+        } else {
+          mStart -= aNumRemovedTracks;
+        }
+      }
+      if (mEnd != nsGridContainerFrame::kAutoLine &&
+          mEnd > aFirstRemovedTrack) {
+        if (mEnd < aFirstRemovedTrack + aNumRemovedTracks) {
+          mEnd = aFirstRemovedTrack;
+        } else {
+          mEnd -= aNumRemovedTracks;
+        }
+      }
+      if (mStart == mEnd) {
+        mEnd = nsGridContainerFrame::kAutoLine;
+      }
+    }
     /**
      * Return the contribution of this line range for step 2 in
      * http://dev.w3.org/csswg/css-grid/#auto-placement-algo
@@ -288,6 +338,27 @@ protected:
     };
     void Fill(const GridArea& aGridArea);
     void ClearOccupied();
+    uint32_t IsEmptyCol(uint32_t aCol) const
+    {
+      for (auto& row : mCells) {
+        if (aCol < row.Length() && row[aCol].mIsOccupied) {
+          return false;
+        }
+      }
+      return true;
+    }
+    uint32_t IsEmptyRow(uint32_t aRow) const
+    {
+      if (aRow >= mCells.Length()) {
+        return true;
+      }
+      for (const Cell& cell : mCells[aRow]) {
+        if (cell.mIsOccupied) {
+          return false;
+        }
+      }
+      return true;
+    }
 #if DEBUG
     void Dump() const;
 #endif
@@ -323,7 +394,7 @@ protected:
    *             a specified line name, it's permitted to pass in zero which
    *             will be treated as one.
    * @param aFromIndex the zero-based index to start counting from
-   * @param aLineNameList the explicit named lines
+   * @param aNameMap for looking up explicit named lines
    * @param aAreaStart a pointer to GridNamedArea::mColumnStart/mRowStart
    * @param aAreaEnd a pointer to GridNamedArea::mColumnEnd/mRowEnd
    * @param aExplicitGridEnd the last line in the explicit grid
@@ -334,7 +405,7 @@ protected:
   int32_t ResolveLine(const nsStyleGridLine& aLine,
                       int32_t aNth,
                       uint32_t aFromIndex,
-                      const nsTArray>& aLineNameList,
+                      const LineNameMap& aNameMap,
                       uint32_t GridNamedArea::* aAreaStart,
                       uint32_t GridNamedArea::* aAreaEnd,
                       uint32_t aExplicitGridEnd,
@@ -349,7 +420,7 @@ protected:
    * @param aStyle the StylePosition() for the grid container
    * @param aStart style data for the start line
    * @param aEnd style data for the end line
-   * @param aLineNameList the explicit named lines
+   * @param aNameMap for looking up explicit named lines
    * @param aAreaStart a pointer to GridNamedArea::mColumnStart/mRowStart
    * @param aAreaEnd a pointer to GridNamedArea::mColumnEnd/mRowEnd
    * @param aExplicitGridEnd the last line in the explicit grid
@@ -357,7 +428,7 @@ protected:
    */
   LineRange ResolveLineRange(const nsStyleGridLine& aStart,
                              const nsStyleGridLine& aEnd,
-                             const nsTArray>& aLineNameList,
+                             const LineNameMap& aNameMap,
                              uint32_t GridNamedArea::* aAreaStart,
                              uint32_t GridNamedArea::* aAreaEnd,
                              uint32_t aExplicitGridEnd,
@@ -372,7 +443,7 @@ protected:
   LineRange
   ResolveAbsPosLineRange(const nsStyleGridLine& aStart,
                          const nsStyleGridLine& aEnd,
-                         const nsTArray>& aLineNameList,
+                         const LineNameMap& aNameMap,
                          uint32_t GridNamedArea::* aAreaStart,
                          uint32_t GridNamedArea::* aAreaEnd,
                          uint32_t aExplicitGridEnd,
@@ -385,9 +456,14 @@ protected:
    * with placement errors resolved.  One or both positions may still
    * be 'auto'.
    * @param aChild the grid item
+   * @param aColLineNameMap for looking up explicit named column lines
+   * @param aRowLineNameMap for looking up explicit named row lines
    * @param aStyle the StylePosition() for the grid container
    */
-  GridArea PlaceDefinite(nsIFrame* aChild, const nsStylePosition* aStyle);
+  GridArea PlaceDefinite(nsIFrame* aChild,
+                         const LineNameMap& aColLineNameMap,
+                         const LineNameMap& aRowLineNameMap,
+                         const nsStylePosition* aStyle);
 
   /**
    * Place aArea in the first column (in row aArea->mRows.mStart) starting at
@@ -448,25 +524,34 @@ protected:
    * a definite line (1-based) with placement errors resolved.  One or both
    * positions may still be 'auto'.
    * @param aChild the abs.pos. grid item to place
+   * @param aColLineNameMap for looking up explicit named column lines
+   * @param aRowLineNameMap for looking up explicit named row lines
    * @param aStyle the StylePosition() for the grid container
    */
-  GridArea PlaceAbsPos(nsIFrame* aChild, const nsStylePosition* aStyle);
+  GridArea PlaceAbsPos(nsIFrame* aChild,
+                       const LineNameMap& aColLineNameMap,
+                       const LineNameMap& aRowLineNameMap,
+                       const nsStylePosition* aStyle);
 
   /**
    * Place all child frames into the grid and expand the (implicit) grid as
    * needed.  The allocated GridAreas are stored in the GridAreaProperty
    * frame property on the child frame.
+   * @param aComputedMinSize the container's min-size - used to determine
+   *   the number of repeat(auto-fill/fit) tracks.
+   * @param aComputedSize the container's size - used to determine
+   *   the number of repeat(auto-fill/fit) tracks.
+   * @param aComputedMaxSize the container's max-size - used to determine
+   *   the number of repeat(auto-fill/fit) tracks.
    */
-  void PlaceGridItems(GridReflowState& aState);
-
-  /**
-   * Initialize the end lines of the Explicit Grid (mExplicitGridCol[Row]End).
-   * This is determined by the larger of the number of rows/columns defined
-   * by 'grid-template-areas' and the 'grid-template-rows'/'-columns', plus one.
-   * Also initialize the Implicit Grid (mGridCol[Row]End) to the same values.
-   * @param aStyle the StylePosition() for the grid container
-   */
-  void InitializeGridBounds(const nsStylePosition* aStyle);
+  void PlaceGridItems(GridReflowState&   aState,
+                      const LogicalSize& aComputedMinSize,
+                      const LogicalSize& aComputedSize,
+                      const LogicalSize& aComputedMaxSize);
+  // Helper for the above.
+  void PlaceGridItems(GridReflowState&   aState,
+                      const LineNameMap& aColLineNameMap,
+                      const LineNameMap& aRowLineNameMap);
 
   /**
    * Inflate the implicit grid to include aArea.
@@ -495,7 +580,7 @@ protected:
   typedef std::pair LinePair;
   LinePair ResolveLineRangeHelper(const nsStyleGridLine& aStart,
                                   const nsStyleGridLine& aEnd,
-                                  const nsTArray>& aLineNameList,
+                                  const LineNameMap& aNameMap,
                                   uint32_t GridNamedArea::* aAreaStart,
                                   uint32_t GridNamedArea::* aAreaEnd,
                                   uint32_t aExplicitGridEnd,
diff --git a/layout/reftests/css-grid/grid-clamping-001-ref.html b/layout/reftests/css-grid/grid-clamping-001-ref.html
new file mode 100644
index 0000000000..2e2b40e051
--- /dev/null
+++ b/layout/reftests/css-grid/grid-clamping-001-ref.html
@@ -0,0 +1,68 @@
+
+
+    
+    Reference: bug 1229999
+    
+
+
+
+    
+ + + + + + +
+ +
+ + + + + +
+ +
+ + + + +
+ +
+ + + + +
+ +
+ + + + +
+ + + + diff --git a/layout/reftests/css-grid/grid-clamping-001.html b/layout/reftests/css-grid/grid-clamping-001.html new file mode 100644 index 0000000000..7d96b2e852 --- /dev/null +++ b/layout/reftests/css-grid/grid-clamping-001.html @@ -0,0 +1,78 @@ + + + + Testcase for bug 1229999 + + + + +
+ + + + + +
+ +
+ + + + + +
+ +
+ + + + + +
+ +
+ + + + + +
+ +
+ + + + + +
+ +
+ + + + +
+ + + + diff --git a/layout/reftests/css-grid/grid-clamping-002-ref.html b/layout/reftests/css-grid/grid-clamping-002-ref.html new file mode 100644 index 0000000000..d605ff9f1b --- /dev/null +++ b/layout/reftests/css-grid/grid-clamping-002-ref.html @@ -0,0 +1,68 @@ + + + + Reference: Clamp number of auto-fill tracks + + + + +
+ + + + + + +
+ +
+ + + + + +
+ +
+ + + + +
+ +
+ + + + +
+ +
+ + + + +
+ + + + diff --git a/layout/reftests/css-grid/grid-clamping-002.html b/layout/reftests/css-grid/grid-clamping-002.html new file mode 100644 index 0000000000..cb3db0d276 --- /dev/null +++ b/layout/reftests/css-grid/grid-clamping-002.html @@ -0,0 +1,75 @@ + + + + Testcase: Clamp number of auto-fill tracks + + + + +
+ + + + +
+ +
+ + + + +
+ +
+ + + + +
+ +
+ + + + +
+ +
+ + + + +
+ +
+ + + + +
+ + + + diff --git a/layout/reftests/css-grid/grid-container-min-max-width-height-001-ref.html b/layout/reftests/css-grid/grid-container-min-max-width-height-001-ref.html new file mode 100644 index 0000000000..0daecf81af --- /dev/null +++ b/layout/reftests/css-grid/grid-container-min-max-width-height-001-ref.html @@ -0,0 +1,56 @@ + + + + + Reference: definite min/max-width/height values on the grid container + + + + + +
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+ + + diff --git a/layout/reftests/css-grid/grid-container-min-max-width-height-001.html b/layout/reftests/css-grid/grid-container-min-max-width-height-001.html new file mode 100644 index 0000000000..b5029429a8 --- /dev/null +++ b/layout/reftests/css-grid/grid-container-min-max-width-height-001.html @@ -0,0 +1,55 @@ + + + + + CSS Grid Test: definite min/max-width/height values on the grid container + + + + + + + +
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+ + + diff --git a/layout/reftests/css-grid/grid-placement-auto-implicit-001-ref.html b/layout/reftests/css-grid/grid-placement-auto-implicit-001-ref.html index 6d97fbb370..9395b35cdf 100644 --- a/layout/reftests/css-grid/grid-placement-auto-implicit-001-ref.html +++ b/layout/reftests/css-grid/grid-placement-auto-implicit-001-ref.html @@ -80,17 +80,17 @@ span {
-b
+b
-b
+b
-b +b
@@ -99,7 +99,7 @@ span {
-b +b
diff --git a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-001-ref.html b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-001-ref.html new file mode 100644 index 0000000000..f2123e2bd4 --- /dev/null +++ b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-001-ref.html @@ -0,0 +1,193 @@ + + + + + Reference: repeat(auto-fill/auto-fit) + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+ + + + diff --git a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-001.html b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-001.html new file mode 100644 index 0000000000..35f338c78d --- /dev/null +++ b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-001.html @@ -0,0 +1,179 @@ + + + + + CSS Grid Test: repeat(auto-fill/auto-fit) + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+ + + + diff --git a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-002-ref.html b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-002-ref.html new file mode 100644 index 0000000000..f2cad12312 --- /dev/null +++ b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-002-ref.html @@ -0,0 +1,190 @@ + + + + + Reference: repeat(auto-fill/auto-fit) + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+ + + + diff --git a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-002.html b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-002.html new file mode 100644 index 0000000000..114b7a120d --- /dev/null +++ b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-002.html @@ -0,0 +1,182 @@ + + + + + CSS Grid Test: repeat(auto-fill/auto-fit) + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+ + + + diff --git a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-003-ref.html b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-003-ref.html new file mode 100644 index 0000000000..9f83cf9b15 --- /dev/null +++ b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-003-ref.html @@ -0,0 +1,189 @@ + + + + + Reference: repeat(auto-fill/auto-fit) + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+ + + + diff --git a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-003.html b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-003.html new file mode 100644 index 0000000000..07ba155d92 --- /dev/null +++ b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-003.html @@ -0,0 +1,181 @@ + + + + + CSS Grid Test: repeat(auto-fill/auto-fit) + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+ + + + diff --git a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-004-ref.html b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-004-ref.html new file mode 100644 index 0000000000..d61a9dcaab --- /dev/null +++ b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-004-ref.html @@ -0,0 +1,191 @@ + + + + + Reference: repeat(auto-fill/auto-fit) + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+ + + + diff --git a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-004.html b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-004.html new file mode 100644 index 0000000000..dd9af776aa --- /dev/null +++ b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-004.html @@ -0,0 +1,183 @@ + + + + + CSS Grid Test: repeat(auto-fill/auto-fit) + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+ + + + diff --git a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-005-ref.html b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-005-ref.html new file mode 100644 index 0000000000..7c29879df3 --- /dev/null +++ b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-005-ref.html @@ -0,0 +1,381 @@ + + + + + Reference: repeat(auto-fill/auto-fit) + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ + +
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ + + +
+ + + + diff --git a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-005.html b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-005.html new file mode 100644 index 0000000000..9549734620 --- /dev/null +++ b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-005.html @@ -0,0 +1,377 @@ + + + + + CSS Grid Test: repeat(auto-fill/auto-fit) + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ + +
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ + +
+ + + + diff --git a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-006-ref.html b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-006-ref.html new file mode 100644 index 0000000000..79ac94548e --- /dev/null +++ b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-006-ref.html @@ -0,0 +1,184 @@ + + + + + Reference: repeat(auto-fit) with grid-aligned abs.pos. + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+ + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+ + + diff --git a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-006.html b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-006.html new file mode 100644 index 0000000000..246b292151 --- /dev/null +++ b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-006.html @@ -0,0 +1,213 @@ + + + + + CSS Grid Test: repeat(auto-fit) with grid-aligned abs.pos. + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+ + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+ + + + + diff --git a/layout/reftests/css-grid/reftest.list b/layout/reftests/css-grid/reftest.list index ca368dc8fb..3cad9b04d8 100644 --- a/layout/reftests/css-grid/reftest.list +++ b/layout/reftests/css-grid/reftest.list @@ -87,3 +87,12 @@ fuzzy-if(winWidget,1,36) == grid-auto-min-sizing-definite-001.html grid-auto-min == grid-item-margin-right-auto-002.html grid-item-margin-right-auto-002-ref.html == grid-item-margin-right-auto-003.html grid-item-margin-right-auto-003-ref.html == grid-item-margin-right-auto-004.html grid-item-margin-right-auto-004-ref.html +== grid-container-min-max-width-height-001.html grid-container-min-max-width-height-001-ref.html +== grid-clamping-001.html grid-clamping-001-ref.html +== grid-clamping-002.html grid-clamping-002-ref.html +== grid-repeat-auto-fill-fit-001.html grid-repeat-auto-fill-fit-001-ref.html +== grid-repeat-auto-fill-fit-002.html grid-repeat-auto-fill-fit-002-ref.html +== grid-repeat-auto-fill-fit-003.html grid-repeat-auto-fill-fit-003-ref.html +== grid-repeat-auto-fill-fit-004.html grid-repeat-auto-fill-fit-004-ref.html +== grid-repeat-auto-fill-fit-005.html grid-repeat-auto-fill-fit-005-ref.html +== grid-repeat-auto-fill-fit-006.html grid-repeat-auto-fill-fit-006-ref.html diff --git a/layout/style/nsCSSKeywordList.h b/layout/style/nsCSSKeywordList.h index e2e574c616..07051c8cf4 100644 --- a/layout/style/nsCSSKeywordList.h +++ b/layout/style/nsCSSKeywordList.h @@ -143,6 +143,8 @@ CSS_KEY(always, always) CSS_KEY(annotation, annotation) CSS_KEY(appworkspace, appworkspace) CSS_KEY(auto, auto) +CSS_KEY(auto-fill, auto_fill) +CSS_KEY(auto-fit, auto_fit) CSS_KEY(avoid, avoid) CSS_KEY(background, background) CSS_KEY(backwards, backwards) diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index d5d38c7fec..c7f7ec792e 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -10,6 +10,7 @@ #include "mozilla/DebugOnly.h" #include "mozilla/Move.h" #include "mozilla/MathAlgorithms.h" +#include "mozilla/TypedEnumBits.h" #include // for std::stable_sort @@ -97,6 +98,12 @@ enum class CSSParseResult : int32_t { Error }; +enum class GridTrackSizeFlags { + eDefaultTrackSize = 0x0, + eFixedTrackSize = 0x1, // parse a instead of +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(GridTrackSizeFlags) + namespace { // Rule processing function @@ -927,10 +934,19 @@ protected: CSSParseResult ParseGridLineNames(nsCSSValue& aValue); bool ParseGridLineNameListRepeat(nsCSSValueList** aTailPtr); bool ParseOptionalLineNameListAfterSubgrid(nsCSSValue& aValue); - CSSParseResult ParseGridTrackBreadth(nsCSSValue& aValue); - CSSParseResult ParseGridTrackSize(nsCSSValue& aValue); + + // eFixedTrackSize in aFlags makes it parse a . + CSSParseResult ParseGridTrackBreadth(nsCSSValue& aValue, + GridTrackSizeFlags aFlags = GridTrackSizeFlags::eDefaultTrackSize); + // eFixedTrackSize in aFlags makes it parse a . + CSSParseResult ParseGridTrackSize(nsCSSValue& aValue, + GridTrackSizeFlags aFlags = GridTrackSizeFlags::eDefaultTrackSize); + bool ParseGridAutoColumnsRows(nsCSSProperty aPropID); bool ParseGridTrackListRepeat(nsCSSValueList** aTailPtr); + bool ParseGridTrackRepeatIntro(bool aForSubgrid, + int32_t* aRepetitions, + Maybe* aRepeatAutoEnum); // Assuming a [ ? ] has already been parsed, // parse the rest of a . @@ -8386,24 +8402,38 @@ CSSParserImpl::ParseGridLineNames(nsCSSValue& aValue) } // Assuming the 'repeat(' function token has already been consumed, -// parse the rest of repeat(, +) +// parse the rest of repeat( | auto-fill, +) // Append to the linked list whose end is given by |aTailPtr|, -// and updated |aTailPtr| to point to the new end of the list. +// and update |aTailPtr| to point to the new end of the list. bool CSSParserImpl::ParseGridLineNameListRepeat(nsCSSValueList** aTailPtr) { - if (!(GetToken(true) && - mToken.mType == eCSSToken_Number && - mToken.mIntegerValid && - mToken.mInteger > 0)) { - SkipUntil(')'); + int32_t repetitions; + Maybe repeatAutoEnum; + if (!ParseGridTrackRepeatIntro(true, &repetitions, &repeatAutoEnum)) { return false; } - int32_t repetitions = std::min(mToken.mInteger, - GRID_TEMPLATE_MAX_REPETITIONS); - if (!ExpectSymbol(',', true)) { - SkipUntil(')'); - return false; + if (repeatAutoEnum.isSome()) { + // Parse exactly one . + nsCSSValue listValue; + nsCSSValueList* list = listValue.SetListValue(); + if (ParseGridLineNames(list->mValue) != CSSParseResult::Ok) { + return false; + } + if (!ExpectSymbol(')', true)) { + return false; + } + // Instead of hooking up this list into the flat name list as usual, + // we create a pair(Int, List) where the first value is the auto-fill + // keyword and the second is the name list to repeat. + nsCSSValue kwd; + kwd.SetIntValue(repeatAutoEnum.value(), eCSSUnit_Enumerated); + *aTailPtr = (*aTailPtr)->mNext = new nsCSSValueList; + (*aTailPtr)->mValue.SetPairValue(kwd, listValue); + // Append an empty list since the caller expects that to represent the names + // that follows the repeat() function. + *aTailPtr = (*aTailPtr)->mNext = new nsCSSValueList; + return true; } // Parse at least one @@ -8412,7 +8442,6 @@ CSSParserImpl::ParseGridLineNameListRepeat(nsCSSValueList** aTailPtr) tail->mNext = new nsCSSValueList; tail = tail->mNext; if (ParseGridLineNames(tail->mValue) != CSSParseResult::Ok) { - SkipUntil(')'); return false; } } while (!ExpectSymbol(')', true)); @@ -8446,16 +8475,27 @@ CSSParserImpl::ParseOptionalLineNameListAfterSubgrid(nsCSSValue& aValue) // This marker distinguishes the value from a . item->mValue.SetIntValue(NS_STYLE_GRID_TEMPLATE_SUBGRID, eCSSUnit_Enumerated); + bool haveRepeatAuto = false; for (;;) { - // First try to parse repeat(, +) + // First try to parse , i.e. + // repeat( | auto-fill, +) if (!GetToken(true)) { return true; } if (mToken.mType == eCSSToken_Function && mToken.mIdent.LowerCaseEqualsLiteral("repeat")) { + nsCSSValueList* startOfRepeat = item; if (!ParseGridLineNameListRepeat(&item)) { + SkipUntil(')'); return false; } + if (startOfRepeat->mNext->mValue.GetUnit() == eCSSUnit_Pair) { + if (haveRepeatAuto) { + REPORT_UNEXPECTED(PEMoreThanOneGridRepeatAutoFillInNameList); + return false; + } + haveRepeatAuto = true; + } } else { UngetToken(); @@ -8475,16 +8515,20 @@ CSSParserImpl::ParseOptionalLineNameListAfterSubgrid(nsCSSValue& aValue) } } -// Parse a . +// Parse a , or when aFlags has eFixedTrackSize. CSSParseResult -CSSParserImpl::ParseGridTrackBreadth(nsCSSValue& aValue) +CSSParserImpl::ParseGridTrackBreadth(nsCSSValue& aValue, + GridTrackSizeFlags aFlags) { - CSSParseResult result = + CSSParseResult result = (aFlags & GridTrackSizeFlags::eFixedTrackSize) ? + ParseNonNegativeVariant(aValue, VARIANT_LPCALC, nullptr) : ParseNonNegativeVariant(aValue, VARIANT_AUTO | VARIANT_LPCALC | VARIANT_KEYWORD, nsCSSProps::kGridTrackBreadthKTable); + if (result == CSSParseResult::Ok || - result == CSSParseResult::Error) { + result == CSSParseResult::Error || + (aFlags & GridTrackSizeFlags::eFixedTrackSize)) { return result; } @@ -8502,12 +8546,13 @@ CSSParserImpl::ParseGridTrackBreadth(nsCSSValue& aValue) return CSSParseResult::Ok; } -// Parse a . +// Parse a , or when aFlags has eFixedTrackSize. CSSParseResult -CSSParserImpl::ParseGridTrackSize(nsCSSValue& aValue) +CSSParserImpl::ParseGridTrackSize(nsCSSValue& aValue, + GridTrackSizeFlags aFlags) { // Attempt to parse a single . - CSSParseResult result = ParseGridTrackBreadth(aValue); + CSSParseResult result = ParseGridTrackBreadth(aValue, aFlags); if (result == CSSParseResult::Ok || result == CSSParseResult::Error) { return result; @@ -8523,9 +8568,9 @@ CSSParserImpl::ParseGridTrackSize(nsCSSValue& aValue) return CSSParseResult::NotFound; } nsCSSValue::Array* func = aValue.InitFunction(eCSSKeyword_minmax, 2); - if (ParseGridTrackBreadth(func->Item(1)) == CSSParseResult::Ok && + if (ParseGridTrackBreadth(func->Item(1), aFlags) == CSSParseResult::Ok && ExpectSymbol(',', true) && - ParseGridTrackBreadth(func->Item(2)) == CSSParseResult::Ok && + ParseGridTrackBreadth(func->Item(2), aFlags) == CSSParseResult::Ok && ExpectSymbol(')', true)) { return CSSParseResult::Ok; } @@ -8564,6 +8609,7 @@ CSSParserImpl::ParseGridTrackListWithFirstLineNames(nsCSSValue& aValue, // case after ParseGridTrackSize(); i.e. we'll greedily parse // repeat()/ until we can't find one. nsCSSValueList* item = firstLineNamesItem; + bool haveRepeatAuto = false; for (;;) { // First try to parse repeat() if (!GetToken(true)) { @@ -8571,9 +8617,18 @@ CSSParserImpl::ParseGridTrackListWithFirstLineNames(nsCSSValue& aValue, } if (mToken.mType == eCSSToken_Function && mToken.mIdent.LowerCaseEqualsLiteral("repeat")) { + nsCSSValueList* startOfRepeat = item; if (!ParseGridTrackListRepeat(&item)) { + SkipUntil(')'); return false; } + if (startOfRepeat->mNext->mValue.GetUnit() == eCSSUnit_Pair) { + if (haveRepeatAuto) { + REPORT_UNEXPECTED(PEMoreThanOneGridRepeatAutoFillFitInTrackList); + return false; + } + haveRepeatAuto = true; + } } else { UngetToken(); @@ -8644,26 +8699,60 @@ ConcatLineNames(nsCSSValue& aFirst, nsCSSValue& aSecond) source->mNext = nullptr; } +// Assuming the 'repeat(' function token has already been consumed, +// parse "repeat( | auto-fill | auto-fit ," +// (or "repeat( | auto-fill ," when aForSubgrid is true) +// and stop after the comma. Return true when parsing succeeds, +// with aRepetitions set to the number of repetitions and aRepeatAutoEnum set +// to an enum value for auto-fill | auto-fit (it's not set at all when +// was parsed). +bool +CSSParserImpl::ParseGridTrackRepeatIntro(bool aForSubgrid, + int32_t* aRepetitions, + Maybe* aRepeatAutoEnum) +{ + if (!GetToken(true)) { + return false; + } + if (mToken.mType == eCSSToken_Ident) { + if (mToken.mIdent.LowerCaseEqualsLiteral("auto-fill")) { + aRepeatAutoEnum->emplace(NS_STYLE_GRID_REPEAT_AUTO_FILL); + } else if (!aForSubgrid && + mToken.mIdent.LowerCaseEqualsLiteral("auto-fit")) { + aRepeatAutoEnum->emplace(NS_STYLE_GRID_REPEAT_AUTO_FIT); + } else { + return false; + } + *aRepetitions = 1; + } else if (mToken.mType == eCSSToken_Number) { + if (!(mToken.mIntegerValid && + mToken.mInteger > 0)) { + return false; + } + *aRepetitions = std::min(mToken.mInteger, GRID_TEMPLATE_MAX_REPETITIONS); + } else { + return false; + } + + if (!ExpectSymbol(',', true)) { + return false; + } + return true; +} + // Assuming the 'repeat(' function token has already been consumed, // parse the rest of -// repeat( , +// repeat( | auto-fill | auto-fit , // [ ? ]+ ? ) // Append to the linked list whose end is given by |aTailPtr|, -// and updated |aTailPtr| to point to the new end of the list. +// and update |aTailPtr| to point to the new end of the list. +// Note: only one is allowed for auto-fill/fit bool CSSParserImpl::ParseGridTrackListRepeat(nsCSSValueList** aTailPtr) { - if (!(GetToken(true) && - mToken.mType == eCSSToken_Number && - mToken.mIntegerValid && - mToken.mInteger > 0)) { - SkipUntil(')'); - return false; - } - int32_t repetitions = std::min(mToken.mInteger, - GRID_TEMPLATE_MAX_REPETITIONS); - if (!ExpectSymbol(',', true)) { - SkipUntil(')'); + int32_t repetitions; + Maybe repeatAutoEnum; + if (!ParseGridTrackRepeatIntro(false, &repetitions, &repeatAutoEnum)) { return false; } @@ -8676,12 +8765,13 @@ CSSParserImpl::ParseGridTrackListRepeat(nsCSSValueList** aTailPtr) nsCSSValue lastLineNames; // Optional if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error) { - SkipUntil(')'); return false; } // Required - if (ParseGridTrackSize(trackSize) != CSSParseResult::Ok) { - SkipUntil(')'); + GridTrackSizeFlags flags = repeatAutoEnum.isSome() + ? GridTrackSizeFlags::eFixedTrackSize + : GridTrackSizeFlags::eDefaultTrackSize; + if (ParseGridTrackSize(trackSize, flags) != CSSParseResult::Ok) { return false; } // Use nsAutoPtr to free the list in case of early return. @@ -8692,7 +8782,6 @@ CSSParserImpl::ParseGridTrackListRepeat(nsCSSValueList** aTailPtr) for (;;) { // Optional if (ParseGridLineNames(lastLineNames) == CSSParseResult::Error) { - SkipUntil(')'); return false; } @@ -8700,9 +8789,15 @@ CSSParserImpl::ParseGridTrackListRepeat(nsCSSValueList** aTailPtr) break; } + // only accepts a single track size: + // ? ? + if (repeatAutoEnum.isSome()) { + REPORT_UNEXPECTED(PEMoreThanOneGridRepeatTrackSize); + return false; + } + // Required if (ParseGridTrackSize(trackSize) != CSSParseResult::Ok) { - SkipUntil(')'); return false; } @@ -8725,6 +8820,30 @@ CSSParserImpl::ParseGridTrackListRepeat(nsCSSValueList** aTailPtr) // with the sublists in between // * lastLineNames: the last + if (repeatAutoEnum.isSome()) { + // Instead of hooking up this list into the flat track/name list as usual, + // we create a pair(Int, List) where the first value is the auto-fill/fit + // keyword and the second is the list to repeat. There are three items + // in this list, the first is the list of line names before the track size, + // the second item is the track size, and the last item is the list of line + // names after the track size. Note that the line names are NOT merged + // with any line names before/after the repeat() itself. + nsCSSValue listValue; + nsCSSValueList* list = listValue.SetListValue(); + list->mValue = firstLineNames; + list = list->mNext = new nsCSSValueList; + list->mValue = trackSize; + list = list->mNext = new nsCSSValueList; + list->mValue = lastLineNames; + nsCSSValue kwd; + kwd.SetIntValue(repeatAutoEnum.value(), eCSSUnit_Enumerated); + *aTailPtr = (*aTailPtr)->mNext = new nsCSSValueList; + (*aTailPtr)->mValue.SetPairValue(kwd, listValue); + // Append an empty list since the caller expects that to represent the names + // that follows the repeat() function. + *aTailPtr = (*aTailPtr)->mNext = new nsCSSValueList; + return true; + } // Join the last and first (in that order.) // For example, repeat(3, (a) 100px (b) 200px (c)) results in diff --git a/layout/style/nsCSSValue.cpp b/layout/style/nsCSSValue.cpp index 47f50e8f36..b40e6e5f3f 100644 --- a/layout/style/nsCSSValue.cpp +++ b/layout/style/nsCSSValue.cpp @@ -2011,9 +2011,48 @@ AppendGridTemplateToString(const nsCSSValueList* val, if (unit == eCSSUnit_Enumerated && val->mValue.GetIntValue() == NS_STYLE_GRID_TEMPLATE_SUBGRID) { + MOZ_ASSERT(!isSubgrid, "saw subgrid once already"); isSubgrid = true; aResult.AppendLiteral("subgrid"); + } else if (unit == eCSSUnit_Pair) { + // This is a repeat 'auto-fill' / 'auto-fit'. + const nsCSSValuePair& pair = val->mValue.GetPairValue(); + switch (pair.mXValue.GetIntValue()) { + case NS_STYLE_GRID_REPEAT_AUTO_FILL: + aResult.AppendLiteral("repeat(auto-fill, "); + break; + case NS_STYLE_GRID_REPEAT_AUTO_FIT: + aResult.AppendLiteral("repeat(auto-fit, "); + break; + default: + MOZ_ASSERT_UNREACHABLE("unexpected enum value"); + } + const nsCSSValueList* repeatList = pair.mYValue.GetListValue(); + if (repeatList->mValue.GetUnit() != eCSSUnit_Null) { + aResult.Append('['); + AppendValueListToString(repeatList->mValue.GetListValue(), aProperty, + aResult, aSerialization); + aResult.Append(']'); + if (!isSubgrid) { + aResult.Append(' '); + } + } else if (isSubgrid) { + aResult.AppendLiteral("[]"); + } + if (!isSubgrid) { + repeatList = repeatList->mNext; + repeatList->mValue.AppendToString(aProperty, aResult, aSerialization); + repeatList = repeatList->mNext; + if (repeatList->mValue.GetUnit() != eCSSUnit_Null) { + aResult.AppendLiteral(" ["); + AppendValueListToString(repeatList->mValue.GetListValue(), aProperty, + aResult, aSerialization); + aResult.Append(']'); + } + } + aResult.Append(')'); + } else if (unit == eCSSUnit_Null) { // Empty or omitted . if (isSubgrid) { diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp index dc79426817..ac4e66946f 100644 --- a/layout/style/nsComputedDOMStyle.cpp +++ b/layout/style/nsComputedDOMStyle.cpp @@ -2362,26 +2362,60 @@ nsComputedDOMStyle::DoGetGridTemplateAreas() return valueList; } -// aLineNames must not be empty -CSSValue* -nsComputedDOMStyle::GetGridLineNames(const nsTArray& aLineNames) +void +nsComputedDOMStyle::AppendGridLineNames(nsString& aResult, + const nsTArray& aLineNames) { - nsROCSSPrimitiveValue *val = new nsROCSSPrimitiveValue; + MOZ_ASSERT(!aLineNames.IsEmpty(), "expected some line names"); + uint32_t numLines = aLineNames.Length(); + for (uint32_t i = 0;;) { + nsStyleUtil::AppendEscapedCSSIdent(aLineNames[i], aResult); + if (++i == numLines) { + break; + } + aResult.Append(' '); + } +} + +void +nsComputedDOMStyle::AppendGridLineNames(nsDOMCSSValueList* aValueList, + const nsTArray& aLineNames) +{ + if (aLineNames.IsEmpty()) { + return; + } + nsROCSSPrimitiveValue* val = new nsROCSSPrimitiveValue; nsAutoString lineNamesString; - uint32_t i_end = aLineNames.Length(); lineNamesString.Assign('['); - if (i_end > 0) { - for (uint32_t i = 0;;) { - nsStyleUtil::AppendEscapedCSSIdent(aLineNames[i], lineNamesString); - if (++i == i_end) { - break; - } + AppendGridLineNames(lineNamesString, aLineNames); + lineNamesString.Append(']'); + val->SetString(lineNamesString); + aValueList->AppendCSSValue(val); +} + +void +nsComputedDOMStyle::AppendGridLineNames(nsDOMCSSValueList* aValueList, + const nsTArray& aLineNames1, + const nsTArray& aLineNames2) +{ + if (aLineNames1.IsEmpty() && aLineNames2.IsEmpty()) { + return; + } + nsROCSSPrimitiveValue* val = new nsROCSSPrimitiveValue; + nsAutoString lineNamesString; + lineNamesString.Assign('['); + if (!aLineNames1.IsEmpty()) { + AppendGridLineNames(lineNamesString, aLineNames1); + } + if (!aLineNames2.IsEmpty()) { + if (!aLineNames1.IsEmpty()) { lineNamesString.Append(' '); } + AppendGridLineNames(lineNamesString, aLineNames2); } lineNamesString.Append(']'); val->SetString(lineNamesString); - return val; + aValueList->AppendCSSValue(val); } CSSValue* @@ -2421,6 +2455,7 @@ nsComputedDOMStyle::GetGridTemplateColumnsRows(const nsStyleGridTemplate& aTrack const nsTArray* aTrackSizes) { if (aTrackList.mIsSubgrid) { + // XXX TODO: add support for repeat(auto-fill) for 'subgrid' (bug 1234311) NS_ASSERTION(aTrackList.mMinTrackSizingFunctions.IsEmpty() && aTrackList.mMaxTrackSizingFunctions.IsEmpty(), "Unexpected sizing functions with subgrid"); @@ -2431,7 +2466,21 @@ nsComputedDOMStyle::GetGridTemplateColumnsRows(const nsStyleGridTemplate& aTrack valueList->AppendCSSValue(subgridKeyword); for (uint32_t i = 0; i < aTrackList.mLineNameLists.Length(); i++) { - valueList->AppendCSSValue(GetGridLineNames(aTrackList.mLineNameLists[i])); + if (MOZ_UNLIKELY(aTrackList.IsRepeatAutoIndex(i))) { + MOZ_ASSERT(aTrackList.mIsAutoFill, "subgrid can only have 'auto-fill'"); + MOZ_ASSERT(!aTrackList.mRepeatAutoLineNameListBefore.IsEmpty(), + "The syntax is + so this shouldn't be empty"); + MOZ_ASSERT(aTrackList.mRepeatAutoLineNameListAfter.IsEmpty(), + "mRepeatAutoLineNameListAfter isn't used for subgrid"); + nsROCSSPrimitiveValue* start = new nsROCSSPrimitiveValue; + start->SetString(NS_LITERAL_STRING("repeat(auto-fill,")); + valueList->AppendCSSValue(start); + AppendGridLineNames(valueList, aTrackList.mRepeatAutoLineNameListBefore); + nsROCSSPrimitiveValue* end = new nsROCSSPrimitiveValue; + end->SetString(NS_LITERAL_STRING(")")); + valueList->AppendCSSValue(end); + } + AppendGridLineNames(valueList, aTrackList.mLineNameLists[i]); } return valueList; } @@ -2452,29 +2501,98 @@ nsComputedDOMStyle::GetGridTemplateColumnsRows(const nsStyleGridTemplate& aTrack MOZ_ASSERT(aTrackList.mLineNameLists.Length() == numSizes + 1, "Unexpected number of line name lists"); if (aTrackSizes) { - for (uint32_t i = 0;; i++) { - const nsTArray& lineNames = aTrackList.mLineNameLists[i]; - if (!lineNames.IsEmpty()) { - valueList->AppendCSSValue(GetGridLineNames(lineNames)); + // We've done layout on the grid and have resolved the sizes of its tracks, + // so we'll return those sizes here. The grid spec says we MAY use + // repeat(, Npx) here for consecutive tracks with the same + // size, but that doesn't seem worth doing since even for repeat(auto-*) + // the resolved size might differ for the repeated tracks. + const uint32_t numTracks = aTrackSizes->Length(); + MOZ_ASSERT(numTracks > 0 || + (aTrackList.HasRepeatAuto() && !aTrackList.mIsAutoFill), + "only 'auto-fit' can result in zero tracks"); + if (numTracks == 0) { + nsROCSSPrimitiveValue* val = new nsROCSSPrimitiveValue; + val->SetIdent(eCSSKeyword_none); + return val; + } + int32_t endOfRepeat = 0; // first index after any repeat() tracks + int32_t offsetToLastRepeat = 0; + if (aTrackList.HasRepeatAuto()) { + // offsetToLastRepeat is -1 if all repeat(auto-fit) tracks are empty + offsetToLastRepeat = numTracks + 1 - aTrackList.mLineNameLists.Length(); + endOfRepeat = aTrackList.mRepeatAutoIndex + offsetToLastRepeat + 1; + } + MOZ_ASSERT(numTracks > 0); + for (int32_t i = 0;; i++) { + if (aTrackList.HasRepeatAuto()) { + if (i == aTrackList.mRepeatAutoIndex) { + const nsTArray& lineNames = aTrackList.mLineNameLists[i]; + if (i == endOfRepeat) { + // all auto-fit tracks are empty, so "[a] repeat(...) [b]" + // becomes "[a b]" + AppendGridLineNames(valueList, lineNames, + aTrackList.mLineNameLists[i + 1]); + } else { + AppendGridLineNames(valueList, lineNames, + aTrackList.mRepeatAutoLineNameListBefore); + } + } else if (i == endOfRepeat) { + const nsTArray& lineNames = + aTrackList.mLineNameLists[aTrackList.mRepeatAutoIndex + 1]; + AppendGridLineNames(valueList, + aTrackList.mRepeatAutoLineNameListAfter, + lineNames); + } else if (i > aTrackList.mRepeatAutoIndex && i < endOfRepeat) { + AppendGridLineNames(valueList, + aTrackList.mRepeatAutoLineNameListAfter, + aTrackList.mRepeatAutoLineNameListBefore); + } else { + uint32_t j = i > endOfRepeat ? i - offsetToLastRepeat : i; + const nsTArray& lineNames = aTrackList.mLineNameLists[j]; + AppendGridLineNames(valueList, lineNames); + } + } else { + const nsTArray& lineNames = aTrackList.mLineNameLists[i]; + AppendGridLineNames(valueList, lineNames); } - if (i == numSizes) { + if (uint32_t(i) == numTracks) { break; } nsROCSSPrimitiveValue* val = new nsROCSSPrimitiveValue; - val->SetAppUnits(aTrackSizes->ElementAt(i)); + val->SetAppUnits((*aTrackSizes)[i]); valueList->AppendCSSValue(val); } } else { + // We don't have a frame. So, we'll just return a serialization of + // the tracks from the style (without resolved sizes). for (uint32_t i = 0;; i++) { const nsTArray& lineNames = aTrackList.mLineNameLists[i]; if (!lineNames.IsEmpty()) { - valueList->AppendCSSValue(GetGridLineNames(lineNames)); + AppendGridLineNames(valueList, lineNames); } if (i == numSizes) { break; } - valueList->AppendCSSValue(GetGridTrackSize(aTrackList.mMinTrackSizingFunctions[i], - aTrackList.mMaxTrackSizingFunctions[i])); + if (MOZ_UNLIKELY(aTrackList.IsRepeatAutoIndex(i))) { + nsROCSSPrimitiveValue* start = new nsROCSSPrimitiveValue; + start->SetString(aTrackList.mIsAutoFill ? NS_LITERAL_STRING("repeat(auto-fill,") + : NS_LITERAL_STRING("repeat(auto-fit,")); + valueList->AppendCSSValue(start); + if (!aTrackList.mRepeatAutoLineNameListBefore.IsEmpty()) { + AppendGridLineNames(valueList, aTrackList.mRepeatAutoLineNameListBefore); + } + valueList->AppendCSSValue(GetGridTrackSize(aTrackList.mMinTrackSizingFunctions[i], + aTrackList.mMaxTrackSizingFunctions[i])); + if (!aTrackList.mRepeatAutoLineNameListAfter.IsEmpty()) { + AppendGridLineNames(valueList, aTrackList.mRepeatAutoLineNameListAfter); + } + nsROCSSPrimitiveValue* end = new nsROCSSPrimitiveValue; + end->SetString(NS_LITERAL_STRING(")")); + valueList->AppendCSSValue(end); + } else { + valueList->AppendCSSValue(GetGridTrackSize(aTrackList.mMinTrackSizingFunctions[i], + aTrackList.mMaxTrackSizingFunctions[i])); + } } } diff --git a/layout/style/nsComputedDOMStyle.h b/layout/style/nsComputedDOMStyle.h index ce7a6d02b4..c5b04d1f27 100644 --- a/layout/style/nsComputedDOMStyle.h +++ b/layout/style/nsComputedDOMStyle.h @@ -176,7 +176,16 @@ private: mozilla::dom::CSSValue* GetSVGPaintFor(bool aFill); - mozilla::dom::CSSValue* GetGridLineNames(const nsTArray& aLineNames); + // Appends all aLineNames (must be non-empty) space-separated to aResult. + void AppendGridLineNames(nsString& aResult, + const nsTArray& aLineNames); + // Appends aLineNames (if non-empty) as a CSSValue* to aValueList. + void AppendGridLineNames(nsDOMCSSValueList* aValueList, + const nsTArray& aLineNames); + // Appends aLineNames1/2 (if non-empty) as a CSSValue* to aValueList. + void AppendGridLineNames(nsDOMCSSValueList* aValueList, + const nsTArray& aLineNames1, + const nsTArray& aLineNames2); mozilla::dom::CSSValue* GetGridTrackSize(const nsStyleCoord& aMinSize, const nsStyleCoord& aMaxSize); mozilla::dom::CSSValue* GetGridTemplateColumnsRows(const nsStyleGridTemplate& aTrackList, diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp index 66981d7783..f10f273cfb 100644 --- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -7690,9 +7690,11 @@ SetGridTrackBreadth(const nsCSSValue& aValue, MOZ_ASSERT(unit != eCSSUnit_Inherit && unit != eCSSUnit_Unset, "Unexpected value that would use dummyParentCoord"); const nsStyleCoord dummyParentCoord; - SetCoord(aValue, aResult, dummyParentCoord, - SETCOORD_LPE | SETCOORD_STORE_CALC, - aStyleContext, aPresContext, aConditions); + DebugOnly stored = + SetCoord(aValue, aResult, dummyParentCoord, + SETCOORD_LPE | SETCOORD_STORE_CALC, + aStyleContext, aPresContext, aConditions); + MOZ_ASSERT(stored, "invalid value"); } } @@ -7760,15 +7762,14 @@ SetGridAutoColumnsRows(const nsCSSValue& aValue, static void AppendGridLineNames(const nsCSSValue& aValue, - nsStyleGridTemplate& aResult) + nsTArray& aNameList) { // Compute a value - nsTArray* nameList = aResult.mLineNameLists.AppendElement(); // Null unit means empty list, nothing more to do. if (aValue.GetUnit() != eCSSUnit_Null) { const nsCSSValueList* item = aValue.GetListValue(); do { - nsString* name = nameList->AppendElement(); + nsString* name = aNameList.AppendElement(); item->mValue.GetStringValue(*name); item = item->mNext; } while (item); @@ -7794,6 +7795,10 @@ SetGridTrackList(const nsCSSValue& aValue, aResult.mLineNameLists = aParentValue.mLineNameLists; aResult.mMinTrackSizingFunctions = aParentValue.mMinTrackSizingFunctions; aResult.mMaxTrackSizingFunctions = aParentValue.mMaxTrackSizingFunctions; + aResult.mRepeatAutoLineNameListBefore = aParentValue.mRepeatAutoLineNameListBefore; + aResult.mRepeatAutoLineNameListAfter = aParentValue.mRepeatAutoLineNameListAfter; + aResult.mRepeatAutoIndex = aParentValue.mRepeatAutoIndex; + aResult.mIsAutoFill = aParentValue.mIsAutoFill; break; case eCSSUnit_Initial: @@ -7803,20 +7808,42 @@ SetGridTrackList(const nsCSSValue& aValue, aResult.mLineNameLists.Clear(); aResult.mMinTrackSizingFunctions.Clear(); aResult.mMaxTrackSizingFunctions.Clear(); + aResult.mRepeatAutoLineNameListBefore.Clear(); + aResult.mRepeatAutoLineNameListAfter.Clear(); + aResult.mRepeatAutoIndex = -1; + aResult.mIsAutoFill = false; break; default: aResult.mLineNameLists.Clear(); aResult.mMinTrackSizingFunctions.Clear(); aResult.mMaxTrackSizingFunctions.Clear(); + aResult.mRepeatAutoLineNameListBefore.Clear(); + aResult.mRepeatAutoLineNameListAfter.Clear(); + aResult.mRepeatAutoIndex = -1; + aResult.mIsAutoFill = false; const nsCSSValueList* item = aValue.GetListValue(); if (item->mValue.GetUnit() == eCSSUnit_Enumerated && item->mValue.GetIntValue() == NS_STYLE_GRID_TEMPLATE_SUBGRID) { // subgrid ? aResult.mIsSubgrid = true; item = item->mNext; - while (item) { - AppendGridLineNames(item->mValue, aResult); + for (int32_t i = 0; item && i < nsStyleGridLine::kMaxLine; ++i) { + if (item->mValue.GetUnit() == eCSSUnit_Pair) { + // This is a 'auto-fill' expression. + const nsCSSValuePair& pair = item->mValue.GetPairValue(); + MOZ_ASSERT(aResult.mRepeatAutoIndex == -1, + "can only have one with auto-fill"); + aResult.mRepeatAutoIndex = i; + aResult.mIsAutoFill = true; + MOZ_ASSERT(pair.mXValue.GetIntValue() == NS_STYLE_GRID_REPEAT_AUTO_FILL, + "unexpected repeat() enum value for subgrid"); + const nsCSSValueList* list = pair.mYValue.GetListValue(); + AppendGridLineNames(list->mValue, aResult.mRepeatAutoLineNameListBefore); + } else { + AppendGridLineNames(item->mValue, + *aResult.mLineNameLists.AppendElement()); + } item = item->mNext; } } else { @@ -7826,17 +7853,45 @@ SetGridTrackList(const nsCSSValue& aValue, // and alternating between that and . aResult.mIsSubgrid = false; for (int32_t line = 1; ; ++line) { - AppendGridLineNames(item->mValue, aResult); + AppendGridLineNames(item->mValue, + *aResult.mLineNameLists.AppendElement()); item = item->mNext; if (!item || line == nsStyleGridLine::kMaxLine) { break; } - nsStyleCoord& min = *aResult.mMinTrackSizingFunctions.AppendElement(); - nsStyleCoord& max = *aResult.mMaxTrackSizingFunctions.AppendElement(); - SetGridTrackSize(item->mValue, min, max, - aStyleContext, aPresContext, aConditions); + if (item->mValue.GetUnit() == eCSSUnit_Pair) { + // This is a 'auto-fill' / 'auto-fit' expression. + const nsCSSValuePair& pair = item->mValue.GetPairValue(); + MOZ_ASSERT(aResult.mRepeatAutoIndex == -1, + "can only have one "); + aResult.mRepeatAutoIndex = line - 1; + switch (pair.mXValue.GetIntValue()) { + case NS_STYLE_GRID_REPEAT_AUTO_FILL: + aResult.mIsAutoFill = true; + break; + case NS_STYLE_GRID_REPEAT_AUTO_FIT: + aResult.mIsAutoFill = false; + break; + default: + MOZ_ASSERT_UNREACHABLE("unexpected repeat() enum value"); + } + const nsCSSValueList* list = pair.mYValue.GetListValue(); + AppendGridLineNames(list->mValue, aResult.mRepeatAutoLineNameListBefore); + list = list->mNext; + nsStyleCoord& min = *aResult.mMinTrackSizingFunctions.AppendElement(); + nsStyleCoord& max = *aResult.mMaxTrackSizingFunctions.AppendElement(); + SetGridTrackSize(list->mValue, min, max, + aStyleContext, aPresContext, aConditions); + list = list->mNext; + AppendGridLineNames(list->mValue, aResult.mRepeatAutoLineNameListAfter); + } else { + nsStyleCoord& min = *aResult.mMinTrackSizingFunctions.AppendElement(); + nsStyleCoord& max = *aResult.mMaxTrackSizingFunctions.AppendElement(); + SetGridTrackSize(item->mValue, min, max, + aStyleContext, aPresContext, aConditions); + } item = item->mNext; MOZ_ASSERT(item, "Expected a eCSSUnit_List of odd length"); diff --git a/layout/style/nsStyleConsts.h b/layout/style/nsStyleConsts.h index 9f14a50275..bf5eda55f4 100644 --- a/layout/style/nsStyleConsts.h +++ b/layout/style/nsStyleConsts.h @@ -640,6 +640,10 @@ enum class FillMode : uint32_t; #define NS_STYLE_GRID_TRACK_BREADTH_MAX_CONTENT 1 #define NS_STYLE_GRID_TRACK_BREADTH_MIN_CONTENT 2 +// CSS Grid keywords for +#define NS_STYLE_GRID_REPEAT_AUTO_FILL 0 +#define NS_STYLE_GRID_REPEAT_AUTO_FIT 1 + // defaults per MathML spec #define NS_MATHML_DEFAULT_SCRIPT_SIZE_MULTIPLIER 0.71f #define NS_MATHML_DEFAULT_SCRIPT_MIN_SIZE_PT 8 diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h index c2f6f9849c..a17ff4e303 100644 --- a/layout/style/nsStyleStruct.h +++ b/layout/style/nsStyleStruct.h @@ -1261,55 +1261,6 @@ public: nsRect mImageRegion; // [inherited] the rect to use within an image }; -// Computed value of the grid-template-columns or grid-template-rows property -// (but *not* grid-template-areas.) -// http://dev.w3.org/csswg/css-grid/#track-sizing -// -// This represents either: -// * none: -// mIsSubgrid is false, all three arrays are empty -// * : -// mIsSubgrid is false, -// mMinTrackSizingFunctions and mMaxTrackSizingFunctions -// are of identical non-zero size, -// and mLineNameLists is one element longer than that. -// (Delimiting N columns requires N+1 lines: -// one before each track, plus one at the very end.) -// -// An omitted is still represented in mLineNameLists, -// as an empty sub-array. -// -// A specified as a single is represented -// as identical min and max sizing functions. -// -// The units for nsStyleCoord are: -// * eStyleUnit_Percent represents a -// * eStyleUnit_FlexFraction represents a flexible fraction -// * eStyleUnit_Coord represents a -// * eStyleUnit_Enumerated represents min-content or max-content -// * subgrid ?: -// mIsSubgrid is true, -// mLineNameLists may or may not be empty, -// mMinTrackSizingFunctions and mMaxTrackSizingFunctions are empty. -struct nsStyleGridTemplate { - bool mIsSubgrid; - nsTArray> mLineNameLists; - nsTArray mMinTrackSizingFunctions; - nsTArray mMaxTrackSizingFunctions; - - nsStyleGridTemplate() - : mIsSubgrid(false) - { - } - - inline bool operator!=(const nsStyleGridTemplate& aOther) const { - return mIsSubgrid != aOther.mIsSubgrid || - mLineNameLists != aOther.mLineNameLists || - mMinTrackSizingFunctions != aOther.mMinTrackSizingFunctions || - mMaxTrackSizingFunctions != aOther.mMaxTrackSizingFunctions; - } -}; - struct nsStyleGridLine { // http://dev.w3.org/csswg/css-grid/#typedef-grid-line // XXXmats we could optimize memory size here @@ -2293,6 +2244,83 @@ struct nsStyleTable { int32_t mSpan; // [reset] the number of columns spanned by a colgroup or col }; +// Computed value of the grid-template-columns or grid-template-rows property +// (but *not* grid-template-areas.) +// http://dev.w3.org/csswg/css-grid/#track-sizing +// +// This represents either: +// * none: +// mIsSubgrid is false, all three arrays are empty +// * : +// mIsSubgrid is false, +// mMinTrackSizingFunctions and mMaxTrackSizingFunctions +// are of identical non-zero size, +// and mLineNameLists is one element longer than that. +// (Delimiting N columns requires N+1 lines: +// one before each track, plus one at the very end.) +// +// An omitted is still represented in mLineNameLists, +// as an empty sub-array. +// +// A specified as a single is represented +// as identical min and max sizing functions. +// +// The units for nsStyleCoord are: +// * eStyleUnit_Percent represents a +// * eStyleUnit_FlexFraction represents a flexible fraction +// * eStyleUnit_Coord represents a +// * eStyleUnit_Enumerated represents min-content or max-content +// * subgrid ?: +// mIsSubgrid is true, +// mLineNameLists may or may not be empty, +// mMinTrackSizingFunctions and mMaxTrackSizingFunctions are empty. +// +// If mRepeatAutoIndex != -1 then that index is an and +// mIsAutoFill == true means it's an 'auto-fill', otherwise 'auto-fit'. +// mRepeatAutoLineNameListBefore is the list of line names before the track +// size, mRepeatAutoLineNameListAfter the names after. (They are empty +// when there is no track, i.e. when mRepeatAutoIndex == -1). +// When mIsSubgrid is true, mRepeatAutoLineNameListBefore contains the line +// names and mRepeatAutoLineNameListAfter is empty. +struct nsStyleGridTemplate { + nsTArray> mLineNameLists; + nsTArray mMinTrackSizingFunctions; + nsTArray mMaxTrackSizingFunctions; + nsTArray mRepeatAutoLineNameListBefore; + nsTArray mRepeatAutoLineNameListAfter; + int16_t mRepeatAutoIndex; // -1 or the track index for an auto-fill/fit track + bool mIsAutoFill : 1; + bool mIsSubgrid : 1; + + nsStyleGridTemplate() + : mRepeatAutoIndex(-1) + , mIsAutoFill(false) + , mIsSubgrid(false) + { + } + + inline bool operator!=(const nsStyleGridTemplate& aOther) const { + return + mIsSubgrid != aOther.mIsSubgrid || + mLineNameLists != aOther.mLineNameLists || + mMinTrackSizingFunctions != aOther.mMinTrackSizingFunctions || + mMaxTrackSizingFunctions != aOther.mMaxTrackSizingFunctions || + mIsAutoFill != aOther.mIsAutoFill || + mRepeatAutoIndex != aOther.mRepeatAutoIndex || + mRepeatAutoLineNameListBefore != aOther.mRepeatAutoLineNameListBefore || + mRepeatAutoLineNameListAfter != aOther.mRepeatAutoLineNameListAfter; + } + + bool HasRepeatAuto() const { + return mRepeatAutoIndex != -1; + } + + bool IsRepeatAutoIndex(uint32_t aIndex) const { + MOZ_ASSERT(aIndex < uint32_t(2*nsStyleGridLine::kMaxLine)); + return int32_t(aIndex) == mRepeatAutoIndex; + } +}; + struct nsStylePosition { nsStylePosition(void); nsStylePosition(const nsStylePosition& aOther); diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js index 734cd0c0da..b5f80c564c 100644 --- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -5955,7 +5955,15 @@ if (IsCSSPropertyPrefEnabled("layout.css.grid.enabled")) { "[a] 2.5fr [z] Repeat(4, [a] 20px [] auto [b c]) [d]", "[a] 2.5fr [z] Repeat(4, [a] 20px [] auto) [d]", "[a] 2.5fr [z] Repeat(4, 20px [b c] auto [b c]) [d]", - "[a] 2.5fr [z] Repeat(4, 20px auto) [d]" + "[a] 2.5fr [z] Repeat(4, 20px auto) [d]", + "repeat(auto-fill, 0)", + "[a] repeat( Auto-fill,1%)", + "[a] repeat(Auto-fit, 0)", + "repeat(Auto-fit,[] 1%)", + "repeat(auto-fit, [a] 1em) auto", + "[a] repeat( auto-fit,[a b] minmax(0,0) )", + "[a] 40px repeat(auto-fit,[a b] minmax(1px, 0) [])", + "[a] auto [b] repeat(auto-fit,[a b] minmax(1mm, 1%) [c]) [c] auto", ], invalid_values: [ "", @@ -5992,7 +6000,22 @@ if (IsCSSPropertyPrefEnabled("layout.css.grid.enabled")) { "repeat(2.5, 20px)", "repeat(2, (foo))", "repeat(2, foo)", - "40px calc(0px + rubbish)" + "40px calc(0px + rubbish)", + "repeat(1, repeat(1, 20px))", + "repeat(auto-fill, auto)", + "repeat(auto-fit,auto)", + "repeat(auto-fit,[])", + "repeat(auto-fill, 0) repeat(auto-fit, 0) ", + "repeat(auto-fit, 0) repeat(auto-fill, 0) ", + "[a] repeat(auto-fit, 0) repeat(auto-fit, 0) ", + "[a] repeat(auto-fill, 0) [a] repeat(auto-fill, 0) ", + "repeat(auto-fill, 0 0)", + "repeat(auto-fill, 0 [] 0)", + "repeat(auto-fill, min-content)", + "repeat(auto-fit,max-content)", + "repeat(auto-fit,minmax(auto,auto))", + "repeat(auto-fit,[] minmax(1px, min-content))", + "repeat(auto-fit,[a] minmax(1%, auto) [])", ], unbalanced_values: [ "(foo] 40px", @@ -6006,7 +6029,11 @@ if (IsCSSPropertyPrefEnabled("layout.css.grid.enabled")) { "subgrid", "subgrid [] [foo bar]", "subgrid repeat(1, [])", - "subgrid Repeat(4, [a] [b c] [] [d])" + "subgrid Repeat(4, [a] [b c] [] [d])", + "repeat(auto-fill, [])", + "[] repeat(Auto-fill, [a] [b c] [] [d])", + "[x] repeat( Auto-fill, [a b c]) []", + "[x] repeat(auto-fill, []) [y z]" ); gCSSProperties["grid-template-columns"].invalid_values.push( "subgrid (foo) 40px", @@ -6021,7 +6048,14 @@ if (IsCSSPropertyPrefEnabled("layout.css.grid.enabled")) { "subgrid repeat(1)", "subgrid repeat(1, )", "subgrid repeat(2, (40px))", - "subgrid repeat(2, foo)" + "subgrid repeat(2, foo)", + "subgrid repeat(1, repeat(1, []))", + "subgrid repeat(auto-fit,[])", + "subgrid [] repeat(auto-fit,[])", + "subgrid [a] repeat(auto-fit,[])", + "subgrid repeat(auto-fill, 1px)", + "subgrid repeat(auto-fill, 1px [])", + "subgrid repeat(auto-fill, []) repeat(auto-fill, [])" ); } gCSSProperties["grid-template-rows"] = { diff --git a/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h b/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h index f7ce554fd1..364261ed2f 100644 --- a/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h +++ b/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h @@ -238,7 +238,7 @@ class CodecPluginID public: virtual ~CodecPluginID() {} - virtual const uint64_t PluginID() = 0; + virtual uint64_t PluginID() const = 0; }; class VideoEncoder : public CodecPluginID diff --git a/media/webrtc/signaling/src/media-conduit/WebrtcGmpVideoCodec.h b/media/webrtc/signaling/src/media-conduit/WebrtcGmpVideoCodec.h index 597d4c3f1b..b81acebf7b 100644 --- a/media/webrtc/signaling/src/media-conduit/WebrtcGmpVideoCodec.h +++ b/media/webrtc/signaling/src/media-conduit/WebrtcGmpVideoCodec.h @@ -133,7 +133,7 @@ public: // Implement VideoEncoder interface, sort of. // (We cannot use |Release|, since that's needed for nsRefPtr) - virtual const uint64_t PluginID() + virtual uint64_t PluginID() const { return mCachedPluginId; } @@ -300,7 +300,7 @@ class WebrtcVideoEncoderProxy : public WebrtcVideoEncoder RegisterEncodeCompleteCallback(nullptr); } - const uint64_t PluginID() override + uint64_t PluginID() const override { return mEncoderImpl->PluginID(); } @@ -359,7 +359,7 @@ public: // Implement VideoEncoder interface, sort of. // (We cannot use |Release|, since that's needed for nsRefPtr) - virtual const uint64_t PluginID() + virtual uint64_t PluginID() const { return mCachedPluginId; } @@ -477,7 +477,7 @@ class WebrtcVideoDecoderProxy : public WebrtcVideoDecoder RegisterDecodeCompleteCallback(nullptr); } - const uint64_t PluginID() override + uint64_t PluginID() const override { return mDecoderImpl->PluginID(); } diff --git a/media/webrtc/signaling/src/media-conduit/WebrtcMediaCodecVP8VideoCodec.h b/media/webrtc/signaling/src/media-conduit/WebrtcMediaCodecVP8VideoCodec.h index 990208406a..c8e57c0587 100644 --- a/media/webrtc/signaling/src/media-conduit/WebrtcMediaCodecVP8VideoCodec.h +++ b/media/webrtc/signaling/src/media-conduit/WebrtcMediaCodecVP8VideoCodec.h @@ -31,7 +31,7 @@ public: virtual ~WebrtcMediaCodecVP8VideoEncoder() override; // Implement VideoEncoder interface. - virtual const uint64_t PluginID() override { return 0; } + virtual uint64_t PluginID() const override { return 0; } virtual int32_t InitEncode(const webrtc::VideoCodec* codecSettings, int32_t numberOfCores, @@ -74,7 +74,7 @@ public: virtual ~WebrtcMediaCodecVP8VideoDecoder() override; // Implement VideoDecoder interface. - virtual const uint64_t PluginID() override { return 0; } + virtual uint64_t PluginID() const override { return 0; } virtual int32_t InitDecode(const webrtc::VideoCodec* codecSettings, int32_t numberOfCores) override; diff --git a/media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.h b/media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.h index 55022ff7aa..940c6270ad 100644 --- a/media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.h +++ b/media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.h @@ -36,7 +36,7 @@ public: virtual ~WebrtcOMXH264VideoEncoder(); // Implement VideoEncoder interface. - virtual const uint64_t PluginID() override { return 0; } + virtual uint64_t PluginID() const override { return 0; } virtual int32_t InitEncode(const webrtc::VideoCodec* aCodecSettings, int32_t aNumOfCores, @@ -82,7 +82,7 @@ public: virtual ~WebrtcOMXH264VideoDecoder(); // Implement VideoDecoder interface. - virtual const uint64_t PluginID() override { return 0; } + virtual uint64_t PluginID() const override { return 0; } virtual int32_t InitDecode(const webrtc::VideoCodec* aCodecSettings, int32_t aNumOfCores) override; diff --git a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h index e7f8cb4b1d..a69eb4a0bc 100644 --- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h +++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h @@ -403,7 +403,7 @@ public: // Index used to refer to this before we know the TrackID // Note: unlike MediaPipeline::trackid(), this is threadsafe // Not set until first media is received - virtual TrackID const trackid_locked() { return listener_->trackid(); } + virtual TrackID trackid_locked() const { return listener_->trackid(); } // written and used from MainThread virtual bool IsVideo() const override { return is_video_; } diff --git a/media/webrtc/signaling/src/sdp/Sdp.h b/media/webrtc/signaling/src/sdp/Sdp.h index 3b00e5ab1b..8eeb89e2f8 100644 --- a/media/webrtc/signaling/src/sdp/Sdp.h +++ b/media/webrtc/signaling/src/sdp/Sdp.h @@ -156,7 +156,7 @@ public: return mSessionVersion; } - const sdp::AddrType + sdp::AddrType GetAddrType() const { return mAddrType; diff --git a/media/webrtc/signaling/test/FakeMediaStreams.h b/media/webrtc/signaling/test/FakeMediaStreams.h index e1cda6f50e..bfc693d4e4 100644 --- a/media/webrtc/signaling/test/FakeMediaStreams.h +++ b/media/webrtc/signaling/test/FakeMediaStreams.h @@ -313,7 +313,7 @@ public: { return mIsVideo? nullptr : this; } - const uint32_t typeSize () const + uint32_t typeSize () const { return sizeof(Fake_MediaStreamTrack); } diff --git a/modules/libjar/InterceptedJARChannel.cpp b/modules/libjar/InterceptedJARChannel.cpp index c52e373b46..659ba98d63 100644 --- a/modules/libjar/InterceptedJARChannel.cpp +++ b/modules/libjar/InterceptedJARChannel.cpp @@ -45,6 +45,12 @@ InterceptedJARChannel::GetChannel(nsIChannel** aChannel) return NS_OK; } +NS_IMETHODIMP +InterceptedJARChannel::GetSecureUpgradedChannelURI(nsIURI** aURI) +{ + return mChannel->GetURI(aURI); +} + NS_IMETHODIMP InterceptedJARChannel::ResetInterception() { diff --git a/mozglue/linker/Mappable.h b/mozglue/linker/Mappable.h index 56f5dccd5f..7ab7a66555 100644 --- a/mozglue/linker/Mappable.h +++ b/mozglue/linker/Mappable.h @@ -239,18 +239,18 @@ private: } /* Returns offset + length */ - const off_t endOffset() const { + off_t endOffset() const { return offset + length; } /* Returns the offset corresponding to the given address */ - const off_t offsetOf(const void *ptr) const { + off_t offsetOf(const void *ptr) const { return reinterpret_cast(ptr) - reinterpret_cast(addr) + offset; } /* Returns whether the given address is in the LazyMap range */ - const bool Contains(const void *ptr) const { + bool Contains(const void *ptr) const { return (ptr >= addr) && (ptr < end()); } }; diff --git a/mozglue/linker/SeekableZStream.h b/mozglue/linker/SeekableZStream.h index 83e6196f46..3505c681e8 100644 --- a/mozglue/linker/SeekableZStream.h +++ b/mozglue/linker/SeekableZStream.h @@ -77,18 +77,18 @@ public: bool DecompressChunk(void *where, size_t chunk, size_t length = 0); /* Returns the uncompressed size of the complete zstream */ - const size_t GetUncompressedSize() const + size_t GetUncompressedSize() const { return (offsetTable.numElements() - 1) * chunkSize + lastChunkSize; } /* Returns the chunk size of the given chunk */ - const size_t GetChunkSize(size_t chunk = 0) const { + size_t GetChunkSize(size_t chunk = 0) const { return (chunk == offsetTable.numElements() - 1) ? lastChunkSize : chunkSize; } /* Returns the number of chunks */ - const size_t GetChunksNum() const { + size_t GetChunksNum() const { return offsetTable.numElements(); } diff --git a/netwerk/base/PrivateBrowsingChannel.h b/netwerk/base/PrivateBrowsingChannel.h index 165bea03c4..0fe96bd1a2 100644 --- a/netwerk/base/PrivateBrowsingChannel.h +++ b/netwerk/base/PrivateBrowsingChannel.h @@ -48,7 +48,7 @@ public: NS_IMETHOD GetIsChannelPrivate(bool *aResult) { NS_ENSURE_ARG_POINTER(aResult); - *aResult = NS_UsePrivateBrowsing(static_cast(this)); + *aResult = mPrivateBrowsing; return NS_OK; } @@ -63,6 +63,21 @@ public: return NS_OK; } + // Must be called every time the channel's callbacks or loadGroup is updated + void UpdatePrivateBrowsing() + { + // once marked as private we never go un-private + if (mPrivateBrowsing) { + return; + } + + nsCOMPtr loadContext; + NS_QueryNotificationCallbacks(static_cast(this), loadContext); + if (loadContext) { + mPrivateBrowsing = loadContext->UsePrivateBrowsing(); + } + } + bool CanSetCallbacks(nsIInterfaceRequestor* aCallbacks) const { // Make sure that the private bit override flag is not set. diff --git a/netwerk/base/nsBaseChannel.cpp b/netwerk/base/nsBaseChannel.cpp index 80d2bc5afc..27ae10478d 100644 --- a/netwerk/base/nsBaseChannel.cpp +++ b/netwerk/base/nsBaseChannel.cpp @@ -101,7 +101,7 @@ nsBaseChannel::Redirect(nsIChannel *newChannel, uint32_t redirectFlags, newChannel->SetLoadInfo(nullptr); } - // Try to preserve the privacy bit if it has been overridden + // Preserve the privacy bit if it has been overridden if (mPrivateBrowsingOverriden) { nsCOMPtr newPBChannel = do_QueryInterface(newChannel); @@ -424,6 +424,7 @@ nsBaseChannel::SetLoadGroup(nsILoadGroup *aLoadGroup) mLoadGroup = aLoadGroup; CallbacksChanged(); + UpdatePrivateBrowsing(); return NS_OK; } @@ -497,6 +498,7 @@ nsBaseChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks) mCallbacks = aCallbacks; CallbacksChanged(); + UpdatePrivateBrowsing(); return NS_OK; } diff --git a/netwerk/base/nsINetworkInterceptController.idl b/netwerk/base/nsINetworkInterceptController.idl index 7e59c6cae5..bbac746b27 100644 --- a/netwerk/base/nsINetworkInterceptController.idl +++ b/netwerk/base/nsINetworkInterceptController.idl @@ -29,7 +29,7 @@ class ChannelInfo; * which do not implement nsIChannel. */ -[scriptable, uuid(64439e24-eda5-4f39-9a7e-162c4b5e0150)] +[scriptable, uuid(f4b82975-6a86-4cc4-87fe-9a1fd430c86d)] interface nsIInterceptedChannel : nsISupports { /** @@ -76,6 +76,12 @@ interface nsIInterceptedChannel : nsISupports */ readonly attribute nsIChannel channel; + /** + * The URL of the underlying channel object, corrected for a potential + * secure upgrade. + */ + readonly attribute nsIURI secureUpgradedChannelURI; + /** * This method allows to override the channel info for the channel. */ diff --git a/netwerk/base/nsNetUtil.cpp b/netwerk/base/nsNetUtil.cpp index 16a3673e4f..8b2b1e779c 100644 --- a/netwerk/base/nsNetUtil.cpp +++ b/netwerk/base/nsNetUtil.cpp @@ -4,6 +4,9 @@ * 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/. */ +// HttpLog.h should generally be included first +#include "HttpLog.h" + #include "mozilla/LoadContext.h" #include "mozilla/LoadInfo.h" #include "mozilla/BasePrincipal.h" @@ -58,6 +61,10 @@ #include "nsInterfaceRequestorAgg.h" #include "plstr.h" #include "nsINestedURI.h" +#include "mozilla/dom/nsCSPUtils.h" +#include "nsIScriptError.h" +#include "nsISiteSecurityService.h" +#include "nsHttpHandler.h" #ifdef MOZ_WIDGET_GONK #include "nsINetworkManager.h" @@ -1220,13 +1227,12 @@ bool NS_UsePrivateBrowsing(nsIChannel *channel) { bool isPrivate = false; - bool isOverriden = false; nsCOMPtr pbChannel = do_QueryInterface(channel); - if (pbChannel && - NS_SUCCEEDED(pbChannel->IsPrivateModeOverriden(&isPrivate, &isOverriden)) && - isOverriden) { + if (pbChannel && NS_SUCCEEDED(pbChannel->GetIsChannelPrivate(&isPrivate))) { return isPrivate; } + + // Some channels may not implement nsIPrivateBrowsingChannel nsCOMPtr loadContext; NS_QueryNotificationCallbacks(channel, loadContext); return loadContext && loadContext->UsePrivateBrowsing(); @@ -2224,6 +2230,100 @@ NS_IsSrcdocChannel(nsIChannel *aChannel) return false; } +nsresult +NS_ShouldSecureUpgrade(nsIURI* aURI, + nsILoadInfo* aLoadInfo, + nsIPrincipal* aChannelResultPrincipal, + bool aPrivateBrowsing, + bool aAllowSTS, + bool& aShouldUpgrade) +{ + // Even if we're in private browsing mode, we still enforce existing STS + // data (it is read-only). + // if the connection is not using SSL and either the exact host matches or + // a superdomain wants to force HTTPS, do it. + bool isHttps = false; + nsresult rv = aURI->SchemeIs("https", &isHttps); + NS_ENSURE_SUCCESS(rv, rv); + + if (!isHttps) { + // If any of the documents up the chain to the root doucment makes use of + // the CSP directive 'upgrade-insecure-requests', then it's time to fulfill + // the promise to CSP and mixed content blocking to upgrade the channel + // from http to https. + if (aLoadInfo) { + bool isPreload = nsContentUtils::IsPreloadType(aLoadInfo->InternalContentPolicyType()); + bool upgradeRequests = + ((isPreload && aLoadInfo->GetUpgradeInsecurePreloads()) || + (aLoadInfo->GetUpgradeInsecureRequests())); + + // Please note that cross origin top level navigations are not subject + // to upgrade-insecure-requests, see: + // http://www.w3.org/TR/upgrade-insecure-requests/#examples + bool crossOriginNavigation = + (aLoadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_DOCUMENT) && + (!aChannelResultPrincipal->Equals(aLoadInfo->LoadingPrincipal())); + + if (upgradeRequests && !crossOriginNavigation) { + // let's log a message to the console that we are upgrading a request + nsAutoCString spec, scheme; + aURI->GetSpec(spec); + aURI->GetScheme(scheme); + // append the additional 's' for security to the scheme :-) + scheme.AppendASCII("s"); + NS_ConvertUTF8toUTF16 reportSpec(spec); + NS_ConvertUTF8toUTF16 reportScheme(scheme); + + const char16_t* params[] = { reportSpec.get(), reportScheme.get() }; + uint32_t innerWindowId = aLoadInfo->GetInnerWindowID(); + CSP_LogLocalizedStr(MOZ_UTF16("upgradeInsecureRequest"), + params, ArrayLength(params), + EmptyString(), // aSourceFile + EmptyString(), // aScriptSample + 0, // aLineNumber + 0, // aColumnNumber + nsIScriptError::warningFlag, "CSP", + innerWindowId); + + //Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 4); + aShouldUpgrade = true; + return NS_OK; + } + } + + // enforce Strict-Transport-Security + nsISiteSecurityService* sss = gHttpHandler->GetSSService(); + NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY); + + bool isStsHost = false; + uint32_t flags = aPrivateBrowsing ? nsISocketProvider::NO_PERMANENT_STORAGE : 0; + rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI, flags, + &isStsHost); + + // if the SSS check fails, it's likely because this load is on a + // malformed URI or something else in the setup is wrong, so any error + // should be reported. + NS_ENSURE_SUCCESS(rv, rv); + + if (isStsHost) { + LOG(("nsHttpChannel::Connect() STS permissions found\n")); + if (aAllowSTS) { + //Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 3); + aShouldUpgrade = true; + return NS_OK; + } else { + //Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 2); + } + } else { + //Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 1); + } + } else { + //Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 0); + } + aShouldUpgrade = false; + return NS_OK; +} + namespace mozilla { namespace net { diff --git a/netwerk/base/nsNetUtil.h b/netwerk/base/nsNetUtil.h index 24d4e3cb37..605f938c2d 100644 --- a/netwerk/base/nsNetUtil.h +++ b/netwerk/base/nsNetUtil.h @@ -979,6 +979,16 @@ bool NS_IsReasonableHTTPHeaderValue(const nsACString &aValue); */ bool NS_IsValidHTTPToken(const nsACString &aToken); +/** + * Return true if the given request must be upgraded to HTTPS. + */ +nsresult NS_ShouldSecureUpgrade(nsIURI* aURI, + nsILoadInfo* aLoadInfo, + nsIPrincipal* aChannelResultPrincipal, + bool aPrivateBrowsing, + bool aAllowSTS, + bool& aShouldUpgrade); + namespace mozilla { namespace net { diff --git a/netwerk/cache2/CacheObserver.cpp b/netwerk/cache2/CacheObserver.cpp index 73449376c1..d1921acd69 100644 --- a/netwerk/cache2/CacheObserver.cpp +++ b/netwerk/cache2/CacheObserver.cpp @@ -234,7 +234,7 @@ CacheObserver::AttachToPreferences() } // static -uint32_t const CacheObserver::MemoryCacheCapacity() +uint32_t CacheObserver::MemoryCacheCapacity() { if (sMemoryCacheCapacity >= 0) return sMemoryCacheCapacity << 10; @@ -272,7 +272,7 @@ uint32_t const CacheObserver::MemoryCacheCapacity() } // static -bool const CacheObserver::UseNewCache() +bool CacheObserver::UseNewCache() { uint32_t useNewCache = sUseNewCache; @@ -383,7 +383,7 @@ nsresult Run(OriginAttributes const &aOa) } // anon // static -bool const CacheObserver::EntryIsTooBig(int64_t aSize, bool aUsingDisk) +bool CacheObserver::EntryIsTooBig(int64_t aSize, bool aUsingDisk) { // If custom limit is set, check it. int64_t preferredLimit = aUsingDisk ? sMaxDiskEntrySize : sMaxMemoryEntrySize; diff --git a/netwerk/cache2/CacheObserver.h b/netwerk/cache2/CacheObserver.h index 8dd20db1bf..174d5a2c38 100644 --- a/netwerk/cache2/CacheObserver.h +++ b/netwerk/cache2/CacheObserver.h @@ -27,43 +27,43 @@ class CacheObserver : public nsIObserver static CacheObserver* Self() { return sSelf; } // Access to preferences - static bool const UseNewCache(); - static bool const UseDiskCache() + static bool UseNewCache(); + static bool UseDiskCache() { return sUseDiskCache; } - static bool const UseMemoryCache() + static bool UseMemoryCache() { return sUseMemoryCache; } - static uint32_t const MetadataMemoryLimit() // result in bytes. + static uint32_t MetadataMemoryLimit() // result in bytes. { return sMetadataMemoryLimit << 10; } - static uint32_t const MemoryCacheCapacity(); // result in bytes. - static uint32_t const DiskCacheCapacity() // result in bytes. + static uint32_t MemoryCacheCapacity(); // result in bytes. + static uint32_t DiskCacheCapacity() // result in bytes. { return sDiskCacheCapacity << 10; } static void SetDiskCacheCapacity(uint32_t); // parameter in bytes. - static uint32_t const DiskFreeSpaceSoftLimit() // result in bytes. + static uint32_t DiskFreeSpaceSoftLimit() // result in bytes. { return sDiskFreeSpaceSoftLimit << 10; } - static uint32_t const DiskFreeSpaceHardLimit() // result in bytes. + static uint32_t DiskFreeSpaceHardLimit() // result in bytes. { return sDiskFreeSpaceHardLimit << 10; } - static bool const SmartCacheSizeEnabled() + static bool SmartCacheSizeEnabled() { return sSmartCacheSizeEnabled; } - static uint32_t const PreloadChunkCount() + static uint32_t PreloadChunkCount() { return sPreloadChunkCount; } - static uint32_t const MaxMemoryEntrySize() // result in bytes. + static uint32_t MaxMemoryEntrySize() // result in bytes. { return sMaxMemoryEntrySize << 10; } - static uint32_t const MaxDiskEntrySize() // result in bytes. + static uint32_t MaxDiskEntrySize() // result in bytes. { return sMaxDiskEntrySize << 10; } - static uint32_t const MaxDiskChunksMemoryUsage(bool aPriority) // result in bytes. + static uint32_t MaxDiskChunksMemoryUsage(bool aPriority) // result in bytes. { return aPriority ? sMaxDiskPriorityChunksMemoryUsage << 10 : sMaxDiskChunksMemoryUsage << 10; } - static uint32_t const CompressionLevel() + static uint32_t CompressionLevel() { return sCompressionLevel; } - static uint32_t const HalfLifeSeconds() + static uint32_t HalfLifeSeconds() { return sHalfLifeHours * 60.0F * 60.0F; } - static int32_t const HalfLifeExperiment() + static int32_t HalfLifeExperiment() { return sHalfLifeExperiment; } - static bool const ClearCacheOnShutdown() + static bool ClearCacheOnShutdown() { return sSanitizeOnShutdown && sClearCacheOnShutdown; } static void ParentDirOverride(nsIFile ** aDir); - static bool const EntryIsTooBig(int64_t aSize, bool aUsingDisk); + static bool EntryIsTooBig(int64_t aSize, bool aUsingDisk); private: static CacheObserver* sSelf; diff --git a/netwerk/cache2/CacheStorageService.cpp b/netwerk/cache2/CacheStorageService.cpp index 1885ce9f82..0cf532a9d9 100644 --- a/netwerk/cache2/CacheStorageService.cpp +++ b/netwerk/cache2/CacheStorageService.cpp @@ -88,7 +88,7 @@ CacheStorageService::MemoryPool::~MemoryPool() } } -uint32_t const +uint32_t CacheStorageService::MemoryPool::Limit() const { switch (mType) { diff --git a/netwerk/cache2/CacheStorageService.h b/netwerk/cache2/CacheStorageService.h index 9c7c20acc3..91d200492c 100644 --- a/netwerk/cache2/CacheStorageService.h +++ b/netwerk/cache2/CacheStorageService.h @@ -318,7 +318,7 @@ private: void PurgeAll(uint32_t aWhat); private: - uint32_t const Limit() const; + uint32_t Limit() const; MemoryPool() = delete; }; diff --git a/netwerk/ipc/NeckoChannelParams.ipdlh b/netwerk/ipc/NeckoChannelParams.ipdlh index b14136cdbf..da5ab38ee1 100644 --- a/netwerk/ipc/NeckoChannelParams.ipdlh +++ b/netwerk/ipc/NeckoChannelParams.ipdlh @@ -115,6 +115,7 @@ struct HttpChannelOpenArgs nsCString schedulingContextID; OptionalCorsPreflightArgs preflightArgs; uint32_t initialRwin; + bool suspendAfterSynthesizeResponse; bool allowStaleCacheContent; bool isFromProcessingFrameAttributes; }; diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp index 089eea6031..2d9b493465 100644 --- a/netwerk/protocol/http/HttpBaseChannel.cpp +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -37,6 +37,7 @@ #include "nsIObserverService.h" #include "nsProxyRelease.h" #include "nsPIDOMWindow.h" +#include "nsIDocShell.h" #include "nsPerformance.h" #include "nsINetworkInterceptController.h" #include "mozIThirdPartyUtil.h" @@ -254,14 +255,14 @@ NS_IMETHODIMP HttpBaseChannel::SetLoadGroup(nsILoadGroup *aLoadGroup) { MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread."); - + if (!CanSetLoadGroup(aLoadGroup)) { return NS_ERROR_FAILURE; } mLoadGroup = aLoadGroup; mProgressSink = nullptr; - mPrivateBrowsing = NS_UsePrivateBrowsing(this); + UpdatePrivateBrowsing(); return NS_OK; } @@ -296,6 +297,47 @@ HttpBaseChannel::SetLoadFlags(nsLoadFlags aLoadFlags) return NS_OK; } +NS_IMETHODIMP +HttpBaseChannel::SetDocshellUserAgentOverride() +{ + // This sets the docshell specific user agent override, it will be overwritten + // by UserAgentOverrides.jsm if site-specific user agent overrides are set. + nsresult rv; + nsCOMPtr loadContext; + NS_QueryNotificationCallbacks(this, loadContext); + if (!loadContext) { + return NS_OK; + } + + nsCOMPtr domWindow; + loadContext->GetAssociatedWindow(getter_AddRefs(domWindow)); + if (!domWindow) { + return NS_OK; + } + + nsCOMPtr pDomWindow = do_QueryInterface(domWindow); + if (!pDomWindow) { + return NS_OK; + } + + nsIDocShell* docshell = pDomWindow->GetDocShell(); + if (!docshell) { + return NS_OK; + } + + nsString customUserAgent; + docshell->GetCustomUserAgent(customUserAgent); + if (customUserAgent.IsEmpty()) { + return NS_OK; + } + + NS_ConvertUTF16toUTF8 utf8CustomUserAgent(customUserAgent); + rv = SetRequestHeader(NS_LITERAL_CSTRING("User-Agent"), utf8CustomUserAgent, false); + if (NS_FAILED(rv)) return rv; + + return NS_OK; +} + //----------------------------------------------------------------------------- // HttpBaseChannel::nsIChannel //----------------------------------------------------------------------------- @@ -370,7 +412,7 @@ NS_IMETHODIMP HttpBaseChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks) { MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread."); - + if (!CanSetCallbacks(aCallbacks)) { return NS_ERROR_FAILURE; } @@ -378,7 +420,7 @@ HttpBaseChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks) mCallbacks = aCallbacks; mProgressSink = nullptr; - mPrivateBrowsing = NS_UsePrivateBrowsing(this); + UpdatePrivateBrowsing(); return NS_OK; } @@ -1378,7 +1420,7 @@ HttpBaseChannel::SetReferrerWithPolicy(nsIURI *referrer, if (eTLDService) { rv = eTLDService->GetBaseDomain(mURI, extraDomains, currentDomain); if (NS_FAILED(rv)) return rv; - rv = eTLDService->GetBaseDomain(clone, extraDomains, referrerDomain); + rv = eTLDService->GetBaseDomain(clone, extraDomains, referrerDomain); if (NS_FAILED(rv)) return rv; } @@ -2403,13 +2445,13 @@ HttpBaseChannel::BypassServiceWorker() const } bool -HttpBaseChannel::ShouldIntercept() +HttpBaseChannel::ShouldIntercept(nsIURI* aURI) { nsCOMPtr controller; GetCallback(controller); bool shouldIntercept = false; if (controller && !BypassServiceWorker() && mLoadInfo) { - nsresult rv = controller->ShouldPrepareForIntercept(mURI, + nsresult rv = controller->ShouldPrepareForIntercept(aURI ? aURI : mURI.get(), nsContentUtils::IsNonSubresourceRequest(this), &shouldIntercept); if (NS_FAILED(rv)) { @@ -2446,7 +2488,7 @@ void HttpBaseChannel::ReleaseListeners() { MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread."); - + mListener = nullptr; mListenerContext = nullptr; mCallbacks = nullptr; @@ -3119,6 +3161,34 @@ HttpBaseChannel::SetCorsPreflightParameters(const nsTArray& aUnsafeHe mUnsafeHeaders = aUnsafeHeaders; } +// static +nsresult +HttpBaseChannel::GetSecureUpgradedURI(nsIURI* aURI, nsIURI** aUpgradedURI) +{ + nsCOMPtr upgradedURI; + + nsresult rv = aURI->Clone(getter_AddRefs(upgradedURI)); + NS_ENSURE_SUCCESS(rv,rv); + + upgradedURI->SetScheme(NS_LITERAL_CSTRING("https")); + + int32_t oldPort = -1; + rv = aURI->GetPort(&oldPort); + if (NS_FAILED(rv)) return rv; + + // Keep any nonstandard ports so only the scheme is changed. + // For example: + // http://foo.com:80 -> https://foo.com:443 + // http://foo.com:81 -> https://foo.com:81 + + if (oldPort == 80 || oldPort == -1) + upgradedURI->SetPort(-1); + else + upgradedURI->SetPort(oldPort); + + upgradedURI.forget(aUpgradedURI); + return NS_OK; +} + } // namespace net } // namespace mozilla - diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h index 58b348c6aa..9466d8492b 100644 --- a/netwerk/protocol/http/HttpBaseChannel.h +++ b/netwerk/protocol/http/HttpBaseChannel.h @@ -100,6 +100,7 @@ public: NS_IMETHOD SetLoadGroup(nsILoadGroup *aLoadGroup) override; NS_IMETHOD GetLoadFlags(nsLoadFlags *aLoadFlags) override; NS_IMETHOD SetLoadFlags(nsLoadFlags aLoadFlags) override; + NS_IMETHOD SetDocshellUserAgentOverride(); // nsIChannel NS_IMETHOD GetOriginalURI(nsIURI **aOriginalURI) override; @@ -298,6 +299,10 @@ public: /* Necko internal use only... */ // the new mUploadStream. void EnsureUploadStreamIsCloneableComplete(nsresult aStatus); + // Returns an https URI for channels that need to go through secure + // upgrades. + static nsresult GetSecureUpgradedURI(nsIURI* aURI, nsIURI** aUpgradedURI); + protected: nsCOMArray mSecurityConsoleMessages; @@ -342,7 +347,7 @@ protected: // Returns true if this channel should intercept the network request and prepare // for a possible synthesized response instead. - bool ShouldIntercept(); + bool ShouldIntercept(nsIURI* aURI = nullptr); friend class PrivateBrowsingChannel; friend class InterceptFailedOnStop; diff --git a/netwerk/protocol/http/HttpChannelChild.cpp b/netwerk/protocol/http/HttpChannelChild.cpp index 562b9187a5..39027d78da 100644 --- a/netwerk/protocol/http/HttpChannelChild.cpp +++ b/netwerk/protocol/http/HttpChannelChild.cpp @@ -154,7 +154,7 @@ InterceptStreamListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aConte { if (mOwner) { mOwner->DoPreOnStopRequest(aStatusCode); - mOwner->DoOnStopRequest(mOwner, mContext); + mOwner->DoOnStopRequest(mOwner, aStatusCode, mContext); } Cleanup(); return NS_OK; @@ -187,7 +187,10 @@ HttpChannelChild::HttpChannelChild() , mSynthesizedResponse(false) , mShouldInterceptSubsequentRedirect(false) , mRedirectingForSubsequentSynthesizedResponse(false) + , mPostRedirectChannelShouldIntercept(false) + , mPostRedirectChannelShouldUpgrade(false) , mShouldParentIntercept(false) + , mSuspendParentAfterSynthesizeResponse(false) { LOG(("Creating HttpChannelChild @%x\n", this)); @@ -457,6 +460,8 @@ HttpChannelChild::OnStartRequest(const nsresult& channelStatus, mCacheEntryAvailable = cacheEntryAvailable; mCacheExpirationTime = cacheExpirationTime; mCachedCharset = cachedCharset; + mSelfAddr = selfAddr; + mPeerAddr = peerAddr; AutoEventEnqueuer ensureSerialDispatch(mEventQ); @@ -486,11 +491,63 @@ HttpChannelChild::OnStartRequest(const nsresult& channelStatus, mTracingEnabled = false; DoOnStartRequest(this, mListenerContext); - - mSelfAddr = selfAddr; - mPeerAddr = peerAddr; } +namespace { + +class SyntheticDiversionListener final : public nsIStreamListener +{ + RefPtr mChannel; + + ~SyntheticDiversionListener() + { + } + +public: + explicit SyntheticDiversionListener(HttpChannelChild* aChannel) + : mChannel(aChannel) + { + MOZ_ASSERT(mChannel); + } + + NS_IMETHOD + OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) override + { + MOZ_ASSERT_UNREACHABLE("SyntheticDiversionListener should never see OnStartRequest"); + return NS_OK; + } + + NS_IMETHOD + OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, + nsresult aStatus) override + { + mChannel->SendDivertOnStopRequest(aStatus); + return NS_OK; + } + + NS_IMETHOD + OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext, + nsIInputStream* aInputStream, uint64_t aOffset, + uint32_t aCount) override + { + nsAutoCString data; + nsresult rv = NS_ConsumeStream(aInputStream, aCount, data); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRequest->Cancel(rv); + return rv; + } + + mChannel->SendDivertOnDataAvailable(data, aOffset, aCount); + return NS_OK; + } + + NS_DECL_ISUPPORTS +}; + +NS_IMPL_ISUPPORTS(SyntheticDiversionListener, nsIStreamListener); + +} // anonymous namespace + void HttpChannelChild::DoOnStartRequest(nsIRequest* aRequest, nsISupports* aContext) { @@ -508,6 +565,15 @@ HttpChannelChild::DoOnStartRequest(nsIRequest* aRequest, nsISupports* aContext) if (mLoadGroup) { mLoadGroup->RemoveRequest(this, nullptr, mStatus); } + + // If the response has been synthesized in the child, then we are going + // be getting OnDataAvailable and OnStopRequest from the synthetic + // stream pump. We need to forward these back to the parent diversion + // listener. + if (mSynthesizedResponse) { + mListener = new SyntheticDiversionListener(this); + } + return; } @@ -874,7 +940,7 @@ HttpChannelChild::OnStopRequest(const nsresult& channelStatus, // so make sure this goes out of scope before then. AutoEventEnqueuer ensureSerialDispatch(mEventQ); - DoOnStopRequest(this, mListenerContext); + DoOnStopRequest(this, channelStatus, mListenerContext); } ReleaseListeners(); @@ -903,12 +969,15 @@ HttpChannelChild::DoPreOnStopRequest(nsresult aStatus) } void -HttpChannelChild::DoOnStopRequest(nsIRequest* aRequest, nsISupports* aContext) +HttpChannelChild::DoOnStopRequest(nsIRequest* aRequest, nsresult aChannelStatus, nsISupports* aContext) { LOG(("HttpChannelChild::DoOnStopRequest [this=%p]\n", this)); MOZ_ASSERT(!mIsPending); - if (mStatus == NS_ERROR_TRACKING_URI) { + // NB: We use aChannelStatus here instead of mStatus because if there was an + // nsCORSListenerProxy on this request, it will override the tracking + // protection's return value. + if (aChannelStatus == NS_ERROR_TRACKING_URI) { nsChannelClassifier::SetBlockedTrackingContent(this); } @@ -1203,11 +1272,24 @@ HttpChannelChild::SetupRedirect(nsIURI* uri, NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr httpChannelChild = do_QueryInterface(newChannel); - if (mShouldInterceptSubsequentRedirect && httpChannelChild) { - // In the case where there was a synthesized response that caused a redirection, - // we must force the new channel to intercept the request in the parent before a - // network transaction is initiated. - httpChannelChild->ForceIntercepted(); + if (httpChannelChild) { + bool shouldUpgrade = false; + if (mShouldInterceptSubsequentRedirect) { + // In the case where there was a synthesized response that caused a redirection, + // we must force the new channel to intercept the request in the parent before a + // network transaction is initiated. + httpChannelChild->ForceIntercepted(false, false); + } else if (mRedirectMode == nsIHttpChannelInternal::REDIRECT_MODE_MANUAL && + ((redirectFlags & (nsIChannelEventSink::REDIRECT_TEMPORARY | + nsIChannelEventSink::REDIRECT_PERMANENT)) != 0) && + ShouldInterceptURI(newChannel, uri, shouldUpgrade)) { + // In the case where the redirect mode is manual, we need to check whether + // the post-redirect channel needs to be intercepted. If that is the + // case, force the new channel to intercept the request in the parent + // similar to the case above, but also remember that ShouldInterceptURI() + // returned true to avoid calling it a second time. + httpChannelChild->ForceIntercepted(true, shouldUpgrade); + } } mRedirectChannelChild = do_QueryInterface(newChannel); @@ -1743,7 +1825,14 @@ HttpChannelChild::AsyncOpen(nsIStreamListener *listener, nsISupports *aContext) return NS_OK; } - if (ShouldIntercept()) { + // Set user agent override + HttpBaseChannel::SetDocshellUserAgentOverride(); + + MOZ_ASSERT_IF(mPostRedirectChannelShouldUpgrade, + mPostRedirectChannelShouldIntercept); + bool shouldUpgrade = mPostRedirectChannelShouldUpgrade; + if (mPostRedirectChannelShouldIntercept || + ShouldInterceptURI(this, mURI, shouldUpgrade)) { mResponseCouldBeSynthesized = true; nsCOMPtr controller; @@ -1752,7 +1841,8 @@ HttpChannelChild::AsyncOpen(nsIStreamListener *listener, nsISupports *aContext) mInterceptListener = new InterceptStreamListener(this, mListenerContext); RefPtr intercepted = - new InterceptedChannelContent(this, controller, mInterceptListener); + new InterceptedChannelContent(this, controller, + mInterceptListener, shouldUpgrade); intercepted->NotifyController(); return NS_OK; } @@ -1820,8 +1910,11 @@ HttpChannelChild::ContinueAsyncOpen() if (mResponseHead) { openArgs.synthesizedResponseHead() = *mResponseHead; + openArgs.suspendAfterSynthesizeResponse() = + mSuspendParentAfterSynthesizeResponse; } else { openArgs.synthesizedResponseHead() = mozilla::void_t(); + openArgs.suspendAfterSynthesizeResponse() = false; } nsCOMPtr secInfoSer = do_QueryInterface(mSecurityInfo); @@ -1989,36 +2082,6 @@ HttpChannelChild::SetupFallbackChannel(const char *aFallbackKey) DROP_DEAD(); } -// The next four _should_ be implemented, but we need to figure out how -// to transfer the data from the chrome process first. - -NS_IMETHODIMP -HttpChannelChild::GetRemoteAddress(nsACString & _result) -{ - return NS_ERROR_NOT_AVAILABLE; -} - -NS_IMETHODIMP -HttpChannelChild::GetRemotePort(int32_t * _result) -{ - NS_ENSURE_ARG_POINTER(_result); - return NS_ERROR_NOT_AVAILABLE; -} - -NS_IMETHODIMP -HttpChannelChild::GetLocalAddress(nsACString & _result) -{ - return NS_ERROR_NOT_AVAILABLE; -} - -NS_IMETHODIMP -HttpChannelChild::GetLocalPort(int32_t * _result) -{ - NS_ENSURE_ARG_POINTER(_result); - return NS_ERROR_NOT_AVAILABLE; -} - - //----------------------------------------------------------------------------- // HttpChannelChild::nsICacheInfoChannel //----------------------------------------------------------------------------- @@ -2395,20 +2458,31 @@ HttpChannelChild::DivertToParent(ChannelDiverterChild **aChild) MOZ_RELEASE_ASSERT(gNeckoChild); MOZ_RELEASE_ASSERT(!mDivertingToParent); + nsresult rv = NS_OK; + + // If the channel was intercepted, then we likely do not have an IPC actor + // yet. We need one, though, in order to have a parent side to divert to. + // Therefore, create the actor just in time for us to suspend and divert it. + if (mSynthesizedResponse && !RemoteChannelExists()) { + mSuspendParentAfterSynthesizeResponse = true; + rv = ContinueAsyncOpen(); + NS_ENSURE_SUCCESS(rv, rv); + } + // We must fail DivertToParent() if there's no parent end of the channel (and // won't be!) due to early failure. if (NS_FAILED(mStatus) && !RemoteChannelExists()) { return mStatus; } - nsresult rv = Suspend(); + // Once this is set, it should not be unset before the child is taken down. + mDivertingToParent = true; + + rv = Suspend(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - // Once this is set, it should not be unset before the child is taken down. - mDivertingToParent = true; - HttpChannelDiverterArgs args; args.mChannelChild() = this; args.mApplyConversion() = mApplyConversion; @@ -2541,9 +2615,12 @@ HttpChannelChild::OverrideWithSynthesizedResponse(nsAutoPtr& } NS_IMETHODIMP -HttpChannelChild::ForceIntercepted() +HttpChannelChild::ForceIntercepted(bool aPostRedirectChannelShouldIntercept, + bool aPostRedirectChannelShouldUpgrade) { mShouldParentIntercept = true; + mPostRedirectChannelShouldIntercept = aPostRedirectChannelShouldIntercept; + mPostRedirectChannelShouldUpgrade = aPostRedirectChannelShouldUpgrade; return NS_OK; } @@ -2573,5 +2650,35 @@ HttpChannelChild::RecvIssueDeprecationWarning(const uint32_t& warning, return true; } +bool +HttpChannelChild::ShouldInterceptURI(nsIChannel* aChannel, + nsIURI* aURI, + bool& aShouldUpgrade) +{ + bool isHttps = false; + nsresult rv = aURI->SchemeIs("https", &isHttps); + NS_ENSURE_SUCCESS(rv, false); + nsCOMPtr resultPrincipal; + if (!isHttps && mLoadInfo) { + nsContentUtils::GetSecurityManager()-> + GetChannelResultPrincipal(aChannel, getter_AddRefs(resultPrincipal)); + } + rv = NS_ShouldSecureUpgrade(aURI, + mLoadInfo, + resultPrincipal, + mPrivateBrowsing, + mAllowSTS, + aShouldUpgrade); + NS_ENSURE_SUCCESS(rv, false); + + nsCOMPtr upgradedURI; + if (aShouldUpgrade) { + rv = GetSecureUpgradedURI(aURI, getter_AddRefs(upgradedURI)); + NS_ENSURE_SUCCESS(rv, false); + } + + return ShouldIntercept(upgradedURI ? upgradedURI.get() : aURI); +} + } // namespace net } // namespace mozilla diff --git a/netwerk/protocol/http/HttpChannelChild.h b/netwerk/protocol/http/HttpChannelChild.h index 438c296af5..825f0837d1 100644 --- a/netwerk/protocol/http/HttpChannelChild.h +++ b/netwerk/protocol/http/HttpChannelChild.h @@ -86,10 +86,6 @@ public: NS_IMETHOD GetProtocolVersion(nsACString& aProtocolVersion) override; // nsIHttpChannelInternal NS_IMETHOD SetupFallbackChannel(const char *aFallbackKey) override; - NS_IMETHOD GetLocalAddress(nsACString& addr) override; - NS_IMETHOD GetLocalPort(int32_t* port) override; - NS_IMETHOD GetRemoteAddress(nsACString& addr) override; - NS_IMETHOD GetRemotePort(int32_t* port) override; NS_IMETHOD ForceIntercepted(uint64_t aInterceptionID) override; // nsISupportsPriority NS_IMETHOD SetPriority(int32_t value) override; @@ -168,7 +164,9 @@ private: void DoOnDataAvailable(nsIRequest* aRequest, nsISupports* aContext, nsIInputStream* aStream, uint64_t offset, uint32_t count); void DoPreOnStopRequest(nsresult aStatus); - void DoOnStopRequest(nsIRequest* aRequest, nsISupports* aContext); + void DoOnStopRequest(nsIRequest* aRequest, nsresult aChannelStatus, nsISupports* aContext); + + bool ShouldInterceptURI(nsIChannel* aChannel, nsIURI* aURI, bool& aShouldUpgrade); // Discard the prior interception and continue with the original network request. void ResetInterception(); @@ -230,10 +228,22 @@ private: // response to a channel using a different principal than the current one. bool mRedirectingForSubsequentSynthesizedResponse; + // Set if a manual redirect mode channel needs to be intercepted in the + // parent. + bool mPostRedirectChannelShouldIntercept; + // Set if a manual redirect mode channel needs to be upgraded to a secure URI + // when it's being considered for interception. Can only be true if + // mPostRedirectChannelShouldIntercept is true. + bool mPostRedirectChannelShouldUpgrade; + // Set if the corresponding parent channel should force an interception to occur // before the network transaction is initiated. bool mShouldParentIntercept; + // Set if the corresponding parent channel should suspend after a response + // is synthesized. + bool mSuspendParentAfterSynthesizeResponse; + // true after successful AsyncOpen until OnStopRequest completes. bool RemoteChannelExists() { return mIPCOpen && !mKeptAlive; } diff --git a/netwerk/protocol/http/HttpChannelParent.cpp b/netwerk/protocol/http/HttpChannelParent.cpp index e4d5371239..e5feab28b2 100644 --- a/netwerk/protocol/http/HttpChannelParent.cpp +++ b/netwerk/protocol/http/HttpChannelParent.cpp @@ -32,7 +32,6 @@ #include "nsIOService.h" #include "nsICachingChannel.h" #include "mozilla/LoadInfo.h" -#include "nsIHttpHeaderVisitor.h" #include "nsQueryObject.h" #include "mozilla/BasePrincipal.h" #include "nsCORSListenerProxy.h" @@ -61,11 +60,12 @@ HttpChannelParent::HttpChannelParent(const PBrowserOrId& iframeEmbedding, , mPBOverride(aOverrideStatus) , mLoadContext(aLoadContext) , mStatus(NS_OK) + , mPendingDiversion(false) , mDivertingFromChild(false) , mDivertedOnStartRequest(false) , mSuspendedForDiversion(false) - , mShouldIntercept(false) - , mShouldSuspendIntercept(false) + , mSuspendAfterSynthesizeResponse(false) + , mWillSynthesizeResponse(false) , mNestedFrameId(0) { LOG(("Creating HttpChannelParent [this=%p]\n", this)); @@ -106,9 +106,8 @@ HttpChannelParent::ActorDestroy(ActorDestroyReason why) // If this is an intercepted channel, we need to make sure that any resources are // cleaned up to avoid leaks. - if (mInterceptedChannel) { - mInterceptedChannel->Cancel(NS_ERROR_INTERCEPTION_FAILED); - mInterceptedChannel = nullptr; + if (mParentListener) { + mParentListener->ClearInterceptedChannel(); } } @@ -132,7 +131,9 @@ HttpChannelParent::Init(const HttpChannelCreationArgs& aArgs) a.loadInfo(), a.synthesizedResponseHead(), a.synthesizedSecurityInfoSerialization(), a.cacheKey(), a.schedulingContextID(), a.preflightArgs(), - a.initialRwin(), a.allowStaleCacheContent()); + a.initialRwin(), + a.suspendAfterSynthesizeResponse(), + a.allowStaleCacheContent()); } case HttpChannelCreationArgs::THttpChannelConnectArgs: { @@ -149,127 +150,23 @@ HttpChannelParent::Init(const HttpChannelCreationArgs& aArgs) // HttpChannelParent::nsISupports //----------------------------------------------------------------------------- -NS_IMPL_ISUPPORTS(HttpChannelParent, - nsIInterfaceRequestor, - nsIProgressEventSink, - nsIRequestObserver, - nsIStreamListener, - nsIPackagedAppChannelListener, - nsIParentChannel, - nsIAuthPromptProvider, - nsIParentRedirectingChannel, - nsINetworkInterceptController, - nsIDeprecationWarner) - -NS_IMETHODIMP -HttpChannelParent::ShouldPrepareForIntercept(nsIURI* aURI, - bool aIsNonSubresourceRequest, - bool* aShouldIntercept) -{ - *aShouldIntercept = mShouldIntercept; - return NS_OK; -} - -class HeaderVisitor final : public nsIHttpHeaderVisitor -{ - nsCOMPtr mChannel; - ~HeaderVisitor() - { - } -public: - explicit HeaderVisitor(nsIInterceptedChannel* aChannel) : mChannel(aChannel) - { - } - - NS_DECL_ISUPPORTS - - NS_IMETHOD VisitHeader(const nsACString& aHeader, const nsACString& aValue) override - { - mChannel->SynthesizeHeader(aHeader, aValue); - return NS_OK; - } -}; - -NS_IMPL_ISUPPORTS(HeaderVisitor, nsIHttpHeaderVisitor) - -class FinishSynthesizedResponse : public nsRunnable -{ - nsCOMPtr mChannel; -public: - explicit FinishSynthesizedResponse(nsIInterceptedChannel* aChannel) - : mChannel(aChannel) - { - } - - NS_IMETHOD Run() - { - // The URL passed as an argument here doesn't matter, since the child will - // receive a redirection notification as a result of this synthesized response. - mChannel->FinishSynthesizedResponse(EmptyCString()); - return NS_OK; - } -}; - -class ResponseSynthesizer final : public nsIFetchEventDispatcher -{ -public: - ResponseSynthesizer(nsIInterceptedChannel* aChannel, - HttpChannelParent* aParentChannel) - : mChannel(aChannel) - , mParentChannel(aParentChannel) - { - } - - NS_DECL_ISUPPORTS - NS_DECL_NSIFETCHEVENTDISPATCHER - -private: - ~ResponseSynthesizer() - { - } - - nsCOMPtr mChannel; - RefPtr mParentChannel; -}; - -NS_IMPL_ISUPPORTS(ResponseSynthesizer, nsIFetchEventDispatcher) - -NS_IMETHODIMP -ResponseSynthesizer::Dispatch() -{ - mParentChannel->SynthesizeResponse(mChannel); - - return NS_OK; -} - -NS_IMETHODIMP -HttpChannelParent::ChannelIntercepted(nsIInterceptedChannel* aChannel, - nsIFetchEventDispatcher** aDispatcher) -{ - RefPtr dispatcher = - new ResponseSynthesizer(aChannel, this); - dispatcher.forget(aDispatcher); - return NS_OK; -} - -void -HttpChannelParent::SynthesizeResponse(nsIInterceptedChannel* aChannel) -{ - if (mShouldSuspendIntercept) { - mInterceptedChannel = aChannel; - return; - } - - aChannel->SynthesizeStatus(mSynthesizedResponseHead->Status(), - mSynthesizedResponseHead->StatusText()); - nsCOMPtr visitor = new HeaderVisitor(aChannel); - mSynthesizedResponseHead->Headers().VisitHeaders(visitor); - - nsCOMPtr event = new FinishSynthesizedResponse(aChannel); - NS_DispatchToCurrentThread(event); - - mSynthesizedResponseHead = nullptr; -} +NS_IMPL_ADDREF(HttpChannelParent) +NS_IMPL_RELEASE(HttpChannelParent) +NS_INTERFACE_MAP_BEGIN(HttpChannelParent) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink) + NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) + NS_INTERFACE_MAP_ENTRY(nsIStreamListener) + NS_INTERFACE_MAP_ENTRY(nsIPackagedAppChannelListener) + NS_INTERFACE_MAP_ENTRY(nsIParentChannel) + NS_INTERFACE_MAP_ENTRY(nsIAuthPromptProvider) + NS_INTERFACE_MAP_ENTRY(nsIParentRedirectingChannel) + NS_INTERFACE_MAP_ENTRY(nsIDeprecationWarner) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIParentRedirectingChannel) + if (aIID.Equals(NS_GET_IID(HttpChannelParent))) { + foundInterface = static_cast(this); + } else +NS_INTERFACE_MAP_END //----------------------------------------------------------------------------- // HttpChannelParent::nsIInterfaceRequestor @@ -369,6 +266,7 @@ HttpChannelParent::DoAsyncOpen( const URIParams& aURI, const nsCString& aSchedulingContextID, const OptionalCorsPreflightArgs& aCorsPreflightArgs, const uint32_t& aInitialRwin, + const bool& aSuspendAfterSynthesizeResponse, const bool& aAllowStaleCacheContent) { nsCOMPtr uri = DeserializeURI(aURI); @@ -490,8 +388,8 @@ HttpChannelParent::DoAsyncOpen( const URIParams& aURI, mChannel->SetAllowStaleCacheContent(aAllowStaleCacheContent); if (aSynthesizedResponseHead.type() == OptionalHttpResponseHead::TnsHttpResponseHead) { - mSynthesizedResponseHead = new nsHttpResponseHead(aSynthesizedResponseHead.get_nsHttpResponseHead()); - mShouldIntercept = true; + mParentListener->SetupInterception(aSynthesizedResponseHead.get_nsHttpResponseHead()); + mWillSynthesizeResponse = true; mChannel->SetCouldBeSynthesized(); if (!aSecurityInfoSerialization.IsEmpty()) { @@ -577,6 +475,8 @@ HttpChannelParent::DoAsyncOpen( const URIParams& aURI, schedulingContextID.Parse(aSchedulingContextID.BeginReading()); mChannel->SetSchedulingContextID(schedulingContextID); + mSuspendAfterSynthesizeResponse = aSuspendAfterSynthesizeResponse; + if (loadInfo && loadInfo->GetEnforceSecurity()) { rv = mChannel->AsyncOpen2(mParentListener); } @@ -601,12 +501,11 @@ HttpChannelParent::ConnectChannel(const uint32_t& channelId, const bool& shouldI mChannel = static_cast(channel.get()); LOG((" found channel %p, rv=%08x", mChannel.get(), rv)); - mShouldIntercept = shouldIntercept; - if (mShouldIntercept) { - // When an interception occurs, this channel should suspend all further activity. - // It will be torn down and recreated if necessary. - mShouldSuspendIntercept = true; - } + nsCOMPtr controller; + NS_QueryNotificationCallbacks(channel, controller); + RefPtr parentListener = do_QueryObject(controller); + MOZ_ASSERT(parentListener); + parentListener->SetupInterceptionAfterRedirect(shouldIntercept); if (mPBOverride != kPBOverride_Unset) { // redirected-to channel may not support PB @@ -1036,6 +935,43 @@ HttpChannelParent::DivertComplete() mParentListener = nullptr; } +void +HttpChannelParent::MaybeFlushPendingDiversion() +{ + if (!mPendingDiversion) { + return; + } + + mPendingDiversion = false; + + nsresult rv = SuspendForDiversion(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + if (mDivertListener) { + DivertTo(mDivertListener); + } + + return; +} + +void +HttpChannelParent::ResponseSynthesized() +{ + // Suspend now even though the FinishSynthesizeResponse runnable has + // not executed. We want to suspend after we get far enough to trigger + // the synthesis, but not actually allow the nsHttpChannel to trigger + // any OnStartRequests(). + if (mSuspendAfterSynthesizeResponse) { + mChannel->Suspend(); + } + + mWillSynthesizeResponse = false; + + MaybeFlushPendingDiversion(); +} + bool HttpChannelParent::RecvRemoveCorsPreflightCacheEntry(const URIParams& uri, const mozilla::ipc::PrincipalInfo& requestingPrincipal) @@ -1379,16 +1315,35 @@ HttpChannelParent::SuspendForDiversion() LOG(("HttpChannelParent::SuspendForDiversion [this=%p]\n", this)); MOZ_ASSERT(mChannel); MOZ_ASSERT(mParentListener); + + // If we're in the process of opening a synthesized response, we must wait + // to perform the diversion. Some of our diversion listeners clear callbacks + // which breaks the synthesis process. + if (mWillSynthesizeResponse) { + mPendingDiversion = true; + return NS_OK; + } + if (NS_WARN_IF(mDivertingFromChild)) { MOZ_ASSERT(!mDivertingFromChild, "Already suspended for diversion!"); return NS_ERROR_UNEXPECTED; } + nsresult rv = NS_OK; + // Try suspending the channel. Allow it to fail, since OnStopRequest may have - // been called and thus the channel may not be pending. - nsresult rv = mChannel->Suspend(); - MOZ_ASSERT(NS_SUCCEEDED(rv) || rv == NS_ERROR_NOT_AVAILABLE); - mSuspendedForDiversion = NS_SUCCEEDED(rv); + // been called and thus the channel may not be pending. If we've already + // automatically suspended after synthesizing the response, then we don't + // need to suspend again here. + if (!mSuspendAfterSynthesizeResponse) { + rv = mChannel->Suspend(); + MOZ_ASSERT(NS_SUCCEEDED(rv) || rv == NS_ERROR_NOT_AVAILABLE); + mSuspendedForDiversion = NS_SUCCEEDED(rv); + } else { + // Take ownership of the automatic suspend that occurred after synthesizing + // the response. + mSuspendedForDiversion = true; + } rv = mParentListener->SuspendForDiversion(); MOZ_ASSERT(NS_SUCCEEDED(rv)); @@ -1435,6 +1390,18 @@ HttpChannelParent::DivertTo(nsIStreamListener *aListener) LOG(("HttpChannelParent::DivertTo [this=%p aListener=%p]\n", this, aListener)); MOZ_ASSERT(mParentListener); + + // If we're in the process of opening a synthesized response, we must wait + // to perform the diversion. Some of our diversion listeners clear callbacks + // which breaks the synthesis process. + if (mWillSynthesizeResponse) { + // We should already have started pending the diversion when + // SuspendForDiversion() was called. + MOZ_ASSERT(mPendingDiversion); + mDivertListener = aListener; + return; + } + if (NS_WARN_IF(!mDivertingFromChild)) { MOZ_ASSERT(mDivertingFromChild, "Cannot DivertTo new listener if diverting is not set!"); diff --git a/netwerk/protocol/http/HttpChannelParent.h b/netwerk/protocol/http/HttpChannelParent.h index a66ff67b42..662046a3fd 100644 --- a/netwerk/protocol/http/HttpChannelParent.h +++ b/netwerk/protocol/http/HttpChannelParent.h @@ -20,13 +20,16 @@ #include "nsHttpChannel.h" #include "nsIAuthPromptProvider.h" #include "mozilla/dom/ipc/IdType.h" -#include "nsINetworkInterceptController.h" #include "nsIDeprecationWarner.h" #include "nsIPackagedAppChannelListener.h" class nsICacheEntry; class nsIAssociatedContentSecurity; +#define HTTP_CHANNEL_PARENT_IID \ + { 0x982b2372, 0x7aa5, 0x4e8a, \ + { 0xbd, 0x9f, 0x89, 0x74, 0xd7, 0xf0, 0x58, 0xeb } } + namespace mozilla { namespace dom{ @@ -38,13 +41,16 @@ namespace net { class HttpChannelParentListener; -class HttpChannelParent final : public PHttpChannelParent +// Note: nsIInterfaceRequestor must be the first base so that do_QueryObject() +// works correctly on this object, as it's needed to compute a void* pointing to +// the beginning of this object. + +class HttpChannelParent final : public nsIInterfaceRequestor + , public PHttpChannelParent , public nsIParentRedirectingChannel , public nsIProgressEventSink - , public nsIInterfaceRequestor , public ADivertableParentChannel , public nsIAuthPromptProvider - , public nsINetworkInterceptController , public nsIDeprecationWarner , public DisconnectableParent , public nsIPackagedAppChannelListener @@ -62,9 +68,10 @@ public: NS_DECL_NSIPROGRESSEVENTSINK NS_DECL_NSIINTERFACEREQUESTOR NS_DECL_NSIAUTHPROMPTPROVIDER - NS_DECL_NSINETWORKINTERCEPTCONTROLLER NS_DECL_NSIDEPRECATIONWARNER + NS_DECLARE_STATIC_IID_ACCESSOR(HTTP_CHANNEL_PARENT_IID) + HttpChannelParent(const dom::PBrowserOrId& iframeEmbedding, nsILoadContext* aLoadContext, PBOverrideStatus aStatus); @@ -129,6 +136,7 @@ protected: const nsCString& aSchedulingContextID, const OptionalCorsPreflightArgs& aCorsPreflightArgs, const uint32_t& aInitialRwin, + const bool& aSuspendAfterSynthesizeResponse, const bool& aAllowStaleCacheContent); virtual bool RecvSetPriority(const uint16_t& priority) override; @@ -178,13 +186,12 @@ private: const uint32_t& count); void DivertOnStopRequest(const nsresult& statusCode); void DivertComplete(); - - void SynthesizeResponse(nsIInterceptedChannel* aChannel); + void MaybeFlushPendingDiversion(); + void ResponseSynthesized(); friend class DivertDataAvailableEvent; friend class DivertStopRequestEvent; friend class DivertCompleteEvent; - friend class ResponseSynthesizer; RefPtr mChannel; nsCOMPtr mCacheEntry; @@ -213,13 +220,15 @@ private: nsCOMPtr mLoadContext; RefPtr mHttpHandler; - nsAutoPtr mSynthesizedResponseHead; - RefPtr mParentListener; - // This is listener we are diverting to. + // The listener we are diverting to or will divert to if mPendingDiversion + // is set. nsCOMPtr mDivertListener; // Set to the canceled status value if the main channel was canceled. nsresult mStatus; + // Indicates that diversion has been requested, but we could not start it + // yet because the channel is still being opened with a synthesized response. + bool mPendingDiversion; // Once set, no OnStart/OnData/OnStop calls should be accepted; conversely, it // must be set when RecvDivertOnData/~DivertOnStop/~DivertComplete are // received from the child channel. @@ -230,19 +239,19 @@ private: bool mSuspendedForDiversion; - // Set if this channel should be intercepted before it sets up the HTTP transaction. - bool mShouldIntercept : 1; - // Set if this channel should suspend on interception. - bool mShouldSuspendIntercept : 1; + // Set if this channel should be suspended after synthesizing a response. + bool mSuspendAfterSynthesizeResponse; + // Set if this channel will synthesize its response. + bool mWillSynthesizeResponse; dom::TabId mNestedFrameId; - // Handle to the channel wrapper if this channel has been intercepted. - nsCOMPtr mInterceptedChannel; - RefPtr mEventQ; }; +NS_DEFINE_STATIC_IID_ACCESSOR(HttpChannelParent, + HTTP_CHANNEL_PARENT_IID) + } // namespace net } // namespace mozilla diff --git a/netwerk/protocol/http/HttpChannelParentListener.cpp b/netwerk/protocol/http/HttpChannelParentListener.cpp index e924c4b921..b25298c0fb 100644 --- a/netwerk/protocol/http/HttpChannelParentListener.cpp +++ b/netwerk/protocol/http/HttpChannelParentListener.cpp @@ -13,6 +13,7 @@ #include "nsIRedirectChannelRegistrar.h" #include "nsIHttpEventSink.h" #include "nsIPackagedAppChannelListener.h" +#include "nsIHttpHeaderVisitor.h" using mozilla::Unused; @@ -23,6 +24,8 @@ HttpChannelParentListener::HttpChannelParentListener(HttpChannelParent* aInitial : mNextListener(aInitialChannel) , mRedirectChannelId(0) , mSuspendedForDiversion(false) + , mShouldIntercept(false) + , mShouldSuspendIntercept(false) { } @@ -34,13 +37,21 @@ HttpChannelParentListener::~HttpChannelParentListener() // HttpChannelParentListener::nsISupports //----------------------------------------------------------------------------- -NS_IMPL_ISUPPORTS(HttpChannelParentListener, - nsIInterfaceRequestor, - nsIStreamListener, - nsIRequestObserver, - nsIChannelEventSink, - nsIPackagedAppChannelListener, - nsIRedirectResultListener) +NS_IMPL_ADDREF(HttpChannelParentListener) +NS_IMPL_RELEASE(HttpChannelParentListener) +NS_INTERFACE_MAP_BEGIN(HttpChannelParentListener) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY(nsIStreamListener) + NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) + NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink) + NS_INTERFACE_MAP_ENTRY(nsIPackagedAppChannelListener) + NS_INTERFACE_MAP_ENTRY(nsIRedirectResultListener) + NS_INTERFACE_MAP_ENTRY(nsINetworkInterceptController) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInterfaceRequestor) + if (aIID.Equals(NS_GET_IID(HttpChannelParentListener))) { + foundInterface = static_cast(this); + } else +NS_INTERFACE_MAP_END //----------------------------------------------------------------------------- // HttpChannelParentListener::nsIRequestObserver @@ -124,6 +135,7 @@ HttpChannelParentListener::GetInterface(const nsIID& aIID, void **result) { if (aIID.Equals(NS_GET_IID(nsIChannelEventSink)) || aIID.Equals(NS_GET_IID(nsIHttpEventSink)) || + aIID.Equals(NS_GET_IID(nsINetworkInterceptController)) || aIID.Equals(NS_GET_IID(nsIRedirectResultListener))) { return QueryInterface(aIID, result); @@ -246,6 +258,125 @@ HttpChannelParentListener::OnRedirectResult(bool succeeded) return NS_OK; } +//----------------------------------------------------------------------------- +// HttpChannelParentListener::nsINetworkInterceptController +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +HttpChannelParentListener::ShouldPrepareForIntercept(nsIURI* aURI, + bool aIsNonSubresourceRequest, + bool* aShouldIntercept) +{ + *aShouldIntercept = mShouldIntercept; + return NS_OK; +} + +class HeaderVisitor final : public nsIHttpHeaderVisitor +{ + nsCOMPtr mChannel; + ~HeaderVisitor() + { + } +public: + explicit HeaderVisitor(nsIInterceptedChannel* aChannel) : mChannel(aChannel) + { + } + + NS_DECL_ISUPPORTS + + NS_IMETHOD VisitHeader(const nsACString& aHeader, const nsACString& aValue) override + { + mChannel->SynthesizeHeader(aHeader, aValue); + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(HeaderVisitor, nsIHttpHeaderVisitor) + +class FinishSynthesizedResponse : public nsRunnable +{ + nsCOMPtr mChannel; +public: + explicit FinishSynthesizedResponse(nsIInterceptedChannel* aChannel) + : mChannel(aChannel) + { + } + + NS_IMETHOD Run() + { + // The URL passed as an argument here doesn't matter, since the child will + // receive a redirection notification as a result of this synthesized response. + mChannel->FinishSynthesizedResponse(EmptyCString()); + return NS_OK; + } +}; + +class ResponseSynthesizer final : public nsIFetchEventDispatcher +{ +public: + ResponseSynthesizer(nsIInterceptedChannel* aChannel, + HttpChannelParentListener* aParentListener) + : mChannel(aChannel) + , mParentListener(aParentListener) + { + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIFETCHEVENTDISPATCHER + +private: + ~ResponseSynthesizer() + { + } + + nsCOMPtr mChannel; + RefPtr mParentListener; +}; + +NS_IMPL_ISUPPORTS(ResponseSynthesizer, nsIFetchEventDispatcher) + +NS_IMETHODIMP +ResponseSynthesizer::Dispatch() +{ + mParentListener->SynthesizeResponse(mChannel); + + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelParentListener::ChannelIntercepted(nsIInterceptedChannel* aChannel, + nsIFetchEventDispatcher** aDispatcher) +{ + RefPtr dispatcher = + new ResponseSynthesizer(aChannel, this); + dispatcher.forget(aDispatcher); + return NS_OK; +} + +void +HttpChannelParentListener::SynthesizeResponse(nsIInterceptedChannel* aChannel) +{ + if (mShouldSuspendIntercept) { + mInterceptedChannel = aChannel; + return; + } + + aChannel->SynthesizeStatus(mSynthesizedResponseHead->Status(), + mSynthesizedResponseHead->StatusText()); + nsCOMPtr visitor = new HeaderVisitor(aChannel); + mSynthesizedResponseHead->Headers().VisitHeaders(visitor); + + nsCOMPtr event = new FinishSynthesizedResponse(aChannel); + NS_DispatchToCurrentThread(event); + + mSynthesizedResponseHead = nullptr; + + MOZ_ASSERT(mNextListener); + RefPtr channel = do_QueryObject(mNextListener); + MOZ_ASSERT(channel); + channel->ResponseSynthesized(); +} + //----------------------------------------------------------------------------- nsresult @@ -285,5 +416,32 @@ HttpChannelParentListener::DivertTo(nsIStreamListener* aListener) return ResumeForDiversion(); } +void +HttpChannelParentListener::SetupInterception(const nsHttpResponseHead& aResponseHead) +{ + mSynthesizedResponseHead = new nsHttpResponseHead(aResponseHead); + mShouldIntercept = true; +} + +void +HttpChannelParentListener::SetupInterceptionAfterRedirect(bool aShouldIntercept) +{ + mShouldIntercept = aShouldIntercept; + if (mShouldIntercept) { + // When an interception occurs, this channel should suspend all further activity. + // It will be torn down and recreated if necessary. + mShouldSuspendIntercept = true; + } +} + +void +HttpChannelParentListener::ClearInterceptedChannel() +{ + if (mInterceptedChannel) { + mInterceptedChannel->Cancel(NS_ERROR_INTERCEPTION_FAILED); + mInterceptedChannel = nullptr; + } +} + } // namespace net } // namespace mozilla diff --git a/netwerk/protocol/http/HttpChannelParentListener.h b/netwerk/protocol/http/HttpChannelParentListener.h index d73050c49b..00475220b3 100644 --- a/netwerk/protocol/http/HttpChannelParentListener.h +++ b/netwerk/protocol/http/HttpChannelParentListener.h @@ -12,17 +12,27 @@ #include "nsIChannelEventSink.h" #include "nsIRedirectResultListener.h" #include "nsIPackagedAppChannelListener.h" +#include "nsINetworkInterceptController.h" namespace mozilla { namespace net { class HttpChannelParent; +#define HTTP_CHANNEL_PARENT_LISTENER_IID \ + { 0xe409da52, 0xda76, 0x4eb7, \ + { 0xa7, 0xf4, 0x03, 0x3d, 0x88, 0xac, 0x87, 0x6d } } + +// Note: nsIInterfaceRequestor must be the first base so that do_QueryObject() +// works correctly on this object, as it's needed to compute a void* pointing to +// the beginning of this object. + class HttpChannelParentListener final : public nsIInterfaceRequestor , public nsIChannelEventSink , public nsIRedirectResultListener , public nsIPackagedAppChannelListener , public nsIStreamListener + , public nsINetworkInterceptController { public: NS_DECL_ISUPPORTS @@ -32,6 +42,9 @@ public: NS_DECL_NSIREDIRECTRESULTLISTENER NS_DECL_NSIREQUESTOBSERVER NS_DECL_NSISTREAMLISTENER + NS_DECL_NSINETWORKINTERCEPTCONTROLLER + + NS_DECLARE_STATIC_IID_ACCESSOR(HTTP_CHANNEL_PARENT_LISTENER_IID) explicit HttpChannelParentListener(HttpChannelParent* aInitialChannel); @@ -39,12 +52,19 @@ public: nsresult DivertTo(nsIStreamListener *aListener); nsresult SuspendForDiversion(); + void SetupInterception(const nsHttpResponseHead& aResponseHead); + void SetupInterceptionAfterRedirect(bool aShouldIntercept); + void ClearInterceptedChannel(); + private: virtual ~HttpChannelParentListener(); // Private partner function to SuspendForDiversion. nsresult ResumeForDiversion(); + void SynthesizeResponse(nsIInterceptedChannel* aChannel); + friend class ResponseSynthesizer; + // Can be the original HttpChannelParent that created this object (normal // case), a different {HTTP|FTP}ChannelParent that we've been redirected to, // or some other listener that we have been diverted to via @@ -53,8 +73,21 @@ private: uint32_t mRedirectChannelId; // When set, no OnStart/OnData/OnStop calls should be received. bool mSuspendedForDiversion; + + // Set if this channel should be intercepted before it sets up the HTTP transaction. + bool mShouldIntercept; + // Set if this channel should suspend on interception. + bool mShouldSuspendIntercept; + + nsAutoPtr mSynthesizedResponseHead; + + // Handle to the channel wrapper if this channel has been intercepted. + nsCOMPtr mInterceptedChannel; }; +NS_DEFINE_STATIC_IID_ACCESSOR(HttpChannelParentListener, + HTTP_CHANNEL_PARENT_LISTENER_IID) + } // namespace net } // namespace mozilla diff --git a/netwerk/protocol/http/InterceptedChannel.cpp b/netwerk/protocol/http/InterceptedChannel.cpp index e2ddf0f89f..3ac0202b77 100644 --- a/netwerk/protocol/http/InterceptedChannel.cpp +++ b/netwerk/protocol/http/InterceptedChannel.cpp @@ -126,6 +126,21 @@ InterceptedChannelBase::SetReleaseHandle(nsISupports* aHandle) return NS_OK; } +/* static */ +already_AddRefed +InterceptedChannelBase::SecureUpgradeChannelURI(nsIChannel* aChannel) +{ + nsCOMPtr uri; + nsresult rv = aChannel->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, nullptr); + + nsCOMPtr upgradedURI; + rv = HttpBaseChannel::GetSecureUpgradedURI(uri, getter_AddRefs(upgradedURI)); + NS_ENSURE_SUCCESS(rv, nullptr); + + return upgradedURI.forget(); +} + InterceptedChannelChrome::InterceptedChannelChrome(nsHttpChannel* aChannel, nsINetworkInterceptController* aController, nsICacheEntry* aEntry) @@ -320,12 +335,20 @@ InterceptedChannelChrome::GetInternalContentPolicyType(nsContentPolicyType* aPol return NS_OK; } +NS_IMETHODIMP +InterceptedChannelChrome::GetSecureUpgradedChannelURI(nsIURI** aURI) +{ + return mChannel->GetURI(aURI); +} + InterceptedChannelContent::InterceptedChannelContent(HttpChannelChild* aChannel, nsINetworkInterceptController* aController, - InterceptStreamListener* aListener) + InterceptStreamListener* aListener, + bool aSecureUpgrade) : InterceptedChannelBase(aController) , mChannel(aChannel) , mStreamListener(aListener) +, mSecureUpgrade(aSecureUpgrade) { } @@ -403,6 +426,10 @@ InterceptedChannelContent::FinishSynthesizedResponse(const nsACString& aFinalURL if (!aFinalURLSpec.IsEmpty()) { nsresult rv = NS_NewURI(getter_AddRefs(responseURI), aFinalURLSpec); NS_ENSURE_SUCCESS(rv, rv); + } else if (mSecureUpgrade) { + nsresult rv = HttpBaseChannel::GetSecureUpgradedURI(originalURI, + getter_AddRefs(responseURI)); + NS_ENSURE_SUCCESS(rv, rv); } else { responseURI = originalURI; } @@ -469,5 +496,22 @@ InterceptedChannelContent::GetInternalContentPolicyType(nsContentPolicyType* aPo return NS_OK; } +NS_IMETHODIMP +InterceptedChannelContent::GetSecureUpgradedChannelURI(nsIURI** aURI) +{ + nsCOMPtr uri; + if (mSecureUpgrade) { + uri = SecureUpgradeChannelURI(mChannel); + } else { + nsresult rv = mChannel->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + } + if (uri) { + uri.forget(aURI); + return NS_OK; + } + return NS_ERROR_FAILURE; +} + } // namespace net } // namespace mozilla diff --git a/netwerk/protocol/http/InterceptedChannel.h b/netwerk/protocol/http/InterceptedChannel.h index 61e196c545..eec5d420ab 100644 --- a/netwerk/protocol/http/InterceptedChannel.h +++ b/netwerk/protocol/http/InterceptedChannel.h @@ -57,6 +57,9 @@ public: NS_IMETHOD GetResponseBody(nsIOutputStream** aOutput) override; NS_IMETHOD GetConsoleReportCollector(nsIConsoleReportCollector** aCollectorOut) override; NS_IMETHOD SetReleaseHandle(nsISupports* aHandle) override; + + static already_AddRefed + SecureUpgradeChannelURI(nsIChannel* aChannel); }; class InterceptedChannelChrome : public InterceptedChannelBase @@ -80,6 +83,7 @@ public: NS_IMETHOD ResetInterception() override; NS_IMETHOD FinishSynthesizedResponse(const nsACString& aFinalURLSpec) override; NS_IMETHOD GetChannel(nsIChannel** aChannel) override; + NS_IMETHOD GetSecureUpgradedChannelURI(nsIURI** aURI) override; NS_IMETHOD SynthesizeStatus(uint16_t aStatus, const nsACString& aReason) override; NS_IMETHOD SynthesizeHeader(const nsACString& aName, const nsACString& aValue) override; NS_IMETHOD Cancel(nsresult aStatus) override; @@ -100,14 +104,19 @@ class InterceptedChannelContent : public InterceptedChannelBase // Listener for the synthesized response to fix up the notifications before they reach // the actual channel. RefPtr mStreamListener; + + // Set for intercepted channels that have gone through a secure upgrade. + bool mSecureUpgrade; public: InterceptedChannelContent(HttpChannelChild* aChannel, nsINetworkInterceptController* aController, - InterceptStreamListener* aListener); + InterceptStreamListener* aListener, + bool aSecureUpgrade); NS_IMETHOD ResetInterception() override; NS_IMETHOD FinishSynthesizedResponse(const nsACString& aFinalURLSpec) override; NS_IMETHOD GetChannel(nsIChannel** aChannel) override; + NS_IMETHOD GetSecureUpgradedChannelURI(nsIURI** aURI) override; NS_IMETHOD SynthesizeStatus(uint16_t aStatus, const nsACString& aReason) override; NS_IMETHOD SynthesizeHeader(const nsACString& aName, const nsACString& aValue) override; NS_IMETHOD Cancel(nsresult aStatus) override; diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index 7d0f9b8dd7..e16593ec34 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -7,7 +7,6 @@ // HttpLog.h should generally be included first #include "HttpLog.h" -#include "mozilla/dom/nsCSPUtils.h" #include "mozilla/dom/nsCSPContext.h" #include "nsHttp.h" #include "nsHttpChannel.h" @@ -56,7 +55,6 @@ #include "nsIClassOfService.h" #include "nsIPermissionManager.h" #include "nsIPrincipal.h" -#include "nsIScriptError.h" #include "nsIScriptSecurityManager.h" #include "nsISSLStatus.h" #include "nsISSLStatusProvider.h" @@ -303,91 +301,24 @@ nsHttpChannel::Connect() NS_ENSURE_SUCCESS(rv, rv); } - // Even if we're in private browsing mode, we still enforce existing STS - // data (it is read-only). - // if the connection is not using SSL and either the exact host matches or - // a superdomain wants to force HTTPS, do it. bool isHttps = false; rv = mURI->SchemeIs("https", &isHttps); NS_ENSURE_SUCCESS(rv,rv); - - if (!isHttps) { - // If any of the documents up the chain to the root doucment makes use of - // the CSP directive 'upgrade-insecure-requests', then it's time to fulfill - // the promise to CSP and mixed content blocking to upgrade the channel - // from http to https. - if (mLoadInfo) { - bool isPreload = - (mLoadInfo->InternalContentPolicyType() == nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD || - mLoadInfo->InternalContentPolicyType() == nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD || - mLoadInfo->InternalContentPolicyType() == nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD); - bool upgradeRequests = - ((isPreload && mLoadInfo->GetUpgradeInsecurePreloads()) || - (mLoadInfo->GetUpgradeInsecureRequests())); - - // Please note that cross origin top level navigations are not subject - // to upgrade-insecure-requests, see: - // http://www.w3.org/TR/upgrade-insecure-requests/#examples - nsCOMPtr resultPrincipal; - nsContentUtils::GetSecurityManager()-> - GetChannelResultPrincipal(this, getter_AddRefs(resultPrincipal)); - bool crossOriginNavigation = - (mLoadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_DOCUMENT) && - (!resultPrincipal->Equals(mLoadInfo->LoadingPrincipal())); - - if (upgradeRequests && !crossOriginNavigation) { - // let's log a message to the console that we are upgrading a request - nsAutoCString spec, scheme; - mURI->GetSpec(spec); - mURI->GetScheme(scheme); - // append the additional 's' for security to the scheme :-) - scheme.AppendASCII("s"); - NS_ConvertUTF8toUTF16 reportSpec(spec); - NS_ConvertUTF8toUTF16 reportScheme(scheme); - - const char16_t* params[] = { reportSpec.get(), reportScheme.get() }; - uint32_t innerWindowId = mLoadInfo ? mLoadInfo->GetInnerWindowID() : 0; - CSP_LogLocalizedStr(MOZ_UTF16("upgradeInsecureRequest"), - params, ArrayLength(params), - EmptyString(), // aSourceFile - EmptyString(), // aScriptSample - 0, // aLineNumber - 0, // aColumnNumber - nsIScriptError::warningFlag, "CSP", - innerWindowId); - - //Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 4); - return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps); - } - } - - // enforce Strict-Transport-Security - nsISiteSecurityService* sss = gHttpHandler->GetSSService(); - NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY); - - bool isStsHost = false; - uint32_t flags = mPrivateBrowsing ? nsISocketProvider::NO_PERMANENT_STORAGE : 0; - rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, mURI, flags, - &isStsHost); - - // if the SSS check fails, it's likely because this load is on a - // malformed URI or something else in the setup is wrong, so any error - // should be reported. - NS_ENSURE_SUCCESS(rv, rv); - - if (isStsHost) { - LOG(("nsHttpChannel::Connect() STS permissions found\n")); - if (mAllowSTS) { - //Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 3); - return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps); - } else { - //Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 2); - } - } else { - //Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 1); - } - } else { - //Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 0); + nsCOMPtr resultPrincipal; + if (!isHttps && mLoadInfo) { + nsContentUtils::GetSecurityManager()-> + GetChannelResultPrincipal(this, getter_AddRefs(resultPrincipal)); + } + bool shouldUpgrade = false; + rv = NS_ShouldSecureUpgrade(mURI, + mLoadInfo, + resultPrincipal, + mPrivateBrowsing, + mAllowSTS, + shouldUpgrade); + NS_ENSURE_SUCCESS(rv, rv); + if (shouldUpgrade) { + return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps); } // ensure that we are using a valid hostname @@ -1987,30 +1918,12 @@ nsHttpChannel::HandleAsyncRedirectChannelToHttps() nsresult nsHttpChannel::StartRedirectChannelToHttps() { - nsresult rv = NS_OK; LOG(("nsHttpChannel::HandleAsyncRedirectChannelToHttps() [STS]\n")); nsCOMPtr upgradedURI; - - rv = mURI->Clone(getter_AddRefs(upgradedURI)); + nsresult rv = GetSecureUpgradedURI(mURI, getter_AddRefs(upgradedURI)); NS_ENSURE_SUCCESS(rv,rv); - upgradedURI->SetScheme(NS_LITERAL_CSTRING("https")); - - int32_t oldPort = -1; - rv = mURI->GetPort(&oldPort); - if (NS_FAILED(rv)) return rv; - - // Keep any nonstandard ports so only the scheme is changed. - // For example: - // http://foo.com:80 -> https://foo.com:443 - // http://foo.com:81 -> https://foo.com:81 - - if (oldPort == 80 || oldPort == -1) - upgradedURI->SetPort(-1); - else - upgradedURI->SetPort(oldPort); - return StartRedirectChannelToURI(upgradedURI, nsIChannelEventSink::REDIRECT_PERMANENT | nsIChannelEventSink::REDIRECT_STS_UPGRADE); @@ -2063,21 +1976,12 @@ nsHttpChannel::StartRedirectChannelToURI(nsIURI *upgradedURI, uint32_t flags) // Inform consumers about this fake redirect mRedirectChannel = newChannel; - if (!(flags & nsIChannelEventSink::REDIRECT_STS_UPGRADE)) { - // Ensure that internally-redirected channels cannot be intercepted, which would look - // like two separate requests to the nsINetworkInterceptController. - if (mInterceptCache == INTERCEPTED) { - nsCOMPtr httpRedirect = do_QueryInterface(mRedirectChannel); - if (httpRedirect) { - httpRedirect->ForceIntercepted(mInterceptionID); - } - } else { - nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL; - rv = mRedirectChannel->GetLoadFlags(&loadFlags); - NS_ENSURE_SUCCESS(rv, rv); - loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER; - rv = mRedirectChannel->SetLoadFlags(loadFlags); - NS_ENSURE_SUCCESS(rv, rv); + if (!(flags & nsIChannelEventSink::REDIRECT_STS_UPGRADE) && + mInterceptCache == INTERCEPTED) { + // Mark the channel as intercepted in order to propagate the response URL. + nsCOMPtr httpRedirect = do_QueryInterface(mRedirectChannel); + if (httpRedirect) { + httpRedirect->ForceIntercepted(mInterceptionID); } } @@ -4675,6 +4579,23 @@ nsHttpChannel::SetupReplacementChannel(nsIURI *newURI, resumableChannel->ResumeAt(mStartPos, mEntityID); } + if (!(redirectFlags & nsIChannelEventSink::REDIRECT_STS_UPGRADE) && + mInterceptCache != INTERCEPTED) { + // Ensure that internally-redirected channels, or loads with manual + // redirect mode cannot be intercepted, which would look like two + // separate requests to the nsINetworkInterceptController. + if (mRedirectMode != nsIHttpChannelInternal::REDIRECT_MODE_MANUAL || + (redirectFlags & (nsIChannelEventSink::REDIRECT_TEMPORARY | + nsIChannelEventSink::REDIRECT_PERMANENT)) == 0) { + nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL; + rv = newChannel->GetLoadFlags(&loadFlags); + NS_ENSURE_SUCCESS(rv, rv); + loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER; + rv = newChannel->SetLoadFlags(loadFlags); + NS_ENSURE_SUCCESS(rv, rv); + } + } + return NS_OK; } @@ -5108,6 +5029,9 @@ nsHttpChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *context) gHttpHandler->OnOpeningRequest(this); } + // Set user agent override + HttpBaseChannel::SetDocshellUserAgentOverride(); + mIsPending = true; mWasOpened = true; diff --git a/netwerk/protocol/http/nsIHttpChannelChild.idl b/netwerk/protocol/http/nsIHttpChannelChild.idl index 9d545f09b1..187a3342cc 100644 --- a/netwerk/protocol/http/nsIHttpChannelChild.idl +++ b/netwerk/protocol/http/nsIHttpChannelChild.idl @@ -11,14 +11,15 @@ interface nsIPrincipal; interface nsIURI; -[uuid(893e29fb-2e84-454e-afc7-41fadbe93fd9)] +[uuid(d02b96ed-2789-4f42-a25c-7abe63de7c18)] interface nsIHttpChannelChild : nsISupports { void addCookiesToRequest(); // Mark this channel as requiring an interception; this will propagate // to the corresponding parent channel when a redirect occurs. - void forceIntercepted(); + void forceIntercepted(in boolean postRedirectChannelShouldIntercept, + in boolean postRedirectChannelShouldUpgrade); // Headers that the channel client has set via SetRequestHeader. readonly attribute RequestHeaderTuples clientSetRequestHeaders; diff --git a/netwerk/protocol/http/nsIHttpChannelInternal.idl b/netwerk/protocol/http/nsIHttpChannelInternal.idl index 48b06cf6ca..b7d4132543 100644 --- a/netwerk/protocol/http/nsIHttpChannelInternal.idl +++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl @@ -39,7 +39,7 @@ interface nsIHttpUpgradeListener : nsISupports * using any feature exposed by this interface, be aware that this interface * will change and you will be broken. You have been warned. */ -[scriptable, uuid(332d5f9c-991c-45e3-922f-99e6fe0deb60)] +[scriptable, uuid(f292a080-f2f6-41d6-8aa4-71337e477360)] interface nsIHttpChannelInternal : nsISupports { /** @@ -226,6 +226,7 @@ interface nsIHttpChannelInternal : nsISupports const unsigned long CORS_MODE_SAME_ORIGIN = 0; const unsigned long CORS_MODE_NO_CORS = 1; const unsigned long CORS_MODE_CORS = 2; + const unsigned long CORS_MODE_NAVIGATE = 3; /** * Set by nsCORSListenerProxy to indicate CORS load type. Defaults to CORS_MODE_NO_CORS. */ diff --git a/netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp b/netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp index c7a55275b4..f4f7c1a266 100644 --- a/netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp +++ b/netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp @@ -433,6 +433,8 @@ WyciwygChannelChild::SetLoadGroup(nsILoadGroup * aLoadGroup) mLoadGroup, NS_GET_IID(nsIProgressEventSink), getter_AddRefs(mProgressSink)); + + UpdatePrivateBrowsing(); return NS_OK; } @@ -525,6 +527,7 @@ WyciwygChannelChild::SetNotificationCallbacks(nsIInterfaceRequestor * aCallbacks mLoadGroup, NS_GET_IID(nsIProgressEventSink), getter_AddRefs(mProgressSink)); + UpdatePrivateBrowsing(); return NS_OK; } diff --git a/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp b/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp index 20e69e660d..dd58105547 100644 --- a/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp +++ b/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp @@ -231,7 +231,7 @@ nsWyciwygChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) mLoadGroup, NS_GET_IID(nsIProgressEventSink), getter_AddRefs(mProgressSink)); - mPrivateBrowsing = NS_UsePrivateBrowsing(this); + UpdatePrivateBrowsing(); NS_GetOriginAttributes(this, mOriginAttributes); return NS_OK; @@ -328,7 +328,7 @@ nsWyciwygChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aNotificationC NS_GET_IID(nsIProgressEventSink), getter_AddRefs(mProgressSink)); - mPrivateBrowsing = NS_UsePrivateBrowsing(this); + UpdatePrivateBrowsing(); NS_GetOriginAttributes(this, mOriginAttributes); return NS_OK; diff --git a/netwerk/test/unit/test_getHost.js b/netwerk/test/unit/test_getHost.js new file mode 100644 index 0000000000..163ee510f0 --- /dev/null +++ b/netwerk/test/unit/test_getHost.js @@ -0,0 +1,75 @@ +// Test getLocalHost/getLocalPort and getRemoteHost/getRemotePort. + +Cu.import("resource://testing-common/httpd.js"); +Cu.import("resource://gre/modules/Services.jsm"); + +var httpserver = new HttpServer(); +httpserver.start(-1); +const PORT = httpserver.identity.primaryPort; + +var gotOnStartRequest = false; + +function CheckGetHostListener() {} + +CheckGetHostListener.prototype = { + onStartRequest: function(request, context) { + dump("*** listener onStartRequest\n"); + + gotOnStartRequest = true; + + request.QueryInterface(Components.interfaces.nsIHttpChannelInternal); + try { + do_check_eq(request.localAddress, "127.0.0.1"); + do_check_eq(request.localPort > 0, true); + do_check_neq(request.localPort, PORT); + do_check_eq(request.remoteAddress, "127.0.0.1"); + do_check_eq(request.remotePort, PORT); + } catch (e) { + do_check_true(0, "Get local/remote host/port throws an error!"); + } + }, + + onStopRequest: function(request, context, statusCode) { + dump("*** listener onStopRequest\n"); + + do_check_eq(gotOnStartRequest, true); + httpserver.stop(do_test_finished); + }, + + QueryInterface: function(iid) { + if (iid.equals(Components.interfaces.nsIRequestObserver) || + iid.equals(Components.interfaces.nsISupports) + ) + return this; + throw Components.results.NS_NOINTERFACE; + }, +} + +function make_channel(url) { + var ios = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); + return ios.newChannel2(url, + null, + null, + null, // aLoadingNode + Services.scriptSecurityManager.getSystemPrincipal(), + null, // aTriggeringPrincipal + Ci.nsILoadInfo.SEC_NORMAL, + Ci.nsIContentPolicy.TYPE_OTHER) + .QueryInterface(Components.interfaces.nsIHttpChannel); +} + +function test_handler(metadata, response) { + response.setHeader("Content-Type", "text/html", false); + response.setStatusLine(metadata.httpVersion, 200, "OK"); + var responseBody = "blah blah"; + response.bodyOutputStream.write(responseBody, responseBody.length); +} + +function run_test() { + httpserver.registerPathHandler("/testdir", test_handler); + + var channel = make_channel("http://localhost:" + PORT + "/testdir"); + channel.asyncOpen(new CheckGetHostListener(), null); + do_test_pending(); +} diff --git a/netwerk/test/unit/test_traceable_channel.js b/netwerk/test/unit/test_traceable_channel.js index 1687186c2b..b9bf1fb022 100644 --- a/netwerk/test/unit/test_traceable_channel.js +++ b/netwerk/test/unit/test_traceable_channel.js @@ -27,13 +27,11 @@ TracingListener.prototype = { request.QueryInterface(Components.interfaces.nsIHttpChannelInternal); // local/remote addresses broken in e10s: disable for now -/* do_check_eq(request.localAddress, "127.0.0.1"); do_check_eq(request.localPort > 0, true); do_check_neq(request.localPort, PORT); do_check_eq(request.remoteAddress, "127.0.0.1"); do_check_eq(request.remotePort, PORT); -*/ // Make sure listener can't be replaced after OnStartRequest was called. request.QueryInterface(Components.interfaces.nsITraceableChannel); diff --git a/netwerk/test/unit/xpcshell.ini b/netwerk/test/unit/xpcshell.ini index e2eeaa054e..cbe7713282 100644 --- a/netwerk/test/unit/xpcshell.ini +++ b/netwerk/test/unit/xpcshell.ini @@ -343,6 +343,7 @@ firefox-appdir = browser [test_packaged_app_service_paths.js] [test_bug1195415.js] [test_cookie_blacklist.js] +[test_getHost.js] [test_packaged_app_bug1214079.js] [test_bug412457.js] diff --git a/netwerk/test/unit_ipc/test_getHost_wrap.js b/netwerk/test/unit_ipc/test_getHost_wrap.js new file mode 100644 index 0000000000..f74ab7d152 --- /dev/null +++ b/netwerk/test/unit_ipc/test_getHost_wrap.js @@ -0,0 +1,3 @@ +function run_test() { + run_test_in_child("../unit/test_getHost.js"); +} diff --git a/netwerk/test/unit_ipc/xpcshell.ini b/netwerk/test/unit_ipc/xpcshell.ini index 7b70f13166..590544a538 100644 --- a/netwerk/test/unit_ipc/xpcshell.ini +++ b/netwerk/test/unit_ipc/xpcshell.ini @@ -41,3 +41,4 @@ skip-if = true [test_redirect_history_wrap.js] [test_reply_without_content_type_wrap.js] [test_app_offline_http.js] +[test_getHost_wrap.js] diff --git a/parser/htmlparser/nsParser.cpp b/parser/htmlparser/nsParser.cpp index 306991a90e..69c503e2f0 100644 --- a/parser/htmlparser/nsParser.cpp +++ b/parser/htmlparser/nsParser.cpp @@ -1686,7 +1686,7 @@ ExtractCharsetFromXmlDeclaration(const unsigned char* aBytes, int32_t aLen, return !oCharset.IsEmpty(); } -inline const char +inline char GetNextChar(nsACString::const_iterator& aStart, nsACString::const_iterator& aEnd) { diff --git a/security/manager/ssl/DataStorage.cpp b/security/manager/ssl/DataStorage.cpp index 11aa33eba9..98efbdd490 100644 --- a/security/manager/ssl/DataStorage.cpp +++ b/security/manager/ssl/DataStorage.cpp @@ -141,7 +141,16 @@ DataStorage::Init(bool& aDataWillPersist) // Clear private data as appropriate. os->AddObserver(this, "last-pb-context-exited", false); // Observe shutdown; save data and prevent any further writes. - os->AddObserver(this, "profile-before-change", false); + // In the parent process, we need to write to the profile directory, so + // we should listen for profile-before-change so that we can safely + // write to the profile. In the content process however we don't have + // access to the profile directory and profile notifications are not + // dispatched, so we need to clean up on xpcom-shutdown. + if (XRE_IsParentProcess()) { + os->AddObserver(this, "profile-before-change", false); + } else { + os->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + } // For test purposes, we can set the write timer to be very fast. mTimerDelay = Preferences::GetInt("test.datastorage.write_timer_ms", @@ -856,26 +865,28 @@ DataStorage::Observe(nsISupports* aSubject, const char* aTopic, MutexAutoLock lock(mMutex); mPrivateDataTable.Clear(); } else if (strcmp(aTopic, "profile-before-change") == 0) { - if (XRE_IsParentProcess()) { - { - MutexAutoLock lock(mMutex); - rv = AsyncWriteData(lock); - mShuttingDown = true; + MOZ_ASSERT(XRE_IsParentProcess()); + { + MutexAutoLock lock(mMutex); + rv = AsyncWriteData(lock); + mShuttingDown = true; + Unused << NS_WARN_IF(NS_FAILED(rv)); + if (mTimer) { + rv = DispatchShutdownTimer(lock); Unused << NS_WARN_IF(NS_FAILED(rv)); - if (mTimer) { - rv = DispatchShutdownTimer(lock); - Unused << NS_WARN_IF(NS_FAILED(rv)); - } - } - // Run the thread to completion and prevent any further events - // being scheduled to it. The thread may need the lock, so we can't - // hold it here. - rv = mWorkerThread->Shutdown(); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; } } + // Run the thread to completion and prevent any further events + // being scheduled to it. The thread may need the lock, so we can't + // hold it here. + rv = mWorkerThread->Shutdown(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + sDataStorages->Clear(); + } else if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { + MOZ_ASSERT(!XRE_IsParentProcess()); sDataStorages->Clear(); } else if (strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) { MutexAutoLock lock(mMutex); diff --git a/storage/mozStorageBindingParamsArray.h b/storage/mozStorageBindingParamsArray.h index c03fd4959d..4626ab55f5 100644 --- a/storage/mozStorageBindingParamsArray.h +++ b/storage/mozStorageBindingParamsArray.h @@ -46,7 +46,7 @@ public: /** * @return the number of elemets the array contains. */ - const size_type length() const { return mArray.Length(); } + size_type length() const { return mArray.Length(); } class iterator { public: diff --git a/testing/web-platform/mozilla/meta/MANIFEST.json b/testing/web-platform/mozilla/meta/MANIFEST.json index a3e47e4379..5787b59caf 100644 --- a/testing/web-platform/mozilla/meta/MANIFEST.json +++ b/testing/web-platform/mozilla/meta/MANIFEST.json @@ -226,6 +226,13 @@ "url": "/_mozilla/service-workers/service-worker/fetch-request-no-freshness-headers.https.html" } ], + "service-workers/service-worker/fetch-request-redirect.https.html": [ + { + "path": "service-workers/service-worker/fetch-request-redirect.https.html", + "timeout": "long", + "url": "/_mozilla/service-workers/service-worker/fetch-request-redirect.https.html" + } + ], "service-workers/service-worker/fetch-request-resources.https.html": [ { "path": "service-workers/service-worker/fetch-request-resources.https.html", diff --git a/testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-request-fallback.https.html.ini b/testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-request-fallback.https.html.ini deleted file mode 100644 index 806dcfabb5..0000000000 --- a/testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-request-fallback.https.html.ini +++ /dev/null @@ -1,6 +0,0 @@ -[fetch-request-fallback.https.html] - type: testharness - expected: TIMEOUT - [Verify the fallback behavior of FetchEvent] - expected: TIMEOUT - diff --git a/testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-request-redirect.https.html.ini b/testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-request-redirect.https.html.ini new file mode 100644 index 0000000000..10ef5d594a --- /dev/null +++ b/testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-request-redirect.https.html.ini @@ -0,0 +1,3 @@ +[fetch-request-redirect.https.html] + disabled: + if e10s: https://bugzilla.mozilla.org/show_bug.cgi?id=1219469 diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-request-fallback.https.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-request-fallback.https.html index 41c120a379..8290e21dd3 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-request-fallback.https.html +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-request-fallback.https.html @@ -12,7 +12,7 @@ function xhr_fail_test(frame, url) { return new Promise(function(resolve, reject) { frame.contentWindow.xhr(url) .then(function(){ - reject(msg + ' should fail.'); + reject(url + ' should fail.'); }) .catch(function(){ resolve(); @@ -28,21 +28,22 @@ function xhr_succeed_test(frame, url) { resolve(); }) .catch(function(){ - reject(msg + ' should succeed.'); + reject(url + ' should succeed.'); }); }); } async_test(function(t) { + var path = new URL(".", window.location).pathname; var SCOPE = 'resources/fetch-request-fallback-iframe.html'; var SCRIPT = 'resources/fetch-request-fallback-worker.js'; var host_info = get_host_info(); - var BASE_URL = host_info['HTTP_ORIGIN'] + - '/service-worker/resources/fetch-access-control.py?'; - var OTHER_BASE_URL = host_info['HTTP_REMOTE_ORIGIN'] + - '/service-worker/resources/fetch-access-control.py?'; - var REDIRECT_URL = host_info['HTTP_ORIGIN'] + - '/service-worker/resources/redirect.py?Redirect='; + var BASE_URL = host_info['HTTPS_ORIGIN'] + + path + 'resources/fetch-access-control.py?'; + var OTHER_BASE_URL = host_info['HTTPS_REMOTE_ORIGIN'] + + path + 'resources/fetch-access-control.py?'; + var REDIRECT_URL = host_info['HTTPS_ORIGIN'] + + path + 'resources/redirect.py?Redirect='; var frame; var worker; service_worker_unregister_and_register(t, SCRIPT, SCOPE) @@ -94,9 +95,9 @@ async_test(function(t) { assert_equals(requests[0].url, new URL(SCOPE, location).toString(), 'The first request to the SW must be the request for ' + 'the page.'); - assert_equals(requests[0].mode, 'no-cors', + assert_equals(requests[0].mode, 'navigate', 'The mode of the first request to the SW must be ' + - 'no-cors.'); + 'navigate'); for (var i = 0; i < expected_urls.length; ++i) { assert_equals(requests[i + 1].url, expected_urls[i], 'The URL of the request which was passed from XHR ' + diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-request-redirect.https.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-request-redirect.https.html new file mode 100644 index 0000000000..fa937f2e18 --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-request-redirect.https.html @@ -0,0 +1,176 @@ + +Service Worker: FetchEvent for resources + + + + + + diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/navigation-redirect.https.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/navigation-redirect.https.html new file mode 100644 index 0000000000..7b606cf0c3 --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/navigation-redirect.https.html @@ -0,0 +1,449 @@ + +Service Worker: Navigation redirection + + + + + + + diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/request-end-to-end.https.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/request-end-to-end.https.html index 0af3ee4fdb..7bcd020fe0 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/request-end-to-end.https.html +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/request-end-to-end.https.html @@ -42,6 +42,8 @@ t.step(function() { location.href.substring(0, location.href.lastIndexOf('/') + 1) + scope, 'request.url should be passed to onfetch event.'); + assert_equals(event.data.mode, 'navigate', + 'request.mode should be passed to onfetch event.'); assert_equals(event.data.method, 'GET', 'request.method should be passed to onfetch event.'); assert_equals(event.data.referrer, location.href, diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/dummy.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/dummy.html new file mode 100644 index 0000000000..12a179980d --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/dummy.html @@ -0,0 +1,2 @@ + +Hello world diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-request-fallback-worker.js b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-request-fallback-worker.js index 0e12911d42..3b028b24bd 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-request-fallback-worker.js +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-request-fallback-worker.js @@ -2,6 +2,7 @@ var requests = []; self.addEventListener('message', function(event) { event.data.port.postMessage({requests: requests}); + requests = []; }); self.addEventListener('fetch', function(event) { diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-request-redirect-iframe.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-request-redirect-iframe.html new file mode 100644 index 0000000000..ffd76bfc49 --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-request-redirect-iframe.html @@ -0,0 +1,35 @@ + diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-rewrite-worker.js b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-rewrite-worker.js index 00adad1a40..470a3b0f53 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-rewrite-worker.js +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-rewrite-worker.js @@ -16,8 +16,11 @@ function get_request_init(base, params) { var init = {}; init['method'] = params['method'] || base['method']; init['mode'] = params['mode'] || base['mode']; + if (init['mode'] == 'navigate') { + init['mode'] = 'same-origin'; + } init['credentials'] = params['credentials'] || base['credentials']; - init['redirect'] = params['redirect'] || base['redirect']; + init['redirect'] = params['redirect-mode'] || base['redirect']; return init; } diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/request-end-to-end-worker.js b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/request-end-to-end-worker.js index 065df37466..323c7f2436 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/request-end-to-end-worker.js +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/request-end-to-end-worker.js @@ -22,6 +22,7 @@ onfetch = function(e) { } port.postMessage({ url: e.request.url, + mode: e.request.mode, method: e.request.method, referrer: e.request.referrer, headers: headers, diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/silence.oga b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/silence.oga new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/square.png b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/square.png new file mode 100644 index 0000000000000000000000000000000000000000..01c9666a8de9d5535615aff830810e5df4b2156f GIT binary patch literal 18299 zcmeI3cT`hZx48PM6f{KXPL2#^~i~=G$;2XffQGxGN^zf;KPc( zS9b@_KBBc3^kkIOsZ^>?Ip}2WNr;}3Yddf1Z(FZd*Su&&S;wduivVra5{`km-$()Y z5JjafHmp>+1So{xS62lp-O?*DbK?fJ-q@zDQi$HBP$@~Ya8Zq(0a!=wu{{A;J19hF zq%80TvXp>zx7n-~U>OovxA5mz_krk)52>3JfR+0VbQH1@0mO7L-VO*@0uc*e&r0+coZ>uwksg#+7Cff)|nzSKV! z7iqVfLZniQsb$7w`$d zjkc#hyjHWQwwAc3RC6uz&1L05Ll&!Lpsg-nWDNi>BvJJPX6TYR(My!0g9nbx?@|g_ zqn@>)Zx^>%%la&k)$!D~M+aL5-6!ml8 z``<3TG>*Zoj&W4_@LScLUf1Ju>-J6F#%g+%;Q0BR`rv2%`-audtTI2-87-dELiX6D z?e4)HH{4;nZ_%~+4TGGQ&1RnzY0U)S)Owo2rbJ}UYPRB^E(^8&B$Y4w0HC{Ec;#0U zRmJFltuN}r2H#orJ7&!XqPfodLI7ZmoiU1WtHkQMDgfAJ#h9M5(d)f3%dAp)?v)>! zuBd-rN8Dy>TwP_WZL7wKo*TMuQNb2llkIm;>6@-Y|7xv|uk;Mqo+Q#lRr#FPv=nK5 zWU6LfF{y}|tYmXTbvo1FX}kh!r=QUtRo&Fs4+dA9l&0-6M%;{_;c4iSNN~b>?PMT) zobLl-{VNIX$dp4((i?y znPa(|c)0yuet_1~1RDK1rtBEn3UskX2FH2e^t+7 z;jnRjPG&|ArzK2BYj29DSCfpV?V#fpmhGM7eEJxpVOoPjgTTwE!z?!)?=;6K>E=^T zV6h5$zZqijjo8+V)~l`Nt$M8n-7D2HSk@uOK6t-0@w&Bs>FhS`Hhh~hn1ZwMIhyA6 zEaxy|Dj{KoZQphJ<~a(w?f7D)jL) zEj9f~C-Iirfu#o)9MCgGGjj7zz|J`*$e&Uv<6eK|ki1b$V?}MGZooJ-Z~_%pg!BfBS|QLiK{v zcc1*U(X>3JU%z~pWnS)KGTnTsxo?SA&wj3zN=r(}heHzg$?YcD$vsg!pU-%==;b24 z6L{A$EVwE#?_lylzkH{B&wR(X7l}ok*%>D;+L!x(iqW*WzI5TLg^s+0+8;97y`OkL z%T~*t>1IiJUxdmFJg#@R+%D|0AiFCi^U|8=Ojlv{^N5S>ALnjH_cQu~KW4vooZ_ck zGR0WAaZ2qh>NP@$kgAWq-uQHMVpov;kNf$oSY6^!m{BPB_SEb$_ayiH%!jsT}c;{HecBMuYOAvjkqV8`T8sLqr_)IXHb?? zo~P9w>ayB=t@mIDn&(%iUH90$rF8o3Mb-Qa@AUhQJY8OycxzAmt{pC0ZljWEsC2!W zXE!dkE|t6wS^Xli;eAGWNqSXhPUFcgVi&(FuIZOM_+J)f`kRaIUA;m7&9klEO8u7u zn84fG_LygueTUD}_t&|g|;EmYET+;ji6cSx1zZk)UA zaaEYPHny4mv(X@DFmkXS$c~<`z*F22V-vG-(x(rRKN(!!V?}8M|15seX|p@4%tps1 zVN2nbwkw4O0XKf%TWHYNo>H4w%h!xu7WMk!Jr(9F=B}$zQx?X?#rkfy+9Qhhn^TWX zCWO^D(Z$VnAMFm>Jx}LhJ;*1KO9`g5Jk)yXQ_=KES-`$ zGi@Ux7-vbjh~2s`ac_uio`G9ZDen#M6?fz90x-6C;F@69IrO{(DmMd5_7?o$k5ntQ zJ@J~c!sL;uN-+=gLq%sMezM!{Y7Bl?$lncb1w4Kk&%!^i3{`y0{?HEih)ym0Me`oK*;XtL~%L z7Q6Xv)1%JS9)4*5=CjO?+cWfNIy-h2&1lq3*7^CdNmF>6UYzjO<Jng{ceUnOe_G@d*?qtU$lOy~PQ?Hkd_cTF10x0ce&j$WpouK=@e*4|xW z#W=?3Wqf21yBeOIWj^{KsPEF-RPiVN_XmwDEBg9rH!n5%DEPQN;64C9Ie#kYvntw= z*YV-tr{L9v?!h6Q*A*KS`&EoIOCOc}`ar+IlHrx`aPeD5&Fep28pwDThSVTx`26co z%}XPZT|{d~-{j`Lc^Z_b8+UIic%gFt$Bp_tee`1k<4$XFR55kyQ=%Vq`SDWZMyGy-?WpIwZU&BZ>R%F_dTwcA1Y5PDq9s;))jg2>?Uqs zhh8SB_F3=6h(BfyK75c#wtRN6CsNpVt?zyF%x6)d3;Sztmp=(x*i~5JQL(nyy3^(f z{aM@ttCa&ykKZ-@yuLCltEaxnu}?X6Yu!NN`vfie4+*IWx3_C-f17DRBa>fRh4y!R z&ZgIK>K0_`4jdV{U8Fk`9rfYC+efwaDfNewyOWbH2mf@u|4rrF*(V!os%qw4x*2Yc zUDLb#Q|FbirZD|?N1L@gT7N?PY%&<|*Xj4(_p(1F%}z=hR8mao`OG#)HUhwscYKDQ z#Lvx@!WIUjm>eMsM1=>7pp7U1P_4p6Om-kBL9jp`UtnqYuKcngg3qxu^d-1q+(dLR zfbSF;3VKJnGuV-VY%<5til#;lr$7#ZK?xHP9vmbPQ^G9`hx}5YYiTpu5HZw65@=~? zBMpe~b6bX>3qwH!0YyNvF*q!OL`Go=1QH2nhQML4cr*r!#+oCsWC|Wn!C(+0A48fN zbVUv2a4BAP4kO_p$%=93hY}!;u29 z(Xf+IKX#y)9m*F;_(B0f>X*q9Zje|S8cG9=eMZI=EE)?W5Rb5fD5AreA~Y6-L4V7L z!ydB{Z3qn-x-||P4F-Y1pg^DLPMv#6HcGObLh!BBjFHkJp5XuJaH$p=(`qtdp)iOxJj=$5s5=#C%T!?Z-SqpIZJUCh$Tz`8+5j#K@BKA zpF?5e@LY~LfsDjsIRqr0z+xepTpWmGVL28=7K=+Hu?a)TaC4hz{*`MxA$x;#-9fI0 zOB6@QhTM-24(Uxjkwi=lZR zF=0JGt751|dV?WfwvH--_(Qc$#0(XK(v@s!IJ%U_isM-AliCbb1PYTat&%jhbfJM9 zD*B7o@!J}+95Lg6ozB09VA%fz^Y6z93jhVOmg%sops|r@IVd?Jvz40hW}H!`&;F3 z7|eeycd$p)|BKuWuf{Kn;%K4$x`ZkOmD6-URQx zj2{jL`PuQI$ER5O7=VT~Vg%QG)6)ODmJ>81mcxmfurnX3pTn)tz8^YrpvTS}UzOIe z$Im}`F+QY!(kslDJO~VkY*CI&HXoQ)jtd4vwkXFXn-5GY#{~l-Ta@FH%?GBHrj_G@0g)}r#HBX=7B47(Ufm6Y-qBq> zeM1BEelLRULwv3O|r9&R#nwjP%!*oy|z{wiCgx35&#Si bDgs((CoOJ&@$4~l+kmsZyIqm(x-I_(KvUo& literal 0 HcmV?d00001 diff --git a/toolkit/components/alerts/AlertNotification.cpp b/toolkit/components/alerts/AlertNotification.cpp new file mode 100644 index 0000000000..c3e1e85aca --- /dev/null +++ b/toolkit/components/alerts/AlertNotification.cpp @@ -0,0 +1,125 @@ +/* This Source Code Form is subject to the terms of the Mozilla Pub + * License, v. 2.0. If a copy of the MPL was not distributed with t + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/AlertNotification.h" + +namespace mozilla { + +NS_IMPL_CYCLE_COLLECTION(AlertNotification, mPrincipal) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AlertNotification) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAlertNotification) + NS_INTERFACE_MAP_ENTRY(nsIAlertNotification) +NS_INTERFACE_MAP_END +NS_IMPL_CYCLE_COLLECTING_ADDREF(AlertNotification) +NS_IMPL_CYCLE_COLLECTING_RELEASE(AlertNotification) + +AlertNotification::AlertNotification() + : mTextClickable(false) + , mPrincipal(nullptr) + , mInPrivateBrowsing(false) +{} + +AlertNotification::~AlertNotification() +{} + +NS_IMETHODIMP +AlertNotification::Init(const nsAString& aName, const nsAString& aImageURL, + const nsAString& aTitle, const nsAString& aText, + bool aTextClickable, const nsAString& aCookie, + const nsAString& aDir, const nsAString& aLang, + const nsAString& aData, nsIPrincipal* aPrincipal, + bool aInPrivateBrowsing) +{ + mName = aName; + mImageURL = aImageURL; + mTitle = aTitle; + mText = aText; + mTextClickable = aTextClickable; + mCookie = aCookie; + mDir = aDir; + mLang = aLang; + mData = aData; + mPrincipal = aPrincipal; + mInPrivateBrowsing = aInPrivateBrowsing; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetName(nsAString& aName) +{ + aName = mName; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetImageURL(nsAString& aImageURL) +{ + aImageURL = mImageURL; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetTitle(nsAString& aTitle) +{ + aTitle = mTitle; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetText(nsAString& aText) +{ + aText = mText; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetTextClickable(bool* aTextClickable) +{ + *aTextClickable = mTextClickable; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetCookie(nsAString& aCookie) +{ + aCookie = mCookie; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetDir(nsAString& aDir) +{ + aDir = mDir; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetLang(nsAString& aLang) +{ + aLang = mLang; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetData(nsAString& aData) +{ + aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetPrincipal(nsIPrincipal** aPrincipal) +{ + NS_IF_ADDREF(*aPrincipal = mPrincipal); + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetInPrivateBrowsing(bool* aInPrivateBrowsing) +{ + *aInPrivateBrowsing = mInPrivateBrowsing; + return NS_OK; +} + +} // namespace mozilla diff --git a/toolkit/components/alerts/AlertNotification.h b/toolkit/components/alerts/AlertNotification.h new file mode 100644 index 0000000000..420e899c87 --- /dev/null +++ b/toolkit/components/alerts/AlertNotification.h @@ -0,0 +1,44 @@ +/* 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_AlertNotification_h__ +#define mozilla_AlertNotification_h__ + +#include "nsIAlertsService.h" +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIPrincipal.h" +#include "nsString.h" + +namespace mozilla { + +class AlertNotification final : public nsIAlertNotification +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(AlertNotification, + nsIAlertNotification) + NS_DECL_NSIALERTNOTIFICATION + AlertNotification(); + +protected: + virtual ~AlertNotification(); + +private: + nsString mName; + nsString mImageURL; + nsString mTitle; + nsString mText; + bool mTextClickable; + nsString mCookie; + nsString mDir; + nsString mLang; + nsString mData; + nsCOMPtr mPrincipal; + bool mInPrivateBrowsing; +}; + +} // namespace mozilla + +#endif /* mozilla_AlertNotification_h__ */ diff --git a/toolkit/components/alerts/AlertNotificationIPCSerializer.h b/toolkit/components/alerts/AlertNotificationIPCSerializer.h new file mode 100644 index 0000000000..9eaefacda5 --- /dev/null +++ b/toolkit/components/alerts/AlertNotificationIPCSerializer.h @@ -0,0 +1,119 @@ +/* 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_AlertNotificationIPCSerializer_h__ +#define mozilla_AlertNotificationIPCSerializer_h__ + +#include "nsComponentManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsIAlertsService.h" +#include "nsIPrincipal.h" +#include "nsString.h" + +#include "ipc/IPCMessageUtils.h" + +#include "mozilla/dom/PermissionMessageUtils.h" + +typedef nsIAlertNotification* AlertNotificationType; + +namespace IPC { + +template <> +struct ParamTraits +{ + typedef AlertNotificationType paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + bool isNull = !aParam; + if (isNull) { + WriteParam(aMsg, isNull); + return; + } + + nsString name, imageURL, title, text, cookie, dir, lang, data; + bool textClickable, inPrivateBrowsing; + nsCOMPtr principal; + + if (NS_WARN_IF(NS_FAILED(aParam->GetName(name))) || + NS_WARN_IF(NS_FAILED(aParam->GetImageURL(imageURL))) || + NS_WARN_IF(NS_FAILED(aParam->GetTitle(title))) || + NS_WARN_IF(NS_FAILED(aParam->GetText(text))) || + NS_WARN_IF(NS_FAILED(aParam->GetTextClickable(&textClickable))) || + NS_WARN_IF(NS_FAILED(aParam->GetCookie(cookie))) || + NS_WARN_IF(NS_FAILED(aParam->GetDir(dir))) || + NS_WARN_IF(NS_FAILED(aParam->GetLang(lang))) || + NS_WARN_IF(NS_FAILED(aParam->GetData(data))) || + NS_WARN_IF(NS_FAILED(aParam->GetPrincipal(getter_AddRefs(principal)))) || + NS_WARN_IF(NS_FAILED(aParam->GetInPrivateBrowsing(&inPrivateBrowsing)))) { + + // Write a `null` object if any getter returns an error. Otherwise, the + // receiver will try to deserialize an incomplete object and crash. + WriteParam(aMsg, /* isNull */ true); + return; + } + + WriteParam(aMsg, isNull); + WriteParam(aMsg, name); + WriteParam(aMsg, imageURL); + WriteParam(aMsg, title); + WriteParam(aMsg, text); + WriteParam(aMsg, textClickable); + WriteParam(aMsg, cookie); + WriteParam(aMsg, dir); + WriteParam(aMsg, lang); + WriteParam(aMsg, data); + WriteParam(aMsg, IPC::Principal(principal)); + WriteParam(aMsg, inPrivateBrowsing); + } + + static bool Read(const Message* aMsg, void** aIter, paramType* aResult) + { + bool isNull; + NS_ENSURE_TRUE(ReadParam(aMsg, aIter, &isNull), false); + if (isNull) { + *aResult = nullptr; + return true; + } + + nsString name, imageURL, title, text, cookie, dir, lang, data; + bool textClickable, inPrivateBrowsing; + IPC::Principal principal; + + if (!ReadParam(aMsg, aIter, &name) || + !ReadParam(aMsg, aIter, &imageURL) || + !ReadParam(aMsg, aIter, &title) || + !ReadParam(aMsg, aIter, &text) || + !ReadParam(aMsg, aIter, &textClickable) || + !ReadParam(aMsg, aIter, &cookie) || + !ReadParam(aMsg, aIter, &dir) || + !ReadParam(aMsg, aIter, &lang) || + !ReadParam(aMsg, aIter, &data) || + !ReadParam(aMsg, aIter, &principal) || + !ReadParam(aMsg, aIter, &inPrivateBrowsing)) { + + return false; + } + + nsCOMPtr alert = + do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID); + if (NS_WARN_IF(!alert)) { + *aResult = nullptr; + return true; + } + nsresult rv = alert->Init(name, imageURL, title, text, textClickable, + cookie, dir, lang, data, principal, + inPrivateBrowsing); + if (NS_WARN_IF(NS_FAILED(rv))) { + *aResult = nullptr; + return true; + } + alert.forget(aResult); + return true; + } +}; + +} // namespace IPC + +#endif /* mozilla_AlertNotificationIPCSerializer_h__ */ diff --git a/toolkit/components/alerts/moz.build b/toolkit/components/alerts/moz.build index ddcc602912..bf862d0901 100644 --- a/toolkit/components/alerts/moz.build +++ b/toolkit/components/alerts/moz.build @@ -16,7 +16,13 @@ EXPORTS += [ 'nsAlertsUtils.h', ] +EXPORTS.mozilla += [ + 'AlertNotification.h', + 'AlertNotificationIPCSerializer.h', +] + UNIFIED_SOURCES += [ + 'AlertNotification.cpp', 'nsAlertsService.cpp', 'nsAlertsUtils.cpp', 'nsXULAlerts.cpp', diff --git a/toolkit/components/alerts/nsAlertsService.cpp b/toolkit/components/alerts/nsAlertsService.cpp index 2e1344268c..7506751ce2 100644 --- a/toolkit/components/alerts/nsAlertsService.cpp +++ b/toolkit/components/alerts/nsAlertsService.cpp @@ -5,6 +5,7 @@ #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/PermissionMessageUtils.h" +#include "mozilla/Telemetry.h" #include "nsXULAppAPI.h" #include "nsAlertsService.h" @@ -72,40 +73,66 @@ NS_IMETHODIMP nsAlertsService::ShowAlertNotification(const nsAString & aImageUrl nsIPrincipal * aPrincipal, bool aInPrivateBrowsing) { + nsCOMPtr alert = + do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID); + NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE); + nsresult rv = alert->Init(aAlertName, aImageUrl, aAlertTitle, + aAlertText, aAlertTextClickable, + aAlertCookie, aBidi, aLang, aData, + aPrincipal, aInPrivateBrowsing); + NS_ENSURE_SUCCESS(rv, rv); + return ShowAlert(alert, aAlertListener); +} + + +NS_IMETHODIMP nsAlertsService::ShowAlert(nsIAlertNotification * aAlert, + nsIObserver * aAlertListener) +{ + NS_ENSURE_ARG(aAlert); + + nsAutoString cookie; + nsresult rv = aAlert->GetCookie(cookie); + NS_ENSURE_SUCCESS(rv, rv); + if (XRE_IsContentProcess()) { ContentChild* cpc = ContentChild::GetSingleton(); if (aAlertListener) - cpc->AddRemoteAlertObserver(PromiseFlatString(aAlertCookie), aAlertListener); + cpc->AddRemoteAlertObserver(cookie, aAlertListener); - cpc->SendShowAlertNotification(PromiseFlatString(aImageUrl), - PromiseFlatString(aAlertTitle), - PromiseFlatString(aAlertText), - aAlertTextClickable, - PromiseFlatString(aAlertCookie), - PromiseFlatString(aAlertName), - PromiseFlatString(aBidi), - PromiseFlatString(aLang), - PromiseFlatString(aData), - IPC::Principal(aPrincipal), - aInPrivateBrowsing); + cpc->SendShowAlert(aAlert); return NS_OK; } + nsAutoString imageUrl; + rv = aAlert->GetImageURL(imageUrl); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString title; + rv = aAlert->GetTitle(title); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString text; + rv = aAlert->GetText(text); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString name; + rv = aAlert->GetName(name); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr principal; + rv = aAlert->GetPrincipal(getter_AddRefs(principal)); + NS_ENSURE_SUCCESS(rv, rv); + #ifdef MOZ_WIDGET_ANDROID - mozilla::AndroidBridge::Bridge()->ShowAlertNotification(aImageUrl, aAlertTitle, aAlertText, aAlertCookie, - aAlertListener, aAlertName); + mozilla::AndroidBridge::Bridge()->ShowAlertNotification(imageUrl, title, text, cookie, + aAlertListener, name, principal); return NS_OK; #else // Check if there is an optional service that handles system-level notifications nsCOMPtr sysAlerts(do_GetService(NS_SYSTEMALERTSERVICE_CONTRACTID)); - nsresult rv; if (sysAlerts) { - rv = sysAlerts->ShowAlertNotification(aImageUrl, aAlertTitle, aAlertText, aAlertTextClickable, - aAlertCookie, aAlertListener, aAlertName, - aBidi, aLang, aData, - IPC::Principal(aPrincipal), - aInPrivateBrowsing); + rv = sysAlerts->ShowAlert(aAlert, aAlertListener); if (NS_SUCCEEDED(rv)) return NS_OK; } @@ -113,14 +140,30 @@ NS_IMETHODIMP nsAlertsService::ShowAlertNotification(const nsAString & aImageUrl if (!ShouldShowAlert()) { // Do not display the alert. Instead call alertfinished and get out. if (aAlertListener) - aAlertListener->Observe(nullptr, "alertfinished", PromiseFlatString(aAlertCookie).get()); + aAlertListener->Observe(nullptr, "alertfinished", cookie.get()); return NS_OK; } + bool textClickable; + rv = aAlert->GetTextClickable(&textClickable); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString bidi; + rv = aAlert->GetDir(bidi); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString lang; + rv = aAlert->GetLang(lang); + NS_ENSURE_SUCCESS(rv, rv); + + bool inPrivateBrowsing; + rv = aAlert->GetInPrivateBrowsing(&inPrivateBrowsing); + NS_ENSURE_SUCCESS(rv, rv); + // Use XUL notifications as a fallback if above methods have failed. - rv = mXULAlerts.ShowAlertNotification(aImageUrl, aAlertTitle, aAlertText, aAlertTextClickable, - aAlertCookie, aAlertListener, aAlertName, - aBidi, aLang, aPrincipal, aInPrivateBrowsing); + rv = mXULAlerts.ShowAlertNotification(imageUrl, title, text, textClickable, + cookie, aAlertListener, name, + bidi, lang, principal, inPrivateBrowsing); return rv; #endif // !MOZ_WIDGET_ANDROID } @@ -177,15 +220,19 @@ NS_IMETHODIMP nsAlertsService::SetManualDoNotDisturb(bool aDoNotDisturb) #else // Try the system notification service. nsCOMPtr sysAlerts(do_GetService(NS_SYSTEMALERTSERVICE_CONTRACTID)); + nsresult rv; if (sysAlerts) { nsCOMPtr alertsDND(do_GetService(NS_SYSTEMALERTSERVICE_CONTRACTID)); if (!alertsDND) { return NS_ERROR_NOT_IMPLEMENTED; } - return alertsDND->SetManualDoNotDisturb(aDoNotDisturb); + rv = alertsDND->SetManualDoNotDisturb(aDoNotDisturb); + } else { + rv = mXULAlerts.SetManualDoNotDisturb(aDoNotDisturb); } - return mXULAlerts.SetManualDoNotDisturb(aDoNotDisturb); + Telemetry::Accumulate(Telemetry::ALERTS_SERVICE_DND_ENABLED, 1); + return rv; #endif } diff --git a/toolkit/components/alerts/nsIAlertsService.idl b/toolkit/components/alerts/nsIAlertsService.idl index 5ebc8586df..ac703d019f 100644 --- a/toolkit/components/alerts/nsIAlertsService.idl +++ b/toolkit/components/alerts/nsIAlertsService.idl @@ -8,9 +8,96 @@ interface nsIPrincipal; -[scriptable, uuid(9d0284bf-db40-42da-8f0d-c2769dbde7aa)] +%{C++ +#define ALERT_NOTIFICATION_CONTRACTID "@mozilla.org/alert-notification;1" +%} + +[scriptable, uuid(b26b4a67-81b0-4270-8311-1e00a097ef92)] +interface nsIAlertNotification : nsISupports +{ + /** Initializes an alert notification. */ + void init([optional] in AString name, + [optional] in AString imageURL, + [optional] in AString title, + [optional] in AString text, + [optional] in boolean textClickable, + [optional] in AString cookie, + [optional] in AString dir, + [optional] in AString lang, + [optional] in AString data, + [optional] in nsIPrincipal principal, + [optional] in boolean inPrivateBrowsing); + + /** + * The name of the notification. This is currently only used on Android and + * OS X. On Android, the name is hashed and used as a notification ID. + * Notifications will replace previous notifications with the same name. + */ + readonly attribute AString name; + + /** + * A URL identifying the image to put in the alert. The OS X backend limits + * the amount of time it will wait for the image to load to six seconds. After + * that time, the alert will show without an image. + */ + readonly attribute AString imageURL; + + /** The title for the alert. */ + readonly attribute AString title; + + /** The contents of the alert. */ + readonly attribute AString text; + + /** + * Controls the click behavior. If true, the alert listener will be notified + * when the user clicks on the alert. + */ + readonly attribute boolean textClickable; + + /** + * An opaque cookie that will be passed to the alert listener for each + * callback. + */ + readonly attribute AString cookie; + + /** + * Bidi override for the title and contents. Valid values are "auto", "ltr", + * or "rtl". Ignored if the backend doesn't support localization. + */ + readonly attribute AString dir; + + /** + * Language of the title and text. Ignored if the backend doesn't support + * localization. + */ + readonly attribute AString lang; + + /** + * A Base64-encoded structured clone buffer containing data associated with + * this alert. Only used for web notifications. Chrome callers should use a + * cookie instead. + */ + readonly attribute AString data; + + /** + * The principal of the page that created the alert. Used for IPC security + * checks, and to determine whether the alert should show the source string + * and action buttons. + */ + readonly attribute nsIPrincipal principal; + + /** + * Controls the image loading behavior. If true, the image URL will be loaded + * in private browsing mode. + */ + readonly attribute boolean inPrivateBrowsing; +}; + +[scriptable, uuid(f7a36392-d98b-4141-a7d7-4e46642684e3)] interface nsIAlertsService : nsISupports { + void showAlert(in nsIAlertNotification alert, + [optional] in nsIObserver alertListener); /** * Displays a sliding notification window. * diff --git a/toolkit/components/alerts/nsXULAlerts.cpp b/toolkit/components/alerts/nsXULAlerts.cpp index c10789a49e..a57b96edf7 100644 --- a/toolkit/components/alerts/nsXULAlerts.cpp +++ b/toolkit/components/alerts/nsXULAlerts.cpp @@ -7,6 +7,8 @@ #include "nsAutoPtr.h" #include "mozilla/LookAndFeel.h" +#include "mozilla/dom/Notification.h" +#include "mozilla/unused.h" #include "nsIServiceManager.h" #include "nsAlertsUtils.h" #include "nsISupportsArray.h" @@ -15,6 +17,7 @@ #include "nsIWindowWatcher.h" using namespace mozilla; +using mozilla::dom::NotificationTelemetryService; #define ALERT_CHROME_URL "chrome://global/content/alerts/alert.xul" @@ -49,6 +52,17 @@ nsXULAlerts::ShowAlertNotification(const nsAString& aImageUrl, const nsAString& bool aInPrivateBrowsing) { if (mDoNotDisturb) { + if (!aInPrivateBrowsing) { + RefPtr telemetry = + NotificationTelemetryService::GetInstance(); + if (telemetry) { + // Record the number of unique senders for XUL alerts. The OS X and + // libnotify backends will fire `alertshow` even if "do not disturb" + // is enabled. In that case, `NotificationObserver` will record the + // sender. + Unused << NS_WARN_IF(NS_FAILED(telemetry->RecordSender(aPrincipal))); + } + } return NS_OK; } diff --git a/toolkit/components/alerts/resources/content/alert.js b/toolkit/components/alerts/resources/content/alert.js index 254a8afabb..57f65fc439 100644 --- a/toolkit/components/alerts/resources/content/alert.js +++ b/toolkit/components/alerts/resources/content/alert.js @@ -276,6 +276,8 @@ function doNotDisturb() { .getService(Ci.nsIAlertsService) .QueryInterface(Ci.nsIAlertsDoNotDisturb); alertService.manualDoNotDisturb = true; + Services.telemetry.getHistogramById("WEB_NOTIFICATION_MENU") + .add(0); onAlertClose(); } diff --git a/toolkit/components/build/nsToolkitCompsCID.h b/toolkit/components/build/nsToolkitCompsCID.h index 34742c7916..1f2075d260 100644 --- a/toolkit/components/build/nsToolkitCompsCID.h +++ b/toolkit/components/build/nsToolkitCompsCID.h @@ -94,6 +94,9 @@ ///////////////////////////////////////////////////////////////////////////// +#define ALERT_NOTIFICATION_CID \ +{ 0x9a7b7a41, 0x0b47, 0x47f7, { 0xb6, 0x1b, 0x15, 0xa2, 0x10, 0xd6, 0xf0, 0x20 } } + // {A0CCAAF8-09DA-44D8-B250-9AC3E93C8117} #define NS_ALERTSSERVICE_CID \ { 0xa0ccaaf8, 0x9da, 0x44d8, { 0xb2, 0x50, 0x9a, 0xc3, 0xe9, 0x3c, 0x81, 0x17 } } diff --git a/toolkit/components/build/nsToolkitCompsModule.cpp b/toolkit/components/build/nsToolkitCompsModule.cpp index f1c76081b3..b701daee21 100644 --- a/toolkit/components/build/nsToolkitCompsModule.cpp +++ b/toolkit/components/build/nsToolkitCompsModule.cpp @@ -16,6 +16,7 @@ #include "nsParentalControlsService.h" #endif +#include "mozilla/AlertNotification.h" #include "nsAlertsService.h" #include "nsDownloadManager.h" @@ -65,6 +66,7 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsFindService) NS_GENERIC_FACTORY_CONSTRUCTOR(nsParentalControlsService) #endif +NS_GENERIC_FACTORY_CONSTRUCTOR(AlertNotification) NS_GENERIC_FACTORY_CONSTRUCTOR(nsAlertsService) NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsDownloadManager, @@ -90,6 +92,7 @@ NS_DEFINE_NAMED_CID(NS_TOOLKIT_PERFORMANCESTATSSERVICE_CID); NS_DEFINE_NAMED_CID(NS_TOOLKIT_TERMINATOR_CID); #endif NS_DEFINE_NAMED_CID(NS_USERINFO_CID); +NS_DEFINE_NAMED_CID(ALERT_NOTIFICATION_CID); NS_DEFINE_NAMED_CID(NS_ALERTSSERVICE_CID); #if !defined(MOZ_DISABLE_PARENTAL_CONTROLS) NS_DEFINE_NAMED_CID(NS_PARENTALCONTROLSSERVICE_CID); @@ -115,6 +118,7 @@ static const Module::CIDEntry kToolkitCIDs[] = { #endif { &kNS_TOOLKIT_PERFORMANCESTATSSERVICE_CID, false, nullptr, nsPerformanceStatsServiceConstructor }, { &kNS_USERINFO_CID, false, nullptr, nsUserInfoConstructor }, + { &kALERT_NOTIFICATION_CID, false, nullptr, AlertNotificationConstructor }, { &kNS_ALERTSSERVICE_CID, false, nullptr, nsAlertsServiceConstructor }, #if !defined(MOZ_DISABLE_PARENTAL_CONTROLS) { &kNS_PARENTALCONTROLSSERVICE_CID, false, nullptr, nsParentalControlsServiceConstructor }, @@ -142,6 +146,7 @@ static const Module::ContractIDEntry kToolkitContracts[] = { #endif { NS_TOOLKIT_PERFORMANCESTATSSERVICE_CONTRACTID, &kNS_TOOLKIT_PERFORMANCESTATSSERVICE_CID }, { NS_USERINFO_CONTRACTID, &kNS_USERINFO_CID }, + { ALERT_NOTIFICATION_CONTRACTID, &kALERT_NOTIFICATION_CID }, { NS_ALERTSERVICE_CONTRACTID, &kNS_ALERTSSERVICE_CID }, #if !defined(MOZ_DISABLE_PARENTAL_CONTROLS) { NS_PARENTALCONTROLSSERVICE_CONTRACTID, &kNS_PARENTALCONTROLSSERVICE_CID }, diff --git a/toolkit/components/osfile/NativeOSFileInternals.cpp b/toolkit/components/osfile/NativeOSFileInternals.cpp index ea5a4809c6..571c24c6be 100644 --- a/toolkit/components/osfile/NativeOSFileInternals.cpp +++ b/toolkit/components/osfile/NativeOSFileInternals.cpp @@ -84,7 +84,7 @@ struct ScopedArrayBufferContentsTraits { type result = {0, 0}; return result; } - const static void release(type ptr) { + static void release(type ptr) { js_free(ptr.data); ptr.data = nullptr; ptr.nbytes = 0; diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 37f629b914..0c0ef4a1cd 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -9142,6 +9142,28 @@ "kind": "flag", "description": "Flag activated whenever a youtube flash embed is seen during a session." }, + "WEB_NOTIFICATION_CLICKED": { + "alert_emails": ["firefox-dev@mozilla.org"], + "bug_numbers": [1225336], + "expires_in_version": "50", + "kind": "count", + "description": "Count of times a web notification was clicked" + }, + "WEB_NOTIFICATION_MENU": { + "alert_emails": ["firefox-dev@mozilla.org"], + "bug_numbers": [1225336], + "expires_in_version": "50", + "kind": "enumerated", + "n_values": 5, + "description": "Count of times a contextual menu item was used from a Notification (0: DND, 1: Disable, 2: Settings)" + }, + "WEB_NOTIFICATION_SHOWN": { + "alert_emails": ["firefox-dev@mozilla.org"], + "bug_numbers": [1225336], + "expires_in_version": "50", + "kind": "count", + "description": "Count of times a Notification was rendered (accounting for XUL DND). A system backend may put the notification directly into the tray if its own DND is on." + }, "WEBFONT_DOWNLOAD_TIME": { "alert_emails": ["jdaggett@mozilla.com"], "expires_in_version": "never", @@ -9214,5 +9236,49 @@ "kind": "boolean", "bug_numbers": [1188391], "description": "The number of ICE connections which immediately failed (0) vs. reached at least checking state (1)." + }, + "ALERTS_SERVICE_DND_ENABLED": { + "alert_emails": ["firefox-dev@mozilla.org"], + "bug_numbers": [1219030], + "expires_in_version": "50", + "kind": "boolean", + "description": "XUL-only: whether the user has toggled do not disturb." + }, + "ALERTS_SERVICE_DND_SUPPORTED_FLAG": { + "alert_emails": ["firefox-dev@mozilla.org"], + "bug_numbers": [1219030], + "expires_in_version": "50", + "kind": "flag", + "description": "Whether the do not disturb option is supported. True if the browser uses XUL alerts." + }, + "WEB_NOTIFICATION_EXCEPTIONS_OPENED": { + "alert_emails": ["firefox-dev@mozilla.org"], + "bug_numbers": [1219030], + "expires_in_version": "50", + "kind": "count", + "description": "Number of times the Notification Permissions dialog has been opened." + }, + "WEB_NOTIFICATION_PERMISSIONS": { + "alert_emails": ["firefox-dev@mozilla.org"], + "bug_numbers": [1219030], + "expires_in_version": "50", + "kind": "enumerated", + "n_values": 10, + "description": "Number of origins with the web notifications permission (0 = denied, 1 = allowed)." + }, + "WEB_NOTIFICATION_PERMISSION_REMOVED": { + "alert_emails": ["firefox-dev@mozilla.org"], + "bug_numbers": [1219030], + "expires_in_version": "50", + "kind": "enumerated", + "n_values": 10, + "description": "Number of removed web notifications permissions (0 = remove deny, 1 = remove allow)." + }, + "WEB_NOTIFICATION_SENDERS": { + "alert_emails": ["firefox-dev@mozilla.org"], + "bug_numbers": [1219030], + "expires_in_version": "50", + "kind": "count", + "description": "Number of origins that have shown a web notification. Excludes system alerts like update reminders and add-ons." } } diff --git a/toolkit/mozapps/update/updater/automounter_gonk.h b/toolkit/mozapps/update/updater/automounter_gonk.h index 1300d39003..e40cacbc2e 100644 --- a/toolkit/mozapps/update/updater/automounter_gonk.h +++ b/toolkit/mozapps/update/updater/automounter_gonk.h @@ -27,7 +27,7 @@ public: GonkAutoMounter(); ~GonkAutoMounter(); - const MountAccess GetAccess() + MountAccess GetAccess() const { return mAccess; } diff --git a/toolkit/system/gnome/nsAlertsIconListener.cpp b/toolkit/system/gnome/nsAlertsIconListener.cpp index 6f04e10e7f..97eca76347 100644 --- a/toolkit/system/gnome/nsAlertsIconListener.cpp +++ b/toolkit/system/gnome/nsAlertsIconListener.cpp @@ -9,6 +9,7 @@ #include "imgIRequest.h" #include "nsNetUtil.h" #include "nsServiceManagerUtils.h" +#include "nsIAlertsService.h" #include "nsIImageToPixbuf.h" #include "nsIStringBundle.h" #include "nsIObserverService.h" @@ -186,6 +187,11 @@ nsAlertsIconListener::ShowAlert(GdkPixbuf* aPixbuf) if (!mNotification) return NS_ERROR_OUT_OF_MEMORY; + nsCOMPtr obsServ = + do_GetService("@mozilla.org/observer-service;1"); + if (obsServ) + obsServ->AddObserver(this, "quit-application", true); + if (aPixbuf) notify_notification_set_icon_from_pixbuf(mNotification, aPixbuf); @@ -277,13 +283,8 @@ nsAlertsIconListener::Observe(nsISupports *aSubject, const char *aTopic, } nsresult -nsAlertsIconListener::InitAlertAsync(const nsAString & aImageUrl, - const nsAString & aAlertTitle, - const nsAString & aAlertText, - bool aAlertTextClickable, - const nsAString & aAlertCookie, - nsIObserver * aAlertListener, - bool aInPrivateBrowsing) +nsAlertsIconListener::InitAlertAsync(nsIAlertNotification* aAlert, + nsIObserver* aAlertListener) { if (!libNotifyHandle) return NS_ERROR_FAILURE; @@ -335,27 +336,38 @@ nsAlertsIconListener::InitAlertAsync(const nsAString & aImageUrl, return NS_ERROR_FAILURE; } - if (!gHasActions && aAlertTextClickable) + nsresult rv = aAlert->GetTextClickable(&mAlertHasAction); + NS_ENSURE_SUCCESS(rv, rv); + if (!gHasActions && mAlertHasAction) return NS_ERROR_FAILURE; // No good, fallback to XUL - nsCOMPtr obsServ = - do_GetService("@mozilla.org/observer-service;1"); - if (obsServ) - obsServ->AddObserver(this, "quit-application", true); - + nsAutoString title; + rv = aAlert->GetTitle(title); + NS_ENSURE_SUCCESS(rv, rv); // Workaround for a libnotify bug - blank titles aren't dealt with // properly so we use a space - if (aAlertTitle.IsEmpty()) { + if (title.IsEmpty()) { mAlertTitle = NS_LITERAL_CSTRING(" "); } else { - mAlertTitle = NS_ConvertUTF16toUTF8(aAlertTitle); + mAlertTitle = NS_ConvertUTF16toUTF8(title); } - mAlertText = NS_ConvertUTF16toUTF8(aAlertText); - mAlertHasAction = aAlertTextClickable; + nsAutoString text; + rv = aAlert->GetText(text); + NS_ENSURE_SUCCESS(rv, rv); + mAlertText = NS_ConvertUTF16toUTF8(text); mAlertListener = aAlertListener; - mAlertCookie = aAlertCookie; - return StartRequest(aImageUrl, aInPrivateBrowsing); + rv = aAlert->GetCookie(mAlertCookie); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString imageUrl; + rv = aAlert->GetImageURL(imageUrl); + NS_ENSURE_SUCCESS(rv, rv); + bool inPrivateBrowsing; + rv = aAlert->GetInPrivateBrowsing(&inPrivateBrowsing); + NS_ENSURE_SUCCESS(rv, rv); + + return StartRequest(imageUrl, inPrivateBrowsing); } diff --git a/toolkit/system/gnome/nsAlertsIconListener.h b/toolkit/system/gnome/nsAlertsIconListener.h index 296a50ac23..8848f0da0f 100644 --- a/toolkit/system/gnome/nsAlertsIconListener.h +++ b/toolkit/system/gnome/nsAlertsIconListener.h @@ -15,6 +15,7 @@ #include class imgIRequest; +class nsIAlertNotification; struct NotifyNotification; @@ -29,13 +30,8 @@ public: nsAlertsIconListener(); - nsresult InitAlertAsync(const nsAString & aImageUrl, - const nsAString & aAlertTitle, - const nsAString & aAlertText, - bool aAlertTextClickable, - const nsAString & aAlertCookie, - nsIObserver * aAlertListener, - bool aInPrivateBrowsing); + nsresult InitAlertAsync(nsIAlertNotification* aAlert, + nsIObserver* aAlertListener); void SendCallback(); void SendClosed(); diff --git a/toolkit/system/gnome/nsSystemAlertsService.cpp b/toolkit/system/gnome/nsSystemAlertsService.cpp index fe7eca5415..8b0bf6856f 100644 --- a/toolkit/system/gnome/nsSystemAlertsService.cpp +++ b/toolkit/system/gnome/nsSystemAlertsService.cpp @@ -3,6 +3,7 @@ * 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 "nsComponentManagerUtils.h" #include "nsXULAppAPI.h" #include "nsSystemAlertsService.h" #include "nsAlertsIconListener.h" @@ -40,12 +41,27 @@ NS_IMETHODIMP nsSystemAlertsService::ShowAlertNotification(const nsAString & aIm nsIPrincipal * aPrincipal, bool aInPrivateBrowsing) { + nsCOMPtr alert = + do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID); + NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE); + nsresult rv = alert->Init(aAlertName, aImageUrl, aAlertTitle, + aAlertText, aAlertTextClickable, + aAlertCookie, aBidi, aLang, aData, + aPrincipal, aInPrivateBrowsing); + NS_ENSURE_SUCCESS(rv, rv); + return ShowAlert(alert, aAlertListener); +} + +NS_IMETHODIMP nsSystemAlertsService::ShowAlert(nsIAlertNotification* aAlert, + nsIObserver* aAlertListener) +{ + NS_ENSURE_ARG(aAlert); + RefPtr alertListener = new nsAlertsIconListener(); if (!alertListener) return NS_ERROR_OUT_OF_MEMORY; - return alertListener->InitAlertAsync(aImageUrl, aAlertTitle, aAlertText, aAlertTextClickable, - aAlertCookie, aAlertListener, aInPrivateBrowsing); + return alertListener->InitAlertAsync(aAlert, aAlertListener); } NS_IMETHODIMP nsSystemAlertsService::CloseAlert(const nsAString& aAlertName, diff --git a/tools/profiler/lul/AutoObjectMapper.cpp b/tools/profiler/lul/AutoObjectMapper.cpp new file mode 100644 index 0000000000..b95fa19f02 --- /dev/null +++ b/tools/profiler/lul/AutoObjectMapper.cpp @@ -0,0 +1,206 @@ +/* -*- 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 +#include +#include +#include +#include + +#include "mozilla/Assertions.h" + +#include "PlatformMacros.h" +#include "AutoObjectMapper.h" + +#if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK) +# include +# include "mozilla/Types.h" + // FIXME move these out of mozglue/linker/ElfLoader.h into their + // own header, so as to avoid conflicts arising from two definitions + // of Array + extern "C" { + MFBT_API size_t + __dl_get_mappable_length(void *handle); + MFBT_API void * + __dl_mmap(void *handle, void *addr, size_t length, off_t offset); + MFBT_API void + __dl_munmap(void *handle, void *addr, size_t length); + } + // The following are for get_installation_lib_dir() +# include "nsString.h" +# include "nsDirectoryServiceUtils.h" +# include "nsDirectoryServiceDefs.h" +#endif + + +// A helper function for creating failure error messages in +// AutoObjectMapper*::Map. +static void +failedToMessage(void(*aLog)(const char*), + const char* aHowFailed, std::string aFileName) +{ + char buf[300]; + snprintf(buf, sizeof(buf), "AutoObjectMapper::Map: Failed to %s \'%s\'", + aHowFailed, aFileName.c_str()); + buf[sizeof(buf)-1] = 0; + aLog(buf); +} + + +AutoObjectMapperPOSIX::AutoObjectMapperPOSIX(void(*aLog)(const char*)) + : mImage(nullptr) + , mSize(0) + , mLog(aLog) + , mIsMapped(false) +{} + +AutoObjectMapperPOSIX::~AutoObjectMapperPOSIX() { + if (!mIsMapped) { + // There's nothing to do. + MOZ_ASSERT(!mImage); + MOZ_ASSERT(mSize == 0); + return; + } + MOZ_ASSERT(mSize > 0); + // The following assertion doesn't necessarily have to be true, + // but we assume (reasonably enough) that no mmap facility would + // be crazy enough to map anything at page zero. + MOZ_ASSERT(mImage); + munmap(mImage, mSize); +} + +bool AutoObjectMapperPOSIX::Map(/*OUT*/void** start, /*OUT*/size_t* length, + std::string fileName) +{ + MOZ_ASSERT(!mIsMapped); + + int fd = open(fileName.c_str(), O_RDONLY); + if (fd == -1) { + failedToMessage(mLog, "open", fileName); + return false; + } + + struct stat st; + int err = fstat(fd, &st); + size_t sz = (err == 0) ? st.st_size : 0; + if (err != 0 || sz == 0) { + failedToMessage(mLog, "fstat", fileName); + close(fd); + return false; + } + + void* image = mmap(nullptr, sz, PROT_READ, MAP_SHARED, fd, 0); + if (image == MAP_FAILED) { + failedToMessage(mLog, "mmap", fileName); + close(fd); + return false; + } + + close(fd); + mIsMapped = true; + mImage = *start = image; + mSize = *length = sz; + return true; +} + + +#if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK) +// A helper function for AutoObjectMapperFaultyLib::Map. Finds out +// where the installation's lib directory is, since we'll have to look +// in there to get hold of libmozglue.so. Returned C string is heap +// allocated and the caller must deallocate it. +static char* +get_installation_lib_dir() +{ + nsCOMPtr + directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID)); + if (!directoryService) { + return nullptr; + } + nsCOMPtr greDir; + nsresult rv = directoryService->Get(NS_GRE_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(greDir)); + if (NS_FAILED(rv)) return nullptr; + nsCString path; + rv = greDir->GetNativePath(path); + if (NS_FAILED(rv)) { + return nullptr; + } + return strdup(path.get()); +} + +AutoObjectMapperFaultyLib::AutoObjectMapperFaultyLib(void(*aLog)(const char*)) + : AutoObjectMapperPOSIX(aLog) + , mHdl(nullptr) +{} + +AutoObjectMapperFaultyLib::~AutoObjectMapperFaultyLib() { + if (mHdl) { + // We've got an object mapped by faulty.lib. Unmap it via faulty.lib. + MOZ_ASSERT(mSize > 0); + // Assert on the basis that no valid mapping would start at page zero. + MOZ_ASSERT(mImage); + __dl_munmap(mHdl, mImage, mSize); + dlclose(mHdl); + // Stop assertions in ~AutoObjectMapperPOSIX from failing. + mImage = nullptr; + mSize = 0; + } + // At this point the parent class destructor, ~AutoObjectMapperPOSIX, + // gets called. If that has something mapped in the normal way, it + // will unmap it in the normal way. Unfortunately there's no + // obvious way to enforce the requirement that the object is mapped + // either by faulty.lib or by the parent class, but not by both. +} + +bool AutoObjectMapperFaultyLib::Map(/*OUT*/void** start, /*OUT*/size_t* length, + std::string fileName) +{ + MOZ_ASSERT(!mHdl); + + if (fileName == "libmozglue.so") { + + // Do (2) in the comment above. + char* libdir = get_installation_lib_dir(); + if (libdir) { + fileName = std::string(libdir) + "/lib/" + fileName; + free(libdir); + } + // Hand the problem off to the standard mapper. + return AutoObjectMapperPOSIX::Map(start, length, fileName); + + } else { + + // Do cases (1) and (3) in the comment above. We have to + // grapple with faulty.lib directly. + void* hdl = dlopen(fileName.c_str(), RTLD_GLOBAL | RTLD_LAZY); + if (!hdl) { + failedToMessage(mLog, "get handle for ELF file", fileName); + return false; + } + + size_t sz = __dl_get_mappable_length(hdl); + if (sz == 0) { + dlclose(hdl); + failedToMessage(mLog, "get size for ELF file", fileName); + return false; + } + + void* image = __dl_mmap(hdl, nullptr, sz, 0); + if (image == MAP_FAILED) { + dlclose(hdl); + failedToMessage(mLog, "mmap ELF file", fileName); + return false; + } + + mHdl = hdl; + mImage = *start = image; + mSize = *length = sz; + return true; + } +} + +#endif // defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK) diff --git a/tools/profiler/lul/AutoObjectMapper.h b/tools/profiler/lul/AutoObjectMapper.h new file mode 100644 index 0000000000..1f813d6f28 --- /dev/null +++ b/tools/profiler/lul/AutoObjectMapper.h @@ -0,0 +1,115 @@ +/* -*- 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 AutoObjectMapper_h +#define AutoObjectMapper_h + +#include + +#include "mozilla/Attributes.h" +#include "PlatformMacros.h" + +// A (nearly-) RAII class that maps an object in and then unmaps it on +// destruction. This base class version uses the "normal" POSIX +// functions: open, fstat, close, mmap, munmap. + +class MOZ_STACK_CLASS AutoObjectMapperPOSIX { +public: + // The constructor does not attempt to map the file, because that + // might fail. Instead, once the object has been constructed, + // call Map() to attempt the mapping. There is no corresponding + // Unmap() since the unmapping is done in the destructor. Failure + // messages are sent to |aLog|. + explicit AutoObjectMapperPOSIX(void(*aLog)(const char*)); + + // Unmap the file on destruction of this object. + ~AutoObjectMapperPOSIX(); + + // Map |fileName| into the address space and return the mapping + // extents. If the file is zero sized this will fail. The file is + // mapped read-only and private. Returns true iff the mapping + // succeeded, in which case *start and *length hold its extent. + // Once a call to Map succeeds, all subsequent calls to it will + // fail. + bool Map(/*OUT*/void** start, /*OUT*/size_t* length, std::string fileName); + +protected: + // If we are currently holding a mapped object, these record the + // mapped address range. + void* mImage; + size_t mSize; + + // A logging sink, for complaining about mapping failures. + void (*mLog)(const char*); + +private: + // Are we currently holding a mapped object? This is private to + // the base class. Derived classes need to have their own way to + // track whether they are holding a mapped object. + bool mIsMapped; + + // Disable copying and assignment. + AutoObjectMapperPOSIX(const AutoObjectMapperPOSIX&); + AutoObjectMapperPOSIX& operator=(const AutoObjectMapperPOSIX&); + // Disable heap allocation of this class. + void* operator new(size_t); + void* operator new[](size_t); + void operator delete(void*); + void operator delete[](void*); +}; + + +#if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK) +// This is a variant of AutoObjectMapperPOSIX suitable for use in +// conjunction with faulty.lib on Android. How it behaves depends on +// the name of the file to be mapped. There are three possible cases: +// +// (1) /foo/bar/xyzzy/blah.apk!/libwurble.so +// We hand it as-is to faulty.lib and let it fish the relevant +// bits out of the APK. +// +// (2) libmozglue.so +// This is part of the Fennec installation, but is not in the +// APK. Instead we have to figure out the installation path +// and look for it there. Because of faulty.lib limitations, +// we have to use regular open/mmap instead of faulty.lib. +// +// (3) libanythingelse.so +// faulty.lib assumes this is a system library, and prepends +// "/system/lib/" to the path. So as in (1), we can give it +// as-is to faulty.lib. +// +// Hence (1) and (3) require special-casing here. Case (2) simply +// hands the problem to the parent class. + +class MOZ_STACK_CLASS AutoObjectMapperFaultyLib : public AutoObjectMapperPOSIX { +public: + AutoObjectMapperFaultyLib(void(*aLog)(const char*)); + + ~AutoObjectMapperFaultyLib(); + + bool Map(/*OUT*/void** start, /*OUT*/size_t* length, std::string fileName); + +private: + // faulty.lib requires us to maintain an abstract handle that can be + // used later to unmap the area. If this is non-NULL, it is assumed + // that unmapping is to be done by faulty.lib. Otherwise it goes + // via the normal mechanism. + void* mHdl; + + // Disable copying and assignment. + AutoObjectMapperFaultyLib(const AutoObjectMapperFaultyLib&); + AutoObjectMapperFaultyLib& operator=(const AutoObjectMapperFaultyLib&); + // Disable heap allocation of this class. + void* operator new(size_t); + void* operator new[](size_t); + void operator delete(void*); + void operator delete[](void*); +}; + +#endif // defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK) + +#endif // AutoObjectMapper_h diff --git a/tools/profiler/lul/LulCommon.cpp b/tools/profiler/lul/LulCommon.cpp new file mode 100644 index 0000000000..7321251c87 --- /dev/null +++ b/tools/profiler/lul/LulCommon.cpp @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ + +// Copyright (c) 2011, 2013 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Original author: Jim Blandy + + +// This file is derived from the following files in +// toolkit/crashreporter/google-breakpad: +// src/common/module.cc +// src/common/unique_string.cc + +// There's no internal-only interface for LulCommon. Hence include +// the external interface directly. +#include "LulCommonExt.h" + +#include +#include + +#include +#include + + +namespace lul { + +using std::string; + +//////////////////////////////////////////////////////////////// +// Module +// +Module::Module(const string &name, const string &os, + const string &architecture, const string &id) : + name_(name), + os_(os), + architecture_(architecture), + id_(id) { } + +Module::~Module() { +} + + +//////////////////////////////////////////////////////////////// +// UniqueString +// +class UniqueString { + public: + explicit UniqueString(string str) { str_ = strdup(str.c_str()); } + ~UniqueString() { free(reinterpret_cast(const_cast(str_))); } + const char* str_; +}; + +const char* FromUniqueString(const UniqueString* ustr) +{ + return ustr->str_; +} + +bool IsEmptyUniqueString(const UniqueString* ustr) +{ + return (ustr->str_)[0] == '\0'; +} + + +//////////////////////////////////////////////////////////////// +// UniqueStringUniverse +// +UniqueStringUniverse::~UniqueStringUniverse() +{ + for (std::map::iterator it = map_.begin(); + it != map_.end(); it++) { + delete it->second; + } +} + +const UniqueString* UniqueStringUniverse::ToUniqueString(string str) +{ + std::map::iterator it = map_.find(str); + if (it == map_.end()) { + UniqueString* ustr = new UniqueString(str); + map_[str] = ustr; + return ustr; + } else { + return it->second; + } +} + +} // namespace lul diff --git a/tools/profiler/lul/LulCommonExt.h b/tools/profiler/lul/LulCommonExt.h new file mode 100644 index 0000000000..99a9676838 --- /dev/null +++ b/tools/profiler/lul/LulCommonExt.h @@ -0,0 +1,554 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ + +// Copyright (c) 2006, 2010, 2012, 2013 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Original author: Jim Blandy + +// module.h: Define google_breakpad::Module. A Module holds debugging +// information, and can write that information out as a Breakpad +// symbol file. + + +// (C) Copyright Greg Colvin and Beman Dawes 1998, 1999. +// Copyright (c) 2001, 2002 Peter Dimov +// +// Permission to copy, use, modify, sell and distribute this software +// is granted provided this copyright notice appears in all copies. +// This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// See http://www.boost.org/libs/smart_ptr/scoped_ptr.htm for documentation. +// + + +// This file is derived from the following files in +// toolkit/crashreporter/google-breakpad: +// src/common/unique_string.h +// src/common/scoped_ptr.h +// src/common/module.h + +// External interface for the "Common" component of LUL. + +#ifndef LulCommonExt_h +#define LulCommonExt_h + +#include +#include +#include + +#include +#include +#include +#include // for std::ptrdiff_t + +#include "mozilla/Assertions.h" + +namespace lul { + +using std::string; +using std::map; + + +//////////////////////////////////////////////////////////////// +// UniqueString +// + +// Abstract type +class UniqueString; + +// Get the contained C string (debugging only) +const char* FromUniqueString(const UniqueString*); + +// Is the given string empty (that is, "") ? +bool IsEmptyUniqueString(const UniqueString*); + + +//////////////////////////////////////////////////////////////// +// UniqueStringUniverse +// + +// All UniqueStrings live in some specific UniqueStringUniverse. +class UniqueStringUniverse { +public: + UniqueStringUniverse() {} + ~UniqueStringUniverse(); + // Convert a |string| to a UniqueString, that lives in this universe. + const UniqueString* ToUniqueString(string str); +private: + map map_; +}; + + +//////////////////////////////////////////////////////////////// +// GUID +// + +typedef struct { + uint32_t data1; + uint16_t data2; + uint16_t data3; + uint8_t data4[8]; +} MDGUID; // GUID + +typedef MDGUID GUID; + + +//////////////////////////////////////////////////////////////// +// scoped_ptr +// + +// scoped_ptr mimics a built-in pointer except that it guarantees deletion +// of the object pointed to, either on destruction of the scoped_ptr or via +// an explicit reset(). scoped_ptr is a simple solution for simple needs; +// use shared_ptr or std::auto_ptr if your needs are more complex. + +// *** NOTE *** +// If your scoped_ptr is a class member of class FOO pointing to a +// forward declared type BAR (as shown below), then you MUST use a non-inlined +// version of the destructor. The destructor of a scoped_ptr (called from +// FOO's destructor) must have a complete definition of BAR in order to +// destroy it. Example: +// +// -- foo.h -- +// class BAR; +// +// class FOO { +// public: +// FOO(); +// ~FOO(); // Required for sources that instantiate class FOO to compile! +// +// private: +// scoped_ptr bar_; +// }; +// +// -- foo.cc -- +// #include "foo.h" +// FOO::~FOO() {} // Empty, but must be non-inlined to FOO's class definition. + +// scoped_ptr_malloc added by Google +// When one of these goes out of scope, instead of doing a delete or +// delete[], it calls free(). scoped_ptr_malloc is likely to see +// much more use than any other specializations. + +// release() added by Google +// Use this to conditionally transfer ownership of a heap-allocated object +// to the caller, usually on method success. + +template +class scoped_ptr { + private: + + T* ptr; + + scoped_ptr(scoped_ptr const &); + scoped_ptr & operator=(scoped_ptr const &); + + public: + + typedef T element_type; + + explicit scoped_ptr(T* p = 0): ptr(p) {} + + ~scoped_ptr() { + delete ptr; + } + + void reset(T* p = 0) { + if (ptr != p) { + delete ptr; + ptr = p; + } + } + + T& operator*() const { + MOZ_ASSERT(ptr != 0); + return *ptr; + } + + T* operator->() const { + MOZ_ASSERT(ptr != 0); + return ptr; + } + + bool operator==(T* p) const { + return ptr == p; + } + + bool operator!=(T* p) const { + return ptr != p; + } + + T* get() const { + return ptr; + } + + void swap(scoped_ptr & b) { + T* tmp = b.ptr; + b.ptr = ptr; + ptr = tmp; + } + + T* release() { + T* tmp = ptr; + ptr = 0; + return tmp; + } + + private: + + // no reason to use these: each scoped_ptr should have its own object + template bool operator==(scoped_ptr const& p) const; + template bool operator!=(scoped_ptr const& p) const; +}; + +template inline +void swap(scoped_ptr& a, scoped_ptr& b) { + a.swap(b); +} + +template inline +bool operator==(T* p, const scoped_ptr& b) { + return p == b.get(); +} + +template inline +bool operator!=(T* p, const scoped_ptr& b) { + return p != b.get(); +} + +// scoped_array extends scoped_ptr to arrays. Deletion of the array pointed to +// is guaranteed, either on destruction of the scoped_array or via an explicit +// reset(). Use shared_array or std::vector if your needs are more complex. + +template +class scoped_array { + private: + + T* ptr; + + scoped_array(scoped_array const &); + scoped_array & operator=(scoped_array const &); + + public: + + typedef T element_type; + + explicit scoped_array(T* p = 0) : ptr(p) {} + + ~scoped_array() { + delete[] ptr; + } + + void reset(T* p = 0) { + if (ptr != p) { + delete [] ptr; + ptr = p; + } + } + + T& operator[](std::ptrdiff_t i) const { + MOZ_ASSERT(ptr != 0); + MOZ_ASSERT(i >= 0); + return ptr[i]; + } + + bool operator==(T* p) const { + return ptr == p; + } + + bool operator!=(T* p) const { + return ptr != p; + } + + T* get() const { + return ptr; + } + + void swap(scoped_array & b) { + T* tmp = b.ptr; + b.ptr = ptr; + ptr = tmp; + } + + T* release() { + T* tmp = ptr; + ptr = 0; + return tmp; + } + + private: + + // no reason to use these: each scoped_array should have its own object + template bool operator==(scoped_array const& p) const; + template bool operator!=(scoped_array const& p) const; +}; + +template inline +void swap(scoped_array& a, scoped_array& b) { + a.swap(b); +} + +template inline +bool operator==(T* p, const scoped_array& b) { + return p == b.get(); +} + +template inline +bool operator!=(T* p, const scoped_array& b) { + return p != b.get(); +} + + +// This class wraps the c library function free() in a class that can be +// passed as a template argument to scoped_ptr_malloc below. +class ScopedPtrMallocFree { + public: + inline void operator()(void* x) const { + free(x); + } +}; + +// scoped_ptr_malloc<> is similar to scoped_ptr<>, but it accepts a +// second template argument, the functor used to free the object. + +template +class scoped_ptr_malloc { + private: + + T* ptr; + + scoped_ptr_malloc(scoped_ptr_malloc const &); + scoped_ptr_malloc & operator=(scoped_ptr_malloc const &); + + public: + + typedef T element_type; + + explicit scoped_ptr_malloc(T* p = 0): ptr(p) {} + + ~scoped_ptr_malloc() { + free_((void*) ptr); + } + + void reset(T* p = 0) { + if (ptr != p) { + free_((void*) ptr); + ptr = p; + } + } + + T& operator*() const { + MOZ_ASSERT(ptr != 0); + return *ptr; + } + + T* operator->() const { + MOZ_ASSERT(ptr != 0); + return ptr; + } + + bool operator==(T* p) const { + return ptr == p; + } + + bool operator!=(T* p) const { + return ptr != p; + } + + T* get() const { + return ptr; + } + + void swap(scoped_ptr_malloc & b) { + T* tmp = b.ptr; + b.ptr = ptr; + ptr = tmp; + } + + T* release() { + T* tmp = ptr; + ptr = 0; + return tmp; + } + + private: + + // no reason to use these: each scoped_ptr_malloc should have its own object + template + bool operator==(scoped_ptr_malloc const& p) const; + template + bool operator!=(scoped_ptr_malloc const& p) const; + + static FreeProc const free_; +}; + +template +FP const scoped_ptr_malloc::free_ = FP(); + +template inline +void swap(scoped_ptr_malloc& a, scoped_ptr_malloc& b) { + a.swap(b); +} + +template inline +bool operator==(T* p, const scoped_ptr_malloc& b) { + return p == b.get(); +} + +template inline +bool operator!=(T* p, const scoped_ptr_malloc& b) { + return p != b.get(); +} + + +//////////////////////////////////////////////////////////////// +// Module +// + +// A Module represents the contents of a module, and supports methods +// for adding information produced by parsing STABS or DWARF data +// --- possibly both from the same file --- and then writing out the +// unified contents as a Breakpad-format symbol file. +class Module { +public: + // The type of addresses and sizes in a symbol table. + typedef uint64_t Address; + + // Representation of an expression. This can either be a postfix + // expression, in which case it is stored as a string, or a simple + // expression of the form (identifier + imm) or *(identifier + imm). + // It can also be invalid (denoting "no value"). + enum ExprHow { + kExprInvalid = 1, + kExprPostfix, + kExprSimple, + kExprSimpleMem + }; + + struct Expr { + // Construct a simple-form expression + Expr(const UniqueString* ident, long offset, bool deref) { + if (IsEmptyUniqueString(ident)) { + Expr(); + } else { + postfix_ = ""; + ident_ = ident; + offset_ = offset; + how_ = deref ? kExprSimpleMem : kExprSimple; + } + } + + // Construct an invalid expression + Expr() { + postfix_ = ""; + ident_ = nullptr; + offset_ = 0; + how_ = kExprInvalid; + } + + // Return the postfix expression string, either directly, + // if this is a postfix expression, or by synthesising it + // for a simple expression. + std::string getExprPostfix() const { + switch (how_) { + case kExprPostfix: + return postfix_; + case kExprSimple: + case kExprSimpleMem: { + char buf[40]; + sprintf(buf, " %ld %c%s", labs(offset_), offset_ < 0 ? '-' : '+', + how_ == kExprSimple ? "" : " ^"); + return std::string(FromUniqueString(ident_)) + std::string(buf); + } + case kExprInvalid: + default: + MOZ_ASSERT(0 && "getExprPostfix: invalid Module::Expr type"); + return "Expr::genExprPostfix: kExprInvalid"; + } + } + + // The identifier that gives the starting value for simple expressions. + const UniqueString* ident_; + // The offset to add for simple expressions. + long offset_; + // The Postfix expression string to evaluate for non-simple expressions. + std::string postfix_; + // The operation expressed by this expression. + ExprHow how_; + }; + + // A map from register names to expressions that recover + // their values. This can represent a complete set of rules to + // follow at some address, or a set of changes to be applied to an + // extant set of rules. + // NOTE! there are two completely different types called RuleMap. This + // is one of them. + typedef std::map RuleMap; + + // A map from addresses to RuleMaps, representing changes that take + // effect at given addresses. + typedef std::map RuleChangeMap; + + // A range of 'STACK CFI' stack walking information. An instance of + // this structure corresponds to a 'STACK CFI INIT' record and the + // subsequent 'STACK CFI' records that fall within its range. + struct StackFrameEntry { + // The starting address and number of bytes of machine code this + // entry covers. + Address address, size; + + // The initial register recovery rules, in force at the starting + // address. + RuleMap initial_rules; + + // A map from addresses to rule changes. To find the rules in + // force at a given address, start with initial_rules, and then + // apply the changes given in this map for all addresses up to and + // including the address you're interested in. + RuleChangeMap rule_changes; + }; + + // Create a new module with the given name, operating system, + // architecture, and ID string. + Module(const std::string &name, const std::string &os, + const std::string &architecture, const std::string &id); + ~Module(); + +private: + + // Module header entries. + std::string name_, os_, architecture_, id_; +}; + + +} // namespace lul + +#endif // LulCommonExt_h diff --git a/tools/profiler/lul/LulDwarf.cpp b/tools/profiler/lul/LulDwarf.cpp new file mode 100644 index 0000000000..02e1df3257 --- /dev/null +++ b/tools/profiler/lul/LulDwarf.cpp @@ -0,0 +1,2181 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ + +// Copyright (c) 2010 Google Inc. All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// CFI reader author: Jim Blandy +// Original author: Jim Blandy + +// Implementation of dwarf2reader::LineInfo, dwarf2reader::CompilationUnit, +// and dwarf2reader::CallFrameInfo. See dwarf2reader.h for details. + +// This file is derived from the following files in +// toolkit/crashreporter/google-breakpad: +// src/common/dwarf/bytereader.cc +// src/common/dwarf/dwarf2reader.cc +// src/common/dwarf_cfi_to_module.cc + +#include +#include +#include +#include + +#include +#include +#include + +#include "mozilla/Assertions.h" +#include "mozilla/Snprintf.h" + +#include "LulCommonExt.h" +#include "LulDwarfInt.h" + + +// Set this to 1 for verbose logging +#define DEBUG_DWARF 0 + + +namespace lul { + +using std::string; + +ByteReader::ByteReader(enum Endianness endian) + :offset_reader_(NULL), address_reader_(NULL), endian_(endian), + address_size_(0), offset_size_(0), + have_section_base_(), have_text_base_(), have_data_base_(), + have_function_base_() { } + +ByteReader::~ByteReader() { } + +void ByteReader::SetOffsetSize(uint8 size) { + offset_size_ = size; + MOZ_ASSERT(size == 4 || size == 8); + if (size == 4) { + this->offset_reader_ = &ByteReader::ReadFourBytes; + } else { + this->offset_reader_ = &ByteReader::ReadEightBytes; + } +} + +void ByteReader::SetAddressSize(uint8 size) { + address_size_ = size; + MOZ_ASSERT(size == 4 || size == 8); + if (size == 4) { + this->address_reader_ = &ByteReader::ReadFourBytes; + } else { + this->address_reader_ = &ByteReader::ReadEightBytes; + } +} + +uint64 ByteReader::ReadInitialLength(const char* start, size_t* len) { + const uint64 initial_length = ReadFourBytes(start); + start += 4; + + // In DWARF2/3, if the initial length is all 1 bits, then the offset + // size is 8 and we need to read the next 8 bytes for the real length. + if (initial_length == 0xffffffff) { + SetOffsetSize(8); + *len = 12; + return ReadOffset(start); + } else { + SetOffsetSize(4); + *len = 4; + } + return initial_length; +} + +bool ByteReader::ValidEncoding(DwarfPointerEncoding encoding) const { + if (encoding == DW_EH_PE_omit) return true; + if (encoding == DW_EH_PE_aligned) return true; + if ((encoding & 0x7) > DW_EH_PE_udata8) + return false; + if ((encoding & 0x70) > DW_EH_PE_funcrel) + return false; + return true; +} + +bool ByteReader::UsableEncoding(DwarfPointerEncoding encoding) const { + switch (encoding & 0x70) { + case DW_EH_PE_absptr: return true; + case DW_EH_PE_pcrel: return have_section_base_; + case DW_EH_PE_textrel: return have_text_base_; + case DW_EH_PE_datarel: return have_data_base_; + case DW_EH_PE_funcrel: return have_function_base_; + default: return false; + } +} + +uint64 ByteReader::ReadEncodedPointer(const char *buffer, + DwarfPointerEncoding encoding, + size_t *len) const { + // UsableEncoding doesn't approve of DW_EH_PE_omit, so we shouldn't + // see it here. + MOZ_ASSERT(encoding != DW_EH_PE_omit); + + // The Linux Standards Base 4.0 does not make this clear, but the + // GNU tools (gcc/unwind-pe.h; readelf/dwarf.c; gdb/dwarf2-frame.c) + // agree that aligned pointers are always absolute, machine-sized, + // machine-signed pointers. + if (encoding == DW_EH_PE_aligned) { + MOZ_ASSERT(have_section_base_); + + // We don't need to align BUFFER in *our* address space. Rather, we + // need to find the next position in our buffer that would be aligned + // when the .eh_frame section the buffer contains is loaded into the + // program's memory. So align assuming that buffer_base_ gets loaded at + // address section_base_, where section_base_ itself may or may not be + // aligned. + + // First, find the offset to START from the closest prior aligned + // address. + uint64 skew = section_base_ & (AddressSize() - 1); + // Now find the offset from that aligned address to buffer. + uint64 offset = skew + (buffer - buffer_base_); + // Round up to the next boundary. + uint64 aligned = (offset + AddressSize() - 1) & -AddressSize(); + // Convert back to a pointer. + const char *aligned_buffer = buffer_base_ + (aligned - skew); + // Finally, store the length and actually fetch the pointer. + *len = aligned_buffer - buffer + AddressSize(); + return ReadAddress(aligned_buffer); + } + + // Extract the value first, ignoring whether it's a pointer or an + // offset relative to some base. + uint64 offset; + switch (encoding & 0x0f) { + case DW_EH_PE_absptr: + // DW_EH_PE_absptr is weird, as it is used as a meaningful value for + // both the high and low nybble of encoding bytes. When it appears in + // the high nybble, it means that the pointer is absolute, not an + // offset from some base address. When it appears in the low nybble, + // as here, it means that the pointer is stored as a normal + // machine-sized and machine-signed address. A low nybble of + // DW_EH_PE_absptr does not imply that the pointer is absolute; it is + // correct for us to treat the value as an offset from a base address + // if the upper nybble is not DW_EH_PE_absptr. + offset = ReadAddress(buffer); + *len = AddressSize(); + break; + + case DW_EH_PE_uleb128: + offset = ReadUnsignedLEB128(buffer, len); + break; + + case DW_EH_PE_udata2: + offset = ReadTwoBytes(buffer); + *len = 2; + break; + + case DW_EH_PE_udata4: + offset = ReadFourBytes(buffer); + *len = 4; + break; + + case DW_EH_PE_udata8: + offset = ReadEightBytes(buffer); + *len = 8; + break; + + case DW_EH_PE_sleb128: + offset = ReadSignedLEB128(buffer, len); + break; + + case DW_EH_PE_sdata2: + offset = ReadTwoBytes(buffer); + // Sign-extend from 16 bits. + offset = (offset ^ 0x8000) - 0x8000; + *len = 2; + break; + + case DW_EH_PE_sdata4: + offset = ReadFourBytes(buffer); + // Sign-extend from 32 bits. + offset = (offset ^ 0x80000000ULL) - 0x80000000ULL; + *len = 4; + break; + + case DW_EH_PE_sdata8: + // No need to sign-extend; this is the full width of our type. + offset = ReadEightBytes(buffer); + *len = 8; + break; + + default: + abort(); + } + + // Find the appropriate base address. + uint64 base; + switch (encoding & 0x70) { + case DW_EH_PE_absptr: + base = 0; + break; + + case DW_EH_PE_pcrel: + MOZ_ASSERT(have_section_base_); + base = section_base_ + (buffer - buffer_base_); + break; + + case DW_EH_PE_textrel: + MOZ_ASSERT(have_text_base_); + base = text_base_; + break; + + case DW_EH_PE_datarel: + MOZ_ASSERT(have_data_base_); + base = data_base_; + break; + + case DW_EH_PE_funcrel: + MOZ_ASSERT(have_function_base_); + base = function_base_; + break; + + default: + abort(); + } + + uint64 pointer = base + offset; + + // Remove inappropriate upper bits. + if (AddressSize() == 4) + pointer = pointer & 0xffffffff; + else + MOZ_ASSERT(AddressSize() == sizeof(uint64)); + + return pointer; +} + + +// A DWARF rule for recovering the address or value of a register, or +// computing the canonical frame address. There is one subclass of this for +// each '*Rule' member function in CallFrameInfo::Handler. +// +// It's annoying that we have to handle Rules using pointers (because +// the concrete instances can have an arbitrary size). They're small, +// so it would be much nicer if we could just handle them by value +// instead of fretting about ownership and destruction. +// +// It seems like all these could simply be instances of std::tr1::bind, +// except that we need instances to be EqualityComparable, too. +// +// This could logically be nested within State, but then the qualified names +// get horrendous. +class CallFrameInfo::Rule { + public: + virtual ~Rule() { } + + // Tell HANDLER that, at ADDRESS in the program, REGISTER can be + // recovered using this rule. If REGISTER is kCFARegister, then this rule + // describes how to compute the canonical frame address. Return what the + // HANDLER member function returned. + virtual bool Handle(Handler *handler, uint64 address, int register) const = 0; + + // Equality on rules. We use these to decide which rules we need + // to report after a DW_CFA_restore_state instruction. + virtual bool operator==(const Rule &rhs) const = 0; + + bool operator!=(const Rule &rhs) const { return ! (*this == rhs); } + + // Return a pointer to a copy of this rule. + virtual Rule *Copy() const = 0; + + // If this is a base+offset rule, change its base register to REG. + // Otherwise, do nothing. (Ugly, but required for DW_CFA_def_cfa_register.) + virtual void SetBaseRegister(unsigned reg) { } + + // If this is a base+offset rule, change its offset to OFFSET. Otherwise, + // do nothing. (Ugly, but required for DW_CFA_def_cfa_offset.) + virtual void SetOffset(long long offset) { } + + // A RTTI workaround, to make it possible to implement equality + // comparisons on classes derived from this one. + enum CFIRTag { + CFIR_UNDEFINED_RULE, + CFIR_SAME_VALUE_RULE, + CFIR_OFFSET_RULE, + CFIR_VAL_OFFSET_RULE, + CFIR_REGISTER_RULE, + CFIR_EXPRESSION_RULE, + CFIR_VAL_EXPRESSION_RULE + }; + + // Produce the tag that identifies the child class of this object. + virtual CFIRTag getTag() const = 0; +}; + +// Rule: the value the register had in the caller cannot be recovered. +class CallFrameInfo::UndefinedRule: public CallFrameInfo::Rule { + public: + UndefinedRule() { } + ~UndefinedRule() { } + CFIRTag getTag() const { return CFIR_UNDEFINED_RULE; } + bool Handle(Handler *handler, uint64 address, int reg) const { + return handler->UndefinedRule(address, reg); + } + bool operator==(const Rule &rhs) const { + if (rhs.getTag() != CFIR_UNDEFINED_RULE) return false; + return true; + } + Rule *Copy() const { return new UndefinedRule(*this); } +}; + +// Rule: the register's value is the same as that it had in the caller. +class CallFrameInfo::SameValueRule: public CallFrameInfo::Rule { + public: + SameValueRule() { } + ~SameValueRule() { } + CFIRTag getTag() const { return CFIR_SAME_VALUE_RULE; } + bool Handle(Handler *handler, uint64 address, int reg) const { + return handler->SameValueRule(address, reg); + } + bool operator==(const Rule &rhs) const { + if (rhs.getTag() != CFIR_SAME_VALUE_RULE) return false; + return true; + } + Rule *Copy() const { return new SameValueRule(*this); } +}; + +// Rule: the register is saved at OFFSET from BASE_REGISTER. BASE_REGISTER +// may be CallFrameInfo::Handler::kCFARegister. +class CallFrameInfo::OffsetRule: public CallFrameInfo::Rule { + public: + OffsetRule(int base_register, long offset) + : base_register_(base_register), offset_(offset) { } + ~OffsetRule() { } + CFIRTag getTag() const { return CFIR_OFFSET_RULE; } + bool Handle(Handler *handler, uint64 address, int reg) const { + return handler->OffsetRule(address, reg, base_register_, offset_); + } + bool operator==(const Rule &rhs) const { + if (rhs.getTag() != CFIR_OFFSET_RULE) return false; + const OffsetRule *our_rhs = static_cast(&rhs); + return (base_register_ == our_rhs->base_register_ && + offset_ == our_rhs->offset_); + } + Rule *Copy() const { return new OffsetRule(*this); } + // We don't actually need SetBaseRegister or SetOffset here, since they + // are only ever applied to CFA rules, for DW_CFA_def_cfa_offset, and it + // doesn't make sense to use OffsetRule for computing the CFA: it + // computes the address at which a register is saved, not a value. + private: + int base_register_; + long offset_; +}; + +// Rule: the value the register had in the caller is the value of +// BASE_REGISTER plus offset. BASE_REGISTER may be +// CallFrameInfo::Handler::kCFARegister. +class CallFrameInfo::ValOffsetRule: public CallFrameInfo::Rule { + public: + ValOffsetRule(int base_register, long offset) + : base_register_(base_register), offset_(offset) { } + ~ValOffsetRule() { } + CFIRTag getTag() const { return CFIR_VAL_OFFSET_RULE; } + bool Handle(Handler *handler, uint64 address, int reg) const { + return handler->ValOffsetRule(address, reg, base_register_, offset_); + } + bool operator==(const Rule &rhs) const { + if (rhs.getTag() != CFIR_VAL_OFFSET_RULE) return false; + const ValOffsetRule *our_rhs = static_cast(&rhs); + return (base_register_ == our_rhs->base_register_ && + offset_ == our_rhs->offset_); + } + Rule *Copy() const { return new ValOffsetRule(*this); } + void SetBaseRegister(unsigned reg) { base_register_ = reg; } + void SetOffset(long long offset) { offset_ = offset; } + private: + int base_register_; + long offset_; +}; + +// Rule: the register has been saved in another register REGISTER_NUMBER_. +class CallFrameInfo::RegisterRule: public CallFrameInfo::Rule { + public: + explicit RegisterRule(int register_number) + : register_number_(register_number) { } + ~RegisterRule() { } + CFIRTag getTag() const { return CFIR_REGISTER_RULE; } + bool Handle(Handler *handler, uint64 address, int reg) const { + return handler->RegisterRule(address, reg, register_number_); + } + bool operator==(const Rule &rhs) const { + if (rhs.getTag() != CFIR_REGISTER_RULE) return false; + const RegisterRule *our_rhs = static_cast(&rhs); + return (register_number_ == our_rhs->register_number_); + } + Rule *Copy() const { return new RegisterRule(*this); } + private: + int register_number_; +}; + +// Rule: EXPRESSION evaluates to the address at which the register is saved. +class CallFrameInfo::ExpressionRule: public CallFrameInfo::Rule { + public: + explicit ExpressionRule(const string &expression) + : expression_(expression) { } + ~ExpressionRule() { } + CFIRTag getTag() const { return CFIR_EXPRESSION_RULE; } + bool Handle(Handler *handler, uint64 address, int reg) const { + return handler->ExpressionRule(address, reg, expression_); + } + bool operator==(const Rule &rhs) const { + if (rhs.getTag() != CFIR_EXPRESSION_RULE) return false; + const ExpressionRule *our_rhs = static_cast(&rhs); + return (expression_ == our_rhs->expression_); + } + Rule *Copy() const { return new ExpressionRule(*this); } + private: + string expression_; +}; + +// Rule: EXPRESSION evaluates to the previous value of the register. +class CallFrameInfo::ValExpressionRule: public CallFrameInfo::Rule { + public: + explicit ValExpressionRule(const string &expression) + : expression_(expression) { } + ~ValExpressionRule() { } + CFIRTag getTag() const { return CFIR_VAL_EXPRESSION_RULE; } + bool Handle(Handler *handler, uint64 address, int reg) const { + return handler->ValExpressionRule(address, reg, expression_); + } + bool operator==(const Rule &rhs) const { + if (rhs.getTag() != CFIR_VAL_EXPRESSION_RULE) return false; + const ValExpressionRule *our_rhs = + static_cast(&rhs); + return (expression_ == our_rhs->expression_); + } + Rule *Copy() const { return new ValExpressionRule(*this); } + private: + string expression_; +}; + +// A map from register numbers to rules. +class CallFrameInfo::RuleMap { + public: + RuleMap() : cfa_rule_(NULL) { } + RuleMap(const RuleMap &rhs) : cfa_rule_(NULL) { *this = rhs; } + ~RuleMap() { Clear(); } + + RuleMap &operator=(const RuleMap &rhs); + + // Set the rule for computing the CFA to RULE. Take ownership of RULE. + void SetCFARule(Rule *rule) { delete cfa_rule_; cfa_rule_ = rule; } + + // Return the current CFA rule. Unlike RegisterRule, this RuleMap retains + // ownership of the rule. We use this for DW_CFA_def_cfa_offset and + // DW_CFA_def_cfa_register, and for detecting references to the CFA before + // a rule for it has been established. + Rule *CFARule() const { return cfa_rule_; } + + // Return the rule for REG, or NULL if there is none. The caller takes + // ownership of the result. + Rule *RegisterRule(int reg) const; + + // Set the rule for computing REG to RULE. Take ownership of RULE. + void SetRegisterRule(int reg, Rule *rule); + + // Make all the appropriate calls to HANDLER as if we were changing from + // this RuleMap to NEW_RULES at ADDRESS. We use this to implement + // DW_CFA_restore_state, where lots of rules can change simultaneously. + // Return true if all handlers returned true; otherwise, return false. + bool HandleTransitionTo(Handler *handler, uint64 address, + const RuleMap &new_rules) const; + + private: + // A map from register numbers to Rules. + typedef std::map RuleByNumber; + + // Remove all register rules and clear cfa_rule_. + void Clear(); + + // The rule for computing the canonical frame address. This RuleMap owns + // this rule. + Rule *cfa_rule_; + + // A map from register numbers to postfix expressions to recover + // their values. This RuleMap owns the Rules the map refers to. + RuleByNumber registers_; +}; + +CallFrameInfo::RuleMap &CallFrameInfo::RuleMap::operator=(const RuleMap &rhs) { + Clear(); + // Since each map owns the rules it refers to, assignment must copy them. + if (rhs.cfa_rule_) cfa_rule_ = rhs.cfa_rule_->Copy(); + for (RuleByNumber::const_iterator it = rhs.registers_.begin(); + it != rhs.registers_.end(); it++) + registers_[it->first] = it->second->Copy(); + return *this; +} + +CallFrameInfo::Rule *CallFrameInfo::RuleMap::RegisterRule(int reg) const { + MOZ_ASSERT(reg != Handler::kCFARegister); + RuleByNumber::const_iterator it = registers_.find(reg); + if (it != registers_.end()) + return it->second->Copy(); + else + return NULL; +} + +void CallFrameInfo::RuleMap::SetRegisterRule(int reg, Rule *rule) { + MOZ_ASSERT(reg != Handler::kCFARegister); + MOZ_ASSERT(rule); + Rule **slot = ®isters_[reg]; + delete *slot; + *slot = rule; +} + +bool CallFrameInfo::RuleMap::HandleTransitionTo( + Handler *handler, + uint64 address, + const RuleMap &new_rules) const { + // Transition from cfa_rule_ to new_rules.cfa_rule_. + if (cfa_rule_ && new_rules.cfa_rule_) { + if (*cfa_rule_ != *new_rules.cfa_rule_ && + !new_rules.cfa_rule_->Handle(handler, address, Handler::kCFARegister)) + return false; + } else if (cfa_rule_) { + // this RuleMap has a CFA rule but new_rules doesn't. + // CallFrameInfo::Handler has no way to handle this --- and shouldn't; + // it's garbage input. The instruction interpreter should have + // detected this and warned, so take no action here. + } else if (new_rules.cfa_rule_) { + // This shouldn't be possible: NEW_RULES is some prior state, and + // there's no way to remove entries. + MOZ_ASSERT(0); + } else { + // Both CFA rules are empty. No action needed. + } + + // Traverse the two maps in order by register number, and report + // whatever differences we find. + RuleByNumber::const_iterator old_it = registers_.begin(); + RuleByNumber::const_iterator new_it = new_rules.registers_.begin(); + while (old_it != registers_.end() && new_it != new_rules.registers_.end()) { + if (old_it->first < new_it->first) { + // This RuleMap has an entry for old_it->first, but NEW_RULES + // doesn't. + // + // This isn't really the right thing to do, but since CFI generally + // only mentions callee-saves registers, and GCC's convention for + // callee-saves registers is that they are unchanged, it's a good + // approximation. + if (!handler->SameValueRule(address, old_it->first)) + return false; + old_it++; + } else if (old_it->first > new_it->first) { + // NEW_RULES has entry for new_it->first, but this RuleMap + // doesn't. This shouldn't be possible: NEW_RULES is some prior + // state, and there's no way to remove entries. + MOZ_ASSERT(0); + } else { + // Both maps have an entry for this register. Report the new + // rule if it is different. + if (*old_it->second != *new_it->second && + !new_it->second->Handle(handler, address, new_it->first)) + return false; + new_it++, old_it++; + } + } + // Finish off entries from this RuleMap with no counterparts in new_rules. + while (old_it != registers_.end()) { + if (!handler->SameValueRule(address, old_it->first)) + return false; + old_it++; + } + // Since we only make transitions from a rule set to some previously + // saved rule set, and we can only add rules to the map, NEW_RULES + // must have fewer rules than *this. + MOZ_ASSERT(new_it == new_rules.registers_.end()); + + return true; +} + +// Remove all register rules and clear cfa_rule_. +void CallFrameInfo::RuleMap::Clear() { + delete cfa_rule_; + cfa_rule_ = NULL; + for (RuleByNumber::iterator it = registers_.begin(); + it != registers_.end(); it++) + delete it->second; + registers_.clear(); +} + +// The state of the call frame information interpreter as it processes +// instructions from a CIE and FDE. +class CallFrameInfo::State { + public: + // Create a call frame information interpreter state with the given + // reporter, reader, handler, and initial call frame info address. + State(ByteReader *reader, Handler *handler, Reporter *reporter, + uint64 address) + : reader_(reader), handler_(handler), reporter_(reporter), + address_(address), entry_(NULL), cursor_(NULL), + saved_rules_(NULL) { } + + ~State() { + if (saved_rules_) + delete saved_rules_; + } + + // Interpret instructions from CIE, save the resulting rule set for + // DW_CFA_restore instructions, and return true. On error, report + // the problem to reporter_ and return false. + bool InterpretCIE(const CIE &cie); + + // Interpret instructions from FDE, and return true. On error, + // report the problem to reporter_ and return false. + bool InterpretFDE(const FDE &fde); + + private: + // The operands of a CFI instruction, for ParseOperands. + struct Operands { + unsigned register_number; // A register number. + uint64 offset; // An offset or address. + long signed_offset; // A signed offset. + string expression; // A DWARF expression. + }; + + // Parse CFI instruction operands from STATE's instruction stream as + // described by FORMAT. On success, populate OPERANDS with the + // results, and return true. On failure, report the problem and + // return false. + // + // Each character of FORMAT should be one of the following: + // + // 'r' unsigned LEB128 register number (OPERANDS->register_number) + // 'o' unsigned LEB128 offset (OPERANDS->offset) + // 's' signed LEB128 offset (OPERANDS->signed_offset) + // 'a' machine-size address (OPERANDS->offset) + // (If the CIE has a 'z' augmentation string, 'a' uses the + // encoding specified by the 'R' argument.) + // '1' a one-byte offset (OPERANDS->offset) + // '2' a two-byte offset (OPERANDS->offset) + // '4' a four-byte offset (OPERANDS->offset) + // '8' an eight-byte offset (OPERANDS->offset) + // 'e' a DW_FORM_block holding a (OPERANDS->expression) + // DWARF expression + bool ParseOperands(const char *format, Operands *operands); + + // Interpret one CFI instruction from STATE's instruction stream, update + // STATE, report any rule changes to handler_, and return true. On + // failure, report the problem and return false. + bool DoInstruction(); + + // The following Do* member functions are subroutines of DoInstruction, + // factoring out the actual work of operations that have several + // different encodings. + + // Set the CFA rule to be the value of BASE_REGISTER plus OFFSET, and + // return true. On failure, report and return false. (Used for + // DW_CFA_def_cfa and DW_CFA_def_cfa_sf.) + bool DoDefCFA(unsigned base_register, long offset); + + // Change the offset of the CFA rule to OFFSET, and return true. On + // failure, report and return false. (Subroutine for + // DW_CFA_def_cfa_offset and DW_CFA_def_cfa_offset_sf.) + bool DoDefCFAOffset(long offset); + + // Specify that REG can be recovered using RULE, and return true. On + // failure, report and return false. + bool DoRule(unsigned reg, Rule *rule); + + // Specify that REG can be found at OFFSET from the CFA, and return true. + // On failure, report and return false. (Subroutine for DW_CFA_offset, + // DW_CFA_offset_extended, and DW_CFA_offset_extended_sf.) + bool DoOffset(unsigned reg, long offset); + + // Specify that the caller's value for REG is the CFA plus OFFSET, + // and return true. On failure, report and return false. (Subroutine + // for DW_CFA_val_offset and DW_CFA_val_offset_sf.) + bool DoValOffset(unsigned reg, long offset); + + // Restore REG to the rule established in the CIE, and return true. On + // failure, report and return false. (Subroutine for DW_CFA_restore and + // DW_CFA_restore_extended.) + bool DoRestore(unsigned reg); + + // Return the section offset of the instruction at cursor. For use + // in error messages. + uint64 CursorOffset() { return entry_->offset + (cursor_ - entry_->start); } + + // Report that entry_ is incomplete, and return false. For brevity. + bool ReportIncomplete() { + reporter_->Incomplete(entry_->offset, entry_->kind); + return false; + } + + // For reading multi-byte values with the appropriate endianness. + ByteReader *reader_; + + // The handler to which we should report the data we find. + Handler *handler_; + + // For reporting problems in the info we're parsing. + Reporter *reporter_; + + // The code address to which the next instruction in the stream applies. + uint64 address_; + + // The entry whose instructions we are currently processing. This is + // first a CIE, and then an FDE. + const Entry *entry_; + + // The next instruction to process. + const char *cursor_; + + // The current set of rules. + RuleMap rules_; + + // The set of rules established by the CIE, used by DW_CFA_restore + // and DW_CFA_restore_extended. We set this after interpreting the + // CIE's instructions. + RuleMap cie_rules_; + + // A stack of saved states, for DW_CFA_remember_state and + // DW_CFA_restore_state. + std::stack* saved_rules_; +}; + +bool CallFrameInfo::State::InterpretCIE(const CIE &cie) { + entry_ = &cie; + cursor_ = entry_->instructions; + while (cursor_ < entry_->end) + if (!DoInstruction()) + return false; + // Note the rules established by the CIE, for use by DW_CFA_restore + // and DW_CFA_restore_extended. + cie_rules_ = rules_; + return true; +} + +bool CallFrameInfo::State::InterpretFDE(const FDE &fde) { + entry_ = &fde; + cursor_ = entry_->instructions; + while (cursor_ < entry_->end) + if (!DoInstruction()) + return false; + return true; +} + +bool CallFrameInfo::State::ParseOperands(const char *format, + Operands *operands) { + size_t len; + const char *operand; + + for (operand = format; *operand; operand++) { + size_t bytes_left = entry_->end - cursor_; + switch (*operand) { + case 'r': + operands->register_number = reader_->ReadUnsignedLEB128(cursor_, &len); + if (len > bytes_left) return ReportIncomplete(); + cursor_ += len; + break; + + case 'o': + operands->offset = reader_->ReadUnsignedLEB128(cursor_, &len); + if (len > bytes_left) return ReportIncomplete(); + cursor_ += len; + break; + + case 's': + operands->signed_offset = reader_->ReadSignedLEB128(cursor_, &len); + if (len > bytes_left) return ReportIncomplete(); + cursor_ += len; + break; + + case 'a': + operands->offset = + reader_->ReadEncodedPointer(cursor_, entry_->cie->pointer_encoding, + &len); + if (len > bytes_left) return ReportIncomplete(); + cursor_ += len; + break; + + case '1': + if (1 > bytes_left) return ReportIncomplete(); + operands->offset = static_cast(*cursor_++); + break; + + case '2': + if (2 > bytes_left) return ReportIncomplete(); + operands->offset = reader_->ReadTwoBytes(cursor_); + cursor_ += 2; + break; + + case '4': + if (4 > bytes_left) return ReportIncomplete(); + operands->offset = reader_->ReadFourBytes(cursor_); + cursor_ += 4; + break; + + case '8': + if (8 > bytes_left) return ReportIncomplete(); + operands->offset = reader_->ReadEightBytes(cursor_); + cursor_ += 8; + break; + + case 'e': { + size_t expression_length = reader_->ReadUnsignedLEB128(cursor_, &len); + if (len > bytes_left || expression_length > bytes_left - len) + return ReportIncomplete(); + cursor_ += len; + operands->expression = string(cursor_, expression_length); + cursor_ += expression_length; + break; + } + + default: + MOZ_ASSERT(0); + } + } + + return true; +} + +bool CallFrameInfo::State::DoInstruction() { + CIE *cie = entry_->cie; + Operands ops; + + // Our entry's kind should have been set by now. + MOZ_ASSERT(entry_->kind != kUnknown); + + // We shouldn't have been invoked unless there were more + // instructions to parse. + MOZ_ASSERT(cursor_ < entry_->end); + + unsigned opcode = *cursor_++; + if ((opcode & 0xc0) != 0) { + switch (opcode & 0xc0) { + // Advance the address. + case DW_CFA_advance_loc: { + size_t code_offset = opcode & 0x3f; + address_ += code_offset * cie->code_alignment_factor; + break; + } + + // Find a register at an offset from the CFA. + case DW_CFA_offset: + if (!ParseOperands("o", &ops) || + !DoOffset(opcode & 0x3f, ops.offset * cie->data_alignment_factor)) + return false; + break; + + // Restore the rule established for a register by the CIE. + case DW_CFA_restore: + if (!DoRestore(opcode & 0x3f)) return false; + break; + + // The 'if' above should have excluded this possibility. + default: + MOZ_ASSERT(0); + } + + // Return here, so the big switch below won't be indented. + return true; + } + + switch (opcode) { + // Set the address. + case DW_CFA_set_loc: + if (!ParseOperands("a", &ops)) return false; + address_ = ops.offset; + break; + + // Advance the address. + case DW_CFA_advance_loc1: + if (!ParseOperands("1", &ops)) return false; + address_ += ops.offset * cie->code_alignment_factor; + break; + + // Advance the address. + case DW_CFA_advance_loc2: + if (!ParseOperands("2", &ops)) return false; + address_ += ops.offset * cie->code_alignment_factor; + break; + + // Advance the address. + case DW_CFA_advance_loc4: + if (!ParseOperands("4", &ops)) return false; + address_ += ops.offset * cie->code_alignment_factor; + break; + + // Advance the address. + case DW_CFA_MIPS_advance_loc8: + if (!ParseOperands("8", &ops)) return false; + address_ += ops.offset * cie->code_alignment_factor; + break; + + // Compute the CFA by adding an offset to a register. + case DW_CFA_def_cfa: + if (!ParseOperands("ro", &ops) || + !DoDefCFA(ops.register_number, ops.offset)) + return false; + break; + + // Compute the CFA by adding an offset to a register. + case DW_CFA_def_cfa_sf: + if (!ParseOperands("rs", &ops) || + !DoDefCFA(ops.register_number, + ops.signed_offset * cie->data_alignment_factor)) + return false; + break; + + // Change the base register used to compute the CFA. + case DW_CFA_def_cfa_register: { + Rule *cfa_rule = rules_.CFARule(); + if (!cfa_rule) { + reporter_->NoCFARule(entry_->offset, entry_->kind, CursorOffset()); + return false; + } + if (!ParseOperands("r", &ops)) return false; + cfa_rule->SetBaseRegister(ops.register_number); + if (!cfa_rule->Handle(handler_, address_, Handler::kCFARegister)) + return false; + break; + } + + // Change the offset used to compute the CFA. + case DW_CFA_def_cfa_offset: + if (!ParseOperands("o", &ops) || + !DoDefCFAOffset(ops.offset)) + return false; + break; + + // Change the offset used to compute the CFA. + case DW_CFA_def_cfa_offset_sf: + if (!ParseOperands("s", &ops) || + !DoDefCFAOffset(ops.signed_offset * cie->data_alignment_factor)) + return false; + break; + + // Specify an expression whose value is the CFA. + case DW_CFA_def_cfa_expression: { + if (!ParseOperands("e", &ops)) + return false; + Rule *rule = new ValExpressionRule(ops.expression); + rules_.SetCFARule(rule); + if (!rule->Handle(handler_, address_, Handler::kCFARegister)) + return false; + break; + } + + // The register's value cannot be recovered. + case DW_CFA_undefined: { + if (!ParseOperands("r", &ops) || + !DoRule(ops.register_number, new UndefinedRule())) + return false; + break; + } + + // The register's value is unchanged from its value in the caller. + case DW_CFA_same_value: { + if (!ParseOperands("r", &ops) || + !DoRule(ops.register_number, new SameValueRule())) + return false; + break; + } + + // Find a register at an offset from the CFA. + case DW_CFA_offset_extended: + if (!ParseOperands("ro", &ops) || + !DoOffset(ops.register_number, + ops.offset * cie->data_alignment_factor)) + return false; + break; + + // The register is saved at an offset from the CFA. + case DW_CFA_offset_extended_sf: + if (!ParseOperands("rs", &ops) || + !DoOffset(ops.register_number, + ops.signed_offset * cie->data_alignment_factor)) + return false; + break; + + // The register is saved at an offset from the CFA. + case DW_CFA_GNU_negative_offset_extended: + if (!ParseOperands("ro", &ops) || + !DoOffset(ops.register_number, + -ops.offset * cie->data_alignment_factor)) + return false; + break; + + // The register's value is the sum of the CFA plus an offset. + case DW_CFA_val_offset: + if (!ParseOperands("ro", &ops) || + !DoValOffset(ops.register_number, + ops.offset * cie->data_alignment_factor)) + return false; + break; + + // The register's value is the sum of the CFA plus an offset. + case DW_CFA_val_offset_sf: + if (!ParseOperands("rs", &ops) || + !DoValOffset(ops.register_number, + ops.signed_offset * cie->data_alignment_factor)) + return false; + break; + + // The register has been saved in another register. + case DW_CFA_register: { + if (!ParseOperands("ro", &ops) || + !DoRule(ops.register_number, new RegisterRule(ops.offset))) + return false; + break; + } + + // An expression yields the address at which the register is saved. + case DW_CFA_expression: { + if (!ParseOperands("re", &ops) || + !DoRule(ops.register_number, new ExpressionRule(ops.expression))) + return false; + break; + } + + // An expression yields the caller's value for the register. + case DW_CFA_val_expression: { + if (!ParseOperands("re", &ops) || + !DoRule(ops.register_number, new ValExpressionRule(ops.expression))) + return false; + break; + } + + // Restore the rule established for a register by the CIE. + case DW_CFA_restore_extended: + if (!ParseOperands("r", &ops) || + !DoRestore( ops.register_number)) + return false; + break; + + // Save the current set of rules on a stack. + case DW_CFA_remember_state: + if (!saved_rules_) { + saved_rules_ = new std::stack(); + } + saved_rules_->push(rules_); + break; + + // Pop the current set of rules off the stack. + case DW_CFA_restore_state: { + if (!saved_rules_ || saved_rules_->empty()) { + reporter_->EmptyStateStack(entry_->offset, entry_->kind, + CursorOffset()); + return false; + } + const RuleMap &new_rules = saved_rules_->top(); + if (rules_.CFARule() && !new_rules.CFARule()) { + reporter_->ClearingCFARule(entry_->offset, entry_->kind, + CursorOffset()); + return false; + } + rules_.HandleTransitionTo(handler_, address_, new_rules); + rules_ = new_rules; + saved_rules_->pop(); + break; + } + + // No operation. (Padding instruction.) + case DW_CFA_nop: + break; + + // A SPARC register window save: Registers 8 through 15 (%o0-%o7) + // are saved in registers 24 through 31 (%i0-%i7), and registers + // 16 through 31 (%l0-%l7 and %i0-%i7) are saved at CFA offsets + // (0-15 * the register size). The register numbers must be + // hard-coded. A GNU extension, and not a pretty one. + case DW_CFA_GNU_window_save: { + // Save %o0-%o7 in %i0-%i7. + for (int i = 8; i < 16; i++) + if (!DoRule(i, new RegisterRule(i + 16))) + return false; + // Save %l0-%l7 and %i0-%i7 at the CFA. + for (int i = 16; i < 32; i++) + // Assume that the byte reader's address size is the same as + // the architecture's register size. !@#%*^ hilarious. + if (!DoRule(i, new OffsetRule(Handler::kCFARegister, + (i - 16) * reader_->AddressSize()))) + return false; + break; + } + + // I'm not sure what this is. GDB doesn't use it for unwinding. + case DW_CFA_GNU_args_size: + if (!ParseOperands("o", &ops)) return false; + break; + + // An opcode we don't recognize. + default: { + reporter_->BadInstruction(entry_->offset, entry_->kind, CursorOffset()); + return false; + } + } + + return true; +} + +bool CallFrameInfo::State::DoDefCFA(unsigned base_register, long offset) { + Rule *rule = new ValOffsetRule(base_register, offset); + rules_.SetCFARule(rule); + return rule->Handle(handler_, address_, Handler::kCFARegister); +} + +bool CallFrameInfo::State::DoDefCFAOffset(long offset) { + Rule *cfa_rule = rules_.CFARule(); + if (!cfa_rule) { + reporter_->NoCFARule(entry_->offset, entry_->kind, CursorOffset()); + return false; + } + cfa_rule->SetOffset(offset); + return cfa_rule->Handle(handler_, address_, Handler::kCFARegister); +} + +bool CallFrameInfo::State::DoRule(unsigned reg, Rule *rule) { + rules_.SetRegisterRule(reg, rule); + return rule->Handle(handler_, address_, reg); +} + +bool CallFrameInfo::State::DoOffset(unsigned reg, long offset) { + if (!rules_.CFARule()) { + reporter_->NoCFARule(entry_->offset, entry_->kind, CursorOffset()); + return false; + } + return DoRule(reg, + new OffsetRule(Handler::kCFARegister, offset)); +} + +bool CallFrameInfo::State::DoValOffset(unsigned reg, long offset) { + if (!rules_.CFARule()) { + reporter_->NoCFARule(entry_->offset, entry_->kind, CursorOffset()); + return false; + } + return DoRule(reg, + new ValOffsetRule(Handler::kCFARegister, offset)); +} + +bool CallFrameInfo::State::DoRestore(unsigned reg) { + // DW_CFA_restore and DW_CFA_restore_extended don't make sense in a CIE. + if (entry_->kind == kCIE) { + reporter_->RestoreInCIE(entry_->offset, CursorOffset()); + return false; + } + Rule *rule = cie_rules_.RegisterRule(reg); + if (!rule) { + // This isn't really the right thing to do, but since CFI generally + // only mentions callee-saves registers, and GCC's convention for + // callee-saves registers is that they are unchanged, it's a good + // approximation. + rule = new SameValueRule(); + } + return DoRule(reg, rule); +} + +bool CallFrameInfo::ReadEntryPrologue(const char *cursor, Entry *entry) { + const char *buffer_end = buffer_ + buffer_length_; + + // Initialize enough of ENTRY for use in error reporting. + entry->offset = cursor - buffer_; + entry->start = cursor; + entry->kind = kUnknown; + entry->end = NULL; + + // Read the initial length. This sets reader_'s offset size. + size_t length_size; + uint64 length = reader_->ReadInitialLength(cursor, &length_size); + if (length_size > size_t(buffer_end - cursor)) + return ReportIncomplete(entry); + cursor += length_size; + + // In a .eh_frame section, a length of zero marks the end of the series + // of entries. + if (length == 0 && eh_frame_) { + entry->kind = kTerminator; + entry->end = cursor; + return true; + } + + // Validate the length. + if (length > size_t(buffer_end - cursor)) + return ReportIncomplete(entry); + + // The length is the number of bytes after the initial length field; + // we have that position handy at this point, so compute the end + // now. (If we're parsing 64-bit-offset DWARF on a 32-bit machine, + // and the length didn't fit in a size_t, we would have rejected it + // above.) + entry->end = cursor + length; + + // Parse the next field: either the offset of a CIE or a CIE id. + size_t offset_size = reader_->OffsetSize(); + if (offset_size > size_t(entry->end - cursor)) return ReportIncomplete(entry); + entry->id = reader_->ReadOffset(cursor); + + // Don't advance cursor past id field yet; in .eh_frame data we need + // the id's position to compute the section offset of an FDE's CIE. + + // Now we can decide what kind of entry this is. + if (eh_frame_) { + // In .eh_frame data, an ID of zero marks the entry as a CIE, and + // anything else is an offset from the id field of the FDE to the start + // of the CIE. + if (entry->id == 0) { + entry->kind = kCIE; + } else { + entry->kind = kFDE; + // Turn the offset from the id into an offset from the buffer's start. + entry->id = (cursor - buffer_) - entry->id; + } + } else { + // In DWARF CFI data, an ID of ~0 (of the appropriate width, given the + // offset size for the entry) marks the entry as a CIE, and anything + // else is the offset of the CIE from the beginning of the section. + if (offset_size == 4) + entry->kind = (entry->id == 0xffffffff) ? kCIE : kFDE; + else { + MOZ_ASSERT(offset_size == 8); + entry->kind = (entry->id == 0xffffffffffffffffULL) ? kCIE : kFDE; + } + } + + // Now advance cursor past the id. + cursor += offset_size; + + // The fields specific to this kind of entry start here. + entry->fields = cursor; + + entry->cie = NULL; + + return true; +} + +bool CallFrameInfo::ReadCIEFields(CIE *cie) { + const char *cursor = cie->fields; + size_t len; + + MOZ_ASSERT(cie->kind == kCIE); + + // Prepare for early exit. + cie->version = 0; + cie->augmentation.clear(); + cie->code_alignment_factor = 0; + cie->data_alignment_factor = 0; + cie->return_address_register = 0; + cie->has_z_augmentation = false; + cie->pointer_encoding = DW_EH_PE_absptr; + cie->instructions = 0; + + // Parse the version number. + if (cie->end - cursor < 1) + return ReportIncomplete(cie); + cie->version = reader_->ReadOneByte(cursor); + cursor++; + + // If we don't recognize the version, we can't parse any more fields of the + // CIE. For DWARF CFI, we handle versions 1 through 3 (there was never a + // version 2 of CFI data). For .eh_frame, we handle versions 1 and 3 as well; + // the difference between those versions seems to be the same as for + // .debug_frame. + if (cie->version < 1 || cie->version > 3) { + reporter_->UnrecognizedVersion(cie->offset, cie->version); + return false; + } + + const char *augmentation_start = cursor; + const void *augmentation_end = + memchr(augmentation_start, '\0', cie->end - augmentation_start); + if (! augmentation_end) return ReportIncomplete(cie); + cursor = static_cast(augmentation_end); + cie->augmentation = string(augmentation_start, + cursor - augmentation_start); + // Skip the terminating '\0'. + cursor++; + + // Is this CFI augmented? + if (!cie->augmentation.empty()) { + // Is it an augmentation we recognize? + if (cie->augmentation[0] == DW_Z_augmentation_start) { + // Linux C++ ABI 'z' augmentation, used for exception handling data. + cie->has_z_augmentation = true; + } else { + // Not an augmentation we recognize. Augmentations can have arbitrary + // effects on the form of rest of the content, so we have to give up. + reporter_->UnrecognizedAugmentation(cie->offset, cie->augmentation); + return false; + } + } + + // Parse the code alignment factor. + cie->code_alignment_factor = reader_->ReadUnsignedLEB128(cursor, &len); + if (size_t(cie->end - cursor) < len) return ReportIncomplete(cie); + cursor += len; + + // Parse the data alignment factor. + cie->data_alignment_factor = reader_->ReadSignedLEB128(cursor, &len); + if (size_t(cie->end - cursor) < len) return ReportIncomplete(cie); + cursor += len; + + // Parse the return address register. This is a ubyte in version 1, and + // a ULEB128 in version 3. + if (cie->version == 1) { + if (cursor >= cie->end) return ReportIncomplete(cie); + cie->return_address_register = uint8(*cursor++); + } else { + cie->return_address_register = reader_->ReadUnsignedLEB128(cursor, &len); + if (size_t(cie->end - cursor) < len) return ReportIncomplete(cie); + cursor += len; + } + + // If we have a 'z' augmentation string, find the augmentation data and + // use the augmentation string to parse it. + if (cie->has_z_augmentation) { + uint64_t data_size = reader_->ReadUnsignedLEB128(cursor, &len); + if (size_t(cie->end - cursor) < len + data_size) + return ReportIncomplete(cie); + cursor += len; + const char *data = cursor; + cursor += data_size; + const char *data_end = cursor; + + cie->has_z_lsda = false; + cie->has_z_personality = false; + cie->has_z_signal_frame = false; + + // Walk the augmentation string, and extract values from the + // augmentation data as the string directs. + for (size_t i = 1; i < cie->augmentation.size(); i++) { + switch (cie->augmentation[i]) { + case DW_Z_has_LSDA: + // The CIE's augmentation data holds the language-specific data + // area pointer's encoding, and the FDE's augmentation data holds + // the pointer itself. + cie->has_z_lsda = true; + // Fetch the LSDA encoding from the augmentation data. + if (data >= data_end) return ReportIncomplete(cie); + cie->lsda_encoding = DwarfPointerEncoding(*data++); + if (!reader_->ValidEncoding(cie->lsda_encoding)) { + reporter_->InvalidPointerEncoding(cie->offset, cie->lsda_encoding); + return false; + } + // Don't check if the encoding is usable here --- we haven't + // read the FDE's fields yet, so we're not prepared for + // DW_EH_PE_funcrel, although that's a fine encoding for the + // LSDA to use, since it appears in the FDE. + break; + + case DW_Z_has_personality_routine: + // The CIE's augmentation data holds the personality routine + // pointer's encoding, followed by the pointer itself. + cie->has_z_personality = true; + // Fetch the personality routine pointer's encoding from the + // augmentation data. + if (data >= data_end) return ReportIncomplete(cie); + cie->personality_encoding = DwarfPointerEncoding(*data++); + if (!reader_->ValidEncoding(cie->personality_encoding)) { + reporter_->InvalidPointerEncoding(cie->offset, + cie->personality_encoding); + return false; + } + if (!reader_->UsableEncoding(cie->personality_encoding)) { + reporter_->UnusablePointerEncoding(cie->offset, + cie->personality_encoding); + return false; + } + // Fetch the personality routine's pointer itself from the data. + cie->personality_address = + reader_->ReadEncodedPointer(data, cie->personality_encoding, + &len); + if (len > size_t(data_end - data)) + return ReportIncomplete(cie); + data += len; + break; + + case DW_Z_has_FDE_address_encoding: + // The CIE's augmentation data holds the pointer encoding to use + // for addresses in the FDE. + if (data >= data_end) return ReportIncomplete(cie); + cie->pointer_encoding = DwarfPointerEncoding(*data++); + if (!reader_->ValidEncoding(cie->pointer_encoding)) { + reporter_->InvalidPointerEncoding(cie->offset, + cie->pointer_encoding); + return false; + } + if (!reader_->UsableEncoding(cie->pointer_encoding)) { + reporter_->UnusablePointerEncoding(cie->offset, + cie->pointer_encoding); + return false; + } + break; + + case DW_Z_is_signal_trampoline: + // Frames using this CIE are signal delivery frames. + cie->has_z_signal_frame = true; + break; + + default: + // An augmentation we don't recognize. + reporter_->UnrecognizedAugmentation(cie->offset, cie->augmentation); + return false; + } + } + } + + // The CIE's instructions start here. + cie->instructions = cursor; + + return true; +} + +bool CallFrameInfo::ReadFDEFields(FDE *fde) { + const char *cursor = fde->fields; + size_t size; + + fde->address = reader_->ReadEncodedPointer(cursor, fde->cie->pointer_encoding, + &size); + if (size > size_t(fde->end - cursor)) + return ReportIncomplete(fde); + cursor += size; + reader_->SetFunctionBase(fde->address); + + // For the length, we strip off the upper nybble of the encoding used for + // the starting address. + DwarfPointerEncoding length_encoding = + DwarfPointerEncoding(fde->cie->pointer_encoding & 0x0f); + fde->size = reader_->ReadEncodedPointer(cursor, length_encoding, &size); + if (size > size_t(fde->end - cursor)) + return ReportIncomplete(fde); + cursor += size; + + // If the CIE has a 'z' augmentation string, then augmentation data + // appears here. + if (fde->cie->has_z_augmentation) { + uint64_t data_size = reader_->ReadUnsignedLEB128(cursor, &size); + if (size_t(fde->end - cursor) < size + data_size) + return ReportIncomplete(fde); + cursor += size; + + // In the abstract, we should walk the augmentation string, and extract + // items from the FDE's augmentation data as we encounter augmentation + // string characters that specify their presence: the ordering of items + // in the augmentation string determines the arrangement of values in + // the augmentation data. + // + // In practice, there's only ever one value in FDE augmentation data + // that we support --- the LSDA pointer --- and we have to bail if we + // see any unrecognized augmentation string characters. So if there is + // anything here at all, we know what it is, and where it starts. + if (fde->cie->has_z_lsda) { + // Check whether the LSDA's pointer encoding is usable now: only once + // we've parsed the FDE's starting address do we call reader_-> + // SetFunctionBase, so that the DW_EH_PE_funcrel encoding becomes + // usable. + if (!reader_->UsableEncoding(fde->cie->lsda_encoding)) { + reporter_->UnusablePointerEncoding(fde->cie->offset, + fde->cie->lsda_encoding); + return false; + } + + fde->lsda_address = + reader_->ReadEncodedPointer(cursor, fde->cie->lsda_encoding, &size); + if (size > data_size) + return ReportIncomplete(fde); + // Ideally, we would also complain here if there were unconsumed + // augmentation data. + } + + cursor += data_size; + } + + // The FDE's instructions start after those. + fde->instructions = cursor; + + return true; +} + +bool CallFrameInfo::Start() { + const char *buffer_end = buffer_ + buffer_length_; + const char *cursor; + bool all_ok = true; + const char *entry_end; + bool ok; + + // Traverse all the entries in buffer_, skipping CIEs and offering + // FDEs to the handler. + for (cursor = buffer_; cursor < buffer_end; + cursor = entry_end, all_ok = all_ok && ok) { + FDE fde; + + // Make it easy to skip this entry with 'continue': assume that + // things are not okay until we've checked all the data, and + // prepare the address of the next entry. + ok = false; + + // Read the entry's prologue. + if (!ReadEntryPrologue(cursor, &fde)) { + if (!fde.end) { + // If we couldn't even figure out this entry's extent, then we + // must stop processing entries altogether. + all_ok = false; + break; + } + entry_end = fde.end; + continue; + } + + // The next iteration picks up after this entry. + entry_end = fde.end; + + // Did we see an .eh_frame terminating mark? + if (fde.kind == kTerminator) { + // If there appears to be more data left in the section after the + // terminating mark, warn the user. But this is just a warning; + // we leave all_ok true. + if (fde.end < buffer_end) reporter_->EarlyEHTerminator(fde.offset); + break; + } + + // In this loop, we skip CIEs. We only parse them fully when we + // parse an FDE that refers to them. This limits our memory + // consumption (beyond the buffer itself) to that needed to + // process the largest single entry. + if (fde.kind != kFDE) { + ok = true; + continue; + } + + // Validate the CIE pointer. + if (fde.id > buffer_length_) { + reporter_->CIEPointerOutOfRange(fde.offset, fde.id); + continue; + } + + CIE cie; + + // Parse this FDE's CIE header. + if (!ReadEntryPrologue(buffer_ + fde.id, &cie)) + continue; + // This had better be an actual CIE. + if (cie.kind != kCIE) { + reporter_->BadCIEId(fde.offset, fde.id); + continue; + } + if (!ReadCIEFields(&cie)) + continue; + + // We now have the values that govern both the CIE and the FDE. + cie.cie = &cie; + fde.cie = &cie; + + // Parse the FDE's header. + if (!ReadFDEFields(&fde)) + continue; + + // Call Entry to ask the consumer if they're interested. + if (!handler_->Entry(fde.offset, fde.address, fde.size, + cie.version, cie.augmentation, + cie.return_address_register)) { + // The handler isn't interested in this entry. That's not an error. + ok = true; + continue; + } + + if (cie.has_z_augmentation) { + // Report the personality routine address, if we have one. + if (cie.has_z_personality) { + if (!handler_ + ->PersonalityRoutine(cie.personality_address, + IsIndirectEncoding(cie.personality_encoding))) + continue; + } + + // Report the language-specific data area address, if we have one. + if (cie.has_z_lsda) { + if (!handler_ + ->LanguageSpecificDataArea(fde.lsda_address, + IsIndirectEncoding(cie.lsda_encoding))) + continue; + } + + // If this is a signal-handling frame, report that. + if (cie.has_z_signal_frame) { + if (!handler_->SignalHandler()) + continue; + } + } + + // Interpret the CIE's instructions, and then the FDE's instructions. + State state(reader_, handler_, reporter_, fde.address); + ok = state.InterpretCIE(cie) && state.InterpretFDE(fde); + + // Tell the ByteReader that the function start address from the + // FDE header is no longer valid. + reader_->ClearFunctionBase(); + + // Report the end of the entry. + handler_->End(); + } + + return all_ok; +} + +const char *CallFrameInfo::KindName(EntryKind kind) { + if (kind == CallFrameInfo::kUnknown) + return "entry"; + else if (kind == CallFrameInfo::kCIE) + return "common information entry"; + else if (kind == CallFrameInfo::kFDE) + return "frame description entry"; + else { + MOZ_ASSERT (kind == CallFrameInfo::kTerminator); + return ".eh_frame sequence terminator"; + } +} + +bool CallFrameInfo::ReportIncomplete(Entry *entry) { + reporter_->Incomplete(entry->offset, entry->kind); + return false; +} + +void CallFrameInfo::Reporter::Incomplete(uint64 offset, + CallFrameInfo::EntryKind kind) { + char buf[300]; + snprintf_literal(buf, + "%s: CFI %s at offset 0x%llx in '%s': entry ends early\n", + filename_.c_str(), CallFrameInfo::KindName(kind), offset, + section_.c_str()); + log_(buf); +} + +void CallFrameInfo::Reporter::EarlyEHTerminator(uint64 offset) { + char buf[300]; + snprintf_literal(buf, + "%s: CFI at offset 0x%llx in '%s': saw end-of-data marker" + " before end of section contents\n", + filename_.c_str(), offset, section_.c_str()); + log_(buf); +} + +void CallFrameInfo::Reporter::CIEPointerOutOfRange(uint64 offset, + uint64 cie_offset) { + char buf[300]; + snprintf_literal(buf, + "%s: CFI frame description entry at offset 0x%llx in '%s':" + " CIE pointer is out of range: 0x%llx\n", + filename_.c_str(), offset, section_.c_str(), cie_offset); + log_(buf); +} + +void CallFrameInfo::Reporter::BadCIEId(uint64 offset, uint64 cie_offset) { + char buf[300]; + snprintf_literal(buf, + "%s: CFI frame description entry at offset 0x%llx in '%s':" + " CIE pointer does not point to a CIE: 0x%llx\n", + filename_.c_str(), offset, section_.c_str(), cie_offset); + log_(buf); +} + +void CallFrameInfo::Reporter::UnrecognizedVersion(uint64 offset, int version) { + char buf[300]; + snprintf_literal(buf, + "%s: CFI frame description entry at offset 0x%llx in '%s':" + " CIE specifies unrecognized version: %d\n", + filename_.c_str(), offset, section_.c_str(), version); + log_(buf); +} + +void CallFrameInfo::Reporter::UnrecognizedAugmentation(uint64 offset, + const string &aug) { + char buf[300]; + snprintf_literal(buf, + "%s: CFI frame description entry at offset 0x%llx in '%s':" + " CIE specifies unrecognized augmentation: '%s'\n", + filename_.c_str(), offset, section_.c_str(), aug.c_str()); + log_(buf); +} + +void CallFrameInfo::Reporter::InvalidPointerEncoding(uint64 offset, + uint8 encoding) { + char buf[300]; + snprintf_literal(buf, + "%s: CFI common information entry at offset 0x%llx in '%s':" + " 'z' augmentation specifies invalid pointer encoding: " + "0x%02x\n", + filename_.c_str(), offset, section_.c_str(), encoding); + log_(buf); +} + +void CallFrameInfo::Reporter::UnusablePointerEncoding(uint64 offset, + uint8 encoding) { + char buf[300]; + snprintf_literal(buf, + "%s: CFI common information entry at offset 0x%llx in '%s':" + " 'z' augmentation specifies a pointer encoding for which" + " we have no base address: 0x%02x\n", + filename_.c_str(), offset, section_.c_str(), encoding); + log_(buf); +} + +void CallFrameInfo::Reporter::RestoreInCIE(uint64 offset, uint64 insn_offset) { + char buf[300]; + snprintf_literal(buf, + "%s: CFI common information entry at offset 0x%llx in '%s':" + " the DW_CFA_restore instruction at offset 0x%llx" + " cannot be used in a common information entry\n", + filename_.c_str(), offset, section_.c_str(), insn_offset); + log_(buf); +} + +void CallFrameInfo::Reporter::BadInstruction(uint64 offset, + CallFrameInfo::EntryKind kind, + uint64 insn_offset) { + char buf[300]; + snprintf_literal(buf, + "%s: CFI %s at offset 0x%llx in section '%s':" + " the instruction at offset 0x%llx is unrecognized\n", + filename_.c_str(), CallFrameInfo::KindName(kind), + offset, section_.c_str(), insn_offset); + log_(buf); +} + +void CallFrameInfo::Reporter::NoCFARule(uint64 offset, + CallFrameInfo::EntryKind kind, + uint64 insn_offset) { + char buf[300]; + snprintf_literal(buf, + "%s: CFI %s at offset 0x%llx in section '%s':" + " the instruction at offset 0x%llx assumes that a CFA rule " + "has been set, but none has been set\n", + filename_.c_str(), CallFrameInfo::KindName(kind), offset, + section_.c_str(), insn_offset); + log_(buf); +} + +void CallFrameInfo::Reporter::EmptyStateStack(uint64 offset, + CallFrameInfo::EntryKind kind, + uint64 insn_offset) { + char buf[300]; + snprintf_literal(buf, + "%s: CFI %s at offset 0x%llx in section '%s':" + " the DW_CFA_restore_state instruction at offset 0x%llx" + " should pop a saved state from the stack, but the stack " + "is empty\n", + filename_.c_str(), CallFrameInfo::KindName(kind), offset, + section_.c_str(), insn_offset); + log_(buf); +} + +void CallFrameInfo::Reporter::ClearingCFARule(uint64 offset, + CallFrameInfo::EntryKind kind, + uint64 insn_offset) { + char buf[300]; + snprintf_literal(buf, + "%s: CFI %s at offset 0x%llx in section '%s':" + " the DW_CFA_restore_state instruction at offset 0x%llx" + " would clear the CFA rule in effect\n", + filename_.c_str(), CallFrameInfo::KindName(kind), offset, + section_.c_str(), insn_offset); + log_(buf); +} + + +unsigned int DwarfCFIToModule::RegisterNames::I386() { + /* + 8 "$eax", "$ecx", "$edx", "$ebx", "$esp", "$ebp", "$esi", "$edi", + 3 "$eip", "$eflags", "$unused1", + 8 "$st0", "$st1", "$st2", "$st3", "$st4", "$st5", "$st6", "$st7", + 2 "$unused2", "$unused3", + 8 "$xmm0", "$xmm1", "$xmm2", "$xmm3", "$xmm4", "$xmm5", "$xmm6", "$xmm7", + 8 "$mm0", "$mm1", "$mm2", "$mm3", "$mm4", "$mm5", "$mm6", "$mm7", + 3 "$fcw", "$fsw", "$mxcsr", + 8 "$es", "$cs", "$ss", "$ds", "$fs", "$gs", "$unused4", "$unused5", + 2 "$tr", "$ldtr" + */ + return 8 + 3 + 8 + 2 + 8 + 8 + 3 + 8 + 2; +} + +unsigned int DwarfCFIToModule::RegisterNames::X86_64() { + /* + 8 "$rax", "$rdx", "$rcx", "$rbx", "$rsi", "$rdi", "$rbp", "$rsp", + 8 "$r8", "$r9", "$r10", "$r11", "$r12", "$r13", "$r14", "$r15", + 1 "$rip", + 8 "$xmm0","$xmm1","$xmm2", "$xmm3", "$xmm4", "$xmm5", "$xmm6", "$xmm7", + 8 "$xmm8","$xmm9","$xmm10","$xmm11","$xmm12","$xmm13","$xmm14","$xmm15", + 8 "$st0", "$st1", "$st2", "$st3", "$st4", "$st5", "$st6", "$st7", + 8 "$mm0", "$mm1", "$mm2", "$mm3", "$mm4", "$mm5", "$mm6", "$mm7", + 1 "$rflags", + 8 "$es", "$cs", "$ss", "$ds", "$fs", "$gs", "$unused1", "$unused2", + 4 "$fs.base", "$gs.base", "$unused3", "$unused4", + 2 "$tr", "$ldtr", + 3 "$mxcsr", "$fcw", "$fsw" + */ + return 8 + 8 + 1 + 8 + 8 + 8 + 8 + 1 + 8 + 4 + 2 + 3; +} + +// Per ARM IHI 0040A, section 3.1 +unsigned int DwarfCFIToModule::RegisterNames::ARM() { + /* + 8 "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", + 8 "r8", "r9", "r10", "r11", "r12", "sp", "lr", "pc", + 8 "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", + 8 "fps", "cpsr", "", "", "", "", "", "", + 8 "", "", "", "", "", "", "", "", + 8 "", "", "", "", "", "", "", "", + 8 "", "", "", "", "", "", "", "", + 8 "", "", "", "", "", "", "", "", + 8 "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", + 8 "s8", "s9", "s10", "s11", "s12", "s13", "s14", "s15", + 8 "s16", "s17", "s18", "s19", "s20", "s21", "s22", "s23", + 8 "s24", "s25", "s26", "s27", "s28", "s29", "s30", "s31", + 8 "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7" + */ + return 13 * 8; +} + +// See prototype for comments. +int32_t parseDwarfExpr(Summariser* summ, const ByteReader* reader, + string expr, bool debug, + bool pushCfaAtStart, bool derefAtEnd) +{ + const char* cursor = expr.c_str(); + const char* end1 = cursor + expr.length(); + + char buf[100]; + if (debug) { + snprintf_literal(buf, "LUL.DW << DwarfExpr, len is %d\n", + (int)(end1 - cursor)); + summ->Log(buf); + } + + // Add a marker for the start of this expression. In it, indicate + // whether or not the CFA should be pushed onto the stack prior to + // evaluation. + int32_t start_ix + = summ->AddPfxInstr(PfxInstr(PX_Start, pushCfaAtStart ? 1 : 0)); + MOZ_ASSERT(start_ix >= 0); + + while (cursor < end1) { + + uint8 opc = reader->ReadOneByte(cursor); + cursor++; + + const char* nm = nullptr; + PfxExprOp pxop = PX_End; + + switch (opc) { + + case DW_OP_lit0 ... DW_OP_lit31: { + int32_t simm32 = (int32_t)(opc - DW_OP_lit0); + if (debug) { + snprintf_literal(buf, "LUL.DW DW_OP_lit%d\n", (int)simm32); + summ->Log(buf); + } + (void) summ->AddPfxInstr(PfxInstr(PX_SImm32, simm32)); + break; + } + + case DW_OP_breg0 ... DW_OP_breg31: { + size_t len; + int64_t n = reader->ReadSignedLEB128(cursor, &len); + cursor += len; + DW_REG_NUMBER reg = (DW_REG_NUMBER)(opc - DW_OP_breg0); + if (debug) { + snprintf_literal(buf, "LUL.DW DW_OP_breg%d %lld\n", + (int)reg, (long long int)n); + summ->Log(buf); + } + // PfxInstr only allows a 32 bit signed offset. So we + // must fail if the immediate is out of range. + if (n < INT32_MIN || INT32_MAX < n) + goto fail; + (void) summ->AddPfxInstr(PfxInstr(PX_DwReg, reg)); + (void) summ->AddPfxInstr(PfxInstr(PX_SImm32, (int32_t)n)); + (void) summ->AddPfxInstr(PfxInstr(PX_Add)); + break; + } + + case DW_OP_const4s: { + uint64_t u64 = reader->ReadFourBytes(cursor); + cursor += 4; + // u64 is guaranteed by |ReadFourBytes| to be in the + // range 0 .. FFFFFFFF inclusive. But to be safe: + uint32_t u32 = (uint32_t)(u64 & 0xFFFFFFFF); + int32_t s32 = (int32_t)u32; + if (debug) { + snprintf_literal(buf, "LUL.DW DW_OP_const4s %d\n", (int)s32); + summ->Log(buf); + } + (void) summ->AddPfxInstr(PfxInstr(PX_SImm32, s32)); + break; + } + + case DW_OP_deref: nm = "deref"; pxop = PX_Deref; goto no_operands; + case DW_OP_and: nm = "and"; pxop = PX_And; goto no_operands; + case DW_OP_plus: nm = "plus"; pxop = PX_Add; goto no_operands; + case DW_OP_minus: nm = "minus"; pxop = PX_Sub; goto no_operands; + case DW_OP_shl: nm = "shl"; pxop = PX_Shl; goto no_operands; + case DW_OP_ge: nm = "ge"; pxop = PX_CmpGES; goto no_operands; + no_operands: + MOZ_ASSERT(nm && pxop != PX_End); + if (debug) { + snprintf_literal(buf, "LUL.DW DW_OP_%s\n", nm); + summ->Log(buf); + } + (void) summ->AddPfxInstr(PfxInstr(pxop)); + break; + + default: + if (debug) { + snprintf_literal(buf, "LUL.DW unknown opc %d\n", (int)opc); + summ->Log(buf); + } + goto fail; + + } // switch (opc) + + } // while (cursor < end1) + + MOZ_ASSERT(cursor >= end1); + + if (cursor > end1) { + // We overran the Dwarf expression. Give up. + goto fail; + } + + // For DW_CFA_expression, what the expression denotes is the address + // of where the previous value is located. The caller of this routine + // may therefore request one last dereference before the end marker is + // inserted. + if (derefAtEnd) { + (void) summ->AddPfxInstr(PfxInstr(PX_Deref)); + } + + // Insert an end marker, and declare success. + (void) summ->AddPfxInstr(PfxInstr(PX_End)); + if (debug) { + snprintf_literal(buf, "LUL.DW conversion of dwarf expression succeeded, " + "ix = %d\n", (int)start_ix); + summ->Log(buf); + summ->Log("LUL.DW >>\n"); + } + return start_ix; + + fail: + if (debug) { + summ->Log("LUL.DW conversion of dwarf expression failed\n"); + summ->Log("LUL.DW >>\n"); + } + return -1; +} + + +bool DwarfCFIToModule::Entry(size_t offset, uint64 address, uint64 length, + uint8 version, const string &augmentation, + unsigned return_address) { + if (DEBUG_DWARF) { + char buf[100]; + snprintf_literal(buf, "LUL.DW DwarfCFIToModule::Entry 0x%llx,+%lld\n", + address, length); + summ_->Log(buf); + } + + summ_->Entry(address, length); + + // If dwarf2reader::CallFrameInfo can handle this version and + // augmentation, then we should be okay with that, so there's no + // need to check them here. + + // Get ready to collect entries. + return_address_ = return_address; + + // Breakpad STACK CFI records must provide a .ra rule, but DWARF CFI + // may not establish any rule for .ra if the return address column + // is an ordinary register, and that register holds the return + // address on entry to the function. So establish an initial .ra + // rule citing the return address register. + if (return_address_ < num_dw_regs_) { + summ_->Rule(address, return_address_, NODEREF, return_address, 0); + } + + return true; +} + +const UniqueString* DwarfCFIToModule::RegisterName(int i) { + if (i < 0) { + MOZ_ASSERT(i == kCFARegister); + return usu_->ToUniqueString(".cfa"); + } + unsigned reg = i; + if (reg == return_address_) + return usu_->ToUniqueString(".ra"); + + char buf[30]; + snprintf_literal(buf, "dwarf_reg_%u", reg); + return usu_->ToUniqueString(buf); +} + +bool DwarfCFIToModule::UndefinedRule(uint64 address, int reg) { + reporter_->UndefinedNotSupported(entry_offset_, RegisterName(reg)); + // Treat this as a non-fatal error. + return true; +} + +bool DwarfCFIToModule::SameValueRule(uint64 address, int reg) { + if (DEBUG_DWARF) { + char buf[100]; + snprintf_literal(buf, "LUL.DW 0x%llx: old r%d = Same\n", address, reg); + summ_->Log(buf); + } + // reg + 0 + summ_->Rule(address, reg, NODEREF, reg, 0); + return true; +} + +bool DwarfCFIToModule::OffsetRule(uint64 address, int reg, + int base_register, long offset) { + if (DEBUG_DWARF) { + char buf[100]; + snprintf_literal(buf, "LUL.DW 0x%llx: old r%d = *(r%d + %ld)\n", + address, reg, base_register, offset); + summ_->Log(buf); + } + // *(base_register + offset) + summ_->Rule(address, reg, DEREF, base_register, offset); + return true; +} + +bool DwarfCFIToModule::ValOffsetRule(uint64 address, int reg, + int base_register, long offset) { + if (DEBUG_DWARF) { + char buf[100]; + snprintf_literal(buf, "LUL.DW 0x%llx: old r%d = r%d + %ld\n", + address, reg, base_register, offset); + summ_->Log(buf); + } + // base_register + offset + summ_->Rule(address, reg, NODEREF, base_register, offset); + return true; +} + +bool DwarfCFIToModule::RegisterRule(uint64 address, int reg, + int base_register) { + if (DEBUG_DWARF) { + char buf[100]; + snprintf_literal(buf, "LUL.DW 0x%llx: old r%d = r%d\n", + address, reg, base_register); + summ_->Log(buf); + } + // base_register + 0 + summ_->Rule(address, reg, NODEREF, base_register, 0); + return true; +} + +bool DwarfCFIToModule::ExpressionRule(uint64 address, int reg, + const string &expression) +{ + bool debug = !!DEBUG_DWARF; + int32_t start_ix = parseDwarfExpr(summ_, reader_, expression, debug, + true/*pushCfaAtStart*/, + true/*derefAtEnd*/); + if (start_ix >= 0) { + summ_->Rule(address, reg, PFXEXPR, 0, start_ix); + } else { + // Parsing of the Dwarf expression failed. Treat this as a + // non-fatal error, hence return |true| even on this path. + reporter_->ExpressionCouldNotBeSummarised(entry_offset_, RegisterName(reg)); + } + return true; +} + +bool DwarfCFIToModule::ValExpressionRule(uint64 address, int reg, + const string &expression) +{ + bool debug = !!DEBUG_DWARF; + int32_t start_ix = parseDwarfExpr(summ_, reader_, expression, debug, + true/*pushCfaAtStart*/, + false/*!derefAtEnd*/); + if (start_ix >= 0) { + summ_->Rule(address, reg, PFXEXPR, 0, start_ix); + } else { + // Parsing of the Dwarf expression failed. Treat this as a + // non-fatal error, hence return |true| even on this path. + reporter_->ExpressionCouldNotBeSummarised(entry_offset_, RegisterName(reg)); + } + return true; +} + +bool DwarfCFIToModule::End() { + //module_->AddStackFrameEntry(entry_); + if (DEBUG_DWARF) { + summ_->Log("LUL.DW DwarfCFIToModule::End()\n"); + } + summ_->End(); + return true; +} + +void DwarfCFIToModule::Reporter::UndefinedNotSupported( + size_t offset, + const UniqueString* reg) { + char buf[300]; + snprintf_literal(buf, + "DwarfCFIToModule::Reporter::UndefinedNotSupported()\n"); + log_(buf); + //BPLOG(INFO) << file_ << ", section '" << section_ + // << "': the call frame entry at offset 0x" + // << std::setbase(16) << offset << std::setbase(10) + // << " sets the rule for register '" << FromUniqueString(reg) + // << "' to 'undefined', but the Breakpad symbol file format cannot " + // << " express this"; +} + +// FIXME: move this somewhere sensible +static bool is_power_of_2(uint64_t n) +{ + int i, nSetBits = 0; + for (i = 0; i < 8*(int)sizeof(n); i++) { + if ((n & ((uint64_t)1) << i) != 0) + nSetBits++; + } + return nSetBits <= 1; +} + +void DwarfCFIToModule::Reporter::ExpressionCouldNotBeSummarised( + size_t offset, + const UniqueString* reg) { + static uint64_t n_complaints = 0; // This isn't threadsafe + n_complaints++; + if (!is_power_of_2(n_complaints)) + return; + char buf[300]; + snprintf_literal(buf, + "DwarfCFIToModule::Reporter::" + "ExpressionCouldNotBeSummarised(shown %llu times)\n", + (unsigned long long int)n_complaints); + log_(buf); +} + +} // namespace lul diff --git a/tools/profiler/lul/LulDwarfExt.h b/tools/profiler/lul/LulDwarfExt.h new file mode 100644 index 0000000000..f3555ac554 --- /dev/null +++ b/tools/profiler/lul/LulDwarfExt.h @@ -0,0 +1,1287 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ + +// Copyright 2006, 2010 Google Inc. All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Original author: Jim Blandy + +// This file is derived from the following files in +// toolkit/crashreporter/google-breakpad: +// src/common/dwarf/types.h +// src/common/dwarf/dwarf2enums.h +// src/common/dwarf/bytereader.h +// src/common/dwarf_cfi_to_module.h +// src/common/dwarf/dwarf2reader.h + +#ifndef LulDwarfExt_h +#define LulDwarfExt_h + +#include + +#include "mozilla/Assertions.h" + +#include "LulDwarfSummariser.h" + +typedef signed char int8; +typedef short int16; +typedef int int32; +typedef long long int64; + +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint32; +typedef unsigned long long uint64; + +#ifdef __PTRDIFF_TYPE__ +typedef __PTRDIFF_TYPE__ intptr; +typedef unsigned __PTRDIFF_TYPE__ uintptr; +#else +#error "Can't find pointer-sized integral types." +#endif + + +namespace lul { + +// Exception handling frame description pointer formats, as described +// by the Linux Standard Base Core Specification 4.0, section 11.5, +// DWARF Extensions. +enum DwarfPointerEncoding + { + DW_EH_PE_absptr = 0x00, + DW_EH_PE_omit = 0xff, + DW_EH_PE_uleb128 = 0x01, + DW_EH_PE_udata2 = 0x02, + DW_EH_PE_udata4 = 0x03, + DW_EH_PE_udata8 = 0x04, + DW_EH_PE_sleb128 = 0x09, + DW_EH_PE_sdata2 = 0x0A, + DW_EH_PE_sdata4 = 0x0B, + DW_EH_PE_sdata8 = 0x0C, + DW_EH_PE_pcrel = 0x10, + DW_EH_PE_textrel = 0x20, + DW_EH_PE_datarel = 0x30, + DW_EH_PE_funcrel = 0x40, + DW_EH_PE_aligned = 0x50, + + // The GNU toolchain sources define this enum value as well, + // simply to help classify the lower nybble values into signed and + // unsigned groups. + DW_EH_PE_signed = 0x08, + + // This is not documented in LSB 4.0, but it is used in both the + // Linux and OS X toolchains. It can be added to any other + // encoding (except DW_EH_PE_aligned), and indicates that the + // encoded value represents the address at which the true address + // is stored, not the true address itself. + DW_EH_PE_indirect = 0x80 + }; + + +// We can't use the obvious name of LITTLE_ENDIAN and BIG_ENDIAN +// because it conflicts with a macro +enum Endianness { + ENDIANNESS_BIG, + ENDIANNESS_LITTLE +}; + +// A ByteReader knows how to read single- and multi-byte values of +// various endiannesses, sizes, and encodings, as used in DWARF +// debugging information and Linux C++ exception handling data. +class ByteReader { + public: + // Construct a ByteReader capable of reading one-, two-, four-, and + // eight-byte values according to ENDIANNESS, absolute machine-sized + // addresses, DWARF-style "initial length" values, signed and + // unsigned LEB128 numbers, and Linux C++ exception handling data's + // encoded pointers. + explicit ByteReader(enum Endianness endianness); + virtual ~ByteReader(); + + // Read a single byte from BUFFER and return it as an unsigned 8 bit + // number. + uint8 ReadOneByte(const char* buffer) const; + + // Read two bytes from BUFFER and return them as an unsigned 16 bit + // number, using this ByteReader's endianness. + uint16 ReadTwoBytes(const char* buffer) const; + + // Read four bytes from BUFFER and return them as an unsigned 32 bit + // number, using this ByteReader's endianness. This function returns + // a uint64 so that it is compatible with ReadAddress and + // ReadOffset. The number it returns will never be outside the range + // of an unsigned 32 bit integer. + uint64 ReadFourBytes(const char* buffer) const; + + // Read eight bytes from BUFFER and return them as an unsigned 64 + // bit number, using this ByteReader's endianness. + uint64 ReadEightBytes(const char* buffer) const; + + // Read an unsigned LEB128 (Little Endian Base 128) number from + // BUFFER and return it as an unsigned 64 bit integer. Set LEN to + // the number of bytes read. + // + // The unsigned LEB128 representation of an integer N is a variable + // number of bytes: + // + // - If N is between 0 and 0x7f, then its unsigned LEB128 + // representation is a single byte whose value is N. + // + // - Otherwise, its unsigned LEB128 representation is (N & 0x7f) | + // 0x80, followed by the unsigned LEB128 representation of N / + // 128, rounded towards negative infinity. + // + // In other words, we break VALUE into groups of seven bits, put + // them in little-endian order, and then write them as eight-bit + // bytes with the high bit on all but the last. + uint64 ReadUnsignedLEB128(const char* buffer, size_t* len) const; + + // Read a signed LEB128 number from BUFFER and return it as an + // signed 64 bit integer. Set LEN to the number of bytes read. + // + // The signed LEB128 representation of an integer N is a variable + // number of bytes: + // + // - If N is between -0x40 and 0x3f, then its signed LEB128 + // representation is a single byte whose value is N in two's + // complement. + // + // - Otherwise, its signed LEB128 representation is (N & 0x7f) | + // 0x80, followed by the signed LEB128 representation of N / 128, + // rounded towards negative infinity. + // + // In other words, we break VALUE into groups of seven bits, put + // them in little-endian order, and then write them as eight-bit + // bytes with the high bit on all but the last. + int64 ReadSignedLEB128(const char* buffer, size_t* len) const; + + // Indicate that addresses on this architecture are SIZE bytes long. SIZE + // must be either 4 or 8. (DWARF allows addresses to be any number of + // bytes in length from 1 to 255, but we only support 32- and 64-bit + // addresses at the moment.) You must call this before using the + // ReadAddress member function. + // + // For data in a .debug_info section, or something that .debug_info + // refers to like line number or macro data, the compilation unit + // header's address_size field indicates the address size to use. Call + // frame information doesn't indicate its address size (a shortcoming of + // the spec); you must supply the appropriate size based on the + // architecture of the target machine. + void SetAddressSize(uint8 size); + + // Return the current address size, in bytes. This is either 4, + // indicating 32-bit addresses, or 8, indicating 64-bit addresses. + uint8 AddressSize() const { return address_size_; } + + // Read an address from BUFFER and return it as an unsigned 64 bit + // integer, respecting this ByteReader's endianness and address size. You + // must call SetAddressSize before calling this function. + uint64 ReadAddress(const char* buffer) const; + + // DWARF actually defines two slightly different formats: 32-bit DWARF + // and 64-bit DWARF. This is *not* related to the size of registers or + // addresses on the target machine; it refers only to the size of section + // offsets and data lengths appearing in the DWARF data. One only needs + // 64-bit DWARF when the debugging data itself is larger than 4GiB. + // 32-bit DWARF can handle x86_64 or PPC64 code just fine, unless the + // debugging data itself is very large. + // + // DWARF information identifies itself as 32-bit or 64-bit DWARF: each + // compilation unit and call frame information entry begins with an + // "initial length" field, which, in addition to giving the length of the + // data, also indicates the size of section offsets and lengths appearing + // in that data. The ReadInitialLength member function, below, reads an + // initial length and sets the ByteReader's offset size as a side effect. + // Thus, in the normal process of reading DWARF data, the appropriate + // offset size is set automatically. So, you should only need to call + // SetOffsetSize if you are using the same ByteReader to jump from the + // midst of one block of DWARF data into another. + + // Read a DWARF "initial length" field from START, and return it as + // an unsigned 64 bit integer, respecting this ByteReader's + // endianness. Set *LEN to the length of the initial length in + // bytes, either four or twelve. As a side effect, set this + // ByteReader's offset size to either 4 (if we see a 32-bit DWARF + // initial length) or 8 (if we see a 64-bit DWARF initial length). + // + // A DWARF initial length is either: + // + // - a byte count stored as an unsigned 32-bit value less than + // 0xffffff00, indicating that the data whose length is being + // measured uses the 32-bit DWARF format, or + // + // - The 32-bit value 0xffffffff, followed by a 64-bit byte count, + // indicating that the data whose length is being measured uses + // the 64-bit DWARF format. + uint64 ReadInitialLength(const char* start, size_t* len); + + // Read an offset from BUFFER and return it as an unsigned 64 bit + // integer, respecting the ByteReader's endianness. In 32-bit DWARF, the + // offset is 4 bytes long; in 64-bit DWARF, the offset is eight bytes + // long. You must call ReadInitialLength or SetOffsetSize before calling + // this function; see the comments above for details. + uint64 ReadOffset(const char* buffer) const; + + // Return the current offset size, in bytes. + // A return value of 4 indicates that we are reading 32-bit DWARF. + // A return value of 8 indicates that we are reading 64-bit DWARF. + uint8 OffsetSize() const { return offset_size_; } + + // Indicate that section offsets and lengths are SIZE bytes long. SIZE + // must be either 4 (meaning 32-bit DWARF) or 8 (meaning 64-bit DWARF). + // Usually, you should not call this function yourself; instead, let a + // call to ReadInitialLength establish the data's offset size + // automatically. + void SetOffsetSize(uint8 size); + + // The Linux C++ ABI uses a variant of DWARF call frame information + // for exception handling. This data is included in the program's + // address space as the ".eh_frame" section, and intepreted at + // runtime to walk the stack, find exception handlers, and run + // cleanup code. The format is mostly the same as DWARF CFI, with + // some adjustments made to provide the additional + // exception-handling data, and to make the data easier to work with + // in memory --- for example, to allow it to be placed in read-only + // memory even when describing position-independent code. + // + // In particular, exception handling data can select a number of + // different encodings for pointers that appear in the data, as + // described by the DwarfPointerEncoding enum. There are actually + // four axes(!) to the encoding: + // + // - The pointer size: pointers can be 2, 4, or 8 bytes long, or use + // the DWARF LEB128 encoding. + // + // - The pointer's signedness: pointers can be signed or unsigned. + // + // - The pointer's base address: the data stored in the exception + // handling data can be the actual address (that is, an absolute + // pointer), or relative to one of a number of different base + // addreses --- including that of the encoded pointer itself, for + // a form of "pc-relative" addressing. + // + // - The pointer may be indirect: it may be the address where the + // true pointer is stored. (This is used to refer to things via + // global offset table entries, program linkage table entries, or + // other tricks used in position-independent code.) + // + // There are also two options that fall outside that matrix + // altogether: the pointer may be omitted, or it may have padding to + // align it on an appropriate address boundary. (That last option + // may seem like it should be just another axis, but it is not.) + + // Indicate that the exception handling data is loaded starting at + // SECTION_BASE, and that the start of its buffer in our own memory + // is BUFFER_BASE. This allows us to find the address that a given + // byte in our buffer would have when loaded into the program the + // data describes. We need this to resolve DW_EH_PE_pcrel pointers. + void SetCFIDataBase(uint64 section_base, const char *buffer_base); + + // Indicate that the base address of the program's ".text" section + // is TEXT_BASE. We need this to resolve DW_EH_PE_textrel pointers. + void SetTextBase(uint64 text_base); + + // Indicate that the base address for DW_EH_PE_datarel pointers is + // DATA_BASE. The proper value depends on the ABI; it is usually the + // address of the global offset table, held in a designated register in + // position-independent code. You will need to look at the startup code + // for the target system to be sure. I tried; my eyes bled. + void SetDataBase(uint64 data_base); + + // Indicate that the base address for the FDE we are processing is + // FUNCTION_BASE. This is the start address of DW_EH_PE_funcrel + // pointers. (This encoding does not seem to be used by the GNU + // toolchain.) + void SetFunctionBase(uint64 function_base); + + // Indicate that we are no longer processing any FDE, so any use of + // a DW_EH_PE_funcrel encoding is an error. + void ClearFunctionBase(); + + // Return true if ENCODING is a valid pointer encoding. + bool ValidEncoding(DwarfPointerEncoding encoding) const; + + // Return true if we have all the information we need to read a + // pointer that uses ENCODING. This checks that the appropriate + // SetFooBase function for ENCODING has been called. + bool UsableEncoding(DwarfPointerEncoding encoding) const; + + // Read an encoded pointer from BUFFER using ENCODING; return the + // absolute address it represents, and set *LEN to the pointer's + // length in bytes, including any padding for aligned pointers. + // + // This function calls 'abort' if ENCODING is invalid or refers to a + // base address this reader hasn't been given, so you should check + // with ValidEncoding and UsableEncoding first if you would rather + // die in a more helpful way. + uint64 ReadEncodedPointer(const char *buffer, DwarfPointerEncoding encoding, + size_t *len) const; + + private: + + // Function pointer type for our address and offset readers. + typedef uint64 (ByteReader::*AddressReader)(const char*) const; + + // Read an offset from BUFFER and return it as an unsigned 64 bit + // integer. DWARF2/3 define offsets as either 4 or 8 bytes, + // generally depending on the amount of DWARF2/3 info present. + // This function pointer gets set by SetOffsetSize. + AddressReader offset_reader_; + + // Read an address from BUFFER and return it as an unsigned 64 bit + // integer. DWARF2/3 allow addresses to be any size from 0-255 + // bytes currently. Internally we support 4 and 8 byte addresses, + // and will CHECK on anything else. + // This function pointer gets set by SetAddressSize. + AddressReader address_reader_; + + Endianness endian_; + uint8 address_size_; + uint8 offset_size_; + + // Base addresses for Linux C++ exception handling data's encoded pointers. + bool have_section_base_, have_text_base_, have_data_base_; + bool have_function_base_; + uint64 section_base_; + uint64 text_base_, data_base_, function_base_; + const char *buffer_base_; +}; + + +inline uint8 ByteReader::ReadOneByte(const char* buffer) const { + return buffer[0]; +} + +inline uint16 ByteReader::ReadTwoBytes(const char* signed_buffer) const { + const unsigned char *buffer + = reinterpret_cast(signed_buffer); + const uint16 buffer0 = buffer[0]; + const uint16 buffer1 = buffer[1]; + if (endian_ == ENDIANNESS_LITTLE) { + return buffer0 | buffer1 << 8; + } else { + return buffer1 | buffer0 << 8; + } +} + +inline uint64 ByteReader::ReadFourBytes(const char* signed_buffer) const { + const unsigned char *buffer + = reinterpret_cast(signed_buffer); + const uint32 buffer0 = buffer[0]; + const uint32 buffer1 = buffer[1]; + const uint32 buffer2 = buffer[2]; + const uint32 buffer3 = buffer[3]; + if (endian_ == ENDIANNESS_LITTLE) { + return buffer0 | buffer1 << 8 | buffer2 << 16 | buffer3 << 24; + } else { + return buffer3 | buffer2 << 8 | buffer1 << 16 | buffer0 << 24; + } +} + +inline uint64 ByteReader::ReadEightBytes(const char* signed_buffer) const { + const unsigned char *buffer + = reinterpret_cast(signed_buffer); + const uint64 buffer0 = buffer[0]; + const uint64 buffer1 = buffer[1]; + const uint64 buffer2 = buffer[2]; + const uint64 buffer3 = buffer[3]; + const uint64 buffer4 = buffer[4]; + const uint64 buffer5 = buffer[5]; + const uint64 buffer6 = buffer[6]; + const uint64 buffer7 = buffer[7]; + if (endian_ == ENDIANNESS_LITTLE) { + return buffer0 | buffer1 << 8 | buffer2 << 16 | buffer3 << 24 | + buffer4 << 32 | buffer5 << 40 | buffer6 << 48 | buffer7 << 56; + } else { + return buffer7 | buffer6 << 8 | buffer5 << 16 | buffer4 << 24 | + buffer3 << 32 | buffer2 << 40 | buffer1 << 48 | buffer0 << 56; + } +} + +// Read an unsigned LEB128 number. Each byte contains 7 bits of +// information, plus one bit saying whether the number continues or +// not. + +inline uint64 ByteReader::ReadUnsignedLEB128(const char* buffer, + size_t* len) const { + uint64 result = 0; + size_t num_read = 0; + unsigned int shift = 0; + unsigned char byte; + + do { + byte = *buffer++; + num_read++; + + result |= (static_cast(byte & 0x7f)) << shift; + + shift += 7; + + } while (byte & 0x80); + + *len = num_read; + + return result; +} + +// Read a signed LEB128 number. These are like regular LEB128 +// numbers, except the last byte may have a sign bit set. + +inline int64 ByteReader::ReadSignedLEB128(const char* buffer, + size_t* len) const { + int64 result = 0; + unsigned int shift = 0; + size_t num_read = 0; + unsigned char byte; + + do { + byte = *buffer++; + num_read++; + result |= (static_cast(byte & 0x7f) << shift); + shift += 7; + } while (byte & 0x80); + + if ((shift < 8 * sizeof (result)) && (byte & 0x40)) + result |= -((static_cast(1)) << shift); + *len = num_read; + return result; +} + +inline uint64 ByteReader::ReadOffset(const char* buffer) const { + MOZ_ASSERT(this->offset_reader_); + return (this->*offset_reader_)(buffer); +} + +inline uint64 ByteReader::ReadAddress(const char* buffer) const { + MOZ_ASSERT(this->address_reader_); + return (this->*address_reader_)(buffer); +} + +inline void ByteReader::SetCFIDataBase(uint64 section_base, + const char *buffer_base) { + section_base_ = section_base; + buffer_base_ = buffer_base; + have_section_base_ = true; +} + +inline void ByteReader::SetTextBase(uint64 text_base) { + text_base_ = text_base; + have_text_base_ = true; +} + +inline void ByteReader::SetDataBase(uint64 data_base) { + data_base_ = data_base; + have_data_base_ = true; +} + +inline void ByteReader::SetFunctionBase(uint64 function_base) { + function_base_ = function_base; + have_function_base_ = true; +} + +inline void ByteReader::ClearFunctionBase() { + have_function_base_ = false; +} + + +// (derived from) +// dwarf_cfi_to_module.h: Define the DwarfCFIToModule class, which +// accepts parsed DWARF call frame info and adds it to a Summariser object. + +// This class is a reader for DWARF's Call Frame Information. CFI +// describes how to unwind stack frames --- even for functions that do +// not follow fixed conventions for saving registers, whose frame size +// varies as they execute, etc. +// +// CFI describes, at each machine instruction, how to compute the +// stack frame's base address, how to find the return address, and +// where to find the saved values of the caller's registers (if the +// callee has stashed them somewhere to free up the registers for its +// own use). +// +// For example, suppose we have a function whose machine code looks +// like this (imagine an assembly language that looks like C, for a +// machine with 32-bit registers, and a stack that grows towards lower +// addresses): +// +// func: ; entry point; return address at sp +// func+0: sp = sp - 16 ; allocate space for stack frame +// func+1: sp[12] = r0 ; save r0 at sp+12 +// ... ; other code, not frame-related +// func+10: sp -= 4; *sp = x ; push some x on the stack +// ... ; other code, not frame-related +// func+20: r0 = sp[16] ; restore saved r0 +// func+21: sp += 20 ; pop whole stack frame +// func+22: pc = *sp; sp += 4 ; pop return address and jump to it +// +// DWARF CFI is (a very compressed representation of) a table with a +// row for each machine instruction address and a column for each +// register showing how to restore it, if possible. +// +// A special column named "CFA", for "Canonical Frame Address", tells how +// to compute the base address of the frame; registers' entries may +// refer to the CFA in describing where the registers are saved. +// +// Another special column, named "RA", represents the return address. +// +// For example, here is a complete (uncompressed) table describing the +// function above: +// +// insn cfa r0 r1 ... ra +// ======================================= +// func+0: sp cfa[0] +// func+1: sp+16 cfa[0] +// func+2: sp+16 cfa[-4] cfa[0] +// func+11: sp+20 cfa[-4] cfa[0] +// func+21: sp+20 cfa[0] +// func+22: sp cfa[0] +// +// Some things to note here: +// +// - Each row describes the state of affairs *before* executing the +// instruction at the given address. Thus, the row for func+0 +// describes the state before we allocate the stack frame. In the +// next row, the formula for computing the CFA has changed, +// reflecting that allocation. +// +// - The other entries are written in terms of the CFA; this allows +// them to remain unchanged as the stack pointer gets bumped around. +// For example, the rule for recovering the return address (the "ra" +// column) remains unchanged throughout the function, even as the +// stack pointer takes on three different offsets from the return +// address. +// +// - Although we haven't shown it, most calling conventions designate +// "callee-saves" and "caller-saves" registers. The callee must +// preserve the values of callee-saves registers; if it uses them, +// it must save their original values somewhere, and restore them +// before it returns. In contrast, the callee is free to trash +// caller-saves registers; if the callee uses these, it will +// probably not bother to save them anywhere, and the CFI will +// probably mark their values as "unrecoverable". +// +// (However, since the caller cannot assume the callee was going to +// save them, caller-saves registers are probably dead in the caller +// anyway, so compilers usually don't generate CFA for caller-saves +// registers.) +// +// - Exactly where the CFA points is a matter of convention that +// depends on the architecture and ABI in use. In the example, the +// CFA is the value the stack pointer had upon entry to the +// function, pointing at the saved return address. But on the x86, +// the call frame information generated by GCC follows the +// convention that the CFA is the address *after* the saved return +// address. +// +// But by definition, the CFA remains constant throughout the +// lifetime of the frame. This makes it a useful value for other +// columns to refer to. It is also gives debuggers a useful handle +// for identifying a frame. +// +// If you look at the table above, you'll notice that a given entry is +// often the same as the one immediately above it: most instructions +// change only one or two aspects of the stack frame, if they affect +// it at all. The DWARF format takes advantage of this fact, and +// reduces the size of the data by mentioning only the addresses and +// columns at which changes take place. So for the above, DWARF CFI +// data would only actually mention the following: +// +// insn cfa r0 r1 ... ra +// ======================================= +// func+0: sp cfa[0] +// func+1: sp+16 +// func+2: cfa[-4] +// func+11: sp+20 +// func+21: r0 +// func+22: sp +// +// In fact, this is the way the parser reports CFI to the consumer: as +// a series of statements of the form, "At address X, column Y changed +// to Z," and related conventions for describing the initial state. +// +// Naturally, it would be impractical to have to scan the entire +// program's CFI, noting changes as we go, just to recover the +// unwinding rules in effect at one particular instruction. To avoid +// this, CFI data is grouped into "entries", each of which covers a +// specified range of addresses and begins with a complete statement +// of the rules for all recoverable registers at that starting +// address. Each entry typically covers a single function. +// +// Thus, to compute the contents of a given row of the table --- that +// is, rules for recovering the CFA, RA, and registers at a given +// instruction --- the consumer should find the entry that covers that +// instruction's address, start with the initial state supplied at the +// beginning of the entry, and work forward until it has processed all +// the changes up to and including those for the present instruction. +// +// There are seven kinds of rules that can appear in an entry of the +// table: +// +// - "undefined": The given register is not preserved by the callee; +// its value cannot be recovered. +// +// - "same value": This register has the same value it did in the callee. +// +// - offset(N): The register is saved at offset N from the CFA. +// +// - val_offset(N): The value the register had in the caller is the +// CFA plus offset N. (This is usually only useful for describing +// the stack pointer.) +// +// - register(R): The register's value was saved in another register R. +// +// - expression(E): Evaluating the DWARF expression E using the +// current frame's registers' values yields the address at which the +// register was saved. +// +// - val_expression(E): Evaluating the DWARF expression E using the +// current frame's registers' values yields the value the register +// had in the caller. + +class CallFrameInfo { + public: + // The different kinds of entries one finds in CFI. Used internally, + // and for error reporting. + enum EntryKind { kUnknown, kCIE, kFDE, kTerminator }; + + // The handler class to which the parser hands the parsed call frame + // information. Defined below. + class Handler; + + // A reporter class, which CallFrameInfo uses to report errors + // encountered while parsing call frame information. Defined below. + class Reporter; + + // Create a DWARF CFI parser. BUFFER points to the contents of the + // .debug_frame section to parse; BUFFER_LENGTH is its length in bytes. + // REPORTER is an error reporter the parser should use to report + // problems. READER is a ByteReader instance that has the endianness and + // address size set properly. Report the data we find to HANDLER. + // + // This class can also parse Linux C++ exception handling data, as found + // in '.eh_frame' sections. This data is a variant of DWARF CFI that is + // placed in loadable segments so that it is present in the program's + // address space, and is interpreted by the C++ runtime to search the + // call stack for a handler interested in the exception being thrown, + // actually pop the frames, and find cleanup code to run. + // + // There are two differences between the call frame information described + // in the DWARF standard and the exception handling data Linux places in + // the .eh_frame section: + // + // - Exception handling data uses uses a different format for call frame + // information entry headers. The distinguished CIE id, the way FDEs + // refer to their CIEs, and the way the end of the series of entries is + // determined are all slightly different. + // + // If the constructor's EH_FRAME argument is true, then the + // CallFrameInfo parses the entry headers as Linux C++ exception + // handling data. If EH_FRAME is false or omitted, the CallFrameInfo + // parses standard DWARF call frame information. + // + // - Linux C++ exception handling data uses CIE augmentation strings + // beginning with 'z' to specify the presence of additional data after + // the CIE and FDE headers and special encodings used for addresses in + // frame description entries. + // + // CallFrameInfo can handle 'z' augmentations in either DWARF CFI or + // exception handling data if you have supplied READER with the base + // addresses needed to interpret the pointer encodings that 'z' + // augmentations can specify. See the ByteReader interface for details + // about the base addresses. See the CallFrameInfo::Handler interface + // for details about the additional information one might find in + // 'z'-augmented data. + // + // Thus: + // + // - If you are parsing standard DWARF CFI, as found in a .debug_frame + // section, you should pass false for the EH_FRAME argument, or omit + // it, and you need not worry about providing READER with the + // additional base addresses. + // + // - If you want to parse Linux C++ exception handling data from a + // .eh_frame section, you should pass EH_FRAME as true, and call + // READER's Set*Base member functions before calling our Start method. + // + // - If you want to parse DWARF CFI that uses the 'z' augmentations + // (although I don't think any toolchain ever emits such data), you + // could pass false for EH_FRAME, but call READER's Set*Base members. + // + // The extensions the Linux C++ ABI makes to DWARF for exception + // handling are described here, rather poorly: + // http://refspecs.linux-foundation.org/LSB_4.0.0/LSB-Core-generic/LSB-Core-generic/dwarfext.html + // http://refspecs.linux-foundation.org/LSB_4.0.0/LSB-Core-generic/LSB-Core-generic/ehframechpt.html + // + // The mechanics of C++ exception handling, personality routines, + // and language-specific data areas are described here, rather nicely: + // http://www.codesourcery.com/public/cxx-abi/abi-eh.html + + CallFrameInfo(const char *buffer, size_t buffer_length, + ByteReader *reader, Handler *handler, Reporter *reporter, + bool eh_frame = false) + : buffer_(buffer), buffer_length_(buffer_length), + reader_(reader), handler_(handler), reporter_(reporter), + eh_frame_(eh_frame) { } + + ~CallFrameInfo() { } + + // Parse the entries in BUFFER, reporting what we find to HANDLER. + // Return true if we reach the end of the section successfully, or + // false if we encounter an error. + bool Start(); + + // Return the textual name of KIND. For error reporting. + static const char *KindName(EntryKind kind); + + private: + + struct CIE; + + // A CFI entry, either an FDE or a CIE. + struct Entry { + // The starting offset of the entry in the section, for error + // reporting. + size_t offset; + + // The start of this entry in the buffer. + const char *start; + + // Which kind of entry this is. + // + // We want to be able to use this for error reporting even while we're + // in the midst of parsing. Error reporting code may assume that kind, + // offset, and start fields are valid, although kind may be kUnknown. + EntryKind kind; + + // The end of this entry's common prologue (initial length and id), and + // the start of this entry's kind-specific fields. + const char *fields; + + // The start of this entry's instructions. + const char *instructions; + + // The address past the entry's last byte in the buffer. (Note that + // since offset points to the entry's initial length field, and the + // length field is the number of bytes after that field, this is not + // simply buffer_ + offset + length.) + const char *end; + + // For both DWARF CFI and .eh_frame sections, this is the CIE id in a + // CIE, and the offset of the associated CIE in an FDE. + uint64 id; + + // The CIE that applies to this entry, if we've parsed it. If this is a + // CIE, then this field points to this structure. + CIE *cie; + }; + + // A common information entry (CIE). + struct CIE: public Entry { + uint8 version; // CFI data version number + std::string augmentation; // vendor format extension markers + uint64 code_alignment_factor; // scale for code address adjustments + int data_alignment_factor; // scale for stack pointer adjustments + unsigned return_address_register; // which register holds the return addr + + // True if this CIE includes Linux C++ ABI 'z' augmentation data. + bool has_z_augmentation; + + // Parsed 'z' augmentation data. These are meaningful only if + // has_z_augmentation is true. + bool has_z_lsda; // The 'z' augmentation included 'L'. + bool has_z_personality; // The 'z' augmentation included 'P'. + bool has_z_signal_frame; // The 'z' augmentation included 'S'. + + // If has_z_lsda is true, this is the encoding to be used for language- + // specific data area pointers in FDEs. + DwarfPointerEncoding lsda_encoding; + + // If has_z_personality is true, this is the encoding used for the + // personality routine pointer in the augmentation data. + DwarfPointerEncoding personality_encoding; + + // If has_z_personality is true, this is the address of the personality + // routine --- or, if personality_encoding & DW_EH_PE_indirect, the + // address where the personality routine's address is stored. + uint64 personality_address; + + // This is the encoding used for addresses in the FDE header and + // in DW_CFA_set_loc instructions. This is always valid, whether + // or not we saw a 'z' augmentation string; its default value is + // DW_EH_PE_absptr, which is what normal DWARF CFI uses. + DwarfPointerEncoding pointer_encoding; + }; + + // A frame description entry (FDE). + struct FDE: public Entry { + uint64 address; // start address of described code + uint64 size; // size of described code, in bytes + + // If cie->has_z_lsda is true, then this is the language-specific data + // area's address --- or its address's address, if cie->lsda_encoding + // has the DW_EH_PE_indirect bit set. + uint64 lsda_address; + }; + + // Internal use. + class Rule; + class UndefinedRule; + class SameValueRule; + class OffsetRule; + class ValOffsetRule; + class RegisterRule; + class ExpressionRule; + class ValExpressionRule; + class RuleMap; + class State; + + // Parse the initial length and id of a CFI entry, either a CIE, an FDE, + // or a .eh_frame end-of-data mark. CURSOR points to the beginning of the + // data to parse. On success, populate ENTRY as appropriate, and return + // true. On failure, report the problem, and return false. Even if we + // return false, set ENTRY->end to the first byte after the entry if we + // were able to figure that out, or NULL if we weren't. + bool ReadEntryPrologue(const char *cursor, Entry *entry); + + // Parse the fields of a CIE after the entry prologue, including any 'z' + // augmentation data. Assume that the 'Entry' fields of CIE are + // populated; use CIE->fields and CIE->end as the start and limit for + // parsing. On success, populate the rest of *CIE, and return true; on + // failure, report the problem and return false. + bool ReadCIEFields(CIE *cie); + + // Parse the fields of an FDE after the entry prologue, including any 'z' + // augmentation data. Assume that the 'Entry' fields of *FDE are + // initialized; use FDE->fields and FDE->end as the start and limit for + // parsing. Assume that FDE->cie is fully initialized. On success, + // populate the rest of *FDE, and return true; on failure, report the + // problem and return false. + bool ReadFDEFields(FDE *fde); + + // Report that ENTRY is incomplete, and return false. This is just a + // trivial wrapper for invoking reporter_->Incomplete; it provides a + // little brevity. + bool ReportIncomplete(Entry *entry); + + // Return true if ENCODING has the DW_EH_PE_indirect bit set. + static bool IsIndirectEncoding(DwarfPointerEncoding encoding) { + return encoding & DW_EH_PE_indirect; + } + + // The contents of the DWARF .debug_info section we're parsing. + const char *buffer_; + size_t buffer_length_; + + // For reading multi-byte values with the appropriate endianness. + ByteReader *reader_; + + // The handler to which we should report the data we find. + Handler *handler_; + + // For reporting problems in the info we're parsing. + Reporter *reporter_; + + // True if we are processing .eh_frame-format data. + bool eh_frame_; +}; + + +// The handler class for CallFrameInfo. The a CFI parser calls the +// member functions of a handler object to report the data it finds. +class CallFrameInfo::Handler { + public: + // The pseudo-register number for the canonical frame address. + enum { kCFARegister = DW_REG_CFA }; + + Handler() { } + virtual ~Handler() { } + + // The parser has found CFI for the machine code at ADDRESS, + // extending for LENGTH bytes. OFFSET is the offset of the frame + // description entry in the section, for use in error messages. + // VERSION is the version number of the CFI format. AUGMENTATION is + // a string describing any producer-specific extensions present in + // the data. RETURN_ADDRESS is the number of the register that holds + // the address to which the function should return. + // + // Entry should return true to process this CFI, or false to skip to + // the next entry. + // + // The parser invokes Entry for each Frame Description Entry (FDE) + // it finds. The parser doesn't report Common Information Entries + // to the handler explicitly; instead, if the handler elects to + // process a given FDE, the parser reiterates the appropriate CIE's + // contents at the beginning of the FDE's rules. + virtual bool Entry(size_t offset, uint64 address, uint64 length, + uint8 version, const std::string &augmentation, + unsigned return_address) = 0; + + // When the Entry function returns true, the parser calls these + // handler functions repeatedly to describe the rules for recovering + // registers at each instruction in the given range of machine code. + // Immediately after a call to Entry, the handler should assume that + // the rule for each callee-saves register is "unchanged" --- that + // is, that the register still has the value it had in the caller. + // + // If a *Rule function returns true, we continue processing this entry's + // instructions. If a *Rule function returns false, we stop evaluating + // instructions, and skip to the next entry. Either way, we call End + // before going on to the next entry. + // + // In all of these functions, if the REG parameter is kCFARegister, then + // the rule describes how to find the canonical frame address. + // kCFARegister may be passed as a BASE_REGISTER argument, meaning that + // the canonical frame address should be used as the base address for the + // computation. All other REG values will be positive. + + // At ADDRESS, register REG's value is not recoverable. + virtual bool UndefinedRule(uint64 address, int reg) = 0; + + // At ADDRESS, register REG's value is the same as that it had in + // the caller. + virtual bool SameValueRule(uint64 address, int reg) = 0; + + // At ADDRESS, register REG has been saved at offset OFFSET from + // BASE_REGISTER. + virtual bool OffsetRule(uint64 address, int reg, + int base_register, long offset) = 0; + + // At ADDRESS, the caller's value of register REG is the current + // value of BASE_REGISTER plus OFFSET. (This rule doesn't provide an + // address at which the register's value is saved.) + virtual bool ValOffsetRule(uint64 address, int reg, + int base_register, long offset) = 0; + + // At ADDRESS, register REG has been saved in BASE_REGISTER. This differs + // from ValOffsetRule(ADDRESS, REG, BASE_REGISTER, 0), in that + // BASE_REGISTER is the "home" for REG's saved value: if you want to + // assign to a variable whose home is REG in the calling frame, you + // should put the value in BASE_REGISTER. + virtual bool RegisterRule(uint64 address, int reg, int base_register) = 0; + + // At ADDRESS, the DWARF expression EXPRESSION yields the address at + // which REG was saved. + virtual bool ExpressionRule(uint64 address, int reg, + const std::string &expression) = 0; + + // At ADDRESS, the DWARF expression EXPRESSION yields the caller's + // value for REG. (This rule doesn't provide an address at which the + // register's value is saved.) + virtual bool ValExpressionRule(uint64 address, int reg, + const std::string &expression) = 0; + + // Indicate that the rules for the address range reported by the + // last call to Entry are complete. End should return true if + // everything is okay, or false if an error has occurred and parsing + // should stop. + virtual bool End() = 0; + + // Handler functions for Linux C++ exception handling data. These are + // only called if the data includes 'z' augmentation strings. + + // The Linux C++ ABI uses an extension of the DWARF CFI format to + // walk the stack to propagate exceptions from the throw to the + // appropriate catch, and do the appropriate cleanups along the way. + // CFI entries used for exception handling have two additional data + // associated with them: + // + // - The "language-specific data area" describes which exception + // types the function has 'catch' clauses for, and indicates how + // to go about re-entering the function at the appropriate catch + // clause. If the exception is not caught, it describes the + // destructors that must run before the frame is popped. + // + // - The "personality routine" is responsible for interpreting the + // language-specific data area's contents, and deciding whether + // the exception should continue to propagate down the stack, + // perhaps after doing some cleanup for this frame, or whether the + // exception will be caught here. + // + // In principle, the language-specific data area is opaque to + // everybody but the personality routine. In practice, these values + // may be useful or interesting to readers with extra context, and + // we have to at least skip them anyway, so we might as well report + // them to the handler. + + // This entry's exception handling personality routine's address is + // ADDRESS. If INDIRECT is true, then ADDRESS is the address at + // which the routine's address is stored. The default definition for + // this handler function simply returns true, allowing parsing of + // the entry to continue. + virtual bool PersonalityRoutine(uint64 address, bool indirect) { + return true; + } + + // This entry's language-specific data area (LSDA) is located at + // ADDRESS. If INDIRECT is true, then ADDRESS is the address at + // which the area's address is stored. The default definition for + // this handler function simply returns true, allowing parsing of + // the entry to continue. + virtual bool LanguageSpecificDataArea(uint64 address, bool indirect) { + return true; + } + + // This entry describes a signal trampoline --- this frame is the + // caller of a signal handler. The default definition for this + // handler function simply returns true, allowing parsing of the + // entry to continue. + // + // The best description of the rationale for and meaning of signal + // trampoline CFI entries seems to be in the GCC bug database: + // http://gcc.gnu.org/bugzilla/show_bug.cgi?id=26208 + virtual bool SignalHandler() { return true; } +}; + + +// The CallFrameInfo class makes calls on an instance of this class to +// report errors or warn about problems in the data it is parsing. +// These messages are sent to the message sink |aLog| provided to the +// constructor. +class CallFrameInfo::Reporter { + public: + // Create an error reporter which attributes troubles to the section + // named SECTION in FILENAME. + // + // Normally SECTION would be .debug_frame, but the Mac puts CFI data + // in a Mach-O section named __debug_frame. If we support + // Linux-style exception handling data, we could be reading an + // .eh_frame section. + Reporter(void (*aLog)(const char*), + const std::string &filename, + const std::string §ion = ".debug_frame") + : log_(aLog), filename_(filename), section_(section) { } + virtual ~Reporter() { } + + // The CFI entry at OFFSET ends too early to be well-formed. KIND + // indicates what kind of entry it is; KIND can be kUnknown if we + // haven't parsed enough of the entry to tell yet. + virtual void Incomplete(uint64 offset, CallFrameInfo::EntryKind kind); + + // The .eh_frame data has a four-byte zero at OFFSET where the next + // entry's length would be; this is a terminator. However, the buffer + // length as given to the CallFrameInfo constructor says there should be + // more data. + virtual void EarlyEHTerminator(uint64 offset); + + // The FDE at OFFSET refers to the CIE at CIE_OFFSET, but the + // section is not that large. + virtual void CIEPointerOutOfRange(uint64 offset, uint64 cie_offset); + + // The FDE at OFFSET refers to the CIE at CIE_OFFSET, but the entry + // there is not a CIE. + virtual void BadCIEId(uint64 offset, uint64 cie_offset); + + // The FDE at OFFSET refers to a CIE with version number VERSION, + // which we don't recognize. We cannot parse DWARF CFI if it uses + // a version number we don't recognize. + virtual void UnrecognizedVersion(uint64 offset, int version); + + // The FDE at OFFSET refers to a CIE with augmentation AUGMENTATION, + // which we don't recognize. We cannot parse DWARF CFI if it uses + // augmentations we don't recognize. + virtual void UnrecognizedAugmentation(uint64 offset, + const std::string &augmentation); + + // The pointer encoding ENCODING, specified by the CIE at OFFSET, is not + // a valid encoding. + virtual void InvalidPointerEncoding(uint64 offset, uint8 encoding); + + // The pointer encoding ENCODING, specified by the CIE at OFFSET, depends + // on a base address which has not been supplied. + virtual void UnusablePointerEncoding(uint64 offset, uint8 encoding); + + // The CIE at OFFSET contains a DW_CFA_restore instruction at + // INSN_OFFSET, which may not appear in a CIE. + virtual void RestoreInCIE(uint64 offset, uint64 insn_offset); + + // The entry at OFFSET, of kind KIND, has an unrecognized + // instruction at INSN_OFFSET. + virtual void BadInstruction(uint64 offset, CallFrameInfo::EntryKind kind, + uint64 insn_offset); + + // The instruction at INSN_OFFSET in the entry at OFFSET, of kind + // KIND, establishes a rule that cites the CFA, but we have not + // established a CFA rule yet. + virtual void NoCFARule(uint64 offset, CallFrameInfo::EntryKind kind, + uint64 insn_offset); + + // The instruction at INSN_OFFSET in the entry at OFFSET, of kind + // KIND, is a DW_CFA_restore_state instruction, but the stack of + // saved states is empty. + virtual void EmptyStateStack(uint64 offset, CallFrameInfo::EntryKind kind, + uint64 insn_offset); + + // The DW_CFA_remember_state instruction at INSN_OFFSET in the entry + // at OFFSET, of kind KIND, would restore a state that has no CFA + // rule, whereas the current state does have a CFA rule. This is + // bogus input, which the CallFrameInfo::Handler interface doesn't + // (and shouldn't) have any way to report. + virtual void ClearingCFARule(uint64 offset, CallFrameInfo::EntryKind kind, + uint64 insn_offset); + + private: + // A logging sink function, as supplied by LUL's user. + void (*log_)(const char*); + + protected: + // The name of the file whose CFI we're reading. + std::string filename_; + + // The name of the CFI section in that file. + std::string section_; +}; + + +using lul::CallFrameInfo; +using lul::Summariser; + +// A class that accepts parsed call frame information from the DWARF +// CFI parser and populates a google_breakpad::Module object with the +// contents. +class DwarfCFIToModule: public CallFrameInfo::Handler { + public: + + // DwarfCFIToModule uses an instance of this class to report errors + // detected while converting DWARF CFI to Breakpad STACK CFI records. + class Reporter { + public: + // Create a reporter that writes messages to the message sink + // |aLog|. FILE is the name of the file we're processing, and + // SECTION is the name of the section within that file that we're + // looking at (.debug_frame, .eh_frame, etc.). + Reporter(void (*aLog)(const char*), + const std::string &file, const std::string §ion) + : log_(aLog), file_(file), section_(section) { } + virtual ~Reporter() { } + + // The DWARF CFI entry at OFFSET says that REG is undefined, but the + // Breakpad symbol file format cannot express this. + virtual void UndefinedNotSupported(size_t offset, + const UniqueString* reg); + + // The DWARF CFI entry at OFFSET says that REG uses a DWARF + // expression to find its value, but parseDwarfExpr could not + // convert it to a sequence of PfxInstrs. + virtual void ExpressionCouldNotBeSummarised(size_t offset, + const UniqueString* reg); + + private: + // A logging sink function, as supplied by LUL's user. + void (*log_)(const char*); + protected: + std::string file_, section_; + }; + + // Register name tables. If TABLE is a vector returned by one of these + // functions, then TABLE[R] is the name of the register numbered R in + // DWARF call frame information. + class RegisterNames { + public: + // Intel's "x86" or IA-32. + static unsigned int I386(); + + // AMD x86_64, AMD64, Intel EM64T, or Intel 64 + static unsigned int X86_64(); + + // ARM. + static unsigned int ARM(); + }; + + // Create a handler for the dwarf2reader::CallFrameInfo parser that + // records the stack unwinding information it receives in SUMM. + // + // Use REGISTER_NAMES[I] as the name of register number I; *this + // keeps a reference to the vector, so the vector should remain + // alive for as long as the DwarfCFIToModule does. + // + // Use REPORTER for reporting problems encountered in the conversion + // process. + DwarfCFIToModule(const unsigned int num_dw_regs, + Reporter *reporter, + ByteReader* reader, + /*MOD*/UniqueStringUniverse* usu, + /*OUT*/Summariser* summ) + : summ_(summ), usu_(usu), num_dw_regs_(num_dw_regs), + reporter_(reporter), reader_(reader), return_address_(-1) { + } + virtual ~DwarfCFIToModule() {} + + virtual bool Entry(size_t offset, uint64 address, uint64 length, + uint8 version, const std::string &augmentation, + unsigned return_address); + virtual bool UndefinedRule(uint64 address, int reg); + virtual bool SameValueRule(uint64 address, int reg); + virtual bool OffsetRule(uint64 address, int reg, + int base_register, long offset); + virtual bool ValOffsetRule(uint64 address, int reg, + int base_register, long offset); + virtual bool RegisterRule(uint64 address, int reg, int base_register); + virtual bool ExpressionRule(uint64 address, int reg, + const std::string &expression); + virtual bool ValExpressionRule(uint64 address, int reg, + const std::string &expression); + virtual bool End(); + + private: + // Return the name to use for register I. + const UniqueString* RegisterName(int i); + + // The Summariser to which we should give entries + Summariser* summ_; + + // Universe for creating UniqueStrings in, should that be necessary. + UniqueStringUniverse* usu_; + + // The number of Dwarf-defined register names for this architecture. + const unsigned int num_dw_regs_; + + // The reporter to use to report problems. + Reporter *reporter_; + + // The ByteReader to use for parsing Dwarf expressions. + ByteReader* reader_; + + // The section offset of the current frame description entry, for + // use in error messages. + size_t entry_offset_; + + // The return address column for that entry. + unsigned return_address_; +}; + + +// Convert the Dwarf expression in |expr| into PfxInstrs stored in the +// SecMap referred to by |summ|, and return the index of the starting +// PfxInstr added, which must be >= 0. In case of failure return -1. +int32_t parseDwarfExpr(Summariser* summ, const ByteReader* reader, + string expr, bool debug, + bool pushCfaAtStart, bool derefAtEnd); + +} // namespace lul + +#endif // LulDwarfExt_h diff --git a/tools/profiler/lul/LulDwarfInt.h b/tools/profiler/lul/LulDwarfInt.h new file mode 100644 index 0000000000..05c231f84c --- /dev/null +++ b/tools/profiler/lul/LulDwarfInt.h @@ -0,0 +1,194 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ + +// Copyright (c) 2008, 2010 Google Inc. All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// CFI reader author: Jim Blandy + +// This file is derived from the following file in +// toolkit/crashreporter/google-breakpad: +// src/common/dwarf/dwarf2enums.h + +#ifndef LulDwarfInt_h +#define LulDwarfInt_h + +#include "LulCommonExt.h" +#include "LulDwarfExt.h" + +namespace lul { + +// These enums do not follow the google3 style only because they are +// known universally (specs, other implementations) by the names in +// exactly this capitalization. +// Tag names and codes. + +// Call Frame Info instructions. +enum DwarfCFI + { + DW_CFA_advance_loc = 0x40, + DW_CFA_offset = 0x80, + DW_CFA_restore = 0xc0, + DW_CFA_nop = 0x00, + DW_CFA_set_loc = 0x01, + DW_CFA_advance_loc1 = 0x02, + DW_CFA_advance_loc2 = 0x03, + DW_CFA_advance_loc4 = 0x04, + DW_CFA_offset_extended = 0x05, + DW_CFA_restore_extended = 0x06, + DW_CFA_undefined = 0x07, + DW_CFA_same_value = 0x08, + DW_CFA_register = 0x09, + DW_CFA_remember_state = 0x0a, + DW_CFA_restore_state = 0x0b, + DW_CFA_def_cfa = 0x0c, + DW_CFA_def_cfa_register = 0x0d, + DW_CFA_def_cfa_offset = 0x0e, + DW_CFA_def_cfa_expression = 0x0f, + DW_CFA_expression = 0x10, + DW_CFA_offset_extended_sf = 0x11, + DW_CFA_def_cfa_sf = 0x12, + DW_CFA_def_cfa_offset_sf = 0x13, + DW_CFA_val_offset = 0x14, + DW_CFA_val_offset_sf = 0x15, + DW_CFA_val_expression = 0x16, + + // Opcodes in this range are reserved for user extensions. + DW_CFA_lo_user = 0x1c, + DW_CFA_hi_user = 0x3f, + + // SGI/MIPS specific. + DW_CFA_MIPS_advance_loc8 = 0x1d, + + // GNU extensions. + DW_CFA_GNU_window_save = 0x2d, + DW_CFA_GNU_args_size = 0x2e, + DW_CFA_GNU_negative_offset_extended = 0x2f + }; + +// Exception handling 'z' augmentation letters. +enum DwarfZAugmentationCodes { + // If the CFI augmentation string begins with 'z', then the CIE and FDE + // have an augmentation data area just before the instructions, whose + // contents are determined by the subsequent augmentation letters. + DW_Z_augmentation_start = 'z', + + // If this letter is present in a 'z' augmentation string, the CIE + // augmentation data includes a pointer encoding, and the FDE + // augmentation data includes a language-specific data area pointer, + // represented using that encoding. + DW_Z_has_LSDA = 'L', + + // If this letter is present in a 'z' augmentation string, the CIE + // augmentation data includes a pointer encoding, followed by a pointer + // to a personality routine, represented using that encoding. + DW_Z_has_personality_routine = 'P', + + // If this letter is present in a 'z' augmentation string, the CIE + // augmentation data includes a pointer encoding describing how the FDE's + // initial location, address range, and DW_CFA_set_loc operands are + // encoded. + DW_Z_has_FDE_address_encoding = 'R', + + // If this letter is present in a 'z' augmentation string, then code + // addresses covered by FDEs that cite this CIE are signal delivery + // trampolines. Return addresses of frames in trampolines should not be + // adjusted as described in section 6.4.4 of the DWARF 3 spec. + DW_Z_is_signal_trampoline = 'S' +}; + +// Expression opcodes +enum DwarfExpressionOpcodes { + DW_OP_addr = 0x03, + DW_OP_deref = 0x06, + DW_OP_const1s = 0x09, + DW_OP_const2u = 0x0a, + DW_OP_const2s = 0x0b, + DW_OP_const4u = 0x0c, + DW_OP_const4s = 0x0d, + DW_OP_const8u = 0x0e, + DW_OP_const8s = 0x0f, + DW_OP_constu = 0x10, + DW_OP_consts = 0x11, + DW_OP_dup = 0x12, + DW_OP_drop = 0x13, + DW_OP_over = 0x14, + DW_OP_pick = 0x15, + DW_OP_swap = 0x16, + DW_OP_rot = 0x17, + DW_OP_xderef = 0x18, + DW_OP_abs = 0x19, + DW_OP_and = 0x1a, + DW_OP_div = 0x1b, + DW_OP_minus = 0x1c, + DW_OP_mod = 0x1d, + DW_OP_mul = 0x1e, + DW_OP_neg = 0x1f, + DW_OP_not = 0x20, + DW_OP_or = 0x21, + DW_OP_plus = 0x22, + DW_OP_plus_uconst = 0x23, + DW_OP_shl = 0x24, + DW_OP_shr = 0x25, + DW_OP_shra = 0x26, + DW_OP_xor = 0x27, + DW_OP_skip = 0x2f, + DW_OP_bra = 0x28, + DW_OP_eq = 0x29, + DW_OP_ge = 0x2a, + DW_OP_gt = 0x2b, + DW_OP_le = 0x2c, + DW_OP_lt = 0x2d, + DW_OP_ne = 0x2e, + DW_OP_lit0 = 0x30, + DW_OP_lit31 = 0x4f, + DW_OP_reg0 = 0x50, + DW_OP_reg31 = 0x6f, + DW_OP_breg0 = 0x70, + DW_OP_breg31 = 0x8f, + DW_OP_regx = 0x90, + DW_OP_fbreg = 0x91, + DW_OP_bregx = 0x92, + DW_OP_piece = 0x93, + DW_OP_deref_size = 0x94, + DW_OP_xderef_size = 0x95, + DW_OP_nop = 0x96, + DW_OP_push_object_address = 0x97, + DW_OP_call2 = 0x98, + DW_OP_call4 = 0x99, + DW_OP_call_ref = 0x9a, + DW_OP_form_tls_address = 0x9b, + DW_OP_call_frame_cfa = 0x9c, + DW_OP_bit_piece = 0x9d, + DW_OP_lo_user = 0xe0, + DW_OP_hi_user = 0xff +}; + +} // namespace lul + +#endif // LulDwarfInt_h diff --git a/tools/profiler/lul/LulDwarfSummariser.cpp b/tools/profiler/lul/LulDwarfSummariser.cpp new file mode 100644 index 0000000000..71b96bb4de --- /dev/null +++ b/tools/profiler/lul/LulDwarfSummariser.cpp @@ -0,0 +1,358 @@ +/* -*- 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 "LulDwarfSummariser.h" + +#include "mozilla/Assertions.h" + +// Set this to 1 for verbose logging +#define DEBUG_SUMMARISER 0 + +namespace lul { + +// Do |s64|'s lowest 32 bits sign extend back to |s64| itself? +static inline bool fitsIn32Bits(int64 s64) { + return s64 == ((s64 & 0xffffffff) ^ 0x80000000) - 0x80000000; +} + +// Check a LExpr prefix expression, starting at pfxInstrs[start] up to +// the next PX_End instruction, to ensure that: +// * It only mentions registers that are tracked on this target +// * The start point is sane +// If the expression is ok, return NULL. Else return a pointer +// a const char* holding a bit of text describing the problem. +static const char* +checkPfxExpr(const vector* pfxInstrs, int64_t start) +{ + size_t nInstrs = pfxInstrs->size(); + if (start < 0 || start >= (ssize_t)nInstrs) { + return "bogus start point"; + } + size_t i; + for (i = start; i < nInstrs; i++) { + PfxInstr pxi = (*pfxInstrs)[i]; + if (pxi.mOpcode == PX_End) + break; + if (pxi.mOpcode == PX_DwReg && + !registerIsTracked((DW_REG_NUMBER)pxi.mOperand)) { + return "uses untracked reg"; + } + } + return nullptr; // success +} + + +Summariser::Summariser(SecMap* aSecMap, uintptr_t aTextBias, + void(*aLog)(const char*)) + : mSecMap(aSecMap) + , mTextBias(aTextBias) + , mLog(aLog) +{ + mCurrAddr = 0; + mMax1Addr = 0; // Gives an empty range. + + // Initialise the running RuleSet to "haven't got a clue" status. + new (&mCurrRules) RuleSet(); +} + +void +Summariser::Entry(uintptr_t aAddress, uintptr_t aLength) +{ + aAddress += mTextBias; + if (DEBUG_SUMMARISER) { + char buf[100]; + snprintf_literal(buf, "LUL Entry(%llx, %llu)\n", + (unsigned long long int)aAddress, + (unsigned long long int)aLength); + mLog(buf); + } + // This throws away any previous summary, that is, assumes + // that the previous summary, if any, has been properly finished + // by a call to End(). + mCurrAddr = aAddress; + mMax1Addr = aAddress + aLength; + new (&mCurrRules) RuleSet(); +} + +void +Summariser::Rule(uintptr_t aAddress, int aNewReg, + LExprHow how, int16_t oldReg, int64_t offset) +{ + aAddress += mTextBias; + if (DEBUG_SUMMARISER) { + char buf[100]; + if (how == NODEREF || how == DEREF) { + bool deref = how == DEREF; + snprintf_literal(buf, + "LUL 0x%llx old-r%d = %sr%d + %lld%s\n", + (unsigned long long int)aAddress, aNewReg, + deref ? "*(" : "", (int)oldReg, (long long int)offset, + deref ? ")" : ""); + } else if (how == PFXEXPR) { + snprintf_literal(buf, + "LUL 0x%llx old-r%d = pfx-expr-at %lld\n", + (unsigned long long int)aAddress, aNewReg, + (long long int)offset); + } else { + snprintf_literal(buf, + "LUL 0x%llx old-r%d = (invalid LExpr!)\n", + (unsigned long long int)aAddress, aNewReg); + } + mLog(buf); + } + + if (mCurrAddr < aAddress) { + // Flush the existing summary first. + mCurrRules.mAddr = mCurrAddr; + mCurrRules.mLen = aAddress - mCurrAddr; + mSecMap->AddRuleSet(&mCurrRules); + if (DEBUG_SUMMARISER) { + mLog("LUL "); mCurrRules.Print(mLog); + mLog("\n"); + } + mCurrAddr = aAddress; + } + + // If for some reason summarisation fails, either or both of these + // become non-null and point at constant text describing the + // problem. Using two rather than just one avoids complications of + // having to concatenate two strings to produce a complete error message. + const char* reason1 = nullptr; + const char* reason2 = nullptr; + + // |offset| needs to be a 32 bit value that sign extends to 64 bits + // on a 64 bit target. We will need to incorporate |offset| into + // any LExpr made here. So we may as well check it right now. + if (!fitsIn32Bits(offset)) { + reason1 = "offset not in signed 32-bit range"; + goto cant_summarise; + } + + // FIXME: factor out common parts of the arch-dependent summarisers. + +#if defined(LUL_ARCH_arm) + + // ----------------- arm ----------------- // + + // Now, can we add the rule to our summary? This depends on whether + // the registers and the overall expression are representable. This + // is the heart of the summarisation process. + switch (aNewReg) { + + case DW_REG_CFA: + // This is a rule that defines the CFA. The only forms we + // choose to represent are: r7/11/12/13 + offset. The offset + // must fit into 32 bits since 'uintptr_t' is 32 bit on ARM, + // hence there is no need to check it for overflow. + if (how != NODEREF) { + reason1 = "rule for DW_REG_CFA: invalid |how|"; + goto cant_summarise; + } + switch (oldReg) { + case DW_REG_ARM_R7: case DW_REG_ARM_R11: + case DW_REG_ARM_R12: case DW_REG_ARM_R13: + break; + default: + reason1 = "rule for DW_REG_CFA: invalid |oldReg|"; + goto cant_summarise; + } + mCurrRules.mCfaExpr = LExpr(how, oldReg, offset); + break; + + case DW_REG_ARM_R7: case DW_REG_ARM_R11: case DW_REG_ARM_R12: + case DW_REG_ARM_R13: case DW_REG_ARM_R14: case DW_REG_ARM_R15: { + // This is a new rule for R7, R11, R12, R13 (SP), R14 (LR) or + // R15 (the return address). + switch (how) { + case NODEREF: case DEREF: + // Check the old register is one we're tracking. + if (!registerIsTracked((DW_REG_NUMBER)oldReg) && + oldReg != DW_REG_CFA) { + reason1 = "rule for R7/11/12/13/14/15: uses untracked reg"; + goto cant_summarise; + } + break; + case PFXEXPR: { + // Check that the prefix expression only mentions tracked registers. + const vector* pfxInstrs = mSecMap->GetPfxInstrs(); + reason2 = checkPfxExpr(pfxInstrs, offset); + if (reason2) { + reason1 = "rule for R7/11/12/13/14/15: "; + goto cant_summarise; + } + break; + } + default: + goto cant_summarise; + } + LExpr expr = LExpr(how, oldReg, offset); + switch (aNewReg) { + case DW_REG_ARM_R7: mCurrRules.mR7expr = expr; break; + case DW_REG_ARM_R11: mCurrRules.mR11expr = expr; break; + case DW_REG_ARM_R12: mCurrRules.mR12expr = expr; break; + case DW_REG_ARM_R13: mCurrRules.mR13expr = expr; break; + case DW_REG_ARM_R14: mCurrRules.mR14expr = expr; break; + case DW_REG_ARM_R15: mCurrRules.mR15expr = expr; break; + default: MOZ_ASSERT(0); + } + break; + } + + default: + // Leave |reason1| and |reason2| unset here. This program point + // is reached so often that it causes a flood of "Can't + // summarise" messages. In any case, we don't really care about + // the fact that this summary would produce a new value for a + // register that we're not tracking. We do on the other hand + // care if the summary's expression *uses* a register that we're + // not tracking. But in that case one of the above failures + // should tell us which. + goto cant_summarise; + } + + // Mark callee-saved registers (r4 .. r11) as unchanged, if there is + // no other information about them. FIXME: do this just once, at + // the point where the ruleset is committed. + if (mCurrRules.mR7expr.mHow == UNKNOWN) { + mCurrRules.mR7expr = LExpr(NODEREF, DW_REG_ARM_R7, 0); + } + if (mCurrRules.mR11expr.mHow == UNKNOWN) { + mCurrRules.mR11expr = LExpr(NODEREF, DW_REG_ARM_R11, 0); + } + if (mCurrRules.mR12expr.mHow == UNKNOWN) { + mCurrRules.mR12expr = LExpr(NODEREF, DW_REG_ARM_R12, 0); + } + + // The old r13 (SP) value before the call is always the same as the + // CFA. + mCurrRules.mR13expr = LExpr(NODEREF, DW_REG_CFA, 0); + + // If there's no information about R15 (the return address), say + // it's a copy of R14 (the link register). + if (mCurrRules.mR15expr.mHow == UNKNOWN) { + mCurrRules.mR15expr = LExpr(NODEREF, DW_REG_ARM_R14, 0); + } + +#elif defined(LUL_ARCH_x64) || defined(LUL_ARCH_x86) + + // ---------------- x64/x86 ---------------- // + + // Now, can we add the rule to our summary? This depends on whether + // the registers and the overall expression are representable. This + // is the heart of the summarisation process. + switch (aNewReg) { + + case DW_REG_CFA: + // This is a rule that defines the CFA. The only forms we can + // represent are: = SP+offset or = FP+offset. + if (how != NODEREF) { + reason1 = "rule for DW_REG_CFA: invalid |how|"; + goto cant_summarise; + } + if (oldReg != DW_REG_INTEL_XSP && oldReg != DW_REG_INTEL_XBP) { + reason1 = "rule for DW_REG_CFA: invalid |oldReg|"; + goto cant_summarise; + } + mCurrRules.mCfaExpr = LExpr(how, oldReg, offset); + break; + + case DW_REG_INTEL_XSP: case DW_REG_INTEL_XBP: case DW_REG_INTEL_XIP: { + // This is a new rule for XSP, XBP or XIP (the return address). + switch (how) { + case NODEREF: case DEREF: + // Check the old register is one we're tracking. + if (!registerIsTracked((DW_REG_NUMBER)oldReg) && + oldReg != DW_REG_CFA) { + reason1 = "rule for XSP/XBP/XIP: uses untracked reg"; + goto cant_summarise; + } + break; + case PFXEXPR: { + // Check that the prefix expression only mentions tracked registers. + const vector* pfxInstrs = mSecMap->GetPfxInstrs(); + reason2 = checkPfxExpr(pfxInstrs, offset); + if (reason2) { + reason1 = "rule for XSP/XBP/XIP: "; + goto cant_summarise; + } + break; + } + default: + goto cant_summarise; + } + LExpr expr = LExpr(how, oldReg, offset); + switch (aNewReg) { + case DW_REG_INTEL_XBP: mCurrRules.mXbpExpr = expr; break; + case DW_REG_INTEL_XSP: mCurrRules.mXspExpr = expr; break; + case DW_REG_INTEL_XIP: mCurrRules.mXipExpr = expr; break; + default: MOZ_CRASH("impossible value for aNewReg"); + } + break; + } + + default: + // Leave |reason1| and |reason2| unset here, for the reasons + // explained in the analogous point in the ARM case just above. + goto cant_summarise; + + } + + // On Intel, it seems the old SP value before the call is always the + // same as the CFA. Therefore, in the absence of any other way to + // recover the SP, specify that the CFA should be copied. + if (mCurrRules.mXspExpr.mHow == UNKNOWN) { + mCurrRules.mXspExpr = LExpr(NODEREF, DW_REG_CFA, 0); + } + + // Also, gcc says "Undef" for BP when it is unchanged. + if (mCurrRules.mXbpExpr.mHow == UNKNOWN) { + mCurrRules.mXbpExpr = LExpr(NODEREF, DW_REG_INTEL_XBP, 0); + } + +#else + +# error "Unsupported arch" +#endif + + return; + + cant_summarise: + if (reason1 || reason2) { + char buf[200]; + snprintf_literal(buf, "LUL can't summarise: " + "SVMA=0x%llx: %s%s, expr=LExpr(%s,%u,%lld)\n", + (unsigned long long int)(aAddress - mTextBias), + reason1 ? reason1 : "", reason2 ? reason2 : "", + NameOf_LExprHow(how), + (unsigned int)oldReg, (long long int)offset); + mLog(buf); + } +} + +uint32_t +Summariser::AddPfxInstr(PfxInstr pfxi) +{ + return mSecMap->AddPfxInstr(pfxi); +} + +void +Summariser::End() +{ + if (DEBUG_SUMMARISER) { + mLog("LUL End\n"); + } + if (mCurrAddr < mMax1Addr) { + mCurrRules.mAddr = mCurrAddr; + mCurrRules.mLen = mMax1Addr - mCurrAddr; + mSecMap->AddRuleSet(&mCurrRules); + if (DEBUG_SUMMARISER) { + mLog("LUL "); mCurrRules.Print(mLog); + mLog("\n"); + } + } +} + +} // namespace lul diff --git a/tools/profiler/lul/LulDwarfSummariser.h b/tools/profiler/lul/LulDwarfSummariser.h new file mode 100644 index 0000000000..b41db1ee3d --- /dev/null +++ b/tools/profiler/lul/LulDwarfSummariser.h @@ -0,0 +1,65 @@ +/* -*- 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 LulDwarfSummariser_h +#define LulDwarfSummariser_h + +#include "LulMainInt.h" + +namespace lul { + +class Summariser +{ +public: + Summariser(SecMap* aSecMap, uintptr_t aTextBias, void(*aLog)(const char*)); + + virtual void Entry(uintptr_t aAddress, uintptr_t aLength); + virtual void End(); + + // Tell the summariser that the value for |aNewReg| at |aAddress| is + // recovered using the LExpr that can be constructed using the + // components |how|, |oldReg| and |offset|. The summariser will + // inspect the components and may reject them for various reasons, + // but the hope is that it will find them acceptable and record this + // rule permanently. + virtual void Rule(uintptr_t aAddress, int aNewReg, + LExprHow how, int16_t oldReg, int64_t offset); + + virtual uint32_t AddPfxInstr(PfxInstr pfxi); + + // Send output to the logging sink, for debugging. + virtual void Log(const char* str) { mLog(str); } + +private: + // The SecMap in which we park the finished summaries (RuleSets) and + // also any PfxInstrs derived from Dwarf expressions. + SecMap* mSecMap; + + // Running state for the current summary (RuleSet) under construction. + RuleSet mCurrRules; + + // The start of the address range to which the RuleSet under + // construction applies. + uintptr_t mCurrAddr; + + // The highest address, plus one, for which the RuleSet under + // construction could possibly apply. If there are no further + // incoming events then mCurrRules will eventually be emitted + // as-is, for the range mCurrAddr.. mMax1Addr - 1, if that is + // nonempty. + uintptr_t mMax1Addr; + + // The bias value (to add to the SVMAs, to get AVMAs) to be used + // when adding entries into mSecMap. + uintptr_t mTextBias; + + // A logging sink, for debugging. + void (*mLog)(const char* aFmt); +}; + +} // namespace lul + +#endif // LulDwarfSummariser_h diff --git a/tools/profiler/lul/LulElf.cpp b/tools/profiler/lul/LulElf.cpp new file mode 100644 index 0000000000..a9221370dd --- /dev/null +++ b/tools/profiler/lul/LulElf.cpp @@ -0,0 +1,914 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ + +// Copyright (c) 2006, 2011, 2012 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Restructured in 2009 by: Jim Blandy + +// (derived from) +// dump_symbols.cc: implement google_breakpad::WriteSymbolFile: +// Find all the debugging info in a file and dump it as a Breakpad symbol file. +// +// dump_symbols.h: Read debugging information from an ELF file, and write +// it out as a Breakpad symbol file. + +// This file is derived from the following files in +// toolkit/crashreporter/google-breakpad: +// src/common/linux/dump_symbols.cc +// src/common/linux/elfutils.cc +// src/common/linux/file_id.cc + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "mozilla/Assertions.h" + +#include "LulPlatformMacros.h" +#include "LulCommonExt.h" +#include "LulDwarfExt.h" +#include "LulElfInt.h" +#include "LulMainInt.h" + + +#if defined(LUL_PLAT_arm_android) && !defined(SHT_ARM_EXIDX) +// bionic and older glibsc don't define it +# define SHT_ARM_EXIDX (SHT_LOPROC + 1) +#endif + + +// This namespace contains helper functions. +namespace { + +using lul::DwarfCFIToModule; +using lul::FindElfSectionByName; +using lul::GetOffset; +using lul::IsValidElf; +using lul::Module; +using lul::UniqueStringUniverse; +using lul::scoped_ptr; +using lul::Summariser; +using std::string; +using std::vector; +using std::set; + +// +// FDWrapper +// +// Wrapper class to make sure opened file is closed. +// +class FDWrapper { + public: + explicit FDWrapper(int fd) : + fd_(fd) {} + ~FDWrapper() { + if (fd_ != -1) + close(fd_); + } + int get() { + return fd_; + } + int release() { + int fd = fd_; + fd_ = -1; + return fd; + } + private: + int fd_; +}; + +// +// MmapWrapper +// +// Wrapper class to make sure mapped regions are unmapped. +// +class MmapWrapper { + public: + MmapWrapper() : is_set_(false), base_(NULL), size_(0){} + ~MmapWrapper() { + if (is_set_ && base_ != NULL) { + MOZ_ASSERT(size_ > 0); + munmap(base_, size_); + } + } + void set(void *mapped_address, size_t mapped_size) { + is_set_ = true; + base_ = mapped_address; + size_ = mapped_size; + } + void release() { + MOZ_ASSERT(is_set_); + is_set_ = false; + base_ = NULL; + size_ = 0; + } + + private: + bool is_set_; + void *base_; + size_t size_; +}; + + +// Set NUM_DW_REGNAMES to be the number of Dwarf register names +// appropriate to the machine architecture given in HEADER. Return +// true on success, or false if HEADER's machine architecture is not +// supported. +template +bool DwarfCFIRegisterNames(const typename ElfClass::Ehdr* elf_header, + unsigned int* num_dw_regnames) { + switch (elf_header->e_machine) { + case EM_386: + *num_dw_regnames = DwarfCFIToModule::RegisterNames::I386(); + return true; + case EM_ARM: + *num_dw_regnames = DwarfCFIToModule::RegisterNames::ARM(); + return true; + case EM_X86_64: + *num_dw_regnames = DwarfCFIToModule::RegisterNames::X86_64(); + return true; + default: + MOZ_ASSERT(0); + return false; + } +} + +template +bool LoadDwarfCFI(const string& dwarf_filename, + const typename ElfClass::Ehdr* elf_header, + const char* section_name, + const typename ElfClass::Shdr* section, + const bool eh_frame, + const typename ElfClass::Shdr* got_section, + const typename ElfClass::Shdr* text_section, + const bool big_endian, + SecMap* smap, + uintptr_t text_bias, + UniqueStringUniverse* usu, + void (*log)(const char*)) { + // Find the appropriate set of register names for this file's + // architecture. + unsigned int num_dw_regs = 0; + if (!DwarfCFIRegisterNames(elf_header, &num_dw_regs)) { + fprintf(stderr, "%s: unrecognized ELF machine architecture '%d';" + " cannot convert DWARF call frame information\n", + dwarf_filename.c_str(), elf_header->e_machine); + return false; + } + + const lul::Endianness endianness + = big_endian ? lul::ENDIANNESS_BIG : lul::ENDIANNESS_LITTLE; + + // Find the call frame information and its size. + const char* cfi = + GetOffset(elf_header, section->sh_offset); + size_t cfi_size = section->sh_size; + + // Plug together the parser, handler, and their entourages. + + // Here's a summariser, which will receive the output of the + // parser, create summaries, and add them to |smap|. + Summariser summ(smap, text_bias, log); + + lul::ByteReader reader(endianness); + reader.SetAddressSize(ElfClass::kAddrSize); + + DwarfCFIToModule::Reporter module_reporter(log, dwarf_filename, section_name); + DwarfCFIToModule handler(num_dw_regs, &module_reporter, &reader, usu, &summ); + + // Provide the base addresses for .eh_frame encoded pointers, if + // possible. + reader.SetCFIDataBase(section->sh_addr, cfi); + if (got_section) + reader.SetDataBase(got_section->sh_addr); + if (text_section) + reader.SetTextBase(text_section->sh_addr); + + lul::CallFrameInfo::Reporter dwarf_reporter(log, dwarf_filename, + section_name); + lul::CallFrameInfo parser(cfi, cfi_size, + &reader, &handler, &dwarf_reporter, + eh_frame); + parser.Start(); + + return true; +} + +bool LoadELF(const string& obj_file, MmapWrapper* map_wrapper, + void** elf_header) { + int obj_fd = open(obj_file.c_str(), O_RDONLY); + if (obj_fd < 0) { + fprintf(stderr, "Failed to open ELF file '%s': %s\n", + obj_file.c_str(), strerror(errno)); + return false; + } + FDWrapper obj_fd_wrapper(obj_fd); + struct stat st; + if (fstat(obj_fd, &st) != 0 && st.st_size <= 0) { + fprintf(stderr, "Unable to fstat ELF file '%s': %s\n", + obj_file.c_str(), strerror(errno)); + return false; + } + // Mapping it read-only is good enough. In any case, mapping it + // read-write confuses Valgrind's debuginfo acquire/discard + // heuristics, making it hard to profile the profiler. + void *obj_base = mmap(nullptr, st.st_size, + PROT_READ, MAP_PRIVATE, obj_fd, 0); + if (obj_base == MAP_FAILED) { + fprintf(stderr, "Failed to mmap ELF file '%s': %s\n", + obj_file.c_str(), strerror(errno)); + return false; + } + map_wrapper->set(obj_base, st.st_size); + *elf_header = obj_base; + if (!IsValidElf(*elf_header)) { + fprintf(stderr, "Not a valid ELF file: %s\n", obj_file.c_str()); + return false; + } + return true; +} + +// Get the endianness of ELF_HEADER. If it's invalid, return false. +template +bool ElfEndianness(const typename ElfClass::Ehdr* elf_header, + bool* big_endian) { + if (elf_header->e_ident[EI_DATA] == ELFDATA2LSB) { + *big_endian = false; + return true; + } + if (elf_header->e_ident[EI_DATA] == ELFDATA2MSB) { + *big_endian = true; + return true; + } + + fprintf(stderr, "bad data encoding in ELF header: %d\n", + elf_header->e_ident[EI_DATA]); + return false; +} + +// +// LoadSymbolsInfo +// +// Holds the state between the two calls to LoadSymbols() in case it's necessary +// to follow the .gnu_debuglink section and load debug information from a +// different file. +// +template +class LoadSymbolsInfo { + public: + typedef typename ElfClass::Addr Addr; + + explicit LoadSymbolsInfo(const vector& dbg_dirs) : + debug_dirs_(dbg_dirs), + has_loading_addr_(false) {} + + // Keeps track of which sections have been loaded so sections don't + // accidentally get loaded twice from two different files. + void LoadedSection(const string §ion) { + if (loaded_sections_.count(section) == 0) { + loaded_sections_.insert(section); + } else { + fprintf(stderr, "Section %s has already been loaded.\n", + section.c_str()); + } + } + + string debuglink_file() const { + return debuglink_file_; + } + + private: + const vector& debug_dirs_; // Directories in which to + // search for the debug ELF file. + + string debuglink_file_; // Full path to the debug ELF file. + + bool has_loading_addr_; // Indicate if LOADING_ADDR_ is valid. + + set loaded_sections_; // Tracks the Loaded ELF sections + // between calls to LoadSymbols(). +}; + +// Find the preferred loading address of the binary. +template +typename ElfClass::Addr GetLoadingAddress( + const typename ElfClass::Phdr* program_headers, + int nheader) { + typedef typename ElfClass::Phdr Phdr; + + // For non-PIC executables (e_type == ET_EXEC), the load address is + // the start address of the first PT_LOAD segment. (ELF requires + // the segments to be sorted by load address.) For PIC executables + // and dynamic libraries (e_type == ET_DYN), this address will + // normally be zero. + for (int i = 0; i < nheader; ++i) { + const Phdr& header = program_headers[i]; + if (header.p_type == PT_LOAD) + return header.p_vaddr; + } + return 0; +} + +template +bool LoadSymbols(const string& obj_file, + const bool big_endian, + const typename ElfClass::Ehdr* elf_header, + const bool read_gnu_debug_link, + LoadSymbolsInfo* info, + SecMap* smap, + void* rx_avma, size_t rx_size, + UniqueStringUniverse* usu, + void (*log)(const char*)) { + typedef typename ElfClass::Phdr Phdr; + typedef typename ElfClass::Shdr Shdr; + + char buf[500]; + snprintf(buf, sizeof(buf), "LoadSymbols: BEGIN %s\n", obj_file.c_str()); + buf[sizeof(buf)-1] = 0; + log(buf); + + // This is how the text bias is calculated. + // BEGIN CALCULATE BIAS + uintptr_t loading_addr = GetLoadingAddress( + GetOffset(elf_header, elf_header->e_phoff), + elf_header->e_phnum); + uintptr_t text_bias = ((uintptr_t)rx_avma) - loading_addr; + snprintf(buf, sizeof(buf), + "LoadSymbols: rx_avma=%llx, text_bias=%llx", + (unsigned long long int)(uintptr_t)rx_avma, + (unsigned long long int)text_bias); + buf[sizeof(buf)-1] = 0; + log(buf); + // END CALCULATE BIAS + + const Shdr* sections = + GetOffset(elf_header, elf_header->e_shoff); + const Shdr* section_names = sections + elf_header->e_shstrndx; + const char* names = + GetOffset(elf_header, section_names->sh_offset); + const char *names_end = names + section_names->sh_size; + bool found_usable_info = false; + + // Dwarf Call Frame Information (CFI) is actually independent from + // the other DWARF debugging information, and can be used alone. + const Shdr* dwarf_cfi_section = + FindElfSectionByName(".debug_frame", SHT_PROGBITS, + sections, names, names_end, + elf_header->e_shnum); + if (dwarf_cfi_section) { + // Ignore the return value of this function; even without call frame + // information, the other debugging information could be perfectly + // useful. + info->LoadedSection(".debug_frame"); + bool result = + LoadDwarfCFI(obj_file, elf_header, ".debug_frame", + dwarf_cfi_section, false, 0, 0, big_endian, + smap, text_bias, usu, log); + found_usable_info = found_usable_info || result; + if (result) + log("LoadSymbols: read CFI from .debug_frame"); + } + + // Linux C++ exception handling information can also provide + // unwinding data. + const Shdr* eh_frame_section = + FindElfSectionByName(".eh_frame", SHT_PROGBITS, + sections, names, names_end, + elf_header->e_shnum); + if (eh_frame_section) { + // Pointers in .eh_frame data may be relative to the base addresses of + // certain sections. Provide those sections if present. + const Shdr* got_section = + FindElfSectionByName(".got", SHT_PROGBITS, + sections, names, names_end, + elf_header->e_shnum); + const Shdr* text_section = + FindElfSectionByName(".text", SHT_PROGBITS, + sections, names, names_end, + elf_header->e_shnum); + info->LoadedSection(".eh_frame"); + // As above, ignore the return value of this function. + bool result = + LoadDwarfCFI(obj_file, elf_header, ".eh_frame", + eh_frame_section, true, + got_section, text_section, big_endian, + smap, text_bias, usu, log); + found_usable_info = found_usable_info || result; + if (result) + log("LoadSymbols: read CFI from .eh_frame"); + } + + snprintf(buf, sizeof(buf), "LoadSymbols: END %s\n", obj_file.c_str()); + buf[sizeof(buf)-1] = 0; + log(buf); + + return found_usable_info; +} + +// Return the breakpad symbol file identifier for the architecture of +// ELF_HEADER. +template +const char* ElfArchitecture(const typename ElfClass::Ehdr* elf_header) { + typedef typename ElfClass::Half Half; + Half arch = elf_header->e_machine; + switch (arch) { + case EM_386: return "x86"; + case EM_ARM: return "arm"; + case EM_MIPS: return "mips"; + case EM_PPC64: return "ppc64"; + case EM_PPC: return "ppc"; + case EM_S390: return "s390"; + case EM_SPARC: return "sparc"; + case EM_SPARCV9: return "sparcv9"; + case EM_X86_64: return "x86_64"; + default: return NULL; + } +} + +// Format the Elf file identifier in IDENTIFIER as a UUID with the +// dashes removed. +string FormatIdentifier(unsigned char identifier[16]) { + char identifier_str[40]; + lul::FileID::ConvertIdentifierToString( + identifier, + identifier_str, + sizeof(identifier_str)); + string id_no_dash; + for (int i = 0; identifier_str[i] != '\0'; ++i) + if (identifier_str[i] != '-') + id_no_dash += identifier_str[i]; + // Add an extra "0" by the end. PDB files on Windows have an 'age' + // number appended to the end of the file identifier; this isn't + // really used or necessary on other platforms, but be consistent. + id_no_dash += '0'; + return id_no_dash; +} + +// Return the non-directory portion of FILENAME: the portion after the +// last slash, or the whole filename if there are no slashes. +string BaseFileName(const string &filename) { + // Lots of copies! basename's behavior is less than ideal. + char *c_filename = strdup(filename.c_str()); + string base = basename(c_filename); + free(c_filename); + return base; +} + +template +bool ReadSymbolDataElfClass(const typename ElfClass::Ehdr* elf_header, + const string& obj_filename, + const vector& debug_dirs, + SecMap* smap, void* rx_avma, size_t rx_size, + UniqueStringUniverse* usu, + void (*log)(const char*)) { + typedef typename ElfClass::Ehdr Ehdr; + + unsigned char identifier[16]; + if (!lul + ::FileID::ElfFileIdentifierFromMappedFile(elf_header, identifier)) { + fprintf(stderr, "%s: unable to generate file identifier\n", + obj_filename.c_str()); + return false; + } + + const char *architecture = ElfArchitecture(elf_header); + if (!architecture) { + fprintf(stderr, "%s: unrecognized ELF machine architecture: %d\n", + obj_filename.c_str(), elf_header->e_machine); + return false; + } + + // Figure out what endianness this file is. + bool big_endian; + if (!ElfEndianness(elf_header, &big_endian)) + return false; + + string name = BaseFileName(obj_filename); + string os = "Linux"; + string id = FormatIdentifier(identifier); + + LoadSymbolsInfo info(debug_dirs); + if (!LoadSymbols(obj_filename, big_endian, elf_header, + !debug_dirs.empty(), &info, + smap, rx_avma, rx_size, usu, log)) { + const string debuglink_file = info.debuglink_file(); + if (debuglink_file.empty()) + return false; + + // Load debuglink ELF file. + fprintf(stderr, "Found debugging info in %s\n", debuglink_file.c_str()); + MmapWrapper debug_map_wrapper; + Ehdr* debug_elf_header = NULL; + if (!LoadELF(debuglink_file, &debug_map_wrapper, + reinterpret_cast(&debug_elf_header))) + return false; + // Sanity checks to make sure everything matches up. + const char *debug_architecture = + ElfArchitecture(debug_elf_header); + if (!debug_architecture) { + fprintf(stderr, "%s: unrecognized ELF machine architecture: %d\n", + debuglink_file.c_str(), debug_elf_header->e_machine); + return false; + } + if (strcmp(architecture, debug_architecture)) { + fprintf(stderr, "%s with ELF machine architecture %s does not match " + "%s with ELF architecture %s\n", + debuglink_file.c_str(), debug_architecture, + obj_filename.c_str(), architecture); + return false; + } + + bool debug_big_endian; + if (!ElfEndianness(debug_elf_header, &debug_big_endian)) + return false; + if (debug_big_endian != big_endian) { + fprintf(stderr, "%s and %s does not match in endianness\n", + obj_filename.c_str(), debuglink_file.c_str()); + return false; + } + + if (!LoadSymbols(debuglink_file, debug_big_endian, + debug_elf_header, false, &info, + smap, rx_avma, rx_size, usu, log)) { + return false; + } + } + + return true; +} + +} // namespace (anon) + + +namespace lul { + +bool ReadSymbolDataInternal(const uint8_t* obj_file, + const string& obj_filename, + const vector& debug_dirs, + SecMap* smap, void* rx_avma, size_t rx_size, + UniqueStringUniverse* usu, + void (*log)(const char*)) { + + if (!IsValidElf(obj_file)) { + fprintf(stderr, "Not a valid ELF file: %s\n", obj_filename.c_str()); + return false; + } + + int elfclass = ElfClass(obj_file); + if (elfclass == ELFCLASS32) { + return ReadSymbolDataElfClass( + reinterpret_cast(obj_file), + obj_filename, debug_dirs, smap, rx_avma, rx_size, usu, log); + } + if (elfclass == ELFCLASS64) { + return ReadSymbolDataElfClass( + reinterpret_cast(obj_file), + obj_filename, debug_dirs, smap, rx_avma, rx_size, usu, log); + } + + return false; +} + +bool ReadSymbolData(const string& obj_file, + const vector& debug_dirs, + SecMap* smap, void* rx_avma, size_t rx_size, + UniqueStringUniverse* usu, + void (*log)(const char*)) { + MmapWrapper map_wrapper; + void* elf_header = NULL; + if (!LoadELF(obj_file, &map_wrapper, &elf_header)) + return false; + + return ReadSymbolDataInternal(reinterpret_cast(elf_header), + obj_file, debug_dirs, + smap, rx_avma, rx_size, usu, log); +} + + +namespace { + +template +void FindElfClassSection(const char *elf_base, + const char *section_name, + typename ElfClass::Word section_type, + const void **section_start, + int *section_size) { + typedef typename ElfClass::Ehdr Ehdr; + typedef typename ElfClass::Shdr Shdr; + + MOZ_ASSERT(elf_base); + MOZ_ASSERT(section_start); + MOZ_ASSERT(section_size); + + MOZ_ASSERT(strncmp(elf_base, ELFMAG, SELFMAG) == 0); + + const Ehdr* elf_header = reinterpret_cast(elf_base); + MOZ_ASSERT(elf_header->e_ident[EI_CLASS] == ElfClass::kClass); + + const Shdr* sections = + GetOffset(elf_header, elf_header->e_shoff); + const Shdr* section_names = sections + elf_header->e_shstrndx; + const char* names = + GetOffset(elf_header, section_names->sh_offset); + const char *names_end = names + section_names->sh_size; + + const Shdr* section = + FindElfSectionByName(section_name, section_type, + sections, names, names_end, + elf_header->e_shnum); + + if (section != NULL && section->sh_size > 0) { + *section_start = elf_base + section->sh_offset; + *section_size = section->sh_size; + } +} + +template +void FindElfClassSegment(const char *elf_base, + typename ElfClass::Word segment_type, + const void **segment_start, + int *segment_size) { + typedef typename ElfClass::Ehdr Ehdr; + typedef typename ElfClass::Phdr Phdr; + + MOZ_ASSERT(elf_base); + MOZ_ASSERT(segment_start); + MOZ_ASSERT(segment_size); + + MOZ_ASSERT(strncmp(elf_base, ELFMAG, SELFMAG) == 0); + + const Ehdr* elf_header = reinterpret_cast(elf_base); + MOZ_ASSERT(elf_header->e_ident[EI_CLASS] == ElfClass::kClass); + + const Phdr* phdrs = + GetOffset(elf_header, elf_header->e_phoff); + + for (int i = 0; i < elf_header->e_phnum; ++i) { + if (phdrs[i].p_type == segment_type) { + *segment_start = elf_base + phdrs[i].p_offset; + *segment_size = phdrs[i].p_filesz; + return; + } + } +} + +} // namespace (anon) + +bool IsValidElf(const void* elf_base) { + return strncmp(reinterpret_cast(elf_base), + ELFMAG, SELFMAG) == 0; +} + +int ElfClass(const void* elf_base) { + const ElfW(Ehdr)* elf_header = + reinterpret_cast(elf_base); + + return elf_header->e_ident[EI_CLASS]; +} + +bool FindElfSection(const void *elf_mapped_base, + const char *section_name, + uint32_t section_type, + const void **section_start, + int *section_size, + int *elfclass) { + MOZ_ASSERT(elf_mapped_base); + MOZ_ASSERT(section_start); + MOZ_ASSERT(section_size); + + *section_start = NULL; + *section_size = 0; + + if (!IsValidElf(elf_mapped_base)) + return false; + + int cls = ElfClass(elf_mapped_base); + if (elfclass) { + *elfclass = cls; + } + + const char* elf_base = + static_cast(elf_mapped_base); + + if (cls == ELFCLASS32) { + FindElfClassSection(elf_base, section_name, section_type, + section_start, section_size); + return *section_start != NULL; + } else if (cls == ELFCLASS64) { + FindElfClassSection(elf_base, section_name, section_type, + section_start, section_size); + return *section_start != NULL; + } + + return false; +} + +bool FindElfSegment(const void *elf_mapped_base, + uint32_t segment_type, + const void **segment_start, + int *segment_size, + int *elfclass) { + MOZ_ASSERT(elf_mapped_base); + MOZ_ASSERT(segment_start); + MOZ_ASSERT(segment_size); + + *segment_start = NULL; + *segment_size = 0; + + if (!IsValidElf(elf_mapped_base)) + return false; + + int cls = ElfClass(elf_mapped_base); + if (elfclass) { + *elfclass = cls; + } + + const char* elf_base = + static_cast(elf_mapped_base); + + if (cls == ELFCLASS32) { + FindElfClassSegment(elf_base, segment_type, + segment_start, segment_size); + return *segment_start != NULL; + } else if (cls == ELFCLASS64) { + FindElfClassSegment(elf_base, segment_type, + segment_start, segment_size); + return *segment_start != NULL; + } + + return false; +} + + +// (derived from) +// file_id.cc: Return a unique identifier for a file +// +// See file_id.h for documentation +// + +// ELF note name and desc are 32-bits word padded. +#define NOTE_PADDING(a) ((a + 3) & ~3) + +// These functions are also used inside the crashed process, so be safe +// and use the syscall/libc wrappers instead of direct syscalls or libc. + +template +static bool ElfClassBuildIDNoteIdentifier(const void *section, int length, + uint8_t identifier[kMDGUIDSize]) { + typedef typename ElfClass::Nhdr Nhdr; + + const void* section_end = reinterpret_cast(section) + length; + const Nhdr* note_header = reinterpret_cast(section); + while (reinterpret_cast(note_header) < section_end) { + if (note_header->n_type == NT_GNU_BUILD_ID) + break; + note_header = reinterpret_cast( + reinterpret_cast(note_header) + sizeof(Nhdr) + + NOTE_PADDING(note_header->n_namesz) + + NOTE_PADDING(note_header->n_descsz)); + } + if (reinterpret_cast(note_header) >= section_end || + note_header->n_descsz == 0) { + return false; + } + + const char* build_id = reinterpret_cast(note_header) + + sizeof(Nhdr) + NOTE_PADDING(note_header->n_namesz); + // Copy as many bits of the build ID as will fit + // into the GUID space. + memset(identifier, 0, kMDGUIDSize); + memcpy(identifier, build_id, + std::min(kMDGUIDSize, (size_t)note_header->n_descsz)); + + return true; +} + +// Attempt to locate a .note.gnu.build-id section in an ELF binary +// and copy as many bytes of it as will fit into |identifier|. +static bool FindElfBuildIDNote(const void *elf_mapped_base, + uint8_t identifier[kMDGUIDSize]) { + void* note_section; + int note_size, elfclass; + if ((!FindElfSegment(elf_mapped_base, PT_NOTE, + (const void**)¬e_section, ¬e_size, &elfclass) || + note_size == 0) && + (!FindElfSection(elf_mapped_base, ".note.gnu.build-id", SHT_NOTE, + (const void**)¬e_section, ¬e_size, &elfclass) || + note_size == 0)) { + return false; + } + + if (elfclass == ELFCLASS32) { + return ElfClassBuildIDNoteIdentifier(note_section, note_size, + identifier); + } else if (elfclass == ELFCLASS64) { + return ElfClassBuildIDNoteIdentifier(note_section, note_size, + identifier); + } + + return false; +} + +// Attempt to locate the .text section of an ELF binary and generate +// a simple hash by XORing the first page worth of bytes into |identifier|. +static bool HashElfTextSection(const void *elf_mapped_base, + uint8_t identifier[kMDGUIDSize]) { + void* text_section; + int text_size; + if (!FindElfSection(elf_mapped_base, ".text", SHT_PROGBITS, + (const void**)&text_section, &text_size, NULL) || + text_size == 0) { + return false; + } + + memset(identifier, 0, kMDGUIDSize); + const uint8_t* ptr = reinterpret_cast(text_section); + const uint8_t* ptr_end = ptr + std::min(text_size, 4096); + while (ptr < ptr_end) { + for (unsigned i = 0; i < kMDGUIDSize; i++) + identifier[i] ^= ptr[i]; + ptr += kMDGUIDSize; + } + return true; +} + +// static +bool FileID::ElfFileIdentifierFromMappedFile(const void* base, + uint8_t identifier[kMDGUIDSize]) { + // Look for a build id note first. + if (FindElfBuildIDNote(base, identifier)) + return true; + + // Fall back on hashing the first page of the text section. + return HashElfTextSection(base, identifier); +} + +// static +void FileID::ConvertIdentifierToString(const uint8_t identifier[kMDGUIDSize], + char* buffer, int buffer_length) { + uint8_t identifier_swapped[kMDGUIDSize]; + + // Endian-ness swap to match dump processor expectation. + memcpy(identifier_swapped, identifier, kMDGUIDSize); + uint32_t* data1 = reinterpret_cast(identifier_swapped); + *data1 = htonl(*data1); + uint16_t* data2 = reinterpret_cast(identifier_swapped + 4); + *data2 = htons(*data2); + uint16_t* data3 = reinterpret_cast(identifier_swapped + 6); + *data3 = htons(*data3); + + int buffer_idx = 0; + for (unsigned int idx = 0; + (buffer_idx < buffer_length) && (idx < kMDGUIDSize); + ++idx) { + int hi = (identifier_swapped[idx] >> 4) & 0x0F; + int lo = (identifier_swapped[idx]) & 0x0F; + + if (idx == 4 || idx == 6 || idx == 8 || idx == 10) + buffer[buffer_idx++] = '-'; + + buffer[buffer_idx++] = (hi >= 10) ? 'A' + hi - 10 : '0' + hi; + buffer[buffer_idx++] = (lo >= 10) ? 'A' + lo - 10 : '0' + lo; + } + + // NULL terminate + buffer[(buffer_idx < buffer_length) ? buffer_idx : buffer_idx - 1] = 0; +} + +} // namespace lul diff --git a/tools/profiler/lul/LulElfExt.h b/tools/profiler/lul/LulElfExt.h new file mode 100644 index 0000000000..b127d96d93 --- /dev/null +++ b/tools/profiler/lul/LulElfExt.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: */ + +// Copyright (c) 2006, 2011, 2012 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// This file is derived from the following files in +// toolkit/crashreporter/google-breakpad: +// src/common/linux/dump_symbols.h + +#ifndef LulElfExt_h +#define LulElfExt_h + +// These two functions are the external interface to the +// ELF/Dwarf/EXIDX reader. + +#include "LulMainInt.h" + +using lul::SecMap; + +namespace lul { + +// Find all the unwind information in OBJ_FILE, an ELF executable +// or shared library, and add it to SMAP. +bool ReadSymbolData(const std::string& obj_file, + const std::vector& debug_dirs, + SecMap* smap, + void* rx_avma, size_t rx_size, + void (*log)(const char*)); + +// The same as ReadSymbolData, except that OBJ_FILE is assumed to +// point to a mapped-in image of OBJ_FILENAME. +bool ReadSymbolDataInternal(const uint8_t* obj_file, + const std::string& obj_filename, + const std::vector& debug_dirs, + SecMap* smap, + void* rx_avma, size_t rx_size, + void (*log)(const char*)); + +} // namespace lul + +#endif // LulElfExt_h diff --git a/tools/profiler/lul/LulElfInt.h b/tools/profiler/lul/LulElfInt.h new file mode 100644 index 0000000000..899d7d3ee4 --- /dev/null +++ b/tools/profiler/lul/LulElfInt.h @@ -0,0 +1,234 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ + +// Copyright (c) 2006, 2012, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// This file is derived from the following files in +// toolkit/crashreporter/google-breakpad: +// src/common/android/include/elf.h +// src/common/linux/elfutils.h +// src/common/linux/file_id.h +// src/common/linux/elfutils-inl.h + +#ifndef LulElfInt_h +#define LulElfInt_h + +// This header defines functions etc internal to the ELF reader. It +// should not be included outside of LulElf.cpp. + +#include +#include + +#include "mozilla/Assertions.h" + +#include "LulPlatformMacros.h" + + +// (derived from) +// elfutils.h: Utilities for dealing with ELF files. +// + +#if defined(LUL_OS_android) + +// From toolkit/crashreporter/google-breakpad/src/common/android/include/elf.h +// The Android headers don't always define this constant. +#ifndef EM_X86_64 +#define EM_X86_64 62 +#endif + +#ifndef EM_PPC64 +#define EM_PPC64 21 +#endif + +#ifndef EM_S390 +#define EM_S390 22 +#endif + +#ifndef NT_GNU_BUILD_ID +#define NT_GNU_BUILD_ID 3 +#endif + +#define ElfW(type) _ElfW (Elf, ELFSIZE, type) +#define _ElfW(e,w,t) _ElfW_1 (e, w, _##t) +#define _ElfW_1(e,w,t) e##w##t + +//FIXME +extern "C" { + extern char* basename(const char* path); +}; +#else + +# include +#endif + + +namespace lul { + +// Traits classes so consumers can write templatized code to deal +// with specific ELF bits. +struct ElfClass32 { + typedef Elf32_Addr Addr; + typedef Elf32_Ehdr Ehdr; + typedef Elf32_Nhdr Nhdr; + typedef Elf32_Phdr Phdr; + typedef Elf32_Shdr Shdr; + typedef Elf32_Half Half; + typedef Elf32_Off Off; + typedef Elf32_Word Word; + static const int kClass = ELFCLASS32; + static const size_t kAddrSize = sizeof(Elf32_Addr); +}; + +struct ElfClass64 { + typedef Elf64_Addr Addr; + typedef Elf64_Ehdr Ehdr; + typedef Elf64_Nhdr Nhdr; + typedef Elf64_Phdr Phdr; + typedef Elf64_Shdr Shdr; + typedef Elf64_Half Half; + typedef Elf64_Off Off; + typedef Elf64_Word Word; + static const int kClass = ELFCLASS64; + static const size_t kAddrSize = sizeof(Elf64_Addr); +}; + +bool IsValidElf(const void* elf_header); +int ElfClass(const void* elf_base); + +// Attempt to find a section named |section_name| of type |section_type| +// in the ELF binary data at |elf_mapped_base|. On success, returns true +// and sets |*section_start| to point to the start of the section data, +// and |*section_size| to the size of the section's data. If |elfclass| +// is not NULL, set |*elfclass| to the ELF file class. +bool FindElfSection(const void *elf_mapped_base, + const char *section_name, + uint32_t section_type, + const void **section_start, + int *section_size, + int *elfclass); + +// Internal helper method, exposed for convenience for callers +// that already have more info. +template +const typename ElfClass::Shdr* +FindElfSectionByName(const char* name, + typename ElfClass::Word section_type, + const typename ElfClass::Shdr* sections, + const char* section_names, + const char* names_end, + int nsection); + +// Attempt to find the first segment of type |segment_type| in the ELF +// binary data at |elf_mapped_base|. On success, returns true and sets +// |*segment_start| to point to the start of the segment data, and +// and |*segment_size| to the size of the segment's data. If |elfclass| +// is not NULL, set |*elfclass| to the ELF file class. +bool FindElfSegment(const void *elf_mapped_base, + uint32_t segment_type, + const void **segment_start, + int *segment_size, + int *elfclass); + +// Convert an offset from an Elf header into a pointer to the mapped +// address in the current process. Takes an extra template parameter +// to specify the return type to avoid having to dynamic_cast the +// result. +template +const T* +GetOffset(const typename ElfClass::Ehdr* elf_header, + typename ElfClass::Off offset); + + +// (derived from) +// file_id.h: Return a unique identifier for a file +// + +static const size_t kMDGUIDSize = sizeof(MDGUID); + +class FileID { + public: + + // Load the identifier for the elf file mapped into memory at |base| into + // |identifier|. Return false if the identifier could not be created for the + // file. + static bool ElfFileIdentifierFromMappedFile(const void* base, + uint8_t identifier[kMDGUIDSize]); + + // Convert the |identifier| data to a NULL terminated string. The string will + // be formatted as a UUID (e.g., 22F065BB-FC9C-49F7-80FE-26A7CEBD7BCE). + // The |buffer| should be at least 37 bytes long to receive all of the data + // and termination. Shorter buffers will contain truncated data. + static void ConvertIdentifierToString(const uint8_t identifier[kMDGUIDSize], + char* buffer, int buffer_length); +}; + + + +template +const T* GetOffset(const typename ElfClass::Ehdr* elf_header, + typename ElfClass::Off offset) { + return reinterpret_cast(reinterpret_cast(elf_header) + + offset); +} + +template +const typename ElfClass::Shdr* FindElfSectionByName( + const char* name, + typename ElfClass::Word section_type, + const typename ElfClass::Shdr* sections, + const char* section_names, + const char* names_end, + int nsection) { + MOZ_ASSERT(name != NULL); + MOZ_ASSERT(sections != NULL); + MOZ_ASSERT(nsection > 0); + + int name_len = strlen(name); + if (name_len == 0) + return NULL; + + for (int i = 0; i < nsection; ++i) { + const char* section_name = section_names + sections[i].sh_name; + if (sections[i].sh_type == section_type && + names_end - section_name >= name_len + 1 && + strcmp(name, section_name) == 0) { + return sections + i; + } + } + return NULL; +} + +} // namespace lul + + +// And finally, the external interface, offered to LulMain.cpp +#include "LulElfExt.h" + +#endif // LulElfInt_h diff --git a/tools/profiler/lul/LulMain.cpp b/tools/profiler/lul/LulMain.cpp new file mode 100644 index 0000000000..a97d693bee --- /dev/null +++ b/tools/profiler/lul/LulMain.cpp @@ -0,0 +1,1944 @@ +/* -*- 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 "LulMain.h" + +#include +#include +#include + +#include // std::sort +#include + +#include "mozilla/Assertions.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/MemoryChecking.h" +#include "mozilla/Snprintf.h" + +#include "LulCommonExt.h" +#include "LulElfExt.h" + +#include "LulMainInt.h" + +#include "platform-linux-lul.h" // for gettid() + +// Set this to 1 for verbose logging +#define DEBUG_MAIN 0 + +namespace lul { + +using std::string; +using std::vector; +using std::pair; +using mozilla::DebugOnly; + + +// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING +// +// Some functions in this file are marked RUNS IN NO-MALLOC CONTEXT. +// Any such function -- and, hence, the transitive closure of those +// reachable from it -- must not do any dynamic memory allocation. +// Doing so risks deadlock. There is exactly one root function for +// the transitive closure: Lul::Unwind. +// +// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING + + +//////////////////////////////////////////////////////////////// +// RuleSet // +//////////////////////////////////////////////////////////////// + +static const char* +NameOf_DW_REG(int16_t aReg) +{ + switch (aReg) { + case DW_REG_CFA: return "cfa"; +#if defined(LUL_ARCH_x64) || defined(LUL_ARCH_x86) + case DW_REG_INTEL_XBP: return "xbp"; + case DW_REG_INTEL_XSP: return "xsp"; + case DW_REG_INTEL_XIP: return "xip"; +#elif defined(LUL_ARCH_arm) + case DW_REG_ARM_R7: return "r7"; + case DW_REG_ARM_R11: return "r11"; + case DW_REG_ARM_R12: return "r12"; + case DW_REG_ARM_R13: return "r13"; + case DW_REG_ARM_R14: return "r14"; + case DW_REG_ARM_R15: return "r15"; +#else +# error "Unsupported arch" +#endif + default: return "???"; + } +} + +string +LExpr::ShowRule(const char* aNewReg) const +{ + char buf[64]; + string res = string(aNewReg) + "="; + switch (mHow) { + case UNKNOWN: + res += "Unknown"; + break; + case NODEREF: + snprintf_literal(buf, "%s+%d", + NameOf_DW_REG(mReg), (int)mOffset); + res += buf; + break; + case DEREF: + snprintf_literal(buf, "*(%s+%d)", + NameOf_DW_REG(mReg), (int)mOffset); + res += buf; + break; + case PFXEXPR: + snprintf_literal(buf, "PfxExpr-at-%d", (int)mOffset); + res += buf; + break; + default: + res += "???"; + break; + } + return res; +} + +void +RuleSet::Print(void(*aLog)(const char*)) const +{ + char buf[96]; + snprintf_literal(buf, "[%llx .. %llx]: let ", + (unsigned long long int)mAddr, + (unsigned long long int)(mAddr + mLen - 1)); + string res = string(buf); + res += mCfaExpr.ShowRule("cfa"); + res += " in"; + // For each reg we care about, print the recovery expression. +#if defined(LUL_ARCH_x64) || defined(LUL_ARCH_x86) + res += mXipExpr.ShowRule(" RA"); + res += mXspExpr.ShowRule(" SP"); + res += mXbpExpr.ShowRule(" BP"); +#elif defined(LUL_ARCH_arm) + res += mR15expr.ShowRule(" R15"); + res += mR7expr .ShowRule(" R7" ); + res += mR11expr.ShowRule(" R11"); + res += mR12expr.ShowRule(" R12"); + res += mR13expr.ShowRule(" R13"); + res += mR14expr.ShowRule(" R14"); +#else +# error "Unsupported arch" +#endif + aLog(res.c_str()); +} + +LExpr* +RuleSet::ExprForRegno(DW_REG_NUMBER aRegno) { + switch (aRegno) { + case DW_REG_CFA: return &mCfaExpr; +# if defined(LUL_ARCH_x64) || defined(LUL_ARCH_x86) + case DW_REG_INTEL_XIP: return &mXipExpr; + case DW_REG_INTEL_XSP: return &mXspExpr; + case DW_REG_INTEL_XBP: return &mXbpExpr; +# elif defined(LUL_ARCH_arm) + case DW_REG_ARM_R15: return &mR15expr; + case DW_REG_ARM_R14: return &mR14expr; + case DW_REG_ARM_R13: return &mR13expr; + case DW_REG_ARM_R12: return &mR12expr; + case DW_REG_ARM_R11: return &mR11expr; + case DW_REG_ARM_R7: return &mR7expr; +# else +# error "Unknown arch" +# endif + default: return nullptr; + } +} + +RuleSet::RuleSet() +{ + mAddr = 0; + mLen = 0; + // The only other fields are of type LExpr and those are initialised + // by LExpr::LExpr(). +} + + +//////////////////////////////////////////////////////////////// +// SecMap // +//////////////////////////////////////////////////////////////// + +// See header file LulMainInt.h for comments about invariants. + +SecMap::SecMap(void(*aLog)(const char*)) + : mSummaryMinAddr(1) + , mSummaryMaxAddr(0) + , mUsable(true) + , mLog(aLog) +{} + +SecMap::~SecMap() { + mRuleSets.clear(); +} + +// RUNS IN NO-MALLOC CONTEXT +RuleSet* +SecMap::FindRuleSet(uintptr_t ia) { + // Binary search mRuleSets to find one that brackets |ia|. + // lo and hi need to be signed, else the loop termination tests + // don't work properly. Note that this works correctly even when + // mRuleSets.size() == 0. + + // Can't do this until the array has been sorted and preened. + MOZ_ASSERT(mUsable); + + long int lo = 0; + long int hi = (long int)mRuleSets.size() - 1; + while (true) { + // current unsearched space is from lo to hi, inclusive. + if (lo > hi) { + // not found + return nullptr; + } + long int mid = lo + ((hi - lo) / 2); + RuleSet* mid_ruleSet = &mRuleSets[mid]; + uintptr_t mid_minAddr = mid_ruleSet->mAddr; + uintptr_t mid_maxAddr = mid_minAddr + mid_ruleSet->mLen - 1; + if (ia < mid_minAddr) { hi = mid-1; continue; } + if (ia > mid_maxAddr) { lo = mid+1; continue; } + MOZ_ASSERT(mid_minAddr <= ia && ia <= mid_maxAddr); + return mid_ruleSet; + } + // NOTREACHED +} + +// Add a RuleSet to the collection. The rule is copied in. Calling +// this makes the map non-searchable. +void +SecMap::AddRuleSet(const RuleSet* rs) { + mUsable = false; + mRuleSets.push_back(*rs); +} + +// Add a PfxInstr to the vector of such instrs, and return the index +// in the vector. Calling this makes the map non-searchable. +uint32_t +SecMap::AddPfxInstr(PfxInstr pfxi) { + mUsable = false; + mPfxInstrs.push_back(pfxi); + return mPfxInstrs.size() - 1; +} + + +static bool +CmpRuleSetsByAddrLE(const RuleSet& rs1, const RuleSet& rs2) { + return rs1.mAddr < rs2.mAddr; +} + +// Prepare the map for searching. Completely remove any which don't +// fall inside the specified range [start, +len). +void +SecMap::PrepareRuleSets(uintptr_t aStart, size_t aLen) +{ + if (mRuleSets.empty()) { + return; + } + + MOZ_ASSERT(aLen > 0); + if (aLen == 0) { + // This should never happen. + mRuleSets.clear(); + return; + } + + // Sort by start addresses. + std::sort(mRuleSets.begin(), mRuleSets.end(), CmpRuleSetsByAddrLE); + + // Detect any entry not completely contained within [start, +len). + // Set its length to zero, so that the next pass will remove it. + for (size_t i = 0; i < mRuleSets.size(); ++i) { + RuleSet* rs = &mRuleSets[i]; + if (rs->mLen > 0 && + (rs->mAddr < aStart || rs->mAddr + rs->mLen > aStart + aLen)) { + rs->mLen = 0; + } + } + + // Iteratively truncate any overlaps and remove any zero length + // entries that might result, or that may have been present + // initially. Unless the input is seriously screwy, this is + // expected to iterate only once. + while (true) { + size_t i; + size_t n = mRuleSets.size(); + size_t nZeroLen = 0; + + if (n == 0) { + break; + } + + for (i = 1; i < n; ++i) { + RuleSet* prev = &mRuleSets[i-1]; + RuleSet* here = &mRuleSets[i]; + MOZ_ASSERT(prev->mAddr <= here->mAddr); + if (prev->mAddr + prev->mLen > here->mAddr) { + prev->mLen = here->mAddr - prev->mAddr; + } + if (prev->mLen == 0) + nZeroLen++; + } + + if (mRuleSets[n-1].mLen == 0) { + nZeroLen++; + } + + // At this point, the entries are in-order and non-overlapping. + // If none of them are zero-length, we are done. + if (nZeroLen == 0) { + break; + } + + // Slide back the entries to remove the zero length ones. + size_t j = 0; // The write-point. + for (i = 0; i < n; ++i) { + if (mRuleSets[i].mLen == 0) { + continue; + } + if (j != i) mRuleSets[j] = mRuleSets[i]; + ++j; + } + MOZ_ASSERT(i == n); + MOZ_ASSERT(nZeroLen <= n); + MOZ_ASSERT(j == n - nZeroLen); + while (nZeroLen > 0) { + mRuleSets.pop_back(); + nZeroLen--; + } + + MOZ_ASSERT(mRuleSets.size() == j); + } + + size_t n = mRuleSets.size(); + +#ifdef DEBUG + // Do a final check on the rules: their address ranges must be + // ascending, non overlapping, non zero sized. + if (n > 0) { + MOZ_ASSERT(mRuleSets[0].mLen > 0); + for (size_t i = 1; i < n; ++i) { + RuleSet* prev = &mRuleSets[i-1]; + RuleSet* here = &mRuleSets[i]; + MOZ_ASSERT(prev->mAddr < here->mAddr); + MOZ_ASSERT(here->mLen > 0); + MOZ_ASSERT(prev->mAddr + prev->mLen <= here->mAddr); + } + } +#endif + + // Set the summary min and max address values. + if (n == 0) { + // Use the values defined in comments in the class declaration. + mSummaryMinAddr = 1; + mSummaryMaxAddr = 0; + } else { + mSummaryMinAddr = mRuleSets[0].mAddr; + mSummaryMaxAddr = mRuleSets[n-1].mAddr + mRuleSets[n-1].mLen - 1; + } + char buf[150]; + snprintf_literal(buf, + "PrepareRuleSets: %d entries, smin/smax 0x%llx, 0x%llx\n", + (int)n, (unsigned long long int)mSummaryMinAddr, + (unsigned long long int)mSummaryMaxAddr); + buf[sizeof(buf)-1] = 0; + mLog(buf); + + // Is now usable for binary search. + mUsable = true; + + if (0) { + mLog("\nRulesets after preening\n"); + for (size_t i = 0; i < mRuleSets.size(); ++i) { + mRuleSets[i].Print(mLog); + mLog("\n"); + } + mLog("\n"); + } +} + +bool SecMap::IsEmpty() { + return mRuleSets.empty(); +} + + +//////////////////////////////////////////////////////////////// +// SegArray // +//////////////////////////////////////////////////////////////// + +// A SegArray holds a set of address ranges that together exactly +// cover an address range, with no overlaps or holes. Each range has +// an associated value, which in this case has been specialised to be +// a simple boolean. The representation is kept to minimal canonical +// form in which adjacent ranges with the same associated value are +// merged together. Each range is represented by a |struct Seg|. +// +// SegArrays are used to keep track of which parts of the address +// space are known to contain instructions. +class SegArray { + + public: + void add(uintptr_t lo, uintptr_t hi, bool val) { + if (lo > hi) { + return; + } + split_at(lo); + if (hi < UINTPTR_MAX) { + split_at(hi+1); + } + std::vector::size_type iLo, iHi, i; + iLo = find(lo); + iHi = find(hi); + for (i = iLo; i <= iHi; ++i) { + mSegs[i].val = val; + } + preen(); + } + + // RUNS IN NO-MALLOC CONTEXT + bool getBoundingCodeSegment(/*OUT*/uintptr_t* rx_min, + /*OUT*/uintptr_t* rx_max, uintptr_t addr) { + std::vector::size_type i = find(addr); + if (!mSegs[i].val) { + return false; + } + *rx_min = mSegs[i].lo; + *rx_max = mSegs[i].hi; + return true; + } + + SegArray() { + Seg s(0, UINTPTR_MAX, false); + mSegs.push_back(s); + } + + private: + struct Seg { + Seg(uintptr_t lo, uintptr_t hi, bool val) : lo(lo), hi(hi), val(val) {} + uintptr_t lo; + uintptr_t hi; + bool val; + }; + + void preen() { + for (std::vector::iterator iter = mSegs.begin(); + iter < mSegs.end()-1; + ++iter) { + if (iter[0].val != iter[1].val) { + continue; + } + iter[0].hi = iter[1].hi; + mSegs.erase(iter+1); + // Back up one, so as not to miss an opportunity to merge + // with the entry after this one. + --iter; + } + } + + // RUNS IN NO-MALLOC CONTEXT + std::vector::size_type find(uintptr_t a) { + long int lo = 0; + long int hi = (long int)mSegs.size(); + while (true) { + // The unsearched space is lo .. hi inclusive. + if (lo > hi) { + // Not found. This can't happen. + return (std::vector::size_type)(-1); + } + long int mid = lo + ((hi - lo) / 2); + uintptr_t mid_lo = mSegs[mid].lo; + uintptr_t mid_hi = mSegs[mid].hi; + if (a < mid_lo) { hi = mid-1; continue; } + if (a > mid_hi) { lo = mid+1; continue; } + return (std::vector::size_type)mid; + } + } + + void split_at(uintptr_t a) { + std::vector::size_type i = find(a); + if (mSegs[i].lo == a) { + return; + } + mSegs.insert( mSegs.begin()+i+1, mSegs[i] ); + mSegs[i].hi = a-1; + mSegs[i+1].lo = a; + } + + void show() { + printf("<< %d entries:\n", (int)mSegs.size()); + for (std::vector::iterator iter = mSegs.begin(); + iter < mSegs.end(); + ++iter) { + printf(" %016llx %016llx %s\n", + (unsigned long long int)(*iter).lo, + (unsigned long long int)(*iter).hi, + (*iter).val ? "true" : "false"); + } + printf(">>\n"); + } + + std::vector mSegs; +}; + + +//////////////////////////////////////////////////////////////// +// PriMap // +//////////////////////////////////////////////////////////////// + +class PriMap { + public: + explicit PriMap(void (*aLog)(const char*)) + : mLog(aLog) + {} + + ~PriMap() { + for (std::vector::iterator iter = mSecMaps.begin(); + iter != mSecMaps.end(); + ++iter) { + delete *iter; + } + mSecMaps.clear(); + } + + // RUNS IN NO-MALLOC CONTEXT + pair*> + Lookup(uintptr_t ia) + { + SecMap* sm = FindSecMap(ia); + return pair*> + (sm ? sm->FindRuleSet(ia) : nullptr, + sm ? sm->GetPfxInstrs() : nullptr); + } + + // Add a secondary map. No overlaps allowed w.r.t. existing + // secondary maps. + void AddSecMap(SecMap* aSecMap) { + // We can't add an empty SecMap to the PriMap. But that's OK + // since we'd never be able to find anything in it anyway. + if (aSecMap->IsEmpty()) { + return; + } + + // Iterate through the SecMaps and find the right place for this + // one. At the same time, ensure that the in-order + // non-overlapping invariant is preserved (and, generally, holds). + // FIXME: this gives a cost that is O(N^2) in the total number of + // shared objects in the system. ToDo: better. + MOZ_ASSERT(aSecMap->mSummaryMinAddr <= aSecMap->mSummaryMaxAddr); + + size_t num_secMaps = mSecMaps.size(); + uintptr_t i; + for (i = 0; i < num_secMaps; ++i) { + SecMap* sm_i = mSecMaps[i]; + MOZ_ASSERT(sm_i->mSummaryMinAddr <= sm_i->mSummaryMaxAddr); + if (aSecMap->mSummaryMinAddr < sm_i->mSummaryMaxAddr) { + // |aSecMap| needs to be inserted immediately before mSecMaps[i]. + break; + } + } + MOZ_ASSERT(i <= num_secMaps); + if (i == num_secMaps) { + // It goes at the end. + mSecMaps.push_back(aSecMap); + } else { + std::vector::iterator iter = mSecMaps.begin() + i; + mSecMaps.insert(iter, aSecMap); + } + char buf[100]; + snprintf_literal(buf, "AddSecMap: now have %d SecMaps\n", + (int)mSecMaps.size()); + buf[sizeof(buf)-1] = 0; + mLog(buf); + } + + // Remove and delete any SecMaps in the mapping, that intersect + // with the specified address range. + void RemoveSecMapsInRange(uintptr_t avma_min, uintptr_t avma_max) { + MOZ_ASSERT(avma_min <= avma_max); + size_t num_secMaps = mSecMaps.size(); + if (num_secMaps > 0) { + intptr_t i; + // Iterate from end to start over the vector, so as to ensure + // that the special case where |avma_min| and |avma_max| denote + // the entire address space, can be completed in time proportional + // to the number of elements in the map. + for (i = (intptr_t)num_secMaps-1; i >= 0; i--) { + SecMap* sm_i = mSecMaps[i]; + if (sm_i->mSummaryMaxAddr < avma_min || + avma_max < sm_i->mSummaryMinAddr) { + // There's no overlap. Move on. + continue; + } + // We need to remove mSecMaps[i] and slide all those above it + // downwards to cover the hole. + mSecMaps.erase(mSecMaps.begin() + i); + delete sm_i; + } + } + } + + // Return the number of currently contained SecMaps. + size_t CountSecMaps() { + return mSecMaps.size(); + } + + // Assess heuristically whether the given address is an instruction + // immediately following a call instruction. + // RUNS IN NO-MALLOC CONTEXT + bool MaybeIsReturnPoint(TaggedUWord aInstrAddr, SegArray* aSegArray) { + if (!aInstrAddr.Valid()) { + return false; + } + + uintptr_t ia = aInstrAddr.Value(); + + // Assume that nobody would be crazy enough to put code in the + // first or last page. + if (ia < 4096 || ((uintptr_t)(-ia)) < 4096) { + return false; + } + + // See if it falls inside a known r-x mapped area. Poking around + // outside such places risks segfaulting. + uintptr_t insns_min, insns_max; + bool b = aSegArray->getBoundingCodeSegment(&insns_min, &insns_max, ia); + if (!b) { + // no code (that we know about) at this address + return false; + } + + // |ia| falls within an r-x range. So we can + // safely poke around in [insns_min, insns_max]. + +#if defined(LUL_ARCH_x64) || defined(LUL_ARCH_x86) + // Is the previous instruction recognisably a CALL? This is + // common for the 32- and 64-bit versions, except for the + // simm32(%rip) case, which is 64-bit only. + // + // For all other cases, the 64 bit versions are either identical + // to the 32 bit versions, or have an optional extra leading REX.W + // byte (0x41). Since the extra 0x41 is optional we have to + // ignore it, with the convenient result that the same matching + // logic works for both 32- and 64-bit cases. + + uint8_t* p = (uint8_t*)ia; +# if defined(LUL_ARCH_x64) + // CALL simm32(%rip) == FF15 simm32 + if (ia - 6 >= insns_min && p[-6] == 0xFF && p[-5] == 0x15) { + return true; + } +# endif + // CALL rel32 == E8 rel32 (both 32- and 64-bit) + if (ia - 5 >= insns_min && p[-5] == 0xE8) { + return true; + } + // CALL *%eax .. CALL *%edi == FFD0 .. FFD7 (32-bit) + // CALL *%rax .. CALL *%rdi == FFD0 .. FFD7 (64-bit) + // CALL *%r8 .. CALL *%r15 == 41FFD0 .. 41FFD7 (64-bit) + if (ia - 2 >= insns_min && + p[-2] == 0xFF && p[-1] >= 0xD0 && p[-1] <= 0xD7) { + return true; + } + // Almost all of the remaining cases that occur in practice are + // of the form CALL *simm8(reg) or CALL *simm32(reg). + // + // 64 bit cases: + // + // call *simm8(%rax) FF50 simm8 + // call *simm8(%rcx) FF51 simm8 + // call *simm8(%rdx) FF52 simm8 + // call *simm8(%rbx) FF53 simm8 + // call *simm8(%rsp) FF5424 simm8 + // call *simm8(%rbp) FF55 simm8 + // call *simm8(%rsi) FF56 simm8 + // call *simm8(%rdi) FF57 simm8 + // + // call *simm8(%r8) 41FF50 simm8 + // call *simm8(%r9) 41FF51 simm8 + // call *simm8(%r10) 41FF52 simm8 + // call *simm8(%r11) 41FF53 simm8 + // call *simm8(%r12) 41FF5424 simm8 + // call *simm8(%r13) 41FF55 simm8 + // call *simm8(%r14) 41FF56 simm8 + // call *simm8(%r15) 41FF57 simm8 + // + // call *simm32(%rax) FF90 simm32 + // call *simm32(%rcx) FF91 simm32 + // call *simm32(%rdx) FF92 simm32 + // call *simm32(%rbx) FF93 simm32 + // call *simm32(%rsp) FF9424 simm32 + // call *simm32(%rbp) FF95 simm32 + // call *simm32(%rsi) FF96 simm32 + // call *simm32(%rdi) FF97 simm32 + // + // call *simm32(%r8) 41FF90 simm32 + // call *simm32(%r9) 41FF91 simm32 + // call *simm32(%r10) 41FF92 simm32 + // call *simm32(%r11) 41FF93 simm32 + // call *simm32(%r12) 41FF9424 simm32 + // call *simm32(%r13) 41FF95 simm32 + // call *simm32(%r14) 41FF96 simm32 + // call *simm32(%r15) 41FF97 simm32 + // + // 32 bit cases: + // + // call *simm8(%eax) FF50 simm8 + // call *simm8(%ecx) FF51 simm8 + // call *simm8(%edx) FF52 simm8 + // call *simm8(%ebx) FF53 simm8 + // call *simm8(%esp) FF5424 simm8 + // call *simm8(%ebp) FF55 simm8 + // call *simm8(%esi) FF56 simm8 + // call *simm8(%edi) FF57 simm8 + // + // call *simm32(%eax) FF90 simm32 + // call *simm32(%ecx) FF91 simm32 + // call *simm32(%edx) FF92 simm32 + // call *simm32(%ebx) FF93 simm32 + // call *simm32(%esp) FF9424 simm32 + // call *simm32(%ebp) FF95 simm32 + // call *simm32(%esi) FF96 simm32 + // call *simm32(%edi) FF97 simm32 + if (ia - 3 >= insns_min && + p[-3] == 0xFF && + (p[-2] >= 0x50 && p[-2] <= 0x57 && p[-2] != 0x54)) { + // imm8 case, not including %esp/%rsp + return true; + } + if (ia - 4 >= insns_min && + p[-4] == 0xFF && p[-3] == 0x54 && p[-2] == 0x24) { + // imm8 case for %esp/%rsp + return true; + } + if (ia - 6 >= insns_min && + p[-6] == 0xFF && + (p[-5] >= 0x90 && p[-5] <= 0x97 && p[-5] != 0x94)) { + // imm32 case, not including %esp/%rsp + return true; + } + if (ia - 7 >= insns_min && + p[-7] == 0xFF && p[-6] == 0x94 && p[-5] == 0x24) { + // imm32 case for %esp/%rsp + return true; + } + +#elif defined(LUL_ARCH_arm) + if (ia & 1) { + uint16_t w0 = 0, w1 = 0; + // The return address has its lowest bit set, indicating a return + // to Thumb code. + ia &= ~(uintptr_t)1; + if (ia - 2 >= insns_min && ia - 1 <= insns_max) { + w1 = *(uint16_t*)(ia - 2); + } + if (ia - 4 >= insns_min && ia - 1 <= insns_max) { + w0 = *(uint16_t*)(ia - 4); + } + // Is it a 32-bit Thumb call insn? + // BL simm26 (Encoding T1) + if ((w0 & 0xF800) == 0xF000 && (w1 & 0xC000) == 0xC000) { + return true; + } + // BLX simm26 (Encoding T2) + if ((w0 & 0xF800) == 0xF000 && (w1 & 0xC000) == 0xC000) { + return true; + } + // Other possible cases: + // (BLX Rm, Encoding T1). + // BLX Rm (encoding T1, 16 bit, inspect w1 and ignore w0.) + // 0100 0111 1 Rm 000 + } else { + // Returning to ARM code. + uint32_t a0 = 0; + if ((ia & 3) == 0 && ia - 4 >= insns_min && ia - 1 <= insns_max) { + a0 = *(uint32_t*)(ia - 4); + } + // Leading E forces unconditional only -- fix. It could be + // anything except F, which is the deprecated NV code. + // BL simm26 (Encoding A1) + if ((a0 & 0xFF000000) == 0xEB000000) { + return true; + } + // Other possible cases: + // BLX simm26 (Encoding A2) + //if ((a0 & 0xFE000000) == 0xFA000000) + // return true; + // BLX (register) (A1): BLX + // cond 0001 0010 1111 1111 1111 0011 Rm + // again, cond can be anything except NV (0xF) + } + +#else +# error "Unsupported arch" +#endif + + // Not an insn we recognise. + return false; + } + + private: + // RUNS IN NO-MALLOC CONTEXT + SecMap* FindSecMap(uintptr_t ia) { + // Binary search mSecMaps to find one that brackets |ia|. + // lo and hi need to be signed, else the loop termination tests + // don't work properly. + long int lo = 0; + long int hi = (long int)mSecMaps.size() - 1; + while (true) { + // current unsearched space is from lo to hi, inclusive. + if (lo > hi) { + // not found + return nullptr; + } + long int mid = lo + ((hi - lo) / 2); + SecMap* mid_secMap = mSecMaps[mid]; + uintptr_t mid_minAddr = mid_secMap->mSummaryMinAddr; + uintptr_t mid_maxAddr = mid_secMap->mSummaryMaxAddr; + if (ia < mid_minAddr) { hi = mid-1; continue; } + if (ia > mid_maxAddr) { lo = mid+1; continue; } + MOZ_ASSERT(mid_minAddr <= ia && ia <= mid_maxAddr); + return mid_secMap; + } + // NOTREACHED + } + + private: + // sorted array of per-object ranges, non overlapping, non empty + std::vector mSecMaps; + + // a logging sink, for debugging. + void (*mLog)(const char*); +}; + + +//////////////////////////////////////////////////////////////// +// LUL // +//////////////////////////////////////////////////////////////// + +#define LUL_LOG(_str) \ + do { \ + char buf[200]; \ + snprintf_literal(buf, \ + "LUL: pid %d tid %d lul-obj %p: %s", \ + getpid(), gettid(), this, (_str)); \ + buf[sizeof(buf)-1] = 0; \ + mLog(buf); \ + } while (0) + +LUL::LUL(void (*aLog)(const char*)) + : mLog(aLog) + , mAdminMode(true) + , mAdminThreadId(gettid()) + , mPriMap(new PriMap(aLog)) + , mSegArray(new SegArray()) + , mUSU(new UniqueStringUniverse()) +{ + LUL_LOG("LUL::LUL: Created object"); +} + + +LUL::~LUL() +{ + LUL_LOG("LUL::~LUL: Destroyed object"); + delete mPriMap; + delete mSegArray; + mLog = nullptr; + delete mUSU; +} + + +void +LUL::MaybeShowStats() +{ + // This is racey in the sense that it can't guarantee that + // n_new == n_new_Context + n_new_CFI + n_new_Scanned + // if it should happen that mStats is updated by some other thread + // in between computation of n_new and n_new_{Context,CFI,Scanned}. + // But it's just stats printing, so we don't really care. + uint32_t n_new = mStats - mStatsPrevious; + if (n_new >= 5000) { + uint32_t n_new_Context = mStats.mContext - mStatsPrevious.mContext; + uint32_t n_new_CFI = mStats.mCFI - mStatsPrevious.mCFI; + uint32_t n_new_Scanned = mStats.mScanned - mStatsPrevious.mScanned; + mStatsPrevious = mStats; + char buf[200]; + snprintf_literal(buf, + "LUL frame stats: TOTAL %5u" + " CTX %4u CFI %4u SCAN %4u", + n_new, n_new_Context, n_new_CFI, n_new_Scanned); + buf[sizeof(buf)-1] = 0; + mLog(buf); + } +} + + +void +LUL::EnableUnwinding() +{ + LUL_LOG("LUL::EnableUnwinding"); + // Don't assert for Admin mode here. That is, tolerate a call here + // if we are already in Unwinding mode. + MOZ_ASSERT(gettid() == mAdminThreadId); + + mAdminMode = false; +} + + +void +LUL::NotifyAfterMap(uintptr_t aRXavma, size_t aSize, + const char* aFileName, const void* aMappedImage) +{ + MOZ_ASSERT(mAdminMode); + MOZ_ASSERT(gettid() == mAdminThreadId); + + mLog(":\n"); + char buf[200]; + snprintf_literal(buf, "NotifyMap %llx %llu %s\n", + (unsigned long long int)aRXavma, (unsigned long long int)aSize, + aFileName); + buf[sizeof(buf)-1] = 0; + mLog(buf); + + // Ignore obviously-stupid notifications. + if (aSize > 0) { + + // Here's a new mapping, for this object. + SecMap* smap = new SecMap(mLog); + + // Read CFI or EXIDX unwind data into |smap|. + if (!aMappedImage) { + (void)lul::ReadSymbolData( + string(aFileName), std::vector(), smap, + (void*)aRXavma, aSize, mUSU, mLog); + } else { + (void)lul::ReadSymbolDataInternal( + (const uint8_t*)aMappedImage, + string(aFileName), std::vector(), smap, + (void*)aRXavma, aSize, mUSU, mLog); + } + + mLog("NotifyMap .. preparing entries\n"); + + smap->PrepareRuleSets(aRXavma, aSize); + + snprintf_literal(buf, + "NotifyMap got %lld entries\n", (long long int)smap->Size()); + buf[sizeof(buf)-1] = 0; + mLog(buf); + + // Add it to the primary map (the top level set of mapped objects). + mPriMap->AddSecMap(smap); + + // Tell the segment array about the mapping, so that the stack + // scan and __kernel_syscall mechanisms know where valid code is. + mSegArray->add(aRXavma, aRXavma + aSize - 1, true); + } +} + + +void +LUL::NotifyExecutableArea(uintptr_t aRXavma, size_t aSize) +{ + MOZ_ASSERT(mAdminMode); + MOZ_ASSERT(gettid() == mAdminThreadId); + + mLog(":\n"); + char buf[200]; + snprintf_literal(buf, "NotifyExecutableArea %llx %llu\n", + (unsigned long long int)aRXavma, (unsigned long long int)aSize); + buf[sizeof(buf)-1] = 0; + mLog(buf); + + // Ignore obviously-stupid notifications. + if (aSize > 0) { + // Tell the segment array about the mapping, so that the stack + // scan and __kernel_syscall mechanisms know where valid code is. + mSegArray->add(aRXavma, aRXavma + aSize - 1, true); + } +} + + +void +LUL::NotifyBeforeUnmap(uintptr_t aRXavmaMin, uintptr_t aRXavmaMax) +{ + MOZ_ASSERT(mAdminMode); + MOZ_ASSERT(gettid() == mAdminThreadId); + + mLog(":\n"); + char buf[100]; + snprintf_literal(buf, "NotifyUnmap %016llx-%016llx\n", + (unsigned long long int)aRXavmaMin, + (unsigned long long int)aRXavmaMax); + buf[sizeof(buf)-1] = 0; + mLog(buf); + + MOZ_ASSERT(aRXavmaMin <= aRXavmaMax); + + // Remove from the primary map, any secondary maps that intersect + // with the address range. Also delete the secondary maps. + mPriMap->RemoveSecMapsInRange(aRXavmaMin, aRXavmaMax); + + // Tell the segment array that the address range no longer + // contains valid code. + mSegArray->add(aRXavmaMin, aRXavmaMax, false); + + snprintf_literal(buf, "NotifyUnmap: now have %d SecMaps\n", + (int)mPriMap->CountSecMaps()); + buf[sizeof(buf)-1] = 0; + mLog(buf); +} + + +size_t +LUL::CountMappings() +{ + MOZ_ASSERT(mAdminMode); + MOZ_ASSERT(gettid() == mAdminThreadId); + + return mPriMap->CountSecMaps(); +} + + +// RUNS IN NO-MALLOC CONTEXT +static +TaggedUWord DerefTUW(TaggedUWord aAddr, const StackImage* aStackImg) +{ + if (!aAddr.Valid()) { + return TaggedUWord(); + } + if (aAddr.Value() < aStackImg->mStartAvma) { + return TaggedUWord(); + } + if (aAddr.Value() + sizeof(uintptr_t) > aStackImg->mStartAvma + + aStackImg->mLen) { + return TaggedUWord(); + } + return TaggedUWord(*(uintptr_t*)(aStackImg->mContents + aAddr.Value() + - aStackImg->mStartAvma)); +} + +// RUNS IN NO-MALLOC CONTEXT +static +TaggedUWord EvaluateReg(int16_t aReg, const UnwindRegs* aOldRegs, + TaggedUWord aCFA) +{ + switch (aReg) { + case DW_REG_CFA: return aCFA; +#if defined(LUL_ARCH_x64) || defined(LUL_ARCH_x86) + case DW_REG_INTEL_XBP: return aOldRegs->xbp; + case DW_REG_INTEL_XSP: return aOldRegs->xsp; + case DW_REG_INTEL_XIP: return aOldRegs->xip; +#elif defined(LUL_ARCH_arm) + case DW_REG_ARM_R7: return aOldRegs->r7; + case DW_REG_ARM_R11: return aOldRegs->r11; + case DW_REG_ARM_R12: return aOldRegs->r12; + case DW_REG_ARM_R13: return aOldRegs->r13; + case DW_REG_ARM_R14: return aOldRegs->r14; + case DW_REG_ARM_R15: return aOldRegs->r15; +#else +# error "Unsupported arch" +#endif + default: MOZ_ASSERT(0); return TaggedUWord(); + } +} + +// RUNS IN NO-MALLOC CONTEXT +// See prototype for comment. +TaggedUWord EvaluatePfxExpr(int32_t start, + const UnwindRegs* aOldRegs, + TaggedUWord aCFA, const StackImage* aStackImg, + const vector& aPfxInstrs) +{ + // A small evaluation stack, and a stack pointer, which points to + // the highest numbered in-use element. + const int N_STACK = 10; + TaggedUWord stack[N_STACK]; + int stackPointer = -1; + for (int i = 0; i < N_STACK; i++) + stack[i] = TaggedUWord(); + +# define PUSH(_tuw) \ + do { \ + if (stackPointer >= N_STACK-1) goto fail; /* overflow */ \ + stack[++stackPointer] = (_tuw); \ + } while (0) + +# define POP(_lval) \ + do { \ + if (stackPointer < 0) goto fail; /* underflow */ \ + _lval = stack[stackPointer--]; \ + } while (0) + + // Cursor in the instruction sequence. + size_t curr = start + 1; + + // Check the start point is sane. + size_t nInstrs = aPfxInstrs.size(); + if (start < 0 || (size_t)start >= nInstrs) + goto fail; + + { + // The instruction sequence must start with PX_Start. If not, + // something is seriously wrong. + PfxInstr first = aPfxInstrs[start]; + if (first.mOpcode != PX_Start) + goto fail; + + // Push the CFA on the stack to start with (or not), as required by + // the original DW_OP_*expression* CFI. + if (first.mOperand != 0) + PUSH(aCFA); + } + + while (true) { + if (curr >= nInstrs) + goto fail; // ran off the end of the sequence + + PfxInstr pfxi = aPfxInstrs[curr++]; + if (pfxi.mOpcode == PX_End) + break; // we're done + + switch (pfxi.mOpcode) { + case PX_Start: + // This should appear only at the start of the sequence. + goto fail; + case PX_End: + // We just took care of that, so we shouldn't see it again. + MOZ_ASSERT(0); + goto fail; + case PX_SImm32: + PUSH(TaggedUWord((intptr_t)pfxi.mOperand)); + break; + case PX_DwReg: { + DW_REG_NUMBER reg = (DW_REG_NUMBER)pfxi.mOperand; + MOZ_ASSERT(reg != DW_REG_CFA); + PUSH(EvaluateReg(reg, aOldRegs, aCFA)); + break; + } + case PX_Deref: { + TaggedUWord addr; + POP(addr); + PUSH(DerefTUW(addr, aStackImg)); + break; + } + case PX_Add: { + TaggedUWord x, y; + POP(x); POP(y); PUSH(y + x); + break; + } + case PX_Sub: { + TaggedUWord x, y; + POP(x); POP(y); PUSH(y - x); + break; + } + case PX_And: { + TaggedUWord x, y; + POP(x); POP(y); PUSH(y & x); + break; + } + case PX_Or: { + TaggedUWord x, y; + POP(x); POP(y); PUSH(y | x); + break; + } + case PX_CmpGES: { + TaggedUWord x, y; + POP(x); POP(y); PUSH(y.CmpGEs(x)); + break; + } + case PX_Shl: { + TaggedUWord x, y; + POP(x); POP(y); PUSH(y << x); + break; + } + default: + MOZ_ASSERT(0); + goto fail; + } + } // while (true) + + // Evaluation finished. The top value on the stack is the result. + if (stackPointer >= 0) { + return stack[stackPointer]; + } + // Else fall through + + fail: + return TaggedUWord(); + +# undef PUSH +# undef POP +} + +// RUNS IN NO-MALLOC CONTEXT +TaggedUWord LExpr::EvaluateExpr(const UnwindRegs* aOldRegs, + TaggedUWord aCFA, const StackImage* aStackImg, + const vector* aPfxInstrs) const +{ + switch (mHow) { + case UNKNOWN: + return TaggedUWord(); + case NODEREF: { + TaggedUWord tuw = EvaluateReg(mReg, aOldRegs, aCFA); + tuw = tuw + TaggedUWord((intptr_t)mOffset); + return tuw; + } + case DEREF: { + TaggedUWord tuw = EvaluateReg(mReg, aOldRegs, aCFA); + tuw = tuw + TaggedUWord((intptr_t)mOffset); + return DerefTUW(tuw, aStackImg); + } + case PFXEXPR: { + MOZ_ASSERT(aPfxInstrs); + if (!aPfxInstrs) { + return TaggedUWord(); + } + return EvaluatePfxExpr(mOffset, aOldRegs, aCFA, aStackImg, *aPfxInstrs); + } + default: + MOZ_ASSERT(0); + return TaggedUWord(); + } +} + +// RUNS IN NO-MALLOC CONTEXT +static +void UseRuleSet(/*MOD*/UnwindRegs* aRegs, + const StackImage* aStackImg, const RuleSet* aRS, + const vector* aPfxInstrs) +{ + // Take a copy of regs, since we'll need to refer to the old values + // whilst computing the new ones. + UnwindRegs old_regs = *aRegs; + + // Mark all the current register values as invalid, so that the + // caller can see, on our return, which ones have been computed + // anew. If we don't even manage to compute a new PC value, then + // the caller will have to abandon the unwind. + // FIXME: Create and use instead: aRegs->SetAllInvalid(); +#if defined(LUL_ARCH_x64) || defined(LUL_ARCH_x86) + aRegs->xbp = TaggedUWord(); + aRegs->xsp = TaggedUWord(); + aRegs->xip = TaggedUWord(); +#elif defined(LUL_ARCH_arm) + aRegs->r7 = TaggedUWord(); + aRegs->r11 = TaggedUWord(); + aRegs->r12 = TaggedUWord(); + aRegs->r13 = TaggedUWord(); + aRegs->r14 = TaggedUWord(); + aRegs->r15 = TaggedUWord(); +#else +# error "Unsupported arch" +#endif + + // This is generally useful. + const TaggedUWord inval = TaggedUWord(); + + // First, compute the CFA. + TaggedUWord cfa + = aRS->mCfaExpr.EvaluateExpr(&old_regs, + inval/*old cfa*/, aStackImg, aPfxInstrs); + + // If we didn't manage to compute the CFA, well .. that's ungood, + // but keep going anyway. It'll be OK provided none of the register + // value rules mention the CFA. In any case, compute the new values + // for each register that we're tracking. + +#if defined(LUL_ARCH_x64) || defined(LUL_ARCH_x86) + aRegs->xbp + = aRS->mXbpExpr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs); + aRegs->xsp + = aRS->mXspExpr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs); + aRegs->xip + = aRS->mXipExpr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs); +#elif defined(LUL_ARCH_arm) + aRegs->r7 + = aRS->mR7expr .EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs); + aRegs->r11 + = aRS->mR11expr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs); + aRegs->r12 + = aRS->mR12expr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs); + aRegs->r13 + = aRS->mR13expr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs); + aRegs->r14 + = aRS->mR14expr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs); + aRegs->r15 + = aRS->mR15expr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs); +#else +# error "Unsupported arch" +#endif + + // We're done. Any regs for which we didn't manage to compute a + // new value will now be marked as invalid. +} + +// RUNS IN NO-MALLOC CONTEXT +void +LUL::Unwind(/*OUT*/uintptr_t* aFramePCs, + /*OUT*/uintptr_t* aFrameSPs, + /*OUT*/size_t* aFramesUsed, + /*OUT*/size_t* aScannedFramesAcquired, + size_t aFramesAvail, + size_t aScannedFramesAllowed, + UnwindRegs* aStartRegs, StackImage* aStackImg) +{ + MOZ_ASSERT(!mAdminMode); + + ///////////////////////////////////////////////////////// + // BEGIN UNWIND + + *aFramesUsed = 0; + + UnwindRegs regs = *aStartRegs; + TaggedUWord last_valid_sp = TaggedUWord(); + + // Stack-scan control + unsigned int n_scanned_frames = 0; // # s-s frames recovered so far + static const int NUM_SCANNED_WORDS = 50; // max allowed scan length + + while (true) { + + if (DEBUG_MAIN) { + char buf[300]; + mLog("\n"); +#if defined(LUL_ARCH_x64) || defined(LUL_ARCH_x86) + snprintf_literal(buf, + "LoopTop: rip %d/%llx rsp %d/%llx rbp %d/%llx\n", + (int)regs.xip.Valid(), (unsigned long long int)regs.xip.Value(), + (int)regs.xsp.Valid(), (unsigned long long int)regs.xsp.Value(), + (int)regs.xbp.Valid(), (unsigned long long int)regs.xbp.Value()); + buf[sizeof(buf)-1] = 0; + mLog(buf); +#elif defined(LUL_ARCH_arm) + snprintf_literal(buf, + "LoopTop: r15 %d/%llx r7 %d/%llx r11 %d/%llx" + " r12 %d/%llx r13 %d/%llx r14 %d/%llx\n", + (int)regs.r15.Valid(), (unsigned long long int)regs.r15.Value(), + (int)regs.r7.Valid(), (unsigned long long int)regs.r7.Value(), + (int)regs.r11.Valid(), (unsigned long long int)regs.r11.Value(), + (int)regs.r12.Valid(), (unsigned long long int)regs.r12.Value(), + (int)regs.r13.Valid(), (unsigned long long int)regs.r13.Value(), + (int)regs.r14.Valid(), (unsigned long long int)regs.r14.Value()); + buf[sizeof(buf)-1] = 0; + mLog(buf); +#else +# error "Unsupported arch" +#endif + } + +#if defined(LUL_ARCH_x64) || defined(LUL_ARCH_x86) + TaggedUWord ia = regs.xip; + TaggedUWord sp = regs.xsp; +#elif defined(LUL_ARCH_arm) + TaggedUWord ia = (*aFramesUsed == 0 ? regs.r15 : regs.r14); + TaggedUWord sp = regs.r13; +#else +# error "Unsupported arch" +#endif + + if (*aFramesUsed >= aFramesAvail) { + break; + } + + // If we don't have a valid value for the PC, give up. + if (!ia.Valid()) { + break; + } + + // If this is the innermost frame, record the SP value, which + // presumably is valid. If this isn't the innermost frame, and we + // have a valid SP value, check that its SP value isn't less that + // the one we've seen so far, so as to catch potential SP value + // cycles. + if (*aFramesUsed == 0) { + last_valid_sp = sp; + } else { + MOZ_ASSERT(last_valid_sp.Valid()); + if (sp.Valid()) { + if (sp.Value() < last_valid_sp.Value()) { + // Hmm, SP going in the wrong direction. Let's stop. + break; + } + // Remember where we got to. + last_valid_sp = sp; + } + } + + // For the innermost frame, the IA value is what we need. For all + // other frames, it's actually the return address, so back up one + // byte so as to get it into the calling instruction. + aFramePCs[*aFramesUsed] = ia.Value() - (*aFramesUsed == 0 ? 0 : 1); + aFrameSPs[*aFramesUsed] = sp.Valid() ? sp.Value() : 0; + (*aFramesUsed)++; + + // Find the RuleSet for the current IA, if any. This will also + // query the backing (secondary) maps if it isn't found in the + // thread-local cache. + + // If this isn't the innermost frame, back up into the calling insn. + if (*aFramesUsed > 1) { + ia = ia + TaggedUWord((uintptr_t)(-1)); + } + + pair*> ruleset_and_pfxinstrs + = mPriMap->Lookup(ia.Value()); + const RuleSet* ruleset = ruleset_and_pfxinstrs.first; + const vector* pfxinstrs = ruleset_and_pfxinstrs.second; + + if (DEBUG_MAIN) { + char buf[100]; + snprintf_literal(buf, "ruleset for 0x%llx = %p\n", + (unsigned long long int)ia.Value(), ruleset); + buf[sizeof(buf)-1] = 0; + mLog(buf); + } + + ///////////////////////////////////////////// + //// + // On 32 bit x86-linux, syscalls are often done via the VDSO + // function __kernel_vsyscall, which doesn't have a corresponding + // object that we can read debuginfo from. That effectively kills + // off all stack traces for threads blocked in syscalls. Hence + // special-case by looking at the code surrounding the program + // counter. + // + // 0xf7757420 <__kernel_vsyscall+0>: push %ecx + // 0xf7757421 <__kernel_vsyscall+1>: push %edx + // 0xf7757422 <__kernel_vsyscall+2>: push %ebp + // 0xf7757423 <__kernel_vsyscall+3>: mov %esp,%ebp + // 0xf7757425 <__kernel_vsyscall+5>: sysenter + // 0xf7757427 <__kernel_vsyscall+7>: nop + // 0xf7757428 <__kernel_vsyscall+8>: nop + // 0xf7757429 <__kernel_vsyscall+9>: nop + // 0xf775742a <__kernel_vsyscall+10>: nop + // 0xf775742b <__kernel_vsyscall+11>: nop + // 0xf775742c <__kernel_vsyscall+12>: nop + // 0xf775742d <__kernel_vsyscall+13>: nop + // 0xf775742e <__kernel_vsyscall+14>: int $0x80 + // 0xf7757430 <__kernel_vsyscall+16>: pop %ebp + // 0xf7757431 <__kernel_vsyscall+17>: pop %edx + // 0xf7757432 <__kernel_vsyscall+18>: pop %ecx + // 0xf7757433 <__kernel_vsyscall+19>: ret + // + // In cases where the sampled thread is blocked in a syscall, its + // program counter will point at "pop %ebp". Hence we look for + // the sequence "int $0x80; pop %ebp; pop %edx; pop %ecx; ret", and + // the corresponding register-recovery actions are: + // new_ebp = *(old_esp + 0) + // new eip = *(old_esp + 12) + // new_esp = old_esp + 16 + // + // It may also be the case that the program counter points two + // nops before the "int $0x80", viz, is __kernel_vsyscall+12, in + // the case where the syscall has been restarted but the thread + // hasn't been rescheduled. The code below doesn't handle that; + // it could easily be made to. + // +#if defined(LUL_PLAT_x86_android) || defined(LUL_PLAT_x86_linux) + if (!ruleset && *aFramesUsed == 1 && ia.Valid() && sp.Valid()) { + uintptr_t insns_min, insns_max; + uintptr_t eip = ia.Value(); + bool b = mSegArray->getBoundingCodeSegment(&insns_min, &insns_max, eip); + if (b && eip - 2 >= insns_min && eip + 3 <= insns_max) { + uint8_t* eipC = (uint8_t*)eip; + if (eipC[-2] == 0xCD && eipC[-1] == 0x80 && eipC[0] == 0x5D && + eipC[1] == 0x5A && eipC[2] == 0x59 && eipC[3] == 0xC3) { + TaggedUWord sp_plus_0 = sp; + TaggedUWord sp_plus_12 = sp; + TaggedUWord sp_plus_16 = sp; + sp_plus_12 = sp_plus_12 + TaggedUWord(12); + sp_plus_16 = sp_plus_16 + TaggedUWord(16); + TaggedUWord new_ebp = DerefTUW(sp_plus_0, aStackImg); + TaggedUWord new_eip = DerefTUW(sp_plus_12, aStackImg); + TaggedUWord new_esp = sp_plus_16; + if (new_ebp.Valid() && new_eip.Valid() && new_esp.Valid()) { + regs.xbp = new_ebp; + regs.xip = new_eip; + regs.xsp = new_esp; + continue; + } + } + } + } +#endif + //// + ///////////////////////////////////////////// + + // So, do we have a ruleset for this address? If so, use it now. + if (ruleset) { + + if (DEBUG_MAIN) { + ruleset->Print(mLog); mLog("\n"); + } + // Use the RuleSet to compute the registers for the previous + // frame. |regs| is modified in-place. + UseRuleSet(®s, aStackImg, ruleset, pfxinstrs); + + } else { + + // There's no RuleSet for the specified address, so see if + // it's possible to get anywhere by stack-scanning. + + // Use stack scanning frugally. + if (n_scanned_frames++ >= aScannedFramesAllowed) { + break; + } + + // We can't scan the stack without a valid, aligned stack pointer. + if (!sp.IsAligned()) { + break; + } + + bool scan_succeeded = false; + for (int i = 0; i < NUM_SCANNED_WORDS; ++i) { + TaggedUWord aWord = DerefTUW(sp, aStackImg); + // aWord is something we fished off the stack. It should be + // valid, unless we overran the stack bounds. + if (!aWord.Valid()) { + break; + } + + // Now, does aWord point inside a text section and immediately + // after something that looks like a call instruction? + if (mPriMap->MaybeIsReturnPoint(aWord, mSegArray)) { + // Yes it does. Update the unwound registers heuristically, + // using the same schemes as Breakpad does. + scan_succeeded = true; + (*aScannedFramesAcquired)++; + +#if defined(LUL_ARCH_x64) || defined(LUL_ARCH_x86) + // The same logic applies for the 32- and 64-bit cases. + // Register names of the form xsp etc refer to (eg) esp in + // the 32-bit case and rsp in the 64-bit case. +# if defined(LUL_ARCH_x64) + const int wordSize = 8; +# else + const int wordSize = 4; +# endif + // The return address -- at XSP -- will have been pushed by + // the CALL instruction. So the caller's XSP value + // immediately before and after that CALL instruction is the + // word above XSP. + regs.xsp = sp + TaggedUWord(wordSize); + + // aWord points at the return point, so back up one byte + // to put it in the calling instruction. + regs.xip = aWord + TaggedUWord((uintptr_t)(-1)); + + // Computing a new value from the frame pointer is more tricky. + if (regs.xbp.Valid() && + sp.Valid() && regs.xbp.Value() == sp.Value() - wordSize) { + // One possibility is that the callee begins with the standard + // preamble "push %xbp; mov %xsp, %xbp". In which case, the + // (1) caller's XBP value will be at the word below XSP, and + // (2) the current (callee's) XBP will point at that word: + regs.xbp = DerefTUW(regs.xbp, aStackImg); + } else if (regs.xbp.Valid() && + sp.Valid() && regs.xbp.Value() >= sp.Value() + wordSize) { + // If that didn't work out, maybe the callee didn't change + // XBP, so it still holds the caller's value. For that to + // be plausible, XBP will need to have a value at least + // higher than XSP since that holds the purported return + // address. In which case do nothing, since XBP already + // holds the "right" value. + } else { + // Mark XBP as invalid, so that subsequent unwind iterations + // don't assume it holds valid data. + regs.xbp = TaggedUWord(); + } + + // Move on to the next word up the stack + sp = sp + TaggedUWord(wordSize); + +#elif defined(LUL_ARCH_arm) + // Set all registers to be undefined, except for SP(R13) and + // PC(R15). + + // aWord points either at the return point, if returning to + // ARM code, or one insn past the return point if returning + // to Thumb code. In both cases, aWord-2 is guaranteed to + // fall within the calling instruction. + regs.r15 = aWord + TaggedUWord((uintptr_t)(-2)); + + // Make SP be the word above the location where the return + // address was found. + regs.r13 = sp + TaggedUWord(4); + + // All other regs are undefined. + regs.r7 = regs.r11 = regs.r12 = regs.r14 = TaggedUWord(); + + // Move on to the next word up the stack + sp = sp + TaggedUWord(4); + +#else +# error "Unknown plat" +#endif + + break; + } + + } // for (int i = 0; i < NUM_SCANNED_WORDS; i++) + + // We tried to make progress by scanning the stack, but failed. + // So give up -- fall out of the top level unwind loop. + if (!scan_succeeded) { + break; + } + } + + } // top level unwind loop + + // END UNWIND + ///////////////////////////////////////////////////////// +} + + +//////////////////////////////////////////////////////////////// +// LUL Unit Testing // +//////////////////////////////////////////////////////////////// + +static const int LUL_UNIT_TEST_STACK_SIZE = 16384; + +// This function is innermost in the test call sequence. It uses LUL +// to unwind, and compares the result with the sequence specified in +// the director string. These need to agree in order for the test to +// pass. In order not to screw up the results, this function needs +// to have a not-very big stack frame, since we're only presenting +// the innermost LUL_UNIT_TEST_STACK_SIZE bytes of stack to LUL, and +// that chunk unavoidably includes the frame for this function. +// +// This function must not be inlined into its callers. Doing so will +// cause the expected-vs-actual backtrace consistency checking to +// fail. Prints summary results to |aLUL|'s logging sink and also +// returns a boolean indicating whether or not the test passed. +static __attribute__((noinline)) +bool GetAndCheckStackTrace(LUL* aLUL, const char* dstring) +{ + // Get hold of the current unwind-start registers. + UnwindRegs startRegs; + memset(&startRegs, 0, sizeof(startRegs)); +#if defined(LUL_PLAT_x64_linux) + volatile uintptr_t block[3]; + MOZ_ASSERT(sizeof(block) == 24); + __asm__ __volatile__( + "leaq 0(%%rip), %%r15" "\n\t" + "movq %%r15, 0(%0)" "\n\t" + "movq %%rsp, 8(%0)" "\n\t" + "movq %%rbp, 16(%0)" "\n" + : : "r"(&block[0]) : "memory", "r15" + ); + startRegs.xip = TaggedUWord(block[0]); + startRegs.xsp = TaggedUWord(block[1]); + startRegs.xbp = TaggedUWord(block[2]); + const uintptr_t REDZONE_SIZE = 128; + uintptr_t start = block[1] - REDZONE_SIZE; +#elif defined(LUL_PLAT_x86_linux) || defined(LUL_PLAT_x86_android) + volatile uintptr_t block[3]; + MOZ_ASSERT(sizeof(block) == 12); + __asm__ __volatile__( + ".byte 0xE8,0x00,0x00,0x00,0x00"/*call next insn*/ "\n\t" + "popl %%edi" "\n\t" + "movl %%edi, 0(%0)" "\n\t" + "movl %%esp, 4(%0)" "\n\t" + "movl %%ebp, 8(%0)" "\n" + : : "r"(&block[0]) : "memory", "edi" + ); + startRegs.xip = TaggedUWord(block[0]); + startRegs.xsp = TaggedUWord(block[1]); + startRegs.xbp = TaggedUWord(block[2]); + const uintptr_t REDZONE_SIZE = 0; + uintptr_t start = block[1] - REDZONE_SIZE; +#elif defined(LUL_PLAT_arm_android) + volatile uintptr_t block[6]; + MOZ_ASSERT(sizeof(block) == 24); + __asm__ __volatile__( + "mov r0, r15" "\n\t" + "str r0, [%0, #0]" "\n\t" + "str r14, [%0, #4]" "\n\t" + "str r13, [%0, #8]" "\n\t" + "str r12, [%0, #12]" "\n\t" + "str r11, [%0, #16]" "\n\t" + "str r7, [%0, #20]" "\n" + : : "r"(&block[0]) : "memory", "r0" + ); + startRegs.r15 = TaggedUWord(block[0]); + startRegs.r14 = TaggedUWord(block[1]); + startRegs.r13 = TaggedUWord(block[2]); + startRegs.r12 = TaggedUWord(block[3]); + startRegs.r11 = TaggedUWord(block[4]); + startRegs.r7 = TaggedUWord(block[5]); + const uintptr_t REDZONE_SIZE = 0; + uintptr_t start = block[1] - REDZONE_SIZE; +#else +# error "Unsupported platform" +#endif + + // Get hold of the innermost LUL_UNIT_TEST_STACK_SIZE bytes of the + // stack. + uintptr_t end = start + LUL_UNIT_TEST_STACK_SIZE; + uintptr_t ws = sizeof(void*); + start &= ~(ws-1); + end &= ~(ws-1); + uintptr_t nToCopy = end - start; + if (nToCopy > lul::N_STACK_BYTES) { + nToCopy = lul::N_STACK_BYTES; + } + MOZ_ASSERT(nToCopy <= lul::N_STACK_BYTES); + StackImage* stackImg = new StackImage(); + stackImg->mLen = nToCopy; + stackImg->mStartAvma = start; + if (nToCopy > 0) { + MOZ_MAKE_MEM_DEFINED((void*)start, nToCopy); + memcpy(&stackImg->mContents[0], (void*)start, nToCopy); + } + + // Unwind it. + const int MAX_TEST_FRAMES = 64; + uintptr_t framePCs[MAX_TEST_FRAMES]; + uintptr_t frameSPs[MAX_TEST_FRAMES]; + size_t framesAvail = mozilla::ArrayLength(framePCs); + size_t framesUsed = 0; + size_t scannedFramesAllowed = 0; + size_t scannedFramesAcquired = 0; + aLUL->Unwind( &framePCs[0], &frameSPs[0], + &framesUsed, &scannedFramesAcquired, + framesAvail, scannedFramesAllowed, + &startRegs, stackImg ); + + delete stackImg; + + //if (0) { + // // Show what we have. + // fprintf(stderr, "Got %d frames:\n", (int)framesUsed); + // for (size_t i = 0; i < framesUsed; i++) { + // fprintf(stderr, " [%2d] SP %p PC %p\n", + // (int)i, (void*)frameSPs[i], (void*)framePCs[i]); + // } + // fprintf(stderr, "\n"); + //} + + // Check to see if there's a consistent binding between digits in + // the director string ('1' .. '8') and the PC values acquired by + // the unwind. If there isn't, the unwinding has failed somehow. + uintptr_t binding[8]; // binding for '1' .. binding for '8' + memset((void*)binding, 0, sizeof(binding)); + + // The general plan is to work backwards along the director string + // and forwards along the framePCs array. Doing so corresponds to + // working outwards from the innermost frame of the recursive test set. + const char* cursor = dstring; + + // Find the end. This leaves |cursor| two bytes past the first + // character we want to look at -- see comment below. + while (*cursor) cursor++; + + // Counts the number of consistent frames. + size_t nConsistent = 0; + + // Iterate back to the start of the director string. The starting + // points are a bit complex. We can't use framePCs[0] because that + // contains the PC in this frame (above). We can't use framePCs[1] + // because that will contain the PC at return point in the recursive + // test group (TestFn[1-8]) for their call "out" to this function, + // GetAndCheckStackTrace. Although LUL will compute a correct + // return address, that will not be the same return address as for a + // recursive call out of the the function to another function in the + // group. Hence we can only start consistency checking at + // framePCs[2]. + // + // To be consistent, then, we must ignore the last element in the + // director string as that corresponds to framePCs[1]. Hence the + // start points are: framePCs[2] and the director string 2 bytes + // before the terminating zero. + // + // Also as a result of this, the number of consistent frames counted + // will always be one less than the length of the director string + // (not including its terminating zero). + size_t frameIx; + for (cursor = cursor-2, frameIx = 2; + cursor >= dstring && frameIx < framesUsed; + cursor--, frameIx++) { + char c = *cursor; + uintptr_t pc = framePCs[frameIx]; + // If this doesn't hold, the director string is ill-formed. + MOZ_ASSERT(c >= '1' && c <= '8'); + int n = ((int)c) - ((int)'1'); + if (binding[n] == 0) { + // There's no binding for |c| yet, so install |pc| and carry on. + binding[n] = pc; + nConsistent++; + continue; + } + // There's a pre-existing binding for |c|. Check it's consistent. + if (binding[n] != pc) { + // Not consistent. Give up now. + break; + } + // Consistent. Keep going. + nConsistent++; + } + + // So, did we succeed? + bool passed = nConsistent+1 == strlen(dstring); + + // Show the results. + char buf[200]; + snprintf_literal(buf, "LULUnitTest: dstring = %s\n", dstring); + buf[sizeof(buf)-1] = 0; + aLUL->mLog(buf); + snprintf_literal(buf, + "LULUnitTest: %d consistent, %d in dstring: %s\n", + (int)nConsistent, (int)strlen(dstring), + passed ? "PASS" : "FAIL"); + buf[sizeof(buf)-1] = 0; + aLUL->mLog(buf); + + return passed; +} + + +// Macro magic to create a set of 8 mutually recursive functions with +// varying frame sizes. These will recurse amongst themselves as +// specified by |strP|, the directory string, and call +// GetAndCheckStackTrace when the string becomes empty, passing it the +// original value of the string. This checks the result, printing +// results on |aLUL|'s logging sink, and also returns a boolean +// indicating whether or not the results are acceptable (correct). + +#define DECL_TEST_FN(NAME) \ + bool NAME(LUL* aLUL, const char* strPorig, const char* strP); + +#define GEN_TEST_FN(NAME, FRAMESIZE) \ + bool NAME(LUL* aLUL, const char* strPorig, const char* strP) { \ + volatile char space[FRAMESIZE]; \ + memset((char*)&space[0], 0, sizeof(space)); \ + if (*strP == '\0') { \ + /* We've come to the end of the director string. */ \ + /* Take a stack snapshot. */ \ + return GetAndCheckStackTrace(aLUL, strPorig); \ + } else { \ + /* Recurse onwards. This is a bit subtle. The obvious */ \ + /* thing to do here is call onwards directly, from within the */ \ + /* arms of the case statement. That gives a problem in that */ \ + /* there will be multiple return points inside each function when */ \ + /* unwinding, so it will be difficult to check for consistency */ \ + /* against the director string. Instead, we make an indirect */ \ + /* call, so as to guarantee that there is only one call site */ \ + /* within each function. This does assume that the compiler */ \ + /* won't transform it back to the simple direct-call form. */ \ + /* To discourage it from doing so, the call is bracketed with */ \ + /* __asm__ __volatile__ sections so as to make it not-movable. */ \ + bool (*nextFn)(LUL*, const char*, const char*) = NULL; \ + switch (*strP) { \ + case '1': nextFn = TestFn1; break; \ + case '2': nextFn = TestFn2; break; \ + case '3': nextFn = TestFn3; break; \ + case '4': nextFn = TestFn4; break; \ + case '5': nextFn = TestFn5; break; \ + case '6': nextFn = TestFn6; break; \ + case '7': nextFn = TestFn7; break; \ + case '8': nextFn = TestFn8; break; \ + default: nextFn = TestFn8; break; \ + } \ + __asm__ __volatile__("":::"cc","memory"); \ + bool passed = nextFn(aLUL, strPorig, strP+1); \ + __asm__ __volatile__("":::"cc","memory"); \ + return passed; \ + } \ + } + +// The test functions are mutually recursive, so it is necessary to +// declare them before defining them. +DECL_TEST_FN(TestFn1) +DECL_TEST_FN(TestFn2) +DECL_TEST_FN(TestFn3) +DECL_TEST_FN(TestFn4) +DECL_TEST_FN(TestFn5) +DECL_TEST_FN(TestFn6) +DECL_TEST_FN(TestFn7) +DECL_TEST_FN(TestFn8) + +GEN_TEST_FN(TestFn1, 123) +GEN_TEST_FN(TestFn2, 456) +GEN_TEST_FN(TestFn3, 789) +GEN_TEST_FN(TestFn4, 23) +GEN_TEST_FN(TestFn5, 47) +GEN_TEST_FN(TestFn6, 117) +GEN_TEST_FN(TestFn7, 1) +GEN_TEST_FN(TestFn8, 99) + + +// This starts the test sequence going. Call here to generate a +// sequence of calls as directed by the string |dstring|. The call +// sequence will, from its innermost frame, finish by calling +// GetAndCheckStackTrace() and passing it |dstring|. +// GetAndCheckStackTrace() will unwind the stack, check consistency +// of those results against |dstring|, and print a pass/fail message +// to aLUL's logging sink. It also updates the counters in *aNTests +// and aNTestsPassed. +__attribute__((noinline)) void +TestUnw(/*OUT*/int* aNTests, /*OUT*/int*aNTestsPassed, + LUL* aLUL, const char* dstring) +{ + // Ensure that the stack has at least this much space on it. This + // makes it safe to saw off the top LUL_UNIT_TEST_STACK_SIZE bytes + // and hand it to LUL. Safe in the sense that no segfault can + // happen because the stack is at least this big. This is all + // somewhat dubious in the sense that a sufficiently clever compiler + // (clang, for one) can figure out that space[] is unused and delete + // it from the frame. Hence the somewhat elaborate hoop jumping to + // fill it up before the call and to at least appear to use the + // value afterwards. + int i; + volatile char space[LUL_UNIT_TEST_STACK_SIZE]; + for (i = 0; i < LUL_UNIT_TEST_STACK_SIZE; i++) { + space[i] = (char)(i & 0x7F); + } + + // Really run the test. + bool passed = TestFn1(aLUL, dstring, dstring); + + // Appear to use space[], by visiting the value to compute some kind + // of checksum, and then (apparently) using the checksum. + int sum = 0; + for (i = 0; i < LUL_UNIT_TEST_STACK_SIZE; i++) { + // If this doesn't fool LLVM, I don't know what will. + sum += space[i] - 3*i; + } + __asm__ __volatile__("" : : "r"(sum)); + + // Update the counters. + (*aNTests)++; + if (passed) { + (*aNTestsPassed)++; + } +} + + +void +RunLulUnitTests(/*OUT*/int* aNTests, /*OUT*/int*aNTestsPassed, LUL* aLUL) +{ + aLUL->mLog(":\n"); + aLUL->mLog("LULUnitTest: BEGIN\n"); + *aNTests = *aNTestsPassed = 0; + TestUnw(aNTests, aNTestsPassed, aLUL, "11111111"); + TestUnw(aNTests, aNTestsPassed, aLUL, "11222211"); + TestUnw(aNTests, aNTestsPassed, aLUL, "111222333"); + TestUnw(aNTests, aNTestsPassed, aLUL, "1212121231212331212121212121212"); + TestUnw(aNTests, aNTestsPassed, aLUL, "31415827271828325332173258"); + TestUnw(aNTests, aNTestsPassed, aLUL, + "123456781122334455667788777777777777777777777"); + aLUL->mLog("LULUnitTest: END\n"); + aLUL->mLog(":\n"); +} + + +} // namespace lul diff --git a/tools/profiler/lul/LulMain.h b/tools/profiler/lul/LulMain.h new file mode 100644 index 0000000000..0916d1b269 --- /dev/null +++ b/tools/profiler/lul/LulMain.h @@ -0,0 +1,397 @@ +/* -*- 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 LulMain_h +#define LulMain_h + +#include "LulPlatformMacros.h" +#include "mozilla/Atomics.h" + +// LUL: A Lightweight Unwind Library. +// This file provides the end-user (external) interface for LUL. + +// Some comments about naming in the implementation. These are safe +// to ignore if you are merely using LUL, but are important if you +// hack on its internals. +// +// Debuginfo readers in general have tended to use the word "address" +// to mean several different things. This sometimes makes them +// difficult to understand and maintain. LUL tries hard to avoid +// using the word "address" and instead uses the following more +// precise terms: +// +// * SVMA ("Stated Virtual Memory Address"): this is an address of a +// symbol (etc) as it is stated in the symbol table, or other +// metadata, of an object. Such values are typically small and +// start from zero or thereabouts, unless the object has been +// prelinked. +// +// * AVMA ("Actual Virtual Memory Address"): this is the address of a +// symbol (etc) in a running process, that is, once the associated +// object has been mapped into a process. Such values are typically +// much larger than SVMAs, since objects can get mapped arbitrarily +// far along the address space. +// +// * "Bias": the difference between AVMA and SVMA for a given symbol +// (specifically, AVMA - SVMA). The bias is always an integral +// number of pages. Once we know the bias for a given object's +// text section (for example), we can compute the AVMAs of all of +// its text symbols by adding the bias to their SVMAs. +// +// * "Image address": typically, to read debuginfo from an object we +// will temporarily mmap in the file so as to read symbol tables +// etc. Addresses in this temporary mapping are called "Image +// addresses". Note that the temporary mapping is entirely +// unrelated to the mappings of the file that the dynamic linker +// must perform merely in order to get the program to run. Hence +// image addresses are unrelated to either SVMAs or AVMAs. + + +namespace lul { + +// A machine word plus validity tag. +class TaggedUWord { +public: + // RUNS IN NO-MALLOC CONTEXT + // Construct a valid one. + explicit TaggedUWord(uintptr_t w) + : mValue(w) + , mValid(true) + {} + + // RUNS IN NO-MALLOC CONTEXT + // Construct an invalid one. + TaggedUWord() + : mValue(0) + , mValid(false) + {} + + // RUNS IN NO-MALLOC CONTEXT + TaggedUWord operator+(TaggedUWord rhs) const { + return (Valid() && rhs.Valid()) ? TaggedUWord(Value() + rhs.Value()) + : TaggedUWord(); + } + + // RUNS IN NO-MALLOC CONTEXT + TaggedUWord operator-(TaggedUWord rhs) const { + return (Valid() && rhs.Valid()) ? TaggedUWord(Value() - rhs.Value()) + : TaggedUWord(); + } + + // RUNS IN NO-MALLOC CONTEXT + TaggedUWord operator&(TaggedUWord rhs) const { + return (Valid() && rhs.Valid()) ? TaggedUWord(Value() & rhs.Value()) + : TaggedUWord(); + } + + // RUNS IN NO-MALLOC CONTEXT + TaggedUWord operator|(TaggedUWord rhs) const { + return (Valid() && rhs.Valid()) ? TaggedUWord(Value() | rhs.Value()) + : TaggedUWord(); + } + + // RUNS IN NO-MALLOC CONTEXT + TaggedUWord CmpGEs(TaggedUWord rhs) const { + if (Valid() && rhs.Valid()) { + intptr_t s1 = (intptr_t)Value(); + intptr_t s2 = (intptr_t)rhs.Value(); + return TaggedUWord(s1 >= s2 ? 1 : 0); + } + return TaggedUWord(); + } + + // RUNS IN NO-MALLOC CONTEXT + TaggedUWord operator<<(TaggedUWord rhs) const { + if (Valid() && rhs.Valid()) { + uintptr_t shift = rhs.Value(); + if (shift < 8 * sizeof(uintptr_t)) + return TaggedUWord(Value() << shift); + } + return TaggedUWord(); + } + + // RUNS IN NO-MALLOC CONTEXT + // Is equal? Note: non-validity on either side gives non-equality. + bool operator==(TaggedUWord other) const { + return (mValid && other.Valid()) ? (mValue == other.Value()) : false; + } + + // RUNS IN NO-MALLOC CONTEXT + // Is it word-aligned? + bool IsAligned() const { + return mValid && (mValue & (sizeof(uintptr_t)-1)) == 0; + } + + // RUNS IN NO-MALLOC CONTEXT + uintptr_t Value() const { return mValue; } + + // RUNS IN NO-MALLOC CONTEXT + bool Valid() const { return mValid; } + +private: + uintptr_t mValue; + bool mValid; +}; + + +// The registers, with validity tags, that will be unwound. + +struct UnwindRegs { +#if defined(LUL_ARCH_arm) + TaggedUWord r7; + TaggedUWord r11; + TaggedUWord r12; + TaggedUWord r13; + TaggedUWord r14; + TaggedUWord r15; +#elif defined(LUL_ARCH_x64) || defined(LUL_ARCH_x86) + TaggedUWord xbp; + TaggedUWord xsp; + TaggedUWord xip; +#else +# error "Unknown plat" +#endif +}; + + +// The maximum number of bytes in a stack snapshot. This can be +// increased if necessary, but larger values cost performance, since a +// stack snapshot needs to be copied between sampling and worker +// threads for each snapshot. In practice 32k seems to be enough +// to get good backtraces. +static const size_t N_STACK_BYTES = 32768; + +// The stack chunk image that will be unwound. +struct StackImage { + // [start_avma, +len) specify the address range in the buffer. + // Obviously we require 0 <= len <= N_STACK_BYTES. + uintptr_t mStartAvma; + size_t mLen; + uint8_t mContents[N_STACK_BYTES]; +}; + + +// Statistics collection for the unwinder. +template +class LULStats { +public: + LULStats() + : mContext(0) + , mCFI(0) + , mScanned(0) + {} + + template + explicit LULStats(const LULStats& aOther) + : mContext(aOther.mContext) + , mCFI(aOther.mCFI) + , mScanned(aOther.mScanned) + {} + + template + LULStats& operator=(const LULStats& aOther) + { + mContext = aOther.mContext; + mCFI = aOther.mCFI; + mScanned = aOther.mScanned; + return *this; + } + + template + uint32_t operator-(const LULStats& aOther) { + return (mContext - aOther.mContext) + + (mCFI - aOther.mCFI) + (mScanned - aOther.mScanned); + } + + T mContext; // Number of context frames + T mCFI; // Number of CFI/EXIDX frames + T mScanned; // Number of scanned frames +}; + + +// The core unwinder library class. Just one of these is needed, and +// it can be shared by multiple unwinder threads. +// +// The library operates in one of two modes. +// +// * Admin mode. The library is this state after creation. In Admin +// mode, no unwinding may be performed. It is however allowable to +// perform administrative tasks -- primarily, loading of unwind info +// -- in this mode. In particular, it is safe for the library to +// perform dynamic memory allocation in this mode. Safe in the +// sense that there is no risk of deadlock against unwinding threads +// that might -- because of where they have been sampled -- hold the +// system's malloc lock. +// +// * Unwind mode. In this mode, calls to ::Unwind may be made, but +// nothing else. ::Unwind guarantees not to make any dynamic memory +// requests, so as to guarantee that the calling thread won't +// deadlock in the case where it already holds the system's malloc lock. +// +// The library is created in Admin mode. After debuginfo is loaded, +// the caller must switch it into Unwind mode by calling +// ::EnableUnwinding. There is no way to switch it back to Admin mode +// after that. To safely switch back to Admin mode would require the +// caller (or other external agent) to guarantee that there are no +// pending ::Unwind calls. + +class PriMap; +class SegArray; +class UniqueStringUniverse; + +class LUL { +public: + // Create; supply a logging sink. Sets the object in Admin mode. + explicit LUL(void (*aLog)(const char*)); + + // Destroy. Caller is responsible for ensuring that no other + // threads are in Unwind calls. All resources are freed and all + // registered unwinder threads are deregistered. Can be called + // either in Admin or Unwind mode. + ~LUL(); + + // Notify the library that unwinding is now allowed and so + // admin-mode calls are no longer allowed. The object is initially + // created in admin mode. The only possible transition is + // admin->unwinding, therefore. + void EnableUnwinding(); + + // Notify of a new r-x mapping, and load the associated unwind info. + // The filename is strdup'd and used for debug printing. If + // aMappedImage is NULL, this function will mmap/munmap the file + // itself, so as to be able to read the unwind info. If + // aMappedImage is non-NULL then it is assumed to point to a + // called-supplied and caller-managed mapped image of the file. + // May only be called in Admin mode. + void NotifyAfterMap(uintptr_t aRXavma, size_t aSize, + const char* aFileName, const void* aMappedImage); + + // In rare cases we know an executable area exists but don't know + // what the associated file is. This call notifies LUL of such + // areas. This is important for correct functioning of stack + // scanning and of the x86-{linux,android} special-case + // __kernel_syscall function handling. + // This must be called only after the code area in + // question really has been mapped. + // May only be called in Admin mode. + void NotifyExecutableArea(uintptr_t aRXavma, size_t aSize); + + // Notify that a mapped area has been unmapped; discard any + // associated unwind info. Acquires mRWlock for writing. Note that + // to avoid segfaulting the stack-scan unwinder, which inspects code + // areas, this must be called before the code area in question is + // really unmapped. Note that, unlike NotifyAfterMap(), this + // function takes the start and end addresses of the range to be + // unmapped, rather than a start and a length parameter. This is so + // as to make it possible to notify an unmap for the entire address + // space using a single call. + // May only be called in Admin mode. + void NotifyBeforeUnmap(uintptr_t aAvmaMin, uintptr_t aAvmaMax); + + // Apply NotifyBeforeUnmap to the entire address space. This causes + // LUL to discard all unwind and executable-area information for the + // entire address space. + // May only be called in Admin mode. + void NotifyBeforeUnmapAll() { + NotifyBeforeUnmap(0, UINTPTR_MAX); + } + + // Returns the number of mappings currently registered. + // May only be called in Admin mode. + size_t CountMappings(); + + // Unwind |aStackImg| starting with the context in |aStartRegs|. + // Write the number of frames recovered in *aFramesUsed. Put + // the PC values in aFramePCs[0 .. *aFramesUsed-1] and + // the SP values in aFrameSPs[0 .. *aFramesUsed-1]. + // |aFramesAvail| is the size of the two output arrays and hence the + // largest possible value of *aFramesUsed. PC values are always + // valid, and the unwind will stop when the PC becomes invalid, but + // the SP values might be invalid, in which case the value zero will + // be written in the relevant frameSPs[] slot. + // + // Unwinding may optionally use stack scanning. The maximum number + // of frames that may be recovered by stack scanning is + // |aScannedFramesAllowed| and the actual number recovered is + // written into *aScannedFramesAcquired. |aScannedFramesAllowed| + // must be less than or equal to |aFramesAvail|. + // + // This function assumes that the SP values increase as it unwinds + // away from the innermost frame -- that is, that the stack grows + // down. It monitors SP values as it unwinds to check they + // decrease, so as to avoid looping on corrupted stacks. + // + // May only be called in Unwind mode. Multiple threads may unwind + // at once. LUL user is responsible for ensuring that no thread makes + // any Admin calls whilst in Unwind mode. + // MOZ_CRASHes if the calling thread is not registered for unwinding. + // + // Up to aScannedFramesAllowed stack-scanned frames may be recovered. + // + // The calling thread must previously have been registered via a call to + // RegisterSampledThread. + void Unwind(/*OUT*/uintptr_t* aFramePCs, + /*OUT*/uintptr_t* aFrameSPs, + /*OUT*/size_t* aFramesUsed, + /*OUT*/size_t* aScannedFramesAcquired, + size_t aFramesAvail, + size_t aScannedFramesAllowed, + UnwindRegs* aStartRegs, StackImage* aStackImg); + + // The logging sink. Call to send debug strings to the caller- + // specified destination. Can only be called by the Admin thread. + void (*mLog)(const char*); + + // Statistics relating to unwinding. These have to be atomic since + // unwinding can occur on different threads simultaneously. + LULStats> mStats; + + // Possibly show the statistics. This may not be called from any + // registered sampling thread, since it involves I/O. + void MaybeShowStats(); + +private: + // The statistics counters at the point where they were last printed. + LULStats mStatsPrevious; + + // Are we in admin mode? Initially |true| but changes to |false| + // once unwinding begins. + bool mAdminMode; + + // The thread ID associated with admin mode. This is the only thread + // that is allowed do perform non-Unwind calls on this object. Conversely, + // no registered Unwinding thread may be the admin thread. This is so + // as to clearly partition the one thread that may do dynamic memory + // allocation from the threads that are being sampled, since the latter + // absolutely may not do dynamic memory allocation. + int mAdminThreadId; + + // The top level mapping from code address ranges to postprocessed + // unwind info. Basically a sorted array of (addr, len, info) + // records. This field is updated by NotifyAfterMap and NotifyBeforeUnmap. + PriMap* mPriMap; + + // An auxiliary structure that records which address ranges are + // mapped r-x, for the benefit of the stack scanner. + SegArray* mSegArray; + + // A UniqueStringUniverse that holds all the strdup'd strings created + // whilst reading unwind information. This is included so as to make + // it possible to free them in ~LUL. + UniqueStringUniverse* mUSU; +}; + + +// Run unit tests on an initialised, loaded-up LUL instance, and print +// summary results on |aLUL|'s logging sink. Also return the number +// of tests run in *aNTests and the number that passed in +// *aNTestsPassed. +void +RunLulUnitTests(/*OUT*/int* aNTests, /*OUT*/int*aNTestsPassed, LUL* aLUL); + +} // namespace lul + +#endif // LulMain_h diff --git a/tools/profiler/lul/LulMainInt.h b/tools/profiler/lul/LulMainInt.h new file mode 100644 index 0000000000..54bd76c88a --- /dev/null +++ b/tools/profiler/lul/LulMainInt.h @@ -0,0 +1,393 @@ +/* -*- 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 LulMainInt_h +#define LulMainInt_h + +#include "LulPlatformMacros.h" +#include "LulMain.h" // for TaggedUWord + +#include + +#include "mozilla/Assertions.h" + +// This file is provides internal interface inside LUL. If you are an +// end-user of LUL, do not include it in your code. The end-user +// interface is in LulMain.h. + + +namespace lul { + +using std::vector; + +//////////////////////////////////////////////////////////////// +// DW_REG_ constants // +//////////////////////////////////////////////////////////////// + +// These are the Dwarf CFI register numbers, as (presumably) defined +// in the ELF ABI supplements for each architecture. + +enum DW_REG_NUMBER { + // No real register has this number. It's convenient to be able to + // treat the CFA (Canonical Frame Address) as "just another + // register", though. + DW_REG_CFA = -1, +#if defined(LUL_ARCH_arm) + // ARM registers + DW_REG_ARM_R7 = 7, + DW_REG_ARM_R11 = 11, + DW_REG_ARM_R12 = 12, + DW_REG_ARM_R13 = 13, + DW_REG_ARM_R14 = 14, + DW_REG_ARM_R15 = 15, +#elif defined(LUL_ARCH_x64) + // Because the X86 (32 bit) and AMD64 (64 bit) summarisers are + // combined, a merged set of register constants is needed. + DW_REG_INTEL_XBP = 6, + DW_REG_INTEL_XSP = 7, + DW_REG_INTEL_XIP = 16, +#elif defined(LUL_ARCH_x86) + DW_REG_INTEL_XBP = 5, + DW_REG_INTEL_XSP = 4, + DW_REG_INTEL_XIP = 8, +#else +# error "Unknown arch" +#endif +}; + + +//////////////////////////////////////////////////////////////// +// PfxExpr // +//////////////////////////////////////////////////////////////// + +enum PfxExprOp { + // meaning of mOperand effect on stack + PX_Start, // bool start-with-CFA? start, with CFA on stack, or not + PX_End, // none stop; result is at top of stack + PX_SImm32, // int32 push signed int32 + PX_DwReg, // DW_REG_NUMBER push value of the specified reg + PX_Deref, // none pop X ; push *X + PX_Add, // none pop X ; pop Y ; push Y + X + PX_Sub, // none pop X ; pop Y ; push Y - X + PX_And, // none pop X ; pop Y ; push Y & X + PX_Or, // none pop X ; pop Y ; push Y | X + PX_CmpGES, // none pop X ; pop Y ; push (Y >=s X) ? 1 : 0 + PX_Shl // none pop X ; pop Y ; push Y << X +}; + +struct PfxInstr { + PfxInstr(PfxExprOp opcode, int32_t operand) + : mOpcode(opcode) + , mOperand(operand) + {} + explicit PfxInstr(PfxExprOp opcode) + : mOpcode(opcode) + , mOperand(0) + {} + bool operator==(const PfxInstr& other) { + return mOpcode == other.mOpcode && mOperand == other.mOperand; + } + PfxExprOp mOpcode; + int32_t mOperand; +}; + +static_assert(sizeof(PfxInstr) <= 8, "PfxInstr size changed unexpectedly"); + +// Evaluate the prefix expression whose PfxInstrs start at aPfxInstrs[start]. +// In the case of any mishap (stack over/underflow, running off the end of +// the instruction vector, obviously malformed sequences), +// return an invalid TaggedUWord. +// RUNS IN NO-MALLOC CONTEXT +TaggedUWord EvaluatePfxExpr(int32_t start, + const UnwindRegs* aOldRegs, + TaggedUWord aCFA, const StackImage* aStackImg, + const vector& aPfxInstrs); + + +//////////////////////////////////////////////////////////////// +// LExpr // +//////////////////////////////////////////////////////////////// + +// An expression -- very primitive. Denotes either "register + +// offset", a dereferenced version of the same, or a reference to a +// prefix expression stored elsewhere. So as to allow convenient +// handling of Dwarf-derived unwind info, the register may also denote +// the CFA. A large number of these need to be stored, so we ensure +// it fits into 8 bytes. See comment below on RuleSet to see how +// expressions fit into the bigger picture. + +enum LExprHow { + UNKNOWN=0, // This LExpr denotes no value. + NODEREF, // Value is (mReg + mOffset). + DEREF, // Value is *(mReg + mOffset). + PFXEXPR // Value is EvaluatePfxExpr(secMap->mPfxInstrs[mOffset]) +}; + +inline static const char* NameOf_LExprHow(LExprHow how) { + switch (how) { + case UNKNOWN: return "UNKNOWN"; + case NODEREF: return "NODEREF"; + case DEREF: return "DEREF"; + case PFXEXPR: return "PFXEXPR"; + default: return "LExpr-??"; + } +} + + +struct LExpr { + // Denotes an expression with no value. + LExpr() + : mHow(UNKNOWN) + , mReg(0) + , mOffset(0) + {} + + // Denotes any expressible expression. + LExpr(LExprHow how, int16_t reg, int32_t offset) + : mHow(how) + , mReg(reg) + , mOffset(offset) + { + switch (how) { + case UNKNOWN: MOZ_ASSERT(reg == 0 && offset == 0); break; + case NODEREF: break; + case DEREF: break; + case PFXEXPR: MOZ_ASSERT(reg == 0 && offset >= 0); break; + default: MOZ_ASSERT(0, "LExpr::LExpr: invalid how"); + } + } + + // Change the offset for an expression that references memory. + LExpr add_delta(long delta) + { + MOZ_ASSERT(mHow == NODEREF); + // If this is a non-debug build and the above assertion would have + // failed, at least return LExpr() so that the machinery that uses + // the resulting expression fails in a repeatable way. + return (mHow == NODEREF) ? LExpr(mHow, mReg, mOffset+delta) + : LExpr(); // Gone bad + } + + // Dereference an expression that denotes a memory address. + LExpr deref() + { + MOZ_ASSERT(mHow == NODEREF); + // Same rationale as for add_delta(). + return (mHow == NODEREF) ? LExpr(DEREF, mReg, mOffset) + : LExpr(); // Gone bad + } + + // Print a rule for recovery of |aNewReg| whose recovered value + // is this LExpr. + string ShowRule(const char* aNewReg) const; + + // Evaluate this expression, producing a TaggedUWord. |aOldRegs| + // holds register values that may be referred to by the expression. + // |aCFA| holds the CFA value, if any, that applies. |aStackImg| + // contains a chuck of stack that will be consulted if the expression + // references memory. |aPfxInstrs| holds the vector of PfxInstrs + // that will be consulted if this is a PFXEXPR. + // RUNS IN NO-MALLOC CONTEXT + TaggedUWord EvaluateExpr(const UnwindRegs* aOldRegs, + TaggedUWord aCFA, const StackImage* aStackImg, + const vector* aPfxInstrs) const; + + // Representation of expressions. If |mReg| is DW_REG_CFA (-1) then + // it denotes the CFA. All other allowed values for |mReg| are + // nonnegative and are DW_REG_ values. + LExprHow mHow:8; + int16_t mReg; // A DW_REG_ value + int32_t mOffset; // 32-bit signed offset should be more than enough. +}; + +static_assert(sizeof(LExpr) <= 8, "LExpr size changed unexpectedly"); + + +//////////////////////////////////////////////////////////////// +// RuleSet // +//////////////////////////////////////////////////////////////// + +// This is platform-dependent. For some address range, describes how +// to recover the CFA and then how to recover the registers for the +// previous frame. +// +// The set of LExprs contained in a given RuleSet describe a DAG which +// says how to compute the caller's registers ("new registers") from +// the callee's registers ("old registers"). The DAG can contain a +// single internal node, which is the value of the CFA for the callee. +// It would be possible to construct a DAG that omits the CFA, but +// including it makes the summarisers simpler, and the Dwarf CFI spec +// has the CFA as a central concept. +// +// For this to make sense, |mCfaExpr| can't have +// |mReg| == DW_REG_CFA since we have no previous value for the CFA. +// All of the other |Expr| fields can -- and usually do -- specify +// |mReg| == DW_REG_CFA. +// +// With that in place, the unwind algorithm proceeds as follows. +// +// (0) Initially: we have values for the old registers, and a memory +// image. +// +// (1) Compute the CFA by evaluating |mCfaExpr|. Add the computed +// value to the set of "old registers". +// +// (2) Compute values for the registers by evaluating all of the other +// |Expr| fields in the RuleSet. These can depend on both the old +// register values and the just-computed CFA. +// +// If we are unwinding without computing a CFA, perhaps because the +// RuleSets are derived from EXIDX instead of Dwarf, then +// |mCfaExpr.mHow| will be LExpr::UNKNOWN, so the computed value will +// be invalid -- that is, TaggedUWord() -- and so any attempt to use +// that will result in the same value. But that's OK because the +// RuleSet would make no sense if depended on the CFA but specified no +// way to compute it. +// +// A RuleSet is not allowed to cover zero address range. Having zero +// length would break binary searching in SecMaps and PriMaps. + +class RuleSet { +public: + RuleSet(); + void Print(void(*aLog)(const char*)) const; + + // Find the LExpr* for a given DW_REG_ value in this class. + LExpr* ExprForRegno(DW_REG_NUMBER aRegno); + + uintptr_t mAddr; + uintptr_t mLen; + // How to compute the CFA. + LExpr mCfaExpr; + // How to compute caller register values. These may reference the + // value defined by |mCfaExpr|. +#if defined(LUL_ARCH_x64) || defined(LUL_ARCH_x86) + LExpr mXipExpr; // return address + LExpr mXspExpr; + LExpr mXbpExpr; +#elif defined(LUL_ARCH_arm) + LExpr mR15expr; // return address + LExpr mR14expr; + LExpr mR13expr; + LExpr mR12expr; + LExpr mR11expr; + LExpr mR7expr; +#else +# error "Unknown arch" +#endif +}; + +// Returns |true| for Dwarf register numbers which are members +// of the set of registers that LUL unwinds on this target. +static inline bool registerIsTracked(DW_REG_NUMBER reg) { + switch (reg) { +# if defined(LUL_ARCH_x64) || defined(LUL_ARCH_x86) + case DW_REG_INTEL_XBP: case DW_REG_INTEL_XSP: case DW_REG_INTEL_XIP: + return true; +# elif defined(LUL_ARCH_arm) + case DW_REG_ARM_R7: case DW_REG_ARM_R11: case DW_REG_ARM_R12: + case DW_REG_ARM_R13: case DW_REG_ARM_R14: case DW_REG_ARM_R15: + return true; +# else +# error "Unknown arch" +# endif + default: + return false; + } +} + + +//////////////////////////////////////////////////////////////// +// SecMap // +//////////////////////////////////////////////////////////////// + +// A SecMap may have zero address range, temporarily, whilst RuleSets +// are being added to it. But adding a zero-range SecMap to a PriMap +// will make it impossible to maintain the total order of the PriMap +// entries, and so that can't be allowed to happen. + +class SecMap { +public: + // These summarise the contained mRuleSets, in that they give + // exactly the lowest and highest addresses that any of the entries + // in this SecMap cover. Hence invariants: + // + // mRuleSets is nonempty + // <=> mSummaryMinAddr <= mSummaryMaxAddr + // && mSummaryMinAddr == mRuleSets[0].mAddr + // && mSummaryMaxAddr == mRuleSets[#rulesets-1].mAddr + // + mRuleSets[#rulesets-1].mLen - 1; + // + // This requires that no RuleSet has zero length. + // + // mRuleSets is empty + // <=> mSummaryMinAddr > mSummaryMaxAddr + // + // This doesn't constrain mSummaryMinAddr and mSummaryMaxAddr uniquely, + // so let's use mSummaryMinAddr == 1 and mSummaryMaxAddr == 0 to denote + // this case. + + explicit SecMap(void(*aLog)(const char*)); + ~SecMap(); + + // Binary search mRuleSets to find one that brackets |ia|, or nullptr + // if none is found. It's not allowable to do this until PrepareRuleSets + // has been called first. + RuleSet* FindRuleSet(uintptr_t ia); + + // Add a RuleSet to the collection. The rule is copied in. Calling + // this makes the map non-searchable. + void AddRuleSet(const RuleSet* rs); + + // Add a PfxInstr to the vector of such instrs, and return the index + // in the vector. Calling this makes the map non-searchable. + uint32_t AddPfxInstr(PfxInstr pfxi); + + // Returns the entire vector of PfxInstrs. + const vector* GetPfxInstrs() { return &mPfxInstrs; } + + // Prepare the map for searching. Also, remove any rules for code + // address ranges which don't fall inside [start, +len). |len| may + // not be zero. + void PrepareRuleSets(uintptr_t start, size_t len); + + bool IsEmpty(); + + size_t Size() { return mRuleSets.size(); } + + // The min and max addresses of the addresses in the contained + // RuleSets. See comment above for invariants. + uintptr_t mSummaryMinAddr; + uintptr_t mSummaryMaxAddr; + +private: + // False whilst adding entries; true once it is safe to call FindRuleSet. + // Transition (false->true) is caused by calling PrepareRuleSets(). + bool mUsable; + + // A vector of RuleSets, sorted, nonoverlapping (post Prepare()). + vector mRuleSets; + + // A vector of PfxInstrs, which are referred to by the RuleSets. + // These are provided as a representation of Dwarf expressions + // (DW_CFA_val_expression, DW_CFA_expression, DW_CFA_def_cfa_expression), + // are relatively expensive to evaluate, and and are therefore + // expected to be used only occasionally. + // + // The vector holds a bunch of separate PfxInstr programs, each one + // starting with a PX_Start and terminated by a PX_End, all + // concatenated together. When a RuleSet can't recover a value + // using a self-contained LExpr, it uses a PFXEXPR whose mOffset is + // the index in this vector of start of the necessary PfxInstr program. + vector mPfxInstrs; + + // A logging sink, for debugging. + void (*mLog)(const char*); +}; + +} // namespace lul + +#endif // ndef LulMainInt_h diff --git a/tools/profiler/lul/LulPlatformMacros.h b/tools/profiler/lul/LulPlatformMacros.h new file mode 100644 index 0000000000..8659a8fbee --- /dev/null +++ b/tools/profiler/lul/LulPlatformMacros.h @@ -0,0 +1,53 @@ +/* -*- 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 LulPlatformMacros_h +#define LulPlatformMacros_h + +#include +#include + +// Define platform selection macros in a consistent way. The primary +// factorisation is on (ARCH,OS) pairs ("PLATforms") but ARCH_ and OS_ +// macros are defined too, since they are sometimes convenient. + +#undef LUL_PLAT_x64_linux +#undef LUL_PLAT_x86_linux +#undef LUL_PLAT_arm_android +#undef LUL_PLAT_x86_android + +#undef LUL_ARCH_arm +#undef LUL_ARCH_x86 +#undef LUL_ARCH_x64 + +#undef LUL_OS_android +#undef LUL_OS_linux + +#if defined(__linux__) && defined(__x86_64__) +# define LUL_PLAT_x64_linux 1 +# define LUL_ARCH_x64 1 +# define LUL_OS_linux 1 + +#elif defined(__linux__) && defined(__i386__) && !defined(__ANDROID__) +# define LUL_PLAT_x86_linux 1 +# define LUL_ARCH_x86 1 +# define LUL_OS_linux 1 + +#elif defined(__ANDROID__) && defined(__arm__) +# define LUL_PLAT_arm_android 1 +# define LUL_ARCH_arm 1 +# define LUL_OS_android 1 + +#elif defined(__ANDROID__) && defined(__i386__) +# define LUL_PLAT_x86_android 1 +# define LUL_ARCH_x86 1 +# define LUL_OS_android 1 + +#else +# error "Unsupported platform" +#endif + +#endif // LulPlatformMacros_h diff --git a/tools/profiler/platform-linux-lul.cpp b/tools/profiler/lul/platform-linux-lul.cpp similarity index 100% rename from tools/profiler/platform-linux-lul.cpp rename to tools/profiler/lul/platform-linux-lul.cpp diff --git a/tools/profiler/moz.build b/tools/profiler/moz.build index 25ca5c3294..ca68192886 100644 --- a/tools/profiler/moz.build +++ b/tools/profiler/moz.build @@ -124,9 +124,9 @@ EXPORTS += [ if CONFIG['MOZ_TASK_TRACER']: EXPORTS += [ - 'public/GeckoTaskTracer.h', - 'public/GeckoTaskTracerImpl.h', - 'public/TracedTaskCommon.h', + 'tasktracer/GeckoTaskTracer.h', + 'tasktracer/GeckoTaskTracerImpl.h', + 'tasktracer/TracedTaskCommon.h', ] UNIFIED_SOURCES += [ 'tasktracer/GeckoTaskTracer.cpp', diff --git a/tools/profiler/public/ProfilerMarkers.h b/tools/profiler/public/ProfilerMarkers.h index b619b3c6c7..29711f210c 100644 --- a/tools/profiler/public/ProfilerMarkers.h +++ b/tools/profiler/public/ProfilerMarkers.h @@ -12,8 +12,8 @@ namespace mozilla { namespace layers { class Layer; -} // layers -} // mozilla +} // namespace layers +} // namespace mozilla class SpliceableJSONWriter; class UniqueStacks; diff --git a/widget/cocoa/OSXNotificationCenter.mm b/widget/cocoa/OSXNotificationCenter.mm index de42a59b96..3ac9ab5d92 100644 --- a/widget/cocoa/OSXNotificationCenter.mm +++ b/widget/cocoa/OSXNotificationCenter.mm @@ -11,6 +11,7 @@ #include "nsNetUtil.h" #include "imgLoader.h" #import "nsCocoaUtils.h" +#include "nsComponentManagerUtils.h" #include "nsContentUtils.h" #include "nsObjCExceptions.h" #include "nsString.h" @@ -242,15 +243,39 @@ OSXNotificationCenter::ShowAlertNotification(const nsAString & aImageUrl, const const nsAString & aData, nsIPrincipal * aPrincipal, bool aInPrivateBrowsing) +{ + nsCOMPtr alert = + do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID); + NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE); + nsresult rv = alert->Init(aAlertName, aImageUrl, aAlertTitle, + aAlertText, aAlertTextClickable, + aAlertCookie, aBidi, aLang, aData, + aPrincipal, aInPrivateBrowsing); + NS_ENSURE_SUCCESS(rv, rv); + return ShowAlert(alert, aAlertListener); +} + +NS_IMETHODIMP +OSXNotificationCenter::ShowAlert(nsIAlertNotification* aAlert, + nsIObserver* aAlertListener) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + NS_ENSURE_ARG(aAlert); + Class unClass = NSClassFromString(@"NSUserNotification"); id notification = [[unClass alloc] init]; - notification.title = nsCocoaUtils::ToNSString(aAlertTitle); + nsAutoString title; + nsresult rv = aAlert->GetTitle(title); + NS_ENSURE_SUCCESS(rv, rv); + notification.title = nsCocoaUtils::ToNSString(title); + + nsCOMPtr principal; + rv = aAlert->GetPrincipal(getter_AddRefs(principal)); + NS_ENSURE_SUCCESS(rv, rv); nsAutoString hostPort; - nsAlertsUtils::GetSourceHostPort(aPrincipal, hostPort); + nsAlertsUtils::GetSourceHostPort(principal, hostPort); nsCOMPtr bundle; nsCOMPtr sbs = do_GetService(NS_STRINGBUNDLE_CONTRACTID); sbs->CreateBundle("chrome://alerts/locale/alert.properties", getter_AddRefs(bundle)); @@ -265,12 +290,16 @@ OSXNotificationCenter::ShowAlertNotification(const nsAString & aImageUrl, const notification.subtitle = nsCocoaUtils::ToNSString(notificationSource); } - notification.informativeText = nsCocoaUtils::ToNSString(aAlertText); + nsAutoString text; + rv = aAlert->GetText(text); + NS_ENSURE_SUCCESS(rv, rv); + notification.informativeText = nsCocoaUtils::ToNSString(text); + notification.soundName = NSUserNotificationDefaultSoundName; notification.hasActionButton = NO; // If this is not an application/extension alert, show additional actions dealing with permissions. - if (nsAlertsUtils::IsActionablePrincipal(aPrincipal)) { + if (nsAlertsUtils::IsActionablePrincipal(principal)) { if (bundle) { nsXPIDLString closeButtonTitle, actionButtonTitle, disableButtonTitle, settingsButtonTitle; bundle->GetStringFromName(MOZ_UTF16("closeButton.title"), @@ -309,7 +338,10 @@ OSXNotificationCenter::ShowAlertNotification(const nsAString & aImageUrl, const } } } - NSString *alertName = nsCocoaUtils::ToNSString(aAlertName); + nsAutoString name; + rv = aAlert->GetName(name); + NS_ENSURE_SUCCESS(rv, rv); + NSString *alertName = nsCocoaUtils::ToNSString(name); // Don't let an alert name be more than MAX_NOTIFICATION_NAME_LEN characters. // More than that shouldn't be necessary and userInfo (assigned to below) has // a length limit of 16k on OS X 10.11. Exception thrown if limit exceeded. @@ -319,17 +351,29 @@ OSXNotificationCenter::ShowAlertNotification(const nsAString & aImageUrl, const notification.userInfo = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:alertName, nil] forKeys:[NSArray arrayWithObjects:@"name", nil]]; - OSXNotificationInfo *osxni = new OSXNotificationInfo(alertName, aAlertListener, aAlertCookie); + nsAutoString cookie; + rv = aAlert->GetCookie(cookie); + NS_ENSURE_SUCCESS(rv, rv); + + OSXNotificationInfo *osxni = new OSXNotificationInfo(alertName, aAlertListener, cookie); + + nsAutoString imageUrl; + rv = aAlert->GetImageURL(imageUrl); + NS_ENSURE_SUCCESS(rv, rv); + + bool inPrivateBrowsing; + rv = aAlert->GetInPrivateBrowsing(&inPrivateBrowsing); + NS_ENSURE_SUCCESS(rv, rv); // Show the notification without waiting for an image if there is no icon URL or // notification icons are not supported on this version of OS X. - if (aImageUrl.IsEmpty() || ![unClass instancesRespondToSelector:@selector(setContentImage:)]) { + if (imageUrl.IsEmpty() || ![unClass instancesRespondToSelector:@selector(setContentImage:)]) { CloseAlertCocoaString(alertName); mActiveAlerts.AppendElement(osxni); [GetNotificationCenter() deliverNotification:notification]; [notification release]; if (aAlertListener) { - aAlertListener->Observe(nullptr, "alertshow", PromiseFlatString(aAlertCookie).get()); + aAlertListener->Observe(nullptr, "alertshow", cookie.get()); } } else { mPendingAlerts.AppendElement(osxni); @@ -337,17 +381,17 @@ OSXNotificationCenter::ShowAlertNotification(const nsAString & aImageUrl, const RefPtr il = imgLoader::GetInstance(); if (il) { nsCOMPtr imageUri; - NS_NewURI(getter_AddRefs(imageUri), aImageUrl); + NS_NewURI(getter_AddRefs(imageUri), imageUrl); if (imageUri) { - nsresult rv = il->LoadImage(imageUri, nullptr, nullptr, - mozilla::net::RP_Default, - aPrincipal, nullptr, - this, nullptr, - aInPrivateBrowsing ? nsIRequest::LOAD_ANONYMOUS : - nsIRequest::LOAD_NORMAL, - nullptr, nsIContentPolicy::TYPE_INTERNAL_IMAGE, - EmptyString(), - getter_AddRefs(osxni->mIconRequest)); + rv = il->LoadImage(imageUri, nullptr, nullptr, + mozilla::net::RP_Default, + principal, nullptr, + this, nullptr, + inPrivateBrowsing ? nsIRequest::LOAD_ANONYMOUS : + nsIRequest::LOAD_NORMAL, + nullptr, nsIContentPolicy::TYPE_INTERNAL_IMAGE, + EmptyString(), + getter_AddRefs(osxni->mIconRequest)); if (NS_SUCCEEDED(rv)) { // Set a timer for six seconds. If we don't have an icon by the time this // goes off then we go ahead without an icon. diff --git a/widget/gtk/nsPrintDialogGTK.cpp b/widget/gtk/nsPrintDialogGTK.cpp index 54aafe9803..892fe57dac 100644 --- a/widget/gtk/nsPrintDialogGTK.cpp +++ b/widget/gtk/nsPrintDialogGTK.cpp @@ -116,7 +116,7 @@ class nsPrintDialogWidgetGTK { nsPrintDialogWidgetGTK(nsIDOMWindow *aParent, nsIPrintSettings *aPrintSettings); ~nsPrintDialogWidgetGTK() { gtk_widget_destroy(dialog); } NS_ConvertUTF16toUTF8 GetUTF8FromBundle(const char* aKey); - const gint Run(); + gint Run(); nsresult ImportSettings(nsIPrintSettings *aNSSettings); nsresult ExportSettings(nsIPrintSettings *aNSSettings); @@ -343,7 +343,7 @@ nsPrintDialogWidgetGTK::OptionWidgetToString(GtkWidget *dropdown) return header_footer_tags[index]; } -const gint +gint nsPrintDialogWidgetGTK::Run() { const gint response = gtk_dialog_run(GTK_DIALOG(dialog)); diff --git a/xpcom/build/PoisonIOInterposerBase.cpp b/xpcom/build/PoisonIOInterposerBase.cpp index 7b7bf9522f..0e5754392e 100644 --- a/xpcom/build/PoisonIOInterposerBase.cpp +++ b/xpcom/build/PoisonIOInterposerBase.cpp @@ -38,8 +38,9 @@ namespace { struct DebugFilesAutoLockTraits { typedef PRLock* type; - const static type empty() { return nullptr; } - const static void release(type aL) { PR_Unlock(aL); } + typedef const PRLock* const_type; + static const_type empty() { return nullptr; } + static void release(type aL) { PR_Unlock(aL); } }; class DebugFilesAutoLock : public Scoped diff --git a/xpcom/ds/nsIAtom.idl b/xpcom/ds/nsIAtom.idl index f5f67ab23e..ef10363491 100644 --- a/xpcom/ds/nsIAtom.idl +++ b/xpcom/ds/nsIAtom.idl @@ -16,7 +16,7 @@ * pointer identity. */ -[scriptable, builtinclass, uuid(1f341018-521a-49de-b806-1bef5c9a00b0)] +[scriptable, builtinclass, uuid(8b8c11d4-3ed5-4079-8974-73c7576cdb34)] interface nsIAtom : nsISupports { /** @@ -48,7 +48,7 @@ interface nsIAtom : nsISupports return mString; } - inline const uint32_t GetLength() const { + inline uint32_t GetLength() const { return mLength; } diff --git a/xpcom/glue/PLDHashTable.h b/xpcom/glue/PLDHashTable.h index 7f4622dce8..327877477d 100644 --- a/xpcom/glue/PLDHashTable.h +++ b/xpcom/glue/PLDHashTable.h @@ -305,7 +305,7 @@ public: ~PLDHashTable(); // This should be used rarely. - const PLDHashTableOps* const Ops() { return mOps; } + const PLDHashTableOps* Ops() const { return mOps; } // Size in entries (gross, not net of free and removed sentinels) for table. // This can be zero if no elements have been added yet, in which case the diff --git a/xpcom/glue/nsTArray.h b/xpcom/glue/nsTArray.h index 5ed37bb1f5..bea1d8a9a1 100644 --- a/xpcom/glue/nsTArray.h +++ b/xpcom/glue/nsTArray.h @@ -261,6 +261,7 @@ template struct nsTArray_SafeElementAtHelper { typedef E* elem_type; + //typedef const E* const_elem_type; XXX: see below typedef size_t index_type; elem_type SafeElementAt(index_type aIndex) @@ -268,7 +269,11 @@ struct nsTArray_SafeElementAtHelper return static_cast(this)->SafeElementAt(aIndex, nullptr); } - const elem_type SafeElementAt(index_type aIndex) const + // XXX: Probably should return const_elem_type, but callsites must be fixed. + // Also, the use of const_elem_type for nsTArray in + // xpcprivate.h causes build failures on Windows because xpcGCCallback is a + // function pointer and MSVC doesn't like qualifying it with |const|. + elem_type SafeElementAt(index_type aIndex) const { return static_cast(this)->SafeElementAt(aIndex, nullptr); } @@ -280,6 +285,7 @@ template struct nsTArray_SafeElementAtSmartPtrHelper { typedef E* elem_type; + typedef const E* const_elem_type; typedef size_t index_type; elem_type SafeElementAt(index_type aIndex) @@ -287,7 +293,8 @@ struct nsTArray_SafeElementAtSmartPtrHelper return static_cast(this)->SafeElementAt(aIndex, nullptr); } - const elem_type SafeElementAt(index_type aIndex) const + // XXX: Probably should return const_elem_type, but callsites must be fixed. + elem_type SafeElementAt(index_type aIndex) const { return static_cast(this)->SafeElementAt(aIndex, nullptr); } @@ -314,8 +321,9 @@ template class OwningNonNull; template struct nsTArray_SafeElementAtHelper, Derived> { - typedef E* elem_type; - typedef size_t index_type; + typedef E* elem_type; + typedef const E* const_elem_type; + typedef size_t index_type; elem_type SafeElementAt(index_type aIndex) { @@ -325,7 +333,8 @@ struct nsTArray_SafeElementAtHelper, Derived> return nullptr; } - const elem_type SafeElementAt(index_type aIndex) const + // XXX: Probably should return const_elem_type, but callsites must be fixed. + elem_type SafeElementAt(index_type aIndex) const { if (aIndex < static_cast(this)->Length()) { return static_cast(this)->ElementAt(aIndex);