From cca3db9fa5affc43ad74af2391f392757ac9f6e7 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Thu, 15 May 2014 12:30:45 -0500 Subject: [PATCH 01/70] Bug 1007383 - Include asm.js frames in JS::DescribeStack (r=jandem,robcee) --- js/public/OldDebugAPI.h | 30 ++++++++--- js/src/vm/OldDebugAPI.cpp | 51 +++++++++++++++---- .../webconsole/test/test_consoleapi.html | 42 +++++++++++++++ 3 files changed, 105 insertions(+), 18 deletions(-) diff --git a/js/public/OldDebugAPI.h b/js/public/OldDebugAPI.h index fe5e3368636..25441936cee 100644 --- a/js/public/OldDebugAPI.h +++ b/js/public/OldDebugAPI.h @@ -24,7 +24,8 @@ class JSFreeOp; namespace js { class InterpreterFrame; -class ScriptFrameIter; +class FrameIter; +class ScriptSource; } // Raw JSScript* because this needs to be callable from a signal handler. @@ -39,18 +40,20 @@ namespace JS { class FrameDescription { public: - explicit FrameDescription(const js::ScriptFrameIter& iter); + explicit FrameDescription(const js::FrameIter& iter); + FrameDescription(const FrameDescription &rhs); + ~FrameDescription(); unsigned lineno() { - if (!linenoComputed) { + if (!linenoComputed_) { lineno_ = JS_PCToLineNumber(nullptr, script_, pc_); - linenoComputed = true; + linenoComputed_ = true; } return lineno_; } const char *filename() const { - return JS_GetScriptFilename(script_); + return filename_; } JSFlatString *funDisplayName() const { @@ -67,11 +70,22 @@ class FrameDescription } private: - Heap script_; + void operator=(const FrameDescription &) MOZ_DELETE; + + // These fields are always initialized: Heap funDisplayName_; - jsbytecode *pc_; + const char *filename_; + + // One of script_ xor scriptSource_ is non-null. + Heap script_; + js::ScriptSource *scriptSource_; + + // For script_-having frames, lineno_ is lazily computed as an optimization. + bool linenoComputed_; unsigned lineno_; - bool linenoComputed; + + // pc_ is non-null iff script_ is non-null. If !pc_, linenoComputed_ = true. + jsbytecode *pc_; }; struct StackDescription diff --git a/js/src/vm/OldDebugAPI.cpp b/js/src/vm/OldDebugAPI.cpp index 0504a22eb7f..0039f394ce5 100644 --- a/js/src/vm/OldDebugAPI.cpp +++ b/js/src/vm/OldDebugAPI.cpp @@ -897,14 +897,45 @@ js_CallContextDebugHandler(JSContext *cx) * constructing a FrameDescription on the stack just to append it to a vector. * FrameDescription contains Heap fields that should not live on the stack. */ -JS::FrameDescription::FrameDescription(const ScriptFrameIter& iter) - : script_(iter.script()), - funDisplayName_(nullptr), - pc_(iter.pc()), - linenoComputed(false) +JS::FrameDescription::FrameDescription(const FrameIter& iter) + : scriptSource_(nullptr), + linenoComputed_(false), + pc_(nullptr) { - if (JSFunction *fun = iter.maybeCallee()) - funDisplayName_ = fun->displayAtom(); + if (iter.isNonEvalFunctionFrame()) + funDisplayName_ = iter.functionDisplayAtom(); + + if (iter.hasScript()) { + script_ = iter.script(); + pc_ = iter.pc(); + filename_ = script_->filename(); + } else { + scriptSource_ = iter.scriptSource(); + scriptSource_->incref(); + filename_ = scriptSource_->filename(); + lineno_ = iter.computeLine(); + linenoComputed_ = true; + } +} + +JS::FrameDescription::FrameDescription(const FrameDescription &rhs) + : funDisplayName_(rhs.funDisplayName_), + filename_(rhs.filename_), + script_(rhs.script_), + scriptSource_(rhs.scriptSource_), + linenoComputed_(rhs.linenoComputed_), + lineno_(rhs.lineno_), + pc_(rhs.pc_) +{ + if (scriptSource_) + scriptSource_->incref(); +} + + +JS::FrameDescription::~FrameDescription() +{ + if (scriptSource_) + scriptSource_->decref(); } JS_PUBLIC_API(JS::StackDescription *) @@ -912,9 +943,9 @@ JS::DescribeStack(JSContext *cx, unsigned maxFrames) { Vector frames(cx); - NonBuiltinScriptFrameIter i(cx, ScriptFrameIter::ALL_CONTEXTS, - ScriptFrameIter::GO_THROUGH_SAVED, - cx->compartment()->principals); + NonBuiltinFrameIter i(cx, FrameIter::ALL_CONTEXTS, + FrameIter::GO_THROUGH_SAVED, + cx->compartment()->principals); for ( ; !i.done(); ++i) { if (!frames.append(i)) return nullptr; diff --git a/toolkit/devtools/webconsole/test/test_consoleapi.html b/toolkit/devtools/webconsole/test/test_consoleapi.html index 0c3f0b9d7a5..4a8dea0e09e 100644 --- a/toolkit/devtools/webconsole/test/test_consoleapi.html +++ b/toolkit/devtools/webconsole/test/test_consoleapi.html @@ -28,6 +28,18 @@ function doConsoleCalls(aState) top.console.dir(top.document, top.location); top.console.log("foo", longString); + function fromAsmJS() { + top.console.error("foobarBaz-asmjs-error", undefined); + } + + (function(global, foreign) { + "use asm"; + var fromAsmJS = foreign.fromAsmJS; + function inAsmJS2() { fromAsmJS() } + function inAsmJS1() { inAsmJS2() } + return inAsmJS1 + })(null, { fromAsmJS:fromAsmJS })(); + expectedConsoleCalls = [ { level: "log", @@ -107,6 +119,36 @@ function doConsoleCalls(aState) }, ], }, + { + level: "error", + filename: /test_consoleapi/, + functionName: "fromAsmJS", + timeStamp: /^\d+$/, + arguments: ["foobarBaz-asmjs-error", { type: "undefined" }], + + stacktrace: [ + { + filename: /test_consoleapi/, + functionName: "fromAsmJS", + }, + { + filename: /test_consoleapi/, + functionName: "inAsmJS2", + }, + { + filename: /test_consoleapi/, + functionName: "inAsmJS1", + }, + { + filename: /test_consoleapi/, + functionName: "doConsoleCalls", + }, + { + filename: /test_consoleapi/, + functionName: "onAttach", + }, + ], + }, ]; } From c8fdca4d92c8dea36ede3c7706634348ae4d41ef Mon Sep 17 00:00:00 2001 From: Bill McCloskey Date: Thu, 15 May 2014 16:16:41 -0700 Subject: [PATCH 02/70] Bug 1008418 - Add dumpObject to TestingFunctions.cpp (r=sfink) --- js/src/builtin/TestingFunctions.cpp | 23 +++++++++++++++++++++++ js/src/shell/js.cpp | 18 ------------------ 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index 18efe335d29..34faa99057f 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -1606,6 +1606,22 @@ DisableTraceLogger(JSContext *cx, unsigned argc, jsval *vp) return true; } +#ifdef DEBUG +static bool +DumpObject(JSContext *cx, unsigned argc, jsval *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + RootedObject arg0(cx); + if (!JS_ConvertArguments(cx, args, "o", arg0.address())) + return false; + + js_DumpObject(arg0); + + args.rval().setUndefined(); + return true; +} +#endif + static const JSFunctionSpecWithHelp TestingFunctions[] = { JS_FN_HELP("gc", ::GC, 0, 0, "gc([obj] | 'compartment')", @@ -1875,6 +1891,13 @@ static const JSFunctionSpecWithHelp TestingFunctions[] = { JS_FN_HELP("stopTraceLogger", DisableTraceLogger, 0, 0, "stopTraceLogger()", " Stop logging the mainThread."), + +#ifdef DEBUG + JS_FN_HELP("dumpObject", DumpObject, 1, 0, +"dumpObject()", +" Dump an internal representation of an object."), +#endif + JS_FS_HELP_END }; diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index ed951bf726a..86b82263e93 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -2510,20 +2510,6 @@ DumpHeap(JSContext *cx, unsigned argc, jsval *vp) return ok; } -static bool -DumpObject(JSContext *cx, unsigned argc, jsval *vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - RootedObject arg0(cx); - if (!JS_ConvertArguments(cx, args, "o", arg0.address())) - return false; - - js_DumpObject(arg0); - - args.rval().setUndefined(); - return true; -} - #endif /* DEBUG */ static bool @@ -4580,10 +4566,6 @@ static const JSFunctionSpecWithHelp shell_functions[] = { "dissrc([fun])", " Disassemble functions with source lines."), - JS_FN_HELP("dumpObject", DumpObject, 1, 0, -"dumpObject()", -" Dump an internal representation of an object."), - JS_FN_HELP("notes", Notes, 1, 0, "notes([fun])", " Show source notes for functions."), From 6bbf07b570ec8858635639f45cfb439ef9423142 Mon Sep 17 00:00:00 2001 From: Randell Jesup Date: Thu, 15 May 2014 19:17:40 -0400 Subject: [PATCH 03/70] Bug 1003006: Move b2g camera rotation checks to MainThread r=mikeh --- content/media/webrtc/MediaEngineWebRTC.h | 6 ++- .../media/webrtc/MediaEngineWebRTCVideo.cpp | 49 ++++++++++++------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/content/media/webrtc/MediaEngineWebRTC.h b/content/media/webrtc/MediaEngineWebRTC.h index 0c32c104f16..5137dadd812 100644 --- a/content/media/webrtc/MediaEngineWebRTC.h +++ b/content/media/webrtc/MediaEngineWebRTC.h @@ -170,6 +170,7 @@ public: NS_DECL_ISUPPORTS_INHERITED void OnHardwareStateChange(HardwareState aState); + void GetRotation(); bool OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight); void OnUserError(UserContext aContext, nsresult aError); void OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType); @@ -212,9 +213,12 @@ private: // Engine variables. #ifdef MOZ_B2G_CAMERA - nsRefPtr mCameraControl; mozilla::ReentrantMonitor mCallbackMonitor; // Monitor for camera callback handling + // This is only modified on MainThread (AllocImpl and DeallocImpl) + nsRefPtr mCameraControl; nsRefPtr mLastCapture; + + // These are protected by mMonitor below int mRotation; int mCameraAngle; // See dom/base/ScreenOrientation.h bool mBackCamera; diff --git a/content/media/webrtc/MediaEngineWebRTCVideo.cpp b/content/media/webrtc/MediaEngineWebRTCVideo.cpp index f2b165e741c..5a12f14ef6b 100644 --- a/content/media/webrtc/MediaEngineWebRTCVideo.cpp +++ b/content/media/webrtc/MediaEngineWebRTCVideo.cpp @@ -415,7 +415,7 @@ MediaEngineWebRTCVideoSource::Allocate(const VideoTrackConstraintsN &aConstraint ReentrantMonitorAutoEnter sync(mCallbackMonitor); if (mState == kReleased && mInitDone) { ChooseCapability(aConstraints, aPrefs); - NS_DispatchToMainThread(WrapRunnable(this, + NS_DispatchToMainThread(WrapRunnable(nsRefPtr(this), &MediaEngineWebRTCVideoSource::AllocImpl)); mCallbackMonitor.Wait(); if (mState != kAllocated) { @@ -459,7 +459,7 @@ MediaEngineWebRTCVideoSource::Deallocate() #ifdef MOZ_B2G_CAMERA // We do not register success callback here - NS_DispatchToMainThread(WrapRunnable(this, + NS_DispatchToMainThread(WrapRunnable(nsRefPtr(this), &MediaEngineWebRTCVideoSource::DeallocImpl)); mCallbackMonitor.Wait(); if (mState != kReleased) { @@ -519,7 +519,7 @@ MediaEngineWebRTCVideoSource::Start(SourceMediaStream* aStream, TrackID aID) mImageContainer = layers::LayerManager::CreateImageContainer(); #ifdef MOZ_B2G_CAMERA - NS_DispatchToMainThread(WrapRunnable(this, + NS_DispatchToMainThread(WrapRunnable(nsRefPtr(this), &MediaEngineWebRTCVideoSource::StartImpl, mCapability)); mCallbackMonitor.Wait(); @@ -573,7 +573,7 @@ MediaEngineWebRTCVideoSource::Stop(SourceMediaStream *aSource, TrackID aID) mImage = nullptr; } #ifdef MOZ_B2G_CAMERA - NS_DispatchToMainThread(WrapRunnable(this, + NS_DispatchToMainThread(WrapRunnable(nsRefPtr(this), &MediaEngineWebRTCVideoSource::StopImpl)); #else mViERender->StopRender(mCaptureIndex); @@ -789,26 +789,37 @@ MediaEngineWebRTCVideoSource::OnHardwareStateChange(HardwareState aState) mCallbackMonitor.Notify(); } } else { - mCameraControl->Get(CAMERA_PARAM_SENSORANGLE, mCameraAngle); - MOZ_ASSERT(mCameraAngle == 0 || mCameraAngle == 90 || mCameraAngle == 180 || - mCameraAngle == 270); - hal::ScreenConfiguration aConfig; - hal::GetCurrentScreenConfiguration(&aConfig); - - nsCString deviceName; - ICameraControl::GetCameraName(mCaptureIndex, deviceName); - if (deviceName.EqualsASCII("back")) { - mBackCamera = true; - } - - mRotation = GetRotateAmount(aConfig.orientation(), mCameraAngle, mBackCamera); - LOG(("*** Initial orientation: %d (Camera %d Back %d MountAngle: %d)", - mRotation, mCaptureIndex, mBackCamera, mCameraAngle)); + // Can't read this except on MainThread (ugh) + NS_DispatchToMainThread(WrapRunnable(nsRefPtr(this), + &MediaEngineWebRTCVideoSource::GetRotation)); mState = kStarted; mCallbackMonitor.Notify(); } } +void +MediaEngineWebRTCVideoSource::GetRotation() +{ + MOZ_ASSERT(NS_IsMainThread()); + MonitorAutoLock enter(mMonitor); + + mCameraControl->Get(CAMERA_PARAM_SENSORANGLE, mCameraAngle); + MOZ_ASSERT(mCameraAngle == 0 || mCameraAngle == 90 || mCameraAngle == 180 || + mCameraAngle == 270); + hal::ScreenConfiguration config; + hal::GetCurrentScreenConfiguration(&config); + + nsCString deviceName; + ICameraControl::GetCameraName(mCaptureIndex, deviceName); + if (deviceName.EqualsASCII("back")) { + mBackCamera = true; + } + + mRotation = GetRotateAmount(config.orientation(), mCameraAngle, mBackCamera); + LOG(("*** Initial orientation: %d (Camera %d Back %d MountAngle: %d)", + mRotation, mCaptureIndex, mBackCamera, mCameraAngle)); +} + void MediaEngineWebRTCVideoSource::OnUserError(UserContext aContext, nsresult aError) { From a73945072639ac18f1f3c5d4a13514af0e405a5b Mon Sep 17 00:00:00 2001 From: Bill McCloskey Date: Thu, 15 May 2014 16:17:52 -0700 Subject: [PATCH 04/70] Bug 1008418 - Fix review comment DONTBUILD --- js/src/builtin/TestingFunctions.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index 34faa99057f..16cb705b9b0 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -1611,11 +1611,11 @@ static bool DumpObject(JSContext *cx, unsigned argc, jsval *vp) { CallArgs args = CallArgsFromVp(argc, vp); - RootedObject arg0(cx); - if (!JS_ConvertArguments(cx, args, "o", arg0.address())) + RootedObject obj(cx); + if (!JS_ConvertArguments(cx, args, "o", obj.address())) return false; - js_DumpObject(arg0); + js_DumpObject(obj); args.rval().setUndefined(); return true; From df408748acce00c2e4ff4940d5f7095da449f892 Mon Sep 17 00:00:00 2001 From: Karl Tomlinson Date: Fri, 16 May 2014 08:44:17 +1200 Subject: [PATCH 05/70] b=991533 move sample rate limits to WebAudioUtils r=padenot Also change WebAudioUtils from a class to a namespace, so that constant variables can be defined inline with internal linkage. static class variables cannot be defined inline because this violates the one definition rule, even though some compilers may not notice. --HG-- extra : transplant_source : %9F4%2Ct%BA%D2%BD%8A1Xev%92%C0%A1%AD%88IH%BF --- content/media/webaudio/AudioContext.cpp | 4 +- content/media/webaudio/WebAudioUtils.cpp | 4 -- content/media/webaudio/WebAudioUtils.h | 47 +++++++++++-------- .../blink/DynamicsCompressorKernel.cpp | 2 +- 4 files changed, 31 insertions(+), 26 deletions(-) diff --git a/content/media/webaudio/AudioContext.cpp b/content/media/webaudio/AudioContext.cpp index beaa28708ef..9ed4b91ad50 100644 --- a/content/media/webaudio/AudioContext.cpp +++ b/content/media/webaudio/AudioContext.cpp @@ -204,7 +204,9 @@ AudioContext::CreateBuffer(JSContext* aJSContext, uint32_t aNumberOfChannels, uint32_t aLength, float aSampleRate, ErrorResult& aRv) { - if (aSampleRate < 8000 || aSampleRate > 192000 || !aLength || !aNumberOfChannels) { + if (aSampleRate < WebAudioUtils::MinSampleRate || + aSampleRate > WebAudioUtils::MaxSampleRate || + !aLength || !aNumberOfChannels) { aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); return nullptr; } diff --git a/content/media/webaudio/WebAudioUtils.cpp b/content/media/webaudio/WebAudioUtils.cpp index 4d5f7cd40cc..47ecabb7d6d 100644 --- a/content/media/webaudio/WebAudioUtils.cpp +++ b/content/media/webaudio/WebAudioUtils.cpp @@ -14,10 +14,6 @@ namespace mozilla { namespace dom { -// 32 is the minimum required by the spec and matches what is used by blink. -// The limit protects against large memory allocations. -const size_t WebAudioUtils::MaxChannelCount = 32; - struct ConvertTimeToTickHelper { AudioNodeStream* mSourceStream; diff --git a/content/media/webaudio/WebAudioUtils.h b/content/media/webaudio/WebAudioUtils.h index 771af3888e1..19abe1123fe 100644 --- a/content/media/webaudio/WebAudioUtils.h +++ b/content/media/webaudio/WebAudioUtils.h @@ -24,15 +24,22 @@ namespace dom { class AudioParamTimeline; -struct WebAudioUtils { - static const size_t MaxChannelCount; +namespace WebAudioUtils { + // 32 is the minimum required by the spec for createBuffer() and + // createScriptProcessor() and matches what is used by Blink. The limit + // protects against large memory allocations. + const size_t MaxChannelCount = 32; + // AudioContext::CreateBuffer() "must support sample-rates in at least the + // range 22050 to 96000." + const uint32_t MinSampleRate = 8000; + const uint32_t MaxSampleRate = 192000; - static bool FuzzyEqual(float v1, float v2) + inline bool FuzzyEqual(float v1, float v2) { using namespace std; return fabsf(v1 - v2) < 1e-7f; } - static bool FuzzyEqual(double v1, double v2) + inline bool FuzzyEqual(double v1, double v2) { using namespace std; return fabs(v1 - v2) < 1e-7; @@ -42,7 +49,7 @@ struct WebAudioUtils { * Computes an exponential smoothing rate for a time based variable * over aDuration seconds. */ - static double ComputeSmoothingRate(double aDuration, double aSampleRate) + inline double ComputeSmoothingRate(double aDuration, double aSampleRate) { return 1.0 - std::exp(-1.0 / (aDuration * aSampleRate)); } @@ -56,15 +63,15 @@ struct WebAudioUtils { * received. This means that such engines need to be aware of their source * and destination streams as well. */ - static void ConvertAudioParamToTicks(AudioParamTimeline& aParam, - AudioNodeStream* aSource, - AudioNodeStream* aDest); + void ConvertAudioParamToTicks(AudioParamTimeline& aParam, + AudioNodeStream* aSource, + AudioNodeStream* aDest); /** * Converts a linear value to decibels. Returns aMinDecibels if the linear * value is 0. */ - static float ConvertLinearToDecibels(float aLinearValue, float aMinDecibels) + inline float ConvertLinearToDecibels(float aLinearValue, float aMinDecibels) { return aLinearValue ? 20.0f * std::log10(aLinearValue) : aMinDecibels; } @@ -72,7 +79,7 @@ struct WebAudioUtils { /** * Converts a decibel value to a linear value. */ - static float ConvertDecibelsToLinear(float aDecibels) + inline float ConvertDecibelsToLinear(float aDecibels) { return std::pow(10.0f, 0.05f * aDecibels); } @@ -80,24 +87,24 @@ struct WebAudioUtils { /** * Converts a decibel to a linear value. */ - static float ConvertDecibelToLinear(float aDecibel) + inline float ConvertDecibelToLinear(float aDecibel) { return std::pow(10.0f, 0.05f * aDecibel); } - static void FixNaN(double& aDouble) + inline void FixNaN(double& aDouble) { if (IsNaN(aDouble) || IsInfinite(aDouble)) { aDouble = 0.0; } } - static double DiscreteTimeConstantForSampleRate(double timeConstant, double sampleRate) + inline double DiscreteTimeConstantForSampleRate(double timeConstant, double sampleRate) { return 1.0 - std::exp(-1.0 / (sampleRate * timeConstant)); } - static bool IsTimeValid(double aTime) + inline bool IsTimeValid(double aTime) { return aTime >= 0 && aTime <= (MEDIA_TIME_MAX >> MEDIA_TIME_FRAC_BITS); } @@ -165,7 +172,7 @@ struct WebAudioUtils { * it sees a NaN. */ template - static IntType TruncateFloatToInt(FloatType f) + IntType TruncateFloatToInt(FloatType f) { using namespace std; @@ -196,26 +203,26 @@ struct WebAudioUtils { return IntType(f); } - static void Shutdown(); + void Shutdown(); - static int + int SpeexResamplerProcess(SpeexResamplerState* aResampler, uint32_t aChannel, const float* aIn, uint32_t* aInLen, float* aOut, uint32_t* aOutLen); - static int + int SpeexResamplerProcess(SpeexResamplerState* aResampler, uint32_t aChannel, const int16_t* aIn, uint32_t* aInLen, float* aOut, uint32_t* aOutLen); - static int + int SpeexResamplerProcess(SpeexResamplerState* aResampler, uint32_t aChannel, const int16_t* aIn, uint32_t* aInLen, int16_t* aOut, uint32_t* aOutLen); - }; + } } } diff --git a/content/media/webaudio/blink/DynamicsCompressorKernel.cpp b/content/media/webaudio/blink/DynamicsCompressorKernel.cpp index 57a71ed8ded..c6f9256e3c2 100644 --- a/content/media/webaudio/blink/DynamicsCompressorKernel.cpp +++ b/content/media/webaudio/blink/DynamicsCompressorKernel.cpp @@ -37,7 +37,7 @@ using namespace std; -using mozilla::dom::WebAudioUtils; +using namespace mozilla::dom; // for WebAudioUtils using mozilla::IsInfinite; using mozilla::IsNaN; From b11287322dab3b5c581d91508221df3ddd0d0e92 Mon Sep 17 00:00:00 2001 From: Karl Tomlinson Date: Fri, 16 May 2014 08:44:18 +1200 Subject: [PATCH 06/70] b=991533 limit OfflineAudioContext to the same sample rates as AudioBuffer r=padenot OfflineAudioCompletionEvent needs to use AudioBuffer for its output, and so the AudioContext should run at the rates supported by AudioBuffer. --HG-- extra : transplant_source : %F2%A0%90%E6%DD%21%15%CDBa%F4%24%93%22%FA%A3%D8%12KU --- content/media/webaudio/AudioContext.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/media/webaudio/AudioContext.cpp b/content/media/webaudio/AudioContext.cpp index 9ed4b91ad50..0bbe2a8b772 100644 --- a/content/media/webaudio/AudioContext.cpp +++ b/content/media/webaudio/AudioContext.cpp @@ -172,8 +172,8 @@ AudioContext::Constructor(const GlobalObject& aGlobal, if (aNumberOfChannels == 0 || aNumberOfChannels > WebAudioUtils::MaxChannelCount || aLength == 0 || - aSampleRate <= 1.0f || - aSampleRate >= TRACK_RATE_MAX) { + aSampleRate < WebAudioUtils::MinSampleRate || + aSampleRate > WebAudioUtils::MaxSampleRate) { // The DOM binding protects us against infinity and NaN aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return nullptr; From 651d766fc4fe0ab2fc508fd6d6081b74d9bd5836 Mon Sep 17 00:00:00 2001 From: Karl Tomlinson Date: Fri, 16 May 2014 09:11:13 +1200 Subject: [PATCH 07/70] b=991533 throw exception from AudioProcessingEvent buffer getters when allocation fails r=ehsan,bz --HG-- extra : transplant_source : C%60%E5f6%1D%D3%0F%D6%0B%9CV%A6%AD%C5%5D%E9%9B%C6%BD --- .../media/webaudio/AudioProcessingEvent.cpp | 21 ++++++++++++------- content/media/webaudio/AudioProcessingEvent.h | 12 +++++------ .../media/webaudio/ScriptProcessorNode.cpp | 12 +++++++++-- dom/webidl/AudioProcessingEvent.webidl | 9 +++++--- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/content/media/webaudio/AudioProcessingEvent.cpp b/content/media/webaudio/AudioProcessingEvent.cpp index 4cb44d9382e..01962fd09b4 100644 --- a/content/media/webaudio/AudioProcessingEvent.cpp +++ b/content/media/webaudio/AudioProcessingEvent.cpp @@ -37,23 +37,30 @@ AudioProcessingEvent::WrapObject(JSContext* aCx) return AudioProcessingEventBinding::Wrap(aCx, this); } -void -AudioProcessingEvent::LazilyCreateBuffer(nsRefPtr& aBuffer, - uint32_t aNumberOfChannels) +already_AddRefed +AudioProcessingEvent::LazilyCreateBuffer(uint32_t aNumberOfChannels, + ErrorResult& aRv) { // We need the global for the context so that we can enter its compartment. JSObject* global = mNode->Context()->GetGlobalJSObject(); if (NS_WARN_IF(!global)) { - return; + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; } AutoJSAPI jsapi; JSContext* cx = jsapi.cx(); JSAutoCompartment ac(cx, global); - aBuffer = new AudioBuffer(mNode->Context(), mNode->BufferSize(), - mNode->Context()->SampleRate()); - aBuffer->InitializeBuffers(aNumberOfChannels, cx); + nsRefPtr buffer = + new AudioBuffer(mNode->Context(), mNode->BufferSize(), + mNode->Context()->SampleRate()); + if (!buffer->InitializeBuffers(aNumberOfChannels, cx)) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + + return buffer.forget(); } } diff --git a/content/media/webaudio/AudioProcessingEvent.h b/content/media/webaudio/AudioProcessingEvent.h index e32e2ebd54c..49a333cb19a 100644 --- a/content/media/webaudio/AudioProcessingEvent.h +++ b/content/media/webaudio/AudioProcessingEvent.h @@ -42,18 +42,18 @@ public: return mPlaybackTime; } - AudioBuffer* InputBuffer() + AudioBuffer* GetInputBuffer(ErrorResult& aRv) { if (!mInputBuffer) { - LazilyCreateBuffer(mInputBuffer, mNumberOfInputChannels); + mInputBuffer = LazilyCreateBuffer(mNumberOfInputChannels, aRv); } return mInputBuffer; } - AudioBuffer* OutputBuffer() + AudioBuffer* GetOutputBuffer(ErrorResult& aRv) { if (!mOutputBuffer) { - LazilyCreateBuffer(mOutputBuffer, mNode->NumberOfOutputChannels()); + mOutputBuffer = LazilyCreateBuffer(mNode->NumberOfOutputChannels(), aRv); } return mOutputBuffer; } @@ -64,8 +64,8 @@ public: } private: - void LazilyCreateBuffer(nsRefPtr& aBuffer, - uint32_t aNumberOfChannels); + already_AddRefed + LazilyCreateBuffer(uint32_t aNumberOfChannels, ErrorResult& rv); private: double mPlaybackTime; diff --git a/content/media/webaudio/ScriptProcessorNode.cpp b/content/media/webaudio/ScriptProcessorNode.cpp index 57b6d447ee5..ea441c7e434 100644 --- a/content/media/webaudio/ScriptProcessorNode.cpp +++ b/content/media/webaudio/ScriptProcessorNode.cpp @@ -438,10 +438,18 @@ private: mPlaybackTime); node->DispatchTrustedEvent(event); - // Steal the output buffers + // Steal the output buffers if they have been set. + // Don't create a buffer if it hasn't been used to return output; + // FinishProducingOutputBuffer() will optimize output = null. + // GetThreadSharedChannelsForRate() may also return null after OOM. nsRefPtr output; if (event->HasOutputBuffer()) { - output = event->OutputBuffer()->GetThreadSharedChannelsForRate(cx); + ErrorResult rv; + AudioBuffer* buffer = event->GetOutputBuffer(rv); + // HasOutputBuffer() returning true means that GetOutputBuffer() + // will not fail. + MOZ_ASSERT(!rv.Failed()); + output = buffer->GetThreadSharedChannelsForRate(cx); } // Append it to our output buffer queue diff --git a/dom/webidl/AudioProcessingEvent.webidl b/dom/webidl/AudioProcessingEvent.webidl index 9d061499e09..17ee321fb19 100644 --- a/dom/webidl/AudioProcessingEvent.webidl +++ b/dom/webidl/AudioProcessingEvent.webidl @@ -12,9 +12,12 @@ interface AudioProcessingEvent : Event { - readonly attribute double playbackTime; - readonly attribute AudioBuffer inputBuffer; - readonly attribute AudioBuffer outputBuffer; + readonly attribute double playbackTime; + + [Throws] + readonly attribute AudioBuffer inputBuffer; + [Throws] + readonly attribute AudioBuffer outputBuffer; }; From 63192b0276aeb0be8966433c95d160708098cbdf Mon Sep 17 00:00:00 2001 From: Karl Tomlinson Date: Fri, 16 May 2014 09:23:27 +1200 Subject: [PATCH 08/70] b=991533 limit AudioBuffers from decodeAudioData to the same sample rates as createBuffer r=ehsan The numberOfChannels array on AudioBuffer is now an infallible array, as this is considerably smaller than infallible channel data array allocations in AllocateAudioBlock and similar to channel data pointer array allocations in AudioChunk. --HG-- extra : transplant_source : C%29_%13%9C%9C%A1%E1%A3%E8%C9_%93%11%85lM%FC%7E%BC --- content/media/webaudio/AudioBuffer.cpp | 44 ++++++++++++------- content/media/webaudio/AudioBuffer.h | 18 ++++---- content/media/webaudio/AudioContext.cpp | 19 ++------ .../media/webaudio/AudioDestinationNode.cpp | 9 ++-- .../media/webaudio/AudioProcessingEvent.cpp | 11 ++--- content/media/webaudio/MediaBufferDecoder.cpp | 6 ++- .../media/webaudio/ScriptProcessorNode.cpp | 10 +++-- 7 files changed, 60 insertions(+), 57 deletions(-) diff --git a/content/media/webaudio/AudioBuffer.cpp b/content/media/webaudio/AudioBuffer.cpp index 7b62c57ed99..65991779f3a 100644 --- a/content/media/webaudio/AudioBuffer.cpp +++ b/content/media/webaudio/AudioBuffer.cpp @@ -41,12 +41,13 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AudioBuffer, AddRef) NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AudioBuffer, Release) -AudioBuffer::AudioBuffer(AudioContext* aContext, uint32_t aLength, - float aSampleRate) +AudioBuffer::AudioBuffer(AudioContext* aContext, uint32_t aNumberOfChannels, + uint32_t aLength, float aSampleRate) : mContext(aContext), mLength(aLength), mSampleRate(aSampleRate) { + mJSChannels.SetCapacity(aNumberOfChannels); SetIsDOMBinding(); mozilla::HoldJSObjects(this); } @@ -63,22 +64,35 @@ AudioBuffer::ClearJSChannels() mozilla::DropJSObjects(this); } -bool -AudioBuffer::InitializeBuffers(uint32_t aNumberOfChannels, JSContext* aJSContext) +/* static */ already_AddRefed +AudioBuffer::Create(AudioContext* aContext, uint32_t aNumberOfChannels, + uint32_t aLength, float aSampleRate, + JSContext* aJSContext, ErrorResult& aRv) { - if (!mJSChannels.SetCapacity(aNumberOfChannels)) { - return false; - } - for (uint32_t i = 0; i < aNumberOfChannels; ++i) { - JS::Rooted array(aJSContext, - JS_NewFloat32Array(aJSContext, mLength)); - if (!array) { - return false; - } - mJSChannels.AppendElement(array.get()); + // Note that a buffer with zero channels is permitted here for the sake of + // AudioProcessingEvent, where channel counts must match parameters passed + // to createScriptProcessor(), one of which may be zero. + if (aSampleRate < WebAudioUtils::MinSampleRate || + aSampleRate > WebAudioUtils::MaxSampleRate || + !aLength || aLength > INT32_MAX) { + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return nullptr; } - return true; + nsRefPtr buffer = + new AudioBuffer(aContext, aNumberOfChannels, aLength, aSampleRate); + + for (uint32_t i = 0; i < aNumberOfChannels; ++i) { + JS::Rooted array(aJSContext, + JS_NewFloat32Array(aJSContext, aLength)); + if (!array) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + buffer->mJSChannels.AppendElement(array.get()); + } + + return buffer.forget(); } JSObject* diff --git a/content/media/webaudio/AudioBuffer.h b/content/media/webaudio/AudioBuffer.h index 188e9caa565..66a6580f7b2 100644 --- a/content/media/webaudio/AudioBuffer.h +++ b/content/media/webaudio/AudioBuffer.h @@ -33,17 +33,13 @@ class AudioContext; class AudioBuffer MOZ_FINAL : public nsWrapperCache { public: - AudioBuffer(AudioContext* aContext, uint32_t aLength, - float aSampleRate); - ~AudioBuffer(); + static already_AddRefed + Create(AudioContext* aContext, uint32_t aNumberOfChannels, + uint32_t aLength, float aSampleRate, + JSContext* aJSContext, ErrorResult& aRv); size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; - // This function needs to be called in order to allocate - // all of the channels. It is fallible! - bool InitializeBuffers(uint32_t aNumberOfChannels, - JSContext* aJSContext); - NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AudioBuffer) NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(AudioBuffer) @@ -100,12 +96,16 @@ public: void SetRawChannelContents(uint32_t aChannel, float* aContents); protected: + AudioBuffer(AudioContext* aContext, uint32_t aNumberOfChannels, + uint32_t aLength, float aSampleRate); + ~AudioBuffer(); + bool RestoreJSChannelData(JSContext* aJSContext); void ClearJSChannels(); nsRefPtr mContext; // Float32Arrays - AutoFallibleTArray, 2> mJSChannels; + nsAutoTArray, 2> mJSChannels; // mSharedChannels aggregates the data from mJSChannels. This is non-null // if and only if the mJSChannels are neutered. diff --git a/content/media/webaudio/AudioContext.cpp b/content/media/webaudio/AudioContext.cpp index 0bbe2a8b772..825685f668a 100644 --- a/content/media/webaudio/AudioContext.cpp +++ b/content/media/webaudio/AudioContext.cpp @@ -204,26 +204,13 @@ AudioContext::CreateBuffer(JSContext* aJSContext, uint32_t aNumberOfChannels, uint32_t aLength, float aSampleRate, ErrorResult& aRv) { - if (aSampleRate < WebAudioUtils::MinSampleRate || - aSampleRate > WebAudioUtils::MaxSampleRate || - !aLength || !aNumberOfChannels) { + if (!aNumberOfChannels) { aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); return nullptr; } - if (aLength > INT32_MAX) { - aRv.Throw(NS_ERROR_OUT_OF_MEMORY); - return nullptr; - } - - nsRefPtr buffer = - new AudioBuffer(this, int32_t(aLength), aSampleRate); - if (!buffer->InitializeBuffers(aNumberOfChannels, aJSContext)) { - aRv.Throw(NS_ERROR_OUT_OF_MEMORY); - return nullptr; - } - - return buffer.forget(); + return AudioBuffer::Create(this, aNumberOfChannels, aLength, + aSampleRate, aJSContext, aRv); } namespace { diff --git a/content/media/webaudio/AudioDestinationNode.cpp b/content/media/webaudio/AudioDestinationNode.cpp index f72215571b0..255e80b5201 100644 --- a/content/media/webaudio/AudioDestinationNode.cpp +++ b/content/media/webaudio/AudioDestinationNode.cpp @@ -137,10 +137,11 @@ public: JSAutoCompartment ac(cx, global); // Create the input buffer - nsRefPtr renderedBuffer = new AudioBuffer(context, - mLength, - mSampleRate); - if (!renderedBuffer->InitializeBuffers(mInputChannels.Length(), cx)) { + ErrorResult rv; + nsRefPtr renderedBuffer = + AudioBuffer::Create(context, mInputChannels.Length(), + mLength, mSampleRate, cx, rv); + if (rv.Failed()) { return; } for (uint32_t i = 0; i < mInputChannels.Length(); ++i) { diff --git a/content/media/webaudio/AudioProcessingEvent.cpp b/content/media/webaudio/AudioProcessingEvent.cpp index 01962fd09b4..9139713d011 100644 --- a/content/media/webaudio/AudioProcessingEvent.cpp +++ b/content/media/webaudio/AudioProcessingEvent.cpp @@ -53,13 +53,10 @@ AudioProcessingEvent::LazilyCreateBuffer(uint32_t aNumberOfChannels, JSAutoCompartment ac(cx, global); nsRefPtr buffer = - new AudioBuffer(mNode->Context(), mNode->BufferSize(), - mNode->Context()->SampleRate()); - if (!buffer->InitializeBuffers(aNumberOfChannels, cx)) { - aRv.Throw(NS_ERROR_OUT_OF_MEMORY); - return nullptr; - } - + AudioBuffer::Create(mNode->Context(), aNumberOfChannels, + mNode->BufferSize(), + mNode->Context()->SampleRate(), cx, aRv); + MOZ_ASSERT(buffer || aRv.ErrorCode() == NS_ERROR_OUT_OF_MEMORY); return buffer.forget(); } diff --git a/content/media/webaudio/MediaBufferDecoder.cpp b/content/media/webaudio/MediaBufferDecoder.cpp index d071f9c0fae..a113b110c24 100644 --- a/content/media/webaudio/MediaBufferDecoder.cpp +++ b/content/media/webaudio/MediaBufferDecoder.cpp @@ -413,8 +413,10 @@ WebAudioDecodeJob::AllocateBuffer() JSAutoCompartment ac(cx, global); // Now create the AudioBuffer - mOutput = new AudioBuffer(mContext, mWriteIndex, mContext->SampleRate()); - if (!mOutput->InitializeBuffers(mChannelBuffers.Length(), cx)) { + ErrorResult rv; + mOutput = AudioBuffer::Create(mContext, mChannelBuffers.Length(), + mWriteIndex, mContext->SampleRate(), cx, rv); + if (rv.Failed()) { return false; } diff --git a/content/media/webaudio/ScriptProcessorNode.cpp b/content/media/webaudio/ScriptProcessorNode.cpp index ea441c7e434..51b287c335e 100644 --- a/content/media/webaudio/ScriptProcessorNode.cpp +++ b/content/media/webaudio/ScriptProcessorNode.cpp @@ -415,10 +415,12 @@ private: // Create the input buffer nsRefPtr inputBuffer; if (!mNullInput) { - inputBuffer = new AudioBuffer(node->Context(), - node->BufferSize(), - node->Context()->SampleRate()); - if (!inputBuffer->InitializeBuffers(mInputChannels.Length(), cx)) { + ErrorResult rv; + inputBuffer = + AudioBuffer::Create(node->Context(), mInputChannels.Length(), + node->BufferSize(), + node->Context()->SampleRate(), cx, rv); + if (rv.Failed()) { return NS_OK; } // Put the channel data inside it From 4980cab4b03582c46f46fcefe51c71a799ededf5 Mon Sep 17 00:00:00 2001 From: Karl Tomlinson Date: Fri, 16 May 2014 09:23:38 +1200 Subject: [PATCH 09/70] b=991533 limit AudioBuffer channel count r=padenot --HG-- extra : transplant_source : %28%F0%2Cc%FE%60%B3%EE%A0O%3F%E6%C4%C4%F9%88%1C%E3%89%09 --- content/media/webaudio/AudioBuffer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/content/media/webaudio/AudioBuffer.cpp b/content/media/webaudio/AudioBuffer.cpp index 65991779f3a..eb968107372 100644 --- a/content/media/webaudio/AudioBuffer.cpp +++ b/content/media/webaudio/AudioBuffer.cpp @@ -74,6 +74,7 @@ AudioBuffer::Create(AudioContext* aContext, uint32_t aNumberOfChannels, // to createScriptProcessor(), one of which may be zero. if (aSampleRate < WebAudioUtils::MinSampleRate || aSampleRate > WebAudioUtils::MaxSampleRate || + aNumberOfChannels > WebAudioUtils::MaxChannelCount || !aLength || aLength > INT32_MAX) { aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); return nullptr; From 152a304e3b425ddc3df18957495d06d4921967f0 Mon Sep 17 00:00:00 2001 From: Karl Tomlinson Date: Fri, 16 May 2014 09:23:38 +1200 Subject: [PATCH 10/70] b=990868 limit ChannelMergerNode output channel count r=padenot --HG-- extra : transplant_source : 5S-%40T%8E%8B%F8%B4W%1F%B3%BA5O%83%BF%044%23 --- content/media/webaudio/ChannelMergerNode.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/content/media/webaudio/ChannelMergerNode.cpp b/content/media/webaudio/ChannelMergerNode.cpp index e902a768f3e..cfe1ce0c8c9 100644 --- a/content/media/webaudio/ChannelMergerNode.cpp +++ b/content/media/webaudio/ChannelMergerNode.cpp @@ -31,7 +31,7 @@ public: MOZ_ASSERT(aInput.Length() >= 1, "Should have one or more input ports"); // Get the number of output channels, and allocate it - uint32_t channelCount = 0; + size_t channelCount = 0; for (uint16_t i = 0; i < InputCount(); ++i) { channelCount += aInput[i].mChannelData.Length(); } @@ -39,17 +39,22 @@ public: aOutput[0].SetNull(WEBAUDIO_BLOCK_SIZE); return; } + channelCount = std::min(channelCount, WebAudioUtils::MaxChannelCount); AllocateAudioBlock(channelCount, &aOutput[0]); // Append each channel in each input to the output - uint32_t channelIndex = 0; - for (uint16_t i = 0; i < InputCount(); ++i) { - for (uint32_t j = 0; j < aInput[i].mChannelData.Length(); ++j) { + size_t channelIndex = 0; + for (uint16_t i = 0; true; ++i) { + MOZ_ASSERT(i < InputCount()); + for (size_t j = 0; j < aInput[i].mChannelData.Length(); ++j) { AudioBlockCopyChannelWithScale( static_cast(aInput[i].mChannelData[j]), aInput[i].mVolume, static_cast(const_cast(aOutput[0].mChannelData[channelIndex]))); ++channelIndex; + if (channelIndex >= channelCount) { + return; + } } } } From 41be280e9fba0ed2c0f17f6e4ae73718bdb427dd Mon Sep 17 00:00:00 2001 From: Daniel Holbert Date: Thu, 15 May 2014 16:29:21 -0700 Subject: [PATCH 11/70] Bug 1010470: Use :disabled pseudoclass instead of [disabled] attribute-selector to style disabled form controls on B2G & Android. r=fabrice --HG-- rename : layout/reftests/forms/button/disabled-2.html => layout/reftests/forms/button/disabled-2a.html rename : layout/reftests/forms/button/disabled-2.html => layout/reftests/forms/button/disabled-2b.html --- b2g/chrome/content/content.css | 50 ++++++------- .../{disabled-2.html => disabled-2a.html} | 0 layout/reftests/forms/button/disabled-2b.html | 18 +++++ layout/reftests/forms/button/reftest.list | 3 +- mobile/android/themes/core/content.css | 70 +++++++++---------- 5 files changed, 80 insertions(+), 61 deletions(-) rename layout/reftests/forms/button/{disabled-2.html => disabled-2a.html} (100%) create mode 100644 layout/reftests/forms/button/disabled-2b.html diff --git a/b2g/chrome/content/content.css b/b2g/chrome/content/content.css index bd8f54a349f..c038779b34f 100644 --- a/b2g/chrome/content/content.css +++ b/b2g/chrome/content/content.css @@ -234,15 +234,15 @@ input[type="radio"]:focus { } /* we need to be specific for selects because the above rules are specific too */ -textarea[disabled], -select[size][disabled], -select[multiple][disabled], -select[size][multiple][disabled], -select:not([size]):not([multiple])[disabled], -select[size="0"][disabled], -select[size="1"][disabled], -button[disabled], -* > input:not([type="image"])[disabled] { +textarea:disabled, +select[size]:disabled, +select[multiple]:disabled, +select[size][multiple]:disabled, +select:not([size]):not([multiple]):disabled, +select[size="0"]:disabled, +select[size="1"]:disabled, +button:disabled, +* > input:not([type="image"]):disabled { color: rgba(0,0,0,0.3); border-color: rgba(125,125,125,0.4); border-style: solid; @@ -250,32 +250,32 @@ button[disabled], background: transparent linear-gradient(rgba(185,185,185,0.4) 0, rgba(235,235,235,0.4) 3px, rgba(255,255,255,0.4) 100%); } -select:not([size]):not([multiple])[disabled], -select[size="0"][disabled], -select[size="1"][disabled] { +select:not([size]):not([multiple]):disabled, +select[size="0"]:disabled, +select[size="1"]:disabled { background: transparent linear-gradient(rgba(255,255,255,0.4) 0, rgba(235,235,235,0.4) 3px, rgba(185,185,185,0.4) 100%); } -input[type="button"][disabled], -input[type="submit"][disabled], -input[type="reset"][disabled], -button[disabled] { +input[type="button"]:disabled, +input[type="submit"]:disabled, +input[type="reset"]:disabled, +button:disabled { padding: 0 7px 0 7px; background: transparent linear-gradient(rgba(255,255,255,0.4) 0, rgba(235,235,235,0.4) 3px, rgba(185,185,185,0.4) 100%); } -input[type="radio"][disabled], -input[type="radio"][disabled]:active, -input[type="radio"][disabled]:hover, -input[type="radio"][disabled]:hover:active, -input[type="checkbox"][disabled], -input[type="checkbox"][disabled]:active, -input[type="checkbox"][disabled]:hover, -input[type="checkbox"][disabled]:hover:active { +input[type="radio"]:disabled, +input[type="radio"]:disabled:active, +input[type="radio"]:disabled:hover, +input[type="radio"]:disabled:hover:active, +input[type="checkbox"]:disabled, +input[type="checkbox"]:disabled:active, +input[type="checkbox"]:disabled:hover, +input[type="checkbox"]:disabled:hover:active { border:1px solid rgba(125,125,125,0.4) !important; } -select[disabled] > button { +select:disabled > button { opacity: 0.6; padding: 1px 7px 1px 7px; } diff --git a/layout/reftests/forms/button/disabled-2.html b/layout/reftests/forms/button/disabled-2a.html similarity index 100% rename from layout/reftests/forms/button/disabled-2.html rename to layout/reftests/forms/button/disabled-2a.html diff --git a/layout/reftests/forms/button/disabled-2b.html b/layout/reftests/forms/button/disabled-2b.html new file mode 100644 index 00000000000..4dc97b0af12 --- /dev/null +++ b/layout/reftests/forms/button/disabled-2b.html @@ -0,0 +1,18 @@ + + + +Bug 1010470: test that buttons in a disabled fieldset look the same as directly-disabled buttons + + + + + + +
+ + + + +
+ + diff --git a/layout/reftests/forms/button/reftest.list b/layout/reftests/forms/button/reftest.list index 82cb90c7135..67f22c47edf 100644 --- a/layout/reftests/forms/button/reftest.list +++ b/layout/reftests/forms/button/reftest.list @@ -21,7 +21,8 @@ fuzzy-if(B2G||Android,125,80) == percent-width-child-2.html percent-width-child # Looks like Android and B2G change the text color, but to something slightly # different from ColorGray fails-if(Android||B2G) == disabled-1.html disabled-1-ref.html -== disabled-2.html disabled-2-ref.html +== disabled-2a.html disabled-2-ref.html +== disabled-2b.html disabled-2-ref.html != disabled-3.html disabled-3-notref.html != disabled-4.html disabled-4-notref.html diff --git a/mobile/android/themes/core/content.css b/mobile/android/themes/core/content.css index 3b5655cacdf..06f0fb59df0 100644 --- a/mobile/android/themes/core/content.css +++ b/mobile/android/themes/core/content.css @@ -240,17 +240,17 @@ input[type="radio"]:focus { } /* we need to be specific for selects because the above rules are specific too */ -textarea[disabled], -select[size][disabled], -select[multiple][disabled], -select[size][multiple][disabled], -select:not([size]):not([multiple])[disabled], -select[size="0"][disabled], -select[size="1"][disabled], -button[disabled], -button[disabled]:active, -* > input:not([type="image"])[disabled], -* > input:not([type="image"])[disabled]:active { +textarea:disabled, +select[size]:disabled, +select[multiple]:disabled, +select[size][multiple]:disabled, +select:not([size]):not([multiple]):disabled, +select[size="0"]:disabled, +select[size="1"]:disabled, +button:disabled, +button:disabled:active, +* > input:not([type="image"]):disabled, +* > input:not([type="image"]):disabled:active { color: rgba(0,0,0,0.3); border-color: rgba(125,125,125,0.4); border-style: solid; @@ -258,36 +258,36 @@ button[disabled]:active, background: transparent linear-gradient(rgba(185,185,185,0.4) 0, rgba(235,235,235,0.4) 3px, rgba(255,255,255,0.4) 100%); } -select:not([size]):not([multiple])[disabled], -select[size="0"][disabled], -select[size="1"][disabled] { +select:not([size]):not([multiple]):disabled, +select[size="0"]:disabled, +select[size="1"]:disabled { background: transparent linear-gradient(rgba(255,255,255,0.4) 0, rgba(235,235,235,0.4) 3px, rgba(185,185,185,0.4) 100%); } -input[type="button"][disabled], -input[type="button"][disabled]:active, -input[type="submit"][disabled], -input[type="submit"][disabled]:active, -input[type="reset"][disabled], -input[type="reset"][disabled]:active, -button[disabled], -button[disabled]:active { +input[type="button"]:disabled, +input[type="button"]:disabled:active, +input[type="submit"]:disabled, +input[type="submit"]:disabled:active, +input[type="reset"]:disabled, +input[type="reset"]:disabled:active, +button:disabled, +button:disabled:active { padding: 0 7px 0 7px; background: transparent linear-gradient(rgba(255,255,255,0.4) 0, rgba(235,235,235,0.4) 3px, rgba(185,185,185,0.4) 100%); } -input[type="radio"][disabled], -input[type="radio"][disabled]:active, -input[type="radio"][disabled]:hover, -input[type="radio"][disabled]:hover:active, -input[type="checkbox"][disabled], -input[type="checkbox"][disabled]:active, -input[type="checkbox"][disabled]:hover, -input[type="checkbox"][disabled]:hover:active { +input[type="radio"]:disabled, +input[type="radio"]:disabled:active, +input[type="radio"]:disabled:hover, +input[type="radio"]:disabled:hover:active, +input[type="checkbox"]:disabled, +input[type="checkbox"]:disabled:active, +input[type="checkbox"]:disabled:hover, +input[type="checkbox"]:disabled:hover:active { border:1px solid rgba(125,125,125,0.4) !important; } -select[disabled] > button { +select:disabled > button { opacity: 0.6; padding: 1px 7px 1px 7px; } @@ -299,10 +299,10 @@ select[disabled] > button { *:-moz-any-link:active, *[role=button]:active, -button:not([disabled]):active, -input:not(:focus):not([disabled]):active, -select:not([disabled]):active, -textarea:not(:focus):not([disabled]):active, +button:enabled:active, +input:not(:focus):enabled:active, +select:enabled:active, +textarea:not(:focus):enabled:active, option:active, label:active, xul|menulist:active { From b7888e2abd687c3d210e04d8365c515dfc9c288e Mon Sep 17 00:00:00 2001 From: Daniel Holbert Date: Thu, 15 May 2014 16:29:27 -0700 Subject: [PATCH 12/70] Bug 1010621: Remove redundant definition for helper-function 'IsCompilingAsmJS'. r=djvj --- js/src/jit/IonMacroAssembler.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/js/src/jit/IonMacroAssembler.cpp b/js/src/jit/IonMacroAssembler.cpp index 1bb89bdf061..aefeb853071 100644 --- a/js/src/jit/IonMacroAssembler.cpp +++ b/js/src/jit/IonMacroAssembler.cpp @@ -1204,14 +1204,6 @@ MacroAssembler::handleFailure(ExecutionMode executionMode) } #ifdef DEBUG -static inline bool -IsCompilingAsmJS() -{ - // asm.js compilation pushes an IonContext with a null JSCompartment. - IonContext *ictx = MaybeGetIonContext(); - return ictx && ictx->compartment == nullptr; -} - static void AssumeUnreachable_(const char *output) { MOZ_ReportAssertionFailure(output, __FILE__, __LINE__); From 3440c8ff6666682901b79da9ba725782bd3f32c7 Mon Sep 17 00:00:00 2001 From: Honza Bambas Date: Thu, 15 May 2014 16:31:26 -0700 Subject: [PATCH 13/70] Bug 913806 - turn HTTP cache v2 on by default, r=jduell --- modules/libpref/src/init/all.js | 5 ++--- netwerk/cache2/CacheObserver.cpp | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index 0d1c1773465..56d8f6874df 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -35,11 +35,10 @@ pref("general.warnOnAboutConfig", true); pref("browser.bookmarks.max_backups", 5); // Preference for switching the cache backend, can be changed freely at runtime -// 0 - use the old (Darin's) cache [DEFAULT] +// 0 - use the old (Darin's) cache // 1 - use the new cache back-end (cache v2) -// 2 - do a random choise for A/B testing (browser chooses old or new back-end at startup -// and keeps it per session) pref("browser.cache.use_new_backend", 0); +pref("browser.cache.use_new_backend_temp", true); pref("browser.cache.disk.enable", true); // Is this the first-time smartsizing has been introduced? diff --git a/netwerk/cache2/CacheObserver.cpp b/netwerk/cache2/CacheObserver.cpp index 5ee4324fe0b..a3c44cc4b1f 100644 --- a/netwerk/cache2/CacheObserver.cpp +++ b/netwerk/cache2/CacheObserver.cpp @@ -22,7 +22,7 @@ namespace net { CacheObserver* CacheObserver::sSelf = nullptr; -static uint32_t const kDefaultUseNewCache = 0; // Don't use the new cache by default +static uint32_t const kDefaultUseNewCache = 1; // Use the new cache by default uint32_t CacheObserver::sUseNewCache = kDefaultUseNewCache; static bool sUseNewCacheTemp = false; // Temp trigger to not lose early adopters From b2437acd816799089810e003a19866a10b9e57e8 Mon Sep 17 00:00:00 2001 From: Peter Moore Date: Thu, 15 May 2014 19:25:35 +0200 Subject: [PATCH 14/70] Bug 1010173 - test root internal variable on devices (SUTAgentAndroid.sTestRoot) should not be set as an error message,r=bc --- build/mobile/sutagent/android/DoCommand.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/build/mobile/sutagent/android/DoCommand.java b/build/mobile/sutagent/android/DoCommand.java index 9868f421303..8d5fea57aa5 100755 --- a/build/mobile/sutagent/android/DoCommand.java +++ b/build/mobile/sutagent/android/DoCommand.java @@ -1464,9 +1464,6 @@ private void CancelNotification() Log.e("SUTAgentAndroid", "Cannot access world writeable test root"); } } - if (!success) { - SUTAgentAndroid.sTestRoot = sErrorPrefix + " unable to determine test root"; - } } public String GetTestRoot() From f023ce771c40fc7c1881d85ac028462e6362daa9 Mon Sep 17 00:00:00 2001 From: Monica Chew Date: Wed, 14 May 2014 16:36:46 -0700 Subject: [PATCH 15/70] Bug 1006594: Implement moz-specific telemetry (r=keeler) --- .../boot/src/PublicKeyPinningService.cpp | 22 +- security/manager/boot/src/StaticHPKPins.h | 615 +++++++++--------- .../manager/ssl/tests/unit/test_pinning.js | 13 +- security/manager/tools/genHPKPStaticPins.js | 7 + toolkit/components/telemetry/Histograms.json | 10 + 5 files changed, 348 insertions(+), 319 deletions(-) diff --git a/security/manager/boot/src/PublicKeyPinningService.cpp b/security/manager/boot/src/PublicKeyPinningService.cpp index d401467970a..db006fb512f 100644 --- a/security/manager/boot/src/PublicKeyPinningService.cpp +++ b/security/manager/boot/src/PublicKeyPinningService.cpp @@ -72,13 +72,10 @@ EvalCertWithHashType(const CERTCertificate* cert, SECOidTag hashType, return false; } - PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG, - ("pkpin: base_64(hash(key)='%s'\n", base64Out.get())); - for (size_t i = 0; i < fingerprints->size; i++) { if (base64Out.Equals(fingerprints->data[i])) { PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG, - ("pkpin: found pin base_64(hash(key)='%s'\n", base64Out.get())); + ("pkpin: found pin base_64 ='%s'\n", base64Out.get())); return true; } } @@ -110,7 +107,7 @@ EvalChainWithHashType(const CERTCertList* certList, SECOidTag hashType, node = CERT_LIST_NEXT(node)) { currentCert = node->cert; PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG, - ("pkpin: certArray subject: '%s'\n", + ("pkpin: certArray subject: '%s'\n", currentCert->subjectName)); PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG, ("pkpin: certArray common_name: '%s'\n", @@ -195,16 +192,21 @@ CheckPinsForHostname(const CERTCertList *certList, const char *hostname, if (foundEntry && foundEntry->pinset) { bool result = EvalChainWithPinset(certList, foundEntry->pinset); bool retval = result; - Telemetry::ID histogram = Telemetry::CERT_PINNING_RESULTS; + Telemetry::ID histogram = foundEntry->mIsMoz + ? Telemetry::CERT_PINNING_MOZ_RESULTS + : Telemetry::CERT_PINNING_RESULTS; if (foundEntry->mTestMode) { - histogram = Telemetry::CERT_PINNING_TEST_RESULTS; + histogram = foundEntry->mIsMoz + ? Telemetry::CERT_PINNING_MOZ_TEST_RESULTS + : Telemetry::CERT_PINNING_TEST_RESULTS; retval = true; } Telemetry::Accumulate(histogram, result ? 1 : 0); PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG, - ("pkpin: Pin check %s for host '%s' (mode=%s)\n", - result ? "passed" : "failed", evalHost, - foundEntry->mTestMode ? "test" : "production")); + ("pkpin: Pin check %s for %s host '%s' (mode=%s)\n", + result ? "passed" : "failed", + foundEntry->mIsMoz ? "mozilla" : "non-mozilla", + hostname, foundEntry->mTestMode ? "test" : "production")); return retval; } return true; // No pinning information for this hostname diff --git a/security/manager/boot/src/StaticHPKPins.h b/security/manager/boot/src/StaticHPKPins.h index 0ce1922630c..9b563a9dd43 100644 --- a/security/manager/boot/src/StaticHPKPins.h +++ b/security/manager/boot/src/StaticHPKPins.h @@ -459,319 +459,320 @@ struct TransportSecurityPreload { const char* mHost; const bool mIncludeSubdomains; const bool mTestMode; + const bool mIsMoz; const StaticPinset *pinset; }; /* Sort hostnames for binary search. */ static const TransportSecurityPreload kPublicKeyPinningPreloadList[] = { - { "accounts.google.com", true, true, &kPinset_google }, - { "addons.mozilla.net", true, true, &kPinset_mozilla }, - { "addons.mozilla.org", true, true, &kPinset_mozilla }, - { "admin.google.com", true, true, &kPinset_google }, - { "android.com", true, true, &kPinset_google }, - { "api.twitter.com", true, true, &kPinset_twitterCDN }, - { "apis.google.com", true, true, &kPinset_google }, - { "appengine.google.com", true, true, &kPinset_google }, - { "appspot.com", true, true, &kPinset_google }, - { "blog.torproject.org", true, true, &kPinset_tor }, - { "business.twitter.com", true, true, &kPinset_twitterCom }, - { "cdn.mozilla.net", true, true, &kPinset_mozilla }, - { "cdn.mozilla.org", true, true, &kPinset_mozilla }, - { "chart.apis.google.com", true, true, &kPinset_google }, - { "check.torproject.org", true, true, &kPinset_tor }, - { "checkout.google.com", true, true, &kPinset_google }, - { "chrome-devtools-frontend.appspot.com", true, true, &kPinset_google }, - { "chrome.google.com", true, true, &kPinset_google }, - { "chromiumcodereview.appspot.com", true, true, &kPinset_google }, - { "cloud.google.com", true, true, &kPinset_google }, - { "code.google.com", true, true, &kPinset_google }, - { "codereview.appspot.com", true, true, &kPinset_google }, - { "codereview.chromium.org", true, true, &kPinset_google }, - { "crypto.cat", false, true, &kPinset_cryptoCat }, - { "dev.twitter.com", true, true, &kPinset_twitterCom }, - { "dist.torproject.org", true, true, &kPinset_tor }, - { "dl.google.com", true, true, &kPinset_google }, - { "docs.google.com", true, true, &kPinset_google }, - { "doubleclick.net", true, true, &kPinset_google }, - { "drive.google.com", true, true, &kPinset_google }, - { "encrypted.google.com", true, true, &kPinset_google }, - { "exclude-subdomains.pinning.example.com", false, false, &kPinset_mozilla_test }, - { "g.co", true, true, &kPinset_google }, - { "glass.google.com", true, true, &kPinset_google }, - { "gmail.com", false, true, &kPinset_google }, - { "goo.gl", true, true, &kPinset_google }, - { "google-analytics.com", true, true, &kPinset_google }, - { "google.ac", true, true, &kPinset_google }, - { "google.ad", true, true, &kPinset_google }, - { "google.ae", true, true, &kPinset_google }, - { "google.af", true, true, &kPinset_google }, - { "google.ag", true, true, &kPinset_google }, - { "google.am", true, true, &kPinset_google }, - { "google.as", true, true, &kPinset_google }, - { "google.at", true, true, &kPinset_google }, - { "google.az", true, true, &kPinset_google }, - { "google.ba", true, true, &kPinset_google }, - { "google.be", true, true, &kPinset_google }, - { "google.bf", true, true, &kPinset_google }, - { "google.bg", true, true, &kPinset_google }, - { "google.bi", true, true, &kPinset_google }, - { "google.bj", true, true, &kPinset_google }, - { "google.bs", true, true, &kPinset_google }, - { "google.by", true, true, &kPinset_google }, - { "google.ca", true, true, &kPinset_google }, - { "google.cat", true, true, &kPinset_google }, - { "google.cc", true, true, &kPinset_google }, - { "google.cd", true, true, &kPinset_google }, - { "google.cf", true, true, &kPinset_google }, - { "google.cg", true, true, &kPinset_google }, - { "google.ch", true, true, &kPinset_google }, - { "google.ci", true, true, &kPinset_google }, - { "google.cl", true, true, &kPinset_google }, - { "google.cm", true, true, &kPinset_google }, - { "google.cn", true, true, &kPinset_google }, - { "google.co.ao", true, true, &kPinset_google }, - { "google.co.bw", true, true, &kPinset_google }, - { "google.co.ck", true, true, &kPinset_google }, - { "google.co.cr", true, true, &kPinset_google }, - { "google.co.hu", true, true, &kPinset_google }, - { "google.co.id", true, true, &kPinset_google }, - { "google.co.il", true, true, &kPinset_google }, - { "google.co.im", true, true, &kPinset_google }, - { "google.co.in", true, true, &kPinset_google }, - { "google.co.je", true, true, &kPinset_google }, - { "google.co.jp", true, true, &kPinset_google }, - { "google.co.ke", true, true, &kPinset_google }, - { "google.co.kr", true, true, &kPinset_google }, - { "google.co.ls", true, true, &kPinset_google }, - { "google.co.ma", true, true, &kPinset_google }, - { "google.co.mz", true, true, &kPinset_google }, - { "google.co.nz", true, true, &kPinset_google }, - { "google.co.th", true, true, &kPinset_google }, - { "google.co.tz", true, true, &kPinset_google }, - { "google.co.ug", true, true, &kPinset_google }, - { "google.co.uk", true, true, &kPinset_google }, - { "google.co.uz", true, true, &kPinset_google }, - { "google.co.ve", true, true, &kPinset_google }, - { "google.co.vi", true, true, &kPinset_google }, - { "google.co.za", true, true, &kPinset_google }, - { "google.co.zm", true, true, &kPinset_google }, - { "google.co.zw", true, true, &kPinset_google }, - { "google.com", true, true, &kPinset_google }, - { "google.com.af", true, true, &kPinset_google }, - { "google.com.ag", true, true, &kPinset_google }, - { "google.com.ai", true, true, &kPinset_google }, - { "google.com.ar", true, true, &kPinset_google }, - { "google.com.au", true, true, &kPinset_google }, - { "google.com.bd", true, true, &kPinset_google }, - { "google.com.bh", true, true, &kPinset_google }, - { "google.com.bn", true, true, &kPinset_google }, - { "google.com.bo", true, true, &kPinset_google }, - { "google.com.br", true, true, &kPinset_google }, - { "google.com.by", true, true, &kPinset_google }, - { "google.com.bz", true, true, &kPinset_google }, - { "google.com.cn", true, true, &kPinset_google }, - { "google.com.co", true, true, &kPinset_google }, - { "google.com.cu", true, true, &kPinset_google }, - { "google.com.cy", true, true, &kPinset_google }, - { "google.com.do", true, true, &kPinset_google }, - { "google.com.ec", true, true, &kPinset_google }, - { "google.com.eg", true, true, &kPinset_google }, - { "google.com.et", true, true, &kPinset_google }, - { "google.com.fj", true, true, &kPinset_google }, - { "google.com.ge", true, true, &kPinset_google }, - { "google.com.gh", true, true, &kPinset_google }, - { "google.com.gi", true, true, &kPinset_google }, - { "google.com.gr", true, true, &kPinset_google }, - { "google.com.gt", true, true, &kPinset_google }, - { "google.com.hk", true, true, &kPinset_google }, - { "google.com.iq", true, true, &kPinset_google }, - { "google.com.jm", true, true, &kPinset_google }, - { "google.com.jo", true, true, &kPinset_google }, - { "google.com.kh", true, true, &kPinset_google }, - { "google.com.kw", true, true, &kPinset_google }, - { "google.com.lb", true, true, &kPinset_google }, - { "google.com.ly", true, true, &kPinset_google }, - { "google.com.mt", true, true, &kPinset_google }, - { "google.com.mx", true, true, &kPinset_google }, - { "google.com.my", true, true, &kPinset_google }, - { "google.com.na", true, true, &kPinset_google }, - { "google.com.nf", true, true, &kPinset_google }, - { "google.com.ng", true, true, &kPinset_google }, - { "google.com.ni", true, true, &kPinset_google }, - { "google.com.np", true, true, &kPinset_google }, - { "google.com.nr", true, true, &kPinset_google }, - { "google.com.om", true, true, &kPinset_google }, - { "google.com.pa", true, true, &kPinset_google }, - { "google.com.pe", true, true, &kPinset_google }, - { "google.com.ph", true, true, &kPinset_google }, - { "google.com.pk", true, true, &kPinset_google }, - { "google.com.pl", true, true, &kPinset_google }, - { "google.com.pr", true, true, &kPinset_google }, - { "google.com.py", true, true, &kPinset_google }, - { "google.com.qa", true, true, &kPinset_google }, - { "google.com.ru", true, true, &kPinset_google }, - { "google.com.sa", true, true, &kPinset_google }, - { "google.com.sb", true, true, &kPinset_google }, - { "google.com.sg", true, true, &kPinset_google }, - { "google.com.sl", true, true, &kPinset_google }, - { "google.com.sv", true, true, &kPinset_google }, - { "google.com.tj", true, true, &kPinset_google }, - { "google.com.tn", true, true, &kPinset_google }, - { "google.com.tr", true, true, &kPinset_google }, - { "google.com.tw", true, true, &kPinset_google }, - { "google.com.ua", true, true, &kPinset_google }, - { "google.com.uy", true, true, &kPinset_google }, - { "google.com.vc", true, true, &kPinset_google }, - { "google.com.ve", true, true, &kPinset_google }, - { "google.com.vn", true, true, &kPinset_google }, - { "google.cv", true, true, &kPinset_google }, - { "google.cz", true, true, &kPinset_google }, - { "google.de", true, true, &kPinset_google }, - { "google.dj", true, true, &kPinset_google }, - { "google.dk", true, true, &kPinset_google }, - { "google.dm", true, true, &kPinset_google }, - { "google.dz", true, true, &kPinset_google }, - { "google.ee", true, true, &kPinset_google }, - { "google.es", true, true, &kPinset_google }, - { "google.fi", true, true, &kPinset_google }, - { "google.fm", true, true, &kPinset_google }, - { "google.fr", true, true, &kPinset_google }, - { "google.ga", true, true, &kPinset_google }, - { "google.ge", true, true, &kPinset_google }, - { "google.gg", true, true, &kPinset_google }, - { "google.gl", true, true, &kPinset_google }, - { "google.gm", true, true, &kPinset_google }, - { "google.gp", true, true, &kPinset_google }, - { "google.gr", true, true, &kPinset_google }, - { "google.gy", true, true, &kPinset_google }, - { "google.hk", true, true, &kPinset_google }, - { "google.hn", true, true, &kPinset_google }, - { "google.hr", true, true, &kPinset_google }, - { "google.ht", true, true, &kPinset_google }, - { "google.hu", true, true, &kPinset_google }, - { "google.ie", true, true, &kPinset_google }, - { "google.im", true, true, &kPinset_google }, - { "google.info", true, true, &kPinset_google }, - { "google.iq", true, true, &kPinset_google }, - { "google.is", true, true, &kPinset_google }, - { "google.it", true, true, &kPinset_google }, - { "google.it.ao", true, true, &kPinset_google }, - { "google.je", true, true, &kPinset_google }, - { "google.jo", true, true, &kPinset_google }, - { "google.jobs", true, true, &kPinset_google }, - { "google.jp", true, true, &kPinset_google }, - { "google.kg", true, true, &kPinset_google }, - { "google.ki", true, true, &kPinset_google }, - { "google.kz", true, true, &kPinset_google }, - { "google.la", true, true, &kPinset_google }, - { "google.li", true, true, &kPinset_google }, - { "google.lk", true, true, &kPinset_google }, - { "google.lt", true, true, &kPinset_google }, - { "google.lu", true, true, &kPinset_google }, - { "google.lv", true, true, &kPinset_google }, - { "google.md", true, true, &kPinset_google }, - { "google.me", true, true, &kPinset_google }, - { "google.mg", true, true, &kPinset_google }, - { "google.mk", true, true, &kPinset_google }, - { "google.ml", true, true, &kPinset_google }, - { "google.mn", true, true, &kPinset_google }, - { "google.ms", true, true, &kPinset_google }, - { "google.mu", true, true, &kPinset_google }, - { "google.mv", true, true, &kPinset_google }, - { "google.mw", true, true, &kPinset_google }, - { "google.ne", true, true, &kPinset_google }, - { "google.ne.jp", true, true, &kPinset_google }, - { "google.net", true, true, &kPinset_google }, - { "google.nl", true, true, &kPinset_google }, - { "google.no", true, true, &kPinset_google }, - { "google.nr", true, true, &kPinset_google }, - { "google.nu", true, true, &kPinset_google }, - { "google.off.ai", true, true, &kPinset_google }, - { "google.pk", true, true, &kPinset_google }, - { "google.pl", true, true, &kPinset_google }, - { "google.pn", true, true, &kPinset_google }, - { "google.ps", true, true, &kPinset_google }, - { "google.pt", true, true, &kPinset_google }, - { "google.ro", true, true, &kPinset_google }, - { "google.rs", true, true, &kPinset_google }, - { "google.ru", true, true, &kPinset_google }, - { "google.rw", true, true, &kPinset_google }, - { "google.sc", true, true, &kPinset_google }, - { "google.se", true, true, &kPinset_google }, - { "google.sh", true, true, &kPinset_google }, - { "google.si", true, true, &kPinset_google }, - { "google.sk", true, true, &kPinset_google }, - { "google.sm", true, true, &kPinset_google }, - { "google.sn", true, true, &kPinset_google }, - { "google.so", true, true, &kPinset_google }, - { "google.st", true, true, &kPinset_google }, - { "google.td", true, true, &kPinset_google }, - { "google.tg", true, true, &kPinset_google }, - { "google.tk", true, true, &kPinset_google }, - { "google.tl", true, true, &kPinset_google }, - { "google.tm", true, true, &kPinset_google }, - { "google.tn", true, true, &kPinset_google }, - { "google.to", true, true, &kPinset_google }, - { "google.tp", true, true, &kPinset_google }, - { "google.tt", true, true, &kPinset_google }, - { "google.us", true, true, &kPinset_google }, - { "google.uz", true, true, &kPinset_google }, - { "google.vg", true, true, &kPinset_google }, - { "google.vu", true, true, &kPinset_google }, - { "google.ws", true, true, &kPinset_google }, - { "googleadservices.com", true, true, &kPinset_google }, - { "googleapis.com", true, true, &kPinset_google }, - { "googlecode.com", true, true, &kPinset_google }, - { "googlecommerce.com", true, true, &kPinset_google }, - { "googlegroups.com", true, true, &kPinset_google }, - { "googlemail.com", false, true, &kPinset_google }, - { "googleplex.com", true, true, &kPinset_google }, - { "googlesyndication.com", true, true, &kPinset_google }, - { "googletagmanager.com", true, true, &kPinset_google }, - { "googletagservices.com", true, true, &kPinset_google }, - { "googleusercontent.com", true, true, &kPinset_google }, - { "goto.google.com", true, true, &kPinset_google }, - { "groups.google.com", true, true, &kPinset_google }, - { "gstatic.com", true, true, &kPinset_google }, - { "history.google.com", true, true, &kPinset_google }, - { "hostedtalkgadget.google.com", true, true, &kPinset_google }, - { "include-subdomains.pinning.example.com", true, false, &kPinset_mozilla_test }, - { "liberty.lavabit.com", true, true, &kPinset_lavabit }, - { "mail.google.com", true, true, &kPinset_google }, - { "market.android.com", true, true, &kPinset_google }, - { "media.mozilla.com", true, true, &kPinset_mozilla }, - { "mobile.twitter.com", true, true, &kPinset_twitterCom }, - { "oauth.twitter.com", true, true, &kPinset_twitterCom }, - { "pinningtest.appspot.com", true, true, &kPinset_test }, - { "platform.twitter.com", true, true, &kPinset_twitterCDN }, - { "play.google.com", false, true, &kPinset_google }, - { "plus.google.com", true, true, &kPinset_google }, - { "plus.sandbox.google.com", true, true, &kPinset_google }, - { "profiles.google.com", true, true, &kPinset_google }, - { "script.google.com", true, true, &kPinset_google }, - { "security.google.com", true, true, &kPinset_google }, - { "sites.google.com", true, true, &kPinset_google }, - { "spreadsheets.google.com", true, true, &kPinset_google }, - { "ssl.google-analytics.com", true, true, &kPinset_google }, - { "talk.google.com", true, true, &kPinset_google }, - { "talkgadget.google.com", true, true, &kPinset_google }, - { "test-mode.pinning.example.com", true, true, &kPinset_mozilla_test }, - { "tor2web.org", true, true, &kPinset_tor2web }, - { "torproject.org", false, true, &kPinset_tor }, - { "translate.googleapis.com", true, true, &kPinset_google }, - { "twimg.com", true, true, &kPinset_twitterCDN }, - { "twitter.com", false, true, &kPinset_twitterCom }, - { "urchin.com", true, true, &kPinset_google }, - { "wallet.google.com", true, true, &kPinset_google }, - { "www.gmail.com", false, true, &kPinset_google }, - { "www.googlemail.com", false, true, &kPinset_google }, - { "www.torproject.org", true, true, &kPinset_tor }, - { "www.twitter.com", true, true, &kPinset_twitterCom }, - { "youtu.be", true, true, &kPinset_google }, - { "youtube.com", true, true, &kPinset_google }, - { "ytimg.com", true, true, &kPinset_google }, + { "accounts.google.com", true, true, false, &kPinset_google }, + { "addons.mozilla.net", true, true, true, &kPinset_mozilla }, + { "addons.mozilla.org", true, true, true, &kPinset_mozilla }, + { "admin.google.com", true, true, false, &kPinset_google }, + { "android.com", true, true, false, &kPinset_google }, + { "api.twitter.com", true, true, false, &kPinset_twitterCDN }, + { "apis.google.com", true, true, false, &kPinset_google }, + { "appengine.google.com", true, true, false, &kPinset_google }, + { "appspot.com", true, true, false, &kPinset_google }, + { "blog.torproject.org", true, true, false, &kPinset_tor }, + { "business.twitter.com", true, true, false, &kPinset_twitterCom }, + { "cdn.mozilla.net", true, true, true, &kPinset_mozilla }, + { "cdn.mozilla.org", true, true, true, &kPinset_mozilla }, + { "chart.apis.google.com", true, true, false, &kPinset_google }, + { "check.torproject.org", true, true, false, &kPinset_tor }, + { "checkout.google.com", true, true, false, &kPinset_google }, + { "chrome-devtools-frontend.appspot.com", true, true, false, &kPinset_google }, + { "chrome.google.com", true, true, false, &kPinset_google }, + { "chromiumcodereview.appspot.com", true, true, false, &kPinset_google }, + { "cloud.google.com", true, true, false, &kPinset_google }, + { "code.google.com", true, true, false, &kPinset_google }, + { "codereview.appspot.com", true, true, false, &kPinset_google }, + { "codereview.chromium.org", true, true, false, &kPinset_google }, + { "crypto.cat", false, true, false, &kPinset_cryptoCat }, + { "dev.twitter.com", true, true, false, &kPinset_twitterCom }, + { "dist.torproject.org", true, true, false, &kPinset_tor }, + { "dl.google.com", true, true, false, &kPinset_google }, + { "docs.google.com", true, true, false, &kPinset_google }, + { "doubleclick.net", true, true, false, &kPinset_google }, + { "drive.google.com", true, true, false, &kPinset_google }, + { "encrypted.google.com", true, true, false, &kPinset_google }, + { "exclude-subdomains.pinning.example.com", false, false, false, &kPinset_mozilla_test }, + { "g.co", true, true, false, &kPinset_google }, + { "glass.google.com", true, true, false, &kPinset_google }, + { "gmail.com", false, true, false, &kPinset_google }, + { "goo.gl", true, true, false, &kPinset_google }, + { "google-analytics.com", true, true, false, &kPinset_google }, + { "google.ac", true, true, false, &kPinset_google }, + { "google.ad", true, true, false, &kPinset_google }, + { "google.ae", true, true, false, &kPinset_google }, + { "google.af", true, true, false, &kPinset_google }, + { "google.ag", true, true, false, &kPinset_google }, + { "google.am", true, true, false, &kPinset_google }, + { "google.as", true, true, false, &kPinset_google }, + { "google.at", true, true, false, &kPinset_google }, + { "google.az", true, true, false, &kPinset_google }, + { "google.ba", true, true, false, &kPinset_google }, + { "google.be", true, true, false, &kPinset_google }, + { "google.bf", true, true, false, &kPinset_google }, + { "google.bg", true, true, false, &kPinset_google }, + { "google.bi", true, true, false, &kPinset_google }, + { "google.bj", true, true, false, &kPinset_google }, + { "google.bs", true, true, false, &kPinset_google }, + { "google.by", true, true, false, &kPinset_google }, + { "google.ca", true, true, false, &kPinset_google }, + { "google.cat", true, true, false, &kPinset_google }, + { "google.cc", true, true, false, &kPinset_google }, + { "google.cd", true, true, false, &kPinset_google }, + { "google.cf", true, true, false, &kPinset_google }, + { "google.cg", true, true, false, &kPinset_google }, + { "google.ch", true, true, false, &kPinset_google }, + { "google.ci", true, true, false, &kPinset_google }, + { "google.cl", true, true, false, &kPinset_google }, + { "google.cm", true, true, false, &kPinset_google }, + { "google.cn", true, true, false, &kPinset_google }, + { "google.co.ao", true, true, false, &kPinset_google }, + { "google.co.bw", true, true, false, &kPinset_google }, + { "google.co.ck", true, true, false, &kPinset_google }, + { "google.co.cr", true, true, false, &kPinset_google }, + { "google.co.hu", true, true, false, &kPinset_google }, + { "google.co.id", true, true, false, &kPinset_google }, + { "google.co.il", true, true, false, &kPinset_google }, + { "google.co.im", true, true, false, &kPinset_google }, + { "google.co.in", true, true, false, &kPinset_google }, + { "google.co.je", true, true, false, &kPinset_google }, + { "google.co.jp", true, true, false, &kPinset_google }, + { "google.co.ke", true, true, false, &kPinset_google }, + { "google.co.kr", true, true, false, &kPinset_google }, + { "google.co.ls", true, true, false, &kPinset_google }, + { "google.co.ma", true, true, false, &kPinset_google }, + { "google.co.mz", true, true, false, &kPinset_google }, + { "google.co.nz", true, true, false, &kPinset_google }, + { "google.co.th", true, true, false, &kPinset_google }, + { "google.co.tz", true, true, false, &kPinset_google }, + { "google.co.ug", true, true, false, &kPinset_google }, + { "google.co.uk", true, true, false, &kPinset_google }, + { "google.co.uz", true, true, false, &kPinset_google }, + { "google.co.ve", true, true, false, &kPinset_google }, + { "google.co.vi", true, true, false, &kPinset_google }, + { "google.co.za", true, true, false, &kPinset_google }, + { "google.co.zm", true, true, false, &kPinset_google }, + { "google.co.zw", true, true, false, &kPinset_google }, + { "google.com", true, true, false, &kPinset_google }, + { "google.com.af", true, true, false, &kPinset_google }, + { "google.com.ag", true, true, false, &kPinset_google }, + { "google.com.ai", true, true, false, &kPinset_google }, + { "google.com.ar", true, true, false, &kPinset_google }, + { "google.com.au", true, true, false, &kPinset_google }, + { "google.com.bd", true, true, false, &kPinset_google }, + { "google.com.bh", true, true, false, &kPinset_google }, + { "google.com.bn", true, true, false, &kPinset_google }, + { "google.com.bo", true, true, false, &kPinset_google }, + { "google.com.br", true, true, false, &kPinset_google }, + { "google.com.by", true, true, false, &kPinset_google }, + { "google.com.bz", true, true, false, &kPinset_google }, + { "google.com.cn", true, true, false, &kPinset_google }, + { "google.com.co", true, true, false, &kPinset_google }, + { "google.com.cu", true, true, false, &kPinset_google }, + { "google.com.cy", true, true, false, &kPinset_google }, + { "google.com.do", true, true, false, &kPinset_google }, + { "google.com.ec", true, true, false, &kPinset_google }, + { "google.com.eg", true, true, false, &kPinset_google }, + { "google.com.et", true, true, false, &kPinset_google }, + { "google.com.fj", true, true, false, &kPinset_google }, + { "google.com.ge", true, true, false, &kPinset_google }, + { "google.com.gh", true, true, false, &kPinset_google }, + { "google.com.gi", true, true, false, &kPinset_google }, + { "google.com.gr", true, true, false, &kPinset_google }, + { "google.com.gt", true, true, false, &kPinset_google }, + { "google.com.hk", true, true, false, &kPinset_google }, + { "google.com.iq", true, true, false, &kPinset_google }, + { "google.com.jm", true, true, false, &kPinset_google }, + { "google.com.jo", true, true, false, &kPinset_google }, + { "google.com.kh", true, true, false, &kPinset_google }, + { "google.com.kw", true, true, false, &kPinset_google }, + { "google.com.lb", true, true, false, &kPinset_google }, + { "google.com.ly", true, true, false, &kPinset_google }, + { "google.com.mt", true, true, false, &kPinset_google }, + { "google.com.mx", true, true, false, &kPinset_google }, + { "google.com.my", true, true, false, &kPinset_google }, + { "google.com.na", true, true, false, &kPinset_google }, + { "google.com.nf", true, true, false, &kPinset_google }, + { "google.com.ng", true, true, false, &kPinset_google }, + { "google.com.ni", true, true, false, &kPinset_google }, + { "google.com.np", true, true, false, &kPinset_google }, + { "google.com.nr", true, true, false, &kPinset_google }, + { "google.com.om", true, true, false, &kPinset_google }, + { "google.com.pa", true, true, false, &kPinset_google }, + { "google.com.pe", true, true, false, &kPinset_google }, + { "google.com.ph", true, true, false, &kPinset_google }, + { "google.com.pk", true, true, false, &kPinset_google }, + { "google.com.pl", true, true, false, &kPinset_google }, + { "google.com.pr", true, true, false, &kPinset_google }, + { "google.com.py", true, true, false, &kPinset_google }, + { "google.com.qa", true, true, false, &kPinset_google }, + { "google.com.ru", true, true, false, &kPinset_google }, + { "google.com.sa", true, true, false, &kPinset_google }, + { "google.com.sb", true, true, false, &kPinset_google }, + { "google.com.sg", true, true, false, &kPinset_google }, + { "google.com.sl", true, true, false, &kPinset_google }, + { "google.com.sv", true, true, false, &kPinset_google }, + { "google.com.tj", true, true, false, &kPinset_google }, + { "google.com.tn", true, true, false, &kPinset_google }, + { "google.com.tr", true, true, false, &kPinset_google }, + { "google.com.tw", true, true, false, &kPinset_google }, + { "google.com.ua", true, true, false, &kPinset_google }, + { "google.com.uy", true, true, false, &kPinset_google }, + { "google.com.vc", true, true, false, &kPinset_google }, + { "google.com.ve", true, true, false, &kPinset_google }, + { "google.com.vn", true, true, false, &kPinset_google }, + { "google.cv", true, true, false, &kPinset_google }, + { "google.cz", true, true, false, &kPinset_google }, + { "google.de", true, true, false, &kPinset_google }, + { "google.dj", true, true, false, &kPinset_google }, + { "google.dk", true, true, false, &kPinset_google }, + { "google.dm", true, true, false, &kPinset_google }, + { "google.dz", true, true, false, &kPinset_google }, + { "google.ee", true, true, false, &kPinset_google }, + { "google.es", true, true, false, &kPinset_google }, + { "google.fi", true, true, false, &kPinset_google }, + { "google.fm", true, true, false, &kPinset_google }, + { "google.fr", true, true, false, &kPinset_google }, + { "google.ga", true, true, false, &kPinset_google }, + { "google.ge", true, true, false, &kPinset_google }, + { "google.gg", true, true, false, &kPinset_google }, + { "google.gl", true, true, false, &kPinset_google }, + { "google.gm", true, true, false, &kPinset_google }, + { "google.gp", true, true, false, &kPinset_google }, + { "google.gr", true, true, false, &kPinset_google }, + { "google.gy", true, true, false, &kPinset_google }, + { "google.hk", true, true, false, &kPinset_google }, + { "google.hn", true, true, false, &kPinset_google }, + { "google.hr", true, true, false, &kPinset_google }, + { "google.ht", true, true, false, &kPinset_google }, + { "google.hu", true, true, false, &kPinset_google }, + { "google.ie", true, true, false, &kPinset_google }, + { "google.im", true, true, false, &kPinset_google }, + { "google.info", true, true, false, &kPinset_google }, + { "google.iq", true, true, false, &kPinset_google }, + { "google.is", true, true, false, &kPinset_google }, + { "google.it", true, true, false, &kPinset_google }, + { "google.it.ao", true, true, false, &kPinset_google }, + { "google.je", true, true, false, &kPinset_google }, + { "google.jo", true, true, false, &kPinset_google }, + { "google.jobs", true, true, false, &kPinset_google }, + { "google.jp", true, true, false, &kPinset_google }, + { "google.kg", true, true, false, &kPinset_google }, + { "google.ki", true, true, false, &kPinset_google }, + { "google.kz", true, true, false, &kPinset_google }, + { "google.la", true, true, false, &kPinset_google }, + { "google.li", true, true, false, &kPinset_google }, + { "google.lk", true, true, false, &kPinset_google }, + { "google.lt", true, true, false, &kPinset_google }, + { "google.lu", true, true, false, &kPinset_google }, + { "google.lv", true, true, false, &kPinset_google }, + { "google.md", true, true, false, &kPinset_google }, + { "google.me", true, true, false, &kPinset_google }, + { "google.mg", true, true, false, &kPinset_google }, + { "google.mk", true, true, false, &kPinset_google }, + { "google.ml", true, true, false, &kPinset_google }, + { "google.mn", true, true, false, &kPinset_google }, + { "google.ms", true, true, false, &kPinset_google }, + { "google.mu", true, true, false, &kPinset_google }, + { "google.mv", true, true, false, &kPinset_google }, + { "google.mw", true, true, false, &kPinset_google }, + { "google.ne", true, true, false, &kPinset_google }, + { "google.ne.jp", true, true, false, &kPinset_google }, + { "google.net", true, true, false, &kPinset_google }, + { "google.nl", true, true, false, &kPinset_google }, + { "google.no", true, true, false, &kPinset_google }, + { "google.nr", true, true, false, &kPinset_google }, + { "google.nu", true, true, false, &kPinset_google }, + { "google.off.ai", true, true, false, &kPinset_google }, + { "google.pk", true, true, false, &kPinset_google }, + { "google.pl", true, true, false, &kPinset_google }, + { "google.pn", true, true, false, &kPinset_google }, + { "google.ps", true, true, false, &kPinset_google }, + { "google.pt", true, true, false, &kPinset_google }, + { "google.ro", true, true, false, &kPinset_google }, + { "google.rs", true, true, false, &kPinset_google }, + { "google.ru", true, true, false, &kPinset_google }, + { "google.rw", true, true, false, &kPinset_google }, + { "google.sc", true, true, false, &kPinset_google }, + { "google.se", true, true, false, &kPinset_google }, + { "google.sh", true, true, false, &kPinset_google }, + { "google.si", true, true, false, &kPinset_google }, + { "google.sk", true, true, false, &kPinset_google }, + { "google.sm", true, true, false, &kPinset_google }, + { "google.sn", true, true, false, &kPinset_google }, + { "google.so", true, true, false, &kPinset_google }, + { "google.st", true, true, false, &kPinset_google }, + { "google.td", true, true, false, &kPinset_google }, + { "google.tg", true, true, false, &kPinset_google }, + { "google.tk", true, true, false, &kPinset_google }, + { "google.tl", true, true, false, &kPinset_google }, + { "google.tm", true, true, false, &kPinset_google }, + { "google.tn", true, true, false, &kPinset_google }, + { "google.to", true, true, false, &kPinset_google }, + { "google.tp", true, true, false, &kPinset_google }, + { "google.tt", true, true, false, &kPinset_google }, + { "google.us", true, true, false, &kPinset_google }, + { "google.uz", true, true, false, &kPinset_google }, + { "google.vg", true, true, false, &kPinset_google }, + { "google.vu", true, true, false, &kPinset_google }, + { "google.ws", true, true, false, &kPinset_google }, + { "googleadservices.com", true, true, false, &kPinset_google }, + { "googleapis.com", true, true, false, &kPinset_google }, + { "googlecode.com", true, true, false, &kPinset_google }, + { "googlecommerce.com", true, true, false, &kPinset_google }, + { "googlegroups.com", true, true, false, &kPinset_google }, + { "googlemail.com", false, true, false, &kPinset_google }, + { "googleplex.com", true, true, false, &kPinset_google }, + { "googlesyndication.com", true, true, false, &kPinset_google }, + { "googletagmanager.com", true, true, false, &kPinset_google }, + { "googletagservices.com", true, true, false, &kPinset_google }, + { "googleusercontent.com", true, true, false, &kPinset_google }, + { "goto.google.com", true, true, false, &kPinset_google }, + { "groups.google.com", true, true, false, &kPinset_google }, + { "gstatic.com", true, true, false, &kPinset_google }, + { "history.google.com", true, true, false, &kPinset_google }, + { "hostedtalkgadget.google.com", true, true, false, &kPinset_google }, + { "include-subdomains.pinning.example.com", true, false, false, &kPinset_mozilla_test }, + { "liberty.lavabit.com", true, true, false, &kPinset_lavabit }, + { "mail.google.com", true, true, false, &kPinset_google }, + { "market.android.com", true, true, false, &kPinset_google }, + { "media.mozilla.com", true, true, true, &kPinset_mozilla }, + { "mobile.twitter.com", true, true, false, &kPinset_twitterCom }, + { "oauth.twitter.com", true, true, false, &kPinset_twitterCom }, + { "pinningtest.appspot.com", true, true, false, &kPinset_test }, + { "platform.twitter.com", true, true, false, &kPinset_twitterCDN }, + { "play.google.com", false, true, false, &kPinset_google }, + { "plus.google.com", true, true, false, &kPinset_google }, + { "plus.sandbox.google.com", true, true, false, &kPinset_google }, + { "profiles.google.com", true, true, false, &kPinset_google }, + { "script.google.com", true, true, false, &kPinset_google }, + { "security.google.com", true, true, false, &kPinset_google }, + { "sites.google.com", true, true, false, &kPinset_google }, + { "spreadsheets.google.com", true, true, false, &kPinset_google }, + { "ssl.google-analytics.com", true, true, false, &kPinset_google }, + { "talk.google.com", true, true, false, &kPinset_google }, + { "talkgadget.google.com", true, true, false, &kPinset_google }, + { "test-mode.pinning.example.com", true, true, false, &kPinset_mozilla_test }, + { "tor2web.org", true, true, false, &kPinset_tor2web }, + { "torproject.org", false, true, false, &kPinset_tor }, + { "translate.googleapis.com", true, true, false, &kPinset_google }, + { "twimg.com", true, true, false, &kPinset_twitterCDN }, + { "twitter.com", false, true, false, &kPinset_twitterCom }, + { "urchin.com", true, true, false, &kPinset_google }, + { "wallet.google.com", true, true, false, &kPinset_google }, + { "www.gmail.com", false, true, false, &kPinset_google }, + { "www.googlemail.com", false, true, false, &kPinset_google }, + { "www.torproject.org", true, true, false, &kPinset_tor }, + { "www.twitter.com", true, true, false, &kPinset_twitterCom }, + { "youtu.be", true, true, false, &kPinset_google }, + { "youtube.com", true, true, false, &kPinset_google }, + { "ytimg.com", true, true, false, &kPinset_google }, }; static const int kPublicKeyPinningPreloadListLength = 306; -static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1410801238668000); +static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1410996539113000); diff --git a/security/manager/ssl/tests/unit/test_pinning.js b/security/manager/ssl/tests/unit/test_pinning.js index a61879ea0ce..0f1b95dd49c 100644 --- a/security/manager/ssl/tests/unit/test_pinning.js +++ b/security/manager/ssl/tests/unit/test_pinning.js @@ -54,7 +54,7 @@ function test_strict() { // bad.include-subdomains.pinning.example.com, but it should pass because // it's in test_mode. add_connection_test("test-mode.pinning.example.com", Cr.NS_OK); -}; +} function test_mitm() { // In MITM mode, we allow pinning to pass if the chain resolves to any @@ -89,7 +89,7 @@ function test_disabled() { add_connection_test("exclude-subdomains.pinning.example.com", Cr.NS_OK); add_connection_test("sub.exclude-subdomains.pinning.example.com", Cr.NS_OK); add_connection_test("test-mode.pinning.example.com", Cr.NS_OK); -}; +} function check_pinning_telemetry() { let service = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry); @@ -104,6 +104,15 @@ function check_pinning_telemetry() { do_check_eq(test_histogram.counts[0], 1); // Failure count do_check_eq(test_histogram.counts[1], 0); // Success count + let moz_prod_histogram = service.getHistogramById("CERT_PINNING_MOZ_RESULTS") + .snapshot(); + let moz_test_histogram = + service.getHistogramById("CERT_PINNING_MOZ_TEST_RESULTS").snapshot(); + do_check_eq(moz_prod_histogram.counts[0], 0); // Failure count + do_check_eq(moz_prod_histogram.counts[1], 0); // Success count + do_check_eq(moz_test_histogram.counts[0], 0); // Failure count + do_check_eq(moz_test_histogram.counts[1], 0); // Success count + run_next_test(); } diff --git a/security/manager/tools/genHPKPStaticPins.js b/security/manager/tools/genHPKPStaticPins.js index d73d82dcd0d..c7c7e45ac8c 100644 --- a/security/manager/tools/genHPKPStaticPins.js +++ b/security/manager/tools/genHPKPStaticPins.js @@ -54,6 +54,7 @@ const DOMAINHEADER = "/* Domainlist */\n" + " const char* mHost;\n" + " const bool mIncludeSubdomains;\n" + " const bool mTestMode;\n" + + " const bool mIsMoz;\n" + " const StaticPinset *pinset;\n" + "};\n\n"; @@ -317,6 +318,7 @@ function downloadAndParseChromePins(filename, name: entry.name, include_subdomains: entry.include_subdomains, test_mode: true, + is_moz: false, pins: entry.pins }); } }); @@ -437,6 +439,11 @@ function writeEntry(entry) { } else { printVal += "false, "; } + if (entry.is_moz || (entry.pins == "mozilla")) { + printVal += "true, "; + } else { + printVal += "false, "; + } printVal += "&kPinset_" + entry.pins; printVal += " },\n"; writeString(printVal); diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 363bc458b9c..c6efa667116 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -5947,5 +5947,15 @@ "expires_in_version": "never", "kind": "boolean", "description": "Certificate pinning evalutation results (0 = failure, 1 = success)" + }, + "CERT_PINNING_MOZ_RESULTS": { + "expires_in_version": "never", + "kind": "boolean", + "description": "Certificate pinning evalutation results (0 = failure, 1 = success)" + }, + "CERT_PINNING_MOZ_TEST_RESULTS": { + "expires_in_version": "never", + "kind": "boolean", + "description": "Certificate pinning evalutation results (0 = failure, 1 = success)" } } From f08c286b61aed9cbe2f4c7e9670fa7581cbe5cce Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Thu, 15 May 2014 16:48:21 -0700 Subject: [PATCH 16/70] Bug 976446 - Add port of irregexp regexp engine, and use by default, r=jandem. --- dom/workers/RuntimeService.cpp | 3 + js/src/builtin/RegExp.cpp | 34 +- js/src/builtin/RegExp.h | 2 + js/src/configure.in | 27 +- js/src/ds/LifoAlloc.h | 29 +- js/src/frontend/Parser.cpp | 2 +- js/src/frontend/TokenStream.cpp | 13 +- .../irregexp/NativeRegExpMacroAssembler.cpp | 1286 +++++ js/src/irregexp/NativeRegExpMacroAssembler.h | 224 + js/src/irregexp/RegExpAST.cpp | 265 + js/src/irregexp/RegExpAST.h | 439 ++ js/src/irregexp/RegExpBytecode.h | 107 + js/src/irregexp/RegExpEngine.cpp | 4766 +++++++++++++++++ js/src/irregexp/RegExpEngine.h | 1518 ++++++ js/src/irregexp/RegExpInterpreter.cpp | 456 ++ js/src/irregexp/RegExpMacroAssembler.cpp | 525 ++ js/src/irregexp/RegExpMacroAssembler.h | 305 ++ js/src/irregexp/RegExpParser.cpp | 1009 ++++ js/src/irregexp/RegExpParser.h | 298 ++ js/src/irregexp/RegExpStack.cpp | 102 + js/src/irregexp/RegExpStack.h | 122 + .../jit-test/tests/basic/bug576837-regexp.js | 14 +- js/src/jit-test/tests/basic/bug976446.js | 16 + .../tests/basic/regexp-match-limit.js | 9 - .../tests/basic/testSlowNativeBail.js | 3 +- js/src/jit/AsmJS.cpp | 2 +- js/src/jit/IonAllocPolicy.h | 9 +- js/src/jit/IonLinker.h | 3 - js/src/jit/JitCommon.h | 20 +- js/src/jit/Label.h | 97 + js/src/jit/arm/MacroAssembler-arm.cpp | 12 + js/src/jit/arm/MacroAssembler-arm.h | 5 + js/src/jit/arm/Simulator-arm.cpp | 15 +- js/src/jit/shared/Assembler-shared.h | 103 +- js/src/jit/shared/MacroAssembler-x86-shared.h | 3 + js/src/jit/x64/MacroAssembler-x64.h | 6 + js/src/jit/x86/MacroAssembler-x86.h | 6 + js/src/js.msg | 12 +- js/src/jsapi.cpp | 12 +- js/src/jsapi.h | 10 +- js/src/jscntxt.cpp | 1 - js/src/jscntxt.h | 4 - js/src/jscompartment.h | 1 + js/src/jsstr.cpp | 37 +- js/src/moz.build | 53 +- js/src/shell/js.cpp | 5 +- .../tests/js1_5/Exceptions/regress-332472.js | 4 +- .../tests/js1_5/Regress/regress-230216-2.js | 12 +- .../tests/js1_5/Regress/regress-280769-1.js | 9 +- .../tests/js1_5/Regress/regress-280769-5.js | 12 +- js/src/tests/js1_5/Regress/regress-440926.js | 10 +- js/src/vm/MatchPairs.h | 37 +- js/src/vm/RegExpObject.cpp | 247 +- js/src/vm/RegExpObject.h | 148 +- js/src/vm/Runtime.cpp | 17 + js/src/vm/Runtime.h | 16 + js/src/vm/SelfHosting.cpp | 2 +- js/src/vm/StructuredClone.cpp | 3 +- js/src/vm/TraceLogging.cpp | 5 + js/src/vm/TraceLogging.h | 2 + js/src/vm/Unicode.h | 2 +- js/src/yarr/YarrJIT.cpp | 2 +- js/src/yarr/YarrJIT.h | 2 +- js/src/yarr/wtfbridge.h | 2 +- js/xpconnect/src/XPCJSRuntime.cpp | 8 +- modules/libpref/src/init/all.js | 1 + toolkit/content/license.html | 1 + 67 files changed, 12213 insertions(+), 319 deletions(-) create mode 100644 js/src/irregexp/NativeRegExpMacroAssembler.cpp create mode 100644 js/src/irregexp/NativeRegExpMacroAssembler.h create mode 100644 js/src/irregexp/RegExpAST.cpp create mode 100644 js/src/irregexp/RegExpAST.h create mode 100644 js/src/irregexp/RegExpBytecode.h create mode 100644 js/src/irregexp/RegExpEngine.cpp create mode 100644 js/src/irregexp/RegExpEngine.h create mode 100644 js/src/irregexp/RegExpInterpreter.cpp create mode 100644 js/src/irregexp/RegExpMacroAssembler.cpp create mode 100644 js/src/irregexp/RegExpMacroAssembler.h create mode 100644 js/src/irregexp/RegExpParser.cpp create mode 100644 js/src/irregexp/RegExpParser.h create mode 100644 js/src/irregexp/RegExpStack.cpp create mode 100644 js/src/irregexp/RegExpStack.h create mode 100644 js/src/jit-test/tests/basic/bug976446.js delete mode 100644 js/src/jit-test/tests/basic/regexp-match-limit.js create mode 100644 js/src/jit/Label.h diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp index 6a39fea8000..8b407f683c8 100644 --- a/dom/workers/RuntimeService.cpp +++ b/dom/workers/RuntimeService.cpp @@ -340,6 +340,9 @@ LoadRuntimeAndContextOptions(const char* aPrefName, void* /* aClosure */) if (GetWorkerPref(NS_LITERAL_CSTRING("ion"))) { runtimeOptions.setIon(true); } + if (GetWorkerPref(NS_LITERAL_CSTRING("native_regexp"))) { + runtimeOptions.setNativeRegExp(true); + } // Common options. JS::ContextOptions commonContextOptions = kRequiredContextOptions; diff --git a/js/src/builtin/RegExp.cpp b/js/src/builtin/RegExp.cpp index 617a85250bf..d2954b5f3d7 100644 --- a/js/src/builtin/RegExp.cpp +++ b/js/src/builtin/RegExp.cpp @@ -8,6 +8,10 @@ #include "jscntxt.h" +#ifndef JS_YARR +#include "irregexp/RegExpParser.h" +#endif + #include "vm/RegExpStatics.h" #include "vm/StringBuffer.h" @@ -94,11 +98,15 @@ ExecuteRegExpImpl(JSContext *cx, RegExpStatics *res, RegExpShared &re, /* Switch between MatchOnly and IncludeSubpatterns modes. */ if (matches.isPair) { +#ifdef JS_YARR size_t lastIndex_orig = *lastIndex; /* Only one MatchPair slot provided: execute short-circuiting regexp. */ status = re.executeMatchOnly(cx, chars, length, lastIndex, *matches.u.pair); if (status == RegExpRunStatus_Success && res) res->updateLazily(cx, input, &re, lastIndex_orig); +#else + MOZ_CRASH(); +#endif } else { /* Vector of MatchPairs provided: execute full regexp. */ status = re.execute(cx, chars, length, lastIndex, *matches.u.pairs); @@ -107,7 +115,6 @@ ExecuteRegExpImpl(JSContext *cx, RegExpStatics *res, RegExpShared &re, return RegExpRunStatus_Error; } } - return status; } @@ -283,8 +290,16 @@ CompileRegExpObject(JSContext *cx, RegExpObjectBuilder &builder, CallArgs args) if (!escapedSourceStr) return false; - if (!js::RegExpShared::checkSyntax(cx, nullptr, escapedSourceStr)) +#ifdef JS_YARR + if (!RegExpShared::checkSyntax(cx, nullptr, escapedSourceStr)) return false; +#else // JS_YARR + CompileOptions options(cx); + frontend::TokenStream dummyTokenStream(cx, options, nullptr, 0, nullptr); + + if (!irregexp::ParsePatternSyntax(dummyTokenStream, cx->tempLifoAlloc(), escapedSourceStr->chars(), escapedSourceStr->length())) + return false; +#endif // JS_YARR RegExpStatics *res = cx->global()->getRegExpStatics(cx); if (!res) @@ -678,8 +693,13 @@ js::regexp_exec_no_statics(JSContext *cx, unsigned argc, Value *vp) static bool regexp_test_impl(JSContext *cx, CallArgs args) { +#ifdef JS_YARR MatchPair match; MatchConduit conduit(&match); +#else + ScopedMatchPairs matches(&cx->tempLifoAlloc()); + MatchConduit conduit(&matches); +#endif RegExpRunStatus status = ExecuteRegExp(cx, args, conduit); args.rval().setBoolean(status == RegExpRunStatus_Success); return status != RegExpRunStatus_Error; @@ -689,8 +709,13 @@ regexp_test_impl(JSContext *cx, CallArgs args) bool js::regexp_test_raw(JSContext *cx, HandleObject regexp, HandleString input, bool *result) { +#ifdef JS_YARR MatchPair match; MatchConduit conduit(&match); +#else + ScopedMatchPairs matches(&cx->tempLifoAlloc()); + MatchConduit conduit(&matches); +#endif RegExpRunStatus status = ExecuteRegExp(cx, regexp, input, conduit, UpdateRegExpStatics); *result = (status == RegExpRunStatus_Success); return status != RegExpRunStatus_Error; @@ -714,8 +739,13 @@ js::regexp_test_no_statics(JSContext *cx, unsigned argc, Value *vp) RootedObject regexp(cx, &args[0].toObject()); RootedString string(cx, args[1].toString()); +#ifdef JS_YARR MatchPair match; MatchConduit conduit(&match); +#else + ScopedMatchPairs matches(&cx->tempLifoAlloc()); + MatchConduit conduit(&matches); +#endif RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, conduit, DontUpdateRegExpStatics); args.rval().setBoolean(status == RegExpRunStatus_Success); return status != RegExpRunStatus_Error; diff --git a/js/src/builtin/RegExp.h b/js/src/builtin/RegExp.h index e92324c290b..a778e6097ee 100644 --- a/js/src/builtin/RegExp.h +++ b/js/src/builtin/RegExp.h @@ -19,6 +19,8 @@ js_InitRegExpClass(JSContext *cx, js::HandleObject obj); namespace js { +class MatchConduit; + // Whether RegExp statics should be updated with the input and results of a // regular expression execution. enum RegExpStaticsUpdate { UpdateRegExpStatics, DontUpdateRegExpStatics }; diff --git a/js/src/configure.in b/js/src/configure.in index 95694adc300..7bf746a8155 100644 --- a/js/src/configure.in +++ b/js/src/configure.in @@ -1977,19 +1977,16 @@ dnl Configure JIT support case "$target" in i?86-*) ENABLE_ION=1 - ENABLE_YARR_JIT=1 AC_DEFINE(JS_CPU_X86) AC_DEFINE(JS_NUNBOX32) ;; x86_64*-*) ENABLE_ION=1 - ENABLE_YARR_JIT=1 AC_DEFINE(JS_CPU_X64) AC_DEFINE(JS_PUNBOX64) ;; arm*-*) ENABLE_ION=1 - ENABLE_YARR_JIT=1 AC_DEFINE(JS_CPU_ARM) AC_DEFINE(JS_NUNBOX32) ;; @@ -2010,10 +2007,6 @@ MOZ_ARG_DISABLE_BOOL(ion, [ --disable-ion Disable use of the IonMonkey JIT], ENABLE_ION= ) -MOZ_ARG_DISABLE_BOOL(yarr-jit, -[ --disable-yarr-jit Disable YARR JIT support], - ENABLE_YARR_JIT= ) - AC_SUBST(ENABLE_METHODJIT_SPEW) AC_SUBST(ENABLE_ION) @@ -2022,12 +2015,6 @@ if test "$ENABLE_ION"; then AC_DEFINE(JS_ION) fi -AC_SUBST(ENABLE_YARR_JIT) - -if test "$ENABLE_YARR_JIT"; then - AC_DEFINE(ENABLE_YARR_JIT) -fi - if test -n "$COMPILE_ENVIRONMENT"; then MOZ_COMPILER_OPTS fi @@ -2994,6 +2981,20 @@ if test "$ENABLE_TRACE_LOGGING"; then AC_DEFINE(JS_TRACE_LOGGING) fi +dnl ======================================================== +dnl = Enable yarr regexp engine +dnl ======================================================== +MOZ_ARG_ENABLE_BOOL(yarr, +[ --enable-yarr Enable yarr regexp engine], + ENABLE_YARR=1, + ENABLE_YARR= ) + +AC_SUBST(ENABLE_YARR) + +if test "$ENABLE_YARR"; then + AC_DEFINE(JS_YARR) +fi + dnl ======================================================== dnl = Enable any treating of compile warnings as errors dnl ======================================================== diff --git a/js/src/ds/LifoAlloc.h b/js/src/ds/LifoAlloc.h index 7617cf54a22..18600332976 100644 --- a/js/src/ds/LifoAlloc.h +++ b/js/src/ds/LifoAlloc.h @@ -147,6 +147,9 @@ class BumpChunk } // namespace detail +void +CrashAtUnhandlableOOM(const char *reason); + // LIFO bump allocator: used for phase-oriented and fast LIFO allocations. // // Note: |latest| is not necessary "last". We leave BumpChunks latent in the @@ -269,6 +272,14 @@ class LifoAlloc return result; } + MOZ_ALWAYS_INLINE + void *allocInfallible(size_t n) { + if (void *result = alloc(n)) + return result; + CrashAtUnhandlableOOM("LifoAlloc::allocInfallible"); + return nullptr; + } + // Ensures that enough space exists to satisfy N bytes worth of // allocation requests, not necessarily contiguous. Note that this does // not guarantee a successful single allocation of N bytes. @@ -385,6 +396,7 @@ class LifoAlloc } JS_DECLARE_NEW_METHODS(new_, alloc, MOZ_ALWAYS_INLINE) + JS_DECLARE_NEW_METHODS(newInfallible, allocInfallible, MOZ_ALWAYS_INLINE) // A mutable enumeration of the allocated data. class Enum @@ -490,6 +502,12 @@ class LifoAllocScope } }; +enum Fallibility { + Fallible, + Infallible +}; + +template class LifoAllocPolicy { LifoAlloc &alloc_; @@ -499,18 +517,19 @@ class LifoAllocPolicy : alloc_(alloc) {} void *malloc_(size_t bytes) { - return alloc_.alloc(bytes); + return fb == Fallible ? alloc_.alloc(bytes) : alloc_.allocInfallible(bytes); } void *calloc_(size_t bytes) { void *p = malloc_(bytes); - if (p) - memset(p, 0, bytes); + if (fb == Fallible && !p) + return nullptr; + memset(p, 0, bytes); return p; } void *realloc_(void *p, size_t oldBytes, size_t bytes) { void *n = malloc_(bytes); - if (!n) - return n; + if (fb == Fallible && !n) + return nullptr; memcpy(n, p, Min(oldBytes, bytes)); return n; } diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 19ba70f3dba..e23695e1a5a 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -6914,7 +6914,7 @@ Parser::newRegExp() if (!res) return null(); - reobj = RegExpObject::create(context, res, chars, length, flags, &tokenStream); + reobj = RegExpObject::create(context, res, chars, length, flags, &tokenStream, alloc); if (!reobj) return null(); diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index faed9138619..59181b9269a 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -291,7 +291,7 @@ TokenStream::TokenStream(ExclusiveContext *cx, const ReadOnlyCompileOptions &opt // initial line's base must be included in the buffer. linebase and userbuf // were adjusted above, and if we are starting tokenization part way through // this line then adjust the next character. - userbuf.setAddressOfNextRawChar(base); + userbuf.setAddressOfNextRawChar(base, /* allowPoisoned = */ true); // Nb: the following tables could be static, but initializing them here is // much easier. Don't worry, the time to initialize them for each @@ -631,6 +631,17 @@ TokenStream::reportCompileErrorNumberVA(uint32_t offset, unsigned flags, unsigne err.report.column = srcCoords.columnIndex(offset); } + // If we have no location information, try to get one from the caller. + if (offset != NoOffset && !err.report.filename && cx->isJSContext()) { + NonBuiltinFrameIter iter(cx->asJSContext(), + FrameIter::ALL_CONTEXTS, FrameIter::GO_THROUGH_SAVED, + cx->compartment()->principals); + if (!iter.done() && iter.scriptFilename()) { + err.report.filename = iter.scriptFilename(); + err.report.lineno = iter.computeLine(&err.report.column); + } + } + err.argumentsType = (flags & JSREPORT_UC) ? ArgumentsAreUnicode : ArgumentsAreASCII; if (!js_ExpandErrorArguments(cx, js_GetErrorMessage, nullptr, errorNumber, &err.message, diff --git a/js/src/irregexp/NativeRegExpMacroAssembler.cpp b/js/src/irregexp/NativeRegExpMacroAssembler.cpp new file mode 100644 index 00000000000..1b9d25626eb --- /dev/null +++ b/js/src/irregexp/NativeRegExpMacroAssembler.cpp @@ -0,0 +1,1286 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: */ + +// Copyright 2012 the V8 project authors. 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. + +#include "irregexp/NativeRegExpMacroAssembler.h" + +#include "irregexp/RegExpStack.h" +#include "jit/IonLinker.h" +#include "vm/MatchPairs.h" + +using namespace js; +using namespace js::irregexp; +using namespace js::jit; + +/* + * This assembler uses the following register assignment convention: + * + * - current_character : + * Must be loaded using LoadCurrentCharacter before using any of the + * dispatch methods. Temporarily stores the index of capture start after a + * matching pass for a global regexp. + * - current_position : + * Current position in input, as negative offset from end of string. + * Please notice that this is the byte offset, not the character offset! + * - input_end_pointer : + * Points to byte after last character in the input. + * - backtrack_stack_pointer : + * Points to tip of the heap allocated backtrack stack + * - StackPointer : + * Points to tip of the native stack, used to access arguments, local + * variables and RegExp registers. + * + * The tempN registers are free to use for computations. + */ + +NativeRegExpMacroAssembler::NativeRegExpMacroAssembler(LifoAlloc *alloc, RegExpShared *shared, + JSRuntime *rt, Mode mode, int registers_to_save) + : RegExpMacroAssembler(*alloc, shared, registers_to_save), + runtime(rt), mode_(mode) +{ + // Find physical registers for each compiler register. + GeneralRegisterSet regs(GeneralRegisterSet::All()); + + input_end_pointer = regs.takeAny(); + current_character = regs.takeAny(); + current_position = regs.takeAny(); + backtrack_stack_pointer = regs.takeAny(); + temp0 = regs.takeAny(); + temp1 = regs.takeAny(); + temp2 = regs.takeAny(); + + IonSpew(IonSpew_Codegen, + "Starting RegExp (input_end_pointer %s) (current_character %s)" + " (current_position %s) (backtrack_stack_pointer %s) (temp0 %s) temp1 (%s) temp2 (%s)", + input_end_pointer.name(), + current_character.name(), + current_position.name(), + backtrack_stack_pointer.name(), + temp0.name(), + temp1.name(), + temp2.name()); + + // Determine the non-volatile registers which might be modified by jitcode. + for (GeneralRegisterIterator iter(GeneralRegisterSet::NonVolatile()); iter.more(); iter++) { + Register reg = *iter; + if (!regs.has(reg)) + savedNonVolatileRegisters.add(reg); + } + +#ifdef JS_CODEGEN_ARM + // ARM additionally requires that the link register be saved. + savedNonVolatileRegisters.add(Register::FromCode(Registers::lr)); +#endif + + masm.jump(&entry_label_); + masm.bind(&start_label_); +} + +#define SPEW_PREFIX IonSpew_Codegen, "!!! " + +// The signature of the code which this generates is: +// +// void execute(InputOutputData*); +RegExpCode +NativeRegExpMacroAssembler::GenerateCode(JSContext *cx) +{ + if (!cx->compartment()->ensureJitCompartmentExists(cx)) + return RegExpCode(); + + IonSpew(SPEW_PREFIX "GenerateCode"); + + // We need an even number of registers, for stack alignment. + if (num_registers_ % 2 == 1) + num_registers_++; + + Label return_temp0; + + // Finalize code - write the entry point code now we know how many + // registers we need. + masm.bind(&entry_label_); + + // Push non-volatile registers which might be modified by jitcode. + size_t pushedNonVolatileRegisters = 0; + for (GeneralRegisterForwardIterator iter(savedNonVolatileRegisters); iter.more(); ++iter) { + masm.Push(*iter); + pushedNonVolatileRegisters++; + } + +#ifndef JS_CODEGEN_X86 + // The InputOutputData* is stored as an argument, save it on the stack + // above the frame. + masm.Push(IntArgReg0); +#endif + + size_t frameSize = sizeof(FrameData) + num_registers_ * sizeof(void *); + frameSize = JS_ROUNDUP(frameSize + masm.framePushed(), StackAlignment) - masm.framePushed(); + + // Actually emit code to start a new stack frame. + masm.reserveStack(frameSize); + masm.checkStackAlignment(); + + // Check if we have space on the stack. + Label stack_ok; + void *stack_limit = &runtime->mainThread.jitStackLimit; + masm.branchPtr(Assembler::Below, AbsoluteAddress(stack_limit), StackPointer, &stack_ok); + + // Exit with an exception. There is not enough space on the stack + // for our working registers. + masm.mov(ImmWord(RegExpRunStatus_Error), temp0); + masm.jump(&return_temp0); + + masm.bind(&stack_ok); + +#ifdef XP_WIN + // Ensure that we write to each stack page, in order. Skipping a page + // on Windows can cause segmentation faults. Assuming page size is 4k. + const int kPageSize = 4096; + for (int i = frameSize - sizeof(void *); i >= 0; i -= kPageSize) + masm.storePtr(temp0, Address(StackPointer, i)); +#endif // XP_WIN + +#ifndef JS_CODEGEN_X86 + // The InputOutputData* is stored on the stack immediately above the frame. + Address inputOutputAddress(StackPointer, frameSize); +#else + // The InputOutputData* is left in its original on stack location. + Address inputOutputAddress(StackPointer, frameSize + (pushedNonVolatileRegisters + 1) * sizeof(void *)); +#endif + + masm.loadPtr(inputOutputAddress, temp0); + + // Copy output registers to FrameData. + { + Register matchPairsRegister = input_end_pointer; + masm.loadPtr(Address(temp0, offsetof(InputOutputData, matches)), matchPairsRegister); + masm.loadPtr(Address(matchPairsRegister, MatchPairs::offsetOfPairs()), temp1); + masm.storePtr(temp1, Address(StackPointer, offsetof(FrameData, outputRegisters))); + masm.load32(Address(matchPairsRegister, MatchPairs::offsetOfPairCount()), temp1); + masm.lshiftPtr(Imm32(1), temp1); + masm.store32(temp1, Address(StackPointer, offsetof(FrameData, numOutputRegisters))); + +#ifdef DEBUG + // Bounds check numOutputRegisters. + Label enoughRegisters; + masm.cmpPtr(temp1, ImmWord(num_saved_registers_)); + masm.j(Assembler::GreaterThanOrEqual, &enoughRegisters); + masm.assumeUnreachable("Not enough output registers for RegExp"); + masm.bind(&enoughRegisters); +#endif + } + + // Load string end pointer. + masm.loadPtr(Address(temp0, offsetof(InputOutputData, inputEnd)), input_end_pointer); + + // Load input start pointer, and copy to FrameData. + masm.loadPtr(Address(temp0, offsetof(InputOutputData, inputStart)), current_position); + masm.storePtr(current_position, Address(StackPointer, offsetof(FrameData, inputStart))); + + // Load start index, and copy to FrameData. + masm.loadPtr(Address(temp0, offsetof(InputOutputData, startIndex)), temp1); + masm.storePtr(temp1, Address(StackPointer, offsetof(FrameData, startIndex))); + + // Set up input position to be negative offset from string end. + masm.subPtr(input_end_pointer, current_position); + + // Set temp0 to address of char before start of the string. + // (effectively string position -1). + masm.computeEffectiveAddress(Address(current_position, -char_size()), temp0); + + // Store this value on the frame, for use when clearing + // position registers. + masm.storePtr(temp0, Address(StackPointer, offsetof(FrameData, inputStartMinusOne))); + + // Update current position based on start index. + masm.computeEffectiveAddress(BaseIndex(current_position, temp1, factor()), current_position); + + Label load_char_start_regexp, start_regexp; + + // Load newline if index is at start, previous character otherwise. + masm.cmpPtr(Address(StackPointer, offsetof(FrameData, startIndex)), ImmWord(0)); + masm.j(Assembler::NotEqual, &load_char_start_regexp); + masm.mov(ImmWord('\n'), current_character); + masm.jump(&start_regexp); + + // Global regexp restarts matching here. + masm.bind(&load_char_start_regexp); + + // Load previous char as initial value of current character register. + LoadCurrentCharacterUnchecked(-1, 1); + masm.bind(&start_regexp); + + // Initialize on-stack registers. + JS_ASSERT(num_saved_registers_ > 0); + + // Fill saved registers with initial value = start offset - 1 + // Fill in stack push order, to avoid accessing across an unwritten + // page (a problem on Windows). + if (num_saved_registers_ > 8) { + masm.mov(ImmWord(register_offset(0)), temp1); + Label init_loop; + masm.bind(&init_loop); + masm.storePtr(temp0, BaseIndex(StackPointer, temp1, TimesOne)); + masm.addPtr(ImmWord(sizeof(void *)), temp1); + masm.cmpPtr(temp1, ImmWord(register_offset(num_saved_registers_))); + masm.j(Assembler::LessThan, &init_loop); + } else { + // Unroll the loop. + for (int i = 0; i < num_saved_registers_; i++) + masm.storePtr(temp0, register_location(i)); + } + + // Initialize backtrack stack pointer. + masm.loadPtr(AbsoluteAddress(runtime->mainThread.regexpStack.addressOfBase()), backtrack_stack_pointer); + masm.storePtr(backtrack_stack_pointer, Address(StackPointer, offsetof(FrameData, backtrackStackBase))); + + masm.jump(&start_label_); + + // Exit code: + if (success_label_.used()) { + JS_ASSERT(num_saved_registers_ > 0); + + Address outputRegistersAddress(StackPointer, offsetof(FrameData, outputRegisters)); + + // Save captures when successful. + masm.bind(&success_label_); + + { + Register outputRegisters = temp1; + Register inputByteLength = backtrack_stack_pointer; + + masm.loadPtr(outputRegistersAddress, outputRegisters); + + masm.loadPtr(inputOutputAddress, temp0); + masm.loadPtr(Address(temp0, offsetof(InputOutputData, inputEnd)), inputByteLength); + masm.subPtr(Address(temp0, offsetof(InputOutputData, inputStart)), inputByteLength); + + // Copy captures to output. Note that registers on the C stack are pointer width + // so that they might hold pointers, but output registers are int32_t. + for (int i = 0; i < num_saved_registers_; i++) { + masm.loadPtr(register_location(i), temp0); + if (i == 0 && global_with_zero_length_check()) { + // Keep capture start in current_character for the zero-length check later. + masm.mov(temp0, current_character); + } + + // Convert to index from start of string, not end. + masm.addPtr(inputByteLength, temp0); + + // Convert byte index to character index. + if (mode_ == JSCHAR) + masm.rshiftPtrArithmetic(Imm32(1), temp0); + + masm.store32(temp0, Address(outputRegisters, i * sizeof(int32_t))); + } + } + + // Restart matching if the regular expression is flagged as global. + if (global()) { + // Increment success counter. + masm.add32(Imm32(1), Address(StackPointer, offsetof(FrameData, successfulCaptures))); + + Address numOutputRegistersAddress(StackPointer, offsetof(FrameData, numOutputRegisters)); + + // Capture results have been stored, so the number of remaining global + // output registers is reduced by the number of stored captures. + masm.load32(numOutputRegistersAddress, temp0); + + masm.sub32(Imm32(num_saved_registers_), temp0); + + // Check whether we have enough room for another set of capture results. + masm.branch32(Assembler::LessThan, temp0, Imm32(num_saved_registers_), &exit_label_); + + masm.store32(temp0, numOutputRegistersAddress); + + // Advance the location for output. + masm.add32(Imm32(num_saved_registers_ * sizeof(void *)), outputRegistersAddress); + + // Prepare temp0 to initialize registers with its value in the next run. + masm.loadPtr(Address(StackPointer, offsetof(FrameData, inputStartMinusOne)), temp0); + + if (global_with_zero_length_check()) { + // Special case for zero-length matches. + + // The capture start index was loaded into current_character above. + masm.branchPtr(Assembler::NotEqual, current_position, current_character, + &load_char_start_regexp); + + // edi (offset from the end) is zero if we already reached the end. + masm.testPtr(current_position, current_position); + masm.j(Assembler::Zero, &exit_label_); + + // Advance current position after a zero-length match. + masm.addPtr(Imm32(char_size()), current_position); + } + + masm.jump(&load_char_start_regexp); + } else { + masm.mov(ImmWord(RegExpRunStatus_Success), temp0); + } + } + + masm.bind(&exit_label_); + + if (global()) { + // Return the number of successful captures. + masm.load32(Address(StackPointer, offsetof(FrameData, successfulCaptures)), temp0); + } + + masm.bind(&return_temp0); + + // Store the result to the input structure. + masm.loadPtr(inputOutputAddress, temp1); + masm.storePtr(temp0, Address(temp1, offsetof(InputOutputData, result))); + +#ifndef JS_CODEGEN_X86 + // Include the InputOutputData* when adjusting the stack size. + masm.freeStack(frameSize + sizeof(void *)); +#else + masm.freeStack(frameSize); +#endif + + // Restore non-volatile registers which were saved on entry. + for (GeneralRegisterBackwardIterator iter(savedNonVolatileRegisters); iter.more(); ++iter) + masm.Pop(*iter); + + masm.abiret(); + + // Backtrack code (branch target for conditional backtracks). + if (backtrack_label_.used()) { + masm.bind(&backtrack_label_); + Backtrack(); + } + + // Backtrack stack overflow code. + if (stack_overflow_label_.used()) { + // Reached if the backtrack-stack limit has been hit. temp2 holds the + // StackPointer to use for accessing FrameData. + masm.bind(&stack_overflow_label_); + + Label grow_failed; + + masm.mov(ImmPtr(runtime), temp1); + + // Save registers before calling C function + RegisterSet volatileRegs = RegisterSet::Volatile(); +#ifdef JS_CODEGEN_ARM + volatileRegs.add(Register::FromCode(Registers::lr)); +#endif + volatileRegs.takeUnchecked(temp0); + volatileRegs.takeUnchecked(temp1); + masm.PushRegsInMask(volatileRegs); + + masm.setupUnalignedABICall(1, temp0); + masm.passABIArg(temp1); + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, GrowBacktrackStack)); + masm.storeCallResult(temp0); + + masm.PopRegsInMask(volatileRegs); + + // If return false, we have failed to grow the stack, and + // must exit with a stack-overflow exception. Do this in the caller + // so that the stack is adjusted by our return instruction. + Label return_from_overflow_handler; + masm.branchTest32(Assembler::Zero, temp0, temp0, &return_from_overflow_handler); + + // Otherwise, store the new backtrack stack base and recompute the new + // top of the stack. + Address backtrackStackBaseAddress(temp2, offsetof(FrameData, backtrackStackBase)); + masm.subPtr(backtrackStackBaseAddress, backtrack_stack_pointer); + + masm.loadPtr(AbsoluteAddress(runtime->mainThread.regexpStack.addressOfBase()), temp1); + masm.storePtr(temp1, backtrackStackBaseAddress); + masm.addPtr(temp1, backtrack_stack_pointer); + + // Resume execution in calling code. + masm.bind(&return_from_overflow_handler); + masm.abiret(); + } + + if (exit_with_exception_label_.used()) { + // If any of the code above needed to exit with an exception. + masm.bind(&exit_with_exception_label_); + + // Exit with an error result to signal thrown exception. + masm.mov(ImmWord(RegExpRunStatus_Error), temp0); + masm.jump(&return_temp0); + } + + Linker linker(masm); + JitCode *code = linker.newCode(cx, JSC::REGEXP_CODE); + if (!code) + return RegExpCode(); + + for (size_t i = 0; i < labelPatches.length(); i++) { + const LabelPatch &v = labelPatches[i]; + JS_ASSERT(!v.label); + Assembler::patchDataWithValueCheck(CodeLocationLabel(code, v.patchOffset), + ImmPtr(code->raw() + v.labelOffset), + ImmPtr(0)); + } + + IonSpew(IonSpew_Codegen, "Created RegExp (raw %p length %d)", + (void *) code->raw(), (int) masm.bytesNeeded()); + + RegExpCode res; + res.jitCode = code; + return res; +} + +int +NativeRegExpMacroAssembler::stack_limit_slack() +{ + return RegExpStack::kStackLimitSlack; +} + +void +NativeRegExpMacroAssembler::AdvanceCurrentPosition(int by) +{ + IonSpew(SPEW_PREFIX "AdvanceCurrentPosition(%d)", by); + + if (by != 0) + masm.addPtr(Imm32(by * char_size()), current_position); +} + +void +NativeRegExpMacroAssembler::AdvanceRegister(int reg, int by) +{ + IonSpew(SPEW_PREFIX "AdvanceRegister(%d, %d)", reg, by); + + JS_ASSERT(reg >= 0); + JS_ASSERT(reg < num_registers_); + if (by != 0) + masm.addPtr(Imm32(by), register_location(reg)); +} + +void +NativeRegExpMacroAssembler::Backtrack() +{ + IonSpew(SPEW_PREFIX "Backtrack"); + + // Pop code location from backtrack stack and jump to location. + PopBacktrack(temp0); + masm.jump(temp0); +} + +void +NativeRegExpMacroAssembler::Bind(Label *label) +{ + IonSpew(SPEW_PREFIX "Bind"); + + masm.bind(label); +} + +void +NativeRegExpMacroAssembler::CheckAtStart(Label* on_at_start) +{ + IonSpew(SPEW_PREFIX "CheckAtStart"); + + Label not_at_start; + + // Did we start the match at the start of the string at all? + masm.cmpPtr(Address(StackPointer, offsetof(FrameData, startIndex)), ImmWord(0)); + BranchOrBacktrack(Assembler::NotEqual, ¬_at_start); + + // If we did, are we still at the start of the input? + masm.computeEffectiveAddress(BaseIndex(input_end_pointer, current_position, TimesOne), temp0); + masm.cmpPtr(Address(StackPointer, offsetof(FrameData, inputStart)), temp0); + + BranchOrBacktrack(Assembler::Equal, on_at_start); + masm.bind(¬_at_start); +} + +void +NativeRegExpMacroAssembler::CheckNotAtStart(Label* on_not_at_start) +{ + IonSpew(SPEW_PREFIX "CheckNotAtStart"); + + // Did we start the match at the start of the string at all? + masm.cmpPtr(Address(StackPointer, offsetof(FrameData, startIndex)), ImmWord(0)); + BranchOrBacktrack(Assembler::NotEqual, on_not_at_start); + + // If we did, are we still at the start of the input? + masm.computeEffectiveAddress(BaseIndex(input_end_pointer, current_position, TimesOne), temp0); + masm.cmpPtr(Address(StackPointer, offsetof(FrameData, inputStart)), temp0); + BranchOrBacktrack(Assembler::NotEqual, on_not_at_start); +} + +void +NativeRegExpMacroAssembler::CheckCharacter(unsigned c, Label* on_equal) +{ + IonSpew(SPEW_PREFIX "CheckCharacter(%d)", (int) c); + + masm.cmp32(current_character, Imm32(c)); + BranchOrBacktrack(Assembler::Equal, on_equal); +} + +void +NativeRegExpMacroAssembler::CheckNotCharacter(unsigned c, Label* on_not_equal) +{ + IonSpew(SPEW_PREFIX "CheckNotCharacter(%d)", (int) c); + + masm.cmp32(current_character, Imm32(c)); + BranchOrBacktrack(Assembler::NotEqual, on_not_equal); +} + +void +NativeRegExpMacroAssembler::CheckCharacterAfterAnd(unsigned c, unsigned and_with, + Label *on_equal) +{ + IonSpew(SPEW_PREFIX "CheckCharacterAfterAnd(%d, %d)", (int) c, (int) and_with); + + if (c == 0) { + masm.test32(current_character, Imm32(and_with)); + BranchOrBacktrack(Assembler::Zero, on_equal); + } else { + masm.mov(ImmWord(and_with), temp0); + masm.and32(current_character, temp0); + masm.cmp32(temp0, Imm32(c)); + BranchOrBacktrack(Assembler::Equal, on_equal); + } +} + +void +NativeRegExpMacroAssembler::CheckNotCharacterAfterAnd(unsigned c, unsigned and_with, + Label *on_not_equal) +{ + IonSpew(SPEW_PREFIX "CheckNotCharacterAfterAnd(%d, %d)", (int) c, (int) and_with); + + if (c == 0) { + masm.test32(current_character, Imm32(and_with)); + BranchOrBacktrack(Assembler::NonZero, on_not_equal); + } else { + masm.mov(ImmWord(and_with), temp0); + masm.and32(current_character, temp0); + masm.cmp32(temp0, Imm32(c)); + BranchOrBacktrack(Assembler::NotEqual, on_not_equal); + } +} + +void +NativeRegExpMacroAssembler::CheckCharacterGT(jschar c, Label* on_greater) +{ + IonSpew(SPEW_PREFIX "CheckCharacterGT(%d)", (int) c); + + masm.cmp32(current_character, Imm32(c)); + BranchOrBacktrack(Assembler::GreaterThan, on_greater); +} + +void +NativeRegExpMacroAssembler::CheckCharacterLT(jschar c, Label* on_less) +{ + IonSpew(SPEW_PREFIX "CheckCharacterLT(%d)", (int) c); + + masm.cmp32(current_character, Imm32(c)); + BranchOrBacktrack(Assembler::LessThan, on_less); +} + +void +NativeRegExpMacroAssembler::CheckGreedyLoop(Label* on_tos_equals_current_position) +{ + IonSpew(SPEW_PREFIX "CheckGreedyLoop"); + + Label fallthrough; + masm.cmpPtr(Address(backtrack_stack_pointer, -int(sizeof(void *))), current_position); + masm.j(Assembler::NotEqual, &fallthrough); + masm.subPtr(Imm32(sizeof(void *)), backtrack_stack_pointer); // Pop. + JumpOrBacktrack(on_tos_equals_current_position); + masm.bind(&fallthrough); +} + +void +NativeRegExpMacroAssembler::CheckNotBackReference(int start_reg, Label* on_no_match) +{ + IonSpew(SPEW_PREFIX "CheckNotBackReference(%d)", start_reg); + + Label fallthrough; + Label success; + Label fail; + + // Find length of back-referenced capture. + masm.loadPtr(register_location(start_reg), current_character); + masm.loadPtr(register_location(start_reg + 1), temp0); + masm.subPtr(current_character, temp0); // Length to check. + masm.cmpPtr(temp0, ImmWord(0)); + + // Fail on partial or illegal capture (start of capture after end of capture). + BranchOrBacktrack(Assembler::LessThan, on_no_match); + + // Succeed on empty capture (including no capture). + masm.j(Assembler::Equal, &fallthrough); + + // Check that there are sufficient characters left in the input. + masm.mov(current_position, temp1); + masm.addPtr(temp0, temp1); + masm.cmpPtr(temp1, ImmWord(0)); + BranchOrBacktrack(Assembler::GreaterThan, on_no_match); + + // Save register to make it available below. + masm.push(backtrack_stack_pointer); + + // Compute pointers to match string and capture string + masm.computeEffectiveAddress(BaseIndex(input_end_pointer, current_position, TimesOne), temp1); // Start of match. + masm.addPtr(input_end_pointer, current_character); // Start of capture. + masm.computeEffectiveAddress(BaseIndex(temp0, temp1, TimesOne), backtrack_stack_pointer); // End of match. + + Label loop; + masm.bind(&loop); + if (mode_ == ASCII) { + MOZ_ASSUME_UNREACHABLE("Ascii loading not implemented"); + } else { + JS_ASSERT(mode_ == JSCHAR); + masm.load16ZeroExtend(Address(current_character, 0), temp0); + masm.load16ZeroExtend(Address(temp1, 0), temp2); + } + masm.branch32(Assembler::NotEqual, temp0, temp2, &fail); + + // Increment pointers into capture and match string. + masm.addPtr(Imm32(char_size()), current_character); + masm.addPtr(Imm32(char_size()), temp1); + + // Check if we have reached end of match area. + masm.branchPtr(Assembler::Below, temp1, backtrack_stack_pointer, &loop); + masm.jump(&success); + + masm.bind(&fail); + + // Restore backtrack stack pointer. + masm.pop(backtrack_stack_pointer); + JumpOrBacktrack(on_no_match); + + masm.bind(&success); + + // Move current character position to position after match. + masm.mov(backtrack_stack_pointer, current_position); + masm.subPtr(input_end_pointer, current_position); + + // Restore backtrack stack pointer. + masm.pop(backtrack_stack_pointer); + + masm.bind(&fallthrough); +} + +void +NativeRegExpMacroAssembler::CheckNotBackReferenceIgnoreCase(int start_reg, Label* on_no_match) +{ + IonSpew(SPEW_PREFIX "CheckNotBackReferenceIgnoreCase(%d)", start_reg); + + Label fallthrough; + + masm.loadPtr(register_location(start_reg), current_character); // Index of start of capture + masm.loadPtr(register_location(start_reg + 1), temp1); // Index of end of capture + masm.subPtr(current_character, temp1); // Length of capture. + masm.cmpPtr(temp1, ImmWord(0)); + + // The length of a capture should not be negative. This can only happen + // if the end of the capture is unrecorded, or at a point earlier than + // the start of the capture. + BranchOrBacktrack(Assembler::LessThan, on_no_match); + + // If length is zero, either the capture is empty or it is completely + // uncaptured. In either case succeed immediately. + masm.j(Assembler::Equal, &fallthrough); + + // Check that there are sufficient characters left in the input. + masm.mov(current_position, temp0); + masm.addPtr(temp1, temp0); + masm.cmpPtr(temp0, ImmWord(0)); + BranchOrBacktrack(Assembler::GreaterThan, on_no_match); + + if (mode_ == ASCII) { + MOZ_ASSUME_UNREACHABLE("Ascii case not implemented"); + } else { + JS_ASSERT(mode_ == JSCHAR); + + // Note: temp1 needs to be saved/restored if it is volatile, as it is used after the call. + RegisterSet volatileRegs = RegisterSet::Volatile(); + volatileRegs.takeUnchecked(temp0); + volatileRegs.takeUnchecked(temp2); + masm.PushRegsInMask(volatileRegs); + + // Set byte_offset1. + // Start of capture, where current_character already holds string-end negative offset. + masm.addPtr(input_end_pointer, current_character); + + // Set byte_offset2. + // Found by adding negative string-end offset of current position + // to end of string. + masm.addPtr(input_end_pointer, current_position); + + // Parameters are + // Address byte_offset1 - Address captured substring's start. + // Address byte_offset2 - Address of current character position. + // size_t byte_length - length of capture in bytes(!) + masm.setupUnalignedABICall(3, temp0); + masm.passABIArg(current_character); + masm.passABIArg(current_position); + masm.passABIArg(temp1); + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, CaseInsensitiveCompareStrings)); + masm.storeCallResult(temp0); + + masm.PopRegsInMask(volatileRegs); + + // Check if function returned non-zero for success or zero for failure. + masm.test32(temp0, temp0); + BranchOrBacktrack(Assembler::Zero, on_no_match); + + // On success, increment position by length of capture. + masm.addPtr(temp1, current_position); + } + + masm.bind(&fallthrough); +} + +void +NativeRegExpMacroAssembler::CheckNotCharacterAfterMinusAnd(jschar c, jschar minus, jschar and_with, + Label* on_not_equal) +{ + IonSpew(SPEW_PREFIX "CheckNotCharacterAfterMinusAnd(%d, %d, %d)", (int) c, (int) minus, (int) and_with); + + masm.computeEffectiveAddress(Address(current_character, -minus), temp0); + if (c == 0) { + masm.test32(temp0, Imm32(and_with)); + BranchOrBacktrack(Assembler::NonZero, on_not_equal); + } else { + masm.and32(Imm32(and_with), temp0); + masm.cmp32(temp0, Imm32(c)); + BranchOrBacktrack(Assembler::NotEqual, on_not_equal); + } +} + +void +NativeRegExpMacroAssembler::CheckCharacterInRange(jschar from, jschar to, + Label* on_in_range) +{ + IonSpew(SPEW_PREFIX "CheckCharacterInRange(%d, %d)", (int) from, (int) to); + + masm.computeEffectiveAddress(Address(current_character, -from), temp0); + masm.cmp32(temp0, Imm32(to - from)); + BranchOrBacktrack(Assembler::BelowOrEqual, on_in_range); +} + +void +NativeRegExpMacroAssembler::CheckCharacterNotInRange(jschar from, jschar to, + Label* on_not_in_range) +{ + IonSpew(SPEW_PREFIX "CheckCharacterNotInRange(%d, %d)", (int) from, (int) to); + + masm.computeEffectiveAddress(Address(current_character, -from), temp0); + masm.cmp32(temp0, Imm32(to - from)); + BranchOrBacktrack(Assembler::Above, on_not_in_range); +} + +void +NativeRegExpMacroAssembler::CheckBitInTable(uint8_t *table, Label *on_bit_set) +{ + IonSpew(SPEW_PREFIX "CheckBitInTable"); + + JS_ASSERT(mode_ != ASCII); // Ascii case not handled here. + + masm.mov(ImmPtr(table), temp0); + masm.mov(ImmWord(kTableSize - 1), temp1); + masm.and32(current_character, temp1); + + masm.load8ZeroExtend(BaseIndex(temp0, temp1, TimesOne), temp0); + masm.test32(temp0, temp0); + BranchOrBacktrack(Assembler::NotEqual, on_bit_set); +} + +void +NativeRegExpMacroAssembler::Fail() +{ + IonSpew(SPEW_PREFIX "Fail"); + + if (!global()) + masm.mov(ImmWord(RegExpRunStatus_Success_NotFound), temp0); + masm.jump(&exit_label_); +} + +void +NativeRegExpMacroAssembler::IfRegisterGE(int reg, int comparand, Label* if_ge) +{ + IonSpew(SPEW_PREFIX "IfRegisterGE(%d, %d)", reg, comparand); + + masm.cmpPtr(register_location(reg), ImmWord(comparand)); + BranchOrBacktrack(Assembler::GreaterThanOrEqual, if_ge); +} + +void +NativeRegExpMacroAssembler::IfRegisterLT(int reg, int comparand, Label* if_lt) +{ + IonSpew(SPEW_PREFIX "IfRegisterLT(%d, %d)", reg, comparand); + + masm.cmpPtr(register_location(reg), ImmWord(comparand)); + BranchOrBacktrack(Assembler::LessThan, if_lt); +} + +void +NativeRegExpMacroAssembler::IfRegisterEqPos(int reg, Label* if_eq) +{ + IonSpew(SPEW_PREFIX "IfRegisterEqPos(%d)", reg); + + masm.cmpPtr(register_location(reg), current_position); + BranchOrBacktrack(Assembler::Equal, if_eq); +} + +void +NativeRegExpMacroAssembler::LoadCurrentCharacter(int cp_offset, Label* on_end_of_input, + bool check_bounds, int characters) +{ + IonSpew(SPEW_PREFIX "LoadCurrentCharacter(%d, %d)", cp_offset, characters); + + JS_ASSERT(cp_offset >= -1); // ^ and \b can look behind one character. + JS_ASSERT(cp_offset < (1<<30)); // Be sane! (And ensure negation works) + if (check_bounds) + CheckPosition(cp_offset + characters - 1, on_end_of_input); + LoadCurrentCharacterUnchecked(cp_offset, characters); +} + +void +NativeRegExpMacroAssembler::LoadCurrentCharacterUnchecked(int cp_offset, int characters) +{ + IonSpew(SPEW_PREFIX "LoadCurrentCharacterUnchecked(%d, %d)", cp_offset, characters); + + JS_ASSERT(characters == 1); + if (mode_ == ASCII) { + MOZ_ASSUME_UNREACHABLE("Ascii loading not implemented"); + } else { + JS_ASSERT(mode_ == JSCHAR); + masm.load16ZeroExtend(BaseIndex(input_end_pointer, current_position, TimesOne, cp_offset * sizeof(jschar)), + current_character); + } +} + +void +NativeRegExpMacroAssembler::PopCurrentPosition() +{ + IonSpew(SPEW_PREFIX "PopCurrentPosition"); + + PopBacktrack(current_position); +} + +void +NativeRegExpMacroAssembler::PopRegister(int register_index) +{ + IonSpew(SPEW_PREFIX "PopRegister(%d)", register_index); + + PopBacktrack(temp0); + masm.storePtr(temp0, register_location(register_index)); +} + +void +NativeRegExpMacroAssembler::PushBacktrack(Label *label) +{ + IonSpew(SPEW_PREFIX "PushBacktrack"); + + CodeOffsetLabel patchOffset = masm.movWithPatch(ImmPtr(nullptr), temp0); + + JS_ASSERT(!label->bound()); + if (!labelPatches.append(LabelPatch(label, patchOffset))) + CrashAtUnhandlableOOM("NativeRegExpMacroAssembler::PushBacktrack"); + + PushBacktrack(temp0); + CheckBacktrackStackLimit(); +} + +void +NativeRegExpMacroAssembler::BindBacktrack(Label *label) +{ + IonSpew(SPEW_PREFIX "BindBacktrack"); + + Bind(label); + + for (size_t i = 0; i < labelPatches.length(); i++) { + LabelPatch &v = labelPatches[i]; + if (v.label == label) { + v.labelOffset = label->offset(); + v.label = nullptr; + break; + } + } +} + +void +NativeRegExpMacroAssembler::PushBacktrack(Register source) +{ + IonSpew(SPEW_PREFIX "PushBacktrack"); + + JS_ASSERT(source != backtrack_stack_pointer); + + // Notice: This updates flags, unlike normal Push. + masm.storePtr(source, Address(backtrack_stack_pointer, 0)); + masm.addPtr(Imm32(sizeof(void *)), backtrack_stack_pointer); +} + +void +NativeRegExpMacroAssembler::PushBacktrack(int32_t value) +{ + IonSpew(SPEW_PREFIX "PushBacktrack(%d)", (int) value); + + // Notice: This updates flags, unlike normal Push. + masm.storePtr(ImmWord(value), Address(backtrack_stack_pointer, 0)); + masm.addPtr(Imm32(sizeof(void *)), backtrack_stack_pointer); +} + +void +NativeRegExpMacroAssembler::PopBacktrack(Register target) +{ + IonSpew(SPEW_PREFIX "PopBacktrack"); + + JS_ASSERT(target != backtrack_stack_pointer); + + // Notice: This updates flags, unlike normal Pop. + masm.subPtr(Imm32(sizeof(void *)), backtrack_stack_pointer); + masm.loadPtr(Address(backtrack_stack_pointer, 0), target); +} + +void +NativeRegExpMacroAssembler::CheckBacktrackStackLimit() +{ + IonSpew(SPEW_PREFIX "CheckBacktrackStackLimit"); + + const void *limitAddr = runtime->mainThread.regexpStack.addressOfLimit(); + + Label no_stack_overflow; + masm.branchPtr(Assembler::AboveOrEqual, AbsoluteAddress(limitAddr), + backtrack_stack_pointer, &no_stack_overflow); + + // Copy the stack pointer before the call() instruction modifies it. + masm.mov(StackPointer, temp2); + + masm.call(&stack_overflow_label_); + masm.bind(&no_stack_overflow); + + // Exit with an exception if the call failed. + masm.test32(temp0, temp0); + masm.j(Assembler::Zero, &exit_with_exception_label_); +} + +void +NativeRegExpMacroAssembler::PushCurrentPosition() +{ + IonSpew(SPEW_PREFIX "PushCurrentPosition"); + + PushBacktrack(current_position); +} + +void +NativeRegExpMacroAssembler::PushRegister(int register_index, StackCheckFlag check_stack_limit) +{ + IonSpew(SPEW_PREFIX "PushRegister(%d)", register_index); + + masm.loadPtr(register_location(register_index), temp0); + PushBacktrack(temp0); + if (check_stack_limit) + CheckBacktrackStackLimit(); +} + +void +NativeRegExpMacroAssembler::ReadCurrentPositionFromRegister(int reg) +{ + IonSpew(SPEW_PREFIX "ReadCurrentPositionFromRegister(%d)", reg); + + masm.loadPtr(register_location(reg), current_position); +} + +void +NativeRegExpMacroAssembler::WriteCurrentPositionToRegister(int reg, int cp_offset) +{ + IonSpew(SPEW_PREFIX "WriteCurrentPositionToRegister(%d, %d)", reg, cp_offset); + + if (cp_offset == 0) { + masm.storePtr(current_position, register_location(reg)); + } else { + masm.computeEffectiveAddress(Address(current_position, cp_offset * char_size()), temp0); + masm.storePtr(temp0, register_location(reg)); + } +} + +void +NativeRegExpMacroAssembler::ReadBacktrackStackPointerFromRegister(int reg) +{ + IonSpew(SPEW_PREFIX "ReadBacktrackStackPointerFromRegister(%d)", reg); + + masm.loadPtr(register_location(reg), backtrack_stack_pointer); + masm.addPtr(Address(StackPointer, offsetof(FrameData, backtrackStackBase)), backtrack_stack_pointer); +} + +void +NativeRegExpMacroAssembler::WriteBacktrackStackPointerToRegister(int reg) +{ + IonSpew(SPEW_PREFIX "WriteBacktrackStackPointerToRegister(%d)", reg); + + masm.mov(backtrack_stack_pointer, temp0); + masm.subPtr(Address(StackPointer, offsetof(FrameData, backtrackStackBase)), temp0); + masm.storePtr(temp0, register_location(reg)); +} + +void +NativeRegExpMacroAssembler::SetCurrentPositionFromEnd(int by) +{ + IonSpew(SPEW_PREFIX "SetCurrentPositionFromEnd(%d)", by); + + Label after_position; + masm.cmpPtr(current_position, ImmWord(-by * char_size())); + masm.j(Assembler::GreaterThanOrEqual, &after_position); + masm.mov(ImmWord(-by * char_size()), current_position); + + // On RegExp code entry (where this operation is used), the character before + // the current position is expected to be already loaded. + // We have advanced the position, so it's safe to read backwards. + LoadCurrentCharacterUnchecked(-1, 1); + masm.bind(&after_position); +} + +void +NativeRegExpMacroAssembler::SetRegister(int register_index, int to) +{ + IonSpew(SPEW_PREFIX "SetRegister(%d, %d)", register_index, to); + + JS_ASSERT(register_index >= num_saved_registers_); // Reserved for positions! + masm.storePtr(ImmWord(to), register_location(register_index)); +} + +bool +NativeRegExpMacroAssembler::Succeed() +{ + IonSpew(SPEW_PREFIX "Succeed"); + + masm.jump(&success_label_); + return global(); +} + +void +NativeRegExpMacroAssembler::ClearRegisters(int reg_from, int reg_to) +{ + IonSpew(SPEW_PREFIX "ClearRegisters(%d, %d)", reg_from, reg_to); + + JS_ASSERT(reg_from <= reg_to); + masm.loadPtr(Address(StackPointer, offsetof(FrameData, inputStartMinusOne)), temp0); + for (int reg = reg_from; reg <= reg_to; reg++) + masm.storePtr(temp0, register_location(reg)); +} + +void +NativeRegExpMacroAssembler::CheckPosition(int cp_offset, Label* on_outside_input) +{ + IonSpew(SPEW_PREFIX "CheckPosition(%d)", cp_offset); + + masm.cmpPtr(current_position, ImmWord(-cp_offset * char_size())); + BranchOrBacktrack(Assembler::GreaterThanOrEqual, on_outside_input); +} + +void +NativeRegExpMacroAssembler::BranchOrBacktrack(Assembler::Condition condition, Label *to) +{ + IonSpew(SPEW_PREFIX "BranchOrBacktrack"); + + if (to) + masm.j(condition, to); + else + masm.j(condition, &backtrack_label_); +} + +void +NativeRegExpMacroAssembler::JumpOrBacktrack(Label *to) +{ + IonSpew(SPEW_PREFIX "JumpOrBacktrack"); + + if (to) + masm.jump(to); + else + Backtrack(); +} + +bool +NativeRegExpMacroAssembler::CheckSpecialCharacterClass(jschar type, Label* on_no_match) +{ + IonSpew(SPEW_PREFIX "CheckSpecialCharacterClass(%d)", (int) type); + + // Range checks (c in min..max) are generally implemented by an unsigned + // (c - min) <= (max - min) check + switch (type) { + case 's': + // Match space-characters + if (mode_ == ASCII) + MOZ_ASSUME_UNREACHABLE("Ascii version not implemented"); + return false; + case 'S': + // The emitted code for generic character classes is good enough. + return false; + case 'd': + // Match ASCII digits ('0'..'9') + masm.computeEffectiveAddress(Address(current_character, -'0'), temp0); + masm.cmp32(temp0, Imm32('9' - '0')); + BranchOrBacktrack(Assembler::Above, on_no_match); + return true; + case 'D': + // Match non ASCII-digits + masm.computeEffectiveAddress(Address(current_character, -'0'), temp0); + masm.cmp32(temp0, Imm32('9' - '0')); + BranchOrBacktrack(Assembler::BelowOrEqual, on_no_match); + return true; + case '.': { + // Match non-newlines (not 0x0a('\n'), 0x0d('\r'), 0x2028 and 0x2029) + masm.mov(current_character, temp0); + masm.xor32(Imm32(0x01), temp0); + + // See if current character is '\n'^1 or '\r'^1, i.e., 0x0b or 0x0c + masm.sub32(Imm32(0x0b), temp0); + masm.cmp32(temp0, Imm32(0x0c - 0x0b)); + BranchOrBacktrack(Assembler::BelowOrEqual, on_no_match); + if (mode_ == JSCHAR) { + // Compare original value to 0x2028 and 0x2029, using the already + // computed (current_char ^ 0x01 - 0x0b). I.e., check for + // 0x201d (0x2028 - 0x0b) or 0x201e. + masm.sub32(Imm32(0x2028 - 0x0b), temp0); + masm.cmp32(temp0, Imm32(0x2029 - 0x2028)); + BranchOrBacktrack(Assembler::BelowOrEqual, on_no_match); + } + return true; + } + case 'w': { + if (mode_ != ASCII) { + // Table is 128 entries, so all ASCII characters can be tested. + masm.cmp32(current_character, Imm32('z')); + BranchOrBacktrack(Assembler::Above, on_no_match); + } + JS_ASSERT(0 == word_character_map[0]); // Character '\0' is not a word char. + masm.mov(ImmPtr(word_character_map), temp0); + masm.load8ZeroExtend(BaseIndex(temp0, current_character, TimesOne), temp0); + masm.test32(temp0, temp0); + BranchOrBacktrack(Assembler::Zero, on_no_match); + return true; + } + case 'W': { + Label done; + if (mode_ != ASCII) { + // Table is 128 entries, so all ASCII characters can be tested. + masm.cmp32(current_character, Imm32('z')); + masm.j(Assembler::Above, &done); + } + JS_ASSERT(0 == word_character_map[0]); // Character '\0' is not a word char. + masm.mov(ImmPtr(word_character_map), temp0); + masm.load8ZeroExtend(BaseIndex(temp0, current_character, TimesOne), temp0); + masm.test32(temp0, temp0); + BranchOrBacktrack(Assembler::NonZero, on_no_match); + if (mode_ != ASCII) + masm.bind(&done); + return true; + } + // Non-standard classes (with no syntactic shorthand) used internally. + case '*': + // Match any character. + return true; + case 'n': { + // Match newlines (0x0a('\n'), 0x0d('\r'), 0x2028 or 0x2029). + // The opposite of '.'. + masm.mov(current_character, temp0); + masm.xor32(Imm32(0x01), temp0); + + // See if current character is '\n'^1 or '\r'^1, i.e., 0x0b or 0x0c + masm.sub32(Imm32(0x0b), temp0); + masm.cmp32(temp0, Imm32(0x0c - 0x0b)); + + if (mode_ == ASCII) { + BranchOrBacktrack(Assembler::Above, on_no_match); + } else { + Label done; + BranchOrBacktrack(Assembler::BelowOrEqual, &done); + JS_ASSERT(JSCHAR == mode_); + + // Compare original value to 0x2028 and 0x2029, using the already + // computed (current_char ^ 0x01 - 0x0b). I.e., check for + // 0x201d (0x2028 - 0x0b) or 0x201e. + masm.sub32(Imm32(0x2028 - 0x0b), temp0); + masm.cmp32(temp0, Imm32(1)); + BranchOrBacktrack(Assembler::Above, on_no_match); + + masm.bind(&done); + } + return true; + } + // No custom implementation (yet): + default: + return false; + } +} + +bool +NativeRegExpMacroAssembler::CanReadUnaligned() +{ + // XXX Bug 1006799 should this be enabled? Unaligned loads can be slow even + // on platforms where they are supported. + return false; +} + +const uint8_t +NativeRegExpMacroAssembler::word_character_map[] = +{ + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, // '0' - '7' + 0xffu, 0xffu, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, // '8' - '9' + + 0x00u, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, // 'A' - 'G' + 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, // 'H' - 'O' + 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, // 'P' - 'W' + 0xffu, 0xffu, 0xffu, 0x00u, 0x00u, 0x00u, 0x00u, 0xffu, // 'X' - 'Z', '_' + + 0x00u, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, // 'a' - 'g' + 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, // 'h' - 'o' + 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, // 'p' - 'w' + 0xffu, 0xffu, 0xffu, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, // 'x' - 'z' + + // Latin-1 range + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, +}; diff --git a/js/src/irregexp/NativeRegExpMacroAssembler.h b/js/src/irregexp/NativeRegExpMacroAssembler.h new file mode 100644 index 00000000000..a8179fe1be5 --- /dev/null +++ b/js/src/irregexp/NativeRegExpMacroAssembler.h @@ -0,0 +1,224 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: */ + +// Copyright 2012 the V8 project authors. 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. + +#ifndef V8_NATIVE_REGEXP_MACRO_ASSEMBLER_H_ +#define V8_NATIVE_REGEXP_MACRO_ASSEMBLER_H_ + +#ifdef JS_ION + +#include "irregexp/RegExpMacroAssembler.h" + +namespace js { +namespace irregexp { + +struct InputOutputData +{ + const jschar *inputStart; + const jschar *inputEnd; + + // Index into inputStart (in chars) at which to begin matching. + size_t startIndex; + + MatchPairs *matches; + + // RegExpMacroAssembler::Result for non-global regexps, number of captures + // for global regexps. + int32_t result; + + InputOutputData(const jschar *inputStart, const jschar *inputEnd, + size_t startIndex, MatchPairs *matches) + : inputStart(inputStart), + inputEnd(inputEnd), + startIndex(startIndex), + matches(matches), + result(0) + {} +}; + +struct FrameData +{ + // Copy of the input/output data's data. + jschar *inputStart; + size_t startIndex; + + // Pointer to the character before the input start. + jschar *inputStartMinusOne; + + // Copy of the input MatchPairs registers, may be modified by JIT code. + int32_t *outputRegisters; + int32_t numOutputRegisters; + + int32_t successfulCaptures; + + void *backtrackStackBase; +}; + +class MOZ_STACK_CLASS NativeRegExpMacroAssembler : public RegExpMacroAssembler +{ + public: + // Type of input string to generate code for. + enum Mode { ASCII = 1, JSCHAR = 2 }; + + NativeRegExpMacroAssembler(LifoAlloc *alloc, RegExpShared *shared, + JSRuntime *rt, Mode mode, int registers_to_save); + + // Inherited virtual methods. + RegExpCode GenerateCode(JSContext *cx); + int stack_limit_slack(); + bool CanReadUnaligned(); + void AdvanceCurrentPosition(int by); + void AdvanceRegister(int reg, int by); + void Backtrack(); + void Bind(jit::Label* label); + void CheckAtStart(jit::Label* on_at_start); + void CheckCharacter(unsigned c, jit::Label* on_equal); + void CheckCharacterAfterAnd(unsigned c, unsigned and_with, jit::Label* on_equal); + void CheckCharacterGT(jschar limit, jit::Label* on_greater); + void CheckCharacterLT(jschar limit, jit::Label* on_less); + void CheckGreedyLoop(jit::Label* on_tos_equals_current_position); + void CheckNotAtStart(jit::Label* on_not_at_start); + void CheckNotBackReference(int start_reg, jit::Label* on_no_match); + void CheckNotBackReferenceIgnoreCase(int start_reg, jit::Label* on_no_match); + void CheckNotCharacter(unsigned c, jit::Label* on_not_equal); + void CheckNotCharacterAfterAnd(unsigned c, unsigned and_with, jit::Label* on_not_equal); + void CheckNotCharacterAfterMinusAnd(jschar c, jschar minus, jschar and_with, + jit::Label* on_not_equal); + void CheckCharacterInRange(jschar from, jschar to, + jit::Label* on_in_range); + void CheckCharacterNotInRange(jschar from, jschar to, + jit::Label* on_not_in_range); + void CheckBitInTable(uint8_t *table, jit::Label* on_bit_set); + void CheckPosition(int cp_offset, jit::Label* on_outside_input); + void JumpOrBacktrack(jit::Label *to); + bool CheckSpecialCharacterClass(jschar type, jit::Label* on_no_match); + void Fail(); + void IfRegisterGE(int reg, int comparand, jit::Label* if_ge); + void IfRegisterLT(int reg, int comparand, jit::Label* if_lt); + void IfRegisterEqPos(int reg, jit::Label* if_eq); + void LoadCurrentCharacter(int cp_offset, jit::Label* on_end_of_input, + bool check_bounds = true, int characters = 1); + void PopCurrentPosition(); + void PopRegister(int register_index); + void PushCurrentPosition(); + void PushRegister(int register_index, StackCheckFlag check_stack_limit); + void ReadCurrentPositionFromRegister(int reg); + void ReadBacktrackStackPointerFromRegister(int reg); + void SetCurrentPositionFromEnd(int by); + void SetRegister(int register_index, int to); + bool Succeed(); + void WriteCurrentPositionToRegister(int reg, int cp_offset); + void ClearRegisters(int reg_from, int reg_to); + void WriteBacktrackStackPointerToRegister(int reg); + void PushBacktrack(jit::Label *label); + void BindBacktrack(jit::Label *label); + + // Compares two-byte strings case insensitively. + // Called from generated RegExp code. + static int CaseInsensitiveCompareUC16(jit::Address byte_offset1, + jit::Address byte_offset2, + size_t byte_length); + + // Byte map of one byte characters with a 0xff if the character is a word + // character (digit, letter or underscore) and 0x00 otherwise. + // Used by generated RegExp code. + static const uint8_t word_character_map[256]; + + // Byte size of chars in the string to match (decided by the Mode argument) + inline int char_size() { return static_cast(mode_); } + inline jit::Scale factor() { return mode_ == JSCHAR ? jit::TimesTwo : jit::TimesOne; } + + void BranchOrBacktrack(jit::Assembler::Condition condition, jit::Label *to); + + // Pushes a register or constant on the backtrack stack. Decrements the + // stack pointer by a word size and stores the register's value there. + void PushBacktrack(jit::Register value); + void PushBacktrack(int32_t value); + + // Pop a value from the backtrack stack. + void PopBacktrack(jit::Register target); + + // Check whether we are exceeding the stack limit on the backtrack stack. + void CheckBacktrackStackLimit(); + + void LoadCurrentCharacterUnchecked(int cp_offset, int characters); + + private: + jit::MacroAssembler masm; + + JSRuntime *runtime; + Mode mode_; + jit::Label entry_label_; + jit::Label start_label_; + jit::Label backtrack_label_; + jit::Label success_label_; + jit::Label exit_label_; + jit::Label stack_overflow_label_; + jit::Label exit_with_exception_label_; + + jit::GeneralRegisterSet savedNonVolatileRegisters; + + struct LabelPatch { + // Once it is bound via BindBacktrack, |label| becomes null and + // |labelOffset| is set. + jit::Label *label; + size_t labelOffset; + + jit::CodeOffsetLabel patchOffset; + + LabelPatch(jit::Label *label, jit::CodeOffsetLabel patchOffset) + : label(label), labelOffset(0), patchOffset(patchOffset) + {} + }; + + Vector labelPatches; + + // See RegExpMacroAssembler.cpp for the meaning of these registers. + jit::Register input_end_pointer; + jit::Register current_character; + jit::Register current_position; + jit::Register backtrack_stack_pointer; + jit::Register temp0, temp1, temp2; + + // The frame_pointer-relative location of a regexp register. + jit::Address register_location(int register_index) { + checkRegister(register_index); + return jit::Address(jit::StackPointer, register_offset(register_index)); + } + + int32_t register_offset(int register_index) { + return sizeof(FrameData) + register_index * sizeof(void *); + } +}; + +} } // namespace js::irregexp + +#endif // JS_ION + +#endif // V8_NATIVE_REGEXP_MACRO_ASSEMBLER_H_ diff --git a/js/src/irregexp/RegExpAST.cpp b/js/src/irregexp/RegExpAST.cpp new file mode 100644 index 00000000000..88a2b0690a0 --- /dev/null +++ b/js/src/irregexp/RegExpAST.cpp @@ -0,0 +1,265 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: */ + +// Copyright 2012 the V8 project authors. 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. + +#include "irregexp/RegExpAST.h" + +using namespace js; +using namespace js::irregexp; + +#define MAKE_ACCEPT(Name) \ + void* RegExp##Name::Accept(RegExpVisitor* visitor, void* data) { \ + return visitor->Visit##Name(this, data); \ + } +FOR_EACH_REG_EXP_TREE_TYPE(MAKE_ACCEPT) +#undef MAKE_ACCEPT + +#define MAKE_TYPE_CASE(Name) \ + RegExp##Name* RegExpTree::As##Name() { \ + return nullptr; \ + } \ + bool RegExpTree::Is##Name() { return false; } +FOR_EACH_REG_EXP_TREE_TYPE(MAKE_TYPE_CASE) +#undef MAKE_TYPE_CASE + +#define MAKE_TYPE_CASE(Name) \ + RegExp##Name* RegExp##Name::As##Name() { \ + return this; \ + } \ + bool RegExp##Name::Is##Name() { return true; } +FOR_EACH_REG_EXP_TREE_TYPE(MAKE_TYPE_CASE) +#undef MAKE_TYPE_CASE + +static Interval +ListCaptureRegisters(const RegExpTreeVector &children) +{ + Interval result = Interval::Empty(); + for (size_t i = 0; i < children.length(); i++) + result = result.Union(children[i]->CaptureRegisters()); + return result; +} + +// ---------------------------------------------------------------------------- +// RegExpDisjunction + +RegExpDisjunction::RegExpDisjunction(RegExpTreeVector *alternatives) + : alternatives_(alternatives) +{ + JS_ASSERT(alternatives->length() > 1); + RegExpTree* first_alternative = (*alternatives)[0]; + min_match_ = first_alternative->min_match(); + max_match_ = first_alternative->max_match(); + for (size_t i = 1; i < alternatives->length(); i++) { + RegExpTree* alternative = (*alternatives)[i]; + min_match_ = Min(min_match_, alternative->min_match()); + max_match_ = Max(max_match_, alternative->max_match()); + } +} + +Interval +RegExpDisjunction::CaptureRegisters() +{ + return ListCaptureRegisters(alternatives()); +} + +bool +RegExpDisjunction::IsAnchoredAtStart() +{ + const RegExpTreeVector &alternatives = this->alternatives(); + for (size_t i = 0; i < alternatives.length(); i++) { + if (!alternatives[i]->IsAnchoredAtStart()) + return false; + } + return true; +} + +bool +RegExpDisjunction::IsAnchoredAtEnd() +{ + const RegExpTreeVector &alternatives = this->alternatives(); + for (size_t i = 0; i < alternatives.length(); i++) { + if (!alternatives[i]->IsAnchoredAtEnd()) + return false; + } + return true; +} + +// ---------------------------------------------------------------------------- +// RegExpAlternative + +static int IncreaseBy(int previous, int increase) +{ + if (RegExpTree::kInfinity - previous < increase) + return RegExpTree::kInfinity; + return previous + increase; +} + +RegExpAlternative::RegExpAlternative(RegExpTreeVector *nodes) + : nodes_(nodes), + min_match_(0), + max_match_(0) +{ + JS_ASSERT(nodes->length() > 1); + for (size_t i = 0; i < nodes->length(); i++) { + RegExpTree* node = (*nodes)[i]; + int node_min_match = node->min_match(); + min_match_ = IncreaseBy(min_match_, node_min_match); + int node_max_match = node->max_match(); + max_match_ = IncreaseBy(max_match_, node_max_match); + } +} + +Interval +RegExpAlternative::CaptureRegisters() +{ + return ListCaptureRegisters(nodes()); +} + +bool +RegExpAlternative::IsAnchoredAtStart() +{ + const RegExpTreeVector &nodes = this->nodes(); + for (size_t i = 0; i < nodes.length(); i++) { + RegExpTree *node = nodes[i]; + if (node->IsAnchoredAtStart()) { return true; } + if (node->max_match() > 0) { return false; } + } + return false; +} + +bool +RegExpAlternative::IsAnchoredAtEnd() +{ + const RegExpTreeVector &nodes = this->nodes(); + for (int i = nodes.length() - 1; i >= 0; i--) { + RegExpTree *node = nodes[i]; + if (node->IsAnchoredAtEnd()) { return true; } + if (node->max_match() > 0) { return false; } + } + return false; +} + +// ---------------------------------------------------------------------------- +// RegExpAssertion + +bool +RegExpAssertion::IsAnchoredAtStart() +{ + return assertion_type() == RegExpAssertion::START_OF_INPUT; +} + +bool +RegExpAssertion::IsAnchoredAtEnd() +{ + return assertion_type() == RegExpAssertion::END_OF_INPUT; +} + +// ---------------------------------------------------------------------------- +// RegExpCharacterClass + +void +RegExpCharacterClass::AppendToText(RegExpText* text) +{ + text->AddElement(TextElement::CharClass(this)); +} + +CharacterRangeVector & +CharacterSet::ranges(LifoAlloc *alloc) +{ + if (ranges_ == nullptr) { + ranges_ = alloc->newInfallible(*alloc); + CharacterRange::AddClassEscape(alloc, standard_set_type_, ranges_); + } + return *ranges_; +} + +// ---------------------------------------------------------------------------- +// RegExpAtom + +void +RegExpAtom::AppendToText(RegExpText* text) +{ + text->AddElement(TextElement::Atom(this)); +} + +// ---------------------------------------------------------------------------- +// RegExpText + +void +RegExpText::AppendToText(RegExpText* text) +{ + for (size_t i = 0; i < elements().length(); i++) + text->AddElement(elements()[i]); +} + +// ---------------------------------------------------------------------------- +// RegExpQuantifier + +Interval +RegExpQuantifier::CaptureRegisters() +{ + return body()->CaptureRegisters(); +} + +// ---------------------------------------------------------------------------- +// RegExpCapture + +bool +RegExpCapture::IsAnchoredAtStart() +{ + return body()->IsAnchoredAtStart(); +} + +bool +RegExpCapture::IsAnchoredAtEnd() +{ + return body()->IsAnchoredAtEnd(); +} + +Interval +RegExpCapture::CaptureRegisters() +{ + Interval self(StartRegister(index()), EndRegister(index())); + return self.Union(body()->CaptureRegisters()); +} + +// ---------------------------------------------------------------------------- +// RegExpLookahead + +Interval +RegExpLookahead::CaptureRegisters() +{ + return body()->CaptureRegisters(); +} + +bool +RegExpLookahead::IsAnchoredAtStart() +{ + return is_positive() && body()->IsAnchoredAtStart(); +} diff --git a/js/src/irregexp/RegExpAST.h b/js/src/irregexp/RegExpAST.h new file mode 100644 index 00000000000..eaed64b49f9 --- /dev/null +++ b/js/src/irregexp/RegExpAST.h @@ -0,0 +1,439 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: */ + +// Copyright 2012 the V8 project authors. 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. + +#ifndef V8_REGEXP_AST_H_ +#define V8_REGEXP_AST_H_ + +#include "irregexp/RegExpEngine.h" + +namespace js { +namespace irregexp { + +class RegExpCompiler; +class RegExpNode; + +class RegExpVisitor +{ + public: + virtual ~RegExpVisitor() { } +#define MAKE_CASE(Name) \ + virtual void* Visit##Name(RegExp##Name*, void* data) = 0; + FOR_EACH_REG_EXP_TREE_TYPE(MAKE_CASE) +#undef MAKE_CASE +}; + +class RegExpTree +{ + public: + static const int kInfinity = INT32_MAX; + virtual ~RegExpTree() {} + virtual RegExpNode* ToNode(RegExpCompiler* compiler, + RegExpNode* on_success) = 0; + virtual bool IsTextElement() { return false; } + virtual bool IsAnchoredAtStart() { return false; } + virtual bool IsAnchoredAtEnd() { return false; } + virtual int min_match() = 0; + virtual int max_match() = 0; + // Returns the interval of registers used for captures within this + // expression. + virtual Interval CaptureRegisters() { return Interval::Empty(); } + virtual void AppendToText(RegExpText* text) { + MOZ_ASSUME_UNREACHABLE("Bad call"); + } +#define MAKE_ASTYPE(Name) \ + virtual RegExp##Name* As##Name(); \ + virtual bool Is##Name(); + FOR_EACH_REG_EXP_TREE_TYPE(MAKE_ASTYPE) +#undef MAKE_ASTYPE +}; + +typedef Vector > RegExpTreeVector; + +class RegExpDisjunction : public RegExpTree +{ + public: + explicit RegExpDisjunction(RegExpTreeVector *alternatives); + virtual void* Accept(RegExpVisitor* visitor, void* data); + virtual RegExpNode* ToNode(RegExpCompiler* compiler, + RegExpNode* on_success); + virtual RegExpDisjunction* AsDisjunction(); + virtual Interval CaptureRegisters(); + virtual bool IsDisjunction(); + virtual bool IsAnchoredAtStart(); + virtual bool IsAnchoredAtEnd(); + virtual int min_match() { return min_match_; } + virtual int max_match() { return max_match_; } + + const RegExpTreeVector &alternatives() { return *alternatives_; } + + private: + RegExpTreeVector *alternatives_; + int min_match_; + int max_match_; +}; + +class RegExpAlternative : public RegExpTree +{ + public: + explicit RegExpAlternative(RegExpTreeVector *nodes); + virtual void* Accept(RegExpVisitor* visitor, void* data); + virtual RegExpNode* ToNode(RegExpCompiler* compiler, + RegExpNode* on_success); + virtual RegExpAlternative* AsAlternative(); + virtual Interval CaptureRegisters(); + virtual bool IsAlternative(); + virtual bool IsAnchoredAtStart(); + virtual bool IsAnchoredAtEnd(); + virtual int min_match() { return min_match_; } + virtual int max_match() { return max_match_; } + + const RegExpTreeVector &nodes() { return *nodes_; } + + private: + RegExpTreeVector *nodes_; + int min_match_; + int max_match_; +}; + +class RegExpAssertion : public RegExpTree { + public: + enum AssertionType { + START_OF_LINE, + START_OF_INPUT, + END_OF_LINE, + END_OF_INPUT, + BOUNDARY, + NON_BOUNDARY + }; + explicit RegExpAssertion(AssertionType type) : assertion_type_(type) { } + virtual void* Accept(RegExpVisitor* visitor, void* data); + virtual RegExpNode* ToNode(RegExpCompiler* compiler, + RegExpNode* on_success); + virtual RegExpAssertion* AsAssertion(); + virtual bool IsAssertion(); + virtual bool IsAnchoredAtStart(); + virtual bool IsAnchoredAtEnd(); + virtual int min_match() { return 0; } + virtual int max_match() { return 0; } + AssertionType assertion_type() { return assertion_type_; } + private: + AssertionType assertion_type_; +}; + +class CharacterSet +{ + public: + explicit CharacterSet(jschar standard_set_type) + : ranges_(nullptr), + standard_set_type_(standard_set_type) + {} + explicit CharacterSet(CharacterRangeVector *ranges) + : ranges_(ranges), + standard_set_type_(0) + {} + + CharacterRangeVector &ranges(LifoAlloc *alloc); + jschar standard_set_type() { return standard_set_type_; } + void set_standard_set_type(jschar special_set_type) { + standard_set_type_ = special_set_type; + } + bool is_standard() { return standard_set_type_ != 0; } + void Canonicalize(); + + private: + CharacterRangeVector *ranges_; + + // If non-zero, the value represents a standard set (e.g., all whitespace + // characters) without having to expand the ranges. + jschar standard_set_type_; +}; + +class RegExpCharacterClass : public RegExpTree +{ + public: + RegExpCharacterClass(CharacterRangeVector *ranges, bool is_negated) + : set_(ranges), + is_negated_(is_negated) + {} + + explicit RegExpCharacterClass(jschar type) + : set_(type), + is_negated_(false) + {} + + virtual void* Accept(RegExpVisitor* visitor, void* data); + virtual RegExpNode* ToNode(RegExpCompiler* compiler, + RegExpNode* on_success); + virtual RegExpCharacterClass* AsCharacterClass(); + virtual bool IsCharacterClass(); + virtual bool IsTextElement() { return true; } + virtual int min_match() { return 1; } + virtual int max_match() { return 1; } + virtual void AppendToText(RegExpText* text); + + CharacterSet character_set() { return set_; } + + // TODO(lrn): Remove need for complex version if is_standard that + // recognizes a mangled standard set and just do { return set_.is_special(); } + bool is_standard(LifoAlloc *alloc); + + // Returns a value representing the standard character set if is_standard() + // returns true. + // Currently used values are: + // s : unicode whitespace + // S : unicode non-whitespace + // w : ASCII word character (digit, letter, underscore) + // W : non-ASCII word character + // d : ASCII digit + // D : non-ASCII digit + // . : non-unicode non-newline + // * : All characters + jschar standard_type() { return set_.standard_set_type(); } + + CharacterRangeVector &ranges(LifoAlloc *alloc) { return set_.ranges(alloc); } + bool is_negated() { return is_negated_; } + + private: + CharacterSet set_; + bool is_negated_; +}; + +typedef Vector > CharacterVector; + +class RegExpAtom : public RegExpTree +{ + public: + explicit RegExpAtom(CharacterVector *data) + : data_(data) + {} + + virtual void* Accept(RegExpVisitor* visitor, void* data); + virtual RegExpNode* ToNode(RegExpCompiler* compiler, + RegExpNode* on_success); + virtual RegExpAtom* AsAtom(); + virtual bool IsAtom(); + virtual bool IsTextElement() { return true; } + virtual int min_match() { return data_->length(); } + virtual int max_match() { return data_->length(); } + virtual void AppendToText(RegExpText* text); + + const CharacterVector &data() { return *data_; } + int length() { return data_->length(); } + + private: + CharacterVector *data_; +}; + +class RegExpText : public RegExpTree +{ + public: + explicit RegExpText(LifoAlloc *alloc) + : elements_(*alloc), length_(0) + {} + + virtual void* Accept(RegExpVisitor* visitor, void* data); + virtual RegExpNode* ToNode(RegExpCompiler* compiler, + RegExpNode* on_success); + virtual RegExpText* AsText(); + virtual bool IsText(); + virtual bool IsTextElement() { return true; } + virtual int min_match() { return length_; } + virtual int max_match() { return length_; } + virtual void AppendToText(RegExpText* text); + + void AddElement(TextElement elm) { + elements_.append(elm); + length_ += elm.length(); + } + const TextElementVector &elements() { return elements_; } + + private: + TextElementVector elements_; + int length_; +}; + +class RegExpQuantifier : public RegExpTree +{ + public: + enum QuantifierType { GREEDY, NON_GREEDY, POSSESSIVE }; + RegExpQuantifier(int min, int max, QuantifierType type, RegExpTree* body) + : body_(body), + min_(min), + max_(max), + min_match_(min * body->min_match()), + quantifier_type_(type) + { + if (max > 0 && body->max_match() > kInfinity / max) { + max_match_ = kInfinity; + } else { + max_match_ = max * body->max_match(); + } + } + + virtual void* Accept(RegExpVisitor* visitor, void* data); + virtual RegExpNode* ToNode(RegExpCompiler* compiler, + RegExpNode* on_success); + static RegExpNode* ToNode(int min, + int max, + bool is_greedy, + RegExpTree* body, + RegExpCompiler* compiler, + RegExpNode* on_success, + bool not_at_start = false); + virtual RegExpQuantifier* AsQuantifier(); + virtual Interval CaptureRegisters(); + virtual bool IsQuantifier(); + virtual int min_match() { return min_match_; } + virtual int max_match() { return max_match_; } + int min() { return min_; } + int max() { return max_; } + bool is_possessive() { return quantifier_type_ == POSSESSIVE; } + bool is_non_greedy() { return quantifier_type_ == NON_GREEDY; } + bool is_greedy() { return quantifier_type_ == GREEDY; } + RegExpTree* body() { return body_; } + + private: + RegExpTree* body_; + int min_; + int max_; + int min_match_; + int max_match_; + QuantifierType quantifier_type_; +}; + +class RegExpCapture : public RegExpTree +{ + public: + explicit RegExpCapture(RegExpTree* body, int index) + : body_(body), index_(index) + {} + + virtual void* Accept(RegExpVisitor* visitor, void* data); + virtual RegExpNode* ToNode(RegExpCompiler* compiler, + RegExpNode* on_success); + static RegExpNode* ToNode(RegExpTree* body, + int index, + RegExpCompiler* compiler, + RegExpNode* on_success); + virtual RegExpCapture* AsCapture(); + virtual bool IsAnchoredAtStart(); + virtual bool IsAnchoredAtEnd(); + virtual Interval CaptureRegisters(); + virtual bool IsCapture(); + virtual int min_match() { return body_->min_match(); } + virtual int max_match() { return body_->max_match(); } + RegExpTree* body() { return body_; } + int index() { return index_; } + static int StartRegister(int index) { return index * 2; } + static int EndRegister(int index) { return index * 2 + 1; } + + private: + RegExpTree* body_; + int index_; +}; + +class RegExpLookahead : public RegExpTree +{ + public: + RegExpLookahead(RegExpTree* body, + bool is_positive, + int capture_count, + int capture_from) + : body_(body), + is_positive_(is_positive), + capture_count_(capture_count), + capture_from_(capture_from) + {} + + virtual void* Accept(RegExpVisitor* visitor, void* data); + virtual RegExpNode* ToNode(RegExpCompiler* compiler, + RegExpNode* on_success); + virtual RegExpLookahead* AsLookahead(); + virtual Interval CaptureRegisters(); + virtual bool IsLookahead(); + virtual bool IsAnchoredAtStart(); + virtual int min_match() { return 0; } + virtual int max_match() { return 0; } + RegExpTree* body() { return body_; } + bool is_positive() { return is_positive_; } + int capture_count() { return capture_count_; } + int capture_from() { return capture_from_; } + + private: + RegExpTree* body_; + bool is_positive_; + int capture_count_; + int capture_from_; +}; + +typedef Vector > RegExpCaptureVector; + +class RegExpBackReference : public RegExpTree +{ + public: + explicit RegExpBackReference(RegExpCapture* capture) + : capture_(capture) + {} + + virtual void* Accept(RegExpVisitor* visitor, void* data); + virtual RegExpNode* ToNode(RegExpCompiler* compiler, + RegExpNode* on_success); + virtual RegExpBackReference* AsBackReference(); + virtual bool IsBackReference(); + virtual int min_match() { return 0; } + virtual int max_match() { return capture_->max_match(); } + int index() { return capture_->index(); } + RegExpCapture* capture() { return capture_; } + private: + RegExpCapture* capture_; +}; + +class RegExpEmpty : public RegExpTree +{ + public: + RegExpEmpty() + {} + + virtual void* Accept(RegExpVisitor* visitor, void* data); + virtual RegExpNode* ToNode(RegExpCompiler* compiler, + RegExpNode* on_success); + virtual RegExpEmpty* AsEmpty(); + virtual bool IsEmpty(); + virtual int min_match() { return 0; } + virtual int max_match() { return 0; } + static RegExpEmpty* GetInstance() { + static RegExpEmpty instance; + return &instance; + } +}; + +} } // namespace js::irregexp + +#endif // V8_REGEXP_AST_H_ diff --git a/js/src/irregexp/RegExpBytecode.h b/js/src/irregexp/RegExpBytecode.h new file mode 100644 index 00000000000..da035c7b52d --- /dev/null +++ b/js/src/irregexp/RegExpBytecode.h @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: */ + +// Copyright 2011 the V8 project authors. 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. + +#ifndef V8_BYTECODES_IRREGEXP_H_ +#define V8_BYTECODES_IRREGEXP_H_ + +namespace js { +namespace irregexp { + +const int BYTECODE_MASK = 0xff; + +// The first argument is packed in with the byte code in one word, so it +// has 24 bits, but it can be positive and negative so only use 23 bits for +// positive values. +const unsigned int MAX_FIRST_ARG = 0x7fffffu; +const int BYTECODE_SHIFT = 8; + +#define BYTECODE_ITERATOR(V) \ +V(BREAK, 0, 4) /* bc8 */ \ +V(PUSH_CP, 1, 4) /* bc8 pad24 */ \ +V(PUSH_BT, 2, 8) /* bc8 pad24 offset32 */ \ +V(PUSH_REGISTER, 3, 4) /* bc8 reg_idx24 */ \ +V(SET_REGISTER_TO_CP, 4, 8) /* bc8 reg_idx24 offset32 */ \ +V(SET_CP_TO_REGISTER, 5, 4) /* bc8 reg_idx24 */ \ +V(SET_REGISTER_TO_SP, 6, 4) /* bc8 reg_idx24 */ \ +V(SET_SP_TO_REGISTER, 7, 4) /* bc8 reg_idx24 */ \ +V(SET_REGISTER, 8, 8) /* bc8 reg_idx24 value32 */ \ +V(ADVANCE_REGISTER, 9, 8) /* bc8 reg_idx24 value32 */ \ +V(POP_CP, 10, 4) /* bc8 pad24 */ \ +V(POP_BT, 11, 4) /* bc8 pad24 */ \ +V(POP_REGISTER, 12, 4) /* bc8 reg_idx24 */ \ +V(FAIL, 13, 4) /* bc8 pad24 */ \ +V(SUCCEED, 14, 4) /* bc8 pad24 */ \ +V(ADVANCE_CP, 15, 4) /* bc8 offset24 */ \ +V(GOTO, 16, 8) /* bc8 pad24 addr32 */ \ +V(LOAD_CURRENT_CHAR, 17, 8) /* bc8 offset24 addr32 */ \ +V(LOAD_CURRENT_CHAR_UNCHECKED, 18, 4) /* bc8 offset24 */ \ +V(LOAD_2_CURRENT_CHARS, 19, 8) /* bc8 offset24 addr32 */ \ +V(LOAD_2_CURRENT_CHARS_UNCHECKED, 20, 4) /* bc8 offset24 */ \ +V(LOAD_4_CURRENT_CHARS, 21, 8) /* bc8 offset24 addr32 */ \ +V(LOAD_4_CURRENT_CHARS_UNCHECKED, 22, 4) /* bc8 offset24 */ \ +V(CHECK_4_CHARS, 23, 12) /* bc8 pad24 uint32 addr32 */ \ +V(CHECK_CHAR, 24, 8) /* bc8 pad8 uint16 addr32 */ \ +V(CHECK_NOT_4_CHARS, 25, 12) /* bc8 pad24 uint32 addr32 */ \ +V(CHECK_NOT_CHAR, 26, 8) /* bc8 pad8 uint16 addr32 */ \ +V(AND_CHECK_4_CHARS, 27, 16) /* bc8 pad24 uint32 uint32 addr32 */ \ +V(AND_CHECK_CHAR, 28, 12) /* bc8 pad8 uint16 uint32 addr32 */ \ +V(AND_CHECK_NOT_4_CHARS, 29, 16) /* bc8 pad24 uint32 uint32 addr32 */ \ +V(AND_CHECK_NOT_CHAR, 30, 12) /* bc8 pad8 uint16 uint32 addr32 */ \ +V(MINUS_AND_CHECK_NOT_CHAR, 31, 12) /* bc8 pad8 uc16 uc16 uc16 addr32 */ \ +V(CHECK_CHAR_IN_RANGE, 32, 12) /* bc8 pad24 uc16 uc16 addr32 */ \ +V(CHECK_CHAR_NOT_IN_RANGE, 33, 12) /* bc8 pad24 uc16 uc16 addr32 */ \ +V(CHECK_BIT_IN_TABLE, 34, 24) /* bc8 pad24 addr32 bits128 */ \ +V(CHECK_LT, 35, 8) /* bc8 pad8 uc16 addr32 */ \ +V(CHECK_GT, 36, 8) /* bc8 pad8 uc16 addr32 */ \ +V(CHECK_NOT_BACK_REF, 37, 8) /* bc8 reg_idx24 addr32 */ \ +V(CHECK_NOT_BACK_REF_NO_CASE, 38, 8) /* bc8 reg_idx24 addr32 */ \ +V(CHECK_NOT_REGS_EQUAL, 39, 12) /* bc8 regidx24 reg_idx32 addr32 */ \ +V(CHECK_REGISTER_LT, 40, 12) /* bc8 reg_idx24 value32 addr32 */ \ +V(CHECK_REGISTER_GE, 41, 12) /* bc8 reg_idx24 value32 addr32 */ \ +V(CHECK_REGISTER_EQ_POS, 42, 8) /* bc8 reg_idx24 addr32 */ \ +V(CHECK_AT_START, 43, 8) /* bc8 pad24 addr32 */ \ +V(CHECK_NOT_AT_START, 44, 8) /* bc8 pad24 addr32 */ \ +V(CHECK_GREEDY, 45, 8) /* bc8 pad24 addr32 */ \ +V(ADVANCE_CP_AND_GOTO, 46, 8) /* bc8 offset24 addr32 */ \ +V(SET_CURRENT_POSITION_FROM_END, 47, 4) /* bc8 idx24 */ + +#define DECLARE_BYTECODES(name, code, length) \ + static const int BC_##name = code; +BYTECODE_ITERATOR(DECLARE_BYTECODES) +#undef DECLARE_BYTECODES + +#define DECLARE_BYTECODE_LENGTH(name, code, length) \ + static const int BC_##name##_LENGTH = length; +BYTECODE_ITERATOR(DECLARE_BYTECODE_LENGTH) +#undef DECLARE_BYTECODE_LENGTH + +} } // namespace js::irregexp + +#endif // V8_BYTECODES_IRREGEXP_H_ diff --git a/js/src/irregexp/RegExpEngine.cpp b/js/src/irregexp/RegExpEngine.cpp new file mode 100644 index 00000000000..74fe9a60ce2 --- /dev/null +++ b/js/src/irregexp/RegExpEngine.cpp @@ -0,0 +1,4766 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: */ + +// Copyright 2012 the V8 project authors. 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. + +#include "irregexp/RegExpEngine.h" + +#include "irregexp/NativeRegExpMacroAssembler.h" +#include "irregexp/RegExpMacroAssembler.h" +#include "jit/JitCommon.h" + +using namespace js; +using namespace js::irregexp; + +using mozilla::ArrayLength; +using mozilla::DebugOnly; +using mozilla::Maybe; + +#define DEFINE_ACCEPT(Type) \ + void Type##Node::Accept(NodeVisitor* visitor) { \ + visitor->Visit##Type(this); \ + } +FOR_EACH_NODE_TYPE(DEFINE_ACCEPT) +#undef DEFINE_ACCEPT + +void LoopChoiceNode::Accept(NodeVisitor* visitor) { + visitor->VisitLoopChoice(this); +} + +static const int kMaxLookaheadForBoyerMoore = 8; + +RegExpNode::RegExpNode(LifoAlloc *alloc) + : replacement_(nullptr), trace_count_(0), alloc_(alloc) +{ + bm_info_[0] = bm_info_[1] = nullptr; +} + +// ------------------------------------------------------------------- +// CharacterRange + +// The '2' variant has inclusive from and exclusive to. +// This covers \s as defined in ECMA-262 5.1, 15.10.2.12, +// which include WhiteSpace (7.2) or LineTerminator (7.3) values. +static const int kSpaceRanges[] = { '\t', '\r' + 1, ' ', ' ' + 1, + 0x00A0, 0x00A1, 0x1680, 0x1681, 0x180E, 0x180F, 0x2000, 0x200B, + 0x2028, 0x202A, 0x202F, 0x2030, 0x205F, 0x2060, 0x3000, 0x3001, + 0xFEFF, 0xFF00, 0x10000 }; +static const int kSpaceRangeCount = ArrayLength(kSpaceRanges); + +static const int kWordRanges[] = { + '0', '9' + 1, 'A', 'Z' + 1, '_', '_' + 1, 'a', 'z' + 1, 0x10000 }; +static const int kWordRangeCount = ArrayLength(kWordRanges); +static const int kDigitRanges[] = { '0', '9' + 1, 0x10000 }; +static const int kDigitRangeCount = ArrayLength(kDigitRanges); +static const int kSurrogateRanges[] = { 0xd800, 0xe000, 0x10000 }; +static const int kSurrogateRangeCount = ArrayLength(kSurrogateRanges); +static const int kLineTerminatorRanges[] = { 0x000A, 0x000B, 0x000D, 0x000E, + 0x2028, 0x202A, 0x10000 }; +static const int kLineTerminatorRangeCount = ArrayLength(kLineTerminatorRanges); +static const unsigned kMaxOneByteCharCode = 0xff; +static const int kMaxUtf16CodeUnit = 0xffff; + +static jschar +MaximumCharacter(bool ascii) +{ + return ascii ? kMaxOneByteCharCode : kMaxUtf16CodeUnit; +} + +static void +AddClass(const int* elmv, int elmc, + CharacterRangeVector *ranges) +{ + elmc--; + JS_ASSERT(elmv[elmc] == 0x10000); + for (int i = 0; i < elmc; i += 2) { + JS_ASSERT(elmv[i] < elmv[i + 1]); + ranges->append(CharacterRange(elmv[i], elmv[i + 1] - 1)); + } +} + +static void +AddClassNegated(const int *elmv, + int elmc, + CharacterRangeVector *ranges) +{ + elmc--; + JS_ASSERT(elmv[elmc] == 0x10000); + JS_ASSERT(elmv[0] != 0x0000); + JS_ASSERT(elmv[elmc-1] != kMaxUtf16CodeUnit); + jschar last = 0x0000; + for (int i = 0; i < elmc; i += 2) { + JS_ASSERT(last <= elmv[i] - 1); + JS_ASSERT(elmv[i] < elmv[i + 1]); + ranges->append(CharacterRange(last, elmv[i] - 1)); + last = elmv[i + 1]; + } + ranges->append(CharacterRange(last, kMaxUtf16CodeUnit)); +} + +void +CharacterRange::AddClassEscape(LifoAlloc *alloc, jschar type, + CharacterRangeVector *ranges) +{ + switch (type) { + case 's': + AddClass(kSpaceRanges, kSpaceRangeCount, ranges); + break; + case 'S': + AddClassNegated(kSpaceRanges, kSpaceRangeCount, ranges); + break; + case 'w': + AddClass(kWordRanges, kWordRangeCount, ranges); + break; + case 'W': + AddClassNegated(kWordRanges, kWordRangeCount, ranges); + break; + case 'd': + AddClass(kDigitRanges, kDigitRangeCount, ranges); + break; + case 'D': + AddClassNegated(kDigitRanges, kDigitRangeCount, ranges); + break; + case '.': + AddClassNegated(kLineTerminatorRanges, kLineTerminatorRangeCount, ranges); + break; + // This is not a character range as defined by the spec but a + // convenient shorthand for a character class that matches any + // character. + case '*': + ranges->append(CharacterRange::Everything()); + break; + // This is the set of characters matched by the $ and ^ symbols + // in multiline mode. + case 'n': + AddClass(kLineTerminatorRanges, kLineTerminatorRangeCount, ranges); + break; + default: + MOZ_ASSUME_UNREACHABLE("Bad character class escape"); + } +} + +// We need to check for the following characters: 0x39c 0x3bc 0x178. +static inline bool +RangeContainsLatin1Equivalents(CharacterRange range) +{ + // TODO(dcarney): this could be a lot more efficient. + return range.Contains(0x39c) || range.Contains(0x3bc) || range.Contains(0x178); +} + +static bool +RangesContainLatin1Equivalents(const CharacterRangeVector &ranges) +{ + for (size_t i = 0; i < ranges.length(); i++) { + // TODO(dcarney): this could be a lot more efficient. + if (RangeContainsLatin1Equivalents(ranges[i])) + return true; + } + return false; +} + +static const size_t kEcma262UnCanonicalizeMaxWidth = 4; + +// Returns the number of characters in the equivalence class, omitting those +// that cannot occur in the source string because it is ASCII. +static int +GetCaseIndependentLetters(jschar character, + bool ascii_subject, + jschar *letters) +{ + JS_ASSERT(!ascii_subject); + + jschar lower = unicode::ToLowerCase(character); + jschar upper = unicode::ToUpperCase(character); + + letters[0] = character; + + if (lower != character) { + letters[1] = lower; + if (upper != character && upper != lower) { + letters[2] = upper; + return 3; + } + return 2; + } + if (upper != character) { + letters[1] = upper; + return 2; + } + return 1; +} + +void +CharacterRange::AddCaseEquivalents(bool is_ascii, CharacterRangeVector *ranges) +{ + jschar bottom = from(); + jschar top = to(); + + if (is_ascii && !RangeContainsLatin1Equivalents(*this)) { + if (bottom > kMaxOneByteCharCode) + return; + if (top > kMaxOneByteCharCode) + top = kMaxOneByteCharCode; + } + + for (jschar c = bottom;; c++) { + jschar chars[kEcma262UnCanonicalizeMaxWidth]; + size_t length = GetCaseIndependentLetters(c, is_ascii, chars); + + for (size_t i = 0; i < length; i++) { + jschar other = chars[i]; + if (other == c) + continue; + + // Try to combine with an existing range. + bool found = false; + for (size_t i = 0; i < ranges->length(); i++) { + CharacterRange &range = (*ranges)[i]; + if (range.Contains(other)) { + found = true; + break; + } else if (other == range.from() - 1) { + range.set_from(other); + found = true; + break; + } else if (other == range.to() + 1) { + range.set_to(other); + found = true; + break; + } + } + + if (!found) + ranges->append(CharacterRange::Singleton(other)); + } + + if (c == top) + break; + } +} + +static bool +CompareInverseRanges(const CharacterRangeVector &ranges, const int *special_class, size_t length) +{ + length--; // Remove final 0x10000. + JS_ASSERT(special_class[length] == 0x10000); + JS_ASSERT(ranges.length() != 0); + JS_ASSERT(length != 0); + JS_ASSERT(special_class[0] != 0); + if (ranges.length() != (length >> 1) + 1) + return false; + CharacterRange range = ranges[0]; + if (range.from() != 0) + return false; + for (size_t i = 0; i < length; i += 2) { + if (special_class[i] != (range.to() + 1)) + return false; + range = ranges[(i >> 1) + 1]; + if (special_class[i+1] != range.from()) + return false; + } + if (range.to() != 0xffff) + return false; + return true; +} + +static bool +CompareRanges(const CharacterRangeVector &ranges, const int *special_class, size_t length) +{ + length--; // Remove final 0x10000. + JS_ASSERT(special_class[length] == 0x10000); + if (ranges.length() * 2 != length) + return false; + for (size_t i = 0; i < length; i += 2) { + CharacterRange range = ranges[i >> 1]; + if (range.from() != special_class[i] || range.to() != special_class[i + 1] - 1) + return false; + } + return true; +} + +bool +RegExpCharacterClass::is_standard(LifoAlloc *alloc) +{ + // TODO(lrn): Remove need for this function, by not throwing away information + // along the way. + if (is_negated_) + return false; + if (set_.is_standard()) + return true; + if (CompareRanges(set_.ranges(alloc), kSpaceRanges, kSpaceRangeCount)) { + set_.set_standard_set_type('s'); + return true; + } + if (CompareInverseRanges(set_.ranges(alloc), kSpaceRanges, kSpaceRangeCount)) { + set_.set_standard_set_type('S'); + return true; + } + if (CompareInverseRanges(set_.ranges(alloc), + kLineTerminatorRanges, + kLineTerminatorRangeCount)) { + set_.set_standard_set_type('.'); + return true; + } + if (CompareRanges(set_.ranges(alloc), + kLineTerminatorRanges, + kLineTerminatorRangeCount)) { + set_.set_standard_set_type('n'); + return true; + } + if (CompareRanges(set_.ranges(alloc), kWordRanges, kWordRangeCount)) { + set_.set_standard_set_type('w'); + return true; + } + if (CompareInverseRanges(set_.ranges(alloc), kWordRanges, kWordRangeCount)) { + set_.set_standard_set_type('W'); + return true; + } + return false; +} + +bool +CharacterRange::IsCanonical(const CharacterRangeVector &ranges) +{ + int n = ranges.length(); + if (n <= 1) + return true; + + int max = ranges[0].to(); + for (int i = 1; i < n; i++) { + CharacterRange next_range = ranges[i]; + if (next_range.from() <= max + 1) + return false; + max = next_range.to(); + } + return true; +} + +// Move a number of elements in a zonelist to another position +// in the same list. Handles overlapping source and target areas. +static +void MoveRanges(CharacterRangeVector &list, int from, int to, int count) +{ + // Ranges are potentially overlapping. + if (from < to) { + for (int i = count - 1; i >= 0; i--) + list[to + i] = list[from + i]; + } else { + for (int i = 0; i < count; i++) + list[to + i] = list[from + i]; + } +} + +static int +InsertRangeInCanonicalList(CharacterRangeVector &list, + int count, + CharacterRange insert) +{ + // Inserts a range into list[0..count[, which must be sorted + // by from value and non-overlapping and non-adjacent, using at most + // list[0..count] for the result. Returns the number of resulting + // canonicalized ranges. Inserting a range may collapse existing ranges into + // fewer ranges, so the return value can be anything in the range 1..count+1. + jschar from = insert.from(); + jschar to = insert.to(); + int start_pos = 0; + int end_pos = count; + for (int i = count - 1; i >= 0; i--) { + CharacterRange current = list[i]; + if (current.from() > to + 1) { + end_pos = i; + } else if (current.to() + 1 < from) { + start_pos = i + 1; + break; + } + } + + // Inserted range overlaps, or is adjacent to, ranges at positions + // [start_pos..end_pos[. Ranges before start_pos or at or after end_pos are + // not affected by the insertion. + // If start_pos == end_pos, the range must be inserted before start_pos. + // if start_pos < end_pos, the entire range from start_pos to end_pos + // must be merged with the insert range. + + if (start_pos == end_pos) { + // Insert between existing ranges at position start_pos. + if (start_pos < count) { + MoveRanges(list, start_pos, start_pos + 1, count - start_pos); + } + list[start_pos] = insert; + return count + 1; + } + if (start_pos + 1 == end_pos) { + // Replace single existing range at position start_pos. + CharacterRange to_replace = list[start_pos]; + int new_from = Min(to_replace.from(), from); + int new_to = Max(to_replace.to(), to); + list[start_pos] = CharacterRange(new_from, new_to); + return count; + } + // Replace a number of existing ranges from start_pos to end_pos - 1. + // Move the remaining ranges down. + + int new_from = Min(list[start_pos].from(), from); + int new_to = Max(list[end_pos - 1].to(), to); + if (end_pos < count) { + MoveRanges(list, end_pos, start_pos + 1, count - end_pos); + } + list[start_pos] = CharacterRange(new_from, new_to); + return count - (end_pos - start_pos) + 1; +} + +void +CharacterRange::Canonicalize(CharacterRangeVector &character_ranges) +{ + if (character_ranges.length() <= 1) return; + // Check whether ranges are already canonical (increasing, non-overlapping, + // non-adjacent). + int n = character_ranges.length(); + int max = character_ranges[0].to(); + int i = 1; + while (i < n) { + CharacterRange current = character_ranges[i]; + if (current.from() <= max + 1) { + break; + } + max = current.to(); + i++; + } + // Canonical until the i'th range. If that's all of them, we are done. + if (i == n) return; + + // The ranges at index i and forward are not canonicalized. Make them so by + // doing the equivalent of insertion sort (inserting each into the previous + // list, in order). + // Notice that inserting a range can reduce the number of ranges in the + // result due to combining of adjacent and overlapping ranges. + int read = i; // Range to insert. + size_t num_canonical = i; // Length of canonicalized part of list. + do { + num_canonical = InsertRangeInCanonicalList(character_ranges, + num_canonical, + character_ranges[read]); + read++; + } while (read < n); + + while (character_ranges.length() > num_canonical) + character_ranges.popBack(); + + JS_ASSERT(CharacterRange::IsCanonical(character_ranges)); +} + +// ------------------------------------------------------------------- +// SeqRegExpNode + +class VisitMarker +{ + public: + explicit VisitMarker(NodeInfo* info) + : info_(info) + { + JS_ASSERT(!info->visited); + info->visited = true; + } + ~VisitMarker() { + info_->visited = false; + } + private: + NodeInfo* info_; +}; + +RegExpNode * +SeqRegExpNode::FilterASCII(int depth, bool ignore_case) +{ + if (info()->replacement_calculated) + return replacement(); + + if (depth < 0) + return this; + + JS_ASSERT(!info()->visited); + VisitMarker marker(info()); + return FilterSuccessor(depth - 1, ignore_case); +} + +RegExpNode * +SeqRegExpNode::FilterSuccessor(int depth, bool ignore_case) +{ + RegExpNode* next = on_success_->FilterASCII(depth - 1, ignore_case); + if (next == nullptr) + return set_replacement(nullptr); + + on_success_ = next; + return set_replacement(this); +} + +// ------------------------------------------------------------------- +// ActionNode + +int +ActionNode::EatsAtLeast(int still_to_find, int budget, bool not_at_start) +{ + if (budget <= 0) + return 0; + if (action_type_ == POSITIVE_SUBMATCH_SUCCESS) + return 0; // Rewinds input! + return on_success()->EatsAtLeast(still_to_find, + budget - 1, + not_at_start); +} + +void +ActionNode::FillInBMInfo(int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start) +{ + if (action_type_ == BEGIN_SUBMATCH) + bm->SetRest(offset); + else if (action_type_ != POSITIVE_SUBMATCH_SUCCESS) + on_success()->FillInBMInfo(offset, budget - 1, bm, not_at_start); + SaveBMInfo(bm, not_at_start, offset); +} + +/* static */ ActionNode * +ActionNode::SetRegister(int reg, + int val, + RegExpNode *on_success) +{ + ActionNode *result = on_success->alloc()->newInfallible(SET_REGISTER, on_success); + result->data_.u_store_register.reg = reg; + result->data_.u_store_register.value = val; + return result; +} + +/* static */ ActionNode * +ActionNode::IncrementRegister(int reg, RegExpNode *on_success) +{ + ActionNode *result = on_success->alloc()->newInfallible(INCREMENT_REGISTER, on_success); + result->data_.u_increment_register.reg = reg; + return result; +} + +/* static */ ActionNode * +ActionNode::StorePosition(int reg, bool is_capture, RegExpNode *on_success) +{ + ActionNode *result = on_success->alloc()->newInfallible(STORE_POSITION, on_success); + result->data_.u_position_register.reg = reg; + result->data_.u_position_register.is_capture = is_capture; + return result; +} + +/* static */ ActionNode * +ActionNode::ClearCaptures(Interval range, RegExpNode *on_success) +{ + ActionNode *result = on_success->alloc()->newInfallible(CLEAR_CAPTURES, on_success); + result->data_.u_clear_captures.range_from = range.from(); + result->data_.u_clear_captures.range_to = range.to(); + return result; +} + +/* static */ ActionNode * +ActionNode::BeginSubmatch(int stack_pointer_reg, int position_reg, RegExpNode *on_success) +{ + ActionNode *result = on_success->alloc()->newInfallible(BEGIN_SUBMATCH, on_success); + result->data_.u_submatch.stack_pointer_register = stack_pointer_reg; + result->data_.u_submatch.current_position_register = position_reg; + return result; +} + +/* static */ ActionNode * +ActionNode::PositiveSubmatchSuccess(int stack_pointer_reg, + int restore_reg, + int clear_capture_count, + int clear_capture_from, + RegExpNode *on_success) +{ + ActionNode *result = on_success->alloc()->newInfallible(POSITIVE_SUBMATCH_SUCCESS, on_success); + result->data_.u_submatch.stack_pointer_register = stack_pointer_reg; + result->data_.u_submatch.current_position_register = restore_reg; + result->data_.u_submatch.clear_register_count = clear_capture_count; + result->data_.u_submatch.clear_register_from = clear_capture_from; + return result; +} + +/* static */ ActionNode * +ActionNode::EmptyMatchCheck(int start_register, + int repetition_register, + int repetition_limit, + RegExpNode *on_success) +{ + ActionNode *result = on_success->alloc()->newInfallible(EMPTY_MATCH_CHECK, on_success); + result->data_.u_empty_match_check.start_register = start_register; + result->data_.u_empty_match_check.repetition_register = repetition_register; + result->data_.u_empty_match_check.repetition_limit = repetition_limit; + return result; +} + +// ------------------------------------------------------------------- +// TextNode + +int +TextNode::EatsAtLeast(int still_to_find, int budget, bool not_at_start) +{ + int answer = Length(); + if (answer >= still_to_find) + return answer; + if (budget <= 0) + return answer; + + // We are not at start after this node so we set the last argument to 'true'. + return answer + on_success()->EatsAtLeast(still_to_find - answer, + budget - 1, + true); +} + +int +TextNode::GreedyLoopTextLength() +{ + TextElement elm = elements()[elements().length() - 1]; + return elm.cp_offset() + elm.length(); +} + +RegExpNode * +TextNode::FilterASCII(int depth, bool ignore_case) +{ + if (info()->replacement_calculated) + return replacement(); + + if (depth < 0) + return this; + + JS_ASSERT(!info()->visited); + VisitMarker marker(info()); + int element_count = elements().length(); + for (int i = 0; i < element_count; i++) { + TextElement elm = elements()[i]; + if (elm.text_type() == TextElement::ATOM) { + CharacterVector &quarks = const_cast(elm.atom()->data()); + for (size_t j = 0; j < quarks.length(); j++) { + uint16_t c = quarks[j]; + if (c <= kMaxOneByteCharCode) + continue; + if (!ignore_case) + return set_replacement(nullptr); + + // Here, we need to check for characters whose upper and lower cases + // are outside the Latin-1 range. + jschar chars[kEcma262UnCanonicalizeMaxWidth]; + size_t length = GetCaseIndependentLetters(c, true, chars); + JS_ASSERT(length <= 1); + + if (length == 0) { + // Character is outside Latin-1 completely + return set_replacement(nullptr); + } + + // Convert quark to Latin-1 in place. + quarks[j] = chars[0]; + } + } else { + JS_ASSERT(elm.text_type() == TextElement::CHAR_CLASS); + RegExpCharacterClass* cc = elm.char_class(); + + CharacterRangeVector &ranges = cc->ranges(alloc()); + if (!CharacterRange::IsCanonical(ranges)) + CharacterRange::Canonicalize(ranges); + + // Now they are in order so we only need to look at the first. + int range_count = ranges.length(); + if (cc->is_negated()) { + if (range_count != 0 && + ranges[0].from() == 0 && + ranges[0].to() >= kMaxOneByteCharCode) + { + // This will be handled in a later filter. + if (ignore_case && RangesContainLatin1Equivalents(ranges)) + continue; + return set_replacement(nullptr); + } + } else { + if (range_count == 0 || + ranges[0].from() > kMaxOneByteCharCode) + { + // This will be handled in a later filter. + if (ignore_case && RangesContainLatin1Equivalents(ranges)) + continue; + return set_replacement(nullptr); + } + } + } + } + return FilterSuccessor(depth - 1, ignore_case); +} + +void +TextNode::CalculateOffsets() +{ + int element_count = elements().length(); + + // Set up the offsets of the elements relative to the start. This is a fixed + // quantity since a TextNode can only contain fixed-width things. + int cp_offset = 0; + for (int i = 0; i < element_count; i++) { + TextElement& elm = elements()[i]; + elm.set_cp_offset(cp_offset); + cp_offset += elm.length(); + } +} + +void TextNode::MakeCaseIndependent(bool is_ascii) +{ + int element_count = elements().length(); + for (int i = 0; i < element_count; i++) { + TextElement elm = elements()[i]; + if (elm.text_type() == TextElement::CHAR_CLASS) { + RegExpCharacterClass* cc = elm.char_class(); + + // None of the standard character classes is different in the case + // independent case and it slows us down if we don't know that. + if (cc->is_standard(alloc())) + continue; + + CharacterRangeVector &ranges = cc->ranges(alloc()); + int range_count = ranges.length(); + for (int j = 0; j < range_count; j++) + ranges[j].AddCaseEquivalents(is_ascii, &ranges); + } + } +} + +// ------------------------------------------------------------------- +// AssertionNode + +int +AssertionNode::EatsAtLeast(int still_to_find, int budget, bool not_at_start) +{ + if (budget <= 0) + return 0; + + // If we know we are not at the start and we are asked "how many characters + // will you match if you succeed?" then we can answer anything since false + // implies false. So lets just return the max answer (still_to_find) since + // that won't prevent us from preloading a lot of characters for the other + // branches in the node graph. + if (assertion_type() == AT_START && not_at_start) + return still_to_find; + + return on_success()->EatsAtLeast(still_to_find, budget - 1, not_at_start); +} + +void +AssertionNode::FillInBMInfo(int offset, int budget, BoyerMooreLookahead* bm, bool not_at_start) +{ + // Match the behaviour of EatsAtLeast on this node. + if (assertion_type() == AT_START && not_at_start) + return; + + on_success()->FillInBMInfo(offset, budget - 1, bm, not_at_start); + SaveBMInfo(bm, not_at_start, offset); +} + +// ------------------------------------------------------------------- +// BackReferenceNode + +int +BackReferenceNode::EatsAtLeast(int still_to_find, int budget, bool not_at_start) +{ + if (budget <= 0) + return 0; + return on_success()->EatsAtLeast(still_to_find, budget - 1, not_at_start); +} + +void +BackReferenceNode::FillInBMInfo(int offset, int budget, BoyerMooreLookahead* bm, bool not_at_start) +{ + // Working out the set of characters that a backreference can match is too + // hard, so we just say that any character can match. + bm->SetRest(offset); + SaveBMInfo(bm, not_at_start, offset); +} + +// ------------------------------------------------------------------- +// ChoiceNode + +int +ChoiceNode::EatsAtLeastHelper(int still_to_find, + int budget, + RegExpNode* ignore_this_node, + bool not_at_start) +{ + if (budget <= 0) + return 0; + + int min = 100; + size_t choice_count = alternatives().length(); + budget = (budget - 1) / choice_count; + for (size_t i = 0; i < choice_count; i++) { + RegExpNode* node = alternatives()[i].node(); + if (node == ignore_this_node) continue; + int node_eats_at_least = + node->EatsAtLeast(still_to_find, budget, not_at_start); + if (node_eats_at_least < min) + min = node_eats_at_least; + if (min == 0) + return 0; + } + return min; +} + +int +ChoiceNode::EatsAtLeast(int still_to_find, int budget, bool not_at_start) +{ + return EatsAtLeastHelper(still_to_find, + budget, + nullptr, + not_at_start); +} + +void +ChoiceNode::GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start) +{ + not_at_start = (not_at_start || not_at_start_); + int choice_count = alternatives().length(); + JS_ASSERT(choice_count > 0); + alternatives()[0].node()->GetQuickCheckDetails(details, + compiler, + characters_filled_in, + not_at_start); + for (int i = 1; i < choice_count; i++) { + QuickCheckDetails new_details(details->characters()); + RegExpNode* node = alternatives()[i].node(); + node->GetQuickCheckDetails(&new_details, compiler, + characters_filled_in, + not_at_start); + // Here we merge the quick match details of the two branches. + details->Merge(&new_details, characters_filled_in); + } +} + +void +ChoiceNode::FillInBMInfo(int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start) +{ + const GuardedAlternativeVector &alts = alternatives(); + budget = (budget - 1) / alts.length(); + for (size_t i = 0; i < alts.length(); i++) { + const GuardedAlternative& alt = alts[i]; + if (alt.guards() != nullptr && alt.guards()->length() != 0) { + bm->SetRest(offset); // Give up trying to fill in info. + SaveBMInfo(bm, not_at_start, offset); + return; + } + alt.node()->FillInBMInfo(offset, budget, bm, not_at_start); + } + SaveBMInfo(bm, not_at_start, offset); +} + +RegExpNode* +ChoiceNode::FilterASCII(int depth, bool ignore_case) +{ + if (info()->replacement_calculated) + return replacement(); + if (depth < 0) + return this; + if (info()->visited) + return this; + VisitMarker marker(info()); + int choice_count = alternatives().length(); + + for (int i = 0; i < choice_count; i++) { + const GuardedAlternative alternative = alternatives()[i]; + if (alternative.guards() != nullptr && alternative.guards()->length() != 0) { + set_replacement(this); + return this; + } + } + + int surviving = 0; + RegExpNode* survivor = nullptr; + for (int i = 0; i < choice_count; i++) { + GuardedAlternative alternative = alternatives()[i]; + RegExpNode* replacement = + alternative.node()->FilterASCII(depth - 1, ignore_case); + JS_ASSERT(replacement != this); // No missing EMPTY_MATCH_CHECK. + if (replacement != nullptr) { + alternatives()[i].set_node(replacement); + surviving++; + survivor = replacement; + } + } + if (surviving < 2) + return set_replacement(survivor); + + set_replacement(this); + if (surviving == choice_count) + return this; + + // Only some of the nodes survived the filtering. We need to rebuild the + // alternatives list. + GuardedAlternativeVector new_alternatives(*alloc()); + new_alternatives.reserve(surviving); + for (int i = 0; i < choice_count; i++) { + RegExpNode* replacement = + alternatives()[i].node()->FilterASCII(depth - 1, ignore_case); + if (replacement != nullptr) { + alternatives()[i].set_node(replacement); + new_alternatives.append(alternatives()[i]); + } + } + + alternatives_.appendAll(new_alternatives); + return this; +} + +// ------------------------------------------------------------------- +// NegativeLookaheadChoiceNode + +int +NegativeLookaheadChoiceNode::EatsAtLeast(int still_to_find, int budget, bool not_at_start) +{ + if (budget <= 0) + return 0; + + // Alternative 0 is the negative lookahead, alternative 1 is what comes + // afterwards. + RegExpNode* node = alternatives()[1].node(); + return node->EatsAtLeast(still_to_find, budget - 1, not_at_start); +} + +void +NegativeLookaheadChoiceNode::GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int filled_in, + bool not_at_start) +{ + // Alternative 0 is the negative lookahead, alternative 1 is what comes + // afterwards. + RegExpNode* node = alternatives()[1].node(); + return node->GetQuickCheckDetails(details, compiler, filled_in, not_at_start); +} + +RegExpNode * +NegativeLookaheadChoiceNode::FilterASCII(int depth, bool ignore_case) +{ + if (info()->replacement_calculated) + return replacement(); + if (depth < 0) + return this; + if (info()->visited) + return this; + + VisitMarker marker(info()); + + // Alternative 0 is the negative lookahead, alternative 1 is what comes + // afterwards. + RegExpNode* node = alternatives()[1].node(); + RegExpNode* replacement = node->FilterASCII(depth - 1, ignore_case); + + if (replacement == nullptr) + return set_replacement(nullptr); + alternatives()[1].set_node(replacement); + + RegExpNode* neg_node = alternatives()[0].node(); + RegExpNode* neg_replacement = neg_node->FilterASCII(depth - 1, ignore_case); + + // If the negative lookahead is always going to fail then + // we don't need to check it. + if (neg_replacement == nullptr) + return set_replacement(replacement); + + alternatives()[0].set_node(neg_replacement); + return set_replacement(this); +} + +// ------------------------------------------------------------------- +// LoopChoiceNode + +void +GuardedAlternative::AddGuard(LifoAlloc *alloc, Guard *guard) +{ + if (guards_ == nullptr) + guards_ = alloc->newInfallible(*alloc); + guards_->append(guard); +} + +void +LoopChoiceNode::AddLoopAlternative(GuardedAlternative alt) +{ + JS_ASSERT(loop_node_ == nullptr); + AddAlternative(alt); + loop_node_ = alt.node(); +} + + +void +LoopChoiceNode::AddContinueAlternative(GuardedAlternative alt) +{ + JS_ASSERT(continue_node_ == nullptr); + AddAlternative(alt); + continue_node_ = alt.node(); +} + +int +LoopChoiceNode::EatsAtLeast(int still_to_find, int budget, bool not_at_start) +{ + return EatsAtLeastHelper(still_to_find, + budget - 1, + loop_node_, + not_at_start); +} + +void +LoopChoiceNode::GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start) +{ + if (body_can_be_zero_length_ || info()->visited) + return; + VisitMarker marker(info()); + return ChoiceNode::GetQuickCheckDetails(details, + compiler, + characters_filled_in, + not_at_start); +} + +void +LoopChoiceNode::FillInBMInfo(int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start) +{ + if (body_can_be_zero_length_ || budget <= 0) { + bm->SetRest(offset); + SaveBMInfo(bm, not_at_start, offset); + return; + } + ChoiceNode::FillInBMInfo(offset, budget - 1, bm, not_at_start); + SaveBMInfo(bm, not_at_start, offset); +} + +RegExpNode * +LoopChoiceNode::FilterASCII(int depth, bool ignore_case) +{ + if (info()->replacement_calculated) + return replacement(); + if (depth < 0) + return this; + if (info()->visited) + return this; + + { + VisitMarker marker(info()); + + RegExpNode* continue_replacement = + continue_node_->FilterASCII(depth - 1, ignore_case); + + // If we can't continue after the loop then there is no sense in doing the + // loop. + if (continue_replacement == nullptr) + return set_replacement(nullptr); + } + + return ChoiceNode::FilterASCII(depth - 1, ignore_case); +} + +// ------------------------------------------------------------------- +// Analysis + +void +Analysis::EnsureAnalyzed(RegExpNode* that) +{ + JS_CHECK_RECURSION(cx, fail("Stack overflow"); return); + + if (that->info()->been_analyzed || that->info()->being_analyzed) + return; + that->info()->being_analyzed = true; + that->Accept(this); + that->info()->being_analyzed = false; + that->info()->been_analyzed = true; +} + +void +Analysis::VisitEnd(EndNode* that) +{ + // nothing to do +} + +void +Analysis::VisitText(TextNode* that) +{ + if (ignore_case_) + that->MakeCaseIndependent(is_ascii_); + EnsureAnalyzed(that->on_success()); + if (!has_failed()) { + that->CalculateOffsets(); + } +} + +void +Analysis::VisitAction(ActionNode* that) +{ + RegExpNode* target = that->on_success(); + EnsureAnalyzed(target); + + if (!has_failed()) { + // If the next node is interested in what it follows then this node + // has to be interested too so it can pass the information on. + that->info()->AddFromFollowing(target->info()); + } +} + +void +Analysis::VisitChoice(ChoiceNode* that) +{ + NodeInfo* info = that->info(); + + for (size_t i = 0; i < that->alternatives().length(); i++) { + RegExpNode* node = that->alternatives()[i].node(); + EnsureAnalyzed(node); + if (has_failed()) return; + + // Anything the following nodes need to know has to be known by + // this node also, so it can pass it on. + info->AddFromFollowing(node->info()); + } +} + +void +Analysis::VisitLoopChoice(LoopChoiceNode* that) +{ + NodeInfo* info = that->info(); + for (size_t i = 0; i < that->alternatives().length(); i++) { + RegExpNode* node = that->alternatives()[i].node(); + if (node != that->loop_node()) { + EnsureAnalyzed(node); + if (has_failed()) return; + info->AddFromFollowing(node->info()); + } + } + + // Check the loop last since it may need the value of this node + // to get a correct result. + EnsureAnalyzed(that->loop_node()); + if (!has_failed()) + info->AddFromFollowing(that->loop_node()->info()); +} + +void +Analysis::VisitBackReference(BackReferenceNode* that) +{ + EnsureAnalyzed(that->on_success()); +} + +void +Analysis::VisitAssertion(AssertionNode* that) +{ + EnsureAnalyzed(that->on_success()); +} + +// ------------------------------------------------------------------- +// Implementation of the Irregexp regular expression engine. +// +// The Irregexp regular expression engine is intended to be a complete +// implementation of ECMAScript regular expressions. It generates either +// bytecodes or native code. + +// The Irregexp regexp engine is structured in three steps. +// 1) The parser generates an abstract syntax tree. See RegExpAST.cpp. +// 2) From the AST a node network is created. The nodes are all +// subclasses of RegExpNode. The nodes represent states when +// executing a regular expression. Several optimizations are +// performed on the node network. +// 3) From the nodes we generate either byte codes or native code +// that can actually execute the regular expression (perform +// the search). The code generation step is described in more +// detail below. + +// Code generation. +// +// The nodes are divided into four main categories. +// * Choice nodes +// These represent places where the regular expression can +// match in more than one way. For example on entry to an +// alternation (foo|bar) or a repetition (*, +, ? or {}). +// * Action nodes +// These represent places where some action should be +// performed. Examples include recording the current position +// in the input string to a register (in order to implement +// captures) or other actions on register for example in order +// to implement the counters needed for {} repetitions. +// * Matching nodes +// These attempt to match some element part of the input string. +// Examples of elements include character classes, plain strings +// or back references. +// * End nodes +// These are used to implement the actions required on finding +// a successful match or failing to find a match. +// +// The code generated (whether as byte codes or native code) maintains +// some state as it runs. This consists of the following elements: +// +// * The capture registers. Used for string captures. +// * Other registers. Used for counters etc. +// * The current position. +// * The stack of backtracking information. Used when a matching node +// fails to find a match and needs to try an alternative. +// +// Conceptual regular expression execution model: +// +// There is a simple conceptual model of regular expression execution +// which will be presented first. The actual code generated is a more +// efficient simulation of the simple conceptual model: +// +// * Choice nodes are implemented as follows: +// For each choice except the last { +// push current position +// push backtrack code location +// +// backtrack code location: +// pop current position +// } +// +// +// * Actions nodes are generated as follows +// +// +// push backtrack code location +// +// backtrack code location: +// +// +// +// * Matching nodes are generated as follows: +// if input string matches at current position +// update current position +// +// else +// +// +// Thus it can be seen that the current position is saved and restored +// by the choice nodes, whereas the registers are saved and restored by +// by the action nodes that manipulate them. +// +// The other interesting aspect of this model is that nodes are generated +// at the point where they are needed by a recursive call to Emit(). If +// the node has already been code generated then the Emit() call will +// generate a jump to the previously generated code instead. In order to +// limit recursion it is possible for the Emit() function to put the node +// on a work list for later generation and instead generate a jump. The +// destination of the jump is resolved later when the code is generated. +// +// Actual regular expression code generation. +// +// Code generation is actually more complicated than the above. In order +// to improve the efficiency of the generated code some optimizations are +// performed +// +// * Choice nodes have 1-character lookahead. +// A choice node looks at the following character and eliminates some of +// the choices immediately based on that character. This is not yet +// implemented. +// * Simple greedy loops store reduced backtracking information. +// A quantifier like /.*foo/m will greedily match the whole input. It will +// then need to backtrack to a point where it can match "foo". The naive +// implementation of this would push each character position onto the +// backtracking stack, then pop them off one by one. This would use space +// proportional to the length of the input string. However since the "." +// can only match in one way and always has a constant length (in this case +// of 1) it suffices to store the current position on the top of the stack +// once. Matching now becomes merely incrementing the current position and +// backtracking becomes decrementing the current position and checking the +// result against the stored current position. This is faster and saves +// space. +// * The current state is virtualized. +// This is used to defer expensive operations until it is clear that they +// are needed and to generate code for a node more than once, allowing +// specialized an efficient versions of the code to be created. This is +// explained in the section below. +// +// Execution state virtualization. +// +// Instead of emitting code, nodes that manipulate the state can record their +// manipulation in an object called the Trace. The Trace object can record a +// current position offset, an optional backtrack code location on the top of +// the virtualized backtrack stack and some register changes. When a node is +// to be emitted it can flush the Trace or update it. Flushing the Trace +// will emit code to bring the actual state into line with the virtual state. +// Avoiding flushing the state can postpone some work (e.g. updates of capture +// registers). Postponing work can save time when executing the regular +// expression since it may be found that the work never has to be done as a +// failure to match can occur. In addition it is much faster to jump to a +// known backtrack code location than it is to pop an unknown backtrack +// location from the stack and jump there. +// +// The virtual state found in the Trace affects code generation. For example +// the virtual state contains the difference between the actual current +// position and the virtual current position, and matching code needs to use +// this offset to attempt a match in the correct location of the input +// string. Therefore code generated for a non-trivial trace is specialized +// to that trace. The code generator therefore has the ability to generate +// code for each node several times. In order to limit the size of the +// generated code there is an arbitrary limit on how many specialized sets of +// code may be generated for a given node. If the limit is reached, the +// trace is flushed and a generic version of the code for a node is emitted. +// This is subsequently used for that node. The code emitted for non-generic +// trace is not recorded in the node and so it cannot currently be reused in +// the event that code generation is requested for an identical trace. + +/* static */ TextElement +TextElement::Atom(RegExpAtom* atom) +{ + return TextElement(ATOM, atom); +} + +/* static */ TextElement +TextElement::CharClass(RegExpCharacterClass* char_class) +{ + return TextElement(CHAR_CLASS, char_class); +} + +int +TextElement::length() const +{ + switch (text_type()) { + case ATOM: + return atom()->length(); + case CHAR_CLASS: + return 1; + } + MOZ_ASSUME_UNREACHABLE("Bad text type"); + return 0; +} + +class FrequencyCollator +{ + public: + FrequencyCollator() : total_samples_(0) { + for (int i = 0; i < RegExpMacroAssembler::kTableSize; i++) { + frequencies_[i] = CharacterFrequency(i); + } + } + + void CountCharacter(int character) { + int index = (character & RegExpMacroAssembler::kTableMask); + frequencies_[index].Increment(); + total_samples_++; + } + + // Does not measure in percent, but rather per-128 (the table size from the + // regexp macro assembler). + int Frequency(int in_character) { + JS_ASSERT((in_character & RegExpMacroAssembler::kTableMask) == in_character); + if (total_samples_ < 1) return 1; // Division by zero. + int freq_in_per128 = + (frequencies_[in_character].counter() * 128) / total_samples_; + return freq_in_per128; + } + + private: + class CharacterFrequency { + public: + CharacterFrequency() : counter_(0), character_(-1) { } + explicit CharacterFrequency(int character) + : counter_(0), character_(character) + {} + + void Increment() { counter_++; } + int counter() { return counter_; } + int character() { return character_; } + + private: + int counter_; + int character_; + }; + + private: + CharacterFrequency frequencies_[RegExpMacroAssembler::kTableSize]; + int total_samples_; +}; + +class irregexp::RegExpCompiler +{ + public: + RegExpCompiler(LifoAlloc *alloc, int capture_count, bool ignore_case, bool is_ascii); + + int AllocateRegister() { + if (next_register_ >= RegExpMacroAssembler::kMaxRegister) { + reg_exp_too_big_ = true; + return next_register_; + } + return next_register_++; + } + + RegExpCode Assemble(JSContext *cx, + RegExpMacroAssembler *assembler, + RegExpNode *start, + int capture_count); + + inline void AddWork(RegExpNode* node) { + if (!work_list_.append(node)) + CrashAtUnhandlableOOM("AddWork"); + } + + static const int kImplementationOffset = 0; + static const int kNumberOfRegistersOffset = 0; + static const int kCodeOffset = 1; + + RegExpMacroAssembler* macro_assembler() { return macro_assembler_; } + EndNode* accept() { return accept_; } + + static const int kMaxRecursion = 100; + inline int recursion_depth() { return recursion_depth_; } + inline void IncrementRecursionDepth() { recursion_depth_++; } + inline void DecrementRecursionDepth() { recursion_depth_--; } + + void SetRegExpTooBig() { reg_exp_too_big_ = true; } + + inline bool ignore_case() { return ignore_case_; } + inline bool ascii() { return ascii_; } + FrequencyCollator* frequency_collator() { return &frequency_collator_; } + + int current_expansion_factor() { return current_expansion_factor_; } + void set_current_expansion_factor(int value) { + current_expansion_factor_ = value; + } + + LifoAlloc *alloc() const { return alloc_; } + + static const int kNoRegister = -1; + + private: + EndNode* accept_; + int next_register_; + js::Vector work_list_; + int recursion_depth_; + RegExpMacroAssembler* macro_assembler_; + bool ignore_case_; + bool ascii_; + bool reg_exp_too_big_; + int current_expansion_factor_; + FrequencyCollator frequency_collator_; + LifoAlloc *alloc_; +}; + +class RecursionCheck +{ + public: + explicit RecursionCheck(RegExpCompiler* compiler) : compiler_(compiler) { + compiler->IncrementRecursionDepth(); + } + ~RecursionCheck() { compiler_->DecrementRecursionDepth(); } + + private: + RegExpCompiler* compiler_; +}; + +// Attempts to compile the regexp using an Irregexp code generator. Returns +// a fixed array or a null handle depending on whether it succeeded. +RegExpCompiler::RegExpCompiler(LifoAlloc *alloc, int capture_count, bool ignore_case, bool ascii) + : next_register_(2 * (capture_count + 1)), + recursion_depth_(0), + ignore_case_(ignore_case), + ascii_(ascii), + reg_exp_too_big_(false), + current_expansion_factor_(1), + frequency_collator_(), + alloc_(alloc) +{ + accept_ = alloc->newInfallible(alloc, EndNode::ACCEPT); + JS_ASSERT(next_register_ - 1 <= RegExpMacroAssembler::kMaxRegister); +} + +RegExpCode +RegExpCompiler::Assemble(JSContext *cx, + RegExpMacroAssembler *assembler, + RegExpNode *start, + int capture_count) +{ + macro_assembler_ = assembler; + macro_assembler_->set_slow_safe(false); + + jit::Label fail; + macro_assembler_->PushBacktrack(&fail); + Trace new_trace; + start->Emit(this, &new_trace); + macro_assembler_->BindBacktrack(&fail); + macro_assembler_->Fail(); + + while (!work_list_.empty()) + work_list_.popCopy()->Emit(this, &new_trace); + + RegExpCode code = macro_assembler_->GenerateCode(cx); + if (code.empty()) + return RegExpCode(); + + if (reg_exp_too_big_) { + JS_ReportError(cx, "regexp too big"); + code.destroy(); + return RegExpCode(); + } + + return code; +} + +RegExpCode +irregexp::CompilePattern(JSContext *cx, RegExpShared *shared, RegExpCompileData *data, + const jschar *sampleChars, size_t sampleLength, + bool is_global, bool ignore_case, bool is_ascii) +{ + if ((data->capture_count + 1) * 2 - 1 > RegExpMacroAssembler::kMaxRegister) { + JS_ReportError(cx, "regexp too big"); + return RegExpCode(); + } + + LifoAlloc &alloc = cx->tempLifoAlloc(); + RegExpCompiler compiler(&alloc, data->capture_count, ignore_case, is_ascii); + + // Sample some characters from the middle of the string. + static const int kSampleSize = 128; + + int chars_sampled = 0; + int half_way = (sampleLength - kSampleSize) / 2; + for (size_t i = Max(0, half_way); + i < sampleLength && chars_sampled < kSampleSize; + i++, chars_sampled++) + { + compiler.frequency_collator()->CountCharacter(sampleChars[i]); + } + + // Wrap the body of the regexp in capture #0. + RegExpNode* captured_body = RegExpCapture::ToNode(data->tree, + 0, + &compiler, + compiler.accept()); + RegExpNode* node = captured_body; + bool is_end_anchored = data->tree->IsAnchoredAtEnd(); + bool is_start_anchored = data->tree->IsAnchoredAtStart(); + int max_length = data->tree->max_match(); + if (!is_start_anchored) { + // Add a .*? at the beginning, outside the body capture, unless + // this expression is anchored at the beginning. + RegExpNode* loop_node = + RegExpQuantifier::ToNode(0, + RegExpTree::kInfinity, + false, + alloc.newInfallible('*'), + &compiler, + captured_body, + data->contains_anchor); + + if (data->contains_anchor) { + // Unroll loop once, to take care of the case that might start + // at the start of input. + ChoiceNode *first_step_node = alloc.newInfallible(&alloc, 2); + RegExpNode *char_class = + alloc.newInfallible(alloc.newInfallible('*'), loop_node); + first_step_node->AddAlternative(GuardedAlternative(captured_body)); + first_step_node->AddAlternative(GuardedAlternative(char_class)); + node = first_step_node; + } else { + node = loop_node; + } + } + if (is_ascii) { + node = node->FilterASCII(RegExpCompiler::kMaxRecursion, ignore_case); + // Do it again to propagate the new nodes to places where they were not + // put because they had not been calculated yet. + if (node != nullptr) { + node = node->FilterASCII(RegExpCompiler::kMaxRecursion, ignore_case); + } + } + + if (node == nullptr) + node = alloc.newInfallible(&alloc, EndNode::BACKTRACK); + + Analysis analysis(cx, ignore_case, is_ascii); + analysis.EnsureAnalyzed(node); + if (analysis.has_failed()) { + JS_ReportError(cx, analysis.errorMessage()); + return RegExpCode(); + } + +#ifdef JS_ION + Maybe ctx; + Maybe native_assembler; + Maybe interpreted_assembler; + + RegExpMacroAssembler *assembler; + if (cx->runtime()->options().nativeRegExp()) { + NativeRegExpMacroAssembler::Mode mode = + is_ascii ? NativeRegExpMacroAssembler::ASCII + : NativeRegExpMacroAssembler::JSCHAR; + + ctx.construct(cx, (jit::TempAllocator *) nullptr); + native_assembler.construct(&alloc, shared, cx->runtime(), mode, (data->capture_count + 1) * 2); + assembler = native_assembler.addr(); + } else { + interpreted_assembler.construct(&alloc, shared, (data->capture_count + 1) * 2); + assembler = interpreted_assembler.addr(); + } +#else // JS_ION + InterpretedRegExpMacroAssembler macro_assembler(&alloc, shared, (data->capture_count + 1) * 2); + RegExpMacroAssembler *assembler = ¯o_assembler; +#endif // JS_ION + + // Inserted here, instead of in Assembler, because it depends on information + // in the AST that isn't replicated in the Node structure. + static const int kMaxBacksearchLimit = 1024; + if (is_end_anchored && + !is_start_anchored && + max_length < kMaxBacksearchLimit) { + assembler->SetCurrentPositionFromEnd(max_length); + } + + if (is_global) { + assembler->set_global_mode((data->tree->min_match() > 0) + ? RegExpMacroAssembler::GLOBAL_NO_ZERO_LENGTH_CHECK + : RegExpMacroAssembler::GLOBAL); + } + + return compiler.Assemble(cx, assembler, node, data->capture_count); +} + +RegExpRunStatus +irregexp::ExecuteCode(JSContext *cx, jit::JitCode *codeBlock, + const jschar *chars, size_t start, size_t length, MatchPairs *matches) +{ +#ifdef JS_ION + typedef void (*RegExpCodeSignature)(InputOutputData *); + + InputOutputData data(chars, chars + length, start, matches); + + RegExpCodeSignature function = reinterpret_cast(codeBlock->raw()); + CALL_GENERATED_REGEXP(function, &data); + + return (RegExpRunStatus) data.result; +#else + MOZ_CRASH(); +#endif +} + +// ------------------------------------------------------------------- +// Tree to graph conversion + +RegExpNode * +RegExpAtom::ToNode(RegExpCompiler* compiler, RegExpNode* on_success) +{ + TextElementVector *elms = + compiler->alloc()->newInfallible(*compiler->alloc()); + elms->append(TextElement::Atom(this)); + return compiler->alloc()->newInfallible(elms, on_success); +} + +RegExpNode * +RegExpText::ToNode(RegExpCompiler* compiler, RegExpNode* on_success) +{ + return compiler->alloc()->newInfallible(&elements_, on_success); +} + +RegExpNode * +RegExpCharacterClass::ToNode(RegExpCompiler* compiler, RegExpNode* on_success) +{ + return compiler->alloc()->newInfallible(this, on_success); +} + +RegExpNode * +RegExpDisjunction::ToNode(RegExpCompiler* compiler, RegExpNode* on_success) +{ + const RegExpTreeVector &alternatives = this->alternatives(); + size_t length = alternatives.length(); + ChoiceNode* result = compiler->alloc()->newInfallible(compiler->alloc(), length); + for (size_t i = 0; i < length; i++) { + GuardedAlternative alternative(alternatives[i]->ToNode(compiler, on_success)); + result->AddAlternative(alternative); + } + return result; +} + +RegExpNode * +RegExpQuantifier::ToNode(RegExpCompiler* compiler, RegExpNode* on_success) +{ + return ToNode(min(), + max(), + is_greedy(), + body(), + compiler, + on_success); +} + +// Scoped object to keep track of how much we unroll quantifier loops in the +// regexp graph generator. +class RegExpExpansionLimiter +{ + public: + static const int kMaxExpansionFactor = 6; + RegExpExpansionLimiter(RegExpCompiler* compiler, int factor) + : compiler_(compiler), + saved_expansion_factor_(compiler->current_expansion_factor()), + ok_to_expand_(saved_expansion_factor_ <= kMaxExpansionFactor) + { + JS_ASSERT(factor > 0); + if (ok_to_expand_) { + if (factor > kMaxExpansionFactor) { + // Avoid integer overflow of the current expansion factor. + ok_to_expand_ = false; + compiler->set_current_expansion_factor(kMaxExpansionFactor + 1); + } else { + int new_factor = saved_expansion_factor_ * factor; + ok_to_expand_ = (new_factor <= kMaxExpansionFactor); + compiler->set_current_expansion_factor(new_factor); + } + } + } + + ~RegExpExpansionLimiter() { + compiler_->set_current_expansion_factor(saved_expansion_factor_); + } + + bool ok_to_expand() { return ok_to_expand_; } + + private: + RegExpCompiler* compiler_; + int saved_expansion_factor_; + bool ok_to_expand_; +}; + +/* static */ RegExpNode * +RegExpQuantifier::ToNode(int min, + int max, + bool is_greedy, + RegExpTree* body, + RegExpCompiler* compiler, + RegExpNode* on_success, + bool not_at_start /* = false */) +{ + // x{f, t} becomes this: + // + // (r++)<-. + // | ` + // | (x) + // v ^ + // (r=0)-->(?)---/ [if r < t] + // | + // [if r >= f] \----> ... + // + + // 15.10.2.5 RepeatMatcher algorithm. + // The parser has already eliminated the case where max is 0. In the case + // where max_match is zero the parser has removed the quantifier if min was + // > 0 and removed the atom if min was 0. See AddQuantifierToAtom. + + // If we know that we cannot match zero length then things are a little + // simpler since we don't need to make the special zero length match check + // from step 2.1. If the min and max are small we can unroll a little in + // this case. + static const int kMaxUnrolledMinMatches = 3; // Unroll (foo)+ and (foo){3,} + static const int kMaxUnrolledMaxMatches = 3; // Unroll (foo)? and (foo){x,3} + + if (max == 0) + return on_success; // This can happen due to recursion. + + bool body_can_be_empty = (body->min_match() == 0); + int body_start_reg = RegExpCompiler::kNoRegister; + Interval capture_registers = body->CaptureRegisters(); + bool needs_capture_clearing = !capture_registers.is_empty(); + LifoAlloc *alloc = compiler->alloc(); + + if (body_can_be_empty) { + body_start_reg = compiler->AllocateRegister(); + } else if (!needs_capture_clearing) { + // Only unroll if there are no captures and the body can't be + // empty. + { + RegExpExpansionLimiter limiter(compiler, min + ((max != min) ? 1 : 0)); + if (min > 0 && min <= kMaxUnrolledMinMatches && limiter.ok_to_expand()) { + int new_max = (max == kInfinity) ? max : max - min; + // Recurse once to get the loop or optional matches after the fixed + // ones. + RegExpNode* answer = ToNode(0, new_max, is_greedy, body, compiler, on_success, true); + // Unroll the forced matches from 0 to min. This can cause chains of + // TextNodes (which the parser does not generate). These should be + // combined if it turns out they hinder good code generation. + for (int i = 0; i < min; i++) + answer = body->ToNode(compiler, answer); + return answer; + } + } + if (max <= kMaxUnrolledMaxMatches && min == 0) { + JS_ASSERT(max > 0); // Due to the 'if' above. + RegExpExpansionLimiter limiter(compiler, max); + if (limiter.ok_to_expand()) { + // Unroll the optional matches up to max. + RegExpNode* answer = on_success; + for (int i = 0; i < max; i++) { + ChoiceNode* alternation = alloc->newInfallible(alloc, 2); + if (is_greedy) { + alternation->AddAlternative(GuardedAlternative(body->ToNode(compiler, answer))); + alternation->AddAlternative(GuardedAlternative(on_success)); + } else { + alternation->AddAlternative(GuardedAlternative(on_success)); + alternation->AddAlternative(GuardedAlternative(body->ToNode(compiler, answer))); + } + answer = alternation; + if (not_at_start) alternation->set_not_at_start(); + } + return answer; + } + } + } + bool has_min = min > 0; + bool has_max = max < RegExpTree::kInfinity; + bool needs_counter = has_min || has_max; + int reg_ctr = needs_counter + ? compiler->AllocateRegister() + : RegExpCompiler::kNoRegister; + LoopChoiceNode* center = alloc->newInfallible(alloc, body->min_match() == 0); + if (not_at_start) + center->set_not_at_start(); + RegExpNode* loop_return = needs_counter + ? static_cast(ActionNode::IncrementRegister(reg_ctr, center)) + : static_cast(center); + if (body_can_be_empty) { + // If the body can be empty we need to check if it was and then + // backtrack. + loop_return = ActionNode::EmptyMatchCheck(body_start_reg, + reg_ctr, + min, + loop_return); + } + RegExpNode* body_node = body->ToNode(compiler, loop_return); + if (body_can_be_empty) { + // If the body can be empty we need to store the start position + // so we can bail out if it was empty. + body_node = ActionNode::StorePosition(body_start_reg, false, body_node); + } + if (needs_capture_clearing) { + // Before entering the body of this loop we need to clear captures. + body_node = ActionNode::ClearCaptures(capture_registers, body_node); + } + GuardedAlternative body_alt(body_node); + if (has_max) { + Guard* body_guard = alloc->newInfallible(reg_ctr, Guard::LT, max); + body_alt.AddGuard(alloc, body_guard); + } + GuardedAlternative rest_alt(on_success); + if (has_min) { + Guard* rest_guard = alloc->newInfallible(reg_ctr, Guard::GEQ, min); + rest_alt.AddGuard(alloc, rest_guard); + } + if (is_greedy) { + center->AddLoopAlternative(body_alt); + center->AddContinueAlternative(rest_alt); + } else { + center->AddContinueAlternative(rest_alt); + center->AddLoopAlternative(body_alt); + } + if (needs_counter) + return ActionNode::SetRegister(reg_ctr, 0, center); + return center; +} + +RegExpNode* +RegExpAssertion::ToNode(RegExpCompiler* compiler, + RegExpNode* on_success) +{ + NodeInfo info; + LifoAlloc *alloc = compiler->alloc(); + + switch (assertion_type()) { + case START_OF_LINE: + return AssertionNode::AfterNewline(on_success); + case START_OF_INPUT: + return AssertionNode::AtStart(on_success); + case BOUNDARY: + return AssertionNode::AtBoundary(on_success); + case NON_BOUNDARY: + return AssertionNode::AtNonBoundary(on_success); + case END_OF_INPUT: + return AssertionNode::AtEnd(on_success); + case END_OF_LINE: { + // Compile $ in multiline regexps as an alternation with a positive + // lookahead in one side and an end-of-input on the other side. + // We need two registers for the lookahead. + int stack_pointer_register = compiler->AllocateRegister(); + int position_register = compiler->AllocateRegister(); + // The ChoiceNode to distinguish between a newline and end-of-input. + ChoiceNode* result = alloc->newInfallible(alloc, 2); + // Create a newline atom. + CharacterRangeVector *newline_ranges = alloc->newInfallible(*alloc); + CharacterRange::AddClassEscape(alloc, 'n', newline_ranges); + RegExpCharacterClass* newline_atom = alloc->newInfallible('n'); + TextNode* newline_matcher = + alloc->newInfallible(newline_atom, + ActionNode::PositiveSubmatchSuccess(stack_pointer_register, + position_register, + 0, // No captures inside. + -1, // Ignored if no captures. + on_success)); + // Create an end-of-input matcher. + RegExpNode* end_of_line = + ActionNode::BeginSubmatch(stack_pointer_register, position_register, newline_matcher); + + // Add the two alternatives to the ChoiceNode. + GuardedAlternative eol_alternative(end_of_line); + result->AddAlternative(eol_alternative); + GuardedAlternative end_alternative(AssertionNode::AtEnd(on_success)); + result->AddAlternative(end_alternative); + return result; + } + default: + MOZ_ASSUME_UNREACHABLE("Bad assertion type"); + } + return on_success; +} + +RegExpNode * +RegExpBackReference::ToNode(RegExpCompiler* compiler, RegExpNode* on_success) +{ + return compiler->alloc()->newInfallible(RegExpCapture::StartRegister(index()), + RegExpCapture::EndRegister(index()), + on_success); +} + +RegExpNode * +RegExpEmpty::ToNode(RegExpCompiler* compiler, RegExpNode* on_success) +{ + return on_success; +} + +RegExpNode * +RegExpLookahead::ToNode(RegExpCompiler* compiler, RegExpNode* on_success) +{ + int stack_pointer_register = compiler->AllocateRegister(); + int position_register = compiler->AllocateRegister(); + + const int registers_per_capture = 2; + const int register_of_first_capture = 2; + int register_count = capture_count_ * registers_per_capture; + int register_start = + register_of_first_capture + capture_from_ * registers_per_capture; + + if (is_positive()) { + RegExpNode *bodyNode = + body()->ToNode(compiler, + ActionNode::PositiveSubmatchSuccess(stack_pointer_register, + position_register, + register_count, + register_start, + on_success)); + return ActionNode::BeginSubmatch(stack_pointer_register, + position_register, + bodyNode); + } + + // We use a ChoiceNode for a negative lookahead because it has most of + // the characteristics we need. It has the body of the lookahead as its + // first alternative and the expression after the lookahead of the second + // alternative. If the first alternative succeeds then the + // NegativeSubmatchSuccess will unwind the stack including everything the + // choice node set up and backtrack. If the first alternative fails then + // the second alternative is tried, which is exactly the desired result + // for a negative lookahead. The NegativeLookaheadChoiceNode is a special + // ChoiceNode that knows to ignore the first exit when calculating quick + // checks. + LifoAlloc *alloc = compiler->alloc(); + + RegExpNode *success = + alloc->newInfallible(alloc, + stack_pointer_register, + position_register, + register_count, + register_start); + GuardedAlternative body_alt(body()->ToNode(compiler, success)); + + ChoiceNode *choice_node = + alloc->newInfallible(alloc, body_alt, GuardedAlternative(on_success)); + + return ActionNode::BeginSubmatch(stack_pointer_register, + position_register, + choice_node); +} + +RegExpNode * +RegExpCapture::ToNode(RegExpCompiler *compiler, RegExpNode* on_success) +{ + return ToNode(body(), index(), compiler, on_success); +} + +/* static */ RegExpNode * +RegExpCapture::ToNode(RegExpTree* body, + int index, + RegExpCompiler* compiler, + RegExpNode* on_success) +{ + int start_reg = RegExpCapture::StartRegister(index); + int end_reg = RegExpCapture::EndRegister(index); + RegExpNode* store_end = ActionNode::StorePosition(end_reg, true, on_success); + RegExpNode* body_node = body->ToNode(compiler, store_end); + return ActionNode::StorePosition(start_reg, true, body_node); +} + +RegExpNode* +RegExpAlternative::ToNode(RegExpCompiler* compiler, RegExpNode* on_success) +{ + const RegExpTreeVector &children = nodes(); + RegExpNode *current = on_success; + for (int i = children.length() - 1; i >= 0; i--) + current = children[i]->ToNode(compiler, current); + return current; +} + +// ------------------------------------------------------------------- +// BoyerMooreLookahead + +ContainedInLattice +irregexp::AddRange(ContainedInLattice containment, + const int* ranges, + int ranges_length, + Interval new_range) +{ + JS_ASSERT((ranges_length & 1) == 1); + JS_ASSERT(ranges[ranges_length - 1] == kMaxUtf16CodeUnit + 1); + if (containment == kLatticeUnknown) return containment; + bool inside = false; + int last = 0; + for (int i = 0; i < ranges_length; inside = !inside, last = ranges[i], i++) { + // Consider the range from last to ranges[i]. + // We haven't got to the new range yet. + if (ranges[i] <= new_range.from()) + continue; + + // New range is wholly inside last-ranges[i]. Note that new_range.to() is + // inclusive, but the values in ranges are not. + if (last <= new_range.from() && new_range.to() < ranges[i]) + return Combine(containment, inside ? kLatticeIn : kLatticeOut); + + return kLatticeUnknown; + } + return containment; +} + +void +BoyerMoorePositionInfo::Set(int character) +{ + SetInterval(Interval(character, character)); +} + +void +BoyerMoorePositionInfo::SetInterval(const Interval& interval) +{ + s_ = AddRange(s_, kSpaceRanges, kSpaceRangeCount, interval); + w_ = AddRange(w_, kWordRanges, kWordRangeCount, interval); + d_ = AddRange(d_, kDigitRanges, kDigitRangeCount, interval); + surrogate_ = + AddRange(surrogate_, kSurrogateRanges, kSurrogateRangeCount, interval); + if (interval.to() - interval.from() >= kMapSize - 1) { + if (map_count_ != kMapSize) { + map_count_ = kMapSize; + for (int i = 0; i < kMapSize; i++) + map_[i] = true; + } + return; + } + for (int i = interval.from(); i <= interval.to(); i++) { + int mod_character = (i & kMask); + if (!map_[mod_character]) { + map_count_++; + map_[mod_character] = true; + } + if (map_count_ == kMapSize) + return; + } +} + +void +BoyerMoorePositionInfo::SetAll() +{ + s_ = w_ = d_ = kLatticeUnknown; + if (map_count_ != kMapSize) { + map_count_ = kMapSize; + for (int i = 0; i < kMapSize; i++) + map_[i] = true; + } +} + +BoyerMooreLookahead::BoyerMooreLookahead(LifoAlloc *alloc, size_t length, RegExpCompiler* compiler) + : length_(length), compiler_(compiler), bitmaps_(*alloc) +{ + max_char_ = MaximumCharacter(compiler->ascii()); + + bitmaps_.reserve(length); + for (size_t i = 0; i < length; i++) + bitmaps_.append(alloc->newInfallible(alloc)); +} + +// Find the longest range of lookahead that has the fewest number of different +// characters that can occur at a given position. Since we are optimizing two +// different parameters at once this is a tradeoff. +bool BoyerMooreLookahead::FindWorthwhileInterval(int* from, int* to) { + int biggest_points = 0; + // If more than 32 characters out of 128 can occur it is unlikely that we can + // be lucky enough to step forwards much of the time. + const int kMaxMax = 32; + for (int max_number_of_chars = 4; + max_number_of_chars < kMaxMax; + max_number_of_chars *= 2) { + biggest_points = + FindBestInterval(max_number_of_chars, biggest_points, from, to); + } + if (biggest_points == 0) return false; + return true; +} + +// Find the highest-points range between 0 and length_ where the character +// information is not too vague. 'Too vague' means that there are more than +// max_number_of_chars that can occur at this position. Calculates the number +// of points as the product of width-of-the-range and +// probability-of-finding-one-of-the-characters, where the probability is +// calculated using the frequency distribution of the sample subject string. +int +BoyerMooreLookahead::FindBestInterval(int max_number_of_chars, int old_biggest_points, + int* from, int* to) +{ + int biggest_points = old_biggest_points; + static const int kSize = RegExpMacroAssembler::kTableSize; + for (int i = 0; i < length_; ) { + while (i < length_ && Count(i) > max_number_of_chars) i++; + if (i == length_) break; + int remembered_from = i; + bool union_map[kSize]; + for (int j = 0; j < kSize; j++) union_map[j] = false; + while (i < length_ && Count(i) <= max_number_of_chars) { + BoyerMoorePositionInfo* map = bitmaps_[i]; + for (int j = 0; j < kSize; j++) union_map[j] |= map->at(j); + i++; + } + int frequency = 0; + for (int j = 0; j < kSize; j++) { + if (union_map[j]) { + // Add 1 to the frequency to give a small per-character boost for + // the cases where our sampling is not good enough and many + // characters have a frequency of zero. This means the frequency + // can theoretically be up to 2*kSize though we treat it mostly as + // a fraction of kSize. + frequency += compiler_->frequency_collator()->Frequency(j) + 1; + } + } + // We use the probability of skipping times the distance we are skipping to + // judge the effectiveness of this. Actually we have a cut-off: By + // dividing by 2 we switch off the skipping if the probability of skipping + // is less than 50%. This is because the multibyte mask-and-compare + // skipping in quickcheck is more likely to do well on this case. + bool in_quickcheck_range = ((i - remembered_from < 4) || + (compiler_->ascii() ? remembered_from <= 4 : remembered_from <= 2)); + // Called 'probability' but it is only a rough estimate and can actually + // be outside the 0-kSize range. + int probability = (in_quickcheck_range ? kSize / 2 : kSize) - frequency; + int points = (i - remembered_from) * probability; + if (points > biggest_points) { + *from = remembered_from; + *to = i - 1; + biggest_points = points; + } + } + return biggest_points; +} + +// Take all the characters that will not prevent a successful match if they +// occur in the subject string in the range between min_lookahead and +// max_lookahead (inclusive) measured from the current position. If the +// character at max_lookahead offset is not one of these characters, then we +// can safely skip forwards by the number of characters in the range. +int BoyerMooreLookahead::GetSkipTable(int min_lookahead, + int max_lookahead, + uint8_t *boolean_skip_table) +{ + const int kSize = RegExpMacroAssembler::kTableSize; + + const int kSkipArrayEntry = 0; + const int kDontSkipArrayEntry = 1; + + for (int i = 0; i < kSize; i++) + boolean_skip_table[i] = kSkipArrayEntry; + int skip = max_lookahead + 1 - min_lookahead; + + for (int i = max_lookahead; i >= min_lookahead; i--) { + BoyerMoorePositionInfo* map = bitmaps_[i]; + for (int j = 0; j < kSize; j++) { + if (map->at(j)) + boolean_skip_table[j] = kDontSkipArrayEntry; + } + } + + return skip; +} + +// See comment on the implementation of GetSkipTable. +bool +BoyerMooreLookahead::EmitSkipInstructions(RegExpMacroAssembler* masm) +{ + const int kSize = RegExpMacroAssembler::kTableSize; + + int min_lookahead = 0; + int max_lookahead = 0; + + if (!FindWorthwhileInterval(&min_lookahead, &max_lookahead)) + return false; + + bool found_single_character = false; + int single_character = 0; + for (int i = max_lookahead; i >= min_lookahead; i--) { + BoyerMoorePositionInfo* map = bitmaps_[i]; + if (map->map_count() > 1 || + (found_single_character && map->map_count() != 0)) { + found_single_character = false; + break; + } + for (int j = 0; j < kSize; j++) { + if (map->at(j)) { + found_single_character = true; + single_character = j; + break; + } + } + } + + int lookahead_width = max_lookahead + 1 - min_lookahead; + + if (found_single_character && lookahead_width == 1 && max_lookahead < 3) { + // The mask-compare can probably handle this better. + return false; + } + + if (found_single_character) { + jit::Label cont, again; + masm->Bind(&again); + masm->LoadCurrentCharacter(max_lookahead, &cont, true); + if (max_char_ > kSize) { + masm->CheckCharacterAfterAnd(single_character, + RegExpMacroAssembler::kTableMask, + &cont); + } else { + masm->CheckCharacter(single_character, &cont); + } + masm->AdvanceCurrentPosition(lookahead_width); + masm->JumpOrBacktrack(&again); + masm->Bind(&cont); + return true; + } + + uint8_t *boolean_skip_table = static_cast(js_malloc(kSize)); + if (!boolean_skip_table || !masm->shared->addTable(boolean_skip_table)) + CrashAtUnhandlableOOM("Table malloc"); + + int skip_distance = GetSkipTable(min_lookahead, max_lookahead, boolean_skip_table); + JS_ASSERT(skip_distance != 0); + + jit::Label cont, again; + masm->Bind(&again); + masm->LoadCurrentCharacter(max_lookahead, &cont, true); + masm->CheckBitInTable(boolean_skip_table, &cont); + masm->AdvanceCurrentPosition(skip_distance); + masm->JumpOrBacktrack(&again); + masm->Bind(&cont); + + return true; +} + +// ------------------------------------------------------------------- +// Trace + +bool Trace::DeferredAction::Mentions(int that) +{ + if (action_type() == ActionNode::CLEAR_CAPTURES) { + Interval range = static_cast(this)->range(); + return range.Contains(that); + } + return reg() == that; +} + +bool Trace::mentions_reg(int reg) +{ + for (DeferredAction* action = actions_; action != nullptr; action = action->next()) { + if (action->Mentions(reg)) + return true; + } + return false; +} + +bool +Trace::GetStoredPosition(int reg, int* cp_offset) +{ + JS_ASSERT(0 == *cp_offset); + for (DeferredAction* action = actions_; action != nullptr; action = action->next()) { + if (action->Mentions(reg)) { + if (action->action_type() == ActionNode::STORE_POSITION) { + *cp_offset = static_cast(action)->cp_offset(); + return true; + } + return false; + } + } + return false; +} + +int +Trace::FindAffectedRegisters(LifoAlloc *alloc, OutSet* affected_registers) +{ + int max_register = RegExpCompiler::kNoRegister; + for (DeferredAction* action = actions_; action != nullptr; action = action->next()) { + if (action->action_type() == ActionNode::CLEAR_CAPTURES) { + Interval range = static_cast(action)->range(); + for (int i = range.from(); i <= range.to(); i++) + affected_registers->Set(alloc, i); + if (range.to() > max_register) max_register = range.to(); + } else { + affected_registers->Set(alloc, action->reg()); + if (action->reg() > max_register) max_register = action->reg(); + } + } + return max_register; +} + +void +Trace::RestoreAffectedRegisters(RegExpMacroAssembler* assembler, + int max_register, + OutSet& registers_to_pop, + OutSet& registers_to_clear) +{ + for (int reg = max_register; reg >= 0; reg--) { + if (registers_to_pop.Get(reg)) assembler->PopRegister(reg); + else if (registers_to_clear.Get(reg)) { + int clear_to = reg; + while (reg > 0 && registers_to_clear.Get(reg - 1)) + reg--; + assembler->ClearRegisters(reg, clear_to); + } + } +} + +enum DeferredActionUndoType { + DEFER_IGNORE, + DEFER_RESTORE, + DEFER_CLEAR +}; + +void +Trace::PerformDeferredActions(LifoAlloc *alloc, + RegExpMacroAssembler* assembler, + int max_register, + OutSet& affected_registers, + OutSet* registers_to_pop, + OutSet* registers_to_clear) +{ + // The "+1" is to avoid a push_limit of zero if stack_limit_slack() is 1. + const int push_limit = (assembler->stack_limit_slack() + 1) / 2; + + // Count pushes performed to force a stack limit check occasionally. + int pushes = 0; + + for (int reg = 0; reg <= max_register; reg++) { + if (!affected_registers.Get(reg)) + continue; + + // The chronologically first deferred action in the trace + // is used to infer the action needed to restore a register + // to its previous state (or not, if it's safe to ignore it). + DeferredActionUndoType undo_action = DEFER_IGNORE; + + int value = 0; + bool absolute = false; + bool clear = false; + int store_position = -1; + // This is a little tricky because we are scanning the actions in reverse + // historical order (newest first). + for (DeferredAction* action = actions_; + action != nullptr; + action = action->next()) { + if (action->Mentions(reg)) { + switch (action->action_type()) { + case ActionNode::SET_REGISTER: { + Trace::DeferredSetRegister* psr = + static_cast(action); + if (!absolute) { + value += psr->value(); + absolute = true; + } + // SET_REGISTER is currently only used for newly introduced loop + // counters. They can have a significant previous value if they + // occour in a loop. TODO(lrn): Propagate this information, so + // we can set undo_action to IGNORE if we know there is no value to + // restore. + undo_action = DEFER_RESTORE; + JS_ASSERT(store_position == -1); + JS_ASSERT(!clear); + break; + } + case ActionNode::INCREMENT_REGISTER: + if (!absolute) { + value++; + } + JS_ASSERT(store_position == -1); + JS_ASSERT(!clear); + undo_action = DEFER_RESTORE; + break; + case ActionNode::STORE_POSITION: { + Trace::DeferredCapture* pc = + static_cast(action); + if (!clear && store_position == -1) { + store_position = pc->cp_offset(); + } + + // For captures we know that stores and clears alternate. + // Other register, are never cleared, and if the occur + // inside a loop, they might be assigned more than once. + if (reg <= 1) { + // Registers zero and one, aka "capture zero", is + // always set correctly if we succeed. There is no + // need to undo a setting on backtrack, because we + // will set it again or fail. + undo_action = DEFER_IGNORE; + } else { + undo_action = pc->is_capture() ? DEFER_CLEAR : DEFER_RESTORE; + } + JS_ASSERT(!absolute); + JS_ASSERT(value == 0); + break; + } + case ActionNode::CLEAR_CAPTURES: { + // Since we're scanning in reverse order, if we've already + // set the position we have to ignore historically earlier + // clearing operations. + if (store_position == -1) { + clear = true; + } + undo_action = DEFER_RESTORE; + JS_ASSERT(!absolute); + JS_ASSERT(value == 0); + break; + } + default: + MOZ_ASSUME_UNREACHABLE("Bad action"); + break; + } + } + } + // Prepare for the undo-action (e.g., push if it's going to be popped). + if (undo_action == DEFER_RESTORE) { + pushes++; + RegExpMacroAssembler::StackCheckFlag stack_check = + RegExpMacroAssembler::kNoStackLimitCheck; + if (pushes == push_limit) { + stack_check = RegExpMacroAssembler::kCheckStackLimit; + pushes = 0; + } + + assembler->PushRegister(reg, stack_check); + registers_to_pop->Set(alloc, reg); + } else if (undo_action == DEFER_CLEAR) { + registers_to_clear->Set(alloc, reg); + } + // Perform the chronologically last action (or accumulated increment) + // for the register. + if (store_position != -1) { + assembler->WriteCurrentPositionToRegister(reg, store_position); + } else if (clear) { + assembler->ClearRegisters(reg, reg); + } else if (absolute) { + assembler->SetRegister(reg, value); + } else if (value != 0) { + assembler->AdvanceRegister(reg, value); + } + } +} + +// This is called as we come into a loop choice node and some other tricky +// nodes. It normalizes the state of the code generator to ensure we can +// generate generic code. +void Trace::Flush(RegExpCompiler* compiler, RegExpNode* successor) +{ + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + + JS_ASSERT(!is_trivial()); + + if (actions_ == nullptr && backtrack() == nullptr) { + // Here we just have some deferred cp advances to fix and we are back to + // a normal situation. We may also have to forget some information gained + // through a quick check that was already performed. + if (cp_offset_ != 0) assembler->AdvanceCurrentPosition(cp_offset_); + // Create a new trivial state and generate the node with that. + Trace new_state; + successor->Emit(compiler, &new_state); + return; + } + + // Generate deferred actions here along with code to undo them again. + OutSet affected_registers; + + if (backtrack() != nullptr) { + // Here we have a concrete backtrack location. These are set up by choice + // nodes and so they indicate that we have a deferred save of the current + // position which we may need to emit here. + assembler->PushCurrentPosition(); + } + + int max_register = FindAffectedRegisters(compiler->alloc(), &affected_registers); + OutSet registers_to_pop; + OutSet registers_to_clear; + PerformDeferredActions(compiler->alloc(), + assembler, + max_register, + affected_registers, + ®isters_to_pop, + ®isters_to_clear); + if (cp_offset_ != 0) + assembler->AdvanceCurrentPosition(cp_offset_); + + // Create a new trivial state and generate the node with that. + jit::Label undo; + assembler->PushBacktrack(&undo); + Trace new_state; + successor->Emit(compiler, &new_state); + + // On backtrack we need to restore state. + assembler->BindBacktrack(&undo); + RestoreAffectedRegisters(assembler, + max_register, + registers_to_pop, + registers_to_clear); + if (backtrack() == nullptr) { + assembler->Backtrack(); + } else { + assembler->PopCurrentPosition(); + assembler->JumpOrBacktrack(backtrack()); + } +} + +void +Trace::InvalidateCurrentCharacter() +{ + characters_preloaded_ = 0; +} + +void +Trace::AdvanceCurrentPositionInTrace(int by, RegExpCompiler* compiler) +{ + JS_ASSERT(by > 0); + // We don't have an instruction for shifting the current character register + // down or for using a shifted value for anything so lets just forget that + // we preloaded any characters into it. + characters_preloaded_ = 0; + // Adjust the offsets of the quick check performed information. This + // information is used to find out what we already determined about the + // characters by means of mask and compare. + quick_check_performed_.Advance(by, compiler->ascii()); + cp_offset_ += by; + if (cp_offset_ > RegExpMacroAssembler::kMaxCPOffset) { + compiler->SetRegExpTooBig(); + cp_offset_ = 0; + } + bound_checked_up_to_ = Max(0, bound_checked_up_to_ - by); +} + +void +OutSet::Set(LifoAlloc *alloc, unsigned value) +{ + if (value < kFirstLimit) { + first_ |= (1 << value); + } else { + if (remaining_ == nullptr) + remaining_ = alloc->newInfallible(*alloc); + + for (size_t i = 0; i < remaining().length(); i++) { + if (remaining()[i] == value) + return; + } + remaining().append(value); + } +} + +bool +OutSet::Get(unsigned value) +{ + if (value < kFirstLimit) + return (first_ & (1 << value)) != 0; + if (remaining_ == nullptr) + return false; + for (size_t i = 0; i < remaining().length(); i++) { + if (remaining()[i] == value) + return true; + } + return false; +} + +// ------------------------------------------------------------------- +// Graph emitting + +void +NegativeSubmatchSuccess::Emit(RegExpCompiler* compiler, Trace* trace) +{ + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + + // Omit flushing the trace. We discard the entire stack frame anyway. + + if (!label()->bound()) { + // We are completely independent of the trace, since we ignore it, + // so this code can be used as the generic version. + assembler->Bind(label()); + } + + // Throw away everything on the backtrack stack since the start + // of the negative submatch and restore the character position. + assembler->ReadCurrentPositionFromRegister(current_position_register_); + assembler->ReadBacktrackStackPointerFromRegister(stack_pointer_register_); + + if (clear_capture_count_ > 0) { + // Clear any captures that might have been performed during the success + // of the body of the negative look-ahead. + int clear_capture_end = clear_capture_start_ + clear_capture_count_ - 1; + assembler->ClearRegisters(clear_capture_start_, clear_capture_end); + } + + // Now that we have unwound the stack we find at the top of the stack the + // backtrack that the BeginSubmatch node got. + assembler->Backtrack(); +} + +void +EndNode::Emit(RegExpCompiler* compiler, Trace* trace) +{ + if (!trace->is_trivial()) { + trace->Flush(compiler, this); + return; + } + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + if (!label()->bound()) { + assembler->Bind(label()); + } + switch (action_) { + case ACCEPT: + assembler->Succeed(); + return; + case BACKTRACK: + assembler->JumpOrBacktrack(trace->backtrack()); + return; + case NEGATIVE_SUBMATCH_SUCCESS: + // This case is handled in a different virtual method. + MOZ_ASSUME_UNREACHABLE("Bad action"); + } + MOZ_ASSUME_UNREACHABLE("Bad action"); +} + +// Emit the code to check for a ^ in multiline mode (1-character lookbehind +// that matches newline or the start of input). +static void +EmitHat(RegExpCompiler* compiler, RegExpNode* on_success, Trace* trace) +{ + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + + // We will be loading the previous character into the current character + // register. + Trace new_trace(*trace); + new_trace.InvalidateCurrentCharacter(); + + jit::Label ok; + if (new_trace.cp_offset() == 0) { + // The start of input counts as a newline in this context, so skip to + // ok if we are at the start. + assembler->CheckAtStart(&ok); + } + + // We already checked that we are not at the start of input so it must be + // OK to load the previous character. + assembler->LoadCurrentCharacter(new_trace.cp_offset() -1, new_trace.backtrack(), false); + + if (!assembler->CheckSpecialCharacterClass('n', new_trace.backtrack())) { + // Newline means \n, \r, 0x2028 or 0x2029. + if (!compiler->ascii()) + assembler->CheckCharacterAfterAnd(0x2028, 0xfffe, &ok); + assembler->CheckCharacter('\n', &ok); + assembler->CheckNotCharacter('\r', new_trace.backtrack()); + } + assembler->Bind(&ok); + on_success->Emit(compiler, &new_trace); +} + +// Check for [0-9A-Z_a-z]. +static void +EmitWordCheck(RegExpMacroAssembler* assembler, + jit::Label* word, jit::Label* non_word, bool fall_through_on_word) +{ + if (assembler->CheckSpecialCharacterClass(fall_through_on_word ? 'w' : 'W', + fall_through_on_word ? non_word : word)) + { + // Optimized implementation available. + return; + } + + assembler->CheckCharacterGT('z', non_word); + assembler->CheckCharacterLT('0', non_word); + assembler->CheckCharacterGT('a' - 1, word); + assembler->CheckCharacterLT('9' + 1, word); + assembler->CheckCharacterLT('A', non_word); + assembler->CheckCharacterLT('Z' + 1, word); + + if (fall_through_on_word) + assembler->CheckNotCharacter('_', non_word); + else + assembler->CheckCharacter('_', word); +} + +// Emit the code to handle \b and \B (word-boundary or non-word-boundary). +void +AssertionNode::EmitBoundaryCheck(RegExpCompiler* compiler, Trace* trace) +{ + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + Trace::TriBool next_is_word_character = Trace::UNKNOWN; + bool not_at_start = (trace->at_start() == Trace::FALSE_VALUE); + BoyerMooreLookahead* lookahead = bm_info(not_at_start); + if (lookahead == nullptr) { + int eats_at_least = + Min(kMaxLookaheadForBoyerMoore, EatsAtLeast(kMaxLookaheadForBoyerMoore, + kRecursionBudget, + not_at_start)); + if (eats_at_least >= 1) { + BoyerMooreLookahead* bm = + alloc()->newInfallible(alloc(), eats_at_least, compiler); + FillInBMInfo(0, kRecursionBudget, bm, not_at_start); + if (bm->at(0)->is_non_word()) + next_is_word_character = Trace::FALSE_VALUE; + if (bm->at(0)->is_word()) next_is_word_character = Trace::TRUE_VALUE; + } + } else { + if (lookahead->at(0)->is_non_word()) + next_is_word_character = Trace::FALSE_VALUE; + if (lookahead->at(0)->is_word()) + next_is_word_character = Trace::TRUE_VALUE; + } + bool at_boundary = (assertion_type_ == AssertionNode::AT_BOUNDARY); + if (next_is_word_character == Trace::UNKNOWN) { + jit::Label before_non_word; + jit::Label before_word; + if (trace->characters_preloaded() != 1) { + assembler->LoadCurrentCharacter(trace->cp_offset(), &before_non_word); + } + // Fall through on non-word. + EmitWordCheck(assembler, &before_word, &before_non_word, false); + // Next character is not a word character. + assembler->Bind(&before_non_word); + jit::Label ok; + BacktrackIfPrevious(compiler, trace, at_boundary ? kIsNonWord : kIsWord); + assembler->JumpOrBacktrack(&ok); + + assembler->Bind(&before_word); + BacktrackIfPrevious(compiler, trace, at_boundary ? kIsWord : kIsNonWord); + assembler->Bind(&ok); + } else if (next_is_word_character == Trace::TRUE_VALUE) { + BacktrackIfPrevious(compiler, trace, at_boundary ? kIsWord : kIsNonWord); + } else { + JS_ASSERT(next_is_word_character == Trace::FALSE_VALUE); + BacktrackIfPrevious(compiler, trace, at_boundary ? kIsNonWord : kIsWord); + } +} + +void +AssertionNode::BacktrackIfPrevious(RegExpCompiler* compiler, + Trace* trace, + AssertionNode::IfPrevious backtrack_if_previous) +{ + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + Trace new_trace(*trace); + new_trace.InvalidateCurrentCharacter(); + + jit::Label fall_through, dummy; + + jit::Label* non_word = backtrack_if_previous == kIsNonWord ? new_trace.backtrack() : &fall_through; + jit::Label* word = backtrack_if_previous == kIsNonWord ? &fall_through : new_trace.backtrack(); + + if (new_trace.cp_offset() == 0) { + // The start of input counts as a non-word character, so the question is + // decided if we are at the start. + assembler->CheckAtStart(non_word); + } + // We already checked that we are not at the start of input so it must be + // OK to load the previous character. + assembler->LoadCurrentCharacter(new_trace.cp_offset() - 1, &dummy, false); + EmitWordCheck(assembler, word, non_word, backtrack_if_previous == kIsNonWord); + + assembler->Bind(&fall_through); + on_success()->Emit(compiler, &new_trace); +} + +void +AssertionNode::GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int filled_in, + bool not_at_start) +{ + if (assertion_type_ == AT_START && not_at_start) { + details->set_cannot_match(); + return; + } + return on_success()->GetQuickCheckDetails(details, compiler, filled_in, not_at_start); +} + +void +AssertionNode::Emit(RegExpCompiler* compiler, Trace* trace) +{ + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + switch (assertion_type_) { + case AT_END: { + jit::Label ok; + assembler->CheckPosition(trace->cp_offset(), &ok); + assembler->JumpOrBacktrack(trace->backtrack()); + assembler->Bind(&ok); + break; + } + case AT_START: { + if (trace->at_start() == Trace::FALSE_VALUE) { + assembler->JumpOrBacktrack(trace->backtrack()); + return; + } + if (trace->at_start() == Trace::UNKNOWN) { + assembler->CheckNotAtStart(trace->backtrack()); + Trace at_start_trace = *trace; + at_start_trace.set_at_start(true); + on_success()->Emit(compiler, &at_start_trace); + return; + } + } + break; + case AFTER_NEWLINE: + EmitHat(compiler, on_success(), trace); + return; + case AT_BOUNDARY: + case AT_NON_BOUNDARY: { + EmitBoundaryCheck(compiler, trace); + return; + } + } + on_success()->Emit(compiler, trace); +} + +static bool +DeterminedAlready(QuickCheckDetails* quick_check, int offset) +{ + if (quick_check == nullptr) + return false; + if (offset >= quick_check->characters()) + return false; + return quick_check->positions(offset)->determines_perfectly; +} + +static void +UpdateBoundsCheck(int index, int* checked_up_to) +{ + if (index > *checked_up_to) + *checked_up_to = index; +} + +static void +EmitBoundaryTest(RegExpMacroAssembler* masm, + int border, + jit::Label* fall_through, + jit::Label* above_or_equal, + jit::Label* below) +{ + if (below != fall_through) { + masm->CheckCharacterLT(border, below); + if (above_or_equal != fall_through) + masm->JumpOrBacktrack(above_or_equal); + } else { + masm->CheckCharacterGT(border - 1, above_or_equal); + } +} + +static void +EmitDoubleBoundaryTest(RegExpMacroAssembler* masm, + int first, + int last, + jit::Label* fall_through, + jit::Label* in_range, + jit::Label* out_of_range) +{ + if (in_range == fall_through) { + if (first == last) + masm->CheckNotCharacter(first, out_of_range); + else + masm->CheckCharacterNotInRange(first, last, out_of_range); + } else { + if (first == last) + masm->CheckCharacter(first, in_range); + else + masm->CheckCharacterInRange(first, last, in_range); + if (out_of_range != fall_through) + masm->JumpOrBacktrack(out_of_range); + } +} + +typedef js::Vector > RangeBoundaryVector; + +// even_label is for ranges[i] to ranges[i + 1] where i - start_index is even. +// odd_label is for ranges[i] to ranges[i + 1] where i - start_index is odd. +static void +EmitUseLookupTable(RegExpMacroAssembler* masm, + RangeBoundaryVector &ranges, + int start_index, + int end_index, + int min_char, + jit::Label* fall_through, + jit::Label* even_label, + jit::Label* odd_label) +{ + static const int kSize = RegExpMacroAssembler::kTableSize; + static const int kMask = RegExpMacroAssembler::kTableMask; + + DebugOnly base = (min_char & ~kMask); + + // Assert that everything is on one kTableSize page. + for (int i = start_index; i <= end_index; i++) + JS_ASSERT((ranges[i] & ~kMask) == base); + JS_ASSERT(start_index == 0 || (ranges[start_index - 1] & ~kMask) <= base); + + char templ[kSize]; + jit::Label* on_bit_set; + jit::Label* on_bit_clear; + int bit; + if (even_label == fall_through) { + on_bit_set = odd_label; + on_bit_clear = even_label; + bit = 1; + } else { + on_bit_set = even_label; + on_bit_clear = odd_label; + bit = 0; + } + for (int i = 0; i < (ranges[start_index] & kMask) && i < kSize; i++) + templ[i] = bit; + int j = 0; + bit ^= 1; + for (int i = start_index; i < end_index; i++) { + for (j = (ranges[i] & kMask); j < (ranges[i + 1] & kMask); j++) { + templ[j] = bit; + } + bit ^= 1; + } + for (int i = j; i < kSize; i++) { + templ[i] = bit; + } + + // TODO(erikcorry): Cache these. + uint8_t *ba = static_cast(js_malloc(kSize)); + if (!ba || !masm->shared->addTable(ba)) + CrashAtUnhandlableOOM("Table malloc"); + + for (int i = 0; i < kSize; i++) + ba[i] = templ[i]; + + masm->CheckBitInTable(ba, on_bit_set); + if (on_bit_clear != fall_through) + masm->JumpOrBacktrack(on_bit_clear); +} + +static void +CutOutRange(RegExpMacroAssembler* masm, + RangeBoundaryVector &ranges, + int start_index, + int end_index, + int cut_index, + jit::Label* even_label, + jit::Label* odd_label) +{ + bool odd = (((cut_index - start_index) & 1) == 1); + jit::Label* in_range_label = odd ? odd_label : even_label; + jit::Label dummy; + EmitDoubleBoundaryTest(masm, + ranges[cut_index], + ranges[cut_index + 1] - 1, + &dummy, + in_range_label, + &dummy); + JS_ASSERT(!dummy.used()); + // Cut out the single range by rewriting the array. This creates a new + // range that is a merger of the two ranges on either side of the one we + // are cutting out. The oddity of the labels is preserved. + for (int j = cut_index; j > start_index; j--) + ranges[j] = ranges[j - 1]; + for (int j = cut_index + 1; j < end_index; j++) + ranges[j] = ranges[j + 1]; +} + +// Unicode case. Split the search space into kSize spaces that are handled +// with recursion. +static void +SplitSearchSpace(RangeBoundaryVector &ranges, + int start_index, + int end_index, + int* new_start_index, + int* new_end_index, + int* border) +{ + static const int kSize = RegExpMacroAssembler::kTableSize; + static const int kMask = RegExpMacroAssembler::kTableMask; + + int first = ranges[start_index]; + int last = ranges[end_index] - 1; + + *new_start_index = start_index; + *border = (ranges[start_index] & ~kMask) + kSize; + while (*new_start_index < end_index) { + if (ranges[*new_start_index] > *border) + break; + (*new_start_index)++; + } + // new_start_index is the index of the first edge that is beyond the + // current kSize space. + + // For very large search spaces we do a binary chop search of the non-ASCII + // space instead of just going to the end of the current kSize space. The + // heuristics are complicated a little by the fact that any 128-character + // encoding space can be quickly tested with a table lookup, so we don't + // wish to do binary chop search at a smaller granularity than that. A + // 128-character space can take up a lot of space in the ranges array if, + // for example, we only want to match every second character (eg. the lower + // case characters on some Unicode pages). + int binary_chop_index = (end_index + start_index) / 2; + // The first test ensures that we get to the code that handles the ASCII + // range with a single not-taken branch, speeding up this important + // character range (even non-ASCII charset-based text has spaces and + // punctuation). + if (*border - 1 > (int) kMaxOneByteCharCode && // ASCII case. + end_index - start_index > (*new_start_index - start_index) * 2 && + last - first > kSize * 2 && + binary_chop_index > *new_start_index && + ranges[binary_chop_index] >= first + 2 * kSize) + { + int scan_forward_for_section_border = binary_chop_index;; + int new_border = (ranges[binary_chop_index] | kMask) + 1; + + while (scan_forward_for_section_border < end_index) { + if (ranges[scan_forward_for_section_border] > new_border) { + *new_start_index = scan_forward_for_section_border; + *border = new_border; + break; + } + scan_forward_for_section_border++; + } + } + + JS_ASSERT(*new_start_index > start_index); + *new_end_index = *new_start_index - 1; + if (ranges[*new_end_index] == *border) + (*new_end_index)--; + if (*border >= ranges[end_index]) { + *border = ranges[end_index]; + *new_start_index = end_index; // Won't be used. + *new_end_index = end_index - 1; + } +} + +// Gets a series of segment boundaries representing a character class. If the +// character is in the range between an even and an odd boundary (counting from +// start_index) then go to even_label, otherwise go to odd_label. We already +// know that the character is in the range of min_char to max_char inclusive. +// Either label can be nullptr indicating backtracking. Either label can also be +// equal to the fall_through label. +static void +GenerateBranches(RegExpMacroAssembler* masm, + RangeBoundaryVector &ranges, + int start_index, + int end_index, + jschar min_char, + jschar max_char, + jit::Label* fall_through, + jit::Label* even_label, + jit::Label* odd_label) +{ + int first = ranges[start_index]; + int last = ranges[end_index] - 1; + + JS_ASSERT(min_char < first); + + // Just need to test if the character is before or on-or-after + // a particular character. + if (start_index == end_index) { + EmitBoundaryTest(masm, first, fall_through, even_label, odd_label); + return; + } + + // Another almost trivial case: There is one interval in the middle that is + // different from the end intervals. + if (start_index + 1 == end_index) { + EmitDoubleBoundaryTest(masm, first, last, fall_through, even_label, odd_label); + return; + } + + // It's not worth using table lookup if there are very few intervals in the + // character class. + if (end_index - start_index <= 6) { + // It is faster to test for individual characters, so we look for those + // first, then try arbitrary ranges in the second round. + static int kNoCutIndex = -1; + int cut = kNoCutIndex; + for (int i = start_index; i < end_index; i++) { + if (ranges[i] == ranges[i + 1] - 1) { + cut = i; + break; + } + } + if (cut == kNoCutIndex) cut = start_index; + CutOutRange(masm, ranges, start_index, end_index, cut, even_label, odd_label); + JS_ASSERT(end_index - start_index >= 2); + GenerateBranches(masm, + ranges, + start_index + 1, + end_index - 1, + min_char, + max_char, + fall_through, + even_label, + odd_label); + return; + } + + // If there are a lot of intervals in the regexp, then we will use tables to + // determine whether the character is inside or outside the character class. + static const int kBits = RegExpMacroAssembler::kTableSizeBits; + + if ((max_char >> kBits) == (min_char >> kBits)) { + EmitUseLookupTable(masm, + ranges, + start_index, + end_index, + min_char, + fall_through, + even_label, + odd_label); + return; + } + + if ((min_char >> kBits) != (first >> kBits)) { + masm->CheckCharacterLT(first, odd_label); + GenerateBranches(masm, + ranges, + start_index + 1, + end_index, + first, + max_char, + fall_through, + odd_label, + even_label); + return; + } + + int new_start_index = 0; + int new_end_index = 0; + int border = 0; + + SplitSearchSpace(ranges, + start_index, + end_index, + &new_start_index, + &new_end_index, + &border); + + jit::Label handle_rest; + jit::Label* above = &handle_rest; + if (border == last + 1) { + // We didn't find any section that started after the limit, so everything + // above the border is one of the terminal labels. + above = (end_index & 1) != (start_index & 1) ? odd_label : even_label; + JS_ASSERT(new_end_index == end_index - 1); + } + + JS_ASSERT(start_index <= new_end_index); + JS_ASSERT(new_start_index <= end_index); + JS_ASSERT(start_index < new_start_index); + JS_ASSERT(new_end_index < end_index); + JS_ASSERT(new_end_index + 1 == new_start_index || + (new_end_index + 2 == new_start_index && + border == ranges[new_end_index + 1])); + JS_ASSERT(min_char < border - 1); + JS_ASSERT(border < max_char); + JS_ASSERT(ranges[new_end_index] < border); + JS_ASSERT(border < ranges[new_start_index] || + (border == ranges[new_start_index] && + new_start_index == end_index && + new_end_index == end_index - 1 && + border == last + 1)); + JS_ASSERT(new_start_index == 0 || border >= ranges[new_start_index - 1]); + + masm->CheckCharacterGT(border - 1, above); + jit::Label dummy; + GenerateBranches(masm, + ranges, + start_index, + new_end_index, + min_char, + border - 1, + &dummy, + even_label, + odd_label); + if (handle_rest.used()) { + masm->Bind(&handle_rest); + bool flip = (new_start_index & 1) != (start_index & 1); + GenerateBranches(masm, + ranges, + new_start_index, + end_index, + border, + max_char, + &dummy, + flip ? odd_label : even_label, + flip ? even_label : odd_label); + } +} + +static void +EmitCharClass(LifoAlloc *alloc, + RegExpMacroAssembler* macro_assembler, + RegExpCharacterClass* cc, + bool ascii, + jit::Label* on_failure, + int cp_offset, + bool check_offset, + bool preloaded) +{ + CharacterRangeVector &ranges = cc->ranges(alloc); + if (!CharacterRange::IsCanonical(ranges)) { + CharacterRange::Canonicalize(ranges); + } + + int max_char = MaximumCharacter(ascii); + int range_count = ranges.length(); + + int last_valid_range = range_count - 1; + while (last_valid_range >= 0) { + CharacterRange& range = ranges[last_valid_range]; + if (range.from() <= max_char) { + break; + } + last_valid_range--; + } + + if (last_valid_range < 0) { + if (!cc->is_negated()) { + macro_assembler->JumpOrBacktrack(on_failure); + } + if (check_offset) { + macro_assembler->CheckPosition(cp_offset, on_failure); + } + return; + } + + if (last_valid_range == 0 && + ranges[0].IsEverything(max_char)) { + if (cc->is_negated()) { + macro_assembler->JumpOrBacktrack(on_failure); + } else { + // This is a common case hit by non-anchored expressions. + if (check_offset) { + macro_assembler->CheckPosition(cp_offset, on_failure); + } + } + return; + } + if (last_valid_range == 0 && + !cc->is_negated() && + ranges[0].IsEverything(max_char)) { + // This is a common case hit by non-anchored expressions. + if (check_offset) { + macro_assembler->CheckPosition(cp_offset, on_failure); + } + return; + } + + if (!preloaded) { + macro_assembler->LoadCurrentCharacter(cp_offset, on_failure, check_offset); + } + + if (cc->is_standard(alloc) && + macro_assembler->CheckSpecialCharacterClass(cc->standard_type(), + on_failure)) { + return; + } + + // A new list with ascending entries. Each entry is a code unit + // where there is a boundary between code units that are part of + // the class and code units that are not. Normally we insert an + // entry at zero which goes to the failure label, but if there + // was already one there we fall through for success on that entry. + // Subsequent entries have alternating meaning (success/failure). + RangeBoundaryVector *range_boundaries = + alloc->newInfallible(*alloc); + + bool zeroth_entry_is_failure = !cc->is_negated(); + + range_boundaries->reserve(last_valid_range); + for (int i = 0; i <= last_valid_range; i++) { + CharacterRange& range = ranges[i]; + if (range.from() == 0) { + JS_ASSERT(i == 0); + zeroth_entry_is_failure = !zeroth_entry_is_failure; + } else { + range_boundaries->append(range.from()); + } + range_boundaries->append(range.to() + 1); + } + int end_index = range_boundaries->length() - 1; + if ((*range_boundaries)[end_index] > max_char) + end_index--; + + jit::Label fall_through; + GenerateBranches(macro_assembler, + *range_boundaries, + 0, // start_index. + end_index, + 0, // min_char. + max_char, + &fall_through, + zeroth_entry_is_failure ? &fall_through : on_failure, + zeroth_entry_is_failure ? on_failure : &fall_through); + macro_assembler->Bind(&fall_through); +} + +typedef bool EmitCharacterFunction(RegExpCompiler* compiler, + jschar c, + jit::Label* on_failure, + int cp_offset, + bool check, + bool preloaded); + +static inline bool +EmitSimpleCharacter(RegExpCompiler* compiler, + jschar c, + jit::Label* on_failure, + int cp_offset, + bool check, + bool preloaded) +{ + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + bool bound_checked = false; + if (!preloaded) { + assembler->LoadCurrentCharacter(cp_offset, on_failure, check); + bound_checked = true; + } + assembler->CheckNotCharacter(c, on_failure); + return bound_checked; +} + +// Only emits non-letters (things that don't have case). Only used for case +// independent matches. +static inline bool +EmitAtomNonLetter(RegExpCompiler* compiler, + jschar c, + jit::Label* on_failure, + int cp_offset, + bool check, + bool preloaded) +{ + RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); + bool ascii = compiler->ascii(); + jschar chars[kEcma262UnCanonicalizeMaxWidth]; + int length = GetCaseIndependentLetters(c, ascii, chars); + if (length < 1) { + // This can't match. Must be an ASCII subject and a non-ASCII character. + // We do not need to do anything since the ASCII pass already handled this. + return false; // Bounds not checked. + } + bool checked = false; + // We handle the length > 1 case in a later pass. + if (length == 1) { + if (ascii && c > kMaxOneByteCharCode) { + // Can't match - see above. + return false; // Bounds not checked. + } + if (!preloaded) { + macro_assembler->LoadCurrentCharacter(cp_offset, on_failure, check); + checked = check; + } + macro_assembler->CheckNotCharacter(c, on_failure); + } + return checked; +} + +static bool +ShortCutEmitCharacterPair(RegExpMacroAssembler* macro_assembler, + bool ascii, + jschar c1, + jschar c2, + jit::Label* on_failure) +{ + jschar char_mask = MaximumCharacter(ascii); + + JS_ASSERT(c1 != c2); + if (c1 > c2) { + jschar tmp = c1; + c1 = c2; + c2 = tmp; + } + + jschar exor = c1 ^ c2; + // Check whether exor has only one bit set. + if (((exor - 1) & exor) == 0) { + // If c1 and c2 differ only by one bit. + jschar mask = char_mask ^ exor; + macro_assembler->CheckNotCharacterAfterAnd(c1, mask, on_failure); + return true; + } + + jschar diff = c2 - c1; + if (((diff - 1) & diff) == 0 && c1 >= diff) { + // If the characters differ by 2^n but don't differ by one bit then + // subtract the difference from the found character, then do the or + // trick. We avoid the theoretical case where negative numbers are + // involved in order to simplify code generation. + jschar mask = char_mask ^ diff; + macro_assembler->CheckNotCharacterAfterMinusAnd(c1 - diff, + diff, + mask, + on_failure); + return true; + } + return false; +} + +// Only emits letters (things that have case). Only used for case independent +// matches. +static inline bool +EmitAtomLetter(RegExpCompiler* compiler, + jschar c, + jit::Label* on_failure, + int cp_offset, + bool check, + bool preloaded) +{ + RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); + bool ascii = compiler->ascii(); + jschar chars[kEcma262UnCanonicalizeMaxWidth]; + int length = GetCaseIndependentLetters(c, ascii, chars); + if (length <= 1) return false; + // We may not need to check against the end of the input string + // if this character lies before a character that matched. + if (!preloaded) + macro_assembler->LoadCurrentCharacter(cp_offset, on_failure, check); + jit::Label ok; + JS_ASSERT(kEcma262UnCanonicalizeMaxWidth == 4); + switch (length) { + case 2: { + if (ShortCutEmitCharacterPair(macro_assembler, + ascii, + chars[0], + chars[1], + on_failure)) { + } else { + macro_assembler->CheckCharacter(chars[0], &ok); + macro_assembler->CheckNotCharacter(chars[1], on_failure); + macro_assembler->Bind(&ok); + } + break; + } + case 4: + macro_assembler->CheckCharacter(chars[3], &ok); + // Fall through! + case 3: + macro_assembler->CheckCharacter(chars[0], &ok); + macro_assembler->CheckCharacter(chars[1], &ok); + macro_assembler->CheckNotCharacter(chars[2], on_failure); + macro_assembler->Bind(&ok); + break; + default: + MOZ_ASSUME_UNREACHABLE("Bad length"); + break; + } + return true; +} + +// We call this repeatedly to generate code for each pass over the text node. +// The passes are in increasing order of difficulty because we hope one +// of the first passes will fail in which case we are saved the work of the +// later passes. for example for the case independent regexp /%[asdfghjkl]a/ +// we will check the '%' in the first pass, the case independent 'a' in the +// second pass and the character class in the last pass. +// +// The passes are done from right to left, so for example to test for /bar/ +// we will first test for an 'r' with offset 2, then an 'a' with offset 1 +// and then a 'b' with offset 0. This means we can avoid the end-of-input +// bounds check most of the time. In the example we only need to check for +// end-of-input when loading the putative 'r'. +// +// A slight complication involves the fact that the first character may already +// be fetched into a register by the previous node. In this case we want to +// do the test for that character first. We do this in separate passes. The +// 'preloaded' argument indicates that we are doing such a 'pass'. If such a +// pass has been performed then subsequent passes will have true in +// first_element_checked to indicate that that character does not need to be +// checked again. +// +// In addition to all this we are passed a Trace, which can +// contain an AlternativeGeneration object. In this AlternativeGeneration +// object we can see details of any quick check that was already passed in +// order to get to the code we are now generating. The quick check can involve +// loading characters, which means we do not need to recheck the bounds +// up to the limit the quick check already checked. In addition the quick +// check can have involved a mask and compare operation which may simplify +// or obviate the need for further checks at some character positions. +void +TextNode::TextEmitPass(RegExpCompiler* compiler, + TextEmitPassType pass, + bool preloaded, + Trace* trace, + bool first_element_checked, + int* checked_up_to) +{ + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + bool ascii = compiler->ascii(); + jit::Label* backtrack = trace->backtrack(); + QuickCheckDetails* quick_check = trace->quick_check_performed(); + int element_count = elements().length(); + for (int i = preloaded ? 0 : element_count - 1; i >= 0; i--) { + TextElement elm = elements()[i]; + int cp_offset = trace->cp_offset() + elm.cp_offset(); + if (elm.text_type() == TextElement::ATOM) { + const CharacterVector &quarks = elm.atom()->data(); + for (int j = preloaded ? 0 : quarks.length() - 1; j >= 0; j--) { + if (first_element_checked && i == 0 && j == 0) continue; + if (DeterminedAlready(quick_check, elm.cp_offset() + j)) continue; + EmitCharacterFunction* emit_function = nullptr; + switch (pass) { + case NON_ASCII_MATCH: + JS_ASSERT(ascii); + if (quarks[j] > kMaxOneByteCharCode) { + assembler->JumpOrBacktrack(backtrack); + return; + } + break; + case NON_LETTER_CHARACTER_MATCH: + emit_function = &EmitAtomNonLetter; + break; + case SIMPLE_CHARACTER_MATCH: + emit_function = &EmitSimpleCharacter; + break; + case CASE_CHARACTER_MATCH: + emit_function = &EmitAtomLetter; + break; + default: + break; + } + if (emit_function != nullptr) { + bool bound_checked = emit_function(compiler, + quarks[j], + backtrack, + cp_offset + j, + *checked_up_to < cp_offset + j, + preloaded); + if (bound_checked) UpdateBoundsCheck(cp_offset + j, checked_up_to); + } + } + } else { + JS_ASSERT(TextElement::CHAR_CLASS == elm.text_type()); + if (pass == CHARACTER_CLASS_MATCH) { + if (first_element_checked && i == 0) continue; + if (DeterminedAlready(quick_check, elm.cp_offset())) continue; + RegExpCharacterClass* cc = elm.char_class(); + EmitCharClass(alloc(), + assembler, + cc, + ascii, + backtrack, + cp_offset, + *checked_up_to < cp_offset, + preloaded); + UpdateBoundsCheck(cp_offset, checked_up_to); + } + } + } +} + +int +TextNode::Length() +{ + TextElement elm = elements()[elements().length() - 1]; + JS_ASSERT(elm.cp_offset() >= 0); + return elm.cp_offset() + elm.length(); +} + +bool +TextNode::SkipPass(int int_pass, bool ignore_case) +{ + TextEmitPassType pass = static_cast(int_pass); + if (ignore_case) + return pass == SIMPLE_CHARACTER_MATCH; + return pass == NON_LETTER_CHARACTER_MATCH || pass == CASE_CHARACTER_MATCH; +} + +// This generates the code to match a text node. A text node can contain +// straight character sequences (possibly to be matched in a case-independent +// way) and character classes. For efficiency we do not do this in a single +// pass from left to right. Instead we pass over the text node several times, +// emitting code for some character positions every time. See the comment on +// TextEmitPass for details. +void +TextNode::Emit(RegExpCompiler* compiler, Trace* trace) +{ + LimitResult limit_result = LimitVersions(compiler, trace); + if (limit_result == DONE) return; + JS_ASSERT(limit_result == CONTINUE); + + if (trace->cp_offset() + Length() > RegExpMacroAssembler::kMaxCPOffset) { + compiler->SetRegExpTooBig(); + return; + } + + if (compiler->ascii()) { + int dummy = 0; + TextEmitPass(compiler, NON_ASCII_MATCH, false, trace, false, &dummy); + } + + bool first_elt_done = false; + int bound_checked_to = trace->cp_offset() - 1; + bound_checked_to += trace->bound_checked_up_to(); + + // If a character is preloaded into the current character register then + // check that now. + if (trace->characters_preloaded() == 1) { + for (int pass = kFirstRealPass; pass <= kLastPass; pass++) { + if (!SkipPass(pass, compiler->ignore_case())) { + TextEmitPass(compiler, + static_cast(pass), + true, + trace, + false, + &bound_checked_to); + } + } + first_elt_done = true; + } + + for (int pass = kFirstRealPass; pass <= kLastPass; pass++) { + if (!SkipPass(pass, compiler->ignore_case())) { + TextEmitPass(compiler, + static_cast(pass), + false, + trace, + first_elt_done, + &bound_checked_to); + } + } + + Trace successor_trace(*trace); + successor_trace.set_at_start(false); + successor_trace.AdvanceCurrentPositionInTrace(Length(), compiler); + RecursionCheck rc(compiler); + on_success()->Emit(compiler, &successor_trace); +} + +void +LoopChoiceNode::Emit(RegExpCompiler* compiler, Trace* trace) +{ + RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); + if (trace->stop_node() == this) { + int text_length = + GreedyLoopTextLengthForAlternative(&alternatives()[0]); + JS_ASSERT(text_length != kNodeIsTooComplexForGreedyLoops); + // Update the counter-based backtracking info on the stack. This is an + // optimization for greedy loops (see below). + JS_ASSERT(trace->cp_offset() == text_length); + macro_assembler->AdvanceCurrentPosition(text_length); + macro_assembler->JumpOrBacktrack(trace->loop_label()); + return; + } + JS_ASSERT(trace->stop_node() == nullptr); + if (!trace->is_trivial()) { + trace->Flush(compiler, this); + return; + } + ChoiceNode::Emit(compiler, trace); +} + +/* Code generation for choice nodes. + * + * We generate quick checks that do a mask and compare to eliminate a + * choice. If the quick check succeeds then it jumps to the continuation to + * do slow checks and check subsequent nodes. If it fails (the common case) + * it falls through to the next choice. + * + * Here is the desired flow graph. Nodes directly below each other imply + * fallthrough. Alternatives 1 and 2 have quick checks. Alternative + * 3 doesn't have a quick check so we have to call the slow check. + * Nodes are marked Qn for quick checks and Sn for slow checks. The entire + * regexp continuation is generated directly after the Sn node, up to the + * next JumpOrBacktrack if we decide to reuse some already generated code. Some + * nodes expect preload_characters to be preloaded into the current + * character register. R nodes do this preloading. Vertices are marked + * F for failures and S for success (possible success in the case of quick + * nodes). L, V, < and > are used as arrow heads. + * + * ----------> R + * | + * V + * Q1 -----> S1 + * | S / + * F| / + * | F/ + * | / + * | R + * | / + * V L + * Q2 -----> S2 + * | S / + * F| / + * | F/ + * | / + * | R + * | / + * V L + * S3 + * | + * F| + * | + * R + * | + * backtrack V + * <----------Q4 + * \ F | + * \ |S + * \ F V + * \-----S4 + * + * For greedy loops we reverse our expectation and expect to match rather + * than fail. Therefore we want the loop code to look like this (U is the + * unwind code that steps back in the greedy loop). The following alternatives + * look the same as above. + * _____ + * / \ + * V | + * ----------> S1 | + * /| | + * / |S | + * F/ \_____/ + * / + * |<----------- + * | \ + * V \ + * Q2 ---> S2 \ + * | S / | + * F| / | + * | F/ | + * | / | + * | R | + * | / | + * F VL | + * <------U | + * back |S | + * \______________/ + */ + +// This class is used when generating the alternatives in a choice node. It +// records the way the alternative is being code generated. +class irregexp::AlternativeGeneration +{ + public: + AlternativeGeneration() + : possible_success(), + expects_preload(false), + after(), + quick_check_details() + {} + + jit::Label possible_success; + bool expects_preload; + jit::Label after; + QuickCheckDetails quick_check_details; +}; + +void +ChoiceNode::GenerateGuard(RegExpMacroAssembler* macro_assembler, + Guard* guard, Trace* trace) +{ + switch (guard->op()) { + case Guard::LT: + JS_ASSERT(!trace->mentions_reg(guard->reg())); + macro_assembler->IfRegisterGE(guard->reg(), + guard->value(), + trace->backtrack()); + break; + case Guard::GEQ: + JS_ASSERT(!trace->mentions_reg(guard->reg())); + macro_assembler->IfRegisterLT(guard->reg(), + guard->value(), + trace->backtrack()); + break; + } +} + +int +ChoiceNode::CalculatePreloadCharacters(RegExpCompiler* compiler, int eats_at_least) +{ + int preload_characters = Min(4, eats_at_least); + if (compiler->macro_assembler()->CanReadUnaligned()) { + bool ascii = compiler->ascii(); + if (ascii) { + if (preload_characters > 4) + preload_characters = 4; + // We can't preload 3 characters because there is no machine instruction + // to do that. We can't just load 4 because we could be reading + // beyond the end of the string, which could cause a memory fault. + if (preload_characters == 3) + preload_characters = 2; + } else { + if (preload_characters > 2) + preload_characters = 2; + } + } else { + if (preload_characters > 1) + preload_characters = 1; + } + return preload_characters; +} + +RegExpNode * +TextNode::GetSuccessorOfOmnivorousTextNode(RegExpCompiler* compiler) +{ + if (elements().length() != 1) + return nullptr; + + TextElement elm = elements()[0]; + if (elm.text_type() != TextElement::CHAR_CLASS) + return nullptr; + + RegExpCharacterClass* node = elm.char_class(); + CharacterRangeVector &ranges = node->ranges(alloc()); + + if (!CharacterRange::IsCanonical(ranges)) + CharacterRange::Canonicalize(ranges); + + if (node->is_negated()) + return ranges.length() == 0 ? on_success() : nullptr; + + if (ranges.length() != 1) + return nullptr; + + uint32_t max_char = MaximumCharacter(compiler->ascii()); + return ranges[0].IsEverything(max_char) ? on_success() : nullptr; +} + +// Finds the fixed match length of a sequence of nodes that goes from +// this alternative and back to this choice node. If there are variable +// length nodes or other complications in the way then return a sentinel +// value indicating that a greedy loop cannot be constructed. +int +ChoiceNode::GreedyLoopTextLengthForAlternative(GuardedAlternative* alternative) +{ + int length = 0; + RegExpNode* node = alternative->node(); + // Later we will generate code for all these text nodes using recursion + // so we have to limit the max number. + int recursion_depth = 0; + while (node != this) { + if (recursion_depth++ > RegExpCompiler::kMaxRecursion) { + return kNodeIsTooComplexForGreedyLoops; + } + int node_length = node->GreedyLoopTextLength(); + if (node_length == kNodeIsTooComplexForGreedyLoops) { + return kNodeIsTooComplexForGreedyLoops; + } + length += node_length; + SeqRegExpNode* seq_node = static_cast(node); + node = seq_node->on_success(); + } + return length; +} + +// Creates a list of AlternativeGenerations. If the list has a reasonable +// size then it is on the stack, otherwise the excess is on the heap. +class AlternativeGenerationList +{ + public: + AlternativeGenerationList(LifoAlloc *alloc, size_t count) + : alt_gens_(*alloc) + { + alt_gens_.reserve(count); + for (size_t i = 0; i < count && i < kAFew; i++) + alt_gens_.append(a_few_alt_gens_ + i); + for (size_t i = kAFew; i < count; i++) + alt_gens_.append(js_new()); + } + + ~AlternativeGenerationList() { + for (size_t i = kAFew; i < alt_gens_.length(); i++) { + js_delete(alt_gens_[i]); + alt_gens_[i] = nullptr; + } + } + + AlternativeGeneration *at(int i) { + return alt_gens_[i]; + } + + private: + static const size_t kAFew = 10; + js::Vector > alt_gens_; + AlternativeGeneration a_few_alt_gens_[kAFew]; +}; + +void +ChoiceNode::Emit(RegExpCompiler* compiler, Trace* trace) +{ + RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); + size_t choice_count = alternatives().length(); +#ifdef DEBUG + for (size_t i = 0; i < choice_count - 1; i++) { + const GuardedAlternative &alternative = alternatives()[i]; + const GuardVector *guards = alternative.guards(); + if (guards) { + for (size_t j = 0; j < guards->length(); j++) + JS_ASSERT(!trace->mentions_reg((*guards)[j]->reg())); + } + } +#endif + + LimitResult limit_result = LimitVersions(compiler, trace); + if (limit_result == DONE) return; + JS_ASSERT(limit_result == CONTINUE); + + int new_flush_budget = trace->flush_budget() / choice_count; + if (trace->flush_budget() == 0 && trace->actions() != nullptr) { + trace->Flush(compiler, this); + return; + } + + RecursionCheck rc(compiler); + + Trace* current_trace = trace; + + int text_length = GreedyLoopTextLengthForAlternative(&alternatives()[0]); + bool greedy_loop = false; + jit::Label greedy_loop_label; + Trace counter_backtrack_trace; + counter_backtrack_trace.set_backtrack(&greedy_loop_label); + if (not_at_start()) counter_backtrack_trace.set_at_start(false); + + if (choice_count > 1 && text_length != kNodeIsTooComplexForGreedyLoops) { + // Here we have special handling for greedy loops containing only text nodes + // and other simple nodes. These are handled by pushing the current + // position on the stack and then incrementing the current position each + // time around the switch. On backtrack we decrement the current position + // and check it against the pushed value. This avoids pushing backtrack + // information for each iteration of the loop, which could take up a lot of + // space. + greedy_loop = true; + JS_ASSERT(trace->stop_node() == nullptr); + macro_assembler->PushCurrentPosition(); + current_trace = &counter_backtrack_trace; + jit::Label greedy_match_failed; + Trace greedy_match_trace; + if (not_at_start()) greedy_match_trace.set_at_start(false); + greedy_match_trace.set_backtrack(&greedy_match_failed); + jit::Label loop_label; + macro_assembler->Bind(&loop_label); + greedy_match_trace.set_stop_node(this); + greedy_match_trace.set_loop_label(&loop_label); + alternatives()[0].node()->Emit(compiler, &greedy_match_trace); + macro_assembler->Bind(&greedy_match_failed); + } + + jit::Label second_choice; // For use in greedy matches. + macro_assembler->Bind(&second_choice); + + size_t first_normal_choice = greedy_loop ? 1 : 0; + + bool not_at_start = current_trace->at_start() == Trace::FALSE_VALUE; + const int kEatsAtLeastNotYetInitialized = -1; + int eats_at_least = kEatsAtLeastNotYetInitialized; + + bool skip_was_emitted = false; + + if (!greedy_loop && choice_count == 2) { + GuardedAlternative alt1 = alternatives()[1]; + if (!alt1.guards() || alt1.guards()->length() == 0) { + RegExpNode* eats_anything_node = alt1.node(); + if (eats_anything_node->GetSuccessorOfOmnivorousTextNode(compiler) == this) { + // At this point we know that we are at a non-greedy loop that will eat + // any character one at a time. Any non-anchored regexp has such a + // loop prepended to it in order to find where it starts. We look for + // a pattern of the form ...abc... where we can look 6 characters ahead + // and step forwards 3 if the character is not one of abc. Abc need + // not be atoms, they can be any reasonably limited character class or + // small alternation. + JS_ASSERT(trace->is_trivial()); // This is the case on LoopChoiceNodes. + BoyerMooreLookahead* lookahead = bm_info(not_at_start); + if (lookahead == nullptr) { + eats_at_least = Min(kMaxLookaheadForBoyerMoore, + EatsAtLeast(kMaxLookaheadForBoyerMoore, + kRecursionBudget, + not_at_start)); + if (eats_at_least >= 1) { + BoyerMooreLookahead* bm = + alloc()->newInfallible(alloc(), eats_at_least, compiler); + GuardedAlternative alt0 = alternatives()[0]; + alt0.node()->FillInBMInfo(0, kRecursionBudget, bm, not_at_start); + skip_was_emitted = bm->EmitSkipInstructions(macro_assembler); + } + } else { + skip_was_emitted = lookahead->EmitSkipInstructions(macro_assembler); + } + } + } + } + + if (eats_at_least == kEatsAtLeastNotYetInitialized) { + // Save some time by looking at most one machine word ahead. + eats_at_least = + EatsAtLeast(compiler->ascii() ? 4 : 2, kRecursionBudget, not_at_start); + } + int preload_characters = CalculatePreloadCharacters(compiler, eats_at_least); + + bool preload_is_current = !skip_was_emitted && + (current_trace->characters_preloaded() == preload_characters); + bool preload_has_checked_bounds = preload_is_current; + + AlternativeGenerationList alt_gens(alloc(), choice_count); + + // For now we just call all choices one after the other. The idea ultimately + // is to use the Dispatch table to try only the relevant ones. + for (size_t i = first_normal_choice; i < choice_count; i++) { + GuardedAlternative alternative = alternatives()[i]; + AlternativeGeneration* alt_gen = alt_gens.at(i); + alt_gen->quick_check_details.set_characters(preload_characters); + const GuardVector *guards = alternative.guards(); + Trace new_trace(*current_trace); + new_trace.set_characters_preloaded(preload_is_current ? + preload_characters : + 0); + if (preload_has_checked_bounds) { + new_trace.set_bound_checked_up_to(preload_characters); + } + new_trace.quick_check_performed()->Clear(); + if (not_at_start_) new_trace.set_at_start(Trace::FALSE_VALUE); + alt_gen->expects_preload = preload_is_current; + bool generate_full_check_inline = false; + if (try_to_emit_quick_check_for_alternative(i) && + alternative.node()->EmitQuickCheck(compiler, + &new_trace, + preload_has_checked_bounds, + &alt_gen->possible_success, + &alt_gen->quick_check_details, + i < choice_count - 1)) { + // Quick check was generated for this choice. + preload_is_current = true; + preload_has_checked_bounds = true; + // On the last choice in the ChoiceNode we generated the quick + // check to fall through on possible success. So now we need to + // generate the full check inline. + if (i == choice_count - 1) { + macro_assembler->Bind(&alt_gen->possible_success); + new_trace.set_quick_check_performed(&alt_gen->quick_check_details); + new_trace.set_characters_preloaded(preload_characters); + new_trace.set_bound_checked_up_to(preload_characters); + generate_full_check_inline = true; + } + } else if (alt_gen->quick_check_details.cannot_match()) { + if (i == choice_count - 1 && !greedy_loop) { + macro_assembler->JumpOrBacktrack(trace->backtrack()); + } + continue; + } else { + // No quick check was generated. Put the full code here. + // If this is not the first choice then there could be slow checks from + // previous cases that go here when they fail. There's no reason to + // insist that they preload characters since the slow check we are about + // to generate probably can't use it. + if (i != first_normal_choice) { + alt_gen->expects_preload = false; + new_trace.InvalidateCurrentCharacter(); + } + if (i < choice_count - 1) { + new_trace.set_backtrack(&alt_gen->after); + } + generate_full_check_inline = true; + } + if (generate_full_check_inline) { + if (new_trace.actions() != nullptr) + new_trace.set_flush_budget(new_flush_budget); + if (guards) { + for (size_t j = 0; j < guards->length(); j++) + GenerateGuard(macro_assembler, (*guards)[j], &new_trace); + } + alternative.node()->Emit(compiler, &new_trace); + preload_is_current = false; + } + macro_assembler->Bind(&alt_gen->after); + } + if (greedy_loop) { + macro_assembler->Bind(&greedy_loop_label); + // If we have unwound to the bottom then backtrack. + macro_assembler->CheckGreedyLoop(trace->backtrack()); + // Otherwise try the second priority at an earlier position. + macro_assembler->AdvanceCurrentPosition(-text_length); + macro_assembler->JumpOrBacktrack(&second_choice); + } + + // At this point we need to generate slow checks for the alternatives where + // the quick check was inlined. We can recognize these because the associated + // label was bound. + for (size_t i = first_normal_choice; i < choice_count - 1; i++) { + AlternativeGeneration* alt_gen = alt_gens.at(i); + Trace new_trace(*current_trace); + // If there are actions to be flushed we have to limit how many times + // they are flushed. Take the budget of the parent trace and distribute + // it fairly amongst the children. + if (new_trace.actions() != nullptr) { + new_trace.set_flush_budget(new_flush_budget); + } + EmitOutOfLineContinuation(compiler, + &new_trace, + alternatives()[i], + alt_gen, + preload_characters, + alt_gens.at(i + 1)->expects_preload); + } +} + +void +ChoiceNode::EmitOutOfLineContinuation(RegExpCompiler* compiler, + Trace* trace, + GuardedAlternative alternative, + AlternativeGeneration* alt_gen, + int preload_characters, + bool next_expects_preload) +{ + if (!alt_gen->possible_success.used()) + return; + + RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); + macro_assembler->Bind(&alt_gen->possible_success); + Trace out_of_line_trace(*trace); + out_of_line_trace.set_characters_preloaded(preload_characters); + out_of_line_trace.set_quick_check_performed(&alt_gen->quick_check_details); + if (not_at_start_) out_of_line_trace.set_at_start(Trace::FALSE_VALUE); + const GuardVector *guards = alternative.guards(); + if (next_expects_preload) { + jit::Label reload_current_char; + out_of_line_trace.set_backtrack(&reload_current_char); + if (guards) { + for (size_t j = 0; j < guards->length(); j++) + GenerateGuard(macro_assembler, (*guards)[j], &out_of_line_trace); + } + alternative.node()->Emit(compiler, &out_of_line_trace); + macro_assembler->Bind(&reload_current_char); + // Reload the current character, since the next quick check expects that. + // We don't need to check bounds here because we only get into this + // code through a quick check which already did the checked load. + macro_assembler->LoadCurrentCharacter(trace->cp_offset(), + nullptr, + false, + preload_characters); + macro_assembler->JumpOrBacktrack(&(alt_gen->after)); + } else { + out_of_line_trace.set_backtrack(&(alt_gen->after)); + if (guards) { + for (size_t j = 0; j < guards->length(); j++) + GenerateGuard(macro_assembler, (*guards)[j], &out_of_line_trace); + } + alternative.node()->Emit(compiler, &out_of_line_trace); + } +} + +void +ActionNode::Emit(RegExpCompiler* compiler, Trace* trace) +{ + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + LimitResult limit_result = LimitVersions(compiler, trace); + if (limit_result == DONE) return; + JS_ASSERT(limit_result == CONTINUE); + + RecursionCheck rc(compiler); + + switch (action_type_) { + case STORE_POSITION: { + Trace::DeferredCapture + new_capture(data_.u_position_register.reg, + data_.u_position_register.is_capture, + trace); + Trace new_trace = *trace; + new_trace.add_action(&new_capture); + on_success()->Emit(compiler, &new_trace); + break; + } + case INCREMENT_REGISTER: { + Trace::DeferredIncrementRegister + new_increment(data_.u_increment_register.reg); + Trace new_trace = *trace; + new_trace.add_action(&new_increment); + on_success()->Emit(compiler, &new_trace); + break; + } + case SET_REGISTER: { + Trace::DeferredSetRegister + new_set(data_.u_store_register.reg, data_.u_store_register.value); + Trace new_trace = *trace; + new_trace.add_action(&new_set); + on_success()->Emit(compiler, &new_trace); + break; + } + case CLEAR_CAPTURES: { + Trace::DeferredClearCaptures + new_capture(Interval(data_.u_clear_captures.range_from, + data_.u_clear_captures.range_to)); + Trace new_trace = *trace; + new_trace.add_action(&new_capture); + on_success()->Emit(compiler, &new_trace); + break; + } + case BEGIN_SUBMATCH: + if (!trace->is_trivial()) { + trace->Flush(compiler, this); + } else { + assembler->WriteCurrentPositionToRegister(data_.u_submatch.current_position_register, 0); + assembler->WriteBacktrackStackPointerToRegister(data_.u_submatch.stack_pointer_register); + on_success()->Emit(compiler, trace); + } + break; + case EMPTY_MATCH_CHECK: { + int start_pos_reg = data_.u_empty_match_check.start_register; + int stored_pos = 0; + int rep_reg = data_.u_empty_match_check.repetition_register; + bool has_minimum = (rep_reg != RegExpCompiler::kNoRegister); + bool know_dist = trace->GetStoredPosition(start_pos_reg, &stored_pos); + if (know_dist && !has_minimum && stored_pos == trace->cp_offset()) { + // If we know we haven't advanced and there is no minimum we + // can just backtrack immediately. + assembler->JumpOrBacktrack(trace->backtrack()); + } else if (know_dist && stored_pos < trace->cp_offset()) { + // If we know we've advanced we can generate the continuation + // immediately. + on_success()->Emit(compiler, trace); + } else if (!trace->is_trivial()) { + trace->Flush(compiler, this); + } else { + jit::Label skip_empty_check; + // If we have a minimum number of repetitions we check the current + // number first and skip the empty check if it's not enough. + if (has_minimum) { + int limit = data_.u_empty_match_check.repetition_limit; + assembler->IfRegisterLT(rep_reg, limit, &skip_empty_check); + } + // If the match is empty we bail out, otherwise we fall through + // to the on-success continuation. + assembler->IfRegisterEqPos(data_.u_empty_match_check.start_register, + trace->backtrack()); + assembler->Bind(&skip_empty_check); + on_success()->Emit(compiler, trace); + } + break; + } + case POSITIVE_SUBMATCH_SUCCESS: { + if (!trace->is_trivial()) { + trace->Flush(compiler, this); + return; + } + assembler->ReadCurrentPositionFromRegister(data_.u_submatch.current_position_register); + assembler->ReadBacktrackStackPointerFromRegister(data_.u_submatch.stack_pointer_register); + int clear_register_count = data_.u_submatch.clear_register_count; + if (clear_register_count == 0) { + on_success()->Emit(compiler, trace); + return; + } + int clear_registers_from = data_.u_submatch.clear_register_from; + jit::Label clear_registers_backtrack; + Trace new_trace = *trace; + new_trace.set_backtrack(&clear_registers_backtrack); + on_success()->Emit(compiler, &new_trace); + + assembler->Bind(&clear_registers_backtrack); + int clear_registers_to = clear_registers_from + clear_register_count - 1; + assembler->ClearRegisters(clear_registers_from, clear_registers_to); + + JS_ASSERT(trace->backtrack() == nullptr); + assembler->Backtrack(); + return; + } + default: + MOZ_ASSUME_UNREACHABLE("Bad action"); + } +} + +void +BackReferenceNode::Emit(RegExpCompiler* compiler, Trace* trace) +{ + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + if (!trace->is_trivial()) { + trace->Flush(compiler, this); + return; + } + + LimitResult limit_result = LimitVersions(compiler, trace); + if (limit_result == DONE) return; + JS_ASSERT(limit_result == CONTINUE); + + RecursionCheck rc(compiler); + + JS_ASSERT(start_reg_ + 1 == end_reg_); + if (compiler->ignore_case()) { + assembler->CheckNotBackReferenceIgnoreCase(start_reg_, + trace->backtrack()); + } else { + assembler->CheckNotBackReference(start_reg_, trace->backtrack()); + } + on_success()->Emit(compiler, trace); +} + +RegExpNode::LimitResult +RegExpNode::LimitVersions(RegExpCompiler* compiler, Trace* trace) +{ + // If we are generating a greedy loop then don't stop and don't reuse code. + if (trace->stop_node() != nullptr) + return CONTINUE; + + RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); + if (trace->is_trivial()) { + if (label()->bound()) { + // We are being asked to generate a generic version, but that's already + // been done so just go to it. + macro_assembler->JumpOrBacktrack(label()); + return DONE; + } + if (compiler->recursion_depth() >= RegExpCompiler::kMaxRecursion) { + // To avoid too deep recursion we push the node to the work queue and just + // generate a goto here. + compiler->AddWork(this); + macro_assembler->JumpOrBacktrack(label()); + return DONE; + } + // Generate generic version of the node and bind the label for later use. + macro_assembler->Bind(label()); + return CONTINUE; + } + + // We are being asked to make a non-generic version. Keep track of how many + // non-generic versions we generate so as not to overdo it. + trace_count_++; + if (trace_count_ < kMaxCopiesCodeGenerated && + compiler->recursion_depth() <= RegExpCompiler::kMaxRecursion) { + return CONTINUE; + } + + // If we get here code has been generated for this node too many times or + // recursion is too deep. Time to switch to a generic version. The code for + // generic versions above can handle deep recursion properly. + trace->Flush(compiler, this); + return DONE; +} + +bool +RegExpNode::EmitQuickCheck(RegExpCompiler* compiler, + Trace* trace, + bool preload_has_checked_bounds, + jit::Label* on_possible_success, + QuickCheckDetails* details, + bool fall_through_on_failure) +{ + if (details->characters() == 0) return false; + GetQuickCheckDetails( + details, compiler, 0, trace->at_start() == Trace::FALSE_VALUE); + if (details->cannot_match()) return false; + if (!details->Rationalize(compiler->ascii())) return false; + JS_ASSERT(details->characters() == 1 || + compiler->macro_assembler()->CanReadUnaligned()); + uint32_t mask = details->mask(); + uint32_t value = details->value(); + + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + + if (trace->characters_preloaded() != details->characters()) { + assembler->LoadCurrentCharacter(trace->cp_offset(), + trace->backtrack(), + !preload_has_checked_bounds, + details->characters()); + } + + bool need_mask = true; + + if (details->characters() == 1) { + // If number of characters preloaded is 1 then we used a byte or 16 bit + // load so the value is already masked down. + uint32_t char_mask = MaximumCharacter(compiler->ascii()); + if ((mask & char_mask) == char_mask) need_mask = false; + mask &= char_mask; + } else { + // For 2-character preloads in ASCII mode or 1-character preloads in + // TWO_BYTE mode we also use a 16 bit load with zero extend. + if (details->characters() == 2 && compiler->ascii()) { + if ((mask & 0xffff) == 0xffff) need_mask = false; + } else if (details->characters() == 1 && !compiler->ascii()) { + if ((mask & 0xffff) == 0xffff) need_mask = false; + } else { + if (mask == 0xffffffff) need_mask = false; + } + } + + if (fall_through_on_failure) { + if (need_mask) { + assembler->CheckCharacterAfterAnd(value, mask, on_possible_success); + } else { + assembler->CheckCharacter(value, on_possible_success); + } + } else { + if (need_mask) { + assembler->CheckNotCharacterAfterAnd(value, mask, trace->backtrack()); + } else { + assembler->CheckNotCharacter(value, trace->backtrack()); + } + } + return true; +} + +void +TextNode::FillInBMInfo(int initial_offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start) +{ + if (initial_offset >= bm->length()) + return; + + int offset = initial_offset; + int max_char = bm->max_char(); + for (size_t i = 0; i < elements().length(); i++) { + if (offset >= bm->length()) { + if (initial_offset == 0) + set_bm_info(not_at_start, bm); + return; + } + TextElement text = elements()[i]; + if (text.text_type() == TextElement::ATOM) { + RegExpAtom* atom = text.atom(); + for (int j = 0; j < atom->length(); j++, offset++) { + if (offset >= bm->length()) { + if (initial_offset == 0) + set_bm_info(not_at_start, bm); + return; + } + jschar character = atom->data()[j]; + if (bm->compiler()->ignore_case()) { + jschar chars[kEcma262UnCanonicalizeMaxWidth]; + int length = GetCaseIndependentLetters(character, + bm->max_char() == kMaxOneByteCharCode, + chars); + for (int j = 0; j < length; j++) + bm->Set(offset, chars[j]); + } else { + if (character <= max_char) bm->Set(offset, character); + } + } + } else { + JS_ASSERT(TextElement::CHAR_CLASS == text.text_type()); + RegExpCharacterClass* char_class = text.char_class(); + const CharacterRangeVector &ranges = char_class->ranges(alloc()); + if (char_class->is_negated()) { + bm->SetAll(offset); + } else { + for (size_t k = 0; k < ranges.length(); k++) { + const CharacterRange &range = ranges[k]; + if (range.from() > max_char) + continue; + int to = Min(max_char, static_cast(range.to())); + bm->SetInterval(offset, Interval(range.from(), to)); + } + } + offset++; + } + } + if (offset >= bm->length()) { + if (initial_offset == 0) set_bm_info(not_at_start, bm); + return; + } + on_success()->FillInBMInfo(offset, + budget - 1, + bm, + true); // Not at start after a text node. + if (initial_offset == 0) + set_bm_info(not_at_start, bm); +} + +// ------------------------------------------------------------------- +// QuickCheckDetails + +// Takes the left-most 1-bit and smears it out, setting all bits to its right. +static inline uint32_t +SmearBitsRight(uint32_t v) +{ + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + return v; +} + +// Here is the meat of GetQuickCheckDetails (see also the comment on the +// super-class in the .h file). +// +// We iterate along the text object, building up for each character a +// mask and value that can be used to test for a quick failure to match. +// The masks and values for the positions will be combined into a single +// machine word for the current character width in order to be used in +// generating a quick check. +void +TextNode::GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start) +{ + JS_ASSERT(characters_filled_in < details->characters()); + int characters = details->characters(); + int char_mask = MaximumCharacter(compiler->ascii()); + + for (size_t k = 0; k < elements().length(); k++) { + TextElement elm = elements()[k]; + if (elm.text_type() == TextElement::ATOM) { + const CharacterVector &quarks = elm.atom()->data(); + for (size_t i = 0; i < (size_t) characters && i < quarks.length(); i++) { + QuickCheckDetails::Position* pos = + details->positions(characters_filled_in); + jschar c = quarks[i]; + if (c > char_mask) { + // If we expect a non-ASCII character from an ASCII string, + // there is no way we can match. Not even case independent + // matching can turn an ASCII character into non-ASCII or + // vice versa. + details->set_cannot_match(); + pos->determines_perfectly = false; + return; + } + if (compiler->ignore_case()) { + jschar chars[kEcma262UnCanonicalizeMaxWidth]; + size_t length = GetCaseIndependentLetters(c, compiler->ascii(), chars); + JS_ASSERT(length != 0); // Can only happen if c > char_mask (see above). + if (length == 1) { + // This letter has no case equivalents, so it's nice and simple + // and the mask-compare will determine definitely whether we have + // a match at this character position. + pos->mask = char_mask; + pos->value = c; + pos->determines_perfectly = true; + } else { + uint32_t common_bits = char_mask; + uint32_t bits = chars[0]; + for (size_t j = 1; j < length; j++) { + uint32_t differing_bits = ((chars[j] & common_bits) ^ bits); + common_bits ^= differing_bits; + bits &= common_bits; + } + // If length is 2 and common bits has only one zero in it then + // our mask and compare instruction will determine definitely + // whether we have a match at this character position. Otherwise + // it can only be an approximate check. + uint32_t one_zero = (common_bits | ~char_mask); + if (length == 2 && ((~one_zero) & ((~one_zero) - 1)) == 0) { + pos->determines_perfectly = true; + } + pos->mask = common_bits; + pos->value = bits; + } + } else { + // Don't ignore case. Nice simple case where the mask-compare will + // determine definitely whether we have a match at this character + // position. + pos->mask = char_mask; + pos->value = c; + pos->determines_perfectly = true; + } + characters_filled_in++; + JS_ASSERT(characters_filled_in <= details->characters()); + if (characters_filled_in == details->characters()) { + return; + } + } + } else { + QuickCheckDetails::Position* pos = + details->positions(characters_filled_in); + RegExpCharacterClass* tree = elm.char_class(); + const CharacterRangeVector &ranges = tree->ranges(alloc()); + if (tree->is_negated()) { + // A quick check uses multi-character mask and compare. There is no + // useful way to incorporate a negative char class into this scheme + // so we just conservatively create a mask and value that will always + // succeed. + pos->mask = 0; + pos->value = 0; + } else { + size_t first_range = 0; + while (ranges[first_range].from() > char_mask) { + first_range++; + if (first_range == ranges.length()) { + details->set_cannot_match(); + pos->determines_perfectly = false; + return; + } + } + CharacterRange range = ranges[first_range]; + jschar from = range.from(); + jschar to = range.to(); + if (to > char_mask) { + to = char_mask; + } + uint32_t differing_bits = (from ^ to); + // A mask and compare is only perfect if the differing bits form a + // number like 00011111 with one single block of trailing 1s. + if ((differing_bits & (differing_bits + 1)) == 0 && + from + differing_bits == to) { + pos->determines_perfectly = true; + } + uint32_t common_bits = ~SmearBitsRight(differing_bits); + uint32_t bits = (from & common_bits); + for (size_t i = first_range + 1; i < ranges.length(); i++) { + CharacterRange range = ranges[i]; + jschar from = range.from(); + jschar to = range.to(); + if (from > char_mask) continue; + if (to > char_mask) to = char_mask; + // Here we are combining more ranges into the mask and compare + // value. With each new range the mask becomes more sparse and + // so the chances of a false positive rise. A character class + // with multiple ranges is assumed never to be equivalent to a + // mask and compare operation. + pos->determines_perfectly = false; + uint32_t new_common_bits = (from ^ to); + new_common_bits = ~SmearBitsRight(new_common_bits); + common_bits &= new_common_bits; + bits &= new_common_bits; + uint32_t differing_bits = (from & common_bits) ^ bits; + common_bits ^= differing_bits; + bits &= common_bits; + } + pos->mask = common_bits; + pos->value = bits; + } + characters_filled_in++; + JS_ASSERT(characters_filled_in <= details->characters()); + if (characters_filled_in == details->characters()) { + return; + } + } + } + JS_ASSERT(characters_filled_in != details->characters()); + if (!details->cannot_match()) { + on_success()-> GetQuickCheckDetails(details, + compiler, + characters_filled_in, + true); + } +} + +void +QuickCheckDetails::Clear() +{ + for (int i = 0; i < characters_; i++) { + positions_[i].mask = 0; + positions_[i].value = 0; + positions_[i].determines_perfectly = false; + } + characters_ = 0; +} + +void +QuickCheckDetails::Advance(int by, bool ascii) +{ + JS_ASSERT(by >= 0); + if (by >= characters_) { + Clear(); + return; + } + for (int i = 0; i < characters_ - by; i++) { + positions_[i] = positions_[by + i]; + } + for (int i = characters_ - by; i < characters_; i++) { + positions_[i].mask = 0; + positions_[i].value = 0; + positions_[i].determines_perfectly = false; + } + characters_ -= by; + // We could change mask_ and value_ here but we would never advance unless + // they had already been used in a check and they won't be used again because + // it would gain us nothing. So there's no point. +} + +bool +QuickCheckDetails::Rationalize(bool is_ascii) +{ + bool found_useful_op = false; + uint32_t char_mask = MaximumCharacter(is_ascii); + + mask_ = 0; + value_ = 0; + int char_shift = 0; + for (int i = 0; i < characters_; i++) { + Position* pos = &positions_[i]; + if ((pos->mask & kMaxOneByteCharCode) != 0) + found_useful_op = true; + mask_ |= (pos->mask & char_mask) << char_shift; + value_ |= (pos->value & char_mask) << char_shift; + char_shift += is_ascii ? 8 : 16; + } + return found_useful_op; +} + +void QuickCheckDetails::Merge(QuickCheckDetails* other, int from_index) +{ + JS_ASSERT(characters_ == other->characters_); + if (other->cannot_match_) + return; + if (cannot_match_) { + *this = *other; + return; + } + for (int i = from_index; i < characters_; i++) { + QuickCheckDetails::Position* pos = positions(i); + QuickCheckDetails::Position* other_pos = other->positions(i); + if (pos->mask != other_pos->mask || + pos->value != other_pos->value || + !other_pos->determines_perfectly) { + // Our mask-compare operation will be approximate unless we have the + // exact same operation on both sides of the alternation. + pos->determines_perfectly = false; + } + pos->mask &= other_pos->mask; + pos->value &= pos->mask; + other_pos->value &= pos->mask; + jschar differing_bits = (pos->value ^ other_pos->value); + pos->mask &= ~differing_bits; + pos->value &= pos->mask; + } +} diff --git a/js/src/irregexp/RegExpEngine.h b/js/src/irregexp/RegExpEngine.h new file mode 100644 index 00000000000..d508fdf0cea --- /dev/null +++ b/js/src/irregexp/RegExpEngine.h @@ -0,0 +1,1518 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: */ + +// Copyright 2012 the V8 project authors. 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. + +#ifndef V8_JSREGEXP_H_ +#define V8_JSREGEXP_H_ + +#include "jscntxt.h" + +#include "ds/SplayTree.h" +#include "jit/Label.h" +#include "vm/RegExpObject.h" + +namespace js { + +class MatchPairs; +class RegExpShared; + +namespace jit { + class Label; + class JitCode; +} + +namespace irregexp { + +class RegExpTree; +class RegExpMacroAssembler; + +struct RegExpCompileData +{ + RegExpCompileData() + : tree(nullptr), + simple(true), + contains_anchor(false), + capture_count(0) + {} + + RegExpTree *tree; + bool simple; + bool contains_anchor; + int capture_count; +}; + +struct RegExpCode +{ +#ifdef JS_ION + jit::JitCode *jitCode; + uint8_t *byteCode; + + RegExpCode() + : jitCode(nullptr), byteCode(nullptr) + {} + + bool empty() { + return !jitCode && !byteCode; + } + + void destroy() { + js_free(byteCode); + } +#else + uint8_t *byteCode; + + RegExpCode() + : byteCode(nullptr) + {} + + bool empty() { + return !byteCode; + } + + void destroy() { + js_free(byteCode); + } +#endif +}; + +RegExpCode +CompilePattern(JSContext *cx, RegExpShared *shared, RegExpCompileData *data, + const jschar *sampleChars, size_t sampleLength, + bool is_global, bool ignore_case = false, bool is_ascii = false); + +// Note: this may return RegExpRunStatus_Error if an interrupt was requested +// while the code was executing. +RegExpRunStatus +ExecuteCode(JSContext *cx, jit::JitCode *codeBlock, + const jschar *chars, size_t start, size_t length, MatchPairs *matches); + +RegExpRunStatus +InterpretCode(JSContext *cx, const uint8_t *byteCode, + const jschar *chars, size_t start, size_t length, MatchPairs *matches); + +#define FOR_EACH_NODE_TYPE(VISIT) \ + VISIT(End) \ + VISIT(Action) \ + VISIT(Choice) \ + VISIT(BackReference) \ + VISIT(Assertion) \ + VISIT(Text) + +#define FOR_EACH_REG_EXP_TREE_TYPE(VISIT) \ + VISIT(Disjunction) \ + VISIT(Alternative) \ + VISIT(Assertion) \ + VISIT(CharacterClass) \ + VISIT(Atom) \ + VISIT(Quantifier) \ + VISIT(Capture) \ + VISIT(Lookahead) \ + VISIT(BackReference) \ + VISIT(Empty) \ + VISIT(Text) + +#define FORWARD_DECLARE(Name) class RegExp##Name; +FOR_EACH_REG_EXP_TREE_TYPE(FORWARD_DECLARE) +#undef FORWARD_DECLARE + +class CharacterRange; +typedef Vector > CharacterRangeVector; + +// Represents code units in the range from from_ to to_, both ends are +// inclusive. +class CharacterRange +{ + public: + CharacterRange() + : from_(0), to_(0) + {} + + CharacterRange(jschar from, jschar to) + : from_(from), to_(to) + {} + + static void AddClassEscape(LifoAlloc *alloc, jschar type, CharacterRangeVector *ranges); + + static inline CharacterRange Singleton(jschar value) { + return CharacterRange(value, value); + } + static inline CharacterRange Range(jschar from, jschar to) { + JS_ASSERT(from <= to); + return CharacterRange(from, to); + } + static inline CharacterRange Everything() { + return CharacterRange(0, 0xFFFF); + } + bool Contains(jschar i) { return from_ <= i && i <= to_; } + jschar from() const { return from_; } + void set_from(jschar value) { from_ = value; } + jschar to() const { return to_; } + void set_to(jschar value) { to_ = value; } + bool is_valid() { return from_ <= to_; } + bool IsEverything(jschar max) { return from_ == 0 && to_ >= max; } + bool IsSingleton() { return (from_ == to_); } + void AddCaseEquivalents(bool is_ascii, CharacterRangeVector *ranges); + + static void Split(const LifoAlloc *alloc, + CharacterRangeVector base, + const Vector &overlay, + CharacterRangeVector* included, + CharacterRangeVector* excluded); + + // Whether a range list is in canonical form: Ranges ordered by from value, + // and ranges non-overlapping and non-adjacent. + static bool IsCanonical(const CharacterRangeVector &ranges); + + // Convert range list to canonical form. The characters covered by the ranges + // will still be the same, but no character is in more than one range, and + // adjacent ranges are merged. The resulting list may be shorter than the + // original, but cannot be longer. + static void Canonicalize(CharacterRangeVector &ranges); + + // Negate the contents of a character range in canonical form. + static void Negate(const LifoAlloc *alloc, + CharacterRangeVector src, + CharacterRangeVector *dst); + + static const int kStartMarker = (1 << 24); + static const int kPayloadMask = (1 << 24) - 1; + + private: + jschar from_; + jschar to_; +}; + +// A set of unsigned integers that behaves especially well on small +// integers (< 32). +class OutSet +{ + public: + OutSet() + : first_(0), remaining_(nullptr), successors_(nullptr) + {} + + OutSet* Extend(LifoAlloc *alloc, unsigned value); + bool Get(unsigned value); + static const unsigned kFirstLimit = 32; + + private: + typedef Vector > OutSetVector; + typedef Vector > RemainingVector; + + // Destructively set a value in this set. In most cases you want + // to use Extend instead to ensure that only one instance exists + // that contains the same values. + void Set(LifoAlloc *alloc, unsigned value); + + // The successors are a list of sets that contain the same values + // as this set and the one more value that is not present in this + // set. + OutSetVector *successors() { return successors_; } + + OutSet(uint32_t first, RemainingVector *remaining) + : first_(first), remaining_(remaining), successors_(nullptr) + {} + + RemainingVector &remaining() { return *remaining_; } + + uint32_t first_; + RemainingVector *remaining_; + OutSetVector *successors_; + friend class Trace; +}; + +// A mapping from integers, specified as ranges, to a set of integers. +// Used for mapping character ranges to choices. +class DispatchTable +{ + public: + explicit DispatchTable(LifoAlloc *alloc) + {} + + class Entry { + public: + Entry() + : from_(0), to_(0), out_set_(nullptr) + {} + + Entry(jschar from, jschar to, OutSet* out_set) + : from_(from), to_(to), out_set_(out_set) + {} + + jschar from() { return from_; } + jschar to() { return to_; } + void set_to(jschar value) { to_ = value; } + void AddValue(LifoAlloc *alloc, int value) { + out_set_ = out_set_->Extend(alloc, value); + } + OutSet* out_set() { return out_set_; } + private: + jschar from_; + jschar to_; + OutSet* out_set_; + }; + + void AddRange(LifoAlloc *alloc, CharacterRange range, int value); + OutSet* Get(jschar value); + void Dump(); + + private: + // There can't be a static empty set since it allocates its + // successors in a LifoAlloc and caches them. + OutSet* empty() { return &empty_; } + OutSet empty_; +}; + +class TextElement +{ + public: + enum TextType { + ATOM, + CHAR_CLASS + }; + + static TextElement Atom(RegExpAtom* atom); + static TextElement CharClass(RegExpCharacterClass* char_class); + + int cp_offset() const { return cp_offset_; } + void set_cp_offset(int cp_offset) { cp_offset_ = cp_offset; } + int length() const; + + TextType text_type() const { return text_type_; } + + RegExpTree* tree() const { return tree_; } + + RegExpAtom* atom() const { + JS_ASSERT(text_type() == ATOM); + return reinterpret_cast(tree()); + } + + RegExpCharacterClass* char_class() const { + JS_ASSERT(text_type() == CHAR_CLASS); + return reinterpret_cast(tree()); + } + + private: + TextElement(TextType text_type, RegExpTree* tree) + : cp_offset_(-1), text_type_(text_type), tree_(tree) + {} + + int cp_offset_; + TextType text_type_; + RegExpTree* tree_; +}; + +typedef Vector > TextElementVector; + +class NodeVisitor; +class RegExpCompiler; +class Trace; +class BoyerMooreLookahead; + +struct NodeInfo +{ + NodeInfo() + : being_analyzed(false), + been_analyzed(false), + follows_word_interest(false), + follows_newline_interest(false), + follows_start_interest(false), + at_end(false), + visited(false), + replacement_calculated(false) + {} + + // Returns true if the interests and assumptions of this node + // matches the given one. + bool Matches(NodeInfo* that) { + return (at_end == that->at_end) && + (follows_word_interest == that->follows_word_interest) && + (follows_newline_interest == that->follows_newline_interest) && + (follows_start_interest == that->follows_start_interest); + } + + // Updates the interests of this node given the interests of the + // node preceding it. + void AddFromPreceding(NodeInfo* that) { + at_end |= that->at_end; + follows_word_interest |= that->follows_word_interest; + follows_newline_interest |= that->follows_newline_interest; + follows_start_interest |= that->follows_start_interest; + } + + bool HasLookbehind() { + return follows_word_interest || + follows_newline_interest || + follows_start_interest; + } + + // Sets the interests of this node to include the interests of the + // following node. + void AddFromFollowing(NodeInfo* that) { + follows_word_interest |= that->follows_word_interest; + follows_newline_interest |= that->follows_newline_interest; + follows_start_interest |= that->follows_start_interest; + } + + void ResetCompilationState() { + being_analyzed = false; + been_analyzed = false; + } + + bool being_analyzed: 1; + bool been_analyzed: 1; + + // These bits are set of this node has to know what the preceding + // character was. + bool follows_word_interest: 1; + bool follows_newline_interest: 1; + bool follows_start_interest: 1; + + bool at_end: 1; + bool visited: 1; + bool replacement_calculated: 1; +}; + +// Details of a quick mask-compare check that can look ahead in the +// input stream. +class QuickCheckDetails +{ + public: + QuickCheckDetails() + : characters_(0), + mask_(0), + value_(0), + cannot_match_(false) + {} + + explicit QuickCheckDetails(int characters) + : characters_(characters), + mask_(0), + value_(0), + cannot_match_(false) + {} + + bool Rationalize(bool ascii); + + // Merge in the information from another branch of an alternation. + void Merge(QuickCheckDetails* other, int from_index); + + // Advance the current position by some amount. + void Advance(int by, bool ascii); + + void Clear(); + + bool cannot_match() { return cannot_match_; } + void set_cannot_match() { cannot_match_ = true; } + + int characters() { return characters_; } + void set_characters(int characters) { characters_ = characters; } + + struct Position { + Position() : mask(0), value(0), determines_perfectly(false) { } + jschar mask; + jschar value; + bool determines_perfectly; + }; + + Position* positions(int index) { + JS_ASSERT(index >= 0); + JS_ASSERT(index < characters_); + return positions_ + index; + } + + uint32_t mask() { return mask_; } + uint32_t value() { return value_; } + + private: + // How many characters do we have quick check information from. This is + // the same for all branches of a choice node. + int characters_; + Position positions_[4]; + + // These values are the condensate of the above array after Rationalize(). + uint32_t mask_; + uint32_t value_; + + // If set to true, there is no way this quick check can match at all. + // E.g., if it requires to be at the start of the input, and isn't. + bool cannot_match_; +}; + +class RegExpNode +{ + public: + explicit RegExpNode(LifoAlloc *alloc); + virtual ~RegExpNode() {} + virtual void Accept(NodeVisitor* visitor) = 0; + + // Generates a goto to this node or actually generates the code at this point. + virtual void Emit(RegExpCompiler* compiler, Trace* trace) = 0; + + // How many characters must this node consume at a minimum in order to + // succeed. If we have found at least 'still_to_find' characters that + // must be consumed there is no need to ask any following nodes whether + // they are sure to eat any more characters. The not_at_start argument is + // used to indicate that we know we are not at the start of the input. In + // this case anchored branches will always fail and can be ignored when + // determining how many characters are consumed on success. + virtual int EatsAtLeast(int still_to_find, int budget, bool not_at_start) = 0; + + // Emits some quick code that checks whether the preloaded characters match. + // Falls through on certain failure, jumps to the label on possible success. + // If the node cannot make a quick check it does nothing and returns false. + bool EmitQuickCheck(RegExpCompiler* compiler, + Trace* trace, + bool preload_has_checked_bounds, + jit::Label* on_possible_success, + QuickCheckDetails* details_return, + bool fall_through_on_failure); + + // For a given number of characters this returns a mask and a value. The + // next n characters are anded with the mask and compared with the value. + // A comparison failure indicates the node cannot match the next n characters. + // A comparison success indicates the node may match. + virtual void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start) = 0; + + static const int kNodeIsTooComplexForGreedyLoops = -1; + + virtual int GreedyLoopTextLength() { return kNodeIsTooComplexForGreedyLoops; } + + // Only returns the successor for a text node of length 1 that matches any + // character and that has no guards on it. + virtual RegExpNode* GetSuccessorOfOmnivorousTextNode(RegExpCompiler* compiler) { + return nullptr; + } + + static const int kRecursionBudget = 200; + + // Collects information on the possible code units (mod 128) that can match if + // we look forward. This is used for a Boyer-Moore-like string searching + // implementation. TODO(erikcorry): This should share more code with + // EatsAtLeast, GetQuickCheckDetails. The budget argument is used to limit + // the number of nodes we are willing to look at in order to create this data. + virtual void FillInBMInfo(int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start) { + MOZ_ASSUME_UNREACHABLE("Bad call"); + } + + // If we know that the input is ASCII then there are some nodes that can + // never match. This method returns a node that can be substituted for + // itself, or nullptr if the node can never match. + virtual RegExpNode* FilterASCII(int depth, bool ignore_case) { return this; } + + // Helper for FilterASCII. + RegExpNode* replacement() { + JS_ASSERT(info()->replacement_calculated); + return replacement_; + } + RegExpNode* set_replacement(RegExpNode* replacement) { + info()->replacement_calculated = true; + replacement_ = replacement; + return replacement; // For convenience. + } + + // We want to avoid recalculating the lookahead info, so we store it on the + // node. Only info that is for this node is stored. We can tell that the + // info is for this node when offset == 0, so the information is calculated + // relative to this node. + void SaveBMInfo(BoyerMooreLookahead* bm, bool not_at_start, int offset) { + if (offset == 0) set_bm_info(not_at_start, bm); + } + + jit::Label* label() { return &label_; } + + // If non-generic code is generated for a node (i.e. the node is not at the + // start of the trace) then it cannot be reused. This variable sets a limit + // on how often we allow that to happen before we insist on starting a new + // trace and generating generic code for a node that can be reused by flushing + // the deferred actions in the current trace and generating a goto. + static const int kMaxCopiesCodeGenerated = 10; + + NodeInfo* info() { return &info_; } + + BoyerMooreLookahead* bm_info(bool not_at_start) { + return bm_info_[not_at_start ? 1 : 0]; + } + + LifoAlloc *alloc() const { return alloc_; } + + protected: + enum LimitResult { DONE, CONTINUE }; + RegExpNode* replacement_; + + LimitResult LimitVersions(RegExpCompiler* compiler, Trace* trace); + + void set_bm_info(bool not_at_start, BoyerMooreLookahead* bm) { + bm_info_[not_at_start ? 1 : 0] = bm; + } + + private: + static const int kFirstCharBudget = 10; + jit::Label label_; + NodeInfo info_; + + // This variable keeps track of how many times code has been generated for + // this node (in different traces). We don't keep track of where the + // generated code is located unless the code is generated at the start of + // a trace, in which case it is generic and can be reused by flushing the + // deferred operations in the current trace and generating a goto. + int trace_count_; + BoyerMooreLookahead* bm_info_[2]; + + LifoAlloc *alloc_; +}; + +// A simple closed interval. +class Interval +{ + public: + Interval() : from_(kNone), to_(kNone) { } + + Interval(int from, int to) : from_(from), to_(to) { } + + Interval Union(Interval that) { + if (that.from_ == kNone) + return *this; + else if (from_ == kNone) + return that; + else + return Interval(Min(from_, that.from_), Max(to_, that.to_)); + } + + bool Contains(int value) { + return (from_ <= value) && (value <= to_); + } + + bool is_empty() { return from_ == kNone; } + + int from() const { return from_; } + int to() const { return to_; } + + static Interval Empty() { return Interval(); } + static const int kNone = -1; + + private: + int from_; + int to_; +}; + +class SeqRegExpNode : public RegExpNode +{ + public: + explicit SeqRegExpNode(RegExpNode* on_success) + : RegExpNode(on_success->alloc()), on_success_(on_success) + {} + + RegExpNode* on_success() { return on_success_; } + void set_on_success(RegExpNode* node) { on_success_ = node; } + virtual RegExpNode* FilterASCII(int depth, bool ignore_case); + virtual void FillInBMInfo(int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start) { + on_success_->FillInBMInfo(offset, budget - 1, bm, not_at_start); + if (offset == 0) set_bm_info(not_at_start, bm); + } + + protected: + RegExpNode* FilterSuccessor(int depth, bool ignore_case); + + private: + RegExpNode* on_success_; +}; + +class ActionNode : public SeqRegExpNode +{ + public: + enum ActionType { + SET_REGISTER, + INCREMENT_REGISTER, + STORE_POSITION, + BEGIN_SUBMATCH, + POSITIVE_SUBMATCH_SUCCESS, + EMPTY_MATCH_CHECK, + CLEAR_CAPTURES + }; + + ActionNode(ActionType action_type, RegExpNode* on_success) + : SeqRegExpNode(on_success), + action_type_(action_type) + {} + + static ActionNode* SetRegister(int reg, int val, RegExpNode* on_success); + static ActionNode* IncrementRegister(int reg, RegExpNode* on_success); + static ActionNode* StorePosition(int reg, + bool is_capture, + RegExpNode* on_success); + static ActionNode* ClearCaptures(Interval range, RegExpNode* on_success); + static ActionNode* BeginSubmatch(int stack_pointer_reg, + int position_reg, + RegExpNode* on_success); + static ActionNode* PositiveSubmatchSuccess(int stack_pointer_reg, + int restore_reg, + int clear_capture_count, + int clear_capture_from, + RegExpNode* on_success); + static ActionNode* EmptyMatchCheck(int start_register, + int repetition_register, + int repetition_limit, + RegExpNode* on_success); + virtual void Accept(NodeVisitor* visitor); + virtual void Emit(RegExpCompiler* compiler, Trace* trace); + virtual int EatsAtLeast(int still_to_find, int budget, bool not_at_start); + virtual void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int filled_in, + bool not_at_start) { + return on_success()->GetQuickCheckDetails( + details, compiler, filled_in, not_at_start); + } + virtual void FillInBMInfo(int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start); + ActionType action_type() { return action_type_; } + // TODO(erikcorry): We should allow some action nodes in greedy loops. + virtual int GreedyLoopTextLength() { return kNodeIsTooComplexForGreedyLoops; } + + private: + union { + struct { + int reg; + int value; + } u_store_register; + struct { + int reg; + } u_increment_register; + struct { + int reg; + bool is_capture; + } u_position_register; + struct { + int stack_pointer_register; + int current_position_register; + int clear_register_count; + int clear_register_from; + } u_submatch; + struct { + int start_register; + int repetition_register; + int repetition_limit; + } u_empty_match_check; + struct { + int range_from; + int range_to; + } u_clear_captures; + } data_; + ActionType action_type_; + friend class DotPrinter; +}; + +class TextNode : public SeqRegExpNode +{ + public: + TextNode(TextElementVector *elements, + RegExpNode *on_success) + : SeqRegExpNode(on_success), + elements_(elements) + {} + + TextNode(RegExpCharacterClass* that, + RegExpNode* on_success) + : SeqRegExpNode(on_success), + elements_(alloc()->newInfallible(*alloc())) + { + elements_->append(TextElement::CharClass(that)); + } + + virtual void Accept(NodeVisitor* visitor); + virtual void Emit(RegExpCompiler* compiler, Trace* trace); + virtual int EatsAtLeast(int still_to_find, int budget, bool not_at_start); + virtual void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start); + TextElementVector &elements() { return *elements_; } + void MakeCaseIndependent(bool is_ascii); + virtual int GreedyLoopTextLength(); + virtual RegExpNode* GetSuccessorOfOmnivorousTextNode( + RegExpCompiler* compiler); + virtual void FillInBMInfo(int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start); + void CalculateOffsets(); + virtual RegExpNode* FilterASCII(int depth, bool ignore_case); + + private: + enum TextEmitPassType { + NON_ASCII_MATCH, // Check for characters that can't match. + SIMPLE_CHARACTER_MATCH, // Case-dependent single character check. + NON_LETTER_CHARACTER_MATCH, // Check characters that have no case equivs. + CASE_CHARACTER_MATCH, // Case-independent single character check. + CHARACTER_CLASS_MATCH // Character class. + }; + static bool SkipPass(int pass, bool ignore_case); + static const int kFirstRealPass = SIMPLE_CHARACTER_MATCH; + static const int kLastPass = CHARACTER_CLASS_MATCH; + void TextEmitPass(RegExpCompiler* compiler, + TextEmitPassType pass, + bool preloaded, + Trace* trace, + bool first_element_checked, + int* checked_up_to); + int Length(); + TextElementVector *elements_; +}; + +class AssertionNode : public SeqRegExpNode +{ + public: + enum AssertionType { + AT_END, + AT_START, + AT_BOUNDARY, + AT_NON_BOUNDARY, + AFTER_NEWLINE + }; + AssertionNode(AssertionType t, RegExpNode* on_success) + : SeqRegExpNode(on_success), assertion_type_(t) + {} + + static AssertionNode* AtEnd(RegExpNode* on_success) { + return on_success->alloc()->newInfallible(AT_END, on_success); + } + static AssertionNode* AtStart(RegExpNode* on_success) { + return on_success->alloc()->newInfallible(AT_START, on_success); + } + static AssertionNode* AtBoundary(RegExpNode* on_success) { + return on_success->alloc()->newInfallible(AT_BOUNDARY, on_success); + } + static AssertionNode* AtNonBoundary(RegExpNode* on_success) { + return on_success->alloc()->newInfallible(AT_NON_BOUNDARY, on_success); + } + static AssertionNode* AfterNewline(RegExpNode* on_success) { + return on_success->alloc()->newInfallible(AFTER_NEWLINE, on_success); + } + virtual void Accept(NodeVisitor* visitor); + virtual void Emit(RegExpCompiler* compiler, Trace* trace); + virtual int EatsAtLeast(int still_to_find, int budget, bool not_at_start); + virtual void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int filled_in, + bool not_at_start); + virtual void FillInBMInfo(int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start); + AssertionType assertion_type() { return assertion_type_; } + + private: + void EmitBoundaryCheck(RegExpCompiler* compiler, Trace* trace); + enum IfPrevious { kIsNonWord, kIsWord }; + void BacktrackIfPrevious(RegExpCompiler* compiler, + Trace* trace, + IfPrevious backtrack_if_previous); + AssertionType assertion_type_; +}; + +class BackReferenceNode : public SeqRegExpNode +{ + public: + BackReferenceNode(int start_reg, + int end_reg, + RegExpNode* on_success) + : SeqRegExpNode(on_success), + start_reg_(start_reg), + end_reg_(end_reg) + {} + + virtual void Accept(NodeVisitor* visitor); + int start_register() { return start_reg_; } + int end_register() { return end_reg_; } + virtual void Emit(RegExpCompiler* compiler, Trace* trace); + virtual int EatsAtLeast(int still_to_find, + int recursion_depth, + bool not_at_start); + virtual void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start) { + return; + } + virtual void FillInBMInfo(int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start); + + private: + int start_reg_; + int end_reg_; +}; + +class EndNode : public RegExpNode +{ + public: + enum Action { ACCEPT, BACKTRACK, NEGATIVE_SUBMATCH_SUCCESS }; + + explicit EndNode(LifoAlloc *alloc, Action action) + : RegExpNode(alloc), action_(action) + {} + + virtual void Accept(NodeVisitor* visitor); + virtual void Emit(RegExpCompiler* compiler, Trace* trace); + virtual int EatsAtLeast(int still_to_find, + int recursion_depth, + bool not_at_start) { return 0; } + virtual void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start) + { + // Returning 0 from EatsAtLeast should ensure we never get here. + MOZ_ASSUME_UNREACHABLE("Bad call"); + } + virtual void FillInBMInfo(int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start) { + // Returning 0 from EatsAtLeast should ensure we never get here. + MOZ_ASSUME_UNREACHABLE("Bad call"); + } + + private: + Action action_; +}; + +class NegativeSubmatchSuccess : public EndNode +{ + public: + NegativeSubmatchSuccess(LifoAlloc *alloc, + int stack_pointer_reg, + int position_reg, + int clear_capture_count, + int clear_capture_start) + : EndNode(alloc, NEGATIVE_SUBMATCH_SUCCESS), + stack_pointer_register_(stack_pointer_reg), + current_position_register_(position_reg), + clear_capture_count_(clear_capture_count), + clear_capture_start_(clear_capture_start) + {} + + virtual void Emit(RegExpCompiler* compiler, Trace* trace); + + private: + int stack_pointer_register_; + int current_position_register_; + int clear_capture_count_; + int clear_capture_start_; +}; + +class Guard +{ + public: + enum Relation { LT, GEQ }; + Guard(int reg, Relation op, int value) + : reg_(reg), + op_(op), + value_(value) + {} + + int reg() { return reg_; } + Relation op() { return op_; } + int value() { return value_; } + + private: + int reg_; + Relation op_; + int value_; +}; + +typedef Vector > GuardVector; + +class GuardedAlternative +{ + public: + explicit GuardedAlternative(RegExpNode* node) + : node_(node), guards_(nullptr) + {} + + void AddGuard(LifoAlloc *alloc, Guard *guard); + RegExpNode *node() const { return node_; } + void set_node(RegExpNode* node) { node_ = node; } + const GuardVector *guards() const { return guards_; } + + private: + RegExpNode *node_; + GuardVector *guards_; +}; + +typedef Vector > GuardedAlternativeVector; + +class AlternativeGeneration; + +class ChoiceNode : public RegExpNode +{ + public: + explicit ChoiceNode(LifoAlloc *alloc, int expected_size) + : RegExpNode(alloc), + alternatives_(*alloc), + table_(nullptr), + not_at_start_(false), + being_calculated_(false) + { + alternatives_.reserve(expected_size); + } + + virtual void Accept(NodeVisitor* visitor); + void AddAlternative(GuardedAlternative node) { + alternatives_.append(node); + } + + GuardedAlternativeVector &alternatives() { return alternatives_; } + DispatchTable* GetTable(bool ignore_case); + virtual void Emit(RegExpCompiler* compiler, Trace* trace); + virtual int EatsAtLeast(int still_to_find, int budget, bool not_at_start); + int EatsAtLeastHelper(int still_to_find, + int budget, + RegExpNode* ignore_this_node, + bool not_at_start); + virtual void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start); + virtual void FillInBMInfo(int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start); + + bool being_calculated() { return being_calculated_; } + bool not_at_start() { return not_at_start_; } + void set_not_at_start() { not_at_start_ = true; } + void set_being_calculated(bool b) { being_calculated_ = b; } + virtual bool try_to_emit_quick_check_for_alternative(int i) { return true; } + virtual RegExpNode* FilterASCII(int depth, bool ignore_case); + + protected: + int GreedyLoopTextLengthForAlternative(GuardedAlternative* alternative); + GuardedAlternativeVector alternatives_; + + private: + friend class Analysis; + void GenerateGuard(RegExpMacroAssembler* macro_assembler, + Guard* guard, + Trace* trace); + int CalculatePreloadCharacters(RegExpCompiler* compiler, int eats_at_least); + void EmitOutOfLineContinuation(RegExpCompiler* compiler, + Trace* trace, + GuardedAlternative alternative, + AlternativeGeneration* alt_gen, + int preload_characters, + bool next_expects_preload); + DispatchTable* table_; + + // If true, this node is never checked at the start of the input. + // Allows a new trace to start with at_start() set to false. + bool not_at_start_; + bool being_calculated_; +}; + +class NegativeLookaheadChoiceNode : public ChoiceNode +{ + public: + explicit NegativeLookaheadChoiceNode(LifoAlloc *alloc, + GuardedAlternative this_must_fail, + GuardedAlternative then_do_this) + : ChoiceNode(alloc, 2) + { + AddAlternative(this_must_fail); + AddAlternative(then_do_this); + } + virtual int EatsAtLeast(int still_to_find, int budget, bool not_at_start); + virtual void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start); + virtual void FillInBMInfo(int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start) + { + alternatives()[1].node()->FillInBMInfo(offset, budget - 1, bm, not_at_start); + if (offset == 0) + set_bm_info(not_at_start, bm); + } + + // For a negative lookahead we don't emit the quick check for the + // alternative that is expected to fail. This is because quick check code + // starts by loading enough characters for the alternative that takes fewest + // characters, but on a negative lookahead the negative branch did not take + // part in that calculation (EatsAtLeast) so the assumptions don't hold. + virtual bool try_to_emit_quick_check_for_alternative(int i) { return i != 0; } + virtual RegExpNode* FilterASCII(int depth, bool ignore_case); +}; + +class LoopChoiceNode : public ChoiceNode +{ + public: + explicit LoopChoiceNode(LifoAlloc *alloc, bool body_can_be_zero_length) + : ChoiceNode(alloc, 2), + loop_node_(nullptr), + continue_node_(nullptr), + body_can_be_zero_length_(body_can_be_zero_length) + {} + + void AddLoopAlternative(GuardedAlternative alt); + void AddContinueAlternative(GuardedAlternative alt); + virtual void Emit(RegExpCompiler* compiler, Trace* trace); + virtual int EatsAtLeast(int still_to_find, int budget, bool not_at_start); + virtual void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start); + virtual void FillInBMInfo(int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start); + RegExpNode* loop_node() { return loop_node_; } + RegExpNode* continue_node() { return continue_node_; } + bool body_can_be_zero_length() { return body_can_be_zero_length_; } + virtual void Accept(NodeVisitor* visitor); + virtual RegExpNode* FilterASCII(int depth, bool ignore_case); + + private: + // AddAlternative is made private for loop nodes because alternatives + // should not be added freely, we need to keep track of which node + // goes back to the node itself. + void AddAlternative(GuardedAlternative node) { + ChoiceNode::AddAlternative(node); + } + + RegExpNode* loop_node_; + RegExpNode* continue_node_; + bool body_can_be_zero_length_; +}; + +// Improve the speed that we scan for an initial point where a non-anchored +// regexp can match by using a Boyer-Moore-like table. This is done by +// identifying non-greedy non-capturing loops in the nodes that eat any +// character one at a time. For example in the middle of the regexp +// /foo[\s\S]*?bar/ we find such a loop. There is also such a loop implicitly +// inserted at the start of any non-anchored regexp. +// +// When we have found such a loop we look ahead in the nodes to find the set of +// characters that can come at given distances. For example for the regexp +// /.?foo/ we know that there are at least 3 characters ahead of us, and the +// sets of characters that can occur are [any, [f, o], [o]]. We find a range in +// the lookahead info where the set of characters is reasonably constrained. In +// our example this is from index 1 to 2 (0 is not constrained). We can now +// look 3 characters ahead and if we don't find one of [f, o] (the union of +// [f, o] and [o]) then we can skip forwards by the range size (in this case 2). +// +// For Unicode input strings we do the same, but modulo 128. +// +// We also look at the first string fed to the regexp and use that to get a hint +// of the character frequencies in the inputs. This affects the assessment of +// whether the set of characters is 'reasonably constrained'. +// +// We also have another lookahead mechanism (called quick check in the code), +// which uses a wide load of multiple characters followed by a mask and compare +// to determine whether a match is possible at this point. +enum ContainedInLattice { + kNotYet = 0, + kLatticeIn = 1, + kLatticeOut = 2, + kLatticeUnknown = 3 // Can also mean both in and out. +}; + +inline ContainedInLattice +Combine(ContainedInLattice a, ContainedInLattice b) { + return static_cast(a | b); +} + +ContainedInLattice +AddRange(ContainedInLattice a, + const int* ranges, + int ranges_size, + Interval new_range); + +class BoyerMoorePositionInfo +{ + public: + explicit BoyerMoorePositionInfo(LifoAlloc *alloc) + : map_(*alloc), + map_count_(0), + w_(kNotYet), + s_(kNotYet), + d_(kNotYet), + surrogate_(kNotYet) + { + map_.reserve(kMapSize); + for (int i = 0; i < kMapSize; i++) + map_.append(false); + } + + bool& at(int i) { return map_[i]; } + + static const int kMapSize = 128; + static const int kMask = kMapSize - 1; + + int map_count() const { return map_count_; } + + void Set(int character); + void SetInterval(const Interval& interval); + void SetAll(); + bool is_non_word() { return w_ == kLatticeOut; } + bool is_word() { return w_ == kLatticeIn; } + + private: + Vector > map_; + int map_count_; // Number of set bits in the map. + ContainedInLattice w_; // The \w character class. + ContainedInLattice s_; // The \s character class. + ContainedInLattice d_; // The \d character class. + ContainedInLattice surrogate_; // Surrogate UTF-16 code units. +}; + +typedef Vector > BoyerMoorePositionInfoVector; + +class BoyerMooreLookahead +{ + public: + BoyerMooreLookahead(LifoAlloc *alloc, size_t length, RegExpCompiler* compiler); + + int length() { return length_; } + int max_char() { return max_char_; } + RegExpCompiler* compiler() { return compiler_; } + + int Count(int map_number) { + return bitmaps_[map_number]->map_count(); + } + + BoyerMoorePositionInfo* at(int i) { return bitmaps_[i]; } + + void Set(int map_number, int character) { + if (character > max_char_) return; + BoyerMoorePositionInfo* info = bitmaps_[map_number]; + info->Set(character); + } + + void SetInterval(int map_number, const Interval& interval) { + if (interval.from() > max_char_) return; + BoyerMoorePositionInfo* info = bitmaps_[map_number]; + if (interval.to() > max_char_) { + info->SetInterval(Interval(interval.from(), max_char_)); + } else { + info->SetInterval(interval); + } + } + + void SetAll(int map_number) { + bitmaps_[map_number]->SetAll(); + } + + void SetRest(int from_map) { + for (int i = from_map; i < length_; i++) SetAll(i); + } + bool EmitSkipInstructions(RegExpMacroAssembler* masm); + + private: + // This is the value obtained by EatsAtLeast. If we do not have at least this + // many characters left in the sample string then the match is bound to fail. + // Therefore it is OK to read a character this far ahead of the current match + // point. + int length_; + RegExpCompiler* compiler_; + + // 0x7f for ASCII, 0xffff for UTF-16. + int max_char_; + BoyerMoorePositionInfoVector bitmaps_; + + int GetSkipTable(int min_lookahead, + int max_lookahead, + uint8_t *boolean_skip_table); + bool FindWorthwhileInterval(int* from, int* to); + int FindBestInterval(int max_number_of_chars, int old_biggest_points, int* from, int* to); +}; + +// There are many ways to generate code for a node. This class encapsulates +// the current way we should be generating. In other words it encapsulates +// the current state of the code generator. The effect of this is that we +// generate code for paths that the matcher can take through the regular +// expression. A given node in the regexp can be code-generated several times +// as it can be part of several traces. For example for the regexp: +// /foo(bar|ip)baz/ the code to match baz will be generated twice, once as part +// of the foo-bar-baz trace and once as part of the foo-ip-baz trace. The code +// to match foo is generated only once (the traces have a common prefix). The +// code to store the capture is deferred and generated (twice) after the places +// where baz has been matched. +class Trace +{ + public: + // A value for a property that is either known to be true, know to be false, + // or not known. + enum TriBool { + UNKNOWN = -1, FALSE_VALUE = 0, TRUE_VALUE = 1 + }; + + class DeferredAction { + public: + DeferredAction(ActionNode::ActionType action_type, int reg) + : action_type_(action_type), reg_(reg), next_(nullptr) + {} + + DeferredAction* next() { return next_; } + bool Mentions(int reg); + int reg() { return reg_; } + ActionNode::ActionType action_type() { return action_type_; } + private: + ActionNode::ActionType action_type_; + int reg_; + DeferredAction* next_; + friend class Trace; + }; + + class DeferredCapture : public DeferredAction { + public: + DeferredCapture(int reg, bool is_capture, Trace* trace) + : DeferredAction(ActionNode::STORE_POSITION, reg), + cp_offset_(trace->cp_offset()), + is_capture_(is_capture) + {} + + int cp_offset() { return cp_offset_; } + bool is_capture() { return is_capture_; } + private: + int cp_offset_; + bool is_capture_; + void set_cp_offset(int cp_offset) { cp_offset_ = cp_offset; } + }; + + class DeferredSetRegister : public DeferredAction { + public: + DeferredSetRegister(int reg, int value) + : DeferredAction(ActionNode::SET_REGISTER, reg), + value_(value) + {} + int value() { return value_; } + private: + int value_; + }; + + class DeferredClearCaptures : public DeferredAction { + public: + explicit DeferredClearCaptures(Interval range) + : DeferredAction(ActionNode::CLEAR_CAPTURES, -1), + range_(range) + {} + + Interval range() { return range_; } + private: + Interval range_; + }; + + class DeferredIncrementRegister : public DeferredAction { + public: + explicit DeferredIncrementRegister(int reg) + : DeferredAction(ActionNode::INCREMENT_REGISTER, reg) + {} + }; + + Trace() + : cp_offset_(0), + actions_(nullptr), + backtrack_(nullptr), + stop_node_(nullptr), + loop_label_(nullptr), + characters_preloaded_(0), + bound_checked_up_to_(0), + flush_budget_(100), + at_start_(UNKNOWN) + {} + + // End the trace. This involves flushing the deferred actions in the trace + // and pushing a backtrack location onto the backtrack stack. Once this is + // done we can start a new trace or go to one that has already been + // generated. + void Flush(RegExpCompiler* compiler, RegExpNode* successor); + + int cp_offset() { return cp_offset_; } + DeferredAction* actions() { return actions_; } + + // A trivial trace is one that has no deferred actions or other state that + // affects the assumptions used when generating code. There is no recorded + // backtrack location in a trivial trace, so with a trivial trace we will + // generate code that, on a failure to match, gets the backtrack location + // from the backtrack stack rather than using a direct jump instruction. We + // always start code generation with a trivial trace and non-trivial traces + // are created as we emit code for nodes or add to the list of deferred + // actions in the trace. The location of the code generated for a node using + // a trivial trace is recorded in a label in the node so that gotos can be + // generated to that code. + bool is_trivial() { + return backtrack_ == nullptr && + actions_ == nullptr && + cp_offset_ == 0 && + characters_preloaded_ == 0 && + bound_checked_up_to_ == 0 && + quick_check_performed_.characters() == 0 && + at_start_ == UNKNOWN; + } + + TriBool at_start() { return at_start_; } + void set_at_start(bool at_start) { + at_start_ = at_start ? TRUE_VALUE : FALSE_VALUE; + } + jit::Label* backtrack() { return backtrack_; } + jit::Label* loop_label() { return loop_label_; } + RegExpNode* stop_node() { return stop_node_; } + int characters_preloaded() { return characters_preloaded_; } + int bound_checked_up_to() { return bound_checked_up_to_; } + int flush_budget() { return flush_budget_; } + QuickCheckDetails* quick_check_performed() { return &quick_check_performed_; } + bool mentions_reg(int reg); + + // Returns true if a deferred position store exists to the specified + // register and stores the offset in the out-parameter. Otherwise + // returns false. + bool GetStoredPosition(int reg, int* cp_offset); + + // These set methods and AdvanceCurrentPositionInTrace should be used only on + // new traces - the intention is that traces are immutable after creation. + void add_action(DeferredAction* new_action) { + JS_ASSERT(new_action->next_ == nullptr); + new_action->next_ = actions_; + actions_ = new_action; + } + + void set_backtrack(jit::Label* backtrack) { backtrack_ = backtrack; } + void set_stop_node(RegExpNode* node) { stop_node_ = node; } + void set_loop_label(jit::Label* label) { loop_label_ = label; } + void set_characters_preloaded(int count) { characters_preloaded_ = count; } + void set_bound_checked_up_to(int to) { bound_checked_up_to_ = to; } + void set_flush_budget(int to) { flush_budget_ = to; } + void set_quick_check_performed(QuickCheckDetails* d) { + quick_check_performed_ = *d; + } + void InvalidateCurrentCharacter(); + void AdvanceCurrentPositionInTrace(int by, RegExpCompiler* compiler); + + private: + int FindAffectedRegisters(LifoAlloc *alloc, OutSet* affected_registers); + void PerformDeferredActions(LifoAlloc *alloc, + RegExpMacroAssembler* macro, + int max_register, + OutSet& affected_registers, + OutSet* registers_to_pop, + OutSet* registers_to_clear); + void RestoreAffectedRegisters(RegExpMacroAssembler* macro, + int max_register, + OutSet& registers_to_pop, + OutSet& registers_to_clear); + int cp_offset_; + DeferredAction* actions_; + jit::Label* backtrack_; + RegExpNode* stop_node_; + jit::Label* loop_label_; + int characters_preloaded_; + int bound_checked_up_to_; + QuickCheckDetails quick_check_performed_; + int flush_budget_; + TriBool at_start_; +}; + +class NodeVisitor +{ + public: + virtual ~NodeVisitor() { } +#define DECLARE_VISIT(Type) \ + virtual void Visit##Type(Type##Node* that) = 0; + FOR_EACH_NODE_TYPE(DECLARE_VISIT) +#undef DECLARE_VISIT + virtual void VisitLoopChoice(LoopChoiceNode* that) { VisitChoice(that); } +}; + +// Assertion propagation moves information about assertions such as +// \b to the affected nodes. For instance, in /.\b./ information must +// be propagated to the first '.' that whatever follows needs to know +// if it matched a word or a non-word, and to the second '.' that it +// has to check if it succeeds a word or non-word. In this case the +// result will be something like: +// +// +-------+ +------------+ +// | . | | . | +// +-------+ ---> +------------+ +// | word? | | check word | +// +-------+ +------------+ +class Analysis : public NodeVisitor +{ + public: + Analysis(JSContext *cx, bool ignore_case, bool is_ascii) + : cx(cx), + ignore_case_(ignore_case), + is_ascii_(is_ascii), + error_message_(nullptr) + {} + + void EnsureAnalyzed(RegExpNode* node); + +#define DECLARE_VISIT(Type) \ + virtual void Visit##Type(Type##Node* that); + FOR_EACH_NODE_TYPE(DECLARE_VISIT) +#undef DECLARE_VISIT + virtual void VisitLoopChoice(LoopChoiceNode* that); + + bool has_failed() { return error_message_ != nullptr; } + const char* errorMessage() { + JS_ASSERT(error_message_ != nullptr); + return error_message_; + } + void fail(const char* error_message) { + error_message_ = error_message; + } + + private: + JSContext *cx; + bool ignore_case_; + bool is_ascii_; + const char* error_message_; + + Analysis(Analysis &) MOZ_DELETE; + void operator=(Analysis &) MOZ_DELETE; +}; + +} } // namespace js::irregexp + +#endif // V8_JSREGEXP_H_ diff --git a/js/src/irregexp/RegExpInterpreter.cpp b/js/src/irregexp/RegExpInterpreter.cpp new file mode 100644 index 00000000000..47928375316 --- /dev/null +++ b/js/src/irregexp/RegExpInterpreter.cpp @@ -0,0 +1,456 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: */ + +// Copyright 2011 the V8 project authors. 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. + +// A simple interpreter for the Irregexp byte code. + +#include "irregexp/RegExpBytecode.h" +#include "irregexp/RegExpMacroAssembler.h" +#include "vm/MatchPairs.h" + +using namespace js; +using namespace js::irregexp; + +static const size_t kBitsPerByte = 8; +static const size_t kBitsPerByteLog2 = 3; + +class MOZ_STACK_CLASS RegExpStackCursor +{ + public: + RegExpStackCursor(JSContext *cx) + : cx(cx), cursor(base()) + {} + + bool push(int32_t value) { + *cursor++ = value; + if (cursor >= stack().limit()) { + int32_t pos = position(); + if (!stack().grow()) { + js_ReportOverRecursed(cx); + return false; + } + setPosition(pos); + } + return true; + } + + int32_t pop() { + JS_ASSERT(cursor > base()); + return *--cursor; + } + + int32_t peek() { + JS_ASSERT(cursor > base()); + return *(cursor - 1); + } + + int32_t position() { + return cursor - base(); + } + + void setPosition(int32_t position) { + cursor = base() + position; + JS_ASSERT(cursor < stack().limit()); + } + + private: + JSContext *cx; + + int32_t *cursor; + + RegExpStack &stack() { return cx->runtime()->mainThread.regexpStack; } + int32_t *base() { return (int32_t *) stack().base(); } +}; + +static int32_t +Load32Aligned(const uint8_t* pc) +{ + JS_ASSERT((reinterpret_cast(pc) & 3) == 0); + return *reinterpret_cast(pc); +} + +static int32_t +Load16Aligned(const uint8_t* pc) +{ + JS_ASSERT((reinterpret_cast(pc) & 1) == 0); + return *reinterpret_cast(pc); +} + +#define BYTECODE(name) case BC_##name: + +RegExpRunStatus +irregexp::InterpretCode(JSContext *cx, const uint8_t *byteCode, + const jschar *chars, size_t current, size_t length, MatchPairs *matches) +{ + const uint8_t* pc = byteCode; + + jschar current_char = current ? chars[current - 1] : '\n'; + + RegExpStackCursor stack(cx); + + int32_t numRegisters = Load32Aligned(pc); + pc += 4; + + Vector registers; + if (!registers.growByUninitialized(numRegisters)) + return RegExpRunStatus_Error; + for (size_t i = 0; i < matches->length() * 2; i++) + registers[i] = -1; + + while (true) { + int32_t insn = Load32Aligned(pc); + switch (insn & BYTECODE_MASK) { + BYTECODE(BREAK) + MOZ_ASSUME_UNREACHABLE("Bad bytecode"); + return RegExpRunStatus_Error; + BYTECODE(PUSH_CP) + if (!stack.push(current)) + return RegExpRunStatus_Error; + pc += BC_PUSH_CP_LENGTH; + break; + BYTECODE(PUSH_BT) + if (!stack.push(Load32Aligned(pc + 4))) + return RegExpRunStatus_Error; + pc += BC_PUSH_BT_LENGTH; + break; + BYTECODE(PUSH_REGISTER) + if (!stack.push(registers[insn >> BYTECODE_SHIFT])) + return RegExpRunStatus_Error; + pc += BC_PUSH_REGISTER_LENGTH; + break; + BYTECODE(SET_REGISTER) + registers[insn >> BYTECODE_SHIFT] = Load32Aligned(pc + 4); + pc += BC_SET_REGISTER_LENGTH; + break; + BYTECODE(ADVANCE_REGISTER) + registers[insn >> BYTECODE_SHIFT] += Load32Aligned(pc + 4); + pc += BC_ADVANCE_REGISTER_LENGTH; + break; + BYTECODE(SET_REGISTER_TO_CP) + registers[insn >> BYTECODE_SHIFT] = current + Load32Aligned(pc + 4); + pc += BC_SET_REGISTER_TO_CP_LENGTH; + break; + BYTECODE(SET_CP_TO_REGISTER) + current = registers[insn >> BYTECODE_SHIFT]; + pc += BC_SET_CP_TO_REGISTER_LENGTH; + break; + BYTECODE(SET_REGISTER_TO_SP) + registers[insn >> BYTECODE_SHIFT] = stack.position(); + pc += BC_SET_REGISTER_TO_SP_LENGTH; + break; + BYTECODE(SET_SP_TO_REGISTER) + stack.setPosition(registers[insn >> BYTECODE_SHIFT]); + pc += BC_SET_SP_TO_REGISTER_LENGTH; + break; + BYTECODE(POP_CP) + current = stack.pop(); + pc += BC_POP_CP_LENGTH; + break; + BYTECODE(POP_BT) + pc = byteCode + stack.pop(); + break; + BYTECODE(POP_REGISTER) + registers[insn >> BYTECODE_SHIFT] = stack.pop(); + pc += BC_POP_REGISTER_LENGTH; + break; + BYTECODE(FAIL) + return RegExpRunStatus_Success_NotFound; + BYTECODE(SUCCEED) + memcpy(matches->pairsRaw(), registers.begin(), matches->length() * 2 * sizeof(int32_t)); + return RegExpRunStatus_Success; + BYTECODE(ADVANCE_CP) + current += insn >> BYTECODE_SHIFT; + pc += BC_ADVANCE_CP_LENGTH; + break; + BYTECODE(GOTO) + pc = byteCode + Load32Aligned(pc + 4); + break; + BYTECODE(ADVANCE_CP_AND_GOTO) + current += insn >> BYTECODE_SHIFT; + pc = byteCode + Load32Aligned(pc + 4); + break; + BYTECODE(CHECK_GREEDY) + if ((int32_t)current == stack.peek()) { + stack.pop(); + pc = byteCode + Load32Aligned(pc + 4); + } else { + pc += BC_CHECK_GREEDY_LENGTH; + } + break; + BYTECODE(LOAD_CURRENT_CHAR) { + size_t pos = current + (insn >> BYTECODE_SHIFT); + if (pos >= length) { + pc = byteCode + Load32Aligned(pc + 4); + } else { + current_char = chars[pos]; + pc += BC_LOAD_CURRENT_CHAR_LENGTH; + } + break; + } + BYTECODE(LOAD_CURRENT_CHAR_UNCHECKED) { + int pos = current + (insn >> BYTECODE_SHIFT); + current_char = chars[pos]; + pc += BC_LOAD_CURRENT_CHAR_UNCHECKED_LENGTH; + break; + } + BYTECODE(LOAD_2_CURRENT_CHARS) { + size_t pos = current + (insn >> BYTECODE_SHIFT); + if (pos + 2 > length) { + pc = byteCode + Load32Aligned(pc + 4); + } else { + jschar next = chars[pos + 1]; + current_char = (chars[pos] | (next << (kBitsPerByte * sizeof(jschar)))); + pc += BC_LOAD_2_CURRENT_CHARS_LENGTH; + } + break; + } + BYTECODE(LOAD_2_CURRENT_CHARS_UNCHECKED) { + int pos = current + (insn >> BYTECODE_SHIFT); + jschar next = chars[pos + 1]; + current_char = (chars[pos] | (next << (kBitsPerByte * sizeof(jschar)))); + pc += BC_LOAD_2_CURRENT_CHARS_UNCHECKED_LENGTH; + break; + } + BYTECODE(LOAD_4_CURRENT_CHARS) + MOZ_ASSUME_UNREACHABLE("Ascii handling implemented"); + break; + BYTECODE(LOAD_4_CURRENT_CHARS_UNCHECKED) + MOZ_ASSUME_UNREACHABLE("Ascii handling implemented"); + BYTECODE(CHECK_4_CHARS) { + uint32_t c = Load32Aligned(pc + 4); + if (c == current_char) + pc = byteCode + Load32Aligned(pc + 8); + else + pc += BC_CHECK_4_CHARS_LENGTH; + break; + } + BYTECODE(CHECK_CHAR) { + uint32_t c = (insn >> BYTECODE_SHIFT); + if (c == current_char) + pc = byteCode + Load32Aligned(pc + 4); + else + pc += BC_CHECK_CHAR_LENGTH; + break; + } + BYTECODE(CHECK_NOT_4_CHARS) { + uint32_t c = Load32Aligned(pc + 4); + if (c != current_char) + pc = byteCode + Load32Aligned(pc + 8); + else + pc += BC_CHECK_NOT_4_CHARS_LENGTH; + break; + } + BYTECODE(CHECK_NOT_CHAR) { + uint32_t c = (insn >> BYTECODE_SHIFT); + if (c != current_char) + pc = byteCode + Load32Aligned(pc + 4); + else + pc += BC_CHECK_NOT_CHAR_LENGTH; + break; + } + BYTECODE(AND_CHECK_4_CHARS) { + uint32_t c = Load32Aligned(pc + 4); + if (c == (current_char & Load32Aligned(pc + 8))) + pc = byteCode + Load32Aligned(pc + 12); + else + pc += BC_AND_CHECK_4_CHARS_LENGTH; + break; + } + BYTECODE(AND_CHECK_CHAR) { + uint32_t c = (insn >> BYTECODE_SHIFT); + if (c == (current_char & Load32Aligned(pc + 4))) + pc = byteCode + Load32Aligned(pc + 8); + else + pc += BC_AND_CHECK_CHAR_LENGTH; + break; + } + BYTECODE(AND_CHECK_NOT_4_CHARS) { + uint32_t c = Load32Aligned(pc + 4); + if (c != (current_char & Load32Aligned(pc + 8))) + pc = byteCode + Load32Aligned(pc + 12); + else + pc += BC_AND_CHECK_NOT_4_CHARS_LENGTH; + break; + } + BYTECODE(AND_CHECK_NOT_CHAR) { + uint32_t c = (insn >> BYTECODE_SHIFT); + if (c != (current_char & Load32Aligned(pc + 4))) + pc = byteCode + Load32Aligned(pc + 8); + else + pc += BC_AND_CHECK_NOT_CHAR_LENGTH; + break; + } + BYTECODE(MINUS_AND_CHECK_NOT_CHAR) { + uint32_t c = (insn >> BYTECODE_SHIFT); + uint32_t minus = Load16Aligned(pc + 4); + uint32_t mask = Load16Aligned(pc + 6); + if (c != ((current_char - minus) & mask)) + pc = byteCode + Load32Aligned(pc + 8); + else + pc += BC_MINUS_AND_CHECK_NOT_CHAR_LENGTH; + break; + } + BYTECODE(CHECK_CHAR_IN_RANGE) { + uint32_t from = Load16Aligned(pc + 4); + uint32_t to = Load16Aligned(pc + 6); + if (from <= current_char && current_char <= to) + pc = byteCode + Load32Aligned(pc + 8); + else + pc += BC_CHECK_CHAR_IN_RANGE_LENGTH; + break; + } + BYTECODE(CHECK_CHAR_NOT_IN_RANGE) { + uint32_t from = Load16Aligned(pc + 4); + uint32_t to = Load16Aligned(pc + 6); + if (from > current_char || current_char > to) + pc = byteCode + Load32Aligned(pc + 8); + else + pc += BC_CHECK_CHAR_NOT_IN_RANGE_LENGTH; + break; + } + BYTECODE(CHECK_BIT_IN_TABLE) { + int mask = RegExpMacroAssembler::kTableMask; + uint8_t b = pc[8 + ((current_char & mask) >> kBitsPerByteLog2)]; + int bit = (current_char & (kBitsPerByte - 1)); + if ((b & (1 << bit)) != 0) + pc = byteCode + Load32Aligned(pc + 4); + else + pc += BC_CHECK_BIT_IN_TABLE_LENGTH; + break; + } + BYTECODE(CHECK_LT) { + uint32_t limit = (insn >> BYTECODE_SHIFT); + if (current_char < limit) + pc = byteCode + Load32Aligned(pc + 4); + else + pc += BC_CHECK_LT_LENGTH; + break; + } + BYTECODE(CHECK_GT) { + uint32_t limit = (insn >> BYTECODE_SHIFT); + if (current_char > limit) + pc = byteCode + Load32Aligned(pc + 4); + else + pc += BC_CHECK_GT_LENGTH; + break; + } + BYTECODE(CHECK_REGISTER_LT) + if (registers[insn >> BYTECODE_SHIFT] < Load32Aligned(pc + 4)) + pc = byteCode + Load32Aligned(pc + 8); + else + pc += BC_CHECK_REGISTER_LT_LENGTH; + break; + BYTECODE(CHECK_REGISTER_GE) + if (registers[insn >> BYTECODE_SHIFT] >= Load32Aligned(pc + 4)) + pc = byteCode + Load32Aligned(pc + 8); + else + pc += BC_CHECK_REGISTER_GE_LENGTH; + break; + BYTECODE(CHECK_REGISTER_EQ_POS) + if (registers[insn >> BYTECODE_SHIFT] == (int32_t) current) + pc = byteCode + Load32Aligned(pc + 4); + else + pc += BC_CHECK_REGISTER_EQ_POS_LENGTH; + break; + BYTECODE(CHECK_NOT_REGS_EQUAL) + if (registers[insn >> BYTECODE_SHIFT] == registers[Load32Aligned(pc + 4)]) + pc += BC_CHECK_NOT_REGS_EQUAL_LENGTH; + else + pc = byteCode + Load32Aligned(pc + 8); + break; + BYTECODE(CHECK_NOT_BACK_REF) { + int from = registers[insn >> BYTECODE_SHIFT]; + int len = registers[(insn >> BYTECODE_SHIFT) + 1] - from; + if (from < 0 || len <= 0) { + pc += BC_CHECK_NOT_BACK_REF_LENGTH; + break; + } + if (current + len > length) { + pc = byteCode + Load32Aligned(pc + 4); + break; + } else { + int i; + for (i = 0; i < len; i++) { + if (chars[from + i] != chars[current + i]) { + pc = byteCode + Load32Aligned(pc + 4); + break; + } + } + if (i < len) break; + current += len; + } + pc += BC_CHECK_NOT_BACK_REF_LENGTH; + break; + } + BYTECODE(CHECK_NOT_BACK_REF_NO_CASE) { + int from = registers[insn >> BYTECODE_SHIFT]; + int len = registers[(insn >> BYTECODE_SHIFT) + 1] - from; + if (from < 0 || len <= 0) { + pc += BC_CHECK_NOT_BACK_REF_NO_CASE_LENGTH; + break; + } + if (current + len > length) { + pc = byteCode + Load32Aligned(pc + 4); + break; + } + if (CaseInsensitiveCompareStrings(chars + from, chars + current, len * 2)) { + current += len; + pc += BC_CHECK_NOT_BACK_REF_NO_CASE_LENGTH; + } else { + pc = byteCode + Load32Aligned(pc + 4); + } + break; + } + BYTECODE(CHECK_AT_START) + if (current == 0) + pc = byteCode + Load32Aligned(pc + 4); + else + pc += BC_CHECK_AT_START_LENGTH; + break; + BYTECODE(CHECK_NOT_AT_START) + if (current == 0) + pc += BC_CHECK_NOT_AT_START_LENGTH; + else + pc = byteCode + Load32Aligned(pc + 4); + break; + BYTECODE(SET_CURRENT_POSITION_FROM_END) { + size_t by = static_cast(insn) >> BYTECODE_SHIFT; + if (length - current > by) { + current = length - by; + current_char = chars[current - 1]; + } + pc += BC_SET_CURRENT_POSITION_FROM_END_LENGTH; + break; + } + default: + MOZ_ASSUME_UNREACHABLE("Bad bytecode"); + break; + } + } +} diff --git a/js/src/irregexp/RegExpMacroAssembler.cpp b/js/src/irregexp/RegExpMacroAssembler.cpp new file mode 100644 index 00000000000..2536be0c95b --- /dev/null +++ b/js/src/irregexp/RegExpMacroAssembler.cpp @@ -0,0 +1,525 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: */ + +// Copyright 2012 the V8 project authors. 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. + +#include "irregexp/RegExpMacroAssembler.h" + +#include "irregexp/RegExpBytecode.h" + +using namespace js; +using namespace js::irregexp; + +int +irregexp::CaseInsensitiveCompareStrings(const jschar *substring1, const jschar *substring2, + size_t byteLength) +{ + JS_ASSERT(byteLength % 2 == 0); + size_t length = byteLength >> 1; + + for (size_t i = 0; i < length; i++) { + jschar c1 = substring1[i]; + jschar c2 = substring2[i]; + if (c1 != c2) { + c1 = unicode::ToLowerCase(c1); + c2 = unicode::ToLowerCase(c2); + if (c1 != c2) + return 0; + } + } + + return 1; +} + +InterpretedRegExpMacroAssembler::InterpretedRegExpMacroAssembler(LifoAlloc *alloc, RegExpShared *shared, + size_t numSavedRegisters) + : RegExpMacroAssembler(*alloc, shared, numSavedRegisters), + pc_(0), + advance_current_start_(0), + advance_current_offset_(0), + advance_current_end_(kInvalidPC), + buffer_(nullptr), + length_(0) +{ + // The first int32 word is the number of registers. + Emit32(0); +} + +InterpretedRegExpMacroAssembler::~InterpretedRegExpMacroAssembler() +{ + js_free(buffer_); +} + +RegExpCode +InterpretedRegExpMacroAssembler::GenerateCode(JSContext *cx) +{ + Bind(&backtrack_); + Emit(BC_POP_BT, 0); + + // Update the number of registers. + *(int32_t *)buffer_ = num_registers_; + + RegExpCode res; + res.byteCode = buffer_; + buffer_ = nullptr; + return res; +} + +void +InterpretedRegExpMacroAssembler::AdvanceCurrentPosition(int by) +{ + JS_ASSERT(by >= kMinCPOffset); + JS_ASSERT(by <= kMaxCPOffset); + advance_current_start_ = pc_; + advance_current_offset_ = by; + Emit(BC_ADVANCE_CP, by); + advance_current_end_ = pc_; +} + +void +InterpretedRegExpMacroAssembler::AdvanceRegister(int reg, int by) +{ + checkRegister(reg); + Emit(BC_ADVANCE_REGISTER, reg); + Emit32(by); +} + +void +InterpretedRegExpMacroAssembler::Backtrack() +{ + Emit(BC_POP_BT, 0); +} + +void +InterpretedRegExpMacroAssembler::Bind(jit::Label* label) +{ + advance_current_end_ = kInvalidPC; + JS_ASSERT(!label->bound()); + if (label->used()) { + int pos = label->offset(); + while (pos != jit::Label::INVALID_OFFSET) { + int fixup = pos; + pos = *reinterpret_cast(buffer_ + fixup); + *reinterpret_cast(buffer_ + fixup) = pc_; + } + } + label->bind(pc_); +} + +void +InterpretedRegExpMacroAssembler::CheckAtStart(jit::Label* on_at_start) +{ + Emit(BC_CHECK_AT_START, 0); + EmitOrLink(on_at_start); +} + +void +InterpretedRegExpMacroAssembler::CheckCharacter(unsigned c, jit::Label* on_equal) +{ + if (c > MAX_FIRST_ARG) { + Emit(BC_CHECK_4_CHARS, 0); + Emit32(c); + } else { + Emit(BC_CHECK_CHAR, c); + } + EmitOrLink(on_equal); +} + +void +InterpretedRegExpMacroAssembler::CheckCharacterAfterAnd(unsigned c, unsigned and_with, jit::Label* on_equal) +{ + if (c > MAX_FIRST_ARG) { + Emit(BC_AND_CHECK_4_CHARS, 0); + Emit32(c); + } else { + Emit(BC_AND_CHECK_CHAR, c); + } + Emit32(and_with); + EmitOrLink(on_equal); +} + +void +InterpretedRegExpMacroAssembler::CheckCharacterGT(jschar limit, jit::Label* on_greater) +{ + Emit(BC_CHECK_GT, limit); + EmitOrLink(on_greater); +} + +void +InterpretedRegExpMacroAssembler::CheckCharacterLT(jschar limit, jit::Label* on_less) +{ + Emit(BC_CHECK_LT, limit); + EmitOrLink(on_less); +} + +void +InterpretedRegExpMacroAssembler::CheckGreedyLoop(jit::Label* on_tos_equals_current_position) +{ + Emit(BC_CHECK_GREEDY, 0); + EmitOrLink(on_tos_equals_current_position); +} + +void +InterpretedRegExpMacroAssembler::CheckNotAtStart(jit::Label* on_not_at_start) +{ + Emit(BC_CHECK_NOT_AT_START, 0); + EmitOrLink(on_not_at_start); +} + +void +InterpretedRegExpMacroAssembler::CheckNotBackReference(int start_reg, jit::Label* on_no_match) +{ + JS_ASSERT(start_reg >= 0); + JS_ASSERT(start_reg <= kMaxRegister); + Emit(BC_CHECK_NOT_BACK_REF, start_reg); + EmitOrLink(on_no_match); +} + +void +InterpretedRegExpMacroAssembler::CheckNotBackReferenceIgnoreCase(int start_reg, jit::Label* on_no_match) +{ + JS_ASSERT(start_reg >= 0); + JS_ASSERT(start_reg <= kMaxRegister); + Emit(BC_CHECK_NOT_BACK_REF_NO_CASE, start_reg); + EmitOrLink(on_no_match); +} + +void +InterpretedRegExpMacroAssembler::CheckNotCharacter(unsigned c, jit::Label* on_not_equal) +{ + if (c > MAX_FIRST_ARG) { + Emit(BC_CHECK_NOT_4_CHARS, 0); + Emit32(c); + } else { + Emit(BC_CHECK_NOT_CHAR, c); + } + EmitOrLink(on_not_equal); +} + +void +InterpretedRegExpMacroAssembler::CheckNotCharacterAfterAnd(unsigned c, unsigned and_with, + jit::Label* on_not_equal) +{ + if (c > MAX_FIRST_ARG) { + Emit(BC_AND_CHECK_NOT_4_CHARS, 0); + Emit32(c); + } else { + Emit(BC_AND_CHECK_NOT_CHAR, c); + } + Emit32(and_with); + EmitOrLink(on_not_equal); +} + +void +InterpretedRegExpMacroAssembler::CheckNotCharacterAfterMinusAnd(jschar c, jschar minus, jschar and_with, + jit::Label* on_not_equal) +{ + Emit(BC_MINUS_AND_CHECK_NOT_CHAR, c); + Emit16(minus); + Emit16(and_with); + EmitOrLink(on_not_equal); +} + +void +InterpretedRegExpMacroAssembler::CheckCharacterInRange(jschar from, jschar to, + jit::Label* on_in_range) +{ + Emit(BC_CHECK_CHAR_IN_RANGE, 0); + Emit16(from); + Emit16(to); + EmitOrLink(on_in_range); +} + +void +InterpretedRegExpMacroAssembler::CheckCharacterNotInRange(jschar from, jschar to, + jit::Label* on_not_in_range) +{ + Emit(BC_CHECK_CHAR_NOT_IN_RANGE, 0); + Emit16(from); + Emit16(to); + EmitOrLink(on_not_in_range); +} + +void +InterpretedRegExpMacroAssembler::CheckBitInTable(uint8_t *table, jit::Label* on_bit_set) +{ + static const int kBitsPerByte = 8; + + Emit(BC_CHECK_BIT_IN_TABLE, 0); + EmitOrLink(on_bit_set); + for (int i = 0; i < kTableSize; i += kBitsPerByte) { + int byte = 0; + for (int j = 0; j < kBitsPerByte; j++) { + if (table[i + j] != 0) + byte |= 1 << j; + } + Emit8(byte); + } +} + +void +InterpretedRegExpMacroAssembler::JumpOrBacktrack(jit::Label *to) +{ + if (advance_current_end_ == pc_) { + // Combine advance current and goto. + pc_ = advance_current_start_; + Emit(BC_ADVANCE_CP_AND_GOTO, advance_current_offset_); + EmitOrLink(to); + advance_current_end_ = kInvalidPC; + } else { + // Regular goto. + Emit(BC_GOTO, 0); + EmitOrLink(to); + } +} + +void +InterpretedRegExpMacroAssembler::Fail() +{ + Emit(BC_FAIL, 0); +} + +void +InterpretedRegExpMacroAssembler::IfRegisterGE(int reg, int comparand, jit::Label* if_ge) +{ + checkRegister(reg); + Emit(BC_CHECK_REGISTER_GE, reg); + Emit32(comparand); + EmitOrLink(if_ge); +} + +void +InterpretedRegExpMacroAssembler::IfRegisterLT(int reg, int comparand, jit::Label* if_lt) +{ + checkRegister(reg); + Emit(BC_CHECK_REGISTER_LT, reg); + Emit32(comparand); + EmitOrLink(if_lt); +} + +void +InterpretedRegExpMacroAssembler::IfRegisterEqPos(int reg, jit::Label* if_eq) +{ + checkRegister(reg); + Emit(BC_CHECK_REGISTER_EQ_POS, reg); + EmitOrLink(if_eq); +} + +void +InterpretedRegExpMacroAssembler::LoadCurrentCharacter(int cp_offset, jit::Label* on_end_of_input, + bool check_bounds, int characters) +{ + JS_ASSERT(cp_offset >= kMinCPOffset); + JS_ASSERT(cp_offset <= kMaxCPOffset); + int bytecode; + if (check_bounds) { + if (characters == 4) { + bytecode = BC_LOAD_4_CURRENT_CHARS; + } else if (characters == 2) { + bytecode = BC_LOAD_2_CURRENT_CHARS; + } else { + JS_ASSERT(characters == 1); + bytecode = BC_LOAD_CURRENT_CHAR; + } + } else { + if (characters == 4) { + bytecode = BC_LOAD_4_CURRENT_CHARS_UNCHECKED; + } else if (characters == 2) { + bytecode = BC_LOAD_2_CURRENT_CHARS_UNCHECKED; + } else { + JS_ASSERT(characters == 1); + bytecode = BC_LOAD_CURRENT_CHAR_UNCHECKED; + } + } + Emit(bytecode, cp_offset); + if (check_bounds) + EmitOrLink(on_end_of_input); +} + +void +InterpretedRegExpMacroAssembler::PopCurrentPosition() +{ + Emit(BC_POP_CP, 0); +} + +void +InterpretedRegExpMacroAssembler::PopRegister(int reg) +{ + checkRegister(reg); + Emit(BC_POP_REGISTER, reg); +} + +void +InterpretedRegExpMacroAssembler::PushCurrentPosition() +{ + Emit(BC_PUSH_CP, 0); +} + +void +InterpretedRegExpMacroAssembler::PushRegister(int reg, StackCheckFlag check_stack_limit) +{ + checkRegister(reg); + Emit(BC_PUSH_REGISTER, reg); +} + +void +InterpretedRegExpMacroAssembler::ReadCurrentPositionFromRegister(int reg) +{ + checkRegister(reg); + Emit(BC_SET_CP_TO_REGISTER, reg); +} + +void +InterpretedRegExpMacroAssembler::ReadBacktrackStackPointerFromRegister(int reg) +{ + checkRegister(reg); + Emit(BC_SET_SP_TO_REGISTER, reg); +} + +void +InterpretedRegExpMacroAssembler::SetCurrentPositionFromEnd(int by) +{ + JS_ASSERT(by >= 0 && by < (1 << 24)); + Emit(BC_SET_CURRENT_POSITION_FROM_END, by); +} + +void +InterpretedRegExpMacroAssembler::SetRegister(int reg, int to) +{ + checkRegister(reg); + Emit(BC_SET_REGISTER, reg); + Emit32(to); +} + +bool +InterpretedRegExpMacroAssembler::Succeed() +{ + Emit(BC_SUCCEED, 0); + + // Restart matching for global regexp not supported. + return false; +} + +void +InterpretedRegExpMacroAssembler::WriteCurrentPositionToRegister(int reg, int cp_offset) +{ + checkRegister(reg); + Emit(BC_SET_REGISTER_TO_CP, reg); + Emit32(cp_offset); // Current position offset. +} + +void +InterpretedRegExpMacroAssembler::ClearRegisters(int reg_from, int reg_to) +{ + JS_ASSERT(reg_from <= reg_to); + for (int reg = reg_from; reg <= reg_to; reg++) + SetRegister(reg, -1); +} + +void +InterpretedRegExpMacroAssembler::WriteBacktrackStackPointerToRegister(int reg) +{ + checkRegister(reg); + Emit(BC_SET_REGISTER_TO_SP, reg); +} + +void +InterpretedRegExpMacroAssembler::PushBacktrack(jit::Label *label) +{ + Emit(BC_PUSH_BT, 0); + EmitOrLink(label); +} + +void +InterpretedRegExpMacroAssembler::BindBacktrack(jit::Label *label) +{ + Bind(label); +} + +void +InterpretedRegExpMacroAssembler::EmitOrLink(jit::Label *label) +{ + if (label == nullptr) + label = &backtrack_; + if (label->bound()) { + Emit32(label->offset()); + } else { + int pos = label->use(pc_); + Emit32(pos); + } +} + +void +InterpretedRegExpMacroAssembler::Emit(uint32_t byte, uint32_t twenty_four_bits) +{ + uint32_t word = ((twenty_four_bits << BYTECODE_SHIFT) | byte); + Emit32(word); +} + +void +InterpretedRegExpMacroAssembler::Emit32(uint32_t word) +{ + JS_ASSERT(pc_ <= length_); + if (pc_ + 3 >= length_) + Expand(); + *reinterpret_cast(buffer_ + pc_) = word; + pc_ += 4; +} + +void +InterpretedRegExpMacroAssembler::Emit16(uint32_t word) +{ + JS_ASSERT(pc_ <= length_); + if (pc_ + 1 >= length_) + Expand(); + *reinterpret_cast(buffer_ + pc_) = word; + pc_ += 2; +} + +void +InterpretedRegExpMacroAssembler::Emit8(uint32_t word) +{ + JS_ASSERT(pc_ <= length_); + if (pc_ == length_) + Expand(); + *reinterpret_cast(buffer_ + pc_) = word; + pc_ += 1; +} + +void +InterpretedRegExpMacroAssembler::Expand() +{ + int newLength = Max(100, length_ * 2); + if (newLength < length_ + 4) + CrashAtUnhandlableOOM("InterpretedRegExpMacroAssembler::Expand"); + + buffer_ = (uint8_t *) js_realloc(buffer_, newLength); + if (!buffer_) + CrashAtUnhandlableOOM("InterpretedRegExpMacroAssembler::Expand"); + length_ = newLength; +} diff --git a/js/src/irregexp/RegExpMacroAssembler.h b/js/src/irregexp/RegExpMacroAssembler.h new file mode 100644 index 00000000000..62a4b4d4054 --- /dev/null +++ b/js/src/irregexp/RegExpMacroAssembler.h @@ -0,0 +1,305 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: */ + +// Copyright 2012 the V8 project authors. 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. + +#ifndef V8_REGEXP_MACRO_ASSEMBLER_H_ +#define V8_REGEXP_MACRO_ASSEMBLER_H_ + +#include "irregexp/RegExpAST.h" +#include "irregexp/RegExpEngine.h" +#include "jit/IonMacroAssembler.h" + +namespace js { +namespace irregexp { + +class MOZ_STACK_CLASS RegExpMacroAssembler +{ + public: + RegExpMacroAssembler(LifoAlloc &alloc, RegExpShared *shared, size_t numSavedRegisters) + : slow_safe_compiler_(false), + global_mode_(NOT_GLOBAL), + alloc_(alloc), + num_registers_(numSavedRegisters), + num_saved_registers_(numSavedRegisters), + shared(shared) + {} + + enum StackCheckFlag { + kNoStackLimitCheck = false, + kCheckStackLimit = true + }; + + // The implementation must be able to handle at least: + static const int kMaxRegister = (1 << 16) - 1; + static const int kMaxCPOffset = (1 << 15) - 1; + static const int kMinCPOffset = -(1 << 15); + + static const int kTableSizeBits = 7; + static const int kTableSize = 1 << kTableSizeBits; + static const int kTableMask = kTableSize - 1; + + // Controls the generation of large inlined constants in the code. + void set_slow_safe(bool ssc) { slow_safe_compiler_ = ssc; } + bool slow_safe() { return slow_safe_compiler_; } + + enum GlobalMode { NOT_GLOBAL, GLOBAL, GLOBAL_NO_ZERO_LENGTH_CHECK }; + + // Set whether the regular expression has the global flag. Exiting due to + // a failure in a global regexp may still mean success overall. + inline void set_global_mode(GlobalMode mode) { global_mode_ = mode; } + inline bool global() { return global_mode_ != NOT_GLOBAL; } + inline bool global_with_zero_length_check() { + return global_mode_ == GLOBAL; + } + + LifoAlloc &alloc() { return alloc_; } + + virtual RegExpCode GenerateCode(JSContext *cx) = 0; + + // The maximal number of pushes between stack checks. Users must supply + // kCheckStackLimit flag to push operations (instead of kNoStackLimitCheck) + // at least once for every stack_limit() pushes that are executed. + virtual int stack_limit_slack() = 0; + + virtual bool CanReadUnaligned() { return false; } + + virtual void AdvanceCurrentPosition(int by) = 0; // Signed cp change. + virtual void AdvanceRegister(int reg, int by) = 0; // r[reg] += by. + + // Continues execution from the position pushed on the top of the backtrack + // stack by an earlier PushBacktrack. + virtual void Backtrack() = 0; + + virtual void Bind(jit::Label* label) = 0; + virtual void CheckAtStart(jit::Label* on_at_start) = 0; + + // Dispatch after looking the current character up in a 2-bits-per-entry + // map. The destinations vector has up to 4 labels. + virtual void CheckCharacter(unsigned c, jit::Label* on_equal) = 0; + + // Bitwise and the current character with the given constant and then + // check for a match with c. + virtual void CheckCharacterAfterAnd(unsigned c, unsigned and_with, jit::Label* on_equal) = 0; + + virtual void CheckCharacterGT(jschar limit, jit::Label* on_greater) = 0; + virtual void CheckCharacterLT(jschar limit, jit::Label* on_less) = 0; + virtual void CheckGreedyLoop(jit::Label* on_tos_equals_current_position) = 0; + virtual void CheckNotAtStart(jit::Label* on_not_at_start) = 0; + virtual void CheckNotBackReference(int start_reg, jit::Label* on_no_match) = 0; + virtual void CheckNotBackReferenceIgnoreCase(int start_reg, jit::Label* on_no_match) = 0; + + // Check the current character for a match with a literal character. If we + // fail to match then goto the on_failure label. End of input always + // matches. If the label is nullptr then we should pop a backtrack address off + // the stack and go to that. + virtual void CheckNotCharacter(unsigned c, jit::Label* on_not_equal) = 0; + virtual void CheckNotCharacterAfterAnd(unsigned c, unsigned and_with, jit::Label* on_not_equal) = 0; + + // Subtract a constant from the current character, then and with the given + // constant and then check for a match with c. + virtual void CheckNotCharacterAfterMinusAnd(jschar c, + jschar minus, + jschar and_with, + jit::Label* on_not_equal) = 0; + + virtual void CheckCharacterInRange(jschar from, jschar to, // Both inclusive. + jit::Label* on_in_range) = 0; + + virtual void CheckCharacterNotInRange(jschar from, jschar to, // Both inclusive. + jit::Label* on_not_in_range) = 0; + + // The current character (modulus the kTableSize) is looked up in the byte + // array, and if the found byte is non-zero, we jump to the on_bit_set label. + virtual void CheckBitInTable(uint8_t *table, jit::Label* on_bit_set) = 0; + + // Checks whether the given offset from the current position is before + // the end of the string. May overwrite the current character. + virtual void CheckPosition(int cp_offset, jit::Label* on_outside_input) { + LoadCurrentCharacter(cp_offset, on_outside_input, true); + } + + // Jump to either the target label or the top of the backtrack stack. + virtual void JumpOrBacktrack(jit::Label *to) = 0; + + // Check whether a standard/default character class matches the current + // character. Returns false if the type of special character class does + // not have custom support. + // May clobber the current loaded character. + virtual bool CheckSpecialCharacterClass(jschar type, jit::Label* on_no_match) { + return false; + } + + virtual void Fail() = 0; + + // Check whether a register is >= a given constant and go to a label if it + // is. Backtracks instead if the label is nullptr. + virtual void IfRegisterGE(int reg, int comparand, jit::Label *if_ge) = 0; + + // Check whether a register is < a given constant and go to a label if it is. + // Backtracks instead if the label is nullptr. + virtual void IfRegisterLT(int reg, int comparand, jit::Label *if_lt) = 0; + + // Check whether a register is == to the current position and go to a + // label if it is. + virtual void IfRegisterEqPos(int reg, jit::Label *if_eq) = 0; + + virtual void LoadCurrentCharacter(int cp_offset, + jit::Label *on_end_of_input, + bool check_bounds = true, + int characters = 1) = 0; + virtual void PopCurrentPosition() = 0; + virtual void PopRegister(int register_index) = 0; + + virtual void PushCurrentPosition() = 0; + virtual void PushRegister(int register_index, StackCheckFlag check_stack_limit) = 0; + virtual void ReadCurrentPositionFromRegister(int reg) = 0; + virtual void ReadBacktrackStackPointerFromRegister(int reg) = 0; + virtual void SetCurrentPositionFromEnd(int by) = 0; + virtual void SetRegister(int register_index, int to) = 0; + + // Return whether the matching (with a global regexp) will be restarted. + virtual bool Succeed() = 0; + + virtual void WriteCurrentPositionToRegister(int reg, int cp_offset) = 0; + virtual void ClearRegisters(int reg_from, int reg_to) = 0; + virtual void WriteBacktrackStackPointerToRegister(int reg) = 0; + + // Pushes the label on the backtrack stack, so that a following Backtrack + // will go to this label. Always checks the backtrack stack limit. + virtual void PushBacktrack(jit::Label *label) = 0; + + // Bind a label that was previously used by PushBacktrack. + virtual void BindBacktrack(jit::Label *label) = 0; + + private: + bool slow_safe_compiler_; + GlobalMode global_mode_; + LifoAlloc &alloc_; + + protected: + int num_registers_; + int num_saved_registers_; + + void checkRegister(int reg) { + JS_ASSERT(reg >= 0); + JS_ASSERT(reg <= kMaxRegister); + if (num_registers_ <= reg) + num_registers_ = reg + 1; + } + + public: + RegExpShared *shared; +}; + +int +CaseInsensitiveCompareStrings(const jschar *substring1, const jschar *substring2, size_t byteLength); + +class MOZ_STACK_CLASS InterpretedRegExpMacroAssembler : public RegExpMacroAssembler +{ + public: + InterpretedRegExpMacroAssembler(LifoAlloc *alloc, RegExpShared *shared, size_t numSavedRegisters); + ~InterpretedRegExpMacroAssembler(); + + // Inherited virtual methods. + RegExpCode GenerateCode(JSContext *cx); + void AdvanceCurrentPosition(int by); + void AdvanceRegister(int reg, int by); + void Backtrack(); + void Bind(jit::Label* label); + void CheckAtStart(jit::Label* on_at_start); + void CheckCharacter(unsigned c, jit::Label* on_equal); + void CheckCharacterAfterAnd(unsigned c, unsigned and_with, jit::Label* on_equal); + void CheckCharacterGT(jschar limit, jit::Label* on_greater); + void CheckCharacterLT(jschar limit, jit::Label* on_less); + void CheckGreedyLoop(jit::Label* on_tos_equals_current_position); + void CheckNotAtStart(jit::Label* on_not_at_start); + void CheckNotBackReference(int start_reg, jit::Label* on_no_match); + void CheckNotBackReferenceIgnoreCase(int start_reg, jit::Label* on_no_match); + void CheckNotCharacter(unsigned c, jit::Label* on_not_equal); + void CheckNotCharacterAfterAnd(unsigned c, unsigned and_with, jit::Label* on_not_equal); + void CheckNotCharacterAfterMinusAnd(jschar c, jschar minus, jschar and_with, + jit::Label* on_not_equal); + void CheckCharacterInRange(jschar from, jschar to, + jit::Label* on_in_range); + void CheckCharacterNotInRange(jschar from, jschar to, + jit::Label* on_not_in_range); + void CheckBitInTable(uint8_t *table, jit::Label* on_bit_set); + void JumpOrBacktrack(jit::Label *to); + void Fail(); + void IfRegisterGE(int reg, int comparand, jit::Label* if_ge); + void IfRegisterLT(int reg, int comparand, jit::Label* if_lt); + void IfRegisterEqPos(int reg, jit::Label* if_eq); + void LoadCurrentCharacter(int cp_offset, jit::Label* on_end_of_input, + bool check_bounds = true, int characters = 1); + void PopCurrentPosition(); + void PopRegister(int register_index); + void PushCurrentPosition(); + void PushRegister(int register_index, StackCheckFlag check_stack_limit); + void ReadCurrentPositionFromRegister(int reg); + void ReadBacktrackStackPointerFromRegister(int reg); + void SetCurrentPositionFromEnd(int by); + void SetRegister(int register_index, int to); + bool Succeed(); + void WriteCurrentPositionToRegister(int reg, int cp_offset); + void ClearRegisters(int reg_from, int reg_to); + void WriteBacktrackStackPointerToRegister(int reg); + void PushBacktrack(jit::Label *label); + void BindBacktrack(jit::Label *label); + + // The byte-code interpreter checks on each push anyway. + int stack_limit_slack() { return 1; } + + private: + void Expand(); + + // Code and bitmap emission. + void EmitOrLink(jit::Label* label); + void Emit32(uint32_t x); + void Emit16(uint32_t x); + void Emit8(uint32_t x); + void Emit(uint32_t bc, uint32_t arg); + + jit::Label backtrack_; + + // The program counter. + int pc_; + + int advance_current_start_; + int advance_current_offset_; + int advance_current_end_; + + static const int kInvalidPC = -1; + + uint8_t *buffer_; + int length_; +}; + +} } // namespace js::irregexp + +#endif // V8_REGEXP_MACRO_ASSEMBLER_H_ diff --git a/js/src/irregexp/RegExpParser.cpp b/js/src/irregexp/RegExpParser.cpp new file mode 100644 index 00000000000..d37cb287e72 --- /dev/null +++ b/js/src/irregexp/RegExpParser.cpp @@ -0,0 +1,1009 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: */ + +// Copyright 2012 the V8 project authors. 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. + +#include "irregexp/RegExpParser.h" + +#include "frontend/TokenStream.h" + +using namespace js; +using namespace js::irregexp; + +// ---------------------------------------------------------------------------- +// RegExpBuilder + +RegExpBuilder::RegExpBuilder(LifoAlloc *alloc) + : alloc(alloc), + pending_empty_(false), + characters_(nullptr), + last_added_(ADD_NONE) +{} + +void +RegExpBuilder::FlushCharacters() +{ + pending_empty_ = false; + if (characters_ != nullptr) { + RegExpTree* atom = alloc->newInfallible(characters_); + characters_ = nullptr; + text_.Add(alloc, atom); + last_added_ = ADD_ATOM; + } +} + +void +RegExpBuilder::FlushText() +{ + FlushCharacters(); + int num_text = text_.length(); + if (num_text == 0) + return; + if (num_text == 1) { + terms_.Add(alloc, text_.last()); + } else { + RegExpText* text = alloc->newInfallible(alloc); + for (int i = 0; i < num_text; i++) + text_.Get(i)->AppendToText(text); + terms_.Add(alloc, text); + } + text_.Clear(); +} + +void +RegExpBuilder::AddCharacter(jschar c) +{ + pending_empty_ = false; + if (characters_ == nullptr) + characters_ = alloc->newInfallible(*alloc); + characters_->append(c); + last_added_ = ADD_CHAR; +} + +void +RegExpBuilder::AddEmpty() +{ + pending_empty_ = true; +} + +void +RegExpBuilder::AddAtom(RegExpTree* term) +{ + if (term->IsEmpty()) { + AddEmpty(); + return; + } + if (term->IsTextElement()) { + FlushCharacters(); + text_.Add(alloc, term); + } else { + FlushText(); + terms_.Add(alloc, term); + } + last_added_ = ADD_ATOM; +} + +void +RegExpBuilder::AddAssertion(RegExpTree *assert) +{ + FlushText(); + terms_.Add(alloc, assert); + last_added_ = ADD_ASSERT; +} + +void +RegExpBuilder::NewAlternative() +{ + FlushTerms(); +} + +void +RegExpBuilder::FlushTerms() +{ + FlushText(); + int num_terms = terms_.length(); + RegExpTree* alternative; + if (num_terms == 0) + alternative = RegExpEmpty::GetInstance(); + else if (num_terms == 1) + alternative = terms_.last(); + else + alternative = alloc->newInfallible(terms_.GetList(alloc)); + alternatives_.Add(alloc, alternative); + terms_.Clear(); + last_added_ = ADD_NONE; +} + +RegExpTree * +RegExpBuilder::ToRegExp() +{ + FlushTerms(); + int num_alternatives = alternatives_.length(); + if (num_alternatives == 0) { + return RegExpEmpty::GetInstance(); + } + if (num_alternatives == 1) { + return alternatives_.last(); + } + return alloc->newInfallible(alternatives_.GetList(alloc)); +} + +void +RegExpBuilder::AddQuantifierToAtom(int min, int max, + RegExpQuantifier::QuantifierType quantifier_type) +{ + if (pending_empty_) { + pending_empty_ = false; + return; + } + RegExpTree* atom; + if (characters_ != nullptr) { + JS_ASSERT(last_added_ == ADD_CHAR); + // Last atom was character. + CharacterVector *char_vector = characters_; + int num_chars = char_vector->length(); + if (num_chars > 1) { + CharacterVector *prefix = alloc->newInfallible(*alloc); + prefix->append(char_vector->begin(), num_chars - 1); + text_.Add(alloc, alloc->newInfallible(prefix)); + char_vector = alloc->newInfallible(*alloc); + char_vector->append((*characters_)[num_chars - 1]); + } + characters_ = nullptr; + atom = alloc->newInfallible(char_vector); + FlushText(); + } else if (text_.length() > 0) { + JS_ASSERT(last_added_ == ADD_ATOM); + atom = text_.RemoveLast(); + FlushText(); + } else if (terms_.length() > 0) { + JS_ASSERT(last_added_ == ADD_ATOM); + atom = terms_.RemoveLast(); + if (atom->max_match() == 0) { + // Guaranteed to only match an empty string. + last_added_ = ADD_TERM; + if (min == 0) + return; + terms_.Add(alloc, atom); + return; + } + } else { + // Only call immediately after adding an atom or character! + MOZ_ASSUME_UNREACHABLE("Bad call"); + return; + } + terms_.Add(alloc, alloc->newInfallible(min, max, quantifier_type, atom)); + last_added_ = ADD_TERM; +} + +// ---------------------------------------------------------------------------- +// RegExpParser + +RegExpParser::RegExpParser(frontend::TokenStream &ts, LifoAlloc *alloc, + const jschar *chars, const jschar *end, bool multiline_mode) + : ts(ts), + alloc(alloc), + captures_(nullptr), + next_pos_(chars), + end_(end), + current_(kEndMarker), + capture_count_(0), + has_more_(true), + multiline_(multiline_mode), + simple_(false), + contains_anchor_(false), + is_scanned_for_captures_(false) +{ + Advance(); +} + +RegExpTree * +RegExpParser::ReportError(unsigned errorNumber) +{ + ts.reportError(errorNumber); + return nullptr; +} + +void +RegExpParser::Advance() +{ + if (next_pos_ < end_) { + current_ = *next_pos_; + next_pos_++; + } else { + current_ = kEndMarker; + has_more_ = false; + } +} + +// Returns the value (0 .. 15) of a hexadecimal character c. +// If c is not a legal hexadecimal character, returns a value < 0. +inline int +HexValue(uint32_t c) +{ + c -= '0'; + if (static_cast(c) <= 9) return c; + c = (c | 0x20) - ('a' - '0'); // detect 0x11..0x16 and 0x31..0x36. + if (static_cast(c) <= 5) return c + 10; + return -1; +} + +size_t +RegExpParser::ParseOctalLiteral() +{ + JS_ASSERT('0' <= current() && current() <= '7'); + // For compatibility with some other browsers (not all), we parse + // up to three octal digits with a value below 256. + widechar value = current() - '0'; + Advance(); + if ('0' <= current() && current() <= '7') { + value = value * 8 + current() - '0'; + Advance(); + if (value < 32 && '0' <= current() && current() <= '7') { + value = value * 8 + current() - '0'; + Advance(); + } + } + return value; +} + +bool +RegExpParser::ParseHexEscape(int length, size_t *value) +{ + const jschar *start = position(); + uint32_t val = 0; + bool done = false; + for (int i = 0; !done; i++) { + widechar c = current(); + int d = HexValue(c); + if (d < 0) { + Reset(start); + return false; + } + val = val * 16 + d; + Advance(); + if (i == length - 1) { + done = true; + } + } + *value = val; + return true; +} + +#ifdef DEBUG +// Currently only used in an assert.kASSERT. +static bool +IsSpecialClassEscape(widechar c) +{ + switch (c) { + case 'd': case 'D': + case 's': case 'S': + case 'w': case 'W': + return true; + default: + return false; + } +} +#endif + +widechar +RegExpParser::ParseClassCharacterEscape() +{ + JS_ASSERT(current() == '\\'); + JS_ASSERT(has_next() && !IsSpecialClassEscape(Next())); + Advance(); + switch (current()) { + case 'b': + Advance(); + return '\b'; + // ControlEscape :: one of + // f n r t v + case 'f': + Advance(); + return '\f'; + case 'n': + Advance(); + return '\n'; + case 'r': + Advance(); + return '\r'; + case 't': + Advance(); + return '\t'; + case 'v': + Advance(); + return '\v'; + case 'c': { + widechar controlLetter = Next(); + widechar letter = controlLetter & ~('A' ^ 'a'); + // For compatibility with JSC, inside a character class + // we also accept digits and underscore as control characters. + if ((controlLetter >= '0' && controlLetter <= '9') || + controlLetter == '_' || + (letter >= 'A' && letter <= 'Z')) { + Advance(2); + // Control letters mapped to ASCII control characters in the range + // 0x00-0x1f. + return controlLetter & 0x1f; + } + // We match JSC in reading the backslash as a literal + // character instead of as starting an escape. + return '\\'; + } + case '0': case '1': case '2': case '3': case '4': case '5': + case '6': case '7': + // For compatibility, we interpret a decimal escape that isn't + // a back reference (and therefore either \0 or not valid according + // to the specification) as a 1..3 digit octal character code. + return ParseOctalLiteral(); + case 'x': { + Advance(); + size_t value; + if (ParseHexEscape(2, &value)) + return value; + // If \x is not followed by a two-digit hexadecimal, treat it + // as an identity escape. + return 'x'; + } + case 'u': { + Advance(); + size_t value; + if (ParseHexEscape(4, &value)) + return value; + // If \u is not followed by a four-digit hexadecimal, treat it + // as an identity escape. + return 'u'; + } + default: { + // Extended identity escape. We accept any character that hasn't + // been matched by a more specific case, not just the subset required + // by the ECMAScript specification. + widechar result = current(); + Advance(); + return result; + } + } + return 0; +} + +static const jschar kNoCharClass = 0; + +// Adds range or pre-defined character class to character ranges. +// If char_class is not kInvalidClass, it's interpreted as a class +// escape (i.e., 's' means whitespace, from '\s'). +static inline void +AddRangeOrEscape(LifoAlloc *alloc, + CharacterRangeVector *ranges, + jschar char_class, + CharacterRange range) +{ + if (char_class != kNoCharClass) + CharacterRange::AddClassEscape(alloc, char_class, ranges); + else + ranges->append(range); +} + +RegExpTree* +RegExpParser::ParseCharacterClass() +{ + JS_ASSERT(current() == '['); + Advance(); + bool is_negated = false; + if (current() == '^') { + is_negated = true; + Advance(); + } + CharacterRangeVector *ranges = alloc->newInfallible(*alloc); + while (has_more() && current() != ']') { + jschar char_class = kNoCharClass; + CharacterRange first; + if (!ParseClassAtom(&char_class, &first)) + return nullptr; + if (current() == '-') { + Advance(); + if (current() == kEndMarker) { + // If we reach the end we break out of the loop and let the + // following code report an error. + break; + } else if (current() == ']') { + AddRangeOrEscape(alloc, ranges, char_class, first); + ranges->append(CharacterRange::Singleton('-')); + break; + } + jschar char_class_2 = kNoCharClass; + CharacterRange next; + if (!ParseClassAtom(&char_class_2, &next)) + return nullptr; + if (char_class != kNoCharClass || char_class_2 != kNoCharClass) { + // Either end is an escaped character class. Treat the '-' verbatim. + AddRangeOrEscape(alloc, ranges, char_class, first); + ranges->append(CharacterRange::Singleton('-')); + AddRangeOrEscape(alloc, ranges, char_class_2, next); + continue; + } + if (first.from() > next.to()) + return ReportError(JSMSG_BAD_CLASS_RANGE); + ranges->append(CharacterRange::Range(first.from(), next.to())); + } else { + AddRangeOrEscape(alloc, ranges, char_class, first); + } + } + if (!has_more()) + return ReportError(JSMSG_UNTERM_CLASS); + Advance(); + if (ranges->length() == 0) { + ranges->append(CharacterRange::Everything()); + is_negated = !is_negated; + } + return alloc->newInfallible(ranges, is_negated); +} + +bool +RegExpParser::ParseClassAtom(jschar* char_class, CharacterRange *char_range) +{ + JS_ASSERT(*char_class == kNoCharClass); + widechar first = current(); + if (first == '\\') { + switch (Next()) { + case 'w': case 'W': case 'd': case 'D': case 's': case 'S': { + *char_class = Next(); + Advance(2); + return true; + } + case kEndMarker: + return ReportError(JSMSG_ESCAPE_AT_END_OF_REGEXP); + default: + widechar c = ParseClassCharacterEscape(); + *char_range = CharacterRange::Singleton(c); + return true; + } + } else { + Advance(); + *char_range = CharacterRange::Singleton(first); + return true; + } +} + +// In order to know whether an escape is a backreference or not we have to scan +// the entire regexp and find the number of capturing parentheses. However we +// don't want to scan the regexp twice unless it is necessary. This mini-parser +// is called when needed. It can see the difference between capturing and +// noncapturing parentheses and can skip character classes and backslash-escaped +// characters. +void +RegExpParser::ScanForCaptures() +{ + // Start with captures started previous to current position + int capture_count = captures_started(); + // Add count of captures after this position. + widechar n; + while ((n = current()) != kEndMarker) { + Advance(); + switch (n) { + case '\\': + Advance(); + break; + case '[': { + int c; + while ((c = current()) != kEndMarker) { + Advance(); + if (c == '\\') { + Advance(); + } else { + if (c == ']') break; + } + } + break; + } + case '(': + if (current() != '?') capture_count++; + break; + } + } + capture_count_ = capture_count; + is_scanned_for_captures_ = true; +} + +inline bool +IsInRange(int value, int lower_limit, int higher_limit) +{ + JS_ASSERT(lower_limit <= higher_limit); + return static_cast(value - lower_limit) <= + static_cast(higher_limit - lower_limit); +} + +inline bool +IsDecimalDigit(widechar c) +{ + // ECMA-262, 3rd, 7.8.3 (p 16) + return IsInRange(c, '0', '9'); +} + +bool +RegExpParser::ParseBackReferenceIndex(int* index_out) +{ + JS_ASSERT('\\' == current()); + JS_ASSERT('1' <= Next() && Next() <= '9'); + + // Try to parse a decimal literal that is no greater than the total number + // of left capturing parentheses in the input. + const jschar *start = position(); + int value = Next() - '0'; + Advance(2); + while (true) { + widechar c = current(); + if (IsDecimalDigit(c)) { + value = 10 * value + (c - '0'); + if (value > kMaxCaptures) { + Reset(start); + return false; + } + Advance(); + } else { + break; + } + } + if (value > captures_started()) { + if (!is_scanned_for_captures_) { + const jschar *saved_position = position(); + ScanForCaptures(); + Reset(saved_position); + } + if (value > capture_count_) { + Reset(start); + return false; + } + } + *index_out = value; + return true; +} + +// QuantifierPrefix :: +// { DecimalDigits } +// { DecimalDigits , } +// { DecimalDigits , DecimalDigits } +// +// Returns true if parsing succeeds, and set the min_out and max_out +// values. Values are truncated to RegExpTree::kInfinity if they overflow. +bool +RegExpParser::ParseIntervalQuantifier(int* min_out, int* max_out) +{ + JS_ASSERT(current() == '{'); + const jschar *start = position(); + Advance(); + int min = 0; + if (!IsDecimalDigit(current())) { + Reset(start); + return false; + } + while (IsDecimalDigit(current())) { + int next = current() - '0'; + if (min > (RegExpTree::kInfinity - next) / 10) { + // Overflow. Skip past remaining decimal digits and return -1. + do { + Advance(); + } while (IsDecimalDigit(current())); + min = RegExpTree::kInfinity; + break; + } + min = 10 * min + next; + Advance(); + } + int max = 0; + if (current() == '}') { + max = min; + Advance(); + } else if (current() == ',') { + Advance(); + if (current() == '}') { + max = RegExpTree::kInfinity; + Advance(); + } else { + while (IsDecimalDigit(current())) { + int next = current() - '0'; + if (max > (RegExpTree::kInfinity - next) / 10) { + do { + Advance(); + } while (IsDecimalDigit(current())); + max = RegExpTree::kInfinity; + break; + } + max = 10 * max + next; + Advance(); + } + if (current() != '}') { + Reset(start); + return false; + } + Advance(); + } + } else { + Reset(start); + return false; + } + *min_out = min; + *max_out = max; + return true; +} + +// Pattern :: +// Disjunction +RegExpTree * +RegExpParser::ParsePattern() +{ + RegExpTree* result = ParseDisjunction(); + JS_ASSERT_IF(result, !has_more()); + return result; +} + +// Disjunction :: +// Alternative +// Alternative | Disjunction +// Alternative :: +// [empty] +// Term Alternative +// Term :: +// Assertion +// Atom +// Atom Quantifier +RegExpTree* RegExpParser::ParseDisjunction() +{ + // Used to store current state while parsing subexpressions. + RegExpParserState initial_state(alloc, nullptr, INITIAL, 0); + RegExpParserState* stored_state = &initial_state; + // Cache the builder in a local variable for quick access. + RegExpBuilder* builder = initial_state.builder(); + while (true) { + switch (current()) { + case kEndMarker: + if (stored_state->IsSubexpression()) { + // Inside a parenthesized group when hitting end of input. + return ReportError(JSMSG_MISSING_PAREN); + } + JS_ASSERT(INITIAL == stored_state->group_type()); + // Parsing completed successfully. + return builder->ToRegExp(); + case ')': { + if (!stored_state->IsSubexpression()) + return ReportError(JSMSG_UNMATCHED_RIGHT_PAREN); + JS_ASSERT(INITIAL != stored_state->group_type()); + + Advance(); + // End disjunction parsing and convert builder content to new single + // regexp atom. + RegExpTree* body = builder->ToRegExp(); + + int end_capture_index = captures_started(); + + int capture_index = stored_state->capture_index(); + SubexpressionType group_type = stored_state->group_type(); + + // Restore previous state. + stored_state = stored_state->previous_state(); + builder = stored_state->builder(); + + // Build result of subexpression. + if (group_type == CAPTURE) { + RegExpCapture* capture = alloc->newInfallible(body, capture_index); + (*captures_)[capture_index - 1] = capture; + body = capture; + } else if (group_type != GROUPING) { + JS_ASSERT(group_type == POSITIVE_LOOKAHEAD || + group_type == NEGATIVE_LOOKAHEAD); + bool is_positive = (group_type == POSITIVE_LOOKAHEAD); + body = alloc->newInfallible(body, + is_positive, + end_capture_index - capture_index, + capture_index); + } + builder->AddAtom(body); + // For compatability with JSC and ES3, we allow quantifiers after + // lookaheads, and break in all cases. + break; + } + case '|': { + Advance(); + builder->NewAlternative(); + continue; + } + case '*': + case '+': + case '?': + return ReportError(JSMSG_NOTHING_TO_REPEAT); + case '^': { + Advance(); + if (multiline_) { + builder->AddAssertion(alloc->newInfallible(RegExpAssertion::START_OF_LINE)); + } else { + builder->AddAssertion(alloc->newInfallible(RegExpAssertion::START_OF_INPUT)); + set_contains_anchor(); + } + continue; + } + case '$': { + Advance(); + RegExpAssertion::AssertionType assertion_type = + multiline_ ? RegExpAssertion::END_OF_LINE : + RegExpAssertion::END_OF_INPUT; + builder->AddAssertion(alloc->newInfallible(assertion_type)); + continue; + } + case '.': { + Advance(); + // everything except \x0a, \x0d, \u2028 and \u2029 + CharacterRangeVector *ranges = alloc->newInfallible(*alloc); + CharacterRange::AddClassEscape(alloc, '.', ranges); + RegExpTree* atom = alloc->newInfallible(ranges, false); + builder->AddAtom(atom); + break; + } + case '(': { + SubexpressionType subexpr_type = CAPTURE; + Advance(); + if (current() == '?') { + switch (Next()) { + case ':': + subexpr_type = GROUPING; + break; + case '=': + subexpr_type = POSITIVE_LOOKAHEAD; + break; + case '!': + subexpr_type = NEGATIVE_LOOKAHEAD; + break; + default: + return ReportError(JSMSG_INVALID_GROUP); + } + Advance(2); + } else { + if (captures_ == nullptr) + captures_ = alloc->newInfallible(*alloc); + if (captures_started() >= kMaxCaptures) + return ReportError(JSMSG_TOO_MANY_PARENS); + captures_->append((RegExpCapture *) nullptr); + } + // Store current state and begin new disjunction parsing. + stored_state = alloc->newInfallible(alloc, stored_state, subexpr_type, + captures_started()); + builder = stored_state->builder(); + continue; + } + case '[': { + RegExpTree* atom = ParseCharacterClass(); + if (!atom) + return nullptr; + builder->AddAtom(atom); + break; + } + // Atom :: + // \ AtomEscape + case '\\': + switch (Next()) { + case kEndMarker: + return ReportError(JSMSG_ESCAPE_AT_END_OF_REGEXP); + case 'b': + Advance(2); + builder->AddAssertion(alloc->newInfallible(RegExpAssertion::BOUNDARY)); + continue; + case 'B': + Advance(2); + builder->AddAssertion(alloc->newInfallible(RegExpAssertion::NON_BOUNDARY)); + continue; + // AtomEscape :: + // CharacterClassEscape + // + // CharacterClassEscape :: one of + // d D s S w W + case 'd': case 'D': case 's': case 'S': case 'w': case 'W': { + widechar c = Next(); + Advance(2); + CharacterRangeVector *ranges = + alloc->newInfallible(*alloc); + CharacterRange::AddClassEscape(alloc, c, ranges); + RegExpTree* atom = alloc->newInfallible(ranges, false); + builder->AddAtom(atom); + break; + } + case '1': case '2': case '3': case '4': case '5': case '6': + case '7': case '8': case '9': { + int index = 0; + if (ParseBackReferenceIndex(&index)) { + RegExpCapture* capture = nullptr; + if (captures_ != nullptr && index <= (int) captures_->length()) { + capture = (*captures_)[index - 1]; + } + if (capture == nullptr) { + builder->AddEmpty(); + break; + } + RegExpTree *atom = alloc->newInfallible(capture); + builder->AddAtom(atom); + break; + } + widechar first_digit = Next(); + if (first_digit == '8' || first_digit == '9') { + // Treat as identity escape + builder->AddCharacter(first_digit); + Advance(2); + break; + } + } + // FALLTHROUGH + case '0': { + Advance(); + size_t octal = ParseOctalLiteral(); + builder->AddCharacter(octal); + break; + } + // ControlEscape :: one of + // f n r t v + case 'f': + Advance(2); + builder->AddCharacter('\f'); + break; + case 'n': + Advance(2); + builder->AddCharacter('\n'); + break; + case 'r': + Advance(2); + builder->AddCharacter('\r'); + break; + case 't': + Advance(2); + builder->AddCharacter('\t'); + break; + case 'v': + Advance(2); + builder->AddCharacter('\v'); + break; + case 'c': { + Advance(); + widechar controlLetter = Next(); + // Special case if it is an ASCII letter. + // Convert lower case letters to uppercase. + widechar letter = controlLetter & ~('a' ^ 'A'); + if (letter < 'A' || 'Z' < letter) { + // controlLetter is not in range 'A'-'Z' or 'a'-'z'. + // This is outside the specification. We match JSC in + // reading the backslash as a literal character instead + // of as starting an escape. + builder->AddCharacter('\\'); + } else { + Advance(2); + builder->AddCharacter(controlLetter & 0x1f); + } + break; + } + case 'x': { + Advance(2); + size_t value; + if (ParseHexEscape(2, &value)) { + builder->AddCharacter(value); + } else { + builder->AddCharacter('x'); + } + break; + } + case 'u': { + Advance(2); + size_t value; + if (ParseHexEscape(4, &value)) { + builder->AddCharacter(value); + } else { + builder->AddCharacter('u'); + } + break; + } + default: + // Identity escape. + builder->AddCharacter(Next()); + Advance(2); + break; + } + break; + case '{': { + int dummy; + if (ParseIntervalQuantifier(&dummy, &dummy)) + return ReportError(JSMSG_NOTHING_TO_REPEAT); + // fallthrough + } + default: + builder->AddCharacter(current()); + Advance(); + break; + } // end switch(current()) + + int min; + int max; + switch (current()) { + // QuantifierPrefix :: + // * + // + + // ? + // { + case '*': + min = 0; + max = RegExpTree::kInfinity; + Advance(); + break; + case '+': + min = 1; + max = RegExpTree::kInfinity; + Advance(); + break; + case '?': + min = 0; + max = 1; + Advance(); + break; + case '{': + if (ParseIntervalQuantifier(&min, &max)) { + if (max < min) + return ReportError(JSMSG_NUMBERS_OUT_OF_ORDER); + break; + } else { + continue; + } + default: + continue; + } + RegExpQuantifier::QuantifierType quantifier_type = RegExpQuantifier::GREEDY; + if (current() == '?') { + quantifier_type = RegExpQuantifier::NON_GREEDY; + Advance(); + } + builder->AddQuantifierToAtom(min, max, quantifier_type); + } +} + +bool +irregexp::ParsePattern(frontend::TokenStream &ts, LifoAlloc &alloc, + const jschar *chars, size_t length, bool multiline, + RegExpCompileData *data) +{ + RegExpParser parser(ts, &alloc, chars, chars + length, multiline); + data->tree = parser.ParsePattern(); + if (!data->tree) + return false; + + data->simple = parser.simple(); + data->contains_anchor = parser.contains_anchor(); + data->capture_count = parser.captures_started(); + return true; +} + +bool +irregexp::ParsePatternSyntax(frontend::TokenStream &ts, LifoAlloc &alloc, + const jschar *chars, size_t length) +{ + LifoAllocScope scope(&alloc); + + RegExpParser parser(ts, &alloc, chars, chars + length, false); + return parser.ParsePattern() != nullptr; +} diff --git a/js/src/irregexp/RegExpParser.h b/js/src/irregexp/RegExpParser.h new file mode 100644 index 00000000000..d6fde26575c --- /dev/null +++ b/js/src/irregexp/RegExpParser.h @@ -0,0 +1,298 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: */ + +// Copyright 2012 the V8 project authors. 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. + +#ifndef V8_PARSER_H_ +#define V8_PARSER_H_ + +#include "irregexp/RegExpAST.h" + +namespace js { + +namespace frontend { + class TokenStream; +} + +namespace irregexp { + +bool +ParsePattern(frontend::TokenStream &ts, LifoAlloc &alloc, + const jschar *chars, size_t length, bool multiline, + RegExpCompileData *data); + +bool +ParsePatternSyntax(frontend::TokenStream &ts, LifoAlloc &alloc, + const jschar *chars, size_t length); + +// A BufferedVector is an automatically growing list, just like (and backed +// by) a Vector, that is optimized for the case of adding and removing +// a single element. The last element added is stored outside the backing list, +// and if no more than one element is ever added, the ZoneList isn't even +// allocated. +// Elements must not be nullptr pointers. +template +class BufferedVector +{ + public: + typedef Vector > VectorType; + + BufferedVector() : list_(nullptr), last_(nullptr) {} + + // Adds element at end of list. This element is buffered and can + // be read using last() or removed using RemoveLast until a new Add or until + // RemoveLast or GetList has been called. + void Add(LifoAlloc *alloc, T* value) { + if (last_ != nullptr) { + if (list_ == nullptr) { + list_ = alloc->newInfallible(*alloc); + list_->reserve(initial_size); + } + list_->append(last_); + } + last_ = value; + } + + T* last() { + JS_ASSERT(last_ != nullptr); + return last_; + } + + T* RemoveLast() { + JS_ASSERT(last_ != nullptr); + T* result = last_; + if ((list_ != nullptr) && (list_->length() > 0)) + last_ = list_->popCopy(); + else + last_ = nullptr; + return result; + } + + T* Get(int i) { + JS_ASSERT((0 <= i) && (i < length())); + if (list_ == nullptr) { + JS_ASSERT(0 == i); + return last_; + } else { + if (size_t(i) == list_->length()) { + JS_ASSERT(last_ != nullptr); + return last_; + } else { + return (*list_)[i]; + } + } + } + + void Clear() { + list_ = nullptr; + last_ = nullptr; + } + + int length() { + int length = (list_ == nullptr) ? 0 : list_->length(); + return length + ((last_ == nullptr) ? 0 : 1); + } + + VectorType *GetList(LifoAlloc *alloc) { + if (list_ == nullptr) + list_ = alloc->newInfallible(*alloc); + if (last_ != nullptr) { + list_->append(last_); + last_ = nullptr; + } + return list_; + } + + private: + VectorType *list_; + T* last_; +}; + + +// Accumulates RegExp atoms and assertions into lists of terms and alternatives. +class RegExpBuilder +{ + public: + explicit RegExpBuilder(LifoAlloc *alloc); + void AddCharacter(jschar character); + // "Adds" an empty expression. Does nothing except consume a + // following quantifier + void AddEmpty(); + void AddAtom(RegExpTree* tree); + void AddAssertion(RegExpTree* tree); + void NewAlternative(); // '|' + void AddQuantifierToAtom(int min, int max, RegExpQuantifier::QuantifierType type); + RegExpTree* ToRegExp(); + + private: + void FlushCharacters(); + void FlushText(); + void FlushTerms(); + + LifoAlloc *alloc; + bool pending_empty_; + CharacterVector *characters_; + BufferedVector terms_; + BufferedVector text_; + BufferedVector alternatives_; + + enum LastAdded { + ADD_NONE, ADD_CHAR, ADD_TERM, ADD_ASSERT, ADD_ATOM + }; + mozilla::DebugOnly last_added_; +}; + +// Characters parsed by RegExpParser can be either jschars or kEndMarker. +typedef uint32_t widechar; + +class RegExpParser +{ + public: + RegExpParser(frontend::TokenStream &ts, LifoAlloc *alloc, + const jschar *chars, const jschar *end, bool multiline_mode); + + RegExpTree* ParsePattern(); + RegExpTree* ParseDisjunction(); + RegExpTree* ParseGroup(); + RegExpTree* ParseCharacterClass(); + + // Parses a {...,...} quantifier and stores the range in the given + // out parameters. + bool ParseIntervalQuantifier(int* min_out, int* max_out); + + // Parses and returns a single escaped character. The character + // must not be 'b' or 'B' since they are usually handled specially. + widechar ParseClassCharacterEscape(); + + // Checks whether the following is a length-digit hexadecimal number, + // and sets the value if it is. + bool ParseHexEscape(int length, size_t* value); + + size_t ParseOctalLiteral(); + + // Tries to parse the input as a back reference. If successful it + // stores the result in the output parameter and returns true. If + // it fails it will push back the characters read so the same characters + // can be reparsed. + bool ParseBackReferenceIndex(int* index_out); + + bool ParseClassAtom(jschar* char_class, CharacterRange *char_range); + RegExpTree* ReportError(unsigned errorNumber); + void Advance(); + void Advance(int dist) { + next_pos_ += dist - 1; + Advance(); + } + + void Reset(const jschar *pos) { + next_pos_ = pos; + has_more_ = (pos < end_); + Advance(); + } + + // Reports whether the pattern might be used as a literal search string. + // Only use if the result of the parse is a single atom node. + bool simple() { return simple_; } + bool contains_anchor() { return contains_anchor_; } + void set_contains_anchor() { contains_anchor_ = true; } + int captures_started() { return captures_ == nullptr ? 0 : captures_->length(); } + const jschar *position() { return next_pos_ - 1; } + + static const int kMaxCaptures = 1 << 16; + static const widechar kEndMarker = (1 << 21); + + private: + enum SubexpressionType { + INITIAL, + CAPTURE, // All positive values represent captures. + POSITIVE_LOOKAHEAD, + NEGATIVE_LOOKAHEAD, + GROUPING + }; + + class RegExpParserState { + public: + RegExpParserState(LifoAlloc *alloc, + RegExpParserState* previous_state, + SubexpressionType group_type, + int disjunction_capture_index) + : previous_state_(previous_state), + builder_(alloc->newInfallible(alloc)), + group_type_(group_type), + disjunction_capture_index_(disjunction_capture_index) + {} + // Parser state of containing expression, if any. + RegExpParserState* previous_state() { return previous_state_; } + bool IsSubexpression() { return previous_state_ != nullptr; } + // RegExpBuilder building this regexp's AST. + RegExpBuilder* builder() { return builder_; } + // Type of regexp being parsed (parenthesized group or entire regexp). + SubexpressionType group_type() { return group_type_; } + // Index in captures array of first capture in this sub-expression, if any. + // Also the capture index of this sub-expression itself, if group_type + // is CAPTURE. + int capture_index() { return disjunction_capture_index_; } + + private: + // Linked list implementation of stack of states. + RegExpParserState* previous_state_; + // Builder for the stored disjunction. + RegExpBuilder* builder_; + // Stored disjunction type (capture, look-ahead or grouping), if any. + SubexpressionType group_type_; + // Stored disjunction's capture index (if any). + int disjunction_capture_index_; + }; + + widechar current() { return current_; } + bool has_more() { return has_more_; } + bool has_next() { return next_pos_ < end_; } + widechar Next() { + if (has_next()) + return *next_pos_; + return kEndMarker; + } + void ScanForCaptures(); + + frontend::TokenStream &ts; + LifoAlloc *alloc; + RegExpCaptureVector *captures_; + const jschar *next_pos_, *end_; + widechar current_; + // The capture count is only valid after we have scanned for captures. + int capture_count_; + bool has_more_; + bool multiline_; + bool simple_; + bool contains_anchor_; + bool is_scanned_for_captures_; +}; + +} } // namespace js::irregexp + +#endif // V8_PARSER_H_ diff --git a/js/src/irregexp/RegExpStack.cpp b/js/src/irregexp/RegExpStack.cpp new file mode 100644 index 00000000000..3771906e1c5 --- /dev/null +++ b/js/src/irregexp/RegExpStack.cpp @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: */ + +// Copyright 2012 the V8 project authors. 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. + +#include "irregexp/RegExpStack.h" + +#include "vm/Runtime.h" + +using namespace js; +using namespace js::irregexp; + +RegExpStackScope::RegExpStackScope(JSRuntime *rt) + : regexp_stack(&rt->mainThread.regexpStack) +{} + +RegExpStackScope::~RegExpStackScope() +{ + regexp_stack->reset(); +} + +int +irregexp::GrowBacktrackStack(JSRuntime *rt) +{ + return rt->mainThread.regexpStack.grow(); +} + +RegExpStack::RegExpStack() + : base_(nullptr), size(0), limit_(nullptr) +{} + +RegExpStack::~RegExpStack() +{ + js_free(base_); +} + +bool +RegExpStack::init() +{ + base_ = js_malloc(kMinimumStackSize); + if (!base_) + return false; + + size = kMinimumStackSize; + updateLimit(); + return true; +} + +void +RegExpStack::reset() +{ + JS_ASSERT(size >= kMinimumStackSize); + + if (size != kMinimumStackSize) { + base_ = js_realloc(base_, kMinimumStackSize); + size = kMinimumStackSize; + updateLimit(); + } +} + +bool +RegExpStack::grow() +{ + size_t newSize = size * 2; + if (newSize > kMaximumStackSize) + return false; + + void *newBase = js_realloc(base_, newSize); + if (!newBase) + return false; + + base_ = newBase; + size = newSize; + updateLimit(); + + return true; +} diff --git a/js/src/irregexp/RegExpStack.h b/js/src/irregexp/RegExpStack.h new file mode 100644 index 00000000000..fa9bbe3e128 --- /dev/null +++ b/js/src/irregexp/RegExpStack.h @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: */ + +// Copyright 2009 the V8 project authors. 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. + +#ifndef V8_REGEXP_STACK_H_ +#define V8_REGEXP_STACK_H_ + +#include "jspubtd.h" +#include "js/Utility.h" + +namespace js { +namespace irregexp { + +class RegExpStack; + +// Maintains a per-thread stack area that can be used by irregexp +// implementation for its backtracking stack. +// +// Since there is only one stack area, the Irregexp implementation is not +// re-entrant. I.e., no regular expressions may be executed in the same thread +// during a preempted Irregexp execution. +class RegExpStackScope +{ + public: + // Create and delete an instance to control the life-time of a growing stack. + + // Initializes the stack memory area if necessary. + explicit RegExpStackScope(JSRuntime *rt); + + // Releases the stack if it has grown. + ~RegExpStackScope(); + + private: + RegExpStack* regexp_stack; +}; + +class RegExpStack +{ + public: + // Number of allocated locations on the stack above the limit. + // No sequence of pushes must be longer that this without doing a stack-limit + // check. + static const int kStackLimitSlack = 32; + + RegExpStack(); + ~RegExpStack(); + bool init(); + + // Resets the buffer if it has grown beyond the default/minimum size. + void reset(); + + // Attempts to grow the stack by at least kStackLimitSlack entries. + bool grow(); + + // Address of allocated memory. + const void *addressOfBase() { return &base_; } + const void *addressOfLimit() { return &limit_; } + + void *base() { return base_; } + void *limit() { return limit_; } + + private: + // Artificial limit used when no memory has been allocated. + static const uintptr_t kMemoryTop = static_cast(-1); + + // Minimal size of allocated stack area, in bytes. + static const size_t kMinimumStackSize = 1 * 1024; + + // Maximal size of allocated stack area, in bytes. + static const size_t kMaximumStackSize = 64 * 1024 * 1024; + + // If size > 0 then base must be non-nullptr. + void *base_; + + // Length in bytes of base. + size_t size; + + // If the stack pointer gets above the limit, we should react and + // either grow the stack or report an out-of-stack exception. + // There is only a limited number of locations above the stack limit, + // so users of the stack should check the stack limit during any + // sequence of pushes longer than this. + void *limit_; + + void updateLimit() { + JS_ASSERT(size >= kStackLimitSlack * sizeof(void *)); + limit_ = static_cast(base()) + size - (kStackLimitSlack * sizeof(void *)); + } +}; + +int +GrowBacktrackStack(JSRuntime *rt); + +}} // namespace js::irregexp + +#endif // V8_REGEXP_STACK_H_ diff --git a/js/src/jit-test/tests/basic/bug576837-regexp.js b/js/src/jit-test/tests/basic/bug576837-regexp.js index 99d161ce76b..956b3eae99a 100644 --- a/js/src/jit-test/tests/basic/bug576837-regexp.js +++ b/js/src/jit-test/tests/basic/bug576837-regexp.js @@ -2,10 +2,6 @@ * Check that builtin character classes within ranges produce syntax * errors. * - * Note that, per the extension in bug 351463, SpiderMonkey permits hyphens - * adjacent to character class escapes in character classes, treating them as a - * hyphen pattern character. Therefore /[\d-\s]/ is okay - * * Note: /\b/ is the backspace escape, which is a single pattern character, * though it looks deceptively like a character class. */ @@ -20,9 +16,9 @@ function isRegExpSyntaxError(pattern) { return false; } -assertEq(isRegExpSyntaxError('[C-\\s]'), true); -assertEq(isRegExpSyntaxError('[C-\\d]'), true); -assertEq(isRegExpSyntaxError('[C-\\W]'), true); +//assertEq(isRegExpSyntaxError('[C-\\s]'), false); +//assertEq(isRegExpSyntaxError('[C-\\d]'), false); +//assertEq(isRegExpSyntaxError('[C-\\W]'), false); assertEq(isRegExpSyntaxError('[C-]'), false); assertEq(isRegExpSyntaxError('[-C]'), false); assertEq(isRegExpSyntaxError('[C-C]'), false); @@ -31,8 +27,8 @@ assertEq(isRegExpSyntaxError('[\\b-\\b]'), false); assertEq(isRegExpSyntaxError('[\\B-\\B]'), false); assertEq(isRegExpSyntaxError('[\\b-\\B]'), false); assertEq(isRegExpSyntaxError('[\\B-\\b]'), true); -assertEq(isRegExpSyntaxError('[\\b-\\w]'), true); -assertEq(isRegExpSyntaxError('[\\B-\\w]'), true); +//assertEq(isRegExpSyntaxError('[\\b-\\w]'), false); +//assertEq(isRegExpSyntaxError('[\\B-\\w]'), false); /* Extension. */ assertEq(isRegExpSyntaxError('[\\s-\\s]'), false); diff --git a/js/src/jit-test/tests/basic/bug976446.js b/js/src/jit-test/tests/basic/bug976446.js new file mode 100644 index 00000000000..e99e221311d --- /dev/null +++ b/js/src/jit-test/tests/basic/bug976446.js @@ -0,0 +1,16 @@ + +try { + +function f() {} +f("".match(/(?:(?=g)).{2147483648,}/ + (/[]/)), null); + +} catch (e) {} // Yarr throws on the above regexp + +var re = new RegExp("a[\x01-\\xDE]+M", "gi"); +re.exec(""); + +var re = new RegExp("a[\x01-\\u00b8]+?c", "gi"); +re.exec(""); + +var re = new RegExp("a[\x01-\\u00f8]?c", "gi"); +re.exec(""); diff --git a/js/src/jit-test/tests/basic/regexp-match-limit.js b/js/src/jit-test/tests/basic/regexp-match-limit.js deleted file mode 100644 index 7fab09cff00..00000000000 --- a/js/src/jit-test/tests/basic/regexp-match-limit.js +++ /dev/null @@ -1,9 +0,0 @@ -// See bug 953013 - -load(libdir + "asserts.js"); - -function test() { - var input = Array(2499999+1).join('a'); - var result = /^([\w])+$/.test(input); -} -assertThrowsInstanceOf(test, InternalError); diff --git a/js/src/jit-test/tests/basic/testSlowNativeBail.js b/js/src/jit-test/tests/basic/testSlowNativeBail.js index f7a1443a71a..07def4543bc 100644 --- a/js/src/jit-test/tests/basic/testSlowNativeBail.js +++ b/js/src/jit-test/tests/basic/testSlowNativeBail.js @@ -3,8 +3,9 @@ function testSlowNativeBail() { try { for (var i = 0; i < a.length; i++) new RegExp(a[i]); + assertEq(true, false); } catch (exc) { - assertEq(""+exc, "SyntaxError: invalid quantifier"); + assertEq(exc instanceof SyntaxError, true); } } testSlowNativeBail(); diff --git a/js/src/jit/AsmJS.cpp b/js/src/jit/AsmJS.cpp index 047ed927268..8089710c1d7 100644 --- a/js/src/jit/AsmJS.cpp +++ b/js/src/jit/AsmJS.cpp @@ -662,7 +662,7 @@ class ABIArgIter typedef js::Vector MIRTypeVector; typedef ABIArgIter ABIArgMIRTypeIter; -typedef js::Vector VarTypeVector; +typedef js::Vector > VarTypeVector; typedef ABIArgIter ABIArgTypeIter; class Signature diff --git a/js/src/jit/IonAllocPolicy.h b/js/src/jit/IonAllocPolicy.h index 2f08bf33d62..fb60b53012e 100644 --- a/js/src/jit/IonAllocPolicy.h +++ b/js/src/jit/IonAllocPolicy.h @@ -32,12 +32,9 @@ class TempAllocator rootList_(nullptr) { } - void *allocateOrCrash(size_t bytes) + void *allocateInfallible(size_t bytes) { - void *p = lifoScope_.alloc().alloc(bytes); - if (!p) - js::CrashAtUnhandlableOOM("LifoAlloc::allocOrCrash"); - return p; + return lifoScope_.alloc().allocInfallible(bytes); } void *allocate(size_t bytes) @@ -178,7 +175,7 @@ class AutoIonContextAlloc struct TempObject { inline void *operator new(size_t nbytes, TempAllocator &alloc) { - return alloc.allocateOrCrash(nbytes); + return alloc.allocateInfallible(nbytes); } template inline void *operator new(size_t nbytes, T *pos) { diff --git a/js/src/jit/IonLinker.h b/js/src/jit/IonLinker.h index 8649e6cb854..6e85f6ffd8f 100644 --- a/js/src/jit/IonLinker.h +++ b/js/src/jit/IonLinker.h @@ -30,9 +30,6 @@ class Linker template JitCode *newCode(JSContext *cx, JSC::ExecutableAllocator *execAlloc, JSC::CodeKind kind) { - JS_ASSERT(kind == JSC::ION_CODE || - kind == JSC::BASELINE_CODE || - kind == JSC::OTHER_CODE); JS_ASSERT(masm.numAsmJSAbsoluteLinks() == 0); gc::AutoSuppressGC suppressGC(cx); diff --git a/js/src/jit/JitCommon.h b/js/src/jit/JitCommon.h index 79523fed4ca..0236dc457ec 100644 --- a/js/src/jit/JitCommon.h +++ b/js/src/jit/JitCommon.h @@ -7,7 +7,7 @@ #ifndef jit_JitCommon_h #define jit_JitCommon_h -// Various macros used by all JITs, including YARR. +// Various macros used by all JITs. #if defined(JS_ARM_SIMULATOR) #include "jit/arm/Simulator-arm.h" @@ -21,12 +21,21 @@ (js::jit::Simulator::Current()->call( \ JS_FUNC_TO_DATA_PTR(uint8_t *, entry), 8, p0, p1, p2, p3, p4, p5, p6, p7) & 0xffffffff) +#ifdef JS_YARR + #define CALL_GENERATED_YARR_CODE3(entry, p0, p1, p2) \ js::jit::Simulator::Current()->call(JS_FUNC_TO_DATA_PTR(uint8_t *, entry), 3, p0, p1, p2) #define CALL_GENERATED_YARR_CODE4(entry, p0, p1, p2, p3) \ js::jit::Simulator::Current()->call(JS_FUNC_TO_DATA_PTR(uint8_t *, entry), 4, p0, p1, p2, p3) +#else // JS_YARR + +#define CALL_GENERATED_REGEXP(entry, p0) \ + js::jit::Simulator::Current()->call(JS_FUNC_TO_DATA_PTR(uint8_t *, entry), 1, p0) + +#endif // JS_YARR + #define CALL_GENERATED_ASMJS(entry, p0, p1) \ (Simulator::Current()->call(JS_FUNC_TO_DATA_PTR(uint8_t *, entry), 2, p0, p1) & 0xffffffff) @@ -36,12 +45,21 @@ #define CALL_GENERATED_CODE(entry, p0, p1, p2, p3, p4, p5, p6, p7) \ entry(p0, p1, p2, p3, p4, p5, p6, p7) +#ifdef JS_YARR + #define CALL_GENERATED_YARR_CODE3(entry, p0, p1, p2) \ entry(p0, p1, p2) #define CALL_GENERATED_YARR_CODE4(entry, p0, p1, p2, p3) \ entry(p0, p1, p2, p3) +#else // JS_YARR + +#define CALL_GENERATED_REGEXP(entry, p0) \ + entry(p0) + +#endif // JS_YARR + #define CALL_GENERATED_ASMJS(entry, p0, p1) \ entry(p0, p1) diff --git a/js/src/jit/Label.h b/js/src/jit/Label.h new file mode 100644 index 00000000000..22c66f9a00e --- /dev/null +++ b/js/src/jit/Label.h @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef jit_Label_h +#define jit_Label_h + +namespace js { +namespace jit { + +struct LabelBase +{ + protected: + // offset_ >= 0 means that the label is either bound or has incoming + // uses and needs to be bound. + int32_t offset_ : 31; + bool bound_ : 1; + + // Disallow assignment. + void operator =(const LabelBase &label); + public: + static const int32_t INVALID_OFFSET = -1; + + LabelBase() : offset_(INVALID_OFFSET), bound_(false) + { } + LabelBase(const LabelBase &label) + : offset_(label.offset_), + bound_(label.bound_) + { } + + // If the label is bound, all incoming edges have been patched and any + // future incoming edges will be immediately patched. + bool bound() const { + return bound_; + } + int32_t offset() const { + JS_ASSERT(bound() || used()); + return offset_; + } + // Returns whether the label is not bound, but has incoming uses. + bool used() const { + return !bound() && offset_ > INVALID_OFFSET; + } + // Binds the label, fixing its final position in the code stream. + void bind(int32_t offset) { + JS_ASSERT(!bound()); + offset_ = offset; + bound_ = true; + JS_ASSERT(offset_ == offset); + } + // Marks the label as neither bound nor used. + void reset() { + offset_ = INVALID_OFFSET; + bound_ = false; + } + // Sets the label's latest used position, returning the old use position in + // the process. + int32_t use(int32_t offset) { + JS_ASSERT(!bound()); + + int32_t old = offset_; + offset_ = offset; + JS_ASSERT(offset_ == offset); + + return old; + } +}; + +// A label represents a position in an assembly buffer that may or may not have +// already been generated. Labels can either be "bound" or "unbound", the +// former meaning that its position is known and the latter that its position +// is not yet known. +// +// A jump to an unbound label adds that jump to the label's incoming queue. A +// jump to a bound label automatically computes the jump distance. The process +// of binding a label automatically corrects all incoming jumps. +class Label : public LabelBase +{ + public: + Label() + { } + Label(const Label &label) : LabelBase(label) + { } +}; + +// Label's destructor asserts that if it has been used it has also been bound. +// In the case long-lived labels, however, failed compilation (e.g. OOM) will +// trigger this failure innocuously. This Label silences the assertion. +class NonAssertingLabel : public Label +{ +}; + +} } // namespace js::jit + +#endif // jit_Label_h diff --git a/js/src/jit/arm/MacroAssembler-arm.cpp b/js/src/jit/arm/MacroAssembler-arm.cpp index 6574ab775a0..53a61a6df2b 100644 --- a/js/src/jit/arm/MacroAssembler-arm.cpp +++ b/js/src/jit/arm/MacroAssembler-arm.cpp @@ -1924,6 +1924,12 @@ MacroAssemblerARMCompat::sub32(Register src, Register dest) ma_sub(src, dest, SetCond); } +void +MacroAssemblerARMCompat::and32(Register src, Register dest) +{ + ma_and(src, dest, SetCond); +} + void MacroAssemblerARMCompat::and32(Imm32 imm, Register dest) { @@ -2406,6 +2412,12 @@ MacroAssemblerARMCompat::storePtr(Register src, const Address &address) ma_str(src, Operand(address)); } +void +MacroAssemblerARMCompat::storePtr(Register src, const BaseIndex &address) +{ + store32(src, address); +} + void MacroAssemblerARMCompat::storePtr(Register src, AbsoluteAddress dest) { diff --git a/js/src/jit/arm/MacroAssembler-arm.h b/js/src/jit/arm/MacroAssembler-arm.h index 37e65212632..a77ae11af13 100644 --- a/js/src/jit/arm/MacroAssembler-arm.h +++ b/js/src/jit/arm/MacroAssembler-arm.h @@ -1295,6 +1295,7 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM } void xor32(Imm32 imm, Register dest); + void and32(Register src, Register dest); void and32(Imm32 imm, Register dest); void and32(Imm32 imm, const Address &dest); void or32(Imm32 imm, const Address &dest); @@ -1370,6 +1371,7 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM void storePtr(ImmPtr imm, const Address &address); void storePtr(ImmGCPtr imm, const Address &address); void storePtr(Register src, const Address &address); + void storePtr(Register src, const BaseIndex &address); void storePtr(Register src, AbsoluteAddress dest); void storeDouble(FloatRegister src, Address addr) { ma_vstr(src, Operand(addr)); @@ -1452,6 +1454,9 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM void rshiftPtr(Imm32 imm, Register dest) { ma_lsr(imm, dest, dest); } + void rshiftPtrArithmetic(Imm32 imm, Register dest) { + ma_asr(imm, dest, dest); + } void lshiftPtr(Imm32 imm, Register dest) { ma_lsl(imm, dest, dest); } diff --git a/js/src/jit/arm/Simulator-arm.cpp b/js/src/jit/arm/Simulator-arm.cpp index cf90525b973..05d36594696 100644 --- a/js/src/jit/arm/Simulator-arm.cpp +++ b/js/src/jit/arm/Simulator-arm.cpp @@ -1489,10 +1489,20 @@ Simulator::setCallResult(int64_t res) int Simulator::readW(int32_t addr, SimInstruction *instr) { +#ifdef JS_YARR // YARR emits unaligned loads, so we don't check for them here like the // other methods below. intptr_t *ptr = reinterpret_cast(addr); return *ptr; +#else // JS_YARR + if ((addr & 3) == 0) { + intptr_t *ptr = reinterpret_cast(addr); + return *ptr; + } else { + printf("Unaligned write at 0x%08x, pc=%p\n", addr, instr); + MOZ_CRASH(); + } +#endif // JS_YARR } void @@ -4204,9 +4214,10 @@ Simulator::call(uint8_t* entry, int argument_count, ...) va_start(parameters, argument_count); // First four arguments passed in registers. - MOZ_ASSERT(argument_count >= 2); + MOZ_ASSERT(argument_count >= 1); set_register(r0, va_arg(parameters, int32_t)); - set_register(r1, va_arg(parameters, int32_t)); + if (argument_count >= 2) + set_register(r1, va_arg(parameters, int32_t)); if (argument_count >= 3) set_register(r2, va_arg(parameters, int32_t)); if (argument_count >= 4) diff --git a/js/src/jit/shared/Assembler-shared.h b/js/src/jit/shared/Assembler-shared.h index b64ac6de1af..b5f87cbfc45 100644 --- a/js/src/jit/shared/Assembler-shared.h +++ b/js/src/jit/shared/Assembler-shared.h @@ -14,6 +14,7 @@ #include "jsworkers.h" #include "jit/IonAllocPolicy.h" +#include "jit/Label.h" #include "jit/Registers.h" #include "jit/RegisterSets.h" @@ -283,108 +284,6 @@ class Relocation { }; }; -struct LabelBase -{ - protected: - // offset_ >= 0 means that the label is either bound or has incoming - // uses and needs to be bound. - int32_t offset_ : 31; - bool bound_ : 1; - - // Disallow assignment. - void operator =(const LabelBase &label); - public: - static const int32_t INVALID_OFFSET = -1; - - LabelBase() : offset_(INVALID_OFFSET), bound_(false) - { } - LabelBase(const LabelBase &label) - : offset_(label.offset_), - bound_(label.bound_) - { } - - // If the label is bound, all incoming edges have been patched and any - // future incoming edges will be immediately patched. - bool bound() const { - return bound_; - } - int32_t offset() const { - JS_ASSERT(bound() || used()); - return offset_; - } - // Returns whether the label is not bound, but has incoming uses. - bool used() const { - return !bound() && offset_ > INVALID_OFFSET; - } - // Binds the label, fixing its final position in the code stream. - void bind(int32_t offset) { - JS_ASSERT(!bound()); - offset_ = offset; - bound_ = true; - JS_ASSERT(offset_ == offset); - } - // Marks the label as neither bound nor used. - void reset() { - offset_ = INVALID_OFFSET; - bound_ = false; - } - // Sets the label's latest used position, returning the old use position in - // the process. - int32_t use(int32_t offset) { - JS_ASSERT(!bound()); - - int32_t old = offset_; - offset_ = offset; - JS_ASSERT(offset_ == offset); - - return old; - } -}; - -// A label represents a position in an assembly buffer that may or may not have -// already been generated. Labels can either be "bound" or "unbound", the -// former meaning that its position is known and the latter that its position -// is not yet known. -// -// A jump to an unbound label adds that jump to the label's incoming queue. A -// jump to a bound label automatically computes the jump distance. The process -// of binding a label automatically corrects all incoming jumps. -class Label : public LabelBase -{ - public: - Label() - { } - Label(const Label &label) : LabelBase(label) - { } - ~Label() - { -#ifdef DEBUG - // The assertion below doesn't hold if an error occurred. - if (OOM_counter > OOM_maxAllocations) - return; - if (MaybeGetIonContext() && GetIonContext()->runtime->hadOutOfMemory()) - return; - - MOZ_ASSERT(!used()); -#endif - } -}; - -// Label's destructor asserts that if it has been used it has also been bound. -// In the case long-lived labels, however, failed compilation (e.g. OOM) will -// trigger this failure innocuously. This Label silences the assertion. -class NonAssertingLabel : public Label -{ - public: - ~NonAssertingLabel() - { -#ifdef DEBUG - if (used()) - bind(0); -#endif - } -}; - class RepatchLabel { static const int32_t INVALID_OFFSET = 0xC0000000; diff --git a/js/src/jit/shared/MacroAssembler-x86-shared.h b/js/src/jit/shared/MacroAssembler-x86-shared.h index 4ac17d576ab..65293e99ad1 100644 --- a/js/src/jit/shared/MacroAssembler-x86-shared.h +++ b/js/src/jit/shared/MacroAssembler-x86-shared.h @@ -108,6 +108,9 @@ class MacroAssemblerX86Shared : public Assembler void move32(Register src, const Operand &dest) { movl(src, dest); } + void and32(Register src, Register dest) { + andl(src, dest); + } void and32(Imm32 imm, Register dest) { andl(imm, dest); } diff --git a/js/src/jit/x64/MacroAssembler-x64.h b/js/src/jit/x64/MacroAssembler-x64.h index c75db3f0b39..7fbd4c4f955 100644 --- a/js/src/jit/x64/MacroAssembler-x64.h +++ b/js/src/jit/x64/MacroAssembler-x64.h @@ -724,6 +724,9 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared void storePtr(Register src, const Address &address) { movq(src, Operand(address)); } + void storePtr(Register src, const BaseIndex &address) { + movq(src, Operand(address)); + } void storePtr(Register src, const Operand &dest) { movq(src, dest); } @@ -746,6 +749,9 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared void rshiftPtr(Imm32 imm, Register dest) { shrq(imm, dest); } + void rshiftPtrArithmetic(Imm32 imm, Register dest) { + sarq(imm, dest); + } void lshiftPtr(Imm32 imm, Register dest) { shlq(imm, dest); } diff --git a/js/src/jit/x86/MacroAssembler-x86.h b/js/src/jit/x86/MacroAssembler-x86.h index d1f52e7a86e..b4c21a49572 100644 --- a/js/src/jit/x86/MacroAssembler-x86.h +++ b/js/src/jit/x86/MacroAssembler-x86.h @@ -699,6 +699,9 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared void storePtr(Register src, const Address &address) { movl(src, Operand(address)); } + void storePtr(Register src, const BaseIndex &address) { + movl(src, Operand(address)); + } void storePtr(Register src, const Operand &dest) { movl(src, dest); } @@ -982,6 +985,9 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared void rshiftPtr(Imm32 imm, Register dest) { shrl(imm, dest); } + void rshiftPtrArithmetic(Imm32 imm, Register dest) { + sarl(imm, dest); + } void lshiftPtr(Imm32 imm, Register dest) { shll(imm, dest); } diff --git a/js/src/js.msg b/js/src/js.msg index 469b1b24d5e..cde21d77952 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -1,4 +1,4 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -105,7 +105,7 @@ MSG_DEF(JSMSG_BAD_DESTRUCT_ASS, 51, 0, JSEXN_REFERENCEERR, "invalid destr MSG_DEF(JSMSG_PAREN_AFTER_LET, 52, 0, JSEXN_SYNTAXERR, "missing ) after let head") MSG_DEF(JSMSG_CURLY_AFTER_LET, 53, 0, JSEXN_SYNTAXERR, "missing } after let block") MSG_DEF(JSMSG_MISSING_PAREN, 54, 0, JSEXN_SYNTAXERR, "unterminated parenthetical") -MSG_DEF(JSMSG_UNTERM_CLASS, 55, 1, JSEXN_SYNTAXERR, "unterminated character class {0}") +MSG_DEF(JSMSG_UNTERM_CLASS, 55, 0, JSEXN_SYNTAXERR, "unterminated character class") MSG_DEF(JSMSG_TRAILING_SLASH, 56, 0, JSEXN_SYNTAXERR, "trailing \\ in regular expression") MSG_DEF(JSMSG_BAD_CLASS_RANGE, 57, 0, JSEXN_SYNTAXERR, "invalid range in character class") MSG_DEF(JSMSG_BAD_REGEXP_FLAG, 58, 1, JSEXN_SYNTAXERR, "invalid regular expression flag {0}") @@ -228,10 +228,10 @@ MSG_DEF(JSMSG_BRACKET_AFTER_ARRAY_COMPREHENSION, 174, 0, JSEXN_SYNTAXERR, "missi MSG_DEF(JSMSG_NESTING_GENERATOR, 175, 0, JSEXN_TYPEERR, "already executing generator") MSG_DEF(JSMSG_PAREN_AFTER_FOR_OF_ITERABLE, 176, 0, JSEXN_SYNTAXERR, "missing ) after for-of iterable") MSG_DEF(JSMSG_INVALID_NORMALIZE_FORM, 177, 0, JSEXN_RANGEERR, "form must be one of 'NFC', 'NFD', 'NFKC', or 'NFKD'") -MSG_DEF(JSMSG_UNUSED178, 178, 0, JSEXN_NONE, "") -MSG_DEF(JSMSG_UNUSED179, 179, 0, JSEXN_NONE, "") -MSG_DEF(JSMSG_UNUSED180, 180, 0, JSEXN_NONE, "") -MSG_DEF(JSMSG_UNUSED181, 181, 0, JSEXN_NONE, "") +MSG_DEF(JSMSG_NOTHING_TO_REPEAT, 178, 0, JSEXN_SYNTAXERR, "nothing to repeat") +MSG_DEF(JSMSG_INVALID_GROUP, 179, 0, JSEXN_SYNTAXERR, "invalid regexp group") +MSG_DEF(JSMSG_ESCAPE_AT_END_OF_REGEXP, 180, 0, JSEXN_SYNTAXERR, "\\ at end of pattern") +MSG_DEF(JSMSG_NUMBERS_OUT_OF_ORDER, 181, 0, JSEXN_SYNTAXERR, "numbers out of order in {} quantifier.") MSG_DEF(JSMSG_BAD_GENERATOR_SEND, 182, 1, JSEXN_TYPEERR, "attempt to send {0} to newborn generator") MSG_DEF(JSMSG_SC_NOT_TRANSFERABLE, 183, 0, JSEXN_TYPEERR, "invalid transferable array for structured clone") MSG_DEF(JSMSG_SC_DUP_TRANSFERABLE, 184, 0, JSEXN_TYPEERR, "duplicate transferable for structured clone") diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index c178cfd047d..e6ca08d4d98 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -46,9 +46,6 @@ #include "jswrapper.h" #include "prmjtime.h" -#if ENABLE_YARR_JIT -#include "assembler/jit/ExecutableAllocator.h" -#endif #include "builtin/Eval.h" #include "builtin/Intl.h" #include "builtin/MapObject.h" @@ -85,7 +82,6 @@ #include "vm/WeakMapObject.h" #include "vm/WrapperObject.h" #include "vm/Xdr.h" -#include "yarr/BumpPointerAllocator.h" #include "jsatominlines.h" #include "jsfuninlines.h" @@ -5885,7 +5881,7 @@ JS_NewRegExpObject(JSContext *cx, HandleObject obj, char *bytes, size_t length, return nullptr; RegExpObject *reobj = RegExpObject::create(cx, res, chars, length, - RegExpFlag(flags), nullptr); + RegExpFlag(flags), nullptr, cx->tempLifoAlloc()); js_free(chars); return reobj; } @@ -5901,7 +5897,7 @@ JS_NewUCRegExpObject(JSContext *cx, HandleObject obj, jschar *chars, size_t leng return nullptr; return RegExpObject::create(cx, res, chars, length, - RegExpFlag(flags), nullptr); + RegExpFlag(flags), nullptr, cx->tempLifoAlloc()); } JS_PUBLIC_API(bool) @@ -5958,7 +5954,7 @@ JS_NewRegExpObjectNoStatics(JSContext *cx, char *bytes, size_t length, unsigned if (!chars) return nullptr; RegExpObject *reobj = RegExpObject::createNoStatics(cx, chars, length, - RegExpFlag(flags), nullptr); + RegExpFlag(flags), nullptr, cx->tempLifoAlloc()); js_free(chars); return reobj; } @@ -5969,7 +5965,7 @@ JS_NewUCRegExpObjectNoStatics(JSContext *cx, jschar *chars, size_t length, unsig AssertHeapIsIdle(cx); CHECK_REQUEST(cx); return RegExpObject::createNoStatics(cx, chars, length, - RegExpFlag(flags), nullptr); + RegExpFlag(flags), nullptr, cx->tempLifoAlloc()); } JS_PUBLIC_API(bool) diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 39cec354f69..d1696c9e8f5 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -1446,7 +1446,8 @@ class JS_PUBLIC_API(RuntimeOptions) { RuntimeOptions() : baseline_(false), ion_(false), - asmJS_(false) + asmJS_(false), + nativeRegExp_(false) { } @@ -1480,10 +1481,17 @@ class JS_PUBLIC_API(RuntimeOptions) { return *this; } + bool nativeRegExp() const { return nativeRegExp_; } + RuntimeOptions &setNativeRegExp(bool flag) { + nativeRegExp_ = flag; + return *this; + } + private: bool baseline_ : 1; bool ion_ : 1; bool asmJS_ : 1; + bool nativeRegExp_ : 1; }; JS_PUBLIC_API(RuntimeOptions &) diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index de55fac0828..2743f01a606 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -46,7 +46,6 @@ #include "js/CharacterEncoding.h" #include "js/OldDebugAPI.h" #include "vm/Shape.h" -#include "yarr/BumpPointerAllocator.h" #include "jsobjinlines.h" #include "jsscriptinlines.h" diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 654f89cb013..b86ad22cb39 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -107,7 +107,6 @@ TraceCycleDetectionSet(JSTracer *trc, ObjectSet &set); struct AutoResolving; class DtoaCache; class ForkJoinContext; -class RegExpCompartment; class RegExpStatics; namespace frontend { struct CompileError; } @@ -1083,9 +1082,6 @@ class AutoLockForExclusiveAccess MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; -void -CrashAtUnhandlableOOM(const char *reason); - } /* namespace js */ #ifdef _MSC_VER diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index 76bb6ef93f2..cb74a301ae5 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -9,6 +9,7 @@ #include "mozilla/MemoryReporting.h" +#include "builtin/RegExp.h" #include "builtin/TypedObject.h" #include "gc/Zone.h" #include "vm/GlobalObject.h" diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index 43f179f5f86..488e0bc4f53 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -2021,7 +2021,11 @@ DoMatchGlobal(JSContext *cx, CallArgs args, RegExpStatics *res, HandletempLifoAlloc()); +#endif size_t charsLen = input->length(); const jschar *chars = input->chars(); RegExpShared &re = g.regExp(); @@ -2031,7 +2035,11 @@ DoMatchGlobal(JSContext *cx, CallArgs args, RegExpStatics *res, HandletempLifoAlloc()); + RegExpRunStatus status = g.regExp().execute(cx, chars, length, &i, matches); +#endif if (status == RegExpRunStatus_Error) return false; if (status == RegExpRunStatus_Success) res->updateLazily(cx, linearStr, &g.regExp(), 0); +#ifdef JS_YARR JS_ASSERT_IF(status == RegExpRunStatus_Success_NotFound, match.start == -1); args.rval().setInt32(match.start); +#else + args.rval().setInt32(status == RegExpRunStatus_Success_NotFound ? -1 : matches[0].start); +#endif return true; } @@ -2813,7 +2833,11 @@ StrReplaceRegexpRemove(JSContext *cx, HandleString str, RegExpShared &re, Mutabl size_t charsLen = flatStr->length(); +#ifdef JS_YARR MatchPair match; +#else + ScopedMatchPairs matches(&cx->tempLifoAlloc()); +#endif size_t startIndex = 0; /* Index used for iterating through the string. */ size_t lastIndex = 0; /* Index after last successful match. */ size_t lazyIndex = 0; /* Index before last successful match. */ @@ -2823,13 +2847,20 @@ StrReplaceRegexpRemove(JSContext *cx, HandleString str, RegExpShared &re, Mutabl if (!CheckForInterrupt(cx)) return false; - RegExpRunStatus status = - re.executeMatchOnly(cx, flatStr->chars(), charsLen, &startIndex, match); +#ifdef JS_YARR + RegExpRunStatus status = re.executeMatchOnly(cx, flatStr->chars(), charsLen, &startIndex, match); +#else + RegExpRunStatus status = re.execute(cx, flatStr->chars(), charsLen, &startIndex, matches); +#endif if (status == RegExpRunStatus_Error) return false; if (status == RegExpRunStatus_Success_NotFound) break; +#ifndef JS_YARR + MatchPair &match = matches[0]; +#endif + /* Include the latest unmatched substring. */ if (size_t(match.start) > lastIndex) { if (!ranges.append(StringRange(lastIndex, match.start - lastIndex))) diff --git a/js/src/moz.build b/js/src/moz.build index 9a743f0f110..2858798e61b 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -193,12 +193,7 @@ UNIFIED_SOURCES += [ 'vm/Unicode.cpp', 'vm/Value.cpp', 'vm/WeakMapPtr.cpp', - 'vm/Xdr.cpp', - 'yarr/PageBlock.cpp', - 'yarr/YarrCanonicalizeUCS2.cpp', - 'yarr/YarrInterpreter.cpp', - 'yarr/YarrPattern.cpp', - 'yarr/YarrSyntaxChecker.cpp', + 'vm/Xdr.cpp' ] # jsarray.cpp and jsatom.cpp cannot be built in unified mode because @@ -235,6 +230,40 @@ if CONFIG['ENABLE_TRACE_LOGGING']: 'vm/TraceLogging.cpp', ] +if CONFIG['ENABLE_YARR']: + UNIFIED_SOURCES += [ + 'yarr/PageBlock.cpp', + 'yarr/YarrCanonicalizeUCS2.cpp', + 'yarr/YarrInterpreter.cpp', + 'yarr/YarrPattern.cpp', + 'yarr/YarrSyntaxChecker.cpp', + ] + if CONFIG['OS_ARCH'] == 'WINNT': + SOURCES += [ + 'yarr/OSAllocatorWin.cpp', + ] + else: + SOURCES += [ + 'yarr/OSAllocatorPosix.cpp', + ] + if CONFIG['ENABLE_ION']: + SOURCES += [ + 'yarr/YarrJIT.cpp' + ] +else: + UNIFIED_SOURCES += [ + 'irregexp/RegExpAST.cpp', + 'irregexp/RegExpEngine.cpp', + 'irregexp/RegExpInterpreter.cpp', + 'irregexp/RegExpMacroAssembler.cpp', + 'irregexp/RegExpParser.cpp', + 'irregexp/RegExpStack.cpp', + ] + if CONFIG['ENABLE_ION']: + UNIFIED_SOURCES += [ + 'irregexp/NativeRegExpMacroAssembler.cpp', + ] + if CONFIG['ENABLE_ION']: UNIFIED_SOURCES += [ 'jit/AliasAnalysis.cpp', @@ -350,17 +379,15 @@ if CONFIG['ENABLE_ION']: if CONFIG['OS_ARCH'] == 'WINNT': SOURCES += [ 'assembler/jit/ExecutableAllocatorWin.cpp', - 'yarr/OSAllocatorWin.cpp', ] # _CRT_RAND_S must be #defined before #including stdlib.h to get rand_s() DEFINES['_CRT_RAND_S'] = True else: SOURCES += [ 'assembler/jit/ExecutableAllocatorPosix.cpp', - 'yarr/OSAllocatorPosix.cpp', ] -if CONFIG['ENABLE_ION'] or CONFIG['ENABLE_YARR_JIT']: +if CONFIG['ENABLE_ION']: if CONFIG['JS_CODEGEN_X86'] or CONFIG['JS_CODEGEN_X64']: SOURCES += [ 'assembler/assembler/MacroAssemblerX86Common.cpp', @@ -371,11 +398,6 @@ if CONFIG['ENABLE_ION'] or CONFIG['ENABLE_YARR_JIT']: 'assembler/assembler/MacroAssemblerARM.cpp', ] -if CONFIG['ENABLE_YARR_JIT']: - SOURCES += [ - 'yarr/YarrJIT.cpp' - ] - if CONFIG['JS_HAS_CTYPES']: SOURCES += [ 'ctypes/CTypes.cpp', @@ -460,6 +482,3 @@ if CONFIG['_MSC_VER']: # the code in js/src/assembler. DEFINES['USE_SYSTEM_MALLOC'] = 1 DEFINES['ENABLE_ASSEMBLER'] = 1 - -if CONFIG['ENABLE_YARR_JIT']: - DEFINES['ENABLE_JIT'] = 1 diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 86b82263e93..89f5fb70025 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -5846,10 +5846,12 @@ SetRuntimeOptions(JSRuntime *rt, const OptionParser &op) bool enableBaseline = !op.getBoolOption("no-baseline"); bool enableIon = !op.getBoolOption("no-ion"); bool enableAsmJS = !op.getBoolOption("no-asmjs"); + bool enableNativeRegExp = !op.getBoolOption("no-native-regexp"); JS::RuntimeOptionsRef(rt).setBaseline(enableBaseline) .setIon(enableIon) - .setAsmJS(enableAsmJS); + .setAsmJS(enableAsmJS) + .setNativeRegExp(enableNativeRegExp); if (const char *str = op.getStringOption("ion-gvn")) { if (strcmp(str, "off") == 0) { @@ -6138,6 +6140,7 @@ main(int argc, char **argv, char **envp) || !op.addBoolOption('\0', "ion", "Enable IonMonkey (default)") || !op.addBoolOption('\0', "no-ion", "Disable IonMonkey") || !op.addBoolOption('\0', "no-asmjs", "Disable asm.js compilation") + || !op.addBoolOption('\0', "no-native-regexp", "Disable native regexp compilation") || !op.addStringOption('\0', "ion-gvn", "[mode]", "Specify Ion global value numbering:\n" " off: disable GVN\n" diff --git a/js/src/tests/js1_5/Exceptions/regress-332472.js b/js/src/tests/js1_5/Exceptions/regress-332472.js index 540d45eee09..bea0e9997e2 100644 --- a/js/src/tests/js1_5/Exceptions/regress-332472.js +++ b/js/src/tests/js1_5/Exceptions/regress-332472.js @@ -7,7 +7,7 @@ var BUGNUMBER = 332472; var summary = 'new RegExp() ignores string boundaries when throwing exceptions'; var actual = ''; -var expect = 'SyntaxError: invalid quantifier'; +var expect = true; printBugNumber(BUGNUMBER); printStatus (summary); @@ -19,7 +19,7 @@ try { } catch(ex) { printStatus(ex); - actual = ex + ''; + actual = ex instanceof SyntaxError; } reportCompare(expect, actual, summary); diff --git a/js/src/tests/js1_5/Regress/regress-230216-2.js b/js/src/tests/js1_5/Regress/regress-230216-2.js index 80567b84a4b..6ebd7bf9162 100644 --- a/js/src/tests/js1_5/Regress/regress-230216-2.js +++ b/js/src/tests/js1_5/Regress/regress-230216-2.js @@ -19,12 +19,13 @@ printStatus (summary); status = inSection(1) + ' check for overflow in quantifier'; actual = 'undefined'; -expect = 'error'; +expect0 = 'no exception thrown false'; +expect1 = 'error'; try { var result = eval('/a{21474836481}/.test("a")'); - actual = 'no exception thrown'; + actual = 'no exception thrown ' + result; status += ' result: ' + result; } catch(e) @@ -32,5 +33,8 @@ catch(e) actual = 'error'; } -reportCompare(expect, actual, status); - +// The result we get depends on the regexp engine. +if (actual != 'error') + reportCompare(expect0, actual, status); +else + reportCompare(expect1, actual, status); diff --git a/js/src/tests/js1_5/Regress/regress-280769-1.js b/js/src/tests/js1_5/Regress/regress-280769-1.js index 59c8d25032b..8cefabf535c 100644 --- a/js/src/tests/js1_5/Regress/regress-280769-1.js +++ b/js/src/tests/js1_5/Regress/regress-280769-1.js @@ -19,6 +19,9 @@ var a = new Array(N + 1); var prefix = a.join("z"); // prefix is sequence of N "z", zzz...zzz var str = prefix+"[AB]"; // str is zzz...zzz[AB] var re = new RegExp(str); -re.exec(prefix+"A"); - -reportCompare(expect, actual, status); +try { + re.exec(prefix+"A"); + reportCompare(expect, actual, status); +} catch (e) { + reportCompare(true, e instanceof Error, actual, status); +} diff --git a/js/src/tests/js1_5/Regress/regress-280769-5.js b/js/src/tests/js1_5/Regress/regress-280769-5.js index e25c5d10e98..fcad59def56 100644 --- a/js/src/tests/js1_5/Regress/regress-280769-5.js +++ b/js/src/tests/js1_5/Regress/regress-280769-5.js @@ -22,9 +22,13 @@ var re = new RegExp(prefix+"0?"+suffix); // re is /aaa...aaa0?111/ var str_to_match = prefix+suffix; // str_to_match is "aaa...aaa111" -var index = str_to_match.search(re); +try { + var index = str_to_match.search(re); -expect = 0; -actual = index; + expect = 0; + actual = index; -reportCompare(expect, actual, summary); + reportCompare(expect, actual, summary); +} catch (e) { + reportCompare(true, e instanceof Error, actual, summary); +} diff --git a/js/src/tests/js1_5/Regress/regress-440926.js b/js/src/tests/js1_5/Regress/regress-440926.js index 89ba9559b37..bbb2a9c9046 100644 --- a/js/src/tests/js1_5/Regress/regress-440926.js +++ b/js/src/tests/js1_5/Regress/regress-440926.js @@ -7,8 +7,8 @@ var BUGNUMBER = 440926; var summary = 'Correctly match regexps with special "i" characters'; var actual = ''; -var expect = 'iI#,iI#;iI#,iI#'; - +var expect0 = '#I#,#I#;#I#,#I#'; +var expect1 = 'iI#,iI#;iI#,iI#'; //----------------------------------------------------------------------------- test(); @@ -28,7 +28,11 @@ function test() actual += ',' + 'iI\u0130'.replace(/\u0130/gi, '#'); jit(false); - reportCompare(expect, actual, summary); + // The result we get depends on the regexp engine in use. + if (actual == expect0) + reportCompare(expect0, actual, summary); + else + reportCompare(expect1, actual, summary); exitFunc ('test'); } diff --git a/js/src/vm/MatchPairs.h b/js/src/vm/MatchPairs.h index 47127cdf135..644d536501e 100644 --- a/js/src/vm/MatchPairs.h +++ b/js/src/vm/MatchPairs.h @@ -24,14 +24,14 @@ namespace js { struct MatchPair { - int start; - int limit; + int32_t start; + int32_t limit; MatchPair() : start(-1), limit(-1) { } - MatchPair(int start, int limit) + MatchPair(int32_t start, int32_t limit) : start(start), limit(limit) { } @@ -56,8 +56,11 @@ struct MatchPair class MatchPairs { protected: - size_t pairCount_; /* Length of pairs_. */ - MatchPair *pairs_; /* Raw pointer into an allocated MatchPair buffer. */ + /* Length of pairs_. */ + uint32_t pairCount_; + + /* Raw pointer into an allocated MatchPair buffer. */ + MatchPair *pairs_; protected: /* Not used directly: use ScopedMatchPairs or VectorMatchPairs. */ @@ -81,7 +84,7 @@ class MatchPairs void checkAgainst(size_t inputLength) { #ifdef DEBUG for (size_t i = 0; i < pairCount_; i++) { - const MatchPair &p = pair(i); + const MatchPair &p = (*this)[i]; JS_ASSERT(p.check()); if (p.isUndefined()) continue; @@ -96,18 +99,22 @@ class MatchPairs size_t pairCount() const { JS_ASSERT(pairCount_ > 0); return pairCount_; } size_t parenCount() const { return pairCount_ - 1; } + static size_t offsetOfPairs() { return offsetof(MatchPairs, pairs_); } + static size_t offsetOfPairCount() { return offsetof(MatchPairs, pairCount_); } + + int32_t *pairsRaw() { return reinterpret_cast(pairs_); } + public: - unsigned *rawBuf() const { return reinterpret_cast(pairs_); } size_t length() const { return pairCount_; } - /* Pair accessors. */ - const MatchPair &pair(size_t i) const { - JS_ASSERT(pairCount_ && i < pairCount_); - JS_ASSERT(pairs_); + const MatchPair &operator[](size_t i) const { + JS_ASSERT(i < pairCount_); + return pairs_[i]; + } + MatchPair &operator[](size_t i) { + JS_ASSERT(i < pairCount_); return pairs_[i]; } - - const MatchPair &operator[](size_t i) const { return pair(i); } }; /* MatchPairs allocated into temporary storage, removed when out of scope. */ @@ -121,8 +128,6 @@ class ScopedMatchPairs : public MatchPairs : lifoScope_(lifoAlloc) { } - const MatchPair &operator[](size_t i) const { return pair(i); } - protected: bool allocOrExpandArray(size_t pairCount); }; @@ -140,8 +145,6 @@ class VectorMatchPairs : public MatchPairs vec_.clear(); } - const MatchPair &operator[](size_t i) const { return pair(i); } - protected: friend class RegExpStatics; bool allocOrExpandArray(size_t pairCount); diff --git a/js/src/vm/RegExpObject.cpp b/js/src/vm/RegExpObject.cpp index 35ba60b9d9e..7ff999472fe 100644 --- a/js/src/vm/RegExpObject.cpp +++ b/js/src/vm/RegExpObject.cpp @@ -11,12 +11,17 @@ #include "jsstr.h" #include "frontend/TokenStream.h" +#ifndef JS_YARR +#include "irregexp/RegExpParser.h" +#endif #include "vm/MatchPairs.h" #include "vm/RegExpStatics.h" #include "vm/StringBuffer.h" #include "vm/TraceLogging.h" #include "vm/Xdr.h" +#ifdef JS_YARR #include "yarr/YarrSyntaxChecker.h" +#endif #include "jsobjinlines.h" @@ -25,6 +30,7 @@ using namespace js; using mozilla::DebugOnly; +using mozilla::Maybe; using js::frontend::TokenStream; JS_STATIC_ASSERT(IgnoreCaseFlag == JSREG_FOLD); @@ -243,29 +249,44 @@ const Class RegExpObject::class_ = { RegExpObject * RegExpObject::create(ExclusiveContext *cx, RegExpStatics *res, const jschar *chars, size_t length, - RegExpFlag flags, TokenStream *tokenStream) + RegExpFlag flags, TokenStream *tokenStream, LifoAlloc &alloc) { RegExpFlag staticsFlags = res->getFlags(); - return createNoStatics(cx, chars, length, RegExpFlag(flags | staticsFlags), tokenStream); + return createNoStatics(cx, chars, length, RegExpFlag(flags | staticsFlags), tokenStream, alloc); } RegExpObject * RegExpObject::createNoStatics(ExclusiveContext *cx, const jschar *chars, size_t length, RegExpFlag flags, - TokenStream *tokenStream) + TokenStream *tokenStream, LifoAlloc &alloc) { RootedAtom source(cx, AtomizeChars(cx, chars, length)); if (!source) return nullptr; - return createNoStatics(cx, source, flags, tokenStream); + return createNoStatics(cx, source, flags, tokenStream, alloc); } RegExpObject * RegExpObject::createNoStatics(ExclusiveContext *cx, HandleAtom source, RegExpFlag flags, - TokenStream *tokenStream) + TokenStream *tokenStream, LifoAlloc &alloc) { +#ifdef JS_YARR if (!RegExpShared::checkSyntax(cx, tokenStream, source)) return nullptr; +#else // JS_YARR + Maybe dummyOptions; + Maybe dummyTokenStream; + if (!tokenStream) { + dummyOptions.construct(cx->asJSContext()); + dummyTokenStream.construct(cx, dummyOptions.ref(), + (const jschar *) nullptr, 0, + (frontend::StrictModeGetter *) nullptr); + tokenStream = dummyTokenStream.addr(); + } + + if (!irregexp::ParsePatternSyntax(*tokenStream, alloc, source->chars(), source->length())) + return nullptr; +#endif RegExpObjectBuilder builder(cx); return builder.build(source, flags); @@ -380,20 +401,32 @@ RegExpObject::toString(JSContext *cx) const RegExpShared::RegExpShared(JSAtom *source, RegExpFlag flags, uint64_t gcNumber) : source(source), flags(flags), parenCount(0), canStringMatch(false), -#if ENABLE_YARR_JIT - codeBlock(), + activeUseCount(0), gcNumberWhenUsed(gcNumber) +{ +#ifdef JS_YARR + bytecode = nullptr; +#else + byteCode = nullptr; #endif - bytecode(nullptr), activeUseCount(0), gcNumberWhenUsed(gcNumber) -{} +} RegExpShared::~RegExpShared() { -#if ENABLE_YARR_JIT +#ifdef JS_YARR +#ifdef JS_ION codeBlock.release(); #endif js_delete(bytecode); +#else // JS_YARR + js_free(byteCode); +#endif // JS_YARR + + for (size_t i = 0; i < tables.length(); i++) + js_delete(tables[i]); } +#ifdef JS_YARR + void RegExpShared::reportYarrError(ExclusiveContext *cx, TokenStream *ts, ErrorCode error) { @@ -437,14 +470,18 @@ RegExpShared::checkSyntax(ExclusiveContext *cx, TokenStream *tokenStream, JSLine return false; } +#endif // JS_YARR + bool -RegExpShared::compile(JSContext *cx, bool matchOnly) +RegExpShared::compile(JSContext *cx, bool matchOnly, const jschar *sampleChars, size_t sampleLength) { TraceLogger *logger = TraceLoggerForMainThread(cx->runtime()); - AutoTraceLog logCompile(logger, TraceLogger::YarrCompile); + AutoTraceLog logCompile(logger, TraceLogger::IrregexpCompile); - if (!sticky()) - return compile(cx, *source, matchOnly); + if (!sticky()) { + RootedAtom pattern(cx, source); + return compile(cx, pattern, matchOnly, sampleChars, sampleLength); + } /* * The sticky case we implement hackily by prepending a caret onto the front @@ -461,32 +498,33 @@ RegExpShared::compile(JSContext *cx, bool matchOnly) sb.infallibleAppend(source->chars(), source->length()); sb.infallibleAppend(postfix, ArrayLength(postfix)); - JSAtom *fakeySource = sb.finishAtom(); + RootedAtom fakeySource(cx, sb.finishAtom()); if (!fakeySource) return false; - return compile(cx, *fakeySource, matchOnly); + return compile(cx, fakeySource, matchOnly, sampleChars, sampleLength); } bool -RegExpShared::compile(JSContext *cx, JSLinearString &pattern, bool matchOnly) +RegExpShared::compile(JSContext *cx, HandleAtom pattern, bool matchOnly, const jschar *sampleChars, size_t sampleLength) { - if (!ignoreCase() && !StringHasRegExpMetaChars(pattern.chars(), pattern.length())) { + if (!ignoreCase() && !StringHasRegExpMetaChars(pattern->chars(), pattern->length())) { canStringMatch = true; parenCount = 0; return true; } - /* Parse the pattern. */ +#ifdef JS_YARR + ErrorCode yarrError; - YarrPattern yarrPattern(pattern, ignoreCase(), multiline(), &yarrError); + YarrPattern yarrPattern(*pattern, ignoreCase(), multiline(), &yarrError); if (yarrError) { reportYarrError(cx, nullptr, yarrError); return false; } this->parenCount = yarrPattern.m_numSubpatterns; -#if ENABLE_YARR_JIT +#ifdef JS_ION if (isJITRuntimeEnabled(cx) && !yarrPattern.m_containsBackreferences) { JSC::ExecutableAllocator *execAlloc = cx->runtime()->getExecAlloc(cx); if (!execAlloc) @@ -512,24 +550,61 @@ RegExpShared::compile(JSContext *cx, JSLinearString &pattern, bool matchOnly) } bytecode = byteCompile(yarrPattern, bumpAlloc).get(); + +#else // JS_YARR + + JS_ASSERT(!matchOnly); + + CompileOptions options(cx); + TokenStream dummyTokenStream(cx, options, nullptr, 0, nullptr); + + LifoAllocScope scope(&cx->tempLifoAlloc()); + + /* Parse the pattern. */ + irregexp::RegExpCompileData data; + if (!irregexp::ParsePattern(dummyTokenStream, cx->tempLifoAlloc(), + pattern->chars(), pattern->length(), multiline(), &data)) + { + return false; + } + + this->parenCount = data.capture_count; + + irregexp::RegExpCode code = irregexp::CompilePattern(cx, this, &data, sampleChars, sampleLength, + false /* global() */, + ignoreCase()); + if (code.empty()) + return false; + +#ifdef JS_ION + JS_ASSERT(!code.jitCode || !code.byteCode); + jitCode = code.jitCode; +#endif + + byteCode = code.byteCode; + +#endif // JS_YARR + return true; } bool -RegExpShared::compileIfNecessary(JSContext *cx) +RegExpShared::compileIfNecessary(JSContext *cx, const jschar *sampleChars, size_t sampleLength) { - if (hasCode() || hasBytecode() || canStringMatch) + if (isCompiled(false) || canStringMatch) return true; - return compile(cx, false); + return compile(cx, false, sampleChars, sampleLength); } +#ifdef JS_YARR bool RegExpShared::compileMatchOnlyIfNecessary(JSContext *cx) { - if (hasMatchOnlyCode() || hasBytecode() || canStringMatch) + if (isCompiled(true) || canStringMatch) return true; - return compile(cx, true); + return compile(cx, true, nullptr, 0); } +#endif // JS_YARR RegExpRunStatus RegExpShared::execute(JSContext *cx, const jschar *chars, size_t length, @@ -538,7 +613,7 @@ RegExpShared::execute(JSContext *cx, const jschar *chars, size_t length, TraceLogger *logger = TraceLoggerForMainThread(cx->runtime()); /* Compile the code at point-of-use. */ - if (!compileIfNecessary(cx)) + if (!compileIfNecessary(cx, chars, length)) return RegExpRunStatus_Error; /* Ensure sufficient memory for output vector. */ @@ -560,16 +635,18 @@ RegExpShared::execute(JSContext *cx, const jschar *chars, size_t length, start = 0; } - unsigned *outputBuf = matches.rawBuf(); - unsigned result; +#ifndef JS_YARR + // Reset the Irregexp backtrack stack if it grows during execution. + irregexp::RegExpStackScope stackScope(cx->runtime()); +#endif if (canStringMatch) { int res = StringFindPattern(chars+start, length-start, source->chars(), source->length()); if (res == -1) return RegExpRunStatus_Success_NotFound; - outputBuf[0] = res + start; - outputBuf[1] = outputBuf[0] + source->length(); + matches[0].start = res + start; + matches[0].limit = res + start + source->length(); matches.displace(displacement); matches.checkAgainst(origLength); @@ -577,20 +654,28 @@ RegExpShared::execute(JSContext *cx, const jschar *chars, size_t length, return RegExpRunStatus_Success; } -#if ENABLE_YARR_JIT +#ifdef JS_YARR + + unsigned result; + + // Yarr wants plain integers for its output buffer (whatever). + JS_STATIC_ASSERT(sizeof(int32_t) == sizeof(int)); + JS_STATIC_ASSERT(sizeof(int32_t) == sizeof(unsigned)); + +#ifdef JS_ION if (codeBlock.isFallBack()) { AutoTraceLog logInterpret(logger, TraceLogger::YarrInterpret); - result = JSC::Yarr::interpret(cx, bytecode, chars, length, start, outputBuf); + result = JSC::Yarr::interpret(cx, bytecode, chars, length, start, (unsigned *) matches.pairsRaw()); } else { AutoTraceLog logJIT(logger, TraceLogger::YarrJIT); - result = codeBlock.execute(chars, start, length, (int *)outputBuf).start; + result = codeBlock.execute(chars, start, length, (int *) matches.pairsRaw()).start; } -#else +#else // JS_ION { AutoTraceLog logInterpret(logger, TraceLogger::YarrInterpret); - result = JSC::Yarr::interpret(cx, bytecode, chars, length, start, outputBuf); + result = JSC::Yarr::interpret(cx, bytecode, chars, length, start, (unsigned *) matches.pairsRaw()); } -#endif +#endif // JS_ION if (result == JSC::Yarr::offsetError) { reportYarrError(cx, nullptr, JSC::Yarr::RuntimeError); @@ -600,12 +685,68 @@ RegExpShared::execute(JSContext *cx, const jschar *chars, size_t length, if (result == JSC::Yarr::offsetNoMatch) return RegExpRunStatus_Success_NotFound; +#else // JS_YARR + + if (hasByteCode()) { + AutoTraceLog logInterpreter(logger, TraceLogger::IrregexpExecute); + RegExpRunStatus result = + irregexp::InterpretCode(cx, byteCode, chars, start, length, &matches); + if (result == RegExpRunStatus_Success) { + matches.displace(displacement); + matches.checkAgainst(origLength); + *lastIndex = matches[0].limit; + } + return result; + } + +#ifdef JS_ION + while (true) { + RegExpRunStatus result; + { + AutoTraceLog logJIT(logger, TraceLogger::IrregexpExecute); + result = irregexp::ExecuteCode(cx, jitCode, chars, start, length, &matches); + } + + if (result == RegExpRunStatus_Error) { + // The RegExp engine might exit with an exception if an interrupt + // was requested. Check this case and retry until a clean result is + // obtained. + bool interrupted; + { + JSRuntime::AutoLockForInterrupt lock(cx->runtime()); + interrupted = cx->runtime()->interrupt; + } + + if (interrupted) { + if (!InvokeInterruptCallback(cx)) + return RegExpRunStatus_Error; + continue; + } + + js_ReportOverRecursed(cx); + return RegExpRunStatus_Error; + } + + if (result == RegExpRunStatus_Success_NotFound) + return RegExpRunStatus_Success_NotFound; + + JS_ASSERT(result == RegExpRunStatus_Success); + break; + } +#else // JS_ION + MOZ_CRASH(); +#endif // JS_ION + +#endif // JS_YARR + matches.displace(displacement); matches.checkAgainst(origLength); *lastIndex = matches[0].limit; return RegExpRunStatus_Success; } +#ifdef JS_YARR + RegExpRunStatus RegExpShared::executeMatchOnly(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex, MatchPair &match) @@ -640,7 +781,7 @@ RegExpShared::executeMatchOnly(JSContext *cx, const jschar *chars, size_t length return RegExpRunStatus_Success; } -#if ENABLE_YARR_JIT +#ifdef JS_ION if (!codeBlock.isFallBack()) { AutoTraceLog logJIT(logger, TraceLogger::YarrJIT); MatchResult result = codeBlock.execute(chars, start, length); @@ -667,7 +808,7 @@ RegExpShared::executeMatchOnly(JSContext *cx, const jschar *chars, size_t length unsigned result; { AutoTraceLog logInterpret(logger, TraceLogger::YarrInterpret); - result = JSC::Yarr::interpret(cx, bytecode, chars, length, start, matches.rawBuf()); + result = JSC::Yarr::interpret(cx, bytecode, chars, length, start, (unsigned *) matches.pairsRaw()); } if (result == JSC::Yarr::offsetError) { @@ -690,6 +831,25 @@ RegExpShared::executeMatchOnly(JSContext *cx, const jschar *chars, size_t length return RegExpRunStatus_Success; } +#endif // JS_YARR + +size_t +RegExpShared::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) +{ + size_t n = mallocSizeOf(this); + +#ifndef JS_YARR + if (byteCode) + n += mallocSizeOf(byteCode); +#endif + + n += tables.sizeOfExcludingThis(mallocSizeOf); + for (size_t i = 0; i < tables.length(); i++) + n += mallocSizeOf(tables[i]); + + return n; +} + /* RegExpCompartment */ RegExpCompartment::RegExpCompartment(JSRuntime *rt) @@ -830,6 +990,10 @@ RegExpCompartment::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) size_t n = 0; n += map_.sizeOfExcludingThis(mallocSizeOf); n += inUse_.sizeOfExcludingThis(mallocSizeOf); + for (PendingSet::Enum e(inUse_); !e.empty(); e.popFront()) { + RegExpShared *shared = e.front(); + n += shared->sizeOfIncludingThis(mallocSizeOf); + } return n; } @@ -901,7 +1065,8 @@ js::XDRScriptRegExpObject(XDRState *xdr, HeapPtrObject *objp) return false; if (mode == XDR_DECODE) { RegExpFlag flags = RegExpFlag(flagsword); - RegExpObject *reobj = RegExpObject::createNoStatics(xdr->cx(), source, flags, nullptr); + RegExpObject *reobj = RegExpObject::createNoStatics(xdr->cx(), source, flags, nullptr, + xdr->cx()->tempLifoAlloc()); if (!reobj) return false; @@ -922,5 +1087,5 @@ js::CloneScriptRegExpObject(JSContext *cx, RegExpObject &reobj) /* NB: Keep this in sync with XDRScriptRegExpObject. */ RootedAtom source(cx, reobj.getSource()); - return RegExpObject::createNoStatics(cx, source, reobj.getFlags(), nullptr); + return RegExpObject::createNoStatics(cx, source, reobj.getFlags(), nullptr, cx->tempLifoAlloc()); } diff --git a/js/src/vm/RegExpObject.h b/js/src/vm/RegExpObject.h index ec4b7f0fc79..412065fa444 100644 --- a/js/src/vm/RegExpObject.h +++ b/js/src/vm/RegExpObject.h @@ -16,11 +16,13 @@ #include "gc/Marking.h" #include "gc/Zone.h" #include "vm/Shape.h" -#if ENABLE_YARR_JIT + +#ifdef JS_YARR +#ifdef JS_ION #include "yarr/YarrJIT.h" -#else -#include "yarr/YarrInterpreter.h" #endif +#include "yarr/YarrInterpreter.h" +#endif // JS_YARR /* * JavaScript Regular Expressions @@ -42,7 +44,6 @@ */ namespace js { -class MatchConduit; class MatchPair; class MatchPairs; class RegExpShared; @@ -127,13 +128,16 @@ class RegExpShared friend class RegExpGuard; typedef frontend::TokenStream TokenStream; + +#ifdef JS_YARR typedef JSC::Yarr::BytecodePattern BytecodePattern; typedef JSC::Yarr::ErrorCode ErrorCode; typedef JSC::Yarr::YarrPattern YarrPattern; -#if ENABLE_YARR_JIT +#ifdef JS_ION typedef JSC::Yarr::JSGlobalData JSGlobalData; typedef JSC::Yarr::YarrCodeBlock YarrCodeBlock; typedef JSC::Yarr::YarrJITCompileMode YarrJITCompileMode; +#endif #endif /* @@ -144,40 +148,53 @@ class RegExpShared JSAtom * source; RegExpFlag flags; - unsigned parenCount; + size_t parenCount; bool canStringMatch; -#if ENABLE_YARR_JIT +#ifdef JS_YARR + +#ifdef JS_ION /* Note: Native code is valid only if |codeBlock.isFallBack() == false|. */ YarrCodeBlock codeBlock; #endif BytecodePattern *bytecode; +#else // JS_YARR + +#ifdef JS_ION + HeapPtrJitCode jitCode; +#endif + uint8_t *byteCode; + +#endif // JS_YARR + + // Tables referenced by JIT code. + Vector tables; + /* Lifetime-preserving variables: see class-level comment above. */ size_t activeUseCount; uint64_t gcNumberWhenUsed; /* Internal functions. */ - bool compile(JSContext *cx, bool matchOnly); - bool compile(JSContext *cx, JSLinearString &pattern, bool matchOnly); + bool compile(JSContext *cx, bool matchOnly, const jschar *sampleChars, size_t sampleLength); + bool compile(JSContext *cx, HandleAtom pattern, bool matchOnly, const jschar *sampleChars, size_t sampleLength); - bool compileIfNecessary(JSContext *cx); + bool compileIfNecessary(JSContext *cx, const jschar *sampleChars, size_t sampleLength); + +#ifdef JS_YARR bool compileMatchOnlyIfNecessary(JSContext *cx); +#endif public: RegExpShared(JSAtom *source, RegExpFlag flags, uint64_t gcNumber); ~RegExpShared(); - /* Explicit trace function for use by the RegExpStatics and JITs. */ - void trace(JSTracer *trc) { - MarkStringUnbarriered(trc, &source, "regexpshared source"); - } - +#ifdef JS_YARR /* Static functions to expose some Yarr logic. */ // This function should be deleted once bad Android platforms phase out. See bug 604774. static bool isJITRuntimeEnabled(JSContext *cx) { - #if ENABLE_YARR_JIT + #ifdef JS_ION # if defined(ANDROID) return !cx->jitIsBroken; # else @@ -189,24 +206,39 @@ class RegExpShared } static void reportYarrError(ExclusiveContext *cx, TokenStream *ts, ErrorCode error); static bool checkSyntax(ExclusiveContext *cx, TokenStream *tokenStream, JSLinearString *source); +#endif // JS_YARR /* Called when a RegExpShared is installed into a RegExpObject. */ void prepareForUse(ExclusiveContext *cx) { gcNumberWhenUsed = cx->zone()->gcNumber(); + JSString::writeBarrierPre(source); +#ifndef JS_YARR +#ifdef JS_ION + if (jitCode) + jit::JitCode::writeBarrierPre(jitCode); +#endif +#endif // !JS_YARR } /* Primary interface: run this regular expression on the given string. */ RegExpRunStatus execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex, MatchPairs &matches); +#ifdef JS_YARR /* Run the regular expression without collecting matches, for test(). */ RegExpRunStatus executeMatchOnly(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex, MatchPair &match); +#endif + + // Register a table with this RegExpShared, and take ownership. + bool addTable(uint8_t *table) { + return tables.append(table); + } /* Accessors */ size_t getParenCount() const { - JS_ASSERT(isCompiled() || canStringMatch); + JS_ASSERT(isCompiled(true) || isCompiled(false) || canStringMatch); return parenCount; } @@ -222,44 +254,64 @@ class RegExpShared bool multiline() const { return flags & MultilineFlag; } bool sticky() const { return flags & StickyFlag; } -#ifdef ENABLE_YARR_JIT - bool hasCode() const { return codeBlock.has16BitCode(); } - bool hasMatchOnlyCode() const { return codeBlock.has16BitCodeMatchOnly(); } +#ifdef JS_YARR + + bool hasCode(bool matchOnly) const { +#ifdef JS_ION + return matchOnly ? codeBlock.has16BitCodeMatchOnly() : codeBlock.has16BitCode(); #else - bool hasCode() const { return false; } - bool hasMatchOnlyCode() const { return false; } + return false; #endif - bool hasBytecode() const { return bytecode != nullptr; } - bool isCompiled() const { return hasBytecode() || hasCode() || hasMatchOnlyCode(); } + } + bool hasBytecode() const { + return bytecode != nullptr; + } + bool isCompiled(bool matchOnly) const { + return hasBytecode() || hasCode(matchOnly); + } + +#else // JS_YARR + + bool hasJitCode() const { +#ifdef JS_ION + return jitCode != nullptr; +#else + return false; +#endif + } + bool hasByteCode() const { + return byteCode != nullptr; + } + + bool isCompiled(bool matchOnly) const { + return hasJitCode() || hasByteCode(); + } + +#endif // JS_YARR + + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); }; /* * Extend the lifetime of a given RegExpShared to at least the lifetime of * the guard object. See Regular Expression comment at the top. */ -class RegExpGuard +class RegExpGuard : public JS::CustomAutoRooter { RegExpShared *re_; - /* - * Prevent the RegExp source from being collected: - * because RegExpShared objects compile at execution time, the source - * must remain rooted for the active lifetime of the RegExpShared. - */ - RootedAtom source_; - RegExpGuard(const RegExpGuard &) MOZ_DELETE; void operator=(const RegExpGuard &) MOZ_DELETE; public: RegExpGuard(ExclusiveContext *cx) - : re_(nullptr), source_(cx) + : CustomAutoRooter(cx), re_(nullptr) {} RegExpGuard(ExclusiveContext *cx, RegExpShared &re) - : re_(&re), source_(cx, re.source) + : CustomAutoRooter(cx), re_(nullptr) { - re_->incRef(); + init(re); } ~RegExpGuard() { @@ -271,17 +323,32 @@ class RegExpGuard JS_ASSERT(!initialized()); re_ = &re; re_->incRef(); - source_ = re_->source; } void release() { if (re_) { re_->decRef(); re_ = nullptr; - source_ = nullptr; } } + virtual void trace(JSTracer *trc) { + if (!re_) + return; + if (re_->source) { + MarkStringRoot(trc, reinterpret_cast(&re_->source), + "RegExpGuard source"); + } +#ifndef JS_YARR +#ifdef JS_ION + if (re_->jitCode) { + MarkJitCodeRoot(trc, reinterpret_cast(&re_->jitCode), + "RegExpGuard code"); + } +#endif +#endif // !JS_YARR + } + bool initialized() const { return !!re_; } RegExpShared *re() const { JS_ASSERT(initialized()); return re_; } RegExpShared *operator->() { return re(); } @@ -376,14 +443,15 @@ class RegExpObject : public JSObject */ static RegExpObject * create(ExclusiveContext *cx, RegExpStatics *res, const jschar *chars, size_t length, - RegExpFlag flags, frontend::TokenStream *ts); + RegExpFlag flags, frontend::TokenStream *ts, LifoAlloc &alloc); static RegExpObject * createNoStatics(ExclusiveContext *cx, const jschar *chars, size_t length, RegExpFlag flags, - frontend::TokenStream *ts); + frontend::TokenStream *ts, LifoAlloc &alloc); static RegExpObject * - createNoStatics(ExclusiveContext *cx, HandleAtom atom, RegExpFlag flags, frontend::TokenStream *ts); + createNoStatics(ExclusiveContext *cx, HandleAtom atom, RegExpFlag flags, + frontend::TokenStream *ts, LifoAlloc &alloc); /* Accessors. */ diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index 5962642715b..481ba9d5556 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -39,7 +39,9 @@ #include "jit/PcScriptCache.h" #include "js/MemoryMetrics.h" #include "js/SliceBudget.h" +#ifdef JS_YARR #include "yarr/BumpPointerAllocator.h" +#endif #include "jscntxtinlines.h" #include "jsgcinlines.h" @@ -104,6 +106,11 @@ PerThreadData::init() if (!dtoaState) return false; +#ifndef JS_YARR + if (!regexpStack.init()) + return false; +#endif + return true; } @@ -146,7 +153,9 @@ JSRuntime::JSRuntime(JSRuntime *parentRuntime, JSUseHelperThreads useHelperThrea tempLifoAlloc(TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE), freeLifoAlloc(TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE), execAlloc_(nullptr), +#ifdef JS_YARR bumpAlloc_(nullptr), +#endif jitRuntime_(nullptr), selfHostingGlobal_(nullptr), nativeStackBase(0), @@ -436,7 +445,9 @@ JSRuntime::~JSRuntime() #endif js_free(defaultLocale); +#ifdef JS_YARR js_delete(bumpAlloc_); +#endif js_delete(mathCache_); #ifdef JS_ION js_delete(jitRuntime_); @@ -511,7 +522,9 @@ JSRuntime::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::Runtim rtSizes->temporary += tempLifoAlloc.sizeOfExcludingThis(mallocSizeOf); +#ifdef JS_YARR rtSizes->regexpData += bumpAlloc_ ? bumpAlloc_->sizeOfNonHeapData() : 0; +#endif rtSizes->interpreterStack += interpreterStack_.sizeOfExcludingThis(mallocSizeOf); @@ -595,6 +608,8 @@ JSRuntime::createExecutableAllocator(JSContext *cx) return execAlloc_; } +#ifdef JS_YARR + WTF::BumpPointerAllocator * JSRuntime::createBumpPointerAllocator(JSContext *cx) { @@ -607,6 +622,8 @@ JSRuntime::createBumpPointerAllocator(JSContext *cx) return bumpAlloc_; } +#endif // JS_YARR + MathCache * JSRuntime::createMathCache(JSContext *cx) { diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index 7d129690c14..4ad5df5435b 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -28,6 +28,9 @@ #include "frontend/ParseMaps.h" #include "gc/GCRuntime.h" #include "gc/Tracer.h" +#ifndef JS_YARR +#include "irregexp/RegExpStack.h" +#endif #ifdef XP_MACOSX # include "jit/AsmJSSignalHandlers.h" #endif @@ -72,7 +75,9 @@ js_ReportOverRecursed(js::ThreadSafeContext *cx); namespace JSC { class ExecutableAllocator; } +#ifdef JS_YARR namespace WTF { class BumpPointerAllocator; } +#endif namespace js { @@ -493,6 +498,11 @@ class PerThreadData : public PerThreadDataFriendFields inline void setJitStackLimit(uintptr_t limit); +#ifndef JS_YARR + // Information about the heap allocated backtrack stack used by RegExp JIT code. + irregexp::RegExpStack regexpStack; +#endif + #ifdef JS_TRACE_LOGGING TraceLogger *traceLogger; #endif @@ -787,7 +797,9 @@ struct JSRuntime : public JS::shadow::Runtime, * thread-data level. */ JSC::ExecutableAllocator *execAlloc_; +#ifdef JS_YARR WTF::BumpPointerAllocator *bumpAlloc_; +#endif js::jit::JitRuntime *jitRuntime_; /* @@ -800,7 +812,9 @@ struct JSRuntime : public JS::shadow::Runtime, js::InterpreterStack interpreterStack_; JSC::ExecutableAllocator *createExecutableAllocator(JSContext *cx); +#ifdef JS_YARR WTF::BumpPointerAllocator *createBumpPointerAllocator(JSContext *cx); +#endif js::jit::JitRuntime *createJitRuntime(JSContext *cx); public: @@ -814,9 +828,11 @@ struct JSRuntime : public JS::shadow::Runtime, JSC::ExecutableAllocator *maybeExecAlloc() { return execAlloc_; } +#ifdef JS_YARR WTF::BumpPointerAllocator *getBumpPointerAllocator(JSContext *cx) { return bumpAlloc_ ? bumpAlloc_ : createBumpPointerAllocator(cx); } +#endif js::jit::JitRuntime *getJitRuntime(JSContext *cx) { return jitRuntime_ ? jitRuntime_ : createJitRuntime(cx); } diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index be119523f00..243e700077c 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -1123,7 +1123,7 @@ CloneObject(JSContext *cx, HandleObject selfHostedObject) RegExpObject &reobj = selfHostedObject->as(); RootedAtom source(cx, reobj.getSource()); JS_ASSERT(source->isPermanentAtom()); - clone = RegExpObject::createNoStatics(cx, source, reobj.getFlags(), nullptr); + clone = RegExpObject::createNoStatics(cx, source, reobj.getFlags(), nullptr, cx->tempLifoAlloc()); } else if (selfHostedObject->is()) { clone = JS_NewDateObjectMsec(cx, selfHostedObject->as().UTCTime().toNumber()); } else if (selfHostedObject->is()) { diff --git a/js/src/vm/StructuredClone.cpp b/js/src/vm/StructuredClone.cpp index 5f0953c0486..85bf2548a98 100644 --- a/js/src/vm/StructuredClone.cpp +++ b/js/src/vm/StructuredClone.cpp @@ -1403,7 +1403,8 @@ JSStructuredCloneReader::startRead(Value *vp) return false; RegExpObject *reobj = RegExpObject::createNoStatics(context(), flat->chars(), - flat->length(), flags, nullptr); + flat->length(), flags, nullptr, + context()->tempLifoAlloc()); if (!reobj) return false; vp->setObject(*reobj); diff --git a/js/src/vm/TraceLogging.cpp b/js/src/vm/TraceLogging.cpp index 56ab9e9f793..978ae972984 100644 --- a/js/src/vm/TraceLogging.cpp +++ b/js/src/vm/TraceLogging.cpp @@ -825,9 +825,14 @@ TraceLogging::lazyInit() enabledTextIds[TraceLogger::ParserCompileFunction] = true; enabledTextIds[TraceLogger::ParserCompileLazy] = true; enabledTextIds[TraceLogger::ParserCompileScript] = true; +#ifdef JS_YARR enabledTextIds[TraceLogger::YarrCompile] = true; enabledTextIds[TraceLogger::YarrInterpret] = true; enabledTextIds[TraceLogger::YarrJIT] = true; +#else + enabledTextIds[TraceLogger::IrregexpCompile] = true; + enabledTextIds[TraceLogger::IrregexpExecute] = true; +#endif } if (ContainsFlag(env, "IonCompiler") || strlen(env) == 0) { diff --git a/js/src/vm/TraceLogging.h b/js/src/vm/TraceLogging.h index f74563bbe7f..5f465d46b63 100644 --- a/js/src/vm/TraceLogging.h +++ b/js/src/vm/TraceLogging.h @@ -128,6 +128,8 @@ namespace jit { _(YarrCompile) \ _(YarrInterpret) \ _(YarrJIT) \ + _(IrregexpCompile) \ + _(IrregexpExecute) \ _(VM) \ \ /* Specific passes during ion compilation */ \ diff --git a/js/src/vm/Unicode.h b/js/src/vm/Unicode.h index 4c84935381c..c8e6b14f41b 100644 --- a/js/src/vm/Unicode.h +++ b/js/src/vm/Unicode.h @@ -69,7 +69,7 @@ const jschar NO_BREAK_SPACE = 0x00A0; class CharacterInfo { /* - * upperCase and loweCase normally store the delta between two + * upperCase and lowerCase normally store the delta between two * letters. For example the lower case alpha (a) has the char code * 97, and the upper case alpha (A) has 65. So for "a" we would * store -32 in upperCase (97 + (-32) = 65) and 0 in lowerCase, diff --git a/js/src/yarr/YarrJIT.cpp b/js/src/yarr/YarrJIT.cpp index a8a2d1e33ab..276eaf11396 100644 --- a/js/src/yarr/YarrJIT.cpp +++ b/js/src/yarr/YarrJIT.cpp @@ -31,7 +31,7 @@ #include "yarr/Yarr.h" #include "yarr/YarrCanonicalizeUCS2.h" -#if ENABLE_YARR_JIT +#if JS_ION using namespace WTF; diff --git a/js/src/yarr/YarrJIT.h b/js/src/yarr/YarrJIT.h index 954d2a4d4d9..30613dd5099 100644 --- a/js/src/yarr/YarrJIT.h +++ b/js/src/yarr/YarrJIT.h @@ -30,7 +30,7 @@ #include "assembler/wtf/Platform.h" -#if ENABLE_YARR_JIT +#if JS_ION #include "assembler/assembler/MacroAssemblerCodeRef.h" diff --git a/js/src/yarr/wtfbridge.h b/js/src/yarr/wtfbridge.h index 0d760c9fc3a..fe2a5f65f31 100644 --- a/js/src/yarr/wtfbridge.h +++ b/js/src/yarr/wtfbridge.h @@ -254,7 +254,7 @@ dataLogF(const char *fmt, ...) va_end(ap); } -#if ENABLE_YARR_JIT +#if JS_ION /* * Minimal JSGlobalData. This used by Yarr to get the allocator. diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp index afe63f86b73..9bb636db40d 100644 --- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -1550,6 +1550,7 @@ ReloadPrefsCallback(const char *pref, void *data) bool useBaseline = Preferences::GetBool(JS_OPTIONS_DOT_STR "baselinejit") && !safeMode; bool useIon = Preferences::GetBool(JS_OPTIONS_DOT_STR "ion") && !safeMode; bool useAsmJS = Preferences::GetBool(JS_OPTIONS_DOT_STR "asmjs") && !safeMode; + bool useNativeRegExp = Preferences::GetBool(JS_OPTIONS_DOT_STR "native_regexp") && !safeMode; bool parallelParsing = Preferences::GetBool(JS_OPTIONS_DOT_STR "parallel_parsing"); bool parallelIonCompilation = Preferences::GetBool(JS_OPTIONS_DOT_STR @@ -1562,7 +1563,8 @@ ReloadPrefsCallback(const char *pref, void *data) JS::RuntimeOptionsRef(rt).setBaseline(useBaseline) .setIon(useIon) - . setAsmJS(useAsmJS); + .setAsmJS(useAsmJS) + .setNativeRegExp(useNativeRegExp); JS_SetParallelParsingEnabled(rt, parallelParsing); JS_SetParallelIonCompilationEnabled(rt, parallelIonCompilation); @@ -2135,7 +2137,7 @@ ReportCompartmentStats(const JS::CompartmentStats &cStats, ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("regexp-compartment"), cStats.regexpCompartment, - "The regexp compartment."); + "The regexp compartment and regexp data."); ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("debuggees-set"), cStats.debuggeesSet, @@ -2303,9 +2305,11 @@ ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats &rtStats, "Transient data (mostly parse nodes) held by the JSRuntime during " "compilation."); +#ifdef JS_YARR RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/regexp-data"), KIND_NONHEAP, rtStats.runtime.regexpData, "Regexp JIT data."); +#endif RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/interpreter-stack"), KIND_HEAP, rtStats.runtime.interpreterStack, diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index 56d8f6874df..f5dc812c247 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -795,6 +795,7 @@ pref("javascript.options.strict.debug", true); pref("javascript.options.baselinejit", true); pref("javascript.options.ion", true); pref("javascript.options.asmjs", true); +pref("javascript.options.native_regexp", true); pref("javascript.options.parallel_parsing", true); pref("javascript.options.ion.parallel_compilation", true); // This preference instructs the JS engine to discard the diff --git a/toolkit/content/license.html b/toolkit/content/license.html index 781a95c411d..deee149688e 100644 --- a/toolkit/content/license.html +++ b/toolkit/content/license.html @@ -4240,6 +4240,7 @@ Additional Contributors:

This license applies to certain files in the directories js/src/v8-dtoa, + js/src/irregexp, js/src/builtin, js/src/jit/arm and js/src/jit/mips. From 03fadcbf08bdc8cb2427d8772d10c79617d9a976 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 15 May 2014 16:47:58 -0700 Subject: [PATCH 17/70] No bug. Fix a tiny error in the JS shell's help message. rs=terrence. DONTBUILD because it's a trivial string-only change. --HG-- extra : rebase_source : b98b49fbb60efed5953f4da6f2e47637172556c5 --- js/src/shell/js.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 89f5fb70025..804c4c7e422 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -6171,7 +6171,7 @@ main(int argc, char **argv, char **envp) || !op.addBoolOption('\0', "ion-eager", "Always ion-compile methods (implies --baseline-eager)") || !op.addBoolOption('\0', "ion-compile-try-catch", "Ion-compile try-catch statements") || !op.addStringOption('\0', "ion-parallel-compile", "on/off", - "Compile scripts off thread (default: off)") + "Compile scripts off thread (default: on)") || !op.addBoolOption('\0', "baseline", "Enable baseline compiler (default)") || !op.addBoolOption('\0', "no-baseline", "Disable baseline compiler") || !op.addBoolOption('\0', "baseline-eager", "Always baseline-compile methods") From a1ab4d859d366c5aea8affd9584fc18622577895 Mon Sep 17 00:00:00 2001 From: Monica Chew Date: Thu, 15 May 2014 16:56:51 -0700 Subject: [PATCH 18/70] Bug 1007844: Implement per-host telemetry for pin violations for AMO and aus4 (r=keeler) --- .../boot/src/PublicKeyPinningService.cpp | 12 +- security/manager/boot/src/StaticHPKPins.h | 620 +++++++++--------- .../manager/ssl/tests/unit/test_pinning.js | 6 +- security/manager/tools/PreloadedHPKPins.json | 11 +- security/manager/tools/genHPKPStaticPins.js | 10 + toolkit/components/telemetry/Histograms.json | 20 +- 6 files changed, 362 insertions(+), 317 deletions(-) diff --git a/security/manager/boot/src/PublicKeyPinningService.cpp b/security/manager/boot/src/PublicKeyPinningService.cpp index db006fb512f..f05e0cca9fd 100644 --- a/security/manager/boot/src/PublicKeyPinningService.cpp +++ b/security/manager/boot/src/PublicKeyPinningService.cpp @@ -201,7 +201,17 @@ CheckPinsForHostname(const CERTCertList *certList, const char *hostname, : Telemetry::CERT_PINNING_TEST_RESULTS; retval = true; } - Telemetry::Accumulate(histogram, result ? 1 : 0); + // We can collect per-host pinning violations for this host because it is + // operationally critical to Firefox. + if (foundEntry->mId != kUnknownId) { + int32_t bucket = foundEntry->mId * 2 + (result ? 1 : 0); + histogram = foundEntry->mTestMode + ? Telemetry::CERT_PINNING_MOZ_TEST_RESULTS_BY_HOST + : Telemetry::CERT_PINNING_MOZ_RESULTS_BY_HOST; + Telemetry::Accumulate(histogram, bucket); + } else { + Telemetry::Accumulate(histogram, result ? 1 : 0); + } PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG, ("pkpin: Pin check %s for %s host '%s' (mode=%s)\n", result ? "passed" : "failed", diff --git a/security/manager/boot/src/StaticHPKPins.h b/security/manager/boot/src/StaticHPKPins.h index 9b563a9dd43..cdb62b0e900 100644 --- a/security/manager/boot/src/StaticHPKPins.h +++ b/security/manager/boot/src/StaticHPKPins.h @@ -460,319 +460,323 @@ struct TransportSecurityPreload { const bool mIncludeSubdomains; const bool mTestMode; const bool mIsMoz; + const int32_t mId; const StaticPinset *pinset; }; /* Sort hostnames for binary search. */ static const TransportSecurityPreload kPublicKeyPinningPreloadList[] = { - { "accounts.google.com", true, true, false, &kPinset_google }, - { "addons.mozilla.net", true, true, true, &kPinset_mozilla }, - { "addons.mozilla.org", true, true, true, &kPinset_mozilla }, - { "admin.google.com", true, true, false, &kPinset_google }, - { "android.com", true, true, false, &kPinset_google }, - { "api.twitter.com", true, true, false, &kPinset_twitterCDN }, - { "apis.google.com", true, true, false, &kPinset_google }, - { "appengine.google.com", true, true, false, &kPinset_google }, - { "appspot.com", true, true, false, &kPinset_google }, - { "blog.torproject.org", true, true, false, &kPinset_tor }, - { "business.twitter.com", true, true, false, &kPinset_twitterCom }, - { "cdn.mozilla.net", true, true, true, &kPinset_mozilla }, - { "cdn.mozilla.org", true, true, true, &kPinset_mozilla }, - { "chart.apis.google.com", true, true, false, &kPinset_google }, - { "check.torproject.org", true, true, false, &kPinset_tor }, - { "checkout.google.com", true, true, false, &kPinset_google }, - { "chrome-devtools-frontend.appspot.com", true, true, false, &kPinset_google }, - { "chrome.google.com", true, true, false, &kPinset_google }, - { "chromiumcodereview.appspot.com", true, true, false, &kPinset_google }, - { "cloud.google.com", true, true, false, &kPinset_google }, - { "code.google.com", true, true, false, &kPinset_google }, - { "codereview.appspot.com", true, true, false, &kPinset_google }, - { "codereview.chromium.org", true, true, false, &kPinset_google }, - { "crypto.cat", false, true, false, &kPinset_cryptoCat }, - { "dev.twitter.com", true, true, false, &kPinset_twitterCom }, - { "dist.torproject.org", true, true, false, &kPinset_tor }, - { "dl.google.com", true, true, false, &kPinset_google }, - { "docs.google.com", true, true, false, &kPinset_google }, - { "doubleclick.net", true, true, false, &kPinset_google }, - { "drive.google.com", true, true, false, &kPinset_google }, - { "encrypted.google.com", true, true, false, &kPinset_google }, - { "exclude-subdomains.pinning.example.com", false, false, false, &kPinset_mozilla_test }, - { "g.co", true, true, false, &kPinset_google }, - { "glass.google.com", true, true, false, &kPinset_google }, - { "gmail.com", false, true, false, &kPinset_google }, - { "goo.gl", true, true, false, &kPinset_google }, - { "google-analytics.com", true, true, false, &kPinset_google }, - { "google.ac", true, true, false, &kPinset_google }, - { "google.ad", true, true, false, &kPinset_google }, - { "google.ae", true, true, false, &kPinset_google }, - { "google.af", true, true, false, &kPinset_google }, - { "google.ag", true, true, false, &kPinset_google }, - { "google.am", true, true, false, &kPinset_google }, - { "google.as", true, true, false, &kPinset_google }, - { "google.at", true, true, false, &kPinset_google }, - { "google.az", true, true, false, &kPinset_google }, - { "google.ba", true, true, false, &kPinset_google }, - { "google.be", true, true, false, &kPinset_google }, - { "google.bf", true, true, false, &kPinset_google }, - { "google.bg", true, true, false, &kPinset_google }, - { "google.bi", true, true, false, &kPinset_google }, - { "google.bj", true, true, false, &kPinset_google }, - { "google.bs", true, true, false, &kPinset_google }, - { "google.by", true, true, false, &kPinset_google }, - { "google.ca", true, true, false, &kPinset_google }, - { "google.cat", true, true, false, &kPinset_google }, - { "google.cc", true, true, false, &kPinset_google }, - { "google.cd", true, true, false, &kPinset_google }, - { "google.cf", true, true, false, &kPinset_google }, - { "google.cg", true, true, false, &kPinset_google }, - { "google.ch", true, true, false, &kPinset_google }, - { "google.ci", true, true, false, &kPinset_google }, - { "google.cl", true, true, false, &kPinset_google }, - { "google.cm", true, true, false, &kPinset_google }, - { "google.cn", true, true, false, &kPinset_google }, - { "google.co.ao", true, true, false, &kPinset_google }, - { "google.co.bw", true, true, false, &kPinset_google }, - { "google.co.ck", true, true, false, &kPinset_google }, - { "google.co.cr", true, true, false, &kPinset_google }, - { "google.co.hu", true, true, false, &kPinset_google }, - { "google.co.id", true, true, false, &kPinset_google }, - { "google.co.il", true, true, false, &kPinset_google }, - { "google.co.im", true, true, false, &kPinset_google }, - { "google.co.in", true, true, false, &kPinset_google }, - { "google.co.je", true, true, false, &kPinset_google }, - { "google.co.jp", true, true, false, &kPinset_google }, - { "google.co.ke", true, true, false, &kPinset_google }, - { "google.co.kr", true, true, false, &kPinset_google }, - { "google.co.ls", true, true, false, &kPinset_google }, - { "google.co.ma", true, true, false, &kPinset_google }, - { "google.co.mz", true, true, false, &kPinset_google }, - { "google.co.nz", true, true, false, &kPinset_google }, - { "google.co.th", true, true, false, &kPinset_google }, - { "google.co.tz", true, true, false, &kPinset_google }, - { "google.co.ug", true, true, false, &kPinset_google }, - { "google.co.uk", true, true, false, &kPinset_google }, - { "google.co.uz", true, true, false, &kPinset_google }, - { "google.co.ve", true, true, false, &kPinset_google }, - { "google.co.vi", true, true, false, &kPinset_google }, - { "google.co.za", true, true, false, &kPinset_google }, - { "google.co.zm", true, true, false, &kPinset_google }, - { "google.co.zw", true, true, false, &kPinset_google }, - { "google.com", true, true, false, &kPinset_google }, - { "google.com.af", true, true, false, &kPinset_google }, - { "google.com.ag", true, true, false, &kPinset_google }, - { "google.com.ai", true, true, false, &kPinset_google }, - { "google.com.ar", true, true, false, &kPinset_google }, - { "google.com.au", true, true, false, &kPinset_google }, - { "google.com.bd", true, true, false, &kPinset_google }, - { "google.com.bh", true, true, false, &kPinset_google }, - { "google.com.bn", true, true, false, &kPinset_google }, - { "google.com.bo", true, true, false, &kPinset_google }, - { "google.com.br", true, true, false, &kPinset_google }, - { "google.com.by", true, true, false, &kPinset_google }, - { "google.com.bz", true, true, false, &kPinset_google }, - { "google.com.cn", true, true, false, &kPinset_google }, - { "google.com.co", true, true, false, &kPinset_google }, - { "google.com.cu", true, true, false, &kPinset_google }, - { "google.com.cy", true, true, false, &kPinset_google }, - { "google.com.do", true, true, false, &kPinset_google }, - { "google.com.ec", true, true, false, &kPinset_google }, - { "google.com.eg", true, true, false, &kPinset_google }, - { "google.com.et", true, true, false, &kPinset_google }, - { "google.com.fj", true, true, false, &kPinset_google }, - { "google.com.ge", true, true, false, &kPinset_google }, - { "google.com.gh", true, true, false, &kPinset_google }, - { "google.com.gi", true, true, false, &kPinset_google }, - { "google.com.gr", true, true, false, &kPinset_google }, - { "google.com.gt", true, true, false, &kPinset_google }, - { "google.com.hk", true, true, false, &kPinset_google }, - { "google.com.iq", true, true, false, &kPinset_google }, - { "google.com.jm", true, true, false, &kPinset_google }, - { "google.com.jo", true, true, false, &kPinset_google }, - { "google.com.kh", true, true, false, &kPinset_google }, - { "google.com.kw", true, true, false, &kPinset_google }, - { "google.com.lb", true, true, false, &kPinset_google }, - { "google.com.ly", true, true, false, &kPinset_google }, - { "google.com.mt", true, true, false, &kPinset_google }, - { "google.com.mx", true, true, false, &kPinset_google }, - { "google.com.my", true, true, false, &kPinset_google }, - { "google.com.na", true, true, false, &kPinset_google }, - { "google.com.nf", true, true, false, &kPinset_google }, - { "google.com.ng", true, true, false, &kPinset_google }, - { "google.com.ni", true, true, false, &kPinset_google }, - { "google.com.np", true, true, false, &kPinset_google }, - { "google.com.nr", true, true, false, &kPinset_google }, - { "google.com.om", true, true, false, &kPinset_google }, - { "google.com.pa", true, true, false, &kPinset_google }, - { "google.com.pe", true, true, false, &kPinset_google }, - { "google.com.ph", true, true, false, &kPinset_google }, - { "google.com.pk", true, true, false, &kPinset_google }, - { "google.com.pl", true, true, false, &kPinset_google }, - { "google.com.pr", true, true, false, &kPinset_google }, - { "google.com.py", true, true, false, &kPinset_google }, - { "google.com.qa", true, true, false, &kPinset_google }, - { "google.com.ru", true, true, false, &kPinset_google }, - { "google.com.sa", true, true, false, &kPinset_google }, - { "google.com.sb", true, true, false, &kPinset_google }, - { "google.com.sg", true, true, false, &kPinset_google }, - { "google.com.sl", true, true, false, &kPinset_google }, - { "google.com.sv", true, true, false, &kPinset_google }, - { "google.com.tj", true, true, false, &kPinset_google }, - { "google.com.tn", true, true, false, &kPinset_google }, - { "google.com.tr", true, true, false, &kPinset_google }, - { "google.com.tw", true, true, false, &kPinset_google }, - { "google.com.ua", true, true, false, &kPinset_google }, - { "google.com.uy", true, true, false, &kPinset_google }, - { "google.com.vc", true, true, false, &kPinset_google }, - { "google.com.ve", true, true, false, &kPinset_google }, - { "google.com.vn", true, true, false, &kPinset_google }, - { "google.cv", true, true, false, &kPinset_google }, - { "google.cz", true, true, false, &kPinset_google }, - { "google.de", true, true, false, &kPinset_google }, - { "google.dj", true, true, false, &kPinset_google }, - { "google.dk", true, true, false, &kPinset_google }, - { "google.dm", true, true, false, &kPinset_google }, - { "google.dz", true, true, false, &kPinset_google }, - { "google.ee", true, true, false, &kPinset_google }, - { "google.es", true, true, false, &kPinset_google }, - { "google.fi", true, true, false, &kPinset_google }, - { "google.fm", true, true, false, &kPinset_google }, - { "google.fr", true, true, false, &kPinset_google }, - { "google.ga", true, true, false, &kPinset_google }, - { "google.ge", true, true, false, &kPinset_google }, - { "google.gg", true, true, false, &kPinset_google }, - { "google.gl", true, true, false, &kPinset_google }, - { "google.gm", true, true, false, &kPinset_google }, - { "google.gp", true, true, false, &kPinset_google }, - { "google.gr", true, true, false, &kPinset_google }, - { "google.gy", true, true, false, &kPinset_google }, - { "google.hk", true, true, false, &kPinset_google }, - { "google.hn", true, true, false, &kPinset_google }, - { "google.hr", true, true, false, &kPinset_google }, - { "google.ht", true, true, false, &kPinset_google }, - { "google.hu", true, true, false, &kPinset_google }, - { "google.ie", true, true, false, &kPinset_google }, - { "google.im", true, true, false, &kPinset_google }, - { "google.info", true, true, false, &kPinset_google }, - { "google.iq", true, true, false, &kPinset_google }, - { "google.is", true, true, false, &kPinset_google }, - { "google.it", true, true, false, &kPinset_google }, - { "google.it.ao", true, true, false, &kPinset_google }, - { "google.je", true, true, false, &kPinset_google }, - { "google.jo", true, true, false, &kPinset_google }, - { "google.jobs", true, true, false, &kPinset_google }, - { "google.jp", true, true, false, &kPinset_google }, - { "google.kg", true, true, false, &kPinset_google }, - { "google.ki", true, true, false, &kPinset_google }, - { "google.kz", true, true, false, &kPinset_google }, - { "google.la", true, true, false, &kPinset_google }, - { "google.li", true, true, false, &kPinset_google }, - { "google.lk", true, true, false, &kPinset_google }, - { "google.lt", true, true, false, &kPinset_google }, - { "google.lu", true, true, false, &kPinset_google }, - { "google.lv", true, true, false, &kPinset_google }, - { "google.md", true, true, false, &kPinset_google }, - { "google.me", true, true, false, &kPinset_google }, - { "google.mg", true, true, false, &kPinset_google }, - { "google.mk", true, true, false, &kPinset_google }, - { "google.ml", true, true, false, &kPinset_google }, - { "google.mn", true, true, false, &kPinset_google }, - { "google.ms", true, true, false, &kPinset_google }, - { "google.mu", true, true, false, &kPinset_google }, - { "google.mv", true, true, false, &kPinset_google }, - { "google.mw", true, true, false, &kPinset_google }, - { "google.ne", true, true, false, &kPinset_google }, - { "google.ne.jp", true, true, false, &kPinset_google }, - { "google.net", true, true, false, &kPinset_google }, - { "google.nl", true, true, false, &kPinset_google }, - { "google.no", true, true, false, &kPinset_google }, - { "google.nr", true, true, false, &kPinset_google }, - { "google.nu", true, true, false, &kPinset_google }, - { "google.off.ai", true, true, false, &kPinset_google }, - { "google.pk", true, true, false, &kPinset_google }, - { "google.pl", true, true, false, &kPinset_google }, - { "google.pn", true, true, false, &kPinset_google }, - { "google.ps", true, true, false, &kPinset_google }, - { "google.pt", true, true, false, &kPinset_google }, - { "google.ro", true, true, false, &kPinset_google }, - { "google.rs", true, true, false, &kPinset_google }, - { "google.ru", true, true, false, &kPinset_google }, - { "google.rw", true, true, false, &kPinset_google }, - { "google.sc", true, true, false, &kPinset_google }, - { "google.se", true, true, false, &kPinset_google }, - { "google.sh", true, true, false, &kPinset_google }, - { "google.si", true, true, false, &kPinset_google }, - { "google.sk", true, true, false, &kPinset_google }, - { "google.sm", true, true, false, &kPinset_google }, - { "google.sn", true, true, false, &kPinset_google }, - { "google.so", true, true, false, &kPinset_google }, - { "google.st", true, true, false, &kPinset_google }, - { "google.td", true, true, false, &kPinset_google }, - { "google.tg", true, true, false, &kPinset_google }, - { "google.tk", true, true, false, &kPinset_google }, - { "google.tl", true, true, false, &kPinset_google }, - { "google.tm", true, true, false, &kPinset_google }, - { "google.tn", true, true, false, &kPinset_google }, - { "google.to", true, true, false, &kPinset_google }, - { "google.tp", true, true, false, &kPinset_google }, - { "google.tt", true, true, false, &kPinset_google }, - { "google.us", true, true, false, &kPinset_google }, - { "google.uz", true, true, false, &kPinset_google }, - { "google.vg", true, true, false, &kPinset_google }, - { "google.vu", true, true, false, &kPinset_google }, - { "google.ws", true, true, false, &kPinset_google }, - { "googleadservices.com", true, true, false, &kPinset_google }, - { "googleapis.com", true, true, false, &kPinset_google }, - { "googlecode.com", true, true, false, &kPinset_google }, - { "googlecommerce.com", true, true, false, &kPinset_google }, - { "googlegroups.com", true, true, false, &kPinset_google }, - { "googlemail.com", false, true, false, &kPinset_google }, - { "googleplex.com", true, true, false, &kPinset_google }, - { "googlesyndication.com", true, true, false, &kPinset_google }, - { "googletagmanager.com", true, true, false, &kPinset_google }, - { "googletagservices.com", true, true, false, &kPinset_google }, - { "googleusercontent.com", true, true, false, &kPinset_google }, - { "goto.google.com", true, true, false, &kPinset_google }, - { "groups.google.com", true, true, false, &kPinset_google }, - { "gstatic.com", true, true, false, &kPinset_google }, - { "history.google.com", true, true, false, &kPinset_google }, - { "hostedtalkgadget.google.com", true, true, false, &kPinset_google }, - { "include-subdomains.pinning.example.com", true, false, false, &kPinset_mozilla_test }, - { "liberty.lavabit.com", true, true, false, &kPinset_lavabit }, - { "mail.google.com", true, true, false, &kPinset_google }, - { "market.android.com", true, true, false, &kPinset_google }, - { "media.mozilla.com", true, true, true, &kPinset_mozilla }, - { "mobile.twitter.com", true, true, false, &kPinset_twitterCom }, - { "oauth.twitter.com", true, true, false, &kPinset_twitterCom }, - { "pinningtest.appspot.com", true, true, false, &kPinset_test }, - { "platform.twitter.com", true, true, false, &kPinset_twitterCDN }, - { "play.google.com", false, true, false, &kPinset_google }, - { "plus.google.com", true, true, false, &kPinset_google }, - { "plus.sandbox.google.com", true, true, false, &kPinset_google }, - { "profiles.google.com", true, true, false, &kPinset_google }, - { "script.google.com", true, true, false, &kPinset_google }, - { "security.google.com", true, true, false, &kPinset_google }, - { "sites.google.com", true, true, false, &kPinset_google }, - { "spreadsheets.google.com", true, true, false, &kPinset_google }, - { "ssl.google-analytics.com", true, true, false, &kPinset_google }, - { "talk.google.com", true, true, false, &kPinset_google }, - { "talkgadget.google.com", true, true, false, &kPinset_google }, - { "test-mode.pinning.example.com", true, true, false, &kPinset_mozilla_test }, - { "tor2web.org", true, true, false, &kPinset_tor2web }, - { "torproject.org", false, true, false, &kPinset_tor }, - { "translate.googleapis.com", true, true, false, &kPinset_google }, - { "twimg.com", true, true, false, &kPinset_twitterCDN }, - { "twitter.com", false, true, false, &kPinset_twitterCom }, - { "urchin.com", true, true, false, &kPinset_google }, - { "wallet.google.com", true, true, false, &kPinset_google }, - { "www.gmail.com", false, true, false, &kPinset_google }, - { "www.googlemail.com", false, true, false, &kPinset_google }, - { "www.torproject.org", true, true, false, &kPinset_tor }, - { "www.twitter.com", true, true, false, &kPinset_twitterCom }, - { "youtu.be", true, true, false, &kPinset_google }, - { "youtube.com", true, true, false, &kPinset_google }, - { "ytimg.com", true, true, false, &kPinset_google }, + { "accounts.google.com", true, true, false, -1, &kPinset_google }, + { "addons.mozilla.net", true, true, true, 2, &kPinset_mozilla }, + { "addons.mozilla.org", true, true, true, 1, &kPinset_mozilla }, + { "admin.google.com", true, true, false, -1, &kPinset_google }, + { "android.com", true, true, false, -1, &kPinset_google }, + { "api.twitter.com", true, true, false, -1, &kPinset_twitterCDN }, + { "apis.google.com", true, true, false, -1, &kPinset_google }, + { "appengine.google.com", true, true, false, -1, &kPinset_google }, + { "appspot.com", true, true, false, -1, &kPinset_google }, + { "aus4.mozilla.org", true, true, true, 3, &kPinset_mozilla }, + { "blog.torproject.org", true, true, false, -1, &kPinset_tor }, + { "business.twitter.com", true, true, false, -1, &kPinset_twitterCom }, + { "cdn.mozilla.net", true, true, true, -1, &kPinset_mozilla }, + { "cdn.mozilla.org", true, true, true, -1, &kPinset_mozilla }, + { "chart.apis.google.com", true, true, false, -1, &kPinset_google }, + { "check.torproject.org", true, true, false, -1, &kPinset_tor }, + { "checkout.google.com", true, true, false, -1, &kPinset_google }, + { "chrome-devtools-frontend.appspot.com", true, true, false, -1, &kPinset_google }, + { "chrome.google.com", true, true, false, -1, &kPinset_google }, + { "chromiumcodereview.appspot.com", true, true, false, -1, &kPinset_google }, + { "cloud.google.com", true, true, false, -1, &kPinset_google }, + { "code.google.com", true, true, false, -1, &kPinset_google }, + { "codereview.appspot.com", true, true, false, -1, &kPinset_google }, + { "codereview.chromium.org", true, true, false, -1, &kPinset_google }, + { "crypto.cat", false, true, false, -1, &kPinset_cryptoCat }, + { "dev.twitter.com", true, true, false, -1, &kPinset_twitterCom }, + { "dist.torproject.org", true, true, false, -1, &kPinset_tor }, + { "dl.google.com", true, true, false, -1, &kPinset_google }, + { "docs.google.com", true, true, false, -1, &kPinset_google }, + { "doubleclick.net", true, true, false, -1, &kPinset_google }, + { "drive.google.com", true, true, false, -1, &kPinset_google }, + { "encrypted.google.com", true, true, false, -1, &kPinset_google }, + { "exclude-subdomains.pinning.example.com", false, false, false, 0, &kPinset_mozilla_test }, + { "g.co", true, true, false, -1, &kPinset_google }, + { "glass.google.com", true, true, false, -1, &kPinset_google }, + { "gmail.com", false, true, false, -1, &kPinset_google }, + { "goo.gl", true, true, false, -1, &kPinset_google }, + { "google-analytics.com", true, true, false, -1, &kPinset_google }, + { "google.ac", true, true, false, -1, &kPinset_google }, + { "google.ad", true, true, false, -1, &kPinset_google }, + { "google.ae", true, true, false, -1, &kPinset_google }, + { "google.af", true, true, false, -1, &kPinset_google }, + { "google.ag", true, true, false, -1, &kPinset_google }, + { "google.am", true, true, false, -1, &kPinset_google }, + { "google.as", true, true, false, -1, &kPinset_google }, + { "google.at", true, true, false, -1, &kPinset_google }, + { "google.az", true, true, false, -1, &kPinset_google }, + { "google.ba", true, true, false, -1, &kPinset_google }, + { "google.be", true, true, false, -1, &kPinset_google }, + { "google.bf", true, true, false, -1, &kPinset_google }, + { "google.bg", true, true, false, -1, &kPinset_google }, + { "google.bi", true, true, false, -1, &kPinset_google }, + { "google.bj", true, true, false, -1, &kPinset_google }, + { "google.bs", true, true, false, -1, &kPinset_google }, + { "google.by", true, true, false, -1, &kPinset_google }, + { "google.ca", true, true, false, -1, &kPinset_google }, + { "google.cat", true, true, false, -1, &kPinset_google }, + { "google.cc", true, true, false, -1, &kPinset_google }, + { "google.cd", true, true, false, -1, &kPinset_google }, + { "google.cf", true, true, false, -1, &kPinset_google }, + { "google.cg", true, true, false, -1, &kPinset_google }, + { "google.ch", true, true, false, -1, &kPinset_google }, + { "google.ci", true, true, false, -1, &kPinset_google }, + { "google.cl", true, true, false, -1, &kPinset_google }, + { "google.cm", true, true, false, -1, &kPinset_google }, + { "google.cn", true, true, false, -1, &kPinset_google }, + { "google.co.ao", true, true, false, -1, &kPinset_google }, + { "google.co.bw", true, true, false, -1, &kPinset_google }, + { "google.co.ck", true, true, false, -1, &kPinset_google }, + { "google.co.cr", true, true, false, -1, &kPinset_google }, + { "google.co.hu", true, true, false, -1, &kPinset_google }, + { "google.co.id", true, true, false, -1, &kPinset_google }, + { "google.co.il", true, true, false, -1, &kPinset_google }, + { "google.co.im", true, true, false, -1, &kPinset_google }, + { "google.co.in", true, true, false, -1, &kPinset_google }, + { "google.co.je", true, true, false, -1, &kPinset_google }, + { "google.co.jp", true, true, false, -1, &kPinset_google }, + { "google.co.ke", true, true, false, -1, &kPinset_google }, + { "google.co.kr", true, true, false, -1, &kPinset_google }, + { "google.co.ls", true, true, false, -1, &kPinset_google }, + { "google.co.ma", true, true, false, -1, &kPinset_google }, + { "google.co.mz", true, true, false, -1, &kPinset_google }, + { "google.co.nz", true, true, false, -1, &kPinset_google }, + { "google.co.th", true, true, false, -1, &kPinset_google }, + { "google.co.tz", true, true, false, -1, &kPinset_google }, + { "google.co.ug", true, true, false, -1, &kPinset_google }, + { "google.co.uk", true, true, false, -1, &kPinset_google }, + { "google.co.uz", true, true, false, -1, &kPinset_google }, + { "google.co.ve", true, true, false, -1, &kPinset_google }, + { "google.co.vi", true, true, false, -1, &kPinset_google }, + { "google.co.za", true, true, false, -1, &kPinset_google }, + { "google.co.zm", true, true, false, -1, &kPinset_google }, + { "google.co.zw", true, true, false, -1, &kPinset_google }, + { "google.com", true, true, false, -1, &kPinset_google }, + { "google.com.af", true, true, false, -1, &kPinset_google }, + { "google.com.ag", true, true, false, -1, &kPinset_google }, + { "google.com.ai", true, true, false, -1, &kPinset_google }, + { "google.com.ar", true, true, false, -1, &kPinset_google }, + { "google.com.au", true, true, false, -1, &kPinset_google }, + { "google.com.bd", true, true, false, -1, &kPinset_google }, + { "google.com.bh", true, true, false, -1, &kPinset_google }, + { "google.com.bn", true, true, false, -1, &kPinset_google }, + { "google.com.bo", true, true, false, -1, &kPinset_google }, + { "google.com.br", true, true, false, -1, &kPinset_google }, + { "google.com.by", true, true, false, -1, &kPinset_google }, + { "google.com.bz", true, true, false, -1, &kPinset_google }, + { "google.com.cn", true, true, false, -1, &kPinset_google }, + { "google.com.co", true, true, false, -1, &kPinset_google }, + { "google.com.cu", true, true, false, -1, &kPinset_google }, + { "google.com.cy", true, true, false, -1, &kPinset_google }, + { "google.com.do", true, true, false, -1, &kPinset_google }, + { "google.com.ec", true, true, false, -1, &kPinset_google }, + { "google.com.eg", true, true, false, -1, &kPinset_google }, + { "google.com.et", true, true, false, -1, &kPinset_google }, + { "google.com.fj", true, true, false, -1, &kPinset_google }, + { "google.com.ge", true, true, false, -1, &kPinset_google }, + { "google.com.gh", true, true, false, -1, &kPinset_google }, + { "google.com.gi", true, true, false, -1, &kPinset_google }, + { "google.com.gr", true, true, false, -1, &kPinset_google }, + { "google.com.gt", true, true, false, -1, &kPinset_google }, + { "google.com.hk", true, true, false, -1, &kPinset_google }, + { "google.com.iq", true, true, false, -1, &kPinset_google }, + { "google.com.jm", true, true, false, -1, &kPinset_google }, + { "google.com.jo", true, true, false, -1, &kPinset_google }, + { "google.com.kh", true, true, false, -1, &kPinset_google }, + { "google.com.kw", true, true, false, -1, &kPinset_google }, + { "google.com.lb", true, true, false, -1, &kPinset_google }, + { "google.com.ly", true, true, false, -1, &kPinset_google }, + { "google.com.mt", true, true, false, -1, &kPinset_google }, + { "google.com.mx", true, true, false, -1, &kPinset_google }, + { "google.com.my", true, true, false, -1, &kPinset_google }, + { "google.com.na", true, true, false, -1, &kPinset_google }, + { "google.com.nf", true, true, false, -1, &kPinset_google }, + { "google.com.ng", true, true, false, -1, &kPinset_google }, + { "google.com.ni", true, true, false, -1, &kPinset_google }, + { "google.com.np", true, true, false, -1, &kPinset_google }, + { "google.com.nr", true, true, false, -1, &kPinset_google }, + { "google.com.om", true, true, false, -1, &kPinset_google }, + { "google.com.pa", true, true, false, -1, &kPinset_google }, + { "google.com.pe", true, true, false, -1, &kPinset_google }, + { "google.com.ph", true, true, false, -1, &kPinset_google }, + { "google.com.pk", true, true, false, -1, &kPinset_google }, + { "google.com.pl", true, true, false, -1, &kPinset_google }, + { "google.com.pr", true, true, false, -1, &kPinset_google }, + { "google.com.py", true, true, false, -1, &kPinset_google }, + { "google.com.qa", true, true, false, -1, &kPinset_google }, + { "google.com.ru", true, true, false, -1, &kPinset_google }, + { "google.com.sa", true, true, false, -1, &kPinset_google }, + { "google.com.sb", true, true, false, -1, &kPinset_google }, + { "google.com.sg", true, true, false, -1, &kPinset_google }, + { "google.com.sl", true, true, false, -1, &kPinset_google }, + { "google.com.sv", true, true, false, -1, &kPinset_google }, + { "google.com.tj", true, true, false, -1, &kPinset_google }, + { "google.com.tn", true, true, false, -1, &kPinset_google }, + { "google.com.tr", true, true, false, -1, &kPinset_google }, + { "google.com.tw", true, true, false, -1, &kPinset_google }, + { "google.com.ua", true, true, false, -1, &kPinset_google }, + { "google.com.uy", true, true, false, -1, &kPinset_google }, + { "google.com.vc", true, true, false, -1, &kPinset_google }, + { "google.com.ve", true, true, false, -1, &kPinset_google }, + { "google.com.vn", true, true, false, -1, &kPinset_google }, + { "google.cv", true, true, false, -1, &kPinset_google }, + { "google.cz", true, true, false, -1, &kPinset_google }, + { "google.de", true, true, false, -1, &kPinset_google }, + { "google.dj", true, true, false, -1, &kPinset_google }, + { "google.dk", true, true, false, -1, &kPinset_google }, + { "google.dm", true, true, false, -1, &kPinset_google }, + { "google.dz", true, true, false, -1, &kPinset_google }, + { "google.ee", true, true, false, -1, &kPinset_google }, + { "google.es", true, true, false, -1, &kPinset_google }, + { "google.fi", true, true, false, -1, &kPinset_google }, + { "google.fm", true, true, false, -1, &kPinset_google }, + { "google.fr", true, true, false, -1, &kPinset_google }, + { "google.ga", true, true, false, -1, &kPinset_google }, + { "google.ge", true, true, false, -1, &kPinset_google }, + { "google.gg", true, true, false, -1, &kPinset_google }, + { "google.gl", true, true, false, -1, &kPinset_google }, + { "google.gm", true, true, false, -1, &kPinset_google }, + { "google.gp", true, true, false, -1, &kPinset_google }, + { "google.gr", true, true, false, -1, &kPinset_google }, + { "google.gy", true, true, false, -1, &kPinset_google }, + { "google.hk", true, true, false, -1, &kPinset_google }, + { "google.hn", true, true, false, -1, &kPinset_google }, + { "google.hr", true, true, false, -1, &kPinset_google }, + { "google.ht", true, true, false, -1, &kPinset_google }, + { "google.hu", true, true, false, -1, &kPinset_google }, + { "google.ie", true, true, false, -1, &kPinset_google }, + { "google.im", true, true, false, -1, &kPinset_google }, + { "google.info", true, true, false, -1, &kPinset_google }, + { "google.iq", true, true, false, -1, &kPinset_google }, + { "google.is", true, true, false, -1, &kPinset_google }, + { "google.it", true, true, false, -1, &kPinset_google }, + { "google.it.ao", true, true, false, -1, &kPinset_google }, + { "google.je", true, true, false, -1, &kPinset_google }, + { "google.jo", true, true, false, -1, &kPinset_google }, + { "google.jobs", true, true, false, -1, &kPinset_google }, + { "google.jp", true, true, false, -1, &kPinset_google }, + { "google.kg", true, true, false, -1, &kPinset_google }, + { "google.ki", true, true, false, -1, &kPinset_google }, + { "google.kz", true, true, false, -1, &kPinset_google }, + { "google.la", true, true, false, -1, &kPinset_google }, + { "google.li", true, true, false, -1, &kPinset_google }, + { "google.lk", true, true, false, -1, &kPinset_google }, + { "google.lt", true, true, false, -1, &kPinset_google }, + { "google.lu", true, true, false, -1, &kPinset_google }, + { "google.lv", true, true, false, -1, &kPinset_google }, + { "google.md", true, true, false, -1, &kPinset_google }, + { "google.me", true, true, false, -1, &kPinset_google }, + { "google.mg", true, true, false, -1, &kPinset_google }, + { "google.mk", true, true, false, -1, &kPinset_google }, + { "google.ml", true, true, false, -1, &kPinset_google }, + { "google.mn", true, true, false, -1, &kPinset_google }, + { "google.ms", true, true, false, -1, &kPinset_google }, + { "google.mu", true, true, false, -1, &kPinset_google }, + { "google.mv", true, true, false, -1, &kPinset_google }, + { "google.mw", true, true, false, -1, &kPinset_google }, + { "google.ne", true, true, false, -1, &kPinset_google }, + { "google.ne.jp", true, true, false, -1, &kPinset_google }, + { "google.net", true, true, false, -1, &kPinset_google }, + { "google.nl", true, true, false, -1, &kPinset_google }, + { "google.no", true, true, false, -1, &kPinset_google }, + { "google.nr", true, true, false, -1, &kPinset_google }, + { "google.nu", true, true, false, -1, &kPinset_google }, + { "google.off.ai", true, true, false, -1, &kPinset_google }, + { "google.pk", true, true, false, -1, &kPinset_google }, + { "google.pl", true, true, false, -1, &kPinset_google }, + { "google.pn", true, true, false, -1, &kPinset_google }, + { "google.ps", true, true, false, -1, &kPinset_google }, + { "google.pt", true, true, false, -1, &kPinset_google }, + { "google.ro", true, true, false, -1, &kPinset_google }, + { "google.rs", true, true, false, -1, &kPinset_google }, + { "google.ru", true, true, false, -1, &kPinset_google }, + { "google.rw", true, true, false, -1, &kPinset_google }, + { "google.sc", true, true, false, -1, &kPinset_google }, + { "google.se", true, true, false, -1, &kPinset_google }, + { "google.sh", true, true, false, -1, &kPinset_google }, + { "google.si", true, true, false, -1, &kPinset_google }, + { "google.sk", true, true, false, -1, &kPinset_google }, + { "google.sm", true, true, false, -1, &kPinset_google }, + { "google.sn", true, true, false, -1, &kPinset_google }, + { "google.so", true, true, false, -1, &kPinset_google }, + { "google.st", true, true, false, -1, &kPinset_google }, + { "google.td", true, true, false, -1, &kPinset_google }, + { "google.tg", true, true, false, -1, &kPinset_google }, + { "google.tk", true, true, false, -1, &kPinset_google }, + { "google.tl", true, true, false, -1, &kPinset_google }, + { "google.tm", true, true, false, -1, &kPinset_google }, + { "google.tn", true, true, false, -1, &kPinset_google }, + { "google.to", true, true, false, -1, &kPinset_google }, + { "google.tp", true, true, false, -1, &kPinset_google }, + { "google.tt", true, true, false, -1, &kPinset_google }, + { "google.us", true, true, false, -1, &kPinset_google }, + { "google.uz", true, true, false, -1, &kPinset_google }, + { "google.vg", true, true, false, -1, &kPinset_google }, + { "google.vu", true, true, false, -1, &kPinset_google }, + { "google.ws", true, true, false, -1, &kPinset_google }, + { "googleadservices.com", true, true, false, -1, &kPinset_google }, + { "googleapis.com", true, true, false, -1, &kPinset_google }, + { "googlecode.com", true, true, false, -1, &kPinset_google }, + { "googlecommerce.com", true, true, false, -1, &kPinset_google }, + { "googlegroups.com", true, true, false, -1, &kPinset_google }, + { "googlemail.com", false, true, false, -1, &kPinset_google }, + { "googleplex.com", true, true, false, -1, &kPinset_google }, + { "googlesyndication.com", true, true, false, -1, &kPinset_google }, + { "googletagmanager.com", true, true, false, -1, &kPinset_google }, + { "googletagservices.com", true, true, false, -1, &kPinset_google }, + { "googleusercontent.com", true, true, false, -1, &kPinset_google }, + { "goto.google.com", true, true, false, -1, &kPinset_google }, + { "groups.google.com", true, true, false, -1, &kPinset_google }, + { "gstatic.com", true, true, false, -1, &kPinset_google }, + { "history.google.com", true, true, false, -1, &kPinset_google }, + { "hostedtalkgadget.google.com", true, true, false, -1, &kPinset_google }, + { "include-subdomains.pinning.example.com", true, false, false, -1, &kPinset_mozilla_test }, + { "liberty.lavabit.com", true, true, false, -1, &kPinset_lavabit }, + { "mail.google.com", true, true, false, -1, &kPinset_google }, + { "market.android.com", true, true, false, -1, &kPinset_google }, + { "media.mozilla.com", true, true, true, -1, &kPinset_mozilla }, + { "mobile.twitter.com", true, true, false, -1, &kPinset_twitterCom }, + { "oauth.twitter.com", true, true, false, -1, &kPinset_twitterCom }, + { "pinningtest.appspot.com", true, true, false, -1, &kPinset_test }, + { "platform.twitter.com", true, true, false, -1, &kPinset_twitterCDN }, + { "play.google.com", false, true, false, -1, &kPinset_google }, + { "plus.google.com", true, true, false, -1, &kPinset_google }, + { "plus.sandbox.google.com", true, true, false, -1, &kPinset_google }, + { "profiles.google.com", true, true, false, -1, &kPinset_google }, + { "script.google.com", true, true, false, -1, &kPinset_google }, + { "security.google.com", true, true, false, -1, &kPinset_google }, + { "sites.google.com", true, true, false, -1, &kPinset_google }, + { "spreadsheets.google.com", true, true, false, -1, &kPinset_google }, + { "ssl.google-analytics.com", true, true, false, -1, &kPinset_google }, + { "talk.google.com", true, true, false, -1, &kPinset_google }, + { "talkgadget.google.com", true, true, false, -1, &kPinset_google }, + { "test-mode.pinning.example.com", true, true, false, -1, &kPinset_mozilla_test }, + { "tor2web.org", true, true, false, -1, &kPinset_tor2web }, + { "torproject.org", false, true, false, -1, &kPinset_tor }, + { "translate.googleapis.com", true, true, false, -1, &kPinset_google }, + { "twimg.com", true, true, false, -1, &kPinset_twitterCDN }, + { "twitter.com", false, true, false, -1, &kPinset_twitterCom }, + { "urchin.com", true, true, false, -1, &kPinset_google }, + { "wallet.google.com", true, true, false, -1, &kPinset_google }, + { "www.gmail.com", false, true, false, -1, &kPinset_google }, + { "www.googlemail.com", false, true, false, -1, &kPinset_google }, + { "www.torproject.org", true, true, false, -1, &kPinset_tor }, + { "www.twitter.com", true, true, false, -1, &kPinset_twitterCom }, + { "youtu.be", true, true, false, -1, &kPinset_google }, + { "youtube.com", true, true, false, -1, &kPinset_google }, + { "ytimg.com", true, true, false, -1, &kPinset_google }, }; -static const int kPublicKeyPinningPreloadListLength = 306; +static const int kPublicKeyPinningPreloadListLength = 307; -static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1410996539113000); +static const int32_t kUnknownId = -1; + +static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1411084309384000); diff --git a/security/manager/ssl/tests/unit/test_pinning.js b/security/manager/ssl/tests/unit/test_pinning.js index 0f1b95dd49c..3a4aa029b86 100644 --- a/security/manager/ssl/tests/unit/test_pinning.js +++ b/security/manager/ssl/tests/unit/test_pinning.js @@ -100,7 +100,7 @@ function check_pinning_telemetry() { // Because all of our test domains are pinned to user-specified trust // anchors, effectively only strict mode gets evaluated do_check_eq(prod_histogram.counts[0], 1); // Failure count - do_check_eq(prod_histogram.counts[1], 3); // Success count + do_check_eq(prod_histogram.counts[1], 2); // Success count do_check_eq(test_histogram.counts[0], 1); // Failure count do_check_eq(test_histogram.counts[1], 0); // Success count @@ -113,6 +113,10 @@ function check_pinning_telemetry() { do_check_eq(moz_test_histogram.counts[0], 0); // Failure count do_check_eq(moz_test_histogram.counts[1], 0); // Success count + let per_host_histogram = + service.getHistogramById("CERT_PINNING_MOZ_RESULTS_BY_HOST").snapshot(); + do_check_eq(per_host_histogram.counts[0], 0); // Failure count + do_check_eq(per_host_histogram.counts[1], 1); // Success count run_next_test(); } diff --git a/security/manager/tools/PreloadedHPKPins.json b/security/manager/tools/PreloadedHPKPins.json index 737bc75704a..d3690bc1ba8 100644 --- a/security/manager/tools/PreloadedHPKPins.json +++ b/security/manager/tools/PreloadedHPKPins.json @@ -67,10 +67,14 @@ ], "entries": [ + // Only domains that are operationally crucial to Firefox can have per-host + // telemetry reporting (the "id") field { "name": "addons.mozilla.org", "include_subdomains": true, - "pins": "mozilla", "test_mode": true }, + "pins": "mozilla", "test_mode": true, "id": 1 }, { "name": "addons.mozilla.net", "include_subdomains": true, - "pins": "mozilla", "test_mode": true }, + "pins": "mozilla", "test_mode": true, "id": 2 }, + { "name": "aus4.mozilla.org", "include_subdomains": true, + "pins": "mozilla", "test_mode": true, "id": 3 }, { "name": "cdn.mozilla.net", "include_subdomains": true, "pins": "mozilla", "test_mode": true}, { "name": "cdn.mozilla.org", "include_subdomains": true, @@ -80,9 +84,10 @@ { "name": "include-subdomains.pinning.example.com", "include_subdomains": true, "pins": "mozilla_test", "test_mode": false }, + // Example domain to collect per-host stats for telemetry tests. { "name": "exclude-subdomains.pinning.example.com", "include_subdomains": false, "pins": "mozilla_test", - "test_mode": false }, + "test_mode": false, "id": 0 }, { "name": "test-mode.pinning.example.com", "include_subdomains": true, "pins": "mozilla_test", "test_mode": true } ] diff --git a/security/manager/tools/genHPKPStaticPins.js b/security/manager/tools/genHPKPStaticPins.js index c7c7e45ac8c..47eb2256032 100644 --- a/security/manager/tools/genHPKPStaticPins.js +++ b/security/manager/tools/genHPKPStaticPins.js @@ -55,6 +55,7 @@ const DOMAINHEADER = "/* Domainlist */\n" + " const bool mIncludeSubdomains;\n" + " const bool mTestMode;\n" + " const bool mIsMoz;\n" + + " const int32_t mId;\n" + " const StaticPinset *pinset;\n" + "};\n\n"; @@ -444,6 +445,14 @@ function writeEntry(entry) { } else { printVal += "false, "; } + if (entry.id >= 256) { + throw("Not enough buckets in histogram"); + } + if (entry.id >= 0) { + printVal += entry.id + ", "; + } else { + printVal += "-1, "; + } printVal += "&kPinset_" + entry.pins; printVal += " },\n"; writeString(printVal); @@ -464,6 +473,7 @@ function writeDomainList(chromeImportedEntries) { writeString("\nstatic const int kPublicKeyPinningPreloadListLength = " + count + ";\n"); + writeString("\nstatic const int32_t kUnknownId = -1;\n"); } function writeFile(certNameToSKD, certSKDToName, diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index c6efa667116..428672bfd42 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -5941,21 +5941,33 @@ "CERT_PINNING_RESULTS": { "expires_in_version": "never", "kind": "boolean", - "description": "Certificate pinning evalutation results (0 = failure, 1 = success)" + "description": "Certificate pinning results (0 = failure, 1 = success)" }, "CERT_PINNING_TEST_RESULTS": { "expires_in_version": "never", "kind": "boolean", - "description": "Certificate pinning evalutation results (0 = failure, 1 = success)" + "description": "Certificate pinning test results (0 = failure, 1 = success)" }, "CERT_PINNING_MOZ_RESULTS": { "expires_in_version": "never", "kind": "boolean", - "description": "Certificate pinning evalutation results (0 = failure, 1 = success)" + "description": "Certificate pinning results for Mozilla sites (0 = failure, 1 = success)" }, "CERT_PINNING_MOZ_TEST_RESULTS": { "expires_in_version": "never", "kind": "boolean", - "description": "Certificate pinning evalutation results (0 = failure, 1 = success)" + "description": "Certificate pinning test results for Mozilla sites (0 = failure, 1 = success)" + }, + "CERT_PINNING_MOZ_RESULTS_BY_HOST": { + "expires_in_version": "never", + "kind": "enumerated", + "n_values": 512, + "description": "Certificate pinning results by host for Mozilla operational sites" + }, + "CERT_PINNING_MOZ_TEST_RESULTS_BY_HOST": { + "expires_in_version": "never", + "kind": "enumerated", + "n_values": 512, + "description": "Certificate pinning test results by host for Mozilla operational sites" } } From 50fa2456f19272e312ddc813c026b13d63d7b61e Mon Sep 17 00:00:00 2001 From: Girish Sharma Date: Thu, 15 May 2014 23:52:45 +0530 Subject: [PATCH 19/70] Bug 1004487 - Add Cu.isModuleLoaded method to know if a resource has been loaded via Cu.import, r=bholley --- js/xpconnect/idl/xpcIJSModuleLoader.idl | 14 +++++++- js/xpconnect/idl/xpccomponents.idl | 14 +++++++- js/xpconnect/loader/mozJSComponentLoader.cpp | 21 ++++++++++++ js/xpconnect/src/XPCComponents.cpp | 12 +++++++ .../tests/unit/test_isModuleLoaded.js | 33 +++++++++++++++++++ js/xpconnect/tests/unit/xpcshell.ini | 1 + 6 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 js/xpconnect/tests/unit/test_isModuleLoaded.js diff --git a/js/xpconnect/idl/xpcIJSModuleLoader.idl b/js/xpconnect/idl/xpcIJSModuleLoader.idl index 761f2809b44..5f66b105a18 100644 --- a/js/xpconnect/idl/xpcIJSModuleLoader.idl +++ b/js/xpconnect/idl/xpcIJSModuleLoader.idl @@ -15,7 +15,7 @@ class nsAXPCNativeCallContext; [ptr] native JSObjectPtr(JSObject); -[scriptable, uuid(243d1a31-db9f-47a1-9922-55a1ad5515fb)] +[scriptable, uuid(4f94b21f-2920-4bd9-8251-5fb60fb054b2)] interface xpcIJSModuleLoader : nsISupports { /** @@ -67,4 +67,16 @@ interface xpcIJSModuleLoader : nsISupports * then this method will do nothing. */ void unload(in AUTF8String aResourceURI); + + /** + * Returns true if the js file located at 'registryLocation' location has + * been loaded previously via the import method above. Returns false + * otherwise. + * + * @param resourceURI A resource:// URI string representing the location of + * the js file to be checked if it is already loaded or not. + * @returns boolean, true if the js file has been loaded via import. false + * otherwise + */ + boolean isModuleLoaded(in AUTF8String aResourceURI); }; diff --git a/js/xpconnect/idl/xpccomponents.idl b/js/xpconnect/idl/xpccomponents.idl index 2ccf9a982d0..2d822159861 100644 --- a/js/xpconnect/idl/xpccomponents.idl +++ b/js/xpconnect/idl/xpccomponents.idl @@ -21,7 +21,7 @@ interface nsIStackFrame; * interface of Components.interfacesByID * (interesting stuff only reflected into JavaScript) */ -[scriptable, uuid(c99cffac-5aed-4267-ad2f-f4a4c9d4a081)] +[scriptable, uuid(f235ef76-9919-478b-aa0f-282d994ddf76)] interface nsIXPCComponents_InterfacesByID : nsISupports { }; @@ -217,6 +217,18 @@ interface nsIXPCComponents_Utils : nsISupports [implicit_jscontext,optional_argc] jsval import(in AUTF8String aResourceURI, [optional] in jsval targetObj); + /** + * Returns true if the js file located at 'registryLocation' location has + * been loaded previously via the import method above. Returns false + * otherwise. + * + * @param resourceURI A resource:// URI string representing the location of + * the js file to be checked if it is already loaded or not. + * @returns boolean, true if the js file has been loaded via import. false + * otherwise + */ + boolean isModuleLoaded(in AUTF8String aResourceURI); + /* * Unloads the JS module at 'registryLocation'. Existing references to the * module will continue to work but any subsequent import of the module will diff --git a/js/xpconnect/loader/mozJSComponentLoader.cpp b/js/xpconnect/loader/mozJSComponentLoader.cpp index 183ee9a9e30..9bfe1eab517 100644 --- a/js/xpconnect/loader/mozJSComponentLoader.cpp +++ b/js/xpconnect/loader/mozJSComponentLoader.cpp @@ -1210,6 +1210,27 @@ mozJSComponentLoader::ImportInto(const nsACString &aLocation, return NS_OK; } +/* boolean isModuleLoaded (in AUTF8String registryLocation); */ +NS_IMETHODIMP +mozJSComponentLoader::IsModuleLoaded(const nsACString& aLocation, + bool *retval) +{ + MOZ_ASSERT(nsContentUtils::IsCallerChrome()); + + nsresult rv; + if (!mInitialized) { + rv = ReallyInit(); + NS_ENSURE_SUCCESS(rv, rv); + } + + ComponentLoaderInfo info(aLocation); + rv = info.EnsureKey(); + NS_ENSURE_SUCCESS(rv, rv); + + *retval = !!mImports.Get(info.Key()); + return NS_OK; +} + nsresult mozJSComponentLoader::ImportInto(const nsACString &aLocation, HandleObject targetObj, diff --git a/js/xpconnect/src/XPCComponents.cpp b/js/xpconnect/src/XPCComponents.cpp index 525aa73a7f9..9da28aaae75 100644 --- a/js/xpconnect/src/XPCComponents.cpp +++ b/js/xpconnect/src/XPCComponents.cpp @@ -2724,6 +2724,18 @@ nsXPCComponents_Utils::Import(const nsACString& registryLocation, return moduleloader->Import(registryLocation, targetObj, cx, optionalArgc, retval); } +/* boolean isModuleLoaded (in AUTF8String registryLocation); + */ +NS_IMETHODIMP +nsXPCComponents_Utils::IsModuleLoaded(const nsACString& registryLocation, bool *retval) +{ + nsCOMPtr moduleloader = + do_GetService(MOZJSCOMPONENTLOADER_CONTRACTID); + if (!moduleloader) + return NS_ERROR_FAILURE; + return moduleloader->IsModuleLoaded(registryLocation, retval); +} + /* unload (in AUTF8String registryLocation); */ NS_IMETHODIMP diff --git a/js/xpconnect/tests/unit/test_isModuleLoaded.js b/js/xpconnect/tests/unit/test_isModuleLoaded.js new file mode 100644 index 00000000000..f08bf2def52 --- /dev/null +++ b/js/xpconnect/tests/unit/test_isModuleLoaded.js @@ -0,0 +1,33 @@ +const Cu = Components.utils; + +function run_test() { + // Existing module. + do_check_true(!Cu.isModuleLoaded("resource://gre/modules/devtools/LayoutHelpers.jsm"), + "isModuleLoaded returned correct value for non-loaded module"); + Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm"); + do_check_true(Cu.isModuleLoaded("resource://gre/modules/devtools/LayoutHelpers.jsm"), + "isModuleLoaded returned true after loading that module"); + Cu.unload("resource://gre/modules/devtools/LayoutHelpers.jsm"); + do_check_true(!Cu.isModuleLoaded("resource://gre/modules/devtools/LayoutHelpers.jsm"), + "isModuleLoaded returned false after unloading that module"); + + // Non-existing module + do_check_true(!Cu.isModuleLoaded("resource://gre/modules/devtools/LayoutHelpers1.jsm"), + "isModuleLoaded returned correct value for non-loaded module"); + try { + Cu.import("resource://gre/modules/devtools/LayoutHelpers1.jsm"); + do_check_true(false, + "Should have thrown while trying to load a non existing file"); + } catch (ex) {} + do_check_true(!Cu.isModuleLoaded("resource://gre/modules/devtools/LayoutHelpers1.jsm"), + "isModuleLoaded returned correct value for non-loaded module"); + + // incorrect url + try { + Cu.isModuleLoaded("resource://modules/devtools/LayoutHelpers1.jsm"); + do_check_true(false, + "Should have thrown while trying to load a non existing file"); + } catch (ex) { + do_check_true(true, "isModuleLoaded threw an exception while loading incorrect uri"); + } +} diff --git a/js/xpconnect/tests/unit/xpcshell.ini b/js/xpconnect/tests/unit/xpcshell.ini index f4b37d48a2a..bfea4b1d0c5 100644 --- a/js/xpconnect/tests/unit/xpcshell.ini +++ b/js/xpconnect/tests/unit/xpcshell.ini @@ -49,6 +49,7 @@ support-files = [test_file2.js] [test_import.js] [test_import_fail.js] +[test_isModuleLoaded.js] [test_js_weak_references.js] [test_reflect_parse.js] [test_localeCompare.js] From a5e699f7e22675d5c6d983582938dbee7007e2e0 Mon Sep 17 00:00:00 2001 From: Simon Wilper Date: Wed, 14 May 2014 05:38:00 +1200 Subject: [PATCH 20/70] Bug 999496 - Move AudioData::SizeOfIncludingThis to MediaData.cpp. r=kinetik --- content/media/MediaData.cpp | 10 ++++++++++ content/media/MediaData.h | 8 +------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/content/media/MediaData.cpp b/content/media/MediaData.cpp index cf9dbca4696..7b29ef25a5d 100644 --- a/content/media/MediaData.cpp +++ b/content/media/MediaData.cpp @@ -38,6 +38,16 @@ AudioData::EnsureAudioBuffer() } } +size_t +AudioData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const +{ + size_t size = aMallocSizeOf(this) + aMallocSizeOf(mAudioData); + if (mAudioBuffer) { + size += mAudioBuffer->SizeOfIncludingThis(aMallocSizeOf); + } + return size; +} + static bool ValidatePlane(const VideoData::YCbCrBuffer::Plane& aPlane) { diff --git a/content/media/MediaData.h b/content/media/MediaData.h index 146663baf2d..65db1f9bd5e 100644 --- a/content/media/MediaData.h +++ b/content/media/MediaData.h @@ -80,13 +80,7 @@ public: MOZ_COUNT_DTOR(AudioData); } - size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { - size_t size = aMallocSizeOf(this) + aMallocSizeOf(mAudioData); - if (mAudioBuffer) { - size += mAudioBuffer->SizeOfIncludingThis(aMallocSizeOf); - } - return size; - } + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const; // If mAudioBuffer is null, creates it from mAudioData. void EnsureAudioBuffer(); From 80f6fe59147d044c58b183fe6fb1b1fd9d6b5b84 Mon Sep 17 00:00:00 2001 From: Matthew Gregan Date: Fri, 16 May 2014 13:46:39 +1200 Subject: [PATCH 21/70] Bug 1010163 - Fix. r=cajbir --- content/media/wave/WaveReader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/media/wave/WaveReader.cpp b/content/media/wave/WaveReader.cpp index 3badd35d448..d1c45d809dd 100644 --- a/content/media/wave/WaveReader.cpp +++ b/content/media/wave/WaveReader.cpp @@ -577,7 +577,7 @@ WaveReader::LoadListChunk(uint32_t aChunkSize, uint32_t length = ReadUint32LE(&p); // Subchunk shall not exceed parent chunk. - if (p + length > end) { + if (end - p < length) { break; } From 54b3c2493eb2165d215a4b606a12042219a6636f Mon Sep 17 00:00:00 2001 From: Benoit Girard Date: Thu, 15 May 2014 16:52:30 -0400 Subject: [PATCH 22/70] Bug 1011225 - [e10s] Name mac content process. r=mstange --HG-- extra : rebase_source : 0b1e31e9fed6ec6227c101c3ccd6b5401822778c --- dom/ipc/ContentChild.cpp | 4 ++++ ipc/glue/ProcessUtils_mac.mm | 20 ++++++++++++++++++++ ipc/glue/moz.build | 4 ++++ 3 files changed, 28 insertions(+) create mode 100644 ipc/glue/ProcessUtils_mac.mm diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index 65b01924891..36051eba1f8 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -556,6 +556,7 @@ ContentChild::InitProcessAttributes() { SendGetProcessAttributes(&mID, &mIsForApp, &mIsForBrowser); +#ifdef MOZ_WIDGET_GONK #ifdef MOZ_NUWA_PROCESS if (IsNuwaProcess()) { SetProcessName(NS_LITERAL_STRING("(Nuwa)"), false); @@ -567,6 +568,9 @@ ContentChild::InitProcessAttributes() } else { SetProcessName(NS_LITERAL_STRING("Browser"), false); } +#else + SetProcessName(NS_LITERAL_STRING("Content Process"), true); +#endif } diff --git a/ipc/glue/ProcessUtils_mac.mm b/ipc/glue/ProcessUtils_mac.mm new file mode 100644 index 00000000000..6c5738871e5 --- /dev/null +++ b/ipc/glue/ProcessUtils_mac.mm @@ -0,0 +1,20 @@ +/* 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 "ProcessUtils.h" + +#include "nsString.h" + +#include "mozilla/plugins/PluginUtilsOSX.h" + +namespace mozilla { +namespace ipc { + +void SetThisProcessName(const char *aName) +{ + mozilla::plugins::PluginUtilsOSX::SetProcessName(aName); +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/moz.build b/ipc/glue/moz.build index 4da4edfb687..d163e5a7dea 100644 --- a/ipc/glue/moz.build +++ b/ipc/glue/moz.build @@ -83,6 +83,10 @@ elif CONFIG['OS_ARCH'] in ('DragonFly', 'FreeBSD', 'NetBSD', 'OpenBSD'): UNIFIED_SOURCES += [ 'ProcessUtils_bsd.cpp' ] +elif CONFIG['OS_ARCH'] in ('Darwin'): + UNIFIED_SOURCES += [ + 'ProcessUtils_mac.mm' + ] else: UNIFIED_SOURCES += [ 'ProcessUtils_none.cpp', From 2913a7cf5a2921f2121badc8e87a27e97d028872 Mon Sep 17 00:00:00 2001 From: Jeff Muizelaar Date: Thu, 15 May 2014 15:47:50 -0400 Subject: [PATCH 23/70] Bug 1011142 - fuzz Skia diagonal linear gradient tests. r=gw280 --HG-- rename : toolkit/devtools/transport/tests/moz.build => toolkit/devtools/moz.build extra : rebase_source : cbd306c5d6cd483dad1af9c97d4281b2d72bad59 --- layout/reftests/css-gradients/reftest.list | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/layout/reftests/css-gradients/reftest.list b/layout/reftests/css-gradients/reftest.list index 3618ec7e5a8..b0a0b809c78 100644 --- a/layout/reftests/css-gradients/reftest.list +++ b/layout/reftests/css-gradients/reftest.list @@ -16,10 +16,16 @@ fuzzy-if(!contentSameGfxBackendAsCanvas,4,92400) fuzzy-if(azureSkiaGL,2,143400) == linear-diagonal-4a.html linear-diagonal-4-ref.html == linear-diagonal-4b.html linear-diagonal-4-ref.html == linear-diagonal-4c.html linear-diagonal-4-ref.html -fuzzy(2,11410) == linear-diagonal-5a.html linear-diagonal-5-ref.html -fuzzy(2,11568) fuzzy-if(azureQuartz&&OSX==10.6,2,11657) == linear-diagonal-6a.html linear-diagonal-6-ref.html -fuzzy(2,11605) == linear-diagonal-7a.html linear-diagonal-7-ref.html -fuzzy(2,11407) fuzzy-if(azureQuartz&&OSX==10.6,2,11443) == linear-diagonal-8a.html linear-diagonal-8-ref.html + +# these tests uses a similar gradient over different bounds. It's perfectly +# reasonable to expect implementations to give slightly different results +# if the gradients are not being normalized. Skia uses a lookup table for +# gradients so it will have less precision when drawing a larger gradient +fuzzy(2,11410) fuzzy-if(skiaContent,4,15271) == linear-diagonal-5a.html linear-diagonal-5-ref.html +fuzzy(2,11568) fuzzy-if(azureQuartz&&OSX==10.6,2,11657) fuzzy-if(skiaContent,8,19701) == linear-diagonal-6a.html linear-diagonal-6-ref.html +fuzzy(2,11605) fuzzy-if(skiaContent,8,19701) == linear-diagonal-7a.html linear-diagonal-7-ref.html +fuzzy(2,11407) fuzzy-if(azureQuartz&&OSX==10.6,2,11443) fuzzy-if(skiaContent,4,15085) == linear-diagonal-8a.html linear-diagonal-8-ref.html + fuzzy-if(azureQuartz,4,29373) == linear-diagonal-9a.html linear-diagonal-9-ref.html fuzzy(1,800000) == linear-flipped-1.html linear-flipped-1-ref.html == linear-position-1a.html linear-position-1-ref.html From 12ed39204288f7608430211e0af9efda2033123c Mon Sep 17 00:00:00 2001 From: Benoit Girard Date: Fri, 16 May 2014 00:52:55 -0400 Subject: [PATCH 24/70] backout 9ebe70d8a9b2 (Bug 1011225) for mochitest failures on a CLOSED TREE --- dom/ipc/ContentChild.cpp | 4 ---- ipc/glue/ProcessUtils_mac.mm | 20 -------------------- ipc/glue/moz.build | 4 ---- 3 files changed, 28 deletions(-) delete mode 100644 ipc/glue/ProcessUtils_mac.mm diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index 36051eba1f8..65b01924891 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -556,7 +556,6 @@ ContentChild::InitProcessAttributes() { SendGetProcessAttributes(&mID, &mIsForApp, &mIsForBrowser); -#ifdef MOZ_WIDGET_GONK #ifdef MOZ_NUWA_PROCESS if (IsNuwaProcess()) { SetProcessName(NS_LITERAL_STRING("(Nuwa)"), false); @@ -568,9 +567,6 @@ ContentChild::InitProcessAttributes() } else { SetProcessName(NS_LITERAL_STRING("Browser"), false); } -#else - SetProcessName(NS_LITERAL_STRING("Content Process"), true); -#endif } diff --git a/ipc/glue/ProcessUtils_mac.mm b/ipc/glue/ProcessUtils_mac.mm deleted file mode 100644 index 6c5738871e5..00000000000 --- a/ipc/glue/ProcessUtils_mac.mm +++ /dev/null @@ -1,20 +0,0 @@ -/* 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 "ProcessUtils.h" - -#include "nsString.h" - -#include "mozilla/plugins/PluginUtilsOSX.h" - -namespace mozilla { -namespace ipc { - -void SetThisProcessName(const char *aName) -{ - mozilla::plugins::PluginUtilsOSX::SetProcessName(aName); -} - -} // namespace ipc -} // namespace mozilla diff --git a/ipc/glue/moz.build b/ipc/glue/moz.build index d163e5a7dea..4da4edfb687 100644 --- a/ipc/glue/moz.build +++ b/ipc/glue/moz.build @@ -83,10 +83,6 @@ elif CONFIG['OS_ARCH'] in ('DragonFly', 'FreeBSD', 'NetBSD', 'OpenBSD'): UNIFIED_SOURCES += [ 'ProcessUtils_bsd.cpp' ] -elif CONFIG['OS_ARCH'] in ('Darwin'): - UNIFIED_SOURCES += [ - 'ProcessUtils_mac.mm' - ] else: UNIFIED_SOURCES += [ 'ProcessUtils_none.cpp', From f2fda58f3bfc37920d40bbe401e095ab73c4e291 Mon Sep 17 00:00:00 2001 From: Matthew Gregan Date: Fri, 16 May 2014 17:09:28 +1200 Subject: [PATCH 25/70] Bug 1010163 - Cast out warning demons on a CLOSED TREE. --- content/media/wave/WaveReader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/media/wave/WaveReader.cpp b/content/media/wave/WaveReader.cpp index d1c45d809dd..b7aa0459d09 100644 --- a/content/media/wave/WaveReader.cpp +++ b/content/media/wave/WaveReader.cpp @@ -577,7 +577,7 @@ WaveReader::LoadListChunk(uint32_t aChunkSize, uint32_t length = ReadUint32LE(&p); // Subchunk shall not exceed parent chunk. - if (end - p < length) { + if (uint32_t(end - p) < length) { break; } From 27facf20280ccb9e5373500f39a0c5244a560b5b Mon Sep 17 00:00:00 2001 From: Daniel Holbert Date: Thu, 15 May 2014 22:13:07 -0700 Subject: [PATCH 26/70] backout 179cea3a84da (bug 1010470) due to reftest failures, on a CLOSED TREE --HG-- rename : layout/reftests/forms/button/disabled-2b.html => layout/reftests/forms/button/disabled-2.html --- b2g/chrome/content/content.css | 50 ++++++------- .../{disabled-2a.html => disabled-2.html} | 0 layout/reftests/forms/button/disabled-2b.html | 18 ----- layout/reftests/forms/button/reftest.list | 3 +- mobile/android/themes/core/content.css | 70 +++++++++---------- 5 files changed, 61 insertions(+), 80 deletions(-) rename layout/reftests/forms/button/{disabled-2a.html => disabled-2.html} (100%) delete mode 100644 layout/reftests/forms/button/disabled-2b.html diff --git a/b2g/chrome/content/content.css b/b2g/chrome/content/content.css index c038779b34f..bd8f54a349f 100644 --- a/b2g/chrome/content/content.css +++ b/b2g/chrome/content/content.css @@ -234,15 +234,15 @@ input[type="radio"]:focus { } /* we need to be specific for selects because the above rules are specific too */ -textarea:disabled, -select[size]:disabled, -select[multiple]:disabled, -select[size][multiple]:disabled, -select:not([size]):not([multiple]):disabled, -select[size="0"]:disabled, -select[size="1"]:disabled, -button:disabled, -* > input:not([type="image"]):disabled { +textarea[disabled], +select[size][disabled], +select[multiple][disabled], +select[size][multiple][disabled], +select:not([size]):not([multiple])[disabled], +select[size="0"][disabled], +select[size="1"][disabled], +button[disabled], +* > input:not([type="image"])[disabled] { color: rgba(0,0,0,0.3); border-color: rgba(125,125,125,0.4); border-style: solid; @@ -250,32 +250,32 @@ button:disabled, background: transparent linear-gradient(rgba(185,185,185,0.4) 0, rgba(235,235,235,0.4) 3px, rgba(255,255,255,0.4) 100%); } -select:not([size]):not([multiple]):disabled, -select[size="0"]:disabled, -select[size="1"]:disabled { +select:not([size]):not([multiple])[disabled], +select[size="0"][disabled], +select[size="1"][disabled] { background: transparent linear-gradient(rgba(255,255,255,0.4) 0, rgba(235,235,235,0.4) 3px, rgba(185,185,185,0.4) 100%); } -input[type="button"]:disabled, -input[type="submit"]:disabled, -input[type="reset"]:disabled, -button:disabled { +input[type="button"][disabled], +input[type="submit"][disabled], +input[type="reset"][disabled], +button[disabled] { padding: 0 7px 0 7px; background: transparent linear-gradient(rgba(255,255,255,0.4) 0, rgba(235,235,235,0.4) 3px, rgba(185,185,185,0.4) 100%); } -input[type="radio"]:disabled, -input[type="radio"]:disabled:active, -input[type="radio"]:disabled:hover, -input[type="radio"]:disabled:hover:active, -input[type="checkbox"]:disabled, -input[type="checkbox"]:disabled:active, -input[type="checkbox"]:disabled:hover, -input[type="checkbox"]:disabled:hover:active { +input[type="radio"][disabled], +input[type="radio"][disabled]:active, +input[type="radio"][disabled]:hover, +input[type="radio"][disabled]:hover:active, +input[type="checkbox"][disabled], +input[type="checkbox"][disabled]:active, +input[type="checkbox"][disabled]:hover, +input[type="checkbox"][disabled]:hover:active { border:1px solid rgba(125,125,125,0.4) !important; } -select:disabled > button { +select[disabled] > button { opacity: 0.6; padding: 1px 7px 1px 7px; } diff --git a/layout/reftests/forms/button/disabled-2a.html b/layout/reftests/forms/button/disabled-2.html similarity index 100% rename from layout/reftests/forms/button/disabled-2a.html rename to layout/reftests/forms/button/disabled-2.html diff --git a/layout/reftests/forms/button/disabled-2b.html b/layout/reftests/forms/button/disabled-2b.html deleted file mode 100644 index 4dc97b0af12..00000000000 --- a/layout/reftests/forms/button/disabled-2b.html +++ /dev/null @@ -1,18 +0,0 @@ - - - -Bug 1010470: test that buttons in a disabled fieldset look the same as directly-disabled buttons - - - - - - -

- - - - -
- - diff --git a/layout/reftests/forms/button/reftest.list b/layout/reftests/forms/button/reftest.list index 67f22c47edf..82cb90c7135 100644 --- a/layout/reftests/forms/button/reftest.list +++ b/layout/reftests/forms/button/reftest.list @@ -21,8 +21,7 @@ fuzzy-if(B2G||Android,125,80) == percent-width-child-2.html percent-width-child # Looks like Android and B2G change the text color, but to something slightly # different from ColorGray fails-if(Android||B2G) == disabled-1.html disabled-1-ref.html -== disabled-2a.html disabled-2-ref.html -== disabled-2b.html disabled-2-ref.html +== disabled-2.html disabled-2-ref.html != disabled-3.html disabled-3-notref.html != disabled-4.html disabled-4-notref.html diff --git a/mobile/android/themes/core/content.css b/mobile/android/themes/core/content.css index 06f0fb59df0..3b5655cacdf 100644 --- a/mobile/android/themes/core/content.css +++ b/mobile/android/themes/core/content.css @@ -240,17 +240,17 @@ input[type="radio"]:focus { } /* we need to be specific for selects because the above rules are specific too */ -textarea:disabled, -select[size]:disabled, -select[multiple]:disabled, -select[size][multiple]:disabled, -select:not([size]):not([multiple]):disabled, -select[size="0"]:disabled, -select[size="1"]:disabled, -button:disabled, -button:disabled:active, -* > input:not([type="image"]):disabled, -* > input:not([type="image"]):disabled:active { +textarea[disabled], +select[size][disabled], +select[multiple][disabled], +select[size][multiple][disabled], +select:not([size]):not([multiple])[disabled], +select[size="0"][disabled], +select[size="1"][disabled], +button[disabled], +button[disabled]:active, +* > input:not([type="image"])[disabled], +* > input:not([type="image"])[disabled]:active { color: rgba(0,0,0,0.3); border-color: rgba(125,125,125,0.4); border-style: solid; @@ -258,36 +258,36 @@ button:disabled:active, background: transparent linear-gradient(rgba(185,185,185,0.4) 0, rgba(235,235,235,0.4) 3px, rgba(255,255,255,0.4) 100%); } -select:not([size]):not([multiple]):disabled, -select[size="0"]:disabled, -select[size="1"]:disabled { +select:not([size]):not([multiple])[disabled], +select[size="0"][disabled], +select[size="1"][disabled] { background: transparent linear-gradient(rgba(255,255,255,0.4) 0, rgba(235,235,235,0.4) 3px, rgba(185,185,185,0.4) 100%); } -input[type="button"]:disabled, -input[type="button"]:disabled:active, -input[type="submit"]:disabled, -input[type="submit"]:disabled:active, -input[type="reset"]:disabled, -input[type="reset"]:disabled:active, -button:disabled, -button:disabled:active { +input[type="button"][disabled], +input[type="button"][disabled]:active, +input[type="submit"][disabled], +input[type="submit"][disabled]:active, +input[type="reset"][disabled], +input[type="reset"][disabled]:active, +button[disabled], +button[disabled]:active { padding: 0 7px 0 7px; background: transparent linear-gradient(rgba(255,255,255,0.4) 0, rgba(235,235,235,0.4) 3px, rgba(185,185,185,0.4) 100%); } -input[type="radio"]:disabled, -input[type="radio"]:disabled:active, -input[type="radio"]:disabled:hover, -input[type="radio"]:disabled:hover:active, -input[type="checkbox"]:disabled, -input[type="checkbox"]:disabled:active, -input[type="checkbox"]:disabled:hover, -input[type="checkbox"]:disabled:hover:active { +input[type="radio"][disabled], +input[type="radio"][disabled]:active, +input[type="radio"][disabled]:hover, +input[type="radio"][disabled]:hover:active, +input[type="checkbox"][disabled], +input[type="checkbox"][disabled]:active, +input[type="checkbox"][disabled]:hover, +input[type="checkbox"][disabled]:hover:active { border:1px solid rgba(125,125,125,0.4) !important; } -select:disabled > button { +select[disabled] > button { opacity: 0.6; padding: 1px 7px 1px 7px; } @@ -299,10 +299,10 @@ select:disabled > button { *:-moz-any-link:active, *[role=button]:active, -button:enabled:active, -input:not(:focus):enabled:active, -select:enabled:active, -textarea:not(:focus):enabled:active, +button:not([disabled]):active, +input:not(:focus):not([disabled]):active, +select:not([disabled]):active, +textarea:not(:focus):not([disabled]):active, option:active, label:active, xul|menulist:active { From 19422a5329c036e67841764ef9245b061dd76fa3 Mon Sep 17 00:00:00 2001 From: Shian-Yow Wu Date: Fri, 16 May 2014 13:34:43 +0800 Subject: [PATCH 27/70] Bug 945152 - Part 3-1: New libjar APIs. r=aklotz --- modules/libjar/nsIJARChannel.idl | 11 +++++++-- modules/libjar/nsJARChannel.cpp | 7 ++++++ modules/libjar/nsZipArchive.cpp | 25 ++++++++++++++++----- modules/libjar/nsZipArchive.h | 7 ++++++ netwerk/protocol/app/AppProtocolHandler.cpp | 5 +++++ 5 files changed, 47 insertions(+), 8 deletions(-) diff --git a/modules/libjar/nsIJARChannel.idl b/modules/libjar/nsIJARChannel.idl index 6da0f0b3b0c..e0fcc833d3e 100644 --- a/modules/libjar/nsIJARChannel.idl +++ b/modules/libjar/nsIJARChannel.idl @@ -5,8 +5,10 @@ #include "nsIChannel.idl" -[scriptable, builtinclass, uuid(6e6cc56d-51eb-4299-a795-dcfd1229ab3d)] -interface nsIJARChannel : nsIChannel +interface nsIFile; + +[scriptable, builtinclass, uuid(063e9698-ec67-4fe2-aa19-d21505beaa61)] +interface nsIJARChannel : nsIChannel { /** * Returns TRUE if the JAR file is not safe (if the content type reported @@ -20,4 +22,9 @@ interface nsIJARChannel : nsIChannel * Forces the uri to be a app:// uri. */ void setAppURI(in nsIURI uri); + + /** + * Returns the JAR file. + */ + readonly attribute nsIFile jarFile; }; diff --git a/modules/libjar/nsJARChannel.cpp b/modules/libjar/nsJARChannel.cpp index d1909737816..4c38f3bc3e4 100644 --- a/modules/libjar/nsJARChannel.cpp +++ b/modules/libjar/nsJARChannel.cpp @@ -825,6 +825,13 @@ nsJARChannel::SetAppURI(nsIURI *aURI) { return NS_OK; } +NS_IMETHODIMP +nsJARChannel::GetJarFile(nsIFile **aFile) +{ + NS_IF_ADDREF(*aFile = mJarFile); + return NS_OK; +} + //----------------------------------------------------------------------------- // nsIDownloadObserver //----------------------------------------------------------------------------- diff --git a/modules/libjar/nsZipArchive.cpp b/modules/libjar/nsZipArchive.cpp index cb3e5d01530..28fcfb3cb2d 100644 --- a/modules/libjar/nsZipArchive.cpp +++ b/modules/libjar/nsZipArchive.cpp @@ -763,9 +763,9 @@ nsZipHandle* nsZipArchive::GetFD() } //--------------------------------------------- -// nsZipArchive::GetData +// nsZipArchive::GetDataOffset //--------------------------------------------- -const uint8_t* nsZipArchive::GetData(nsZipItem* aItem) +uint32_t nsZipArchive::GetDataOffset(nsZipItem* aItem) { PR_ASSERT (aItem); MOZ_WIN_MEM_TRY_BEGIN @@ -775,12 +775,12 @@ MOZ_WIN_MEM_TRY_BEGIN const uint8_t* data = mFd->mFileData; uint32_t offset = aItem->LocalOffset(); if (offset + ZIPLOCAL_SIZE > len) - return nullptr; + return 0; // -- check signature before using the structure, in case the zip file is corrupt ZipLocal* Local = (ZipLocal*)(data + offset); if ((xtolong(Local->signature) != LOCALSIG)) - return nullptr; + return 0; //-- NOTE: extralen is different in central header and local header //-- for archives created using the Unix "zip" utility. To set @@ -789,11 +789,24 @@ MOZ_WIN_MEM_TRY_BEGIN xtoint(Local->filename_len) + xtoint(Local->extrafield_len); + return offset; +MOZ_WIN_MEM_TRY_CATCH(return 0) +} + +//--------------------------------------------- +// nsZipArchive::GetData +//--------------------------------------------- +const uint8_t* nsZipArchive::GetData(nsZipItem* aItem) +{ + PR_ASSERT (aItem); +MOZ_WIN_MEM_TRY_BEGIN + uint32_t offset = GetDataOffset(aItem); + // -- check if there is enough source data in the file - if (offset + aItem->Size() > len) + if (!offset || offset + aItem->Size() > mFd->mLen) return nullptr; - return data + offset; + return mFd->mFileData + offset; MOZ_WIN_MEM_TRY_CATCH(return nullptr) } diff --git a/modules/libjar/nsZipArchive.h b/modules/libjar/nsZipArchive.h index bb2de4373fc..8e110169652 100644 --- a/modules/libjar/nsZipArchive.h +++ b/modules/libjar/nsZipArchive.h @@ -178,6 +178,13 @@ public: */ nsZipHandle* GetFD(); + /** + * Gets the data offset. + * @param aItem Pointer to nsZipItem + * returns 0 on failure. + */ + uint32_t GetDataOffset(nsZipItem* aItem); + /** * Get pointer to the data of the item. * @param aItem Pointer to nsZipItem diff --git a/netwerk/protocol/app/AppProtocolHandler.cpp b/netwerk/protocol/app/AppProtocolHandler.cpp index 2fe9344f013..c88e533ab30 100644 --- a/netwerk/protocol/app/AppProtocolHandler.cpp +++ b/netwerk/protocol/app/AppProtocolHandler.cpp @@ -118,6 +118,11 @@ NS_IMETHODIMP DummyChannel::SetAppURI(nsIURI *aURI) return NS_ERROR_NOT_IMPLEMENTED; } +NS_IMETHODIMP DummyChannel::GetJarFile(nsIFile* *aFile) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + NS_IMETHODIMP DummyChannel::Run() { nsresult rv = mListener->OnStartRequest(this, mListenerContext); From d5d5389b8fc704ffef3afbba2eb3678ca6363e6f Mon Sep 17 00:00:00 2001 From: Shian-Yow Wu Date: Fri, 16 May 2014 13:34:43 +0800 Subject: [PATCH 28/70] Bug 945152 - Part 3-2: XHR changes for mapped array buffer. r=smaug --- b2g/app/b2g.js | 3 + content/base/src/nsXMLHttpRequest.cpp | 145 ++++++++++++++++++++++++-- content/base/src/nsXMLHttpRequest.h | 10 ++ 3 files changed, 148 insertions(+), 10 deletions(-) diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js index 70161039c4f..dcf683048fd 100644 --- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -974,3 +974,6 @@ pref("dom.wakelock.enabled", true); pref("services.sync.fxaccounts.enabled", true); pref("identity.fxaccounts.enabled", true); #endif + +// Enable mapped array buffer +pref("dom.mapped_arraybuffer.enabled", true); diff --git a/content/base/src/nsXMLHttpRequest.cpp b/content/base/src/nsXMLHttpRequest.cpp index 781bae77819..30211f5f567 100644 --- a/content/base/src/nsXMLHttpRequest.cpp +++ b/content/base/src/nsXMLHttpRequest.cpp @@ -6,6 +6,9 @@ #include "nsXMLHttpRequest.h" +#ifndef XP_WIN +#include +#endif #include "mozilla/ArrayUtils.h" #include "mozilla/dom/XMLHttpRequestUploadBinding.h" #include "mozilla/EventDispatcher.h" @@ -15,6 +18,7 @@ #include "nsIDOMDocument.h" #include "nsIDOMProgressEvent.h" #include "nsIJARChannel.h" +#include "nsIJARURI.h" #include "nsLayoutCID.h" #include "nsReadableUtils.h" @@ -69,8 +73,10 @@ #include "nsStreamListenerWrapper.h" #include "xpcjsid.h" #include "nsITimedChannel.h" - #include "nsWrapperCacheInlines.h" +#include "nsZipArchive.h" +#include "mozilla/Preferences.h" +#include "private/pprio.h" using namespace mozilla; using namespace mozilla::dom; @@ -296,6 +302,7 @@ nsXMLHttpRequest::nsXMLHttpRequest() mInLoadProgressEvent(false), mResultJSON(JSVAL_VOID), mResultArrayBuffer(nullptr), + mIsMappedArrayBuffer(false), mXPCOMifier(nullptr) { SetIsDOMBinding(); @@ -1748,7 +1755,8 @@ nsXMLHttpRequest::StreamReaderFunc(nsIInputStream* in, if (xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_MOZ_BLOB) { xmlHttpRequest->mResponseBlob = nullptr; } - } else if (xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER || + } else if ((xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER && + !xmlHttpRequest->mIsMappedArrayBuffer) || xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER) { // get the initial capacity to something reasonable to avoid a bunch of reallocs right // at the start @@ -1955,12 +1963,46 @@ nsXMLHttpRequest::OnStartRequest(nsIRequest *request, nsISupports *ctxt) // Set up arraybuffer if (mResponseType == XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER && NS_SUCCEEDED(status)) { - int64_t contentLength; - rv = channel->GetContentLength(&contentLength); - if (NS_SUCCEEDED(rv) && - contentLength > 0 && - contentLength < XML_HTTP_REQUEST_MAX_CONTENT_LENGTH_PREALLOCATE) { - mArrayBufferBuilder.setCapacity(static_cast(contentLength)); + if (mIsMappedArrayBuffer) { + nsCOMPtr jarChannel = do_QueryInterface(channel); + if (jarChannel) { + nsCOMPtr uri; + rv = channel->GetURI(getter_AddRefs(uri)); + if (NS_SUCCEEDED(rv)) { + nsAutoCString file; + nsAutoCString scheme; + uri->GetScheme(scheme); + if (scheme.LowerCaseEqualsLiteral("app")) { + uri->GetPath(file); + // The actual file inside zip package has no leading slash. + file.Trim("/", true, false, false); + } else if (scheme.LowerCaseEqualsLiteral("jar")) { + nsCOMPtr jarURI = do_QueryInterface(uri); + if (jarURI) { + jarURI->GetJAREntry(file); + } + } + nsCOMPtr jarFile; + jarChannel->GetJarFile(getter_AddRefs(jarFile)); + rv = mArrayBufferBuilder.mapToFileInPackage(file, jarFile); + if (NS_WARN_IF(NS_FAILED(rv))) { + mIsMappedArrayBuffer = false; + } else { + channel->SetContentType(NS_LITERAL_CSTRING("application/mem-mapped")); + } + } + } + } + // If memory mapping failed, mIsMappedArrayBuffer would be set to false, + // and we want it fallback to the malloc way. + if (!mIsMappedArrayBuffer) { + int64_t contentLength; + rv = channel->GetContentLength(&contentLength); + if (NS_SUCCEEDED(rv) && + contentLength > 0 && + contentLength < XML_HTTP_REQUEST_MAX_CONTENT_LENGTH_PREALLOCATE) { + mArrayBufferBuilder.setCapacity(static_cast(contentLength)); + } } } @@ -2145,7 +2187,8 @@ nsXMLHttpRequest::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty"); NS_ASSERTION(mResponseText.IsEmpty(), "mResponseText should be empty"); } else if (NS_SUCCEEDED(status) && - (mResponseType == XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER || + ((mResponseType == XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER && + !mIsMappedArrayBuffer) || mResponseType == XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER)) { // set the capacity down to the actual length, to realloc back // down to the actual size @@ -2880,6 +2923,21 @@ nsXMLHttpRequest::Send(nsIVariant* aVariant, const Nullable& aBody) NS_ENSURE_SUCCESS(rv, rv); } else { + mIsMappedArrayBuffer = false; + if (mResponseType == XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER && + Preferences::GetBool("dom.mapped_arraybuffer.enabled", false)) { + nsCOMPtr uri; + nsAutoCString scheme; + + rv = mChannel->GetURI(getter_AddRefs(uri)); + if (NS_SUCCEEDED(rv)) { + uri->GetScheme(scheme); + if (scheme.LowerCaseEqualsLiteral("app") || + scheme.LowerCaseEqualsLiteral("jar")) { + mIsMappedArrayBuffer = true; + } + } + } // Start reading from the channel rv = mChannel->AsyncOpen(listener, nullptr); } @@ -3844,7 +3902,8 @@ namespace mozilla { ArrayBufferBuilder::ArrayBufferBuilder() : mDataPtr(nullptr), mCapacity(0), - mLength(0) + mLength(0), + mMapPtr(nullptr) { } @@ -3859,6 +3918,12 @@ ArrayBufferBuilder::reset() if (mDataPtr) { JS_free(nullptr, mDataPtr); } + + if (mMapPtr) { + JS_ReleaseMappedArrayBufferContents(mMapPtr, mLength); + mMapPtr = nullptr; + } + mDataPtr = nullptr; mCapacity = mLength = 0; } @@ -3866,6 +3931,8 @@ ArrayBufferBuilder::reset() bool ArrayBufferBuilder::setCapacity(uint32_t aNewCap) { + MOZ_ASSERT(!mMapPtr); + uint8_t *newdata = (uint8_t *) JS_ReallocateArrayBufferContents(nullptr, aNewCap, mDataPtr, mCapacity); if (!newdata) { return false; @@ -3884,6 +3951,8 @@ bool ArrayBufferBuilder::append(const uint8_t *aNewData, uint32_t aDataLen, uint32_t aMaxGrowth) { + MOZ_ASSERT(!mMapPtr); + if (mLength + aDataLen > mCapacity) { uint32_t newcap; // Double while under aMaxGrowth or if not specified. @@ -3921,6 +3990,18 @@ ArrayBufferBuilder::append(const uint8_t *aNewData, uint32_t aDataLen, JSObject* ArrayBufferBuilder::getArrayBuffer(JSContext* aCx) { + if (mMapPtr) { + JSObject* obj = JS_NewMappedArrayBufferWithContents(aCx, mLength, mMapPtr); + if (!obj) { + JS_ReleaseMappedArrayBufferContents(mMapPtr, mLength); + } + mMapPtr = nullptr; + + // The memory-mapped contents will be released when obj been finalized(GCed + // or neutered). + return obj; + } + // we need to check for mLength == 0, because nothing may have been // added if (mCapacity > mLength || mLength == 0) { @@ -3939,6 +4020,50 @@ ArrayBufferBuilder::getArrayBuffer(JSContext* aCx) return obj; } +nsresult +ArrayBufferBuilder::mapToFileInPackage(const nsCString& aFile, + nsIFile* aJarFile) +{ +#ifdef XP_WIN + // TODO: Bug 988813 - Support memory mapped array buffer for Windows platform. + MOZ_CRASH("Not implemented"); + return NS_ERROR_NOT_IMPLEMENTED; +#else + nsresult rv; + + // Open Jar file to get related attributes of target file. + nsRefPtr zip = new nsZipArchive(); + rv = zip->OpenArchive(aJarFile); + if (NS_FAILED(rv)) { + return rv; + } + nsZipItem* zipItem = zip->GetItem(aFile.get()); + if (NS_FAILED(rv)) { + return rv; + } + + // If file was added to the package as stored(uncompressed), map to the + // offset of file in zip package. + if (!zipItem->Compression()) { + uint32_t offset = zip->GetDataOffset(zipItem); + uint32_t size = zipItem->RealSize(); + mozilla::AutoFDClose pr_fd; + mozilla::ScopedClose fd; + rv = aJarFile->OpenNSPRFileDesc(PR_RDONLY, 0, &pr_fd.rwget()); + if (NS_FAILED(rv)) { + return rv; + } + fd.rwget() = PR_FileDesc2NativeHandle(pr_fd); + mMapPtr = JS_CreateMappedArrayBufferContents(fd, offset, size); + if (mMapPtr) { + mLength = size; + return NS_OK; + } + } + return NS_ERROR_FAILURE; +#endif +} + /* static */ bool ArrayBufferBuilder::areOverlappingRegions(const uint8_t* aStart1, uint32_t aLength1, diff --git a/content/base/src/nsXMLHttpRequest.h b/content/base/src/nsXMLHttpRequest.h index ae11524d1db..965c3791498 100644 --- a/content/base/src/nsXMLHttpRequest.h +++ b/content/base/src/nsXMLHttpRequest.h @@ -65,6 +65,7 @@ class ArrayBufferBuilder uint8_t* mDataPtr; uint32_t mCapacity; uint32_t mLength; + void* mMapPtr; public: ArrayBufferBuilder(); ~ArrayBufferBuilder(); @@ -89,6 +90,14 @@ public: JSObject* getArrayBuffer(JSContext* aCx); + // Memory mapping to starting position of file(aFile) in the zip + // package(aJarFile). + // + // The file in the zip package has to be uncompressed and the starting + // position of the file must be aligned according to array buffer settings + // in JS engine. + nsresult mapToFileInPackage(const nsCString& aFile, nsIFile* aJarFile); + protected: static bool areOverlappingRegions(const uint8_t* aStart1, uint32_t aLength1, const uint8_t* aStart2, uint32_t aLength2); @@ -727,6 +736,7 @@ protected: mozilla::ArrayBufferBuilder mArrayBufferBuilder; JS::Heap mResultArrayBuffer; + bool mIsMappedArrayBuffer; void ResetResponse(); From e0687a66c97553fcc468ce934008e6c9b896450b Mon Sep 17 00:00:00 2001 From: Shian-Yow Wu Date: Fri, 16 May 2014 13:34:44 +0800 Subject: [PATCH 29/70] Bug 945152 - Part 3-3: Test case to get array buffor by XHR with jar:// URL. r=smaug --- content/base/test/file_bug945152.jar | Bin 0 -> 14522 bytes content/base/test/file_bug945152_worker.js | 98 +++++++++++++++++++++ content/base/test/mochitest.ini | 4 + content/base/test/test_bug945152.html | 51 +++++++++++ 4 files changed, 153 insertions(+) create mode 100644 content/base/test/file_bug945152.jar create mode 100644 content/base/test/file_bug945152_worker.js create mode 100644 content/base/test/test_bug945152.html diff --git a/content/base/test/file_bug945152.jar b/content/base/test/file_bug945152.jar new file mode 100644 index 0000000000000000000000000000000000000000..23cc979b170d05cd1aee63c12cdd681c93e174bf GIT binary patch literal 14522 zcmeI(S5Q<}7zgk}3ri;ouIth}3#@bpq*tjTup+p!v_+6!!h#_*2@s_W(m^5!L!>Ho z6~=WJBFYdE1Vp3+P>LYkfVd)AP0-Q7w>)LexpQaE!@1ww?>BSK)A`$3v9JmNfIt8N z50zIwg=x1M001Z^0Qdk02yhSd^(KaeV@X60_uychBO73ncN7Ex8IO1ql({>Xb=Zbc z&dQ;^zrW#>nU5+cqc-_-DRd^TET!uo;&md+3}5Y2r#k5aw58%Oq_nuYLeAHIDPR+uayl^fp-^XE3WZ zoByz0?r7OO0W(w)M{;qv@MoiI+!I5zk7(i`7n4nZYWJB-)osky=F&IaIWWXzsb0)4 z{d)Ni7u&CizXf(*^hv-InXOp>z##zOWc2A9=tYdg`h*Age`+x&cOmSt0YCNb=8aS9 zhVA@v6K!#7&9x`#229PAiYyuAWtGu;ZN`^ucSxiR9c=b0jr~}}W=z{K64iM=Q*R8_ z`3K*sIh$(N>;7WlxMfP>R_TpxkyL7du+7=LWP0Ov_xSkx#tR)~vxn0O6m2ctqoQ|X z)CzjWDL=-pPL`?N^$Q1s%zEe4H8uKQ$Qs++ zxOCM-GVhEWuBVgDngJr#)5l6=&Lr3_HTkTyd~DC? z&xJ^!PqatV7A2>ui=y06vftlw?#CPP%%vXRpvE5%5CgfGmWOLzc(OW{M4C)hO2juK zV{+^Hqvf{+@k(VWUFpJE-j;;Av{8u~F>jg^CTj$-N^cnI8)}`L*4Q-YmR>i&ZgKNf z7Y>D)VBdGBA>Rd!(_1zv?3oz_PiH7nPKk0BeFJXS(^R%P^Vn&)$#m`K+7-OdU$Nt_ z6pQ#3+dL&pw~y&;USCd04I!^i`6QL{Ke4x{P^AeL)vC_lPl|f+*Zw1lAn#zir7xjT zY-8(+e|LVLM?)!AtsyYGSysK^GRG(?je8?FI>r&je5@kxGT$1#hzCsOaW0LsE$~`w z2M#6K*~|GUSLsfas8dhM++pXPM(txe)ulIVt=1M0Ty5?;cJ zrmdkbQ`v*|*NHAWjB}HRYi=8v$r7XMJTEpYo)z~SoVlH1(HB9bGK))dHHD!oA4|Cf z4z-#3KN%G;Xsu&$KN_M?ho~9017|>C;$bZ02F`% zP~blma5QyrB$yaF8WL2r4ULRVOwG)XSy)C;$bZ02F`%Pyh-* zf&YQPZlL9#)Lb@BTw9%SD^KQQpoIf`478|z5olSYS@BTC1c0)+*UiIinQ_eH!aLX* zPhYfW=e*K0Oc0hH;=1WTX8m-xPk)8U&Wec{$@1-R-tOA+_z$yhMfJX}@^$p>bCo&9 zbdMn1=K_1hcQ|&x2j%fZ#_#VD=J{M;Z?4@6uU H41m7@*Ia-} literal 0 HcmV?d00001 diff --git a/content/base/test/file_bug945152_worker.js b/content/base/test/file_bug945152_worker.js new file mode 100644 index 00000000000..c0520bbd83a --- /dev/null +++ b/content/base/test/file_bug945152_worker.js @@ -0,0 +1,98 @@ +var gData1 = "TEST_DATA_1:ABCDEFGHIJKLMNOPQRSTUVWXYZ"; +var gData2 = "TEST_DATA_2:1234567890"; +var gPaddingChar = '.'; +var gPaddingSize = 10000; +var gPadding = ""; + +for (var i = 0; i < gPaddingSize; i++) { + gPadding += gPaddingChar; +} + +function ok(a, msg) { + postMessage({type: 'status', status: !!a, msg: msg }); +} + +function is(a, b, msg) { + postMessage({type: 'status', status: a === b, msg: msg }); +} + +function checkData(response, data_head, cb) { + ok(response, "Data is non-null"); + var str = String.fromCharCode.apply(null, Uint8Array(response)); + ok(str.length == data_head.length + gPaddingSize, "Data size is correct"); + ok(str.slice(0, data_head.length) == data_head, "Data head is correct"); + ok(str.slice(data_head.length) == gPadding, "Data padding is correct"); + cb(); +} + +self.onmessage = function onmessage(event) { + + function test_mapped_sync() { + var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true}); + xhr.open('GET', 'jar:http://example.org/tests/content/base/test/file_bug945152.jar!/data_1.txt', false); + xhr.responseType = 'arraybuffer'; + xhr.send(); + if (xhr.status) { + ok(xhr.status == 200, "Status is 200"); + var ct = xhr.getResponseHeader("Content-Type"); + ok(ct.indexOf("mem-mapped") != -1, "Data is memory-mapped"); + checkData(xhr.response, gData1, runTests); + } + } + + function test_mapped_async() { + var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true}); + xhr.open('GET', 'jar:http://example.org/tests/content/base/test/file_bug945152.jar!/data_1.txt'); + xhr.responseType = 'arraybuffer'; + xhr.onreadystatechange = function() { + if (xhr.readyState !== xhr.DONE) { + return; + } + if (xhr.status) { + ok(xhr.status == 200, "Status is 200"); + var ct = xhr.getResponseHeader("Content-Type"); + ok(ct.indexOf("mem-mapped") != -1, "Data is memory-mapped"); + checkData(xhr.response, gData1, runTests); + } + } + xhr.send(); + } + + // Make sure array buffer retrieved from compressed file in package is + // handled by memory allocation instead of memory mapping. + function test_non_mapped() { + var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true}); + xhr.open('GET', 'jar:http://example.org/tests/content/base/test/file_bug945152.jar!/data_2.txt'); + xhr.responseType = 'arraybuffer'; + xhr.onreadystatechange = function() { + if (xhr.readyState !== xhr.DONE) { + return; + } + if (xhr.status) { + ok(xhr.status == 200, "Status is 200"); + var ct = xhr.getResponseHeader("Content-Type"); + ok(ct.indexOf("mem-mapped") == -1, "Data is not memory-mapped"); + checkData(xhr.response, gData2, runTests); + } + } + xhr.send(); + } + + var tests = [ + test_mapped_sync, + test_mapped_async, + test_non_mapped + ]; + + function runTests() { + if (!tests.length) { + postMessage({type: 'finish' }); + return; + } + + var test = tests.shift(); + test(); + } + + runTests(); +}; diff --git a/content/base/test/mochitest.ini b/content/base/test/mochitest.ini index 7c9270779a1..408d081a5d6 100644 --- a/content/base/test/mochitest.ini +++ b/content/base/test/mochitest.ini @@ -135,6 +135,8 @@ support-files = file_bug902350.html file_bug902350_frame.html file_bug907892.html + file_bug945152.jar + file_bug945152_worker.js file_general_document.html file_html_in_xhr.html file_html_in_xhr.sjs @@ -544,6 +546,8 @@ skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s #needs plugin suppor [test_bug907892.html] [test_bug922681.html] [test_bug927196.html] +[test_bug945152.html] +run-if = os == 'linux' [test_caretPositionFromPoint.html] [test_classList.html] # This test fails on the Mac for some reason diff --git a/content/base/test/test_bug945152.html b/content/base/test/test_bug945152.html new file mode 100644 index 00000000000..3f7144f158c --- /dev/null +++ b/content/base/test/test_bug945152.html @@ -0,0 +1,51 @@ + + + + + + Test for Bug 945152 + + + + +Mozilla Bug 945152 +

+ +
+
+
+ + From 5c6307cea7176fcaf3e2ddea45599322ed84d971 Mon Sep 17 00:00:00 2001 From: Shian-Yow Wu Date: Fri, 16 May 2014 13:34:44 +0800 Subject: [PATCH 30/70] Bug 945152 - Part 3-4: Test case to get array buffor by XHR with app:// URL. r=smaug,fabrice --- dom/apps/tests/chrome.ini | 4 + dom/apps/tests/file_bug_945152.html | 92 ++++++++++++++++ dom/apps/tests/file_bug_945152.sjs | 115 +++++++++++++++++++ dom/apps/tests/test_bug_945152.html | 164 ++++++++++++++++++++++++++++ 4 files changed, 375 insertions(+) create mode 100644 dom/apps/tests/file_bug_945152.html create mode 100644 dom/apps/tests/file_bug_945152.sjs create mode 100644 dom/apps/tests/test_bug_945152.html diff --git a/dom/apps/tests/chrome.ini b/dom/apps/tests/chrome.ini index fe3f2995df2..90fef415c1b 100644 --- a/dom/apps/tests/chrome.ini +++ b/dom/apps/tests/chrome.ini @@ -1,8 +1,12 @@ [DEFAULT] support-files = asmjs/* + file_bug_945152.html + file_bug_945152.sjs [test_apps_service.xul] +[test_bug_945152.html] +run-if = os == 'linux' [test_operator_app_install.js] [test_operator_app_install.xul] # bug 928262 diff --git a/dom/apps/tests/file_bug_945152.html b/dom/apps/tests/file_bug_945152.html new file mode 100644 index 00000000000..7f7ed6aec52 --- /dev/null +++ b/dom/apps/tests/file_bug_945152.html @@ -0,0 +1,92 @@ + + + + + + + diff --git a/dom/apps/tests/file_bug_945152.sjs b/dom/apps/tests/file_bug_945152.sjs new file mode 100644 index 00000000000..dad2a23e0bd --- /dev/null +++ b/dom/apps/tests/file_bug_945152.sjs @@ -0,0 +1,115 @@ +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + +// From prio.h +const PR_RDWR = 0x04; +const PR_CREATE_FILE = 0x08; +const PR_TRUNCATE = 0x20; + +Cu.import("resource://gre/modules/FileUtils.jsm"); +Cu.import("resource://gre/modules/NetUtil.jsm"); + +var gBasePath = "chrome/dom/apps/tests/"; +var gAppPath = gBasePath + "file_bug_945152.html"; +var gData1 = "TEST_DATA_1:ABCDEFGHIJKLMNOPQRSTUVWXYZ"; +var gData2 = "TEST_DATA_2:1234567890"; +var gPaddingChar = '.'; +var gPaddingSize = 10000; +var gPackageName = "file_bug_945152.zip"; +var gManifestData = { + name: "Testing app", + description: "Testing app", + package_path: "http://test/chrome/dom/apps/tests/file_bug_945152.sjs?getPackage=1", + launch_path: "/index.html", + developer: { + name: "devname", + url: "http://dev.url" + }, + default_locale: "en-US" +}; + +function handleRequest(request, response) { + var query = getQuery(request); + + // Create the packaged app. + if ("createApp" in query) { + var zipWriter = Cc["@mozilla.org/zipwriter;1"] + .createInstance(Ci.nsIZipWriter); + var zipFile = FileUtils.getFile("TmpD", [gPackageName]); + zipWriter.open(zipFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE); + + var manifest = JSON.stringify(gManifestData); + addZipEntry(zipWriter, manifest, "manifest.webapp", Ci.nsIZipWriter.COMPRESSION_BEST); + var app = readFile(gAppPath, false); + addZipEntry(zipWriter, app, "index.html", Ci.nsIZipWriter.COMPRESSION_BEST); + var padding = ""; + for (var i = 0; i < gPaddingSize; i++) { + padding += gPaddingChar; + } + var data = gData1 + padding; + addZipEntry(zipWriter, data, "data_1.txt", Ci.nsIZipWriter.COMPRESSION_NONE); + data = gData2 + padding; + addZipEntry(zipWriter, data, "data_2.txt", Ci.nsIZipWriter.COMPRESSION_BEST); + + zipWriter.alignStoredFiles(4096); + zipWriter.close(); + + response.setHeader("Content-Type", "text/html", false); + response.write("OK"); + return; + } + + // Check if we're generating a webapp manifest. + if ("getManifest" in query) { + response.setHeader("Content-Type", "application/x-web-app-manifest+json", false); + response.write(JSON.stringify(gManifestData)); + return; + } + + // Serve the application package. + if ("getPackage" in query) { + var resource = readFile(gPackageName, true); + response.setHeader("Content-Type", + "Content-Type: application/java-archive", false); + response.write(resource); + return; + } + + response.setHeader("Content-type", "text-html", false); + response.write("KO"); +} + +function getQuery(request) { + var query = {}; + request.queryString.split('&').forEach(function (val) { + var [name, value] = val.split('='); + query[name] = unescape(value); + }); + return query; +} + +// File and resources helpers + +function addZipEntry(zipWriter, entry, entryName, compression) { + var stream = Cc["@mozilla.org/io/string-input-stream;1"] + .createInstance(Ci.nsIStringInputStream); + stream.setData(entry, entry.length); + zipWriter.addEntryStream(entryName, Date.now(), + compression, stream, false); +} + +function readFile(path, fromTmp) { + var dir = fromTmp ? "TmpD" : "CurWorkD"; + var file = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get(dir, Ci.nsILocalFile); + var fstream = Cc["@mozilla.org/network/file-input-stream;1"] + .createInstance(Ci.nsIFileInputStream); + var split = path.split("/"); + for(var i = 0; i < split.length; ++i) { + file.append(split[i]); + } + fstream.init(file, -1, 0, 0); + var data = NetUtil.readInputStreamToString(fstream, fstream.available()); + fstream.close(); + return data; +} diff --git a/dom/apps/tests/test_bug_945152.html b/dom/apps/tests/test_bug_945152.html new file mode 100644 index 00000000000..74d6599afed --- /dev/null +++ b/dom/apps/tests/test_bug_945152.html @@ -0,0 +1,164 @@ + + + + + + Test for Bug 945152 + + + + + +Mozilla Bug 945152 +

+ +
+
+
+ + From d75d945512ca855a1094c3664fab04cf4631cbeb Mon Sep 17 00:00:00 2001 From: Shu-yu Guo Date: Thu, 15 May 2014 18:18:21 -0700 Subject: [PATCH 31/70] Bug 1009335 - Propagate errors from ParallelSafetyAnalysis when compiling off main thread. (r=bhackett) --- js/src/jit/Ion.cpp | 8 ++++++-- js/src/jit/IonBuilder.cpp | 2 +- js/src/jit/IonBuilder.h | 3 --- js/src/jit/MIRGenerator.h | 8 ++++++++ js/src/jit/MIRGraph.cpp | 1 + js/src/jit/ParallelSafetyAnalysis.cpp | 1 + 6 files changed, 17 insertions(+), 6 deletions(-) diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index 9de0884b567..e7498c6e7a8 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -547,8 +547,12 @@ jit::FinishOffThreadBuilder(IonBuilder *builder) builder->script()->ionScript()->clearRecompiling(); // Clean up if compilation did not succeed. - if (CompilingOffThread(builder->script(), executionMode)) - SetIonScript(builder->script(), executionMode, nullptr); + if (CompilingOffThread(builder->script(), executionMode)) { + SetIonScript(builder->script(), executionMode, + builder->abortReason() == AbortReason_Disable + ? ION_DISABLED_SCRIPT + : nullptr); + } // The builder is allocated into its LifoAlloc, so destroying that will // destroy the builder and all other data accumulated during compilation, diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 8c63e9fcc26..a61eae1e75b 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -116,7 +116,6 @@ IonBuilder::IonBuilder(JSContext *analysisContext, CompileCompartment *comp, backgroundCodegen_(nullptr), analysisContext(analysisContext), baselineFrame_(baselineFrame), - abortReason_(AbortReason_Disable), descrSetHash_(nullptr), constraints_(constraints), analysis_(*temp, info->script()), @@ -145,6 +144,7 @@ IonBuilder::IonBuilder(JSContext *analysisContext, CompileCompartment *comp, { script_ = info->script(); pc = info->startPC(); + abortReason_ = AbortReason_Disable; JS_ASSERT(script()->hasBaselineScript() == (info->executionMode() != ArgumentsUsageAnalysis)); JS_ASSERT(!!analysisContext == (info->executionMode() == DefinitePropertiesAnalysis)); diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 3c7720406a4..885d03be310 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -818,8 +818,6 @@ class IonBuilder : public MIRGenerator CodeGenerator *backgroundCodegen() const { return backgroundCodegen_; } void setBackgroundCodegen(CodeGenerator *codegen) { backgroundCodegen_ = codegen; } - AbortReason abortReason() { return abortReason_; } - TypeDescrSetHash *getOrCreateDescrSetHash(); // fallible types::CompilerConstraintList *constraints() { @@ -837,7 +835,6 @@ class IonBuilder : public MIRGenerator JSContext *analysisContext; BaselineFrameInspector *baselineFrame_; - AbortReason abortReason_; TypeDescrSetHash *descrSetHash_; // Constraints for recording dependencies on type information. diff --git a/js/src/jit/MIRGenerator.h b/js/src/jit/MIRGenerator.h index 16a46baa280..636abd78578 100644 --- a/js/src/jit/MIRGenerator.h +++ b/js/src/jit/MIRGenerator.h @@ -87,6 +87,13 @@ class MIRGenerator cancelBuild_ = true; } + void disable() { + abortReason_ = AbortReason_Disable; + } + AbortReason abortReason() { + return abortReason_; + } + bool compilingAsmJS() const { return info_->compilingAsmJS(); } @@ -141,6 +148,7 @@ class MIRGenerator JSFunction *fun_; uint32_t nslots_; MIRGraph *graph_; + AbortReason abortReason_; bool error_; mozilla::Atomic cancelBuild_; diff --git a/js/src/jit/MIRGraph.cpp b/js/src/jit/MIRGraph.cpp index 31af60dfe8c..1553e82c567 100644 --- a/js/src/jit/MIRGraph.cpp +++ b/js/src/jit/MIRGraph.cpp @@ -24,6 +24,7 @@ MIRGenerator::MIRGenerator(CompileCompartment *compartment, const JitCompileOpti optimizationInfo_(optimizationInfo), alloc_(alloc), graph_(graph), + abortReason_(AbortReason_NoAbort), error_(false), cancelBuild_(false), maxAsmJSStackArgBytes_(0), diff --git a/js/src/jit/ParallelSafetyAnalysis.cpp b/js/src/jit/ParallelSafetyAnalysis.cpp index f6251804386..ffdd88b5716 100644 --- a/js/src/jit/ParallelSafetyAnalysis.cpp +++ b/js/src/jit/ParallelSafetyAnalysis.cpp @@ -384,6 +384,7 @@ ParallelSafetyAnalysis::analyze() // always bailout. if (*block == graph_.entryBlock()) { Spew(SpewCompile, "Entry block contains unsafe MIR"); + mir_->disable(); return false; } From 22dfa21a80b594238e581d463504342137d87327 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 15 May 2014 17:27:08 -0700 Subject: [PATCH 32/70] Bug 1011355 (part 1) - Remove FreeList::allocateFromNewArena(). r=billm. --HG-- extra : rebase_source : 0098da34802c9febdd0fe351859ce995ba7e449a --- js/src/gc/Heap.h | 18 +----------------- js/src/jsgc.cpp | 16 +++++++++++----- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/js/src/gc/Heap.h b/js/src/gc/Heap.h index c66702a8413..8c242f7d48e 100644 --- a/js/src/gc/Heap.h +++ b/js/src/gc/Heap.h @@ -379,22 +379,6 @@ class FreeList JS_EXTRA_POISON(reinterpret_cast(thing), JS_ALLOCATED_TENURED_PATTERN, thingSize); return reinterpret_cast(thing); } - - /* - * Allocate from a newly allocated arena. The arena will have been set up - * as fully used during the initialization so to allocate we simply return - * the first thing in the arena and set the free list to point to the - * second. - */ - MOZ_ALWAYS_INLINE void *allocateFromNewArena(uintptr_t arenaAddr, size_t firstThingOffset, - size_t thingSize) { - JS_ASSERT(isEmpty()); - JS_ASSERT(!(arenaAddr & ArenaMask)); - uintptr_t thing = arenaAddr + firstThingOffset; - head.initFinal(thing + thingSize, arenaAddr + ArenaSize - thingSize, thingSize); - JS_EXTRA_POISON(reinterpret_cast(thing), JS_ALLOCATED_TENURED_PATTERN, thingSize); - return reinterpret_cast(thing); - } }; /* Every arena has a header. */ @@ -479,7 +463,7 @@ struct ArenaHeader : public JS::shadow::ArenaHeader static_assert(FINALIZE_LIMIT <= 255, "We must be able to fit the allockind into uint8_t."); allocKind = size_t(kind); - /* See comments in FreeSpan::allocateFromNewArena. */ + /* The arena is initially marked as full; see allocateFromArenaInline(). */ firstFreeSpanOffsets = FreeSpan::FullArenaOffsets; } diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index c21e79c0849..da5dd4ff87f 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -1591,12 +1591,18 @@ ArenaLists::allocateFromArenaInline(Zone *zone, AllocKind thingKind) } al->insertAtStart(aheader); - /* See comments before allocateFromNewArena about this assert. */ + /* + * Allocate from a newly allocated arena. The arena will have been set up + * as fully used during the initialization so we have to re-mark it as + * empty before allocating. + */ JS_ASSERT(!aheader->hasFreeThings()); - uintptr_t arenaAddr = aheader->arenaAddress(); - return freeLists[thingKind].allocateFromNewArena(arenaAddr, - Arena::firstThingOffset(thingKind), - Arena::thingSize(thingKind)); + Arena *arena = aheader->getArena(); + size_t thingSize = Arena::thingSize(thingKind); + FreeSpan fullSpan; + fullSpan.initFinal(arena->thingsStart(thingKind), arena->thingsEnd() - thingSize, thingSize); + freeLists[thingKind].setHead(&fullSpan); + return freeLists[thingKind].allocate(thingSize); } void * From ea70beb0f9d713dff2ddcfa329d9ff29ea397d8c Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 15 May 2014 22:16:25 -0700 Subject: [PATCH 33/70] Bug 1011355 (part 2) - Add a CompactFreeSpan class. r=billm. --HG-- extra : rebase_source : 693f7ff5abb5e3335b62c790c997f713526027f0 --- js/src/gc/Heap.h | 114 ++++++++++++++++++++++++++++------------------- js/src/jsgc.cpp | 2 +- 2 files changed, 68 insertions(+), 48 deletions(-) diff --git a/js/src/gc/Heap.h b/js/src/gc/Heap.h index 8c242f7d48e..36776a75355 100644 --- a/js/src/gc/Heap.h +++ b/js/src/gc/Heap.h @@ -148,8 +148,9 @@ const size_t ArenaBitmapWords = ArenaBitmapBits / JS_BITS_PER_WORD; */ class FreeSpan { - friend class FreeList; friend class ArenaCellIterImpl; + friend class CompactFreeSpan; + friend class FreeList; uintptr_t first; uintptr_t last; @@ -188,37 +189,6 @@ class FreeSpan checkSpan(thingSize); } - /* - * To minimize the size of the arena header the first span is encoded - * there as offsets from the arena start. - */ - static size_t encodeOffsets(size_t firstOffset, size_t lastOffset) { - static_assert(ArenaShift < 16, "Check that we can pack offsets into uint16_t."); - JS_ASSERT(firstOffset <= lastOffset); - JS_ASSERT(lastOffset < ArenaSize); - return firstOffset | (lastOffset << 16); - } - - /* - * Encoded offsets for a full arena, i.e. one with an empty FreeSpan. - */ - static const size_t FullArenaOffsets = 0; - - static FreeSpan decodeOffsets(uintptr_t arenaAddr, size_t offsets) { - JS_ASSERT(!(arenaAddr & ArenaMask)); - FreeSpan decodedSpan; - if (offsets == FullArenaOffsets) { - decodedSpan.initAsEmpty(); - } else { - size_t firstOffset = offsets & 0xFFFF; - size_t lastOffset = offsets >> 16; - JS_ASSERT(firstOffset <= lastOffset); - JS_ASSERT(lastOffset < ArenaSize); - decodedSpan.initBounds(arenaAddr + firstOffset, arenaAddr + lastOffset); - } - return decodedSpan; - } - bool isEmpty() const { checkSpan(); return !first; @@ -247,10 +217,6 @@ class FreeSpan } #endif - size_t encodeAsOffsets() const { - return encodeOffsets(first & ArenaMask, last & ArenaMask); - } - size_t length(size_t thingSize) const { checkSpan(); JS_ASSERT((last - first) % thingSize == 0); @@ -300,6 +266,57 @@ class FreeSpan } }; +class CompactFreeSpan +{ + uint16_t firstOffset_; + uint16_t lastOffset_; + + public: + CompactFreeSpan(size_t firstOffset, size_t lastOffset) + : firstOffset_(firstOffset) + , lastOffset_(lastOffset) + {} + + void initAsEmpty() { + firstOffset_ = 0; + lastOffset_ = 0; + } + + bool operator==(const CompactFreeSpan &other) const { + return firstOffset_ == other.firstOffset_ && + lastOffset_ == other.lastOffset_; + } + + void compact(FreeSpan span) { + if (span.isEmpty()) { + initAsEmpty(); + } else { + static_assert(ArenaShift < 16, "Check that we can pack offsets into uint16_t."); + uintptr_t arenaAddr = span.arenaAddress(); + firstOffset_ = span.first - arenaAddr; + lastOffset_ = span.last - arenaAddr; + } + } + + bool isEmpty() const { + JS_ASSERT(!!firstOffset_ == !!lastOffset_); + return !firstOffset_; + } + + FreeSpan decompact(uintptr_t arenaAddr) const { + JS_ASSERT(!(arenaAddr & ArenaMask)); + FreeSpan decodedSpan; + if (isEmpty()) { + decodedSpan.initAsEmpty(); + } else { + JS_ASSERT(firstOffset_ <= lastOffset_); + JS_ASSERT(lastOffset_ < ArenaSize); + decodedSpan.initBounds(arenaAddr + firstOffset_, arenaAddr + lastOffset_); + } + return decodedSpan; + } +}; + class FreeList { // Although |head| is private, it is exposed to the JITs via the @@ -395,11 +412,10 @@ struct ArenaHeader : public JS::shadow::ArenaHeader private: /* - * The first span of free things in the arena. We encode it as the start - * and end offsets within the arena, not as FreeSpan structure, to - * minimize the header size. + * The first span of free things in the arena. We encode it as a + * CompactFreeSpan rather than a FreeSpan to minimize the header size. */ - size_t firstFreeSpanOffsets; + CompactFreeSpan firstFreeSpan; /* * One of AllocKind constants or FINALIZE_LIMIT when the arena does not @@ -463,8 +479,11 @@ struct ArenaHeader : public JS::shadow::ArenaHeader static_assert(FINALIZE_LIMIT <= 255, "We must be able to fit the allockind into uint8_t."); allocKind = size_t(kind); - /* The arena is initially marked as full; see allocateFromArenaInline(). */ - firstFreeSpanOffsets = FreeSpan::FullArenaOffsets; + /* + * The firstFreeSpan is initially marked as empty (and thus the arena + * is marked as full). See allocateFromArenaInline(). + */ + firstFreeSpan.initAsEmpty(); } void setAsNotAllocated() { @@ -486,13 +505,13 @@ struct ArenaHeader : public JS::shadow::ArenaHeader inline size_t getThingSize() const; bool hasFreeThings() const { - return firstFreeSpanOffsets != FreeSpan::FullArenaOffsets; + return !firstFreeSpan.isEmpty(); } inline bool isEmpty() const; void setAsFullyUsed() { - firstFreeSpanOffsets = FreeSpan::FullArenaOffsets; + firstFreeSpan.initAsEmpty(); } inline FreeSpan getFirstFreeSpan() const; @@ -900,7 +919,8 @@ ArenaHeader::isEmpty() const JS_ASSERT(allocated()); size_t firstThingOffset = Arena::firstThingOffset(getAllocKind()); size_t lastThingOffset = ArenaSize - getThingSize(); - return firstFreeSpanOffsets == FreeSpan::encodeOffsets(firstThingOffset, lastThingOffset); + const CompactFreeSpan emptyCompactSpan(firstThingOffset, lastThingOffset); + return firstFreeSpan == emptyCompactSpan; } FreeSpan @@ -909,14 +929,14 @@ ArenaHeader::getFirstFreeSpan() const #ifdef DEBUG checkSynchronizedWithFreeList(); #endif - return FreeSpan::decodeOffsets(arenaAddress(), firstFreeSpanOffsets); + return firstFreeSpan.decompact(arenaAddress()); } void ArenaHeader::setFirstFreeSpan(const FreeSpan *span) { JS_ASSERT_IF(!span->isEmpty(), span->isWithinArena(arenaAddress())); - firstFreeSpanOffsets = span->encodeAsOffsets(); + firstFreeSpan.compact(*span); } inline ArenaHeader * diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index da5dd4ff87f..45e697e6eaa 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -412,7 +412,7 @@ ArenaHeader::checkSynchronizedWithFreeList() const if (IsBackgroundFinalized(getAllocKind()) && zone->runtimeFromAnyThread()->gc.helperThread.onBackgroundThread()) return; - FreeSpan firstSpan = FreeSpan::decodeOffsets(arenaAddress(), firstFreeSpanOffsets); + FreeSpan firstSpan = firstFreeSpan.decompact(arenaAddress()); if (firstSpan.isEmpty()) return; const FreeList *freeList = zone->allocator.arenas.getFreeList(getAllocKind()); From 0032128ea4d9471a8842f5057a6d96c2096a9335 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 15 May 2014 22:16:27 -0700 Subject: [PATCH 34/70] Bug 1011355 (part 3) - Tiny FreeSpan tweaks. r=billm. --HG-- extra : rebase_source : 739e5104bee8ea878d5c8e421d7cd6cfd095c2da --- js/src/gc/Heap.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/js/src/gc/Heap.h b/js/src/gc/Heap.h index 36776a75355..996cfc9237d 100644 --- a/js/src/gc/Heap.h +++ b/js/src/gc/Heap.h @@ -156,8 +156,6 @@ class FreeSpan uintptr_t last; public: - FreeSpan() {} - // This inits just |first| and |last|; if the span is non-empty it doesn't // do anything with the next span stored at |last|. void initBoundsUnchecked(uintptr_t first, uintptr_t last) { @@ -236,6 +234,7 @@ class FreeSpan return false; } + private: // Some callers can pass in |thingSize| easily, and we can do stronger // checking in that case. void checkSpan(size_t thingSize = 0) const { From 72b05e085be6ecc99f59e2da3395bd40a5a62c7d Mon Sep 17 00:00:00 2001 From: "Nicolas B. Pierron" Date: Thu, 15 May 2014 22:57:18 -0700 Subject: [PATCH 35/70] Bug 1007027 - Replace MPhi::slot by a flag based on ResumePoint indexes. r=h4writer --- js/src/jit-test/tests/ion/bug1007027.js | 5 + js/src/jit/CompileInfo.h | 28 +++++- js/src/jit/IonAnalysis.cpp | 39 ++------ js/src/jit/IonBuilder.cpp | 41 ++++---- js/src/jit/MIR.cpp | 9 +- js/src/jit/MIR.h | 20 ++-- js/src/jit/MIRGraph.cpp | 123 ++++++++++++++++-------- js/src/jit/MIRGraph.h | 3 + js/src/jit/shared/Lowering-shared.cpp | 2 +- 9 files changed, 163 insertions(+), 107 deletions(-) create mode 100644 js/src/jit-test/tests/ion/bug1007027.js diff --git a/js/src/jit-test/tests/ion/bug1007027.js b/js/src/jit-test/tests/ion/bug1007027.js new file mode 100644 index 00000000000..f98c14fe87f --- /dev/null +++ b/js/src/jit-test/tests/ion/bug1007027.js @@ -0,0 +1,5 @@ +// |jit-test| error: ReferenceError +(function(x) { + x = i ? 4 : 2 + y +})() diff --git a/js/src/jit/CompileInfo.h b/js/src/jit/CompileInfo.h index 237dedbb0dc..08a8e707114 100644 --- a/js/src/jit/CompileInfo.h +++ b/js/src/jit/CompileInfo.h @@ -404,13 +404,31 @@ class CompileInfo return executionMode_ == ParallelExecution; } - bool canOptimizeOutSlot(uint32_t i) const { - if (script()->strict()) + // Returns true if a slot can be observed out-side the current frame while + // the frame is active on the stack. This implies that these definitions + // would have to be executed and that they cannot be removed even if they + // are unused. + bool isObservableSlot(uint32_t slot) const { + if (!funMaybeLazy()) + return false; + + // The |this| value must always be observable. + if (slot == thisSlot()) return true; - // Function.arguments can be used to access all arguments in - // non-strict scripts, so we can't optimize out any arguments. - return !(firstArgSlot() <= i && i - firstArgSlot() < nargs()); + // If the function may need an arguments object, then make sure to + // preserve the scope chain, because it may be needed to construct the + // arguments object during bailout. If we've already created an + // arguments object (or got one via OSR), preserve that as well. + if (hasArguments() && (slot == scopeChainSlot() || slot == argsObjSlot())) + return true; + + // Function.arguments can be used to access all arguments in non-strict + // scripts, so we can't optimize out any arguments. + if (!script()->strict() && firstArgSlot() <= slot && slot - firstArgSlot() < nargs()) + return true; + + return false; } private: diff --git a/js/src/jit/IonAnalysis.cpp b/js/src/jit/IonAnalysis.cpp index 49acd6e74ac..adcbe452ce3 100644 --- a/js/src/jit/IonAnalysis.cpp +++ b/js/src/jit/IonAnalysis.cpp @@ -107,6 +107,12 @@ jit::EliminateDeadResumePointOperands(MIRGenerator *mir, MIRGraph &graph) if (ins->isImplicitlyUsed()) continue; + // If the instruction's is captured by one of the resume point, then + // it might be observed indirectly while the frame is live on the + // stack, so it has to be computed. + if (ins->isObserved()) + continue; + // Check if this instruction's result is only used within the // current block, and keep track of its last use in a definition // (not resume point). This requires the instructions in the block @@ -143,14 +149,6 @@ jit::EliminateDeadResumePointOperands(MIRGenerator *mir, MIRGraph &graph) continue; } - // The operand is an uneliminable slot. This currently - // includes argument slots in non-strict scripts (due to being - // observable via Function.arguments). - if (!block->info().canOptimizeOutSlot(uses->index())) { - uses++; - continue; - } - // Store an optimized out magic value in place of all dead // resume point operands. Making any such substitution can in // general alter the interpreter's behavior, even though the @@ -232,30 +230,7 @@ IsPhiObservable(MPhi *phi, Observability observe) break; } - uint32_t slot = phi->slot(); - CompileInfo &info = phi->block()->info(); - JSFunction *fun = info.funMaybeLazy(); - - // If the Phi is of the |this| value, it must always be observable. - if (fun && slot == info.thisSlot()) - return true; - - // If the function may need an arguments object, then make sure to - // preserve the scope chain, because it may be needed to construct the - // arguments object during bailout. If we've already created an arguments - // object (or got one via OSR), preserve that as well. - if (fun && info.hasArguments() && - (slot == info.scopeChainSlot() || slot == info.argsObjSlot())) - { - return true; - } - - // The Phi is an uneliminable slot. Currently this includes argument slots - // in non-strict scripts (due to being observable via Function.arguments). - if (fun && !info.canOptimizeOutSlot(slot)) - return true; - - return false; + return phi->isObserved(); } // Handles cases like: diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index a61eae1e75b..4f723ec0e74 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -444,14 +444,21 @@ IonBuilder::analyzeNewLoopTypes(MBasicBlock *entry, jsbytecode *start, jsbytecod for (size_t i = 0; i < loopHeaders_.length(); i++) { if (loopHeaders_[i].pc == start) { MBasicBlock *oldEntry = loopHeaders_[i].header; - for (MPhiIterator oldPhi = oldEntry->phisBegin(); - oldPhi != oldEntry->phisEnd(); - oldPhi++) - { - MPhi *newPhi = entry->getSlot(oldPhi->slot())->toPhi(); + MResumePoint *oldEntryRp = oldEntry->entryResumePoint(); + size_t stackDepth = oldEntryRp->numOperands(); + for (size_t slot = 0; slot < stackDepth; slot++) { + MDefinition *oldDef = oldEntryRp->getOperand(slot); + if (!oldDef->isPhi()) { + MOZ_ASSERT(oldDef->block()->id() < oldEntry->id()); + MOZ_ASSERT(oldDef == entry->getSlot(slot)); + continue; + } + MPhi *oldPhi = oldDef->toPhi(); + MPhi *newPhi = entry->getSlot(slot)->toPhi(); if (!newPhi->addBackedgeType(oldPhi->type(), oldPhi->resultTypeSet())) return false; } + // Update the most recent header for this loop encountered, in case // new types flow to the phis and the loop is processed at least // three times. @@ -1150,26 +1157,24 @@ IonBuilder::maybeAddOsrTypeBarriers() static const size_t OSR_PHI_POSITION = 1; JS_ASSERT(preheader->getPredecessor(OSR_PHI_POSITION) == osrBlock); - MPhiIterator headerPhi = header->phisBegin(); - while (headerPhi != header->phisEnd() && headerPhi->slot() < info().startArgSlot()) - headerPhi++; - - for (uint32_t i = info().startArgSlot(); i < osrBlock->stackDepth(); i++, headerPhi++) { + MResumePoint *headerRp = header->entryResumePoint(); + size_t stackDepth = headerRp->numOperands(); + MOZ_ASSERT(stackDepth == osrBlock->stackDepth()); + for (uint32_t slot = info().startArgSlot(); slot < stackDepth; slot++) { // Aliased slots are never accessed, since they need to go through // the callobject. The typebarriers are added there and can be // discarded here. - if (info().isSlotAliasedAtOsr(i)) + if (info().isSlotAliasedAtOsr(slot)) continue; - MInstruction *def = osrBlock->getSlot(i)->toInstruction(); - - JS_ASSERT(headerPhi->slot() == i); - MPhi *preheaderPhi = preheader->getSlot(i)->toPhi(); + MInstruction *def = osrBlock->getSlot(slot)->toInstruction(); + MPhi *preheaderPhi = preheader->getSlot(slot)->toPhi(); + MPhi *headerPhi = headerRp->getOperand(slot)->toPhi(); MIRType type = headerPhi->type(); types::TemporaryTypeSet *typeSet = headerPhi->resultTypeSet(); - if (!addOsrValueTypeBarrier(i, &def, type, typeSet)) + if (!addOsrValueTypeBarrier(slot, &def, type, typeSet)) return false; preheaderPhi->replaceOperand(OSR_PHI_POSITION, def); @@ -4104,7 +4109,7 @@ IonBuilder::patchInlinedReturns(CallInfo &callInfo, MIRGraphReturns &returns, MB return patchInlinedReturn(callInfo, returns[0], bottom); // Accumulate multiple returns with a phi. - MPhi *phi = MPhi::New(alloc(), bottom->stackDepth()); + MPhi *phi = MPhi::New(alloc()); if (!phi->reserveLength(returns.length())) return nullptr; @@ -4533,7 +4538,7 @@ IonBuilder::inlineCalls(CallInfo &callInfo, ObjectVector &targets, returnBlock->inheritSlots(dispatchBlock); callInfo.popFormals(returnBlock); - MPhi *retPhi = MPhi::New(alloc(), returnBlock->stackDepth()); + MPhi *retPhi = MPhi::New(alloc()); returnBlock->addPhi(retPhi); returnBlock->push(retPhi); diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp index 336b0adee22..57e4f94979f 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -2342,7 +2342,8 @@ MResumePoint::inherit(MBasicBlock *block) } } -void MResumePoint::dump(FILE *fp) const +void +MResumePoint::dump(FILE *fp) const { fprintf(fp, "resumepoint mode="); @@ -2377,6 +2378,12 @@ MResumePoint::dump() const dump(stderr); } +bool +MResumePoint::isObservableOperand(size_t index) const +{ + return block()->info().isObservableSlot(index); +} + MDefinition * MToInt32::foldsTo(TempAllocator &alloc, bool useValueNumbers) { diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 92d69be469b..14efcd7ab14 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -67,6 +67,7 @@ MIRType MIRTypeFromValue(const js::Value &vp) _(Movable) /* Allow LICM and GVN to move this instruction */ \ _(Lowered) /* (Debug only) has a virtual register */ \ _(Guard) /* Not removable if uses == 0 */ \ + _(Observed) /* Cannot be optimized out */ \ \ /* Keep the flagged instruction in resume points and do not substitute this * instruction by an UndefinedValue. This might be used by call inlining @@ -4687,7 +4688,6 @@ class MPhi MOZ_FINAL : public MDefinition, public InlineForwardListNode { js::Vector inputs_; - uint32_t slot_; bool hasBackedgeType_; bool triedToSpecialize_; bool isIterator_; @@ -4707,9 +4707,8 @@ class MPhi MOZ_FINAL : public MDefinition, public InlineForwardListNode public: INSTRUCTION_HEADER(Phi) - MPhi(TempAllocator &alloc, uint32_t slot, MIRType resultType) + MPhi(TempAllocator &alloc, MIRType resultType) : inputs_(alloc), - slot_(slot), hasBackedgeType_(false), triedToSpecialize_(false), isIterator_(false), @@ -4723,8 +4722,8 @@ class MPhi MOZ_FINAL : public MDefinition, public InlineForwardListNode setResultType(resultType); } - static MPhi *New(TempAllocator &alloc, uint32_t slot, MIRType resultType = MIRType_Value) { - return new(alloc) MPhi(alloc, slot, resultType); + static MPhi *New(TempAllocator &alloc, MIRType resultType = MIRType_Value) { + return new(alloc) MPhi(alloc, resultType); } void setOperand(size_t index, MDefinition *operand) { @@ -4745,9 +4744,6 @@ class MPhi MOZ_FINAL : public MDefinition, public InlineForwardListNode size_t numOperands() const { return inputs_.length(); } - uint32_t slot() const { - return slot_; - } bool hasBackedgeType() const { return hasBackedgeType_; } @@ -9692,8 +9688,12 @@ class MResumePoint MOZ_FINAL : public MNode, public InlineForwardListNodeaddUse(&operands_[index]); + if (!operand->isObserved() && isObservableOperand(index)) + operand->setObserved(); } void clearOperand(size_t index) { @@ -9715,8 +9715,12 @@ class MResumePoint MOZ_FINAL : public MNode, public InlineForwardListNodeisObserved()); return operands_[index].producer(); } jsbytecode *pc() const { diff --git a/js/src/jit/MIRGraph.cpp b/js/src/jit/MIRGraph.cpp index 1553e82c567..1dcdf633f3b 100644 --- a/js/src/jit/MIRGraph.cpp +++ b/js/src/jit/MIRGraph.cpp @@ -273,15 +273,17 @@ MBasicBlock::NewAsmJS(MIRGraph &graph, CompileInfo &info, MBasicBlock *pred, Kin if (!phis) return nullptr; + // Note: Phis are inserted in the same order as the slots. for (size_t i = 0; i < nphis; i++) { MDefinition *predSlot = pred->getSlot(i); JS_ASSERT(predSlot->type() != MIRType_Value); - MPhi *phi = new(phis + i) MPhi(alloc, i, predSlot->type()); + MPhi *phi = new(phis + i) MPhi(alloc, predSlot->type()); JS_ALWAYS_TRUE(phi->reserveLength(2)); phi->addInput(predSlot); + // Add append Phis in the block. block->addPhi(phi); block->setSlot(i, phi); } @@ -390,7 +392,7 @@ MBasicBlock::inherit(TempAllocator &alloc, BytecodeAnalysis *analysis, MBasicBlo if (kind_ == PENDING_LOOP_HEADER) { size_t i = 0; for (i = 0; i < info().firstStackSlot(); i++) { - MPhi *phi = MPhi::New(alloc, i); + MPhi *phi = MPhi::New(alloc); if (!phi->addInputSlow(pred->getSlot(i))) return false; addPhi(phi); @@ -411,7 +413,7 @@ MBasicBlock::inherit(TempAllocator &alloc, BytecodeAnalysis *analysis, MBasicBlo } for (; i < stackDepth(); i++) { - MPhi *phi = MPhi::New(alloc, i); + MPhi *phi = MPhi::New(alloc); if (!phi->addInputSlow(pred->getSlot(i))) return false; addPhi(phi); @@ -910,9 +912,9 @@ MBasicBlock::addPredecessorPopN(TempAllocator &alloc, MBasicBlock *pred, uint32_ // Otherwise, create a new phi node. MPhi *phi; if (mine->type() == other->type()) - phi = MPhi::New(alloc, i, mine->type()); + phi = MPhi::New(alloc, mine->type()); else - phi = MPhi::New(alloc, i); + phi = MPhi::New(alloc); addPhi(phi); // Prime the phi for each predecessor, so input(x) comes from @@ -992,34 +994,8 @@ MBasicBlock::setBackedge(MBasicBlock *pred) bool hadTypeChange = false; // Add exit definitions to each corresponding phi at the entry. - for (MPhiIterator phi = phisBegin(); phi != phisEnd(); phi++) { - MPhi *entryDef = *phi; - MDefinition *exitDef = pred->slots_[entryDef->slot()]; - - // Assert that we already placed phis for each slot. - JS_ASSERT(entryDef->block() == this); - - if (entryDef == exitDef) { - // If the exit def is the same as the entry def, make a redundant - // phi. Since loop headers have exactly two incoming edges, we - // know that that's just the first input. - // - // Note that we eliminate later rather than now, to avoid any - // weirdness around pending continue edges which might still hold - // onto phis. - exitDef = entryDef->getOperand(0); - } - - bool typeChange = false; - - if (!entryDef->addInputSlow(exitDef, &typeChange)) - return AbortReason_Alloc; - - hadTypeChange |= typeChange; - - JS_ASSERT(entryDef->slot() < pred->stackDepth()); - setSlot(entryDef->slot(), entryDef); - } + if (!inheritPhisFromBackedge(pred, &hadTypeChange)) + return AbortReason_Alloc; if (hadTypeChange) { for (MPhiIterator phi = phisBegin(); phi != phisEnd(); phi++) @@ -1048,9 +1024,12 @@ MBasicBlock::setBackedgeAsmJS(MBasicBlock *pred) JS_ASSERT(kind_ == PENDING_LOOP_HEADER); // Add exit definitions to each corresponding phi at the entry. - for (MPhiIterator phi = phisBegin(); phi != phisEnd(); phi++) { + // Note: Phis are inserted in the same order as the slots. (see + // MBasicBlock::NewAsmJS) + size_t slot = 0; + for (MPhiIterator phi = phisBegin(); phi != phisEnd(); phi++, slot++) { MPhi *entryDef = *phi; - MDefinition *exitDef = pred->getSlot(entryDef->slot()); + MDefinition *exitDef = pred->getSlot(slot); // Assert that we already placed phis for each slot. JS_ASSERT(entryDef->block() == this); @@ -1073,8 +1052,8 @@ MBasicBlock::setBackedgeAsmJS(MBasicBlock *pred) // MBasicBlock::NewAsmJS calls reserveLength(2) for loop header phis. entryDef->addInput(exitDef); - JS_ASSERT(entryDef->slot() < pred->stackDepth()); - setSlot(entryDef->slot(), entryDef); + MOZ_ASSERT(slot < pred->stackDepth()); + setSlot(slot, entryDef); } // We are now a loop header proper @@ -1187,13 +1166,23 @@ MBasicBlock::removePredecessor(MBasicBlock *pred) void MBasicBlock::inheritPhis(MBasicBlock *header) { - for (MPhiIterator iter = header->phisBegin(); iter != header->phisEnd(); iter++) { - MPhi *phi = *iter; - JS_ASSERT(phi->numOperands() == 2); + MResumePoint *headerRp = header->entryResumePoint(); + size_t stackDepth = headerRp->numOperands(); + for (size_t slot = 0; slot < stackDepth; slot++) { + MDefinition *exitDef = getSlot(slot); + MDefinition *loopDef = headerRp->getOperand(slot); + if (!loopDef->isPhi()) { + MOZ_ASSERT(loopDef->block()->id() < header->id()); + MOZ_ASSERT(loopDef == exitDef); + continue; + } + + // Phis are allocated by NewPendingLoopHeader. + MPhi *phi = loopDef->toPhi(); + MOZ_ASSERT(phi->numOperands() == 2); // The entry definition is always the leftmost input to the phi. MDefinition *entryDef = phi->getOperand(0); - MDefinition *exitDef = getSlot(phi->slot()); if (entryDef != exitDef) continue; @@ -1201,10 +1190,60 @@ MBasicBlock::inheritPhis(MBasicBlock *header) // If the entryDef is the same as exitDef, then we must propagate the // phi down to this successor. This chance was missed as part of // setBackedge() because exits are not captured in resume points. - setSlot(phi->slot(), phi); + setSlot(slot, phi); } } +bool +MBasicBlock::inheritPhisFromBackedge(MBasicBlock *backedge, bool *hadTypeChange) +{ + // We must be a pending loop header + MOZ_ASSERT(kind_ == PENDING_LOOP_HEADER); + + size_t stackDepth = entryResumePoint()->numOperands(); + for (size_t slot = 0; slot < stackDepth; slot++) { + // Get the value stack-slot of the back edge. + MDefinition *exitDef = backedge->getSlot(slot); + + // Get the value of the loop header. + MDefinition *loopDef = entryResumePoint()->getOperand(slot); + if (!loopDef->isPhi()) { + // If we are finishing a pending loop header, then we need to ensure + // that all operands are phis. This is usualy the case, except for + // object/arrays build with generators, in which case we share the + // same allocations across all blocks. + MOZ_ASSERT(loopDef->block()->id() < id()); + MOZ_ASSERT(loopDef == exitDef); + continue; + } + + // Phis are allocated by NewPendingLoopHeader. + MPhi *entryDef = loopDef->toPhi(); + MOZ_ASSERT(entryDef->block() == this); + + if (entryDef == exitDef) { + // If the exit def is the same as the entry def, make a redundant + // phi. Since loop headers have exactly two incoming edges, we + // know that that's just the first input. + // + // Note that we eliminate later rather than now, to avoid any + // weirdness around pending continue edges which might still hold + // onto phis. + exitDef = entryDef->getOperand(0); + } + + bool typeChange = false; + + if (!entryDef->addInputSlow(exitDef, &typeChange)) + return false; + + *hadTypeChange |= typeChange; + setSlot(slot, entryDef); + } + + return true; +} + bool MBasicBlock::specializePhis() { diff --git a/js/src/jit/MIRGraph.h b/js/src/jit/MIRGraph.h index 1fac09d7507..e8a52f76acc 100644 --- a/js/src/jit/MIRGraph.h +++ b/js/src/jit/MIRGraph.h @@ -207,6 +207,9 @@ class MBasicBlock : public TempObject, public InlineListNode // Propagates phis placed in a loop header down to this successor block. void inheritPhis(MBasicBlock *header); + // Propagates backedge slots into phis operands of the loop header. + bool inheritPhisFromBackedge(MBasicBlock *backedge, bool *hadTypeChange); + // Compute the types for phis in this block according to their inputs. bool specializePhis(); diff --git a/js/src/jit/shared/Lowering-shared.cpp b/js/src/jit/shared/Lowering-shared.cpp index ca3b90dac90..ad0f209356c 100644 --- a/js/src/jit/shared/Lowering-shared.cpp +++ b/js/src/jit/shared/Lowering-shared.cpp @@ -82,7 +82,7 @@ LRecoverInfo::OperandIter::canOptimizeOutIfUnused() if ((ins->isUnused() || ins->type() == MIRType_MagicOptimizedOut) && (*it_)->isResumePoint()) { - return (*it_)->block()->info().canOptimizeOutSlot(op_); + return !(*it_)->toResumePoint()->isObservableOperand(op_); } return true; From 8552c496da16bee0f1d3b5edd00ab291c22b435b Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 13 Mar 2014 22:35:45 -0700 Subject: [PATCH 36/70] Bug 983538 (part 1) - Convert mfbt/SplayTree.h to Mozilla style. r=froydnj. --HG-- extra : rebase_source : 9143ee56f94e7c6e0b1e1a70c4dc26c90c69d84a --- mfbt/SplayTree.h | 435 ++++++++++++++++++++++++----------------------- 1 file changed, 224 insertions(+), 211 deletions(-) diff --git a/mfbt/SplayTree.h b/mfbt/SplayTree.h index e2a0a29dedd..a6d692266f7 100644 --- a/mfbt/SplayTree.h +++ b/mfbt/SplayTree.h @@ -23,18 +23,20 @@ class SplayTree; template class SplayTreeNode { - public: - template - friend class SplayTree; +public: + template + friend class SplayTree; - SplayTreeNode() - : left(nullptr), right(nullptr), parent(nullptr) - {} + SplayTreeNode() + : mLeft(nullptr) + , mRight(nullptr) + , mParent(nullptr) + {} - private: - T* left; - T* right; - T* parent; +private: + T* mLeft; + T* mRight; + T* mParent; }; @@ -51,231 +53,242 @@ class SplayTreeNode template class SplayTree { - T* root; + T* mRoot; - public: - SplayTree() - : root(nullptr) - {} +public: + SplayTree() + : mRoot(nullptr) + {} - bool empty() const { - return !root; + bool empty() const + { + return !mRoot; + } + + T* find(const T& aValue) + { + if (empty()) { + return nullptr; } - T* find(const T& v) - { - if (empty()) - return nullptr; + T* last = lookup(aValue); + splay(last); + checkCoherency(mRoot, nullptr); + return Comparator::compare(aValue, *last) == 0 ? last : nullptr; + } - T* last = lookup(v); - splay(last); - checkCoherency(root, nullptr); - return Comparator::compare(v, *last) == 0 ? last : nullptr; - } + bool insert(T* aValue) + { + MOZ_ASSERT(!find(*aValue), "Duplicate elements are not allowed."); - bool insert(T* v) - { - MOZ_ASSERT(!find(*v), "Duplicate elements are not allowed."); - - if (!root) { - root = v; - return true; - } - T* last = lookup(*v); - int cmp = Comparator::compare(*v, *last); - - T** parentPointer = (cmp < 0) ? &last->left : &last->right; - MOZ_ASSERT(!*parentPointer); - *parentPointer = v; - v->parent = last; - - splay(v); - checkCoherency(root, nullptr); + if (!mRoot) { + mRoot = aValue; return true; } + T* last = lookup(*aValue); + int cmp = Comparator::compare(*aValue, *last); - T* remove(const T& v) - { - T* last = lookup(v); - MOZ_ASSERT(last, "This tree must contain the element being removed."); - MOZ_ASSERT(Comparator::compare(v, *last) == 0); + T** parentPointer = (cmp < 0) ? &last->mLeft : &last->mRight; + MOZ_ASSERT(!*parentPointer); + *parentPointer = aValue; + aValue->mParent = last; - // Splay the tree so that the item to remove is the root. - splay(last); - MOZ_ASSERT(last == root); + splay(aValue); + checkCoherency(mRoot, nullptr); + return true; + } - // Find another node which can be swapped in for the root: either the - // rightmost child of the root's left, or the leftmost child of the - // root's right. - T* swap; - T* swapChild; - if (root->left) { - swap = root->left; - while (swap->right) - swap = swap->right; - swapChild = swap->left; - } else if (root->right) { - swap = root->right; - while (swap->left) - swap = swap->left; - swapChild = swap->right; + T* remove(const T& aValue) + { + T* last = lookup(aValue); + MOZ_ASSERT(last, "This tree must contain the element being removed."); + MOZ_ASSERT(Comparator::compare(aValue, *last) == 0); + + // Splay the tree so that the item to remove is the root. + splay(last); + MOZ_ASSERT(last == mRoot); + + // Find another node which can be swapped in for the root: either the + // rightmost child of the root's left, or the leftmost child of the + // root's right. + T* swap; + T* swapChild; + if (mRoot->mLeft) { + swap = mRoot->mLeft; + while (swap->mRight) { + swap = swap->mRight; + } + swapChild = swap->mLeft; + } else if (mRoot->mRight) { + swap = mRoot->mRight; + while (swap->mLeft) { + swap = swap->mLeft; + } + swapChild = swap->mRight; + } else { + T* result = mRoot; + mRoot = nullptr; + return result; + } + + // The selected node has at most one child, in swapChild. Detach it + // from the subtree by replacing it with that child. + if (swap == swap->mParent->mLeft) { + swap->mParent->mLeft = swapChild; + } else { + swap->mParent->mRight = swapChild; + } + if (swapChild) { + swapChild->mParent = swap->mParent; + } + + // Make the selected node the new root. + mRoot = swap; + mRoot->mParent = nullptr; + mRoot->mLeft = last->mLeft; + mRoot->mRight = last->mRight; + if (mRoot->mLeft) { + mRoot->mLeft->mParent = mRoot; + } + if (mRoot->mRight) { + mRoot->mRight->mParent = mRoot; + } + + checkCoherency(mRoot, nullptr); + return last; + } + + T* removeMin() + { + MOZ_ASSERT(mRoot, "No min to remove!"); + + T* min = mRoot; + while (min->mLeft) { + min = min->mLeft; + } + return remove(*min); + } + +private: + /** + * Returns the node in this comparing equal to |aValue|, or a node just + * greater or just less than |aValue| if there is no such node. + */ + T* lookup(const T& aValue) + { + MOZ_ASSERT(!empty()); + + T* node = mRoot; + T* parent; + do { + parent = node; + int c = Comparator::compare(aValue, *node); + if (c == 0) { + return node; + } else if (c < 0) { + node = node->mLeft; } else { - T* result = root; - root = nullptr; - return result; + node = node->mRight; } + } while (node); + return parent; + } - // The selected node has at most one child, in swapChild. Detach it - // from the subtree by replacing it with that child. - if (swap == swap->parent->left) - swap->parent->left = swapChild; - else - swap->parent->right = swapChild; - if (swapChild) - swapChild->parent = swap->parent; + /** + * Rotate the tree until |node| is at the root of the tree. Performing + * the rotations in this fashion preserves the amortized balancing of + * the tree. + */ + void splay(T* aNode) + { + MOZ_ASSERT(aNode); - // Make the selected node the new root. - root = swap; - root->parent = nullptr; - root->left = last->left; - root->right = last->right; - if (root->left) { - root->left->parent = root; + while (aNode != mRoot) { + T* parent = aNode->mParent; + if (parent == mRoot) { + // Zig rotation. + rotate(aNode); + MOZ_ASSERT(aNode == mRoot); + return; } - if (root->right) { - root->right->parent = root; - } - - checkCoherency(root, nullptr); - return last; - } - - T* removeMin() - { - MOZ_ASSERT(root, "No min to remove!"); - - T* min = root; - while (min->left) - min = min->left; - return remove(*min); - } - - private: - /** - * Returns the node in this comparing equal to |v|, or a node just greater or - * just less than |v| if there is no such node. - */ - T* lookup(const T& v) - { - MOZ_ASSERT(!empty()); - - T* node = root; - T* parent; - do { - parent = node; - int c = Comparator::compare(v, *node); - if (c == 0) - return node; - else if (c < 0) - node = node->left; - else - node = node->right; - } while (node); - return parent; - } - - /** - * Rotate the tree until |node| is at the root of the tree. Performing - * the rotations in this fashion preserves the amortized balancing of - * the tree. - */ - void splay(T* node) - { - MOZ_ASSERT(node); - - while (node != root) { - T* parent = node->parent; - if (parent == root) { - // Zig rotation. - rotate(node); - MOZ_ASSERT(node == root); - return; - } - T* grandparent = parent->parent; - if ((parent->left == node) == (grandparent->left == parent)) { - // Zig-zig rotation. - rotate(parent); - rotate(node); - } else { - // Zig-zag rotation. - rotate(node); - rotate(node); - } - } - } - - void rotate(T* node) - { - // Rearrange nodes so that node becomes the parent of its current - // parent, while preserving the sortedness of the tree. - T* parent = node->parent; - if (parent->left == node) { - // x y - // y c ==> a x - // a b b c - parent->left = node->right; - if (node->right) - node->right->parent = parent; - node->right = parent; + T* grandparent = parent->mParent; + if ((parent->mLeft == aNode) == (grandparent->mLeft == parent)) { + // Zig-zig rotation. + rotate(parent); + rotate(aNode); } else { - MOZ_ASSERT(parent->right == node); - // x y - // a y ==> x c - // b c a b - parent->right = node->left; - if (node->left) - node->left->parent = parent; - node->left = parent; - } - node->parent = parent->parent; - parent->parent = node; - if (T* grandparent = node->parent) { - if (grandparent->left == parent) - grandparent->left = node; - else - grandparent->right = node; - } else { - root = node; + // Zig-zag rotation. + rotate(aNode); + rotate(aNode); } } + } - T* checkCoherency(T* node, T* minimum) - { + void rotate(T* aNode) + { + // Rearrange nodes so that aNode becomes the parent of its current + // parent, while preserving the sortedness of the tree. + T* parent = aNode->mParent; + if (parent->mLeft == aNode) { + // x y + // y c ==> a x + // a b b c + parent->mLeft = aNode->mRight; + if (aNode->mRight) { + aNode->mRight->mParent = parent; + } + aNode->mRight = parent; + } else { + MOZ_ASSERT(parent->mRight == aNode); + // x y + // a y ==> x c + // b c a b + parent->mRight = aNode->mLeft; + if (aNode->mLeft) { + aNode->mLeft->mParent = parent; + } + aNode->mLeft = parent; + } + aNode->mParent = parent->mParent; + parent->mParent = aNode; + if (T* grandparent = aNode->mParent) { + if (grandparent->mLeft == parent) { + grandparent->mLeft = aNode; + } else { + grandparent->mRight = aNode; + } + } else { + mRoot = aNode; + } + } + + T* checkCoherency(T* aNode, T* aMinimum) + { #ifdef DEBUG - MOZ_ASSERT_IF(root, !root->parent); - if (!node) { - MOZ_ASSERT(!root); - return nullptr; - } - MOZ_ASSERT_IF(!node->parent, node == root); - MOZ_ASSERT_IF(minimum, Comparator::compare(*minimum, *node) < 0); - if (node->left) { - MOZ_ASSERT(node->left->parent == node); - T* leftMaximum = checkCoherency(node->left, minimum); - MOZ_ASSERT(Comparator::compare(*leftMaximum, *node) < 0); - } - if (node->right) { - MOZ_ASSERT(node->right->parent == node); - return checkCoherency(node->right, node); - } - return node; -#else + MOZ_ASSERT_IF(mRoot, !mRoot->mParent); + if (!aNode) { + MOZ_ASSERT(!mRoot); return nullptr; -#endif } + MOZ_ASSERT_IF(!aNode->mParent, aNode == mRoot); + MOZ_ASSERT_IF(aMinimum, Comparator::compare(*aMinimum, *aNode) < 0); + if (aNode->mLeft) { + MOZ_ASSERT(aNode->mLeft->mParent == aNode); + T* leftMaximum = checkCoherency(aNode->mLeft, aMinimum); + MOZ_ASSERT(Comparator::compare(*leftMaximum, *aNode) < 0); + } + if (aNode->mRight) { + MOZ_ASSERT(aNode->mRight->mParent == aNode); + return checkCoherency(aNode->mRight, aNode); + } + return aNode; +#else + return nullptr; +#endif + } - SplayTree(const SplayTree&) MOZ_DELETE; - void operator=(const SplayTree&) MOZ_DELETE; + SplayTree(const SplayTree&) MOZ_DELETE; + void operator=(const SplayTree&) MOZ_DELETE; }; } /* namespace mozilla */ From 84887415e0c36213b70085fe77f8ec217cd2c95e Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 13 Mar 2014 22:42:59 -0700 Subject: [PATCH 37/70] Bug 983538 (part 2) - Update mfbt/STYLE. r=froydnj. --HG-- extra : rebase_source : dac8a5683794cc838966afaf8c037aa466d44410 --- mfbt/STYLE | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mfbt/STYLE b/mfbt/STYLE index bdae22a4101..d575f928c04 100644 --- a/mfbt/STYLE +++ b/mfbt/STYLE @@ -1,3 +1,7 @@ +New files added to MFBT should follow standard Mozilla style, and existing +files are in the process of being converted. Nonetheless, some files still use +the old style, which is described below. + = mfbt style rules = == Line length == From 865fe21cf3413d01d0a423403ebbef3337ed984b Mon Sep 17 00:00:00 2001 From: Inanc Seylan Date: Thu, 15 May 2014 23:40:44 -0700 Subject: [PATCH 38/70] Bug 1009967 - Add recover functionality for BitXor; r=nbp --- .../tests/ion/dce-with-rinstructions.js | 19 ++++++++++++++ js/src/jit/MIR.h | 5 ++++ js/src/jit/Recover.cpp | 26 +++++++++++++++++++ js/src/jit/Recover.h | 13 ++++++++++ 4 files changed, 63 insertions(+) diff --git a/js/src/jit-test/tests/ion/dce-with-rinstructions.js b/js/src/jit-test/tests/ion/dce-with-rinstructions.js index ae50a818df2..60207a8d558 100644 --- a/js/src/jit-test/tests/ion/dce-with-rinstructions.js +++ b/js/src/jit-test/tests/ion/dce-with-rinstructions.js @@ -50,6 +50,25 @@ function rbitor_object(i) { return i; } +var uceFault_bitxor_number = eval(uneval(uceFault).replace('uceFault', 'uceFault_bitxor_number')); +function rbitxor_number(i) { + var x = 1 ^ i; + if (uceFault_bitxor_number(i) || uceFault_bitxor_number(i)) + assertEq(x, 98 /* = 1 XOR 99 */); + return i; +} + +var uceFault_bitxor_object = eval(uneval(uceFault).replace('uceFault', 'uceFault_bitxor_object')); +function rbitxor_object(i) { + var t = i; + var o = { valueOf: function () { return t; } }; + var x = 1 ^ o; /* computed with t == i, not 1000 */ + t = 1000; + if (uceFault_bitxor_object(i) || uceFault_bitxor_object(i)) + assertEq(x, 98 /* = 1 XOR 99 */); + return i; +} + var uceFault_add_number = eval(uneval(uceFault).replace('uceFault', 'uceFault_add_number')); function radd_number(i) { var x = 1 + i; diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 14efcd7ab14..ae8e73f1c23 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -3536,6 +3536,11 @@ class MBitXor : public MBinaryBitwiseInstruction return this; } void computeRange(TempAllocator &alloc); + + bool writeRecoverData(CompactBufferWriter &writer) const; + bool canRecoverOnBailout() const { + return specialization_ < MIRType_Object; + } }; class MShiftInstruction diff --git a/js/src/jit/Recover.cpp b/js/src/jit/Recover.cpp index d40d1aa0a2b..f6c78914d0a 100644 --- a/js/src/jit/Recover.cpp +++ b/js/src/jit/Recover.cpp @@ -197,6 +197,32 @@ RBitNot::recover(JSContext *cx, SnapshotIterator &iter) const return true; } +bool +MBitXor::writeRecoverData(CompactBufferWriter &writer) const +{ + MOZ_ASSERT(canRecoverOnBailout()); + writer.writeUnsigned(uint32_t(RInstruction::Recover_BitXor)); + return true; +} + +RBitXor::RBitXor(CompactBufferReader &reader) +{ } + +bool +RBitXor::recover(JSContext *cx, SnapshotIterator &iter) const +{ + RootedValue lhs(cx, iter.read()); + RootedValue rhs(cx, iter.read()); + + int32_t result; + if (!js::BitXor(cx, lhs, rhs, &result)) + return false; + + RootedValue rootedResult(cx, js::Int32Value(result)); + iter.storeInstructionResult(rootedResult); + return true; +} + bool MNewObject::writeRecoverData(CompactBufferWriter &writer) const { diff --git a/js/src/jit/Recover.h b/js/src/jit/Recover.h index a11d8452d08..373b01cc291 100644 --- a/js/src/jit/Recover.h +++ b/js/src/jit/Recover.h @@ -20,6 +20,7 @@ namespace jit { _(ResumePoint) \ _(BitNot) \ _(BitOr) \ + _(BitXor) \ _(Add) \ _(NewObject) \ _(NewDerivedTypedObject) @@ -102,6 +103,18 @@ class RBitNot MOZ_FINAL : public RInstruction bool recover(JSContext *cx, SnapshotIterator &iter) const; }; +class RBitXor MOZ_FINAL : public RInstruction +{ + public: + RINSTRUCTION_HEADER_(BitXor) + + virtual uint32_t numOperands() const { + return 2; + } + + bool recover(JSContext *cx, SnapshotIterator &iter) const; +}; + class RAdd MOZ_FINAL : public RInstruction { private: From 0354862afe74afe294b1f1973834528f0ef3e423 Mon Sep 17 00:00:00 2001 From: David Rajchenbach-Teller Date: Thu, 15 May 2014 03:12:00 +0200 Subject: [PATCH 39/70] Bug 1010289 - Experiments.jsm should now handle its errors. r=bsmedberg --- browser/experiments/Experiments.jsm | 38 ++++++++++--------- browser/experiments/test/xpcshell/test_api.js | 2 +- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/browser/experiments/Experiments.jsm b/browser/experiments/Experiments.jsm index 6ff3ed4221c..50ad54b2920 100644 --- a/browser/experiments/Experiments.jsm +++ b/browser/experiments/Experiments.jsm @@ -336,11 +336,14 @@ Experiments.Policy.prototype = { }; function AlreadyShutdownError(message="already shut down") { + Error.call(this, message); + let error = new Error(); this.name = "AlreadyShutdownError"; this.message = message; + this.stack = error.stack; } -AlreadyShutdownError.prototype = new Error(); +AlreadyShutdownError.prototype = Object.create(Error.prototype); AlreadyShutdownError.prototype.constructor = AlreadyShutdownError; /** @@ -403,22 +406,19 @@ Experiments.Experiments.prototype = { this._registerWithAddonManager(); - let deferred = Promise.defer(); - this._loadTask = this._loadFromCache(); - this._loadTask.then( + + return this._loadTask.then( () => { this._log.trace("_loadTask finished ok"); this._loadTask = null; - this._run().then(deferred.resolve, deferred.reject); + return this._run(); }, (e) => { this._log.error("_loadFromCache caught error: " + e); - deferred.reject(e); + throw e; } ); - - return deferred.promise; }, /** @@ -666,18 +666,22 @@ Experiments.Experiments.prototype = { this._log.trace("_run"); this._checkForShutdown(); if (!this._mainTask) { - this._mainTask = Task.spawn(this._main.bind(this)); - this._mainTask.then( - () => { - this._log.trace("_main finished, scheduling next run"); - this._mainTask = null; - this._scheduleNextRun(); - }, - (e) => { + this._mainTask = Task.spawn(function*() { + try { + yield this._main(); + } catch (e) { this._log.error("_main caught error: " + e); + return; + } finally { this._mainTask = null; } - ); + this._log.trace("_main finished, scheduling next run"); + try { + yield this._scheduleNextRun(); + } catch (ex if ex instanceof AlreadyShutdownError) { + // We error out of tasks after shutdown via that exception. + } + }.bind(this)); } return this._mainTask; }, diff --git a/browser/experiments/test/xpcshell/test_api.js b/browser/experiments/test/xpcshell/test_api.js index 2c5be3532ca..e599cafa98b 100644 --- a/browser/experiments/test/xpcshell/test_api.js +++ b/browser/experiments/test/xpcshell/test_api.js @@ -1564,7 +1564,7 @@ add_task(function* test_foreignUninstallAndRestart() { Assert.ok(!experimentList[0].active, "Experiment 1 should not be active anymore."); // Fake restart behaviour. - experiments.uninit(); + yield experiments.uninit(); restartManager(); experiments = new Experiments.Experiments(gPolicy); yield experiments.updateManifest(); From b8ba3b5ea7aadee633b5b5a0e648e3f028c73d1b Mon Sep 17 00:00:00 2001 From: "Nils Ohlmeier [:drno]" Date: Wed, 14 May 2014 21:17:00 +0200 Subject: [PATCH 40/70] Bug 1010670 - allow test_peerConnection_basic* to be executed by steeplechase. r=ted --- .../tests/mochitest/test_peerConnection_basicAudioVideo.html | 4 ++-- .../test_peerConnection_basicAudioVideoCombined.html | 4 ++-- dom/media/tests/mochitest/test_peerConnection_basicVideo.html | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dom/media/tests/mochitest/test_peerConnection_basicAudioVideo.html b/dom/media/tests/mochitest/test_peerConnection_basicAudioVideo.html index 263cc80577c..5b7fc414332 100644 --- a/dom/media/tests/mochitest/test_peerConnection_basicAudioVideo.html +++ b/dom/media/tests/mochitest/test_peerConnection_basicAudioVideo.html @@ -18,8 +18,8 @@ }); var test; - runTest(function () { - test = new PeerConnectionTest(); + runTest(function (options) { + test = new PeerConnectionTest(options); test.setMediaConstraints([{audio: true}, {video: true}], [{audio: true}, {video: true}]); test.run(); diff --git a/dom/media/tests/mochitest/test_peerConnection_basicAudioVideoCombined.html b/dom/media/tests/mochitest/test_peerConnection_basicAudioVideoCombined.html index 3a2d9a06965..db7980d4b6f 100644 --- a/dom/media/tests/mochitest/test_peerConnection_basicAudioVideoCombined.html +++ b/dom/media/tests/mochitest/test_peerConnection_basicAudioVideoCombined.html @@ -18,8 +18,8 @@ }); var test; - runTest(function () { - test = new PeerConnectionTest(); + runTest(function (options) { + test = new PeerConnectionTest(options); test.setMediaConstraints([{audio: true, video: true}], [{audio: true, video: true}]); test.run(); diff --git a/dom/media/tests/mochitest/test_peerConnection_basicVideo.html b/dom/media/tests/mochitest/test_peerConnection_basicVideo.html index f96163e7c25..a618175132e 100644 --- a/dom/media/tests/mochitest/test_peerConnection_basicVideo.html +++ b/dom/media/tests/mochitest/test_peerConnection_basicVideo.html @@ -17,8 +17,8 @@ }); var test; - runTest(function () { - test = new PeerConnectionTest(); + runTest(function (options) { + test = new PeerConnectionTest(options); test.setMediaConstraints([{video: true}], [{video: true}]); test.run(); }); From 042ab281b5265b0ca25140d0f0b8e7982fade221 Mon Sep 17 00:00:00 2001 From: Sam Penrose Date: Thu, 15 May 2014 16:52:52 -0700 Subject: [PATCH 41/70] Bug 1010623 - sign out when password was reset on web. r=jedp --- services/fxaccounts/FxAccounts.jsm | 10 +++++ services/fxaccounts/FxAccountsClient.jsm | 18 +++++++++ .../tests/xpcshell/test_accounts.js | 40 +++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/services/fxaccounts/FxAccounts.jsm b/services/fxaccounts/FxAccounts.jsm index fdf466bba6c..509b33da0a2 100644 --- a/services/fxaccounts/FxAccounts.jsm +++ b/services/fxaccounts/FxAccounts.jsm @@ -25,6 +25,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "jwcrypto", // All properties exposed by the public FxAccounts API. let publicProperties = [ + "accountStatus", "getAccountsClient", "getAccountsSignInURI", "getAccountsSignUpURI", @@ -511,6 +512,15 @@ FxAccountsInternal.prototype = { this.currentAccountState = new AccountState(this); }, + accountStatus: function accountStatus() { + return this.currentAccountState.getUserAccountData().then(data => { + if (!data) { + return false; + } + return this.fxAccountsClient.accountStatus(data.uid); + }); + }, + signOut: function signOut(localOnly) { let currentState = this.currentAccountState; let sessionToken; diff --git a/services/fxaccounts/FxAccountsClient.jsm b/services/fxaccounts/FxAccountsClient.jsm index ba96041a679..b46ed4a3c7d 100644 --- a/services/fxaccounts/FxAccountsClient.jsm +++ b/services/fxaccounts/FxAccountsClient.jsm @@ -288,6 +288,24 @@ this.FxAccountsClient.prototype = { ); }, + /** + * Given the uid of an existing account (not an arbitrary email), ask + * the server if it still exists via /account/status. + * + * Used for differentiating between password change and account deletion. + */ + accountStatus: function(uid) { + return this._request("/account/status?uid="+uid, "GET").then( + (result) => { + return result.exists; + }, + (error) => { + log.error("accountStatus failed with: " + error); + return Promise.reject(error); + } + ); + }, + /** * The FxA auth server expects requests to certain endpoints to be authorized using Hawk. * Hawk credentials are derived using shared secrets, which depend on the context diff --git a/services/fxaccounts/tests/xpcshell/test_accounts.js b/services/fxaccounts/tests/xpcshell/test_accounts.js index 66af7ccdefc..805b3e66319 100644 --- a/services/fxaccounts/tests/xpcshell/test_accounts.js +++ b/services/fxaccounts/tests/xpcshell/test_accounts.js @@ -41,6 +41,7 @@ function run_test() { function MockFxAccountsClient() { this._email = "nobody@example.com"; this._verified = false; + this._deletedOnServer = false; // for testing accountStatus // mock calls up to the auth server to determine whether the // user account has been verified @@ -57,6 +58,12 @@ function MockFxAccountsClient() { return deferred.promise; }; + this.accountStatus = function(uid) { + let deferred = Promise.defer(); + deferred.resolve(!!uid && (!this._deletedOnServer)); + return deferred.promise; + }; + this.accountKeys = function (keyFetchToken) { let deferred = Promise.defer(); @@ -505,6 +512,39 @@ add_task(function test_resend_email_not_signed_in() { do_throw("Should not be able to resend email when nobody is signed in"); }); +add_test(function test_accountStatus() { + let fxa = new MockFxAccounts(); + let alice = getTestUser("alice"); + + // If we have no user, we have no account server-side + fxa.accountStatus().then( + (result) => { + do_check_false(result); + } + ).then( + () => { + fxa.setSignedInUser(alice).then( + () => { + fxa.accountStatus().then( + (result) => { + // FxAccounts.accountStatus() should match Client.accountStatus() + do_check_true(result); + fxa.internal.fxAccountsClient._deletedOnServer = true; + fxa.accountStatus().then( + (result) => { + do_check_false(result); + fxa.internal.fxAccountsClient._deletedOnServer = false; + run_next_test(); + } + ); + } + ) + } + ); + } + ); +}); + add_test(function test_resend_email() { let fxa = new MockFxAccounts(); let alice = getTestUser("alice"); From 30cde70b294ce2b5ffa917f06752581f0c4220ef Mon Sep 17 00:00:00 2001 From: Chris Peterson Date: Wed, 14 May 2014 20:11:14 -0700 Subject: [PATCH 42/70] Bug 666646 - Fix warnings in toolkit/crashreporter and suppress MSVC warning in third-party Breakpad code. r=ted --- .../src/client/mac/handler/breakpad_nlist_64.cc | 4 ++-- .../google-breakpad/src/common/dwarf_cu_to_module.h | 1 - .../google-breakpad/src/processor/tokenize.cc | 6 ++---- toolkit/crashreporter/moz.build | 7 +++++++ toolkit/crashreporter/nsExceptionHandler.cpp | 2 +- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/breakpad_nlist_64.cc b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/breakpad_nlist_64.cc index fcb667e311c..f6e1d95abc1 100644 --- a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/breakpad_nlist_64.cc +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/breakpad_nlist_64.cc @@ -281,7 +281,7 @@ int __breakpad_fdnlist(int fd, nlist_type *list, const char **symbolNames, off_t sa; /* symbol address */ off_t ss; /* start of strings */ - register register_t n; + register_t n; if (*((unsigned int *)&buf) == magic) { if (lseek(fd, arch_offset, SEEK_SET) == -1) { return -1; @@ -354,7 +354,7 @@ int __breakpad_fdnlist(int fd, nlist_type *list, const char **symbolNames, // and look for a match while (n) { nlist_type space[BUFSIZ/sizeof (nlist_type)]; - register register_t m = sizeof (space); + register_t m = sizeof (space); if (n < m) m = n; diff --git a/toolkit/crashreporter/google-breakpad/src/common/dwarf_cu_to_module.h b/toolkit/crashreporter/google-breakpad/src/common/dwarf_cu_to_module.h index 85453316398..4f1f05801d0 100644 --- a/toolkit/crashreporter/google-breakpad/src/common/dwarf_cu_to_module.h +++ b/toolkit/crashreporter/google-breakpad/src/common/dwarf_cu_to_module.h @@ -221,7 +221,6 @@ class DwarfCUToModule: public dwarf2reader::RootDIEHandler { // Used internally by the handler. Full definitions are in // dwarf_cu_to_module.cc. - struct FilePrivate; struct Specification; struct CUContext; struct DIEContext; diff --git a/toolkit/crashreporter/google-breakpad/src/processor/tokenize.cc b/toolkit/crashreporter/google-breakpad/src/processor/tokenize.cc index a5b028e326d..f468120c0c2 100644 --- a/toolkit/crashreporter/google-breakpad/src/processor/tokenize.cc +++ b/toolkit/crashreporter/google-breakpad/src/processor/tokenize.cc @@ -62,10 +62,8 @@ bool Tokenize(char *line, } // If there's anything left, just add it as a single token. - if (!remaining > 0) { - if ((token = strtok_r(NULL, "\r\n", &save_ptr))) { - tokens->push_back(token); - } + if (remaining == 0 && (token = strtok_r(NULL, "\r\n", &save_ptr))) { + tokens->push_back(token); } return tokens->size() == static_cast(max_tokens); diff --git a/toolkit/crashreporter/moz.build b/toolkit/crashreporter/moz.build index e446902b1b6..fe8a2603922 100644 --- a/toolkit/crashreporter/moz.build +++ b/toolkit/crashreporter/moz.build @@ -103,3 +103,10 @@ LOCAL_INCLUDES += [ 'google-breakpad/src', ] +# Suppress warnings in third-party code. +if CONFIG['_MSC_VER']: + CXXFLAGS += [ + '-wd4005', # macro redefinition + ] + +FAIL_ON_WARNINGS = True diff --git a/toolkit/crashreporter/nsExceptionHandler.cpp b/toolkit/crashreporter/nsExceptionHandler.cpp index b1838d4b248..34e539827c3 100644 --- a/toolkit/crashreporter/nsExceptionHandler.cpp +++ b/toolkit/crashreporter/nsExceptionHandler.cpp @@ -773,7 +773,7 @@ bool MinidumpCallback( if (eventloopNestingLevel > 0) { unused << sys_write(fd, kEventLoopNestingLevelParameter, kEventLoopNestingLevelParameterLen); char buffer[16]; - XP_TTOA(eventloopNestingLevel, buffer, 10); + XP_TTOA(static_cast(eventloopNestingLevel), buffer, 10); unused << sys_write(fd, buffer, my_strlen(buffer)); unused << sys_write(fd, "\n", 1); } From 1c759c64ad9ed7041bfcdb335715adb90e5dc7cd Mon Sep 17 00:00:00 2001 From: Chris Peterson Date: Wed, 14 May 2014 20:15:36 -0700 Subject: [PATCH 43/70] Bug 1007708 - Fix uninitialized variable warnings in gfx/layers/opengl/CompositorOGL.cpp. r=mwoodrow --- gfx/layers/opengl/CompositorOGL.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gfx/layers/opengl/CompositorOGL.cpp b/gfx/layers/opengl/CompositorOGL.cpp index 79d0c348d2f..c9caf84fc02 100644 --- a/gfx/layers/opengl/CompositorOGL.cpp +++ b/gfx/layers/opengl/CompositorOGL.cpp @@ -920,7 +920,8 @@ static bool SetBlendMode(GLContext* aGL, gfx::CompositionOp aBlendMode, bool aIs dstBlend = LOCAL_GL_ONE_MINUS_SRC_ALPHA; break; default: - MOZ_ASSERT(0, "Unsupported blend mode!"); + MOZ_ASSERT_UNREACHABLE("Unsupported blend mode!"); + return false; } aGL->fBlendFuncSeparate(srcBlend, dstBlend, From 38e23a0e94258b96d9708abd276a24ffcfc0083e Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 15 May 2014 12:28:00 -0700 Subject: [PATCH 44/70] Bumping manifests a=b2g-bump --- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame/sources.xml | 2 +- b2g/config/hamachi/sources.xml | 2 +- b2g/config/inari/sources.xml | 2 +- b2g/config/leo/sources.xml | 2 +- b2g/config/mako/sources.xml | 2 +- b2g/config/wasabi/sources.xml | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index 0d98de6f7f5..f30ab0ec3b1 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -25,7 +25,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 2379a98f7cc..3166939bb73 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -20,7 +20,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 350f95842fd..b1af98b6f1d 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -22,7 +22,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index 0d98de6f7f5..f30ab0ec3b1 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -25,7 +25,7 @@ - + diff --git a/b2g/config/flame/sources.xml b/b2g/config/flame/sources.xml index 5c60c8fea02..be5a0cf4c69 100644 --- a/b2g/config/flame/sources.xml +++ b/b2g/config/flame/sources.xml @@ -21,7 +21,7 @@ - + diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml index 88859c95bc1..b94859d54a3 100644 --- a/b2g/config/hamachi/sources.xml +++ b/b2g/config/hamachi/sources.xml @@ -22,7 +22,7 @@ - + diff --git a/b2g/config/inari/sources.xml b/b2g/config/inari/sources.xml index 80e0eb11d6a..41b3586b4d5 100644 --- a/b2g/config/inari/sources.xml +++ b/b2g/config/inari/sources.xml @@ -24,7 +24,7 @@ - + diff --git a/b2g/config/leo/sources.xml b/b2g/config/leo/sources.xml index d4f2ca98e8c..e6d65957eab 100644 --- a/b2g/config/leo/sources.xml +++ b/b2g/config/leo/sources.xml @@ -22,7 +22,7 @@ - + diff --git a/b2g/config/mako/sources.xml b/b2g/config/mako/sources.xml index fb7d0acdc42..54f9ad73157 100644 --- a/b2g/config/mako/sources.xml +++ b/b2g/config/mako/sources.xml @@ -20,7 +20,7 @@ - + diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml index dda4c4482f7..a7105c533aa 100644 --- a/b2g/config/wasabi/sources.xml +++ b/b2g/config/wasabi/sources.xml @@ -22,7 +22,7 @@ - + From 07ea865113ac41d8bb478a633490efde70a3f959 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 15 May 2014 13:10:58 -0700 Subject: [PATCH 45/70] Bumping gaia.json for 2 gaia revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/200422910918 Author: Miller Medeiros Desc: Merge pull request #18847 from millermedeiros/951075-day-view-visual-refresh Bug 951075 - [Calendar] Day View_1.4 Visual Refresh r=gaye ======== https://hg.mozilla.org/integration/gaia-central/rev/43e7c7d69fd9 Author: Miller Medeiros Desc: Bug 951075 - [Calendar] Day View_1.4 Visual Refresh --- b2g/config/gaia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 9cfb15426cf..5734bc2b85a 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -4,6 +4,6 @@ "remote": "", "branch": "" }, - "revision": "7a8ed4b21466d68e078529a3737bc5677ba3692f", + "revision": "2004229109189e54ddb18b5ccb41b83e777e0fc4", "repo_path": "/integration/gaia-central" } From f6df001aeef2e6ba58f5f642b5b54d09e627215d Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 15 May 2014 13:16:31 -0700 Subject: [PATCH 46/70] Bumping manifests a=b2g-bump --- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame/sources.xml | 2 +- b2g/config/hamachi/sources.xml | 2 +- b2g/config/helix/sources.xml | 2 +- b2g/config/inari/sources.xml | 2 +- b2g/config/leo/sources.xml | 2 +- b2g/config/mako/sources.xml | 2 +- b2g/config/wasabi/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index f30ab0ec3b1..2726a9955f5 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 3166939bb73..9797ebc3383 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index b1af98b6f1d..0eae53834f6 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index f30ab0ec3b1..2726a9955f5 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame/sources.xml b/b2g/config/flame/sources.xml index be5a0cf4c69..ce83797eb96 100644 --- a/b2g/config/flame/sources.xml +++ b/b2g/config/flame/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml index b94859d54a3..600b4f3a6eb 100644 --- a/b2g/config/hamachi/sources.xml +++ b/b2g/config/hamachi/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/helix/sources.xml b/b2g/config/helix/sources.xml index 72be5c4f26a..0f248da84f0 100644 --- a/b2g/config/helix/sources.xml +++ b/b2g/config/helix/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/inari/sources.xml b/b2g/config/inari/sources.xml index 41b3586b4d5..146c3b41305 100644 --- a/b2g/config/inari/sources.xml +++ b/b2g/config/inari/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/leo/sources.xml b/b2g/config/leo/sources.xml index e6d65957eab..1f9e785db0e 100644 --- a/b2g/config/leo/sources.xml +++ b/b2g/config/leo/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/mako/sources.xml b/b2g/config/mako/sources.xml index 54f9ad73157..9e3b84d7b0a 100644 --- a/b2g/config/mako/sources.xml +++ b/b2g/config/mako/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml index a7105c533aa..ad2b11bfbda 100644 --- a/b2g/config/wasabi/sources.xml +++ b/b2g/config/wasabi/sources.xml @@ -17,7 +17,7 @@ - + From 3141f75b07300ebab1e6948576d8e7431b120f20 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 15 May 2014 14:12:56 -0700 Subject: [PATCH 47/70] Bumping manifests a=b2g-bump --- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/flame/sources.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 0eae53834f6..828b8f54490 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -110,7 +110,7 @@ - + diff --git a/b2g/config/flame/sources.xml b/b2g/config/flame/sources.xml index ce83797eb96..c24850ee41d 100644 --- a/b2g/config/flame/sources.xml +++ b/b2g/config/flame/sources.xml @@ -119,7 +119,7 @@ - + From c20156b1c14b64dbc89b53f8e5aab71eb2140901 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 15 May 2014 14:30:52 -0700 Subject: [PATCH 48/70] Bumping gaia.json for 4 gaia revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/da614aad0565 Author: Ryan VanderMeulen Desc: Merge pull request #19268 from crh0716/1010394 Bug 1010394 - Refresh the items when entering the carrier panel ======== https://hg.mozilla.org/integration/gaia-central/rev/ab1dda7ff939 Author: Arthur Chen Desc: Bug 1010394 - Refresh the items when entering the carrier panel ======== https://hg.mozilla.org/integration/gaia-central/rev/5c7f7082866b Author: Ryan VanderMeulen Desc: Merge pull request #19236 from frsela/STK/Bug1009188 Bug 1009188 - [STK] No help info required (code 13) terminal response is sent back when we click on Help button from the GET INKEY prompt ======== https://hg.mozilla.org/integration/gaia-central/rev/0c4a1ed71a06 Author: Fernando Rodriguez Sela Desc: Bug 1009188 - [STK] No help info required (code 13) terminal response is sent back when we click on Help button from the GET INKEY prompt --- b2g/config/gaia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 5734bc2b85a..ce3fc1c7df1 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -4,6 +4,6 @@ "remote": "", "branch": "" }, - "revision": "2004229109189e54ddb18b5ccb41b83e777e0fc4", + "revision": "da614aad0565a73873d14bd1c5935a4107dacc12", "repo_path": "/integration/gaia-central" } From feaab0d774491c987f688cf6ecc2697cd086f430 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 15 May 2014 14:33:05 -0700 Subject: [PATCH 49/70] Bumping manifests a=b2g-bump --- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame/sources.xml | 2 +- b2g/config/hamachi/sources.xml | 2 +- b2g/config/helix/sources.xml | 2 +- b2g/config/inari/sources.xml | 2 +- b2g/config/leo/sources.xml | 2 +- b2g/config/mako/sources.xml | 2 +- b2g/config/wasabi/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index 2726a9955f5..9bc40466e25 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 9797ebc3383..cd315991102 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 828b8f54490..38f887c9121 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index 2726a9955f5..9bc40466e25 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame/sources.xml b/b2g/config/flame/sources.xml index c24850ee41d..7574539b3f7 100644 --- a/b2g/config/flame/sources.xml +++ b/b2g/config/flame/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml index 600b4f3a6eb..2e0bc46e127 100644 --- a/b2g/config/hamachi/sources.xml +++ b/b2g/config/hamachi/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/helix/sources.xml b/b2g/config/helix/sources.xml index 0f248da84f0..42b2d08bba4 100644 --- a/b2g/config/helix/sources.xml +++ b/b2g/config/helix/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/inari/sources.xml b/b2g/config/inari/sources.xml index 146c3b41305..945f2287fca 100644 --- a/b2g/config/inari/sources.xml +++ b/b2g/config/inari/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/leo/sources.xml b/b2g/config/leo/sources.xml index 1f9e785db0e..35ffdd0365d 100644 --- a/b2g/config/leo/sources.xml +++ b/b2g/config/leo/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/mako/sources.xml b/b2g/config/mako/sources.xml index 9e3b84d7b0a..736f3f1e1f7 100644 --- a/b2g/config/mako/sources.xml +++ b/b2g/config/mako/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml index ad2b11bfbda..b3ce8577d6a 100644 --- a/b2g/config/wasabi/sources.xml +++ b/b2g/config/wasabi/sources.xml @@ -17,7 +17,7 @@ - + From d57479fe408e5ec3000da317483a9c11b4e56e84 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 15 May 2014 15:11:07 -0700 Subject: [PATCH 50/70] Bumping gaia.json for 4 gaia revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/b8c55b884564 Author: Kevin Grandon Desc: Merge pull request #19172 from KevinGrandon/bug_983616_search_marketplace_region Bug 983616 - [Search] Pass in region for faster marketplace searches ======== https://hg.mozilla.org/integration/gaia-central/rev/1e8b904bd489 Author: Kevin Grandon Desc: Bug 983616 - [Search] Pass in region for faster marketplace searches r=daleharvey ======== https://hg.mozilla.org/integration/gaia-central/rev/891a4fe89351 Author: Kevin Grandon Desc: Merge pull request #19132 from KevinGrandon/bug_1008630_system2_bootstrap_fixup Bug 1008630 - [System2] Bootstrap_test.js fixup ======== https://hg.mozilla.org/integration/gaia-central/rev/cea7da534858 Author: Kevin Grandon Desc: Bug 1008630 - [System2] Bootstrap_test.js fixup r=alive --- b2g/config/gaia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index ce3fc1c7df1..29065e1abc2 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -4,6 +4,6 @@ "remote": "", "branch": "" }, - "revision": "da614aad0565a73873d14bd1c5935a4107dacc12", + "revision": "b8c55b88456409b5e4f9a8b86d58314761f31f54", "repo_path": "/integration/gaia-central" } From b595d8c34d7ec66d2a091267f0a0b1eb038b449d Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 15 May 2014 15:16:30 -0700 Subject: [PATCH 51/70] Bumping manifests a=b2g-bump --- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame/sources.xml | 2 +- b2g/config/hamachi/sources.xml | 2 +- b2g/config/helix/sources.xml | 2 +- b2g/config/inari/sources.xml | 2 +- b2g/config/leo/sources.xml | 2 +- b2g/config/mako/sources.xml | 2 +- b2g/config/wasabi/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index 9bc40466e25..0777bb1a0e8 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index cd315991102..80ad8063046 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 38f887c9121..5e6c2445c69 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index 9bc40466e25..0777bb1a0e8 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame/sources.xml b/b2g/config/flame/sources.xml index 7574539b3f7..2acc7eaafef 100644 --- a/b2g/config/flame/sources.xml +++ b/b2g/config/flame/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml index 2e0bc46e127..c46a1525fbc 100644 --- a/b2g/config/hamachi/sources.xml +++ b/b2g/config/hamachi/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/helix/sources.xml b/b2g/config/helix/sources.xml index 42b2d08bba4..0287b9e4bcb 100644 --- a/b2g/config/helix/sources.xml +++ b/b2g/config/helix/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/inari/sources.xml b/b2g/config/inari/sources.xml index 945f2287fca..015b6a5948c 100644 --- a/b2g/config/inari/sources.xml +++ b/b2g/config/inari/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/leo/sources.xml b/b2g/config/leo/sources.xml index 35ffdd0365d..0bc3d62d57b 100644 --- a/b2g/config/leo/sources.xml +++ b/b2g/config/leo/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/mako/sources.xml b/b2g/config/mako/sources.xml index 736f3f1e1f7..de3670f76c4 100644 --- a/b2g/config/mako/sources.xml +++ b/b2g/config/mako/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml index b3ce8577d6a..d869af2f061 100644 --- a/b2g/config/wasabi/sources.xml +++ b/b2g/config/wasabi/sources.xml @@ -17,7 +17,7 @@ - + From 516e912ab7f09d6c0da6fa08ce9d75edc2886cea Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 15 May 2014 15:25:51 -0700 Subject: [PATCH 52/70] Bumping gaia.json for 2 gaia revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/2314fc146434 Author: Etienne Segonzac Desc: Merge pull request #19298 from etiennesegonzac/bug-1011146 Bug 1011146 - Exit youtube fullscreen videos before doing an edge gesture transition r=vingtetun ======== https://hg.mozilla.org/integration/gaia-central/rev/061b320a3217 Author: Etienne Segonzac Desc: Bug 1011146 - Exit youtube fullscreen videos before doing an edge gesture transition. --- b2g/config/gaia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 29065e1abc2..f501f92b479 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -4,6 +4,6 @@ "remote": "", "branch": "" }, - "revision": "b8c55b88456409b5e4f9a8b86d58314761f31f54", + "revision": "2314fc1464347650b4af19c9e34db09d958af4a8", "repo_path": "/integration/gaia-central" } From 8bcbc598d0d8001c0730510e9cc3034dcc852245 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 15 May 2014 15:31:20 -0700 Subject: [PATCH 53/70] Bumping manifests a=b2g-bump --- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame/sources.xml | 2 +- b2g/config/hamachi/sources.xml | 2 +- b2g/config/helix/sources.xml | 2 +- b2g/config/inari/sources.xml | 2 +- b2g/config/leo/sources.xml | 2 +- b2g/config/mako/sources.xml | 2 +- b2g/config/wasabi/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index 0777bb1a0e8..ce13b5bb821 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 80ad8063046..d3b12d002e4 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 5e6c2445c69..262875eab23 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index 0777bb1a0e8..ce13b5bb821 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame/sources.xml b/b2g/config/flame/sources.xml index 2acc7eaafef..19b08730e3f 100644 --- a/b2g/config/flame/sources.xml +++ b/b2g/config/flame/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml index c46a1525fbc..d5676bb7d69 100644 --- a/b2g/config/hamachi/sources.xml +++ b/b2g/config/hamachi/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/helix/sources.xml b/b2g/config/helix/sources.xml index 0287b9e4bcb..4aaa09cc783 100644 --- a/b2g/config/helix/sources.xml +++ b/b2g/config/helix/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/inari/sources.xml b/b2g/config/inari/sources.xml index 015b6a5948c..c14885629f2 100644 --- a/b2g/config/inari/sources.xml +++ b/b2g/config/inari/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/leo/sources.xml b/b2g/config/leo/sources.xml index 0bc3d62d57b..7d7b67f48a7 100644 --- a/b2g/config/leo/sources.xml +++ b/b2g/config/leo/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/mako/sources.xml b/b2g/config/mako/sources.xml index de3670f76c4..1288186835f 100644 --- a/b2g/config/mako/sources.xml +++ b/b2g/config/mako/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml index d869af2f061..8420df71d95 100644 --- a/b2g/config/wasabi/sources.xml +++ b/b2g/config/wasabi/sources.xml @@ -17,7 +17,7 @@ - + From 3536fb59fbf1a5ce5f1dad770beacae095db1f3c Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 15 May 2014 16:05:52 -0700 Subject: [PATCH 54/70] Bumping gaia.json for 2 gaia revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/26a239d80401 Author: Etienne Segonzac Desc: Merge pull request #19296 from etiennesegonzac/bug-1011072 Bug 1011072 - Pre-position appwindow for edge gestures in an orientation independent way. r=21 ======== https://hg.mozilla.org/integration/gaia-central/rev/953580d1f48b Author: Etienne Segonzac Desc: Bug 1011072 - Pre-position appwindow for edge gestures in an orientation independent way. --- b2g/config/gaia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index f501f92b479..b795effc419 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -4,6 +4,6 @@ "remote": "", "branch": "" }, - "revision": "2314fc1464347650b4af19c9e34db09d958af4a8", + "revision": "26a239d804014b0c94f13dc80da23d7556bbdff9", "repo_path": "/integration/gaia-central" } From 9e2ed1dfba5efd1a9efa9ea108b53c148139cfbc Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 15 May 2014 16:11:22 -0700 Subject: [PATCH 55/70] Bumping manifests a=b2g-bump --- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame/sources.xml | 2 +- b2g/config/hamachi/sources.xml | 2 +- b2g/config/helix/sources.xml | 2 +- b2g/config/inari/sources.xml | 2 +- b2g/config/leo/sources.xml | 2 +- b2g/config/mako/sources.xml | 2 +- b2g/config/wasabi/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index ce13b5bb821..9e278c0c105 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index d3b12d002e4..827f771ff08 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 262875eab23..d317accb632 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index ce13b5bb821..9e278c0c105 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame/sources.xml b/b2g/config/flame/sources.xml index 19b08730e3f..cccb21c6334 100644 --- a/b2g/config/flame/sources.xml +++ b/b2g/config/flame/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml index d5676bb7d69..aeac5c32934 100644 --- a/b2g/config/hamachi/sources.xml +++ b/b2g/config/hamachi/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/helix/sources.xml b/b2g/config/helix/sources.xml index 4aaa09cc783..f022e162717 100644 --- a/b2g/config/helix/sources.xml +++ b/b2g/config/helix/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/inari/sources.xml b/b2g/config/inari/sources.xml index c14885629f2..595f933708f 100644 --- a/b2g/config/inari/sources.xml +++ b/b2g/config/inari/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/leo/sources.xml b/b2g/config/leo/sources.xml index 7d7b67f48a7..8abffe2ddf6 100644 --- a/b2g/config/leo/sources.xml +++ b/b2g/config/leo/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/mako/sources.xml b/b2g/config/mako/sources.xml index 1288186835f..055d9083009 100644 --- a/b2g/config/mako/sources.xml +++ b/b2g/config/mako/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml index 8420df71d95..a50d8b4002a 100644 --- a/b2g/config/wasabi/sources.xml +++ b/b2g/config/wasabi/sources.xml @@ -17,7 +17,7 @@ - + From f0402fe0776bb312eafd961821fb362af743c095 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 15 May 2014 16:30:51 -0700 Subject: [PATCH 56/70] Bumping gaia.json for 3 gaia revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/c041fbb3644d Author: Michael Henretty Desc: Merge pull request #19177 from mikehenrty/bug-968483-rnowm Bug 968483 rnowm ======== https://hg.mozilla.org/integration/gaia-central/rev/2ae6e9b6cae9 Author: Michael Henretty Desc: Bug 968483 - Fixes to increaded back button size, python tests ======== https://hg.mozilla.org/integration/gaia-central/rev/4cbdf6405a47 Author: rnowm Desc: Bug 968483 - [Building Blocks] Update header design to accommodate larger back-button --- b2g/config/gaia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index b795effc419..04e2fe923cd 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -4,6 +4,6 @@ "remote": "", "branch": "" }, - "revision": "26a239d804014b0c94f13dc80da23d7556bbdff9", + "revision": "c041fbb3644d1e50cffe82b01356401883ccc8ad", "repo_path": "/integration/gaia-central" } From fc1489e89d08f06adb3e758a4e357720144def84 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 15 May 2014 16:36:22 -0700 Subject: [PATCH 57/70] Bumping manifests a=b2g-bump --- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame/sources.xml | 2 +- b2g/config/hamachi/sources.xml | 2 +- b2g/config/helix/sources.xml | 2 +- b2g/config/inari/sources.xml | 2 +- b2g/config/leo/sources.xml | 2 +- b2g/config/mako/sources.xml | 2 +- b2g/config/wasabi/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index 9e278c0c105..bd6e8788d30 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 827f771ff08..5d771b91a52 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index d317accb632..dced68ea3c3 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index 9e278c0c105..bd6e8788d30 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame/sources.xml b/b2g/config/flame/sources.xml index cccb21c6334..1f068c6f973 100644 --- a/b2g/config/flame/sources.xml +++ b/b2g/config/flame/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml index aeac5c32934..5a51df077ff 100644 --- a/b2g/config/hamachi/sources.xml +++ b/b2g/config/hamachi/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/helix/sources.xml b/b2g/config/helix/sources.xml index f022e162717..dfa27c17f7b 100644 --- a/b2g/config/helix/sources.xml +++ b/b2g/config/helix/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/inari/sources.xml b/b2g/config/inari/sources.xml index 595f933708f..cedf80757ce 100644 --- a/b2g/config/inari/sources.xml +++ b/b2g/config/inari/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/leo/sources.xml b/b2g/config/leo/sources.xml index 8abffe2ddf6..68d87aaacfc 100644 --- a/b2g/config/leo/sources.xml +++ b/b2g/config/leo/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/mako/sources.xml b/b2g/config/mako/sources.xml index 055d9083009..857d563eb02 100644 --- a/b2g/config/mako/sources.xml +++ b/b2g/config/mako/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml index a50d8b4002a..d40b18eaacf 100644 --- a/b2g/config/wasabi/sources.xml +++ b/b2g/config/wasabi/sources.xml @@ -17,7 +17,7 @@ - + From aae5661afa762ed1ba99e6c9f60a4c431e0110ef Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 15 May 2014 17:10:46 -0700 Subject: [PATCH 58/70] Bumping gaia.json for 2 gaia revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/9fc7f382d9cb Author: Douglas Sherk Desc: Merge pull request #18087 from DouglasSherk/920011-dial-call-log Bug 920011 - [DSDS][Dialer] Dialing from call log by tapping instead of showing a menu. r=etienne ======== https://hg.mozilla.org/integration/gaia-central/rev/a117a6749339 Author: DouglasSherk Desc: Bug 920011 - [DSDS][Dialer] Dialing from call log by tapping instead of showing a menu. r=etienne Tapping now places a call immediately, except in the DSDS case, where we show the keypad screen with the contact's number filled in. Long pressing shows the old action menu, with the exception of the call button. --- b2g/config/gaia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 04e2fe923cd..3afbc8cde85 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -4,6 +4,6 @@ "remote": "", "branch": "" }, - "revision": "c041fbb3644d1e50cffe82b01356401883ccc8ad", + "revision": "9fc7f382d9cbe9e816c2baf59605b0dd497b41d9", "repo_path": "/integration/gaia-central" } From 23c62becbbc827cf220ff74c9f515889a8d347c8 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 15 May 2014 17:16:55 -0700 Subject: [PATCH 59/70] Bumping manifests a=b2g-bump --- b2g/config/emulator-ics/sources.xml | 4 ++-- b2g/config/emulator-jb/sources.xml | 4 ++-- b2g/config/emulator-kk/sources.xml | 4 ++-- b2g/config/emulator/sources.xml | 4 ++-- b2g/config/flame/sources.xml | 4 ++-- b2g/config/hamachi/sources.xml | 4 ++-- b2g/config/helix/sources.xml | 2 +- b2g/config/inari/sources.xml | 4 ++-- b2g/config/leo/sources.xml | 4 ++-- b2g/config/mako/sources.xml | 4 ++-- b2g/config/wasabi/sources.xml | 4 ++-- 11 files changed, 21 insertions(+), 21 deletions(-) diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index bd6e8788d30..8f633366abf 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,13 +19,13 @@ - + - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 5d771b91a52..2656b002e30 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,10 +17,10 @@ - + - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index dced68ea3c3..c38e9f1d9d4 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,14 +15,14 @@ - + - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index bd6e8788d30..8f633366abf 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,13 +19,13 @@ - + - + diff --git a/b2g/config/flame/sources.xml b/b2g/config/flame/sources.xml index 1f068c6f973..6a1e8dfb565 100644 --- a/b2g/config/flame/sources.xml +++ b/b2g/config/flame/sources.xml @@ -18,10 +18,10 @@ - + - + diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml index 5a51df077ff..40f43edfa66 100644 --- a/b2g/config/hamachi/sources.xml +++ b/b2g/config/hamachi/sources.xml @@ -17,12 +17,12 @@ - + - + diff --git a/b2g/config/helix/sources.xml b/b2g/config/helix/sources.xml index dfa27c17f7b..3674a6f0bb2 100644 --- a/b2g/config/helix/sources.xml +++ b/b2g/config/helix/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/inari/sources.xml b/b2g/config/inari/sources.xml index cedf80757ce..c2945bec4fd 100644 --- a/b2g/config/inari/sources.xml +++ b/b2g/config/inari/sources.xml @@ -19,12 +19,12 @@ - + - + diff --git a/b2g/config/leo/sources.xml b/b2g/config/leo/sources.xml index 68d87aaacfc..34bb306d0f1 100644 --- a/b2g/config/leo/sources.xml +++ b/b2g/config/leo/sources.xml @@ -17,12 +17,12 @@ - + - + diff --git a/b2g/config/mako/sources.xml b/b2g/config/mako/sources.xml index 857d563eb02..092ec472488 100644 --- a/b2g/config/mako/sources.xml +++ b/b2g/config/mako/sources.xml @@ -17,10 +17,10 @@ - + - + diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml index d40b18eaacf..311464e168f 100644 --- a/b2g/config/wasabi/sources.xml +++ b/b2g/config/wasabi/sources.xml @@ -17,12 +17,12 @@ - + - + From 9e06010f004f9fa89e08f5b7872249e139007306 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 15 May 2014 18:05:46 -0700 Subject: [PATCH 60/70] Bumping gaia.json for 2 gaia revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/d0a4b418d877 Author: Ryan VanderMeulen Desc: Revert "Merge pull request #19268 from crh0716/1010394" This reverts commit 829e1d6e6806509d5010faa5ba900f0cad6523f9, reversing changes made to 5936e35e386a65e98d97392799b411203f3b737a. ======== https://hg.mozilla.org/integration/gaia-central/rev/fccad5620e26 Author: Ryan VanderMeulen Desc: Revert "Merge pull request #19236 from frsela/STK/Bug1009188" This reverts commit 5936e35e386a65e98d97392799b411203f3b737a, reversing changes made to c0d92f9e1c0580208ee83a7f2db01e56c2c21490. --- b2g/config/gaia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 3afbc8cde85..961c114f289 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -4,6 +4,6 @@ "remote": "", "branch": "" }, - "revision": "9fc7f382d9cbe9e816c2baf59605b0dd497b41d9", + "revision": "d0a4b418d877e00dfd5cab7a2a97e54f6e243adf", "repo_path": "/integration/gaia-central" } From 3b9f34ebfd0d8f2f576cba466d4be119d52f3c34 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 15 May 2014 18:07:59 -0700 Subject: [PATCH 61/70] Bumping manifests a=b2g-bump --- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame/sources.xml | 2 +- b2g/config/hamachi/sources.xml | 2 +- b2g/config/helix/sources.xml | 2 +- b2g/config/inari/sources.xml | 2 +- b2g/config/leo/sources.xml | 2 +- b2g/config/mako/sources.xml | 2 +- b2g/config/wasabi/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index 8f633366abf..da442158558 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 2656b002e30..97300500f6c 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index c38e9f1d9d4..5d3a1d94431 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index 8f633366abf..da442158558 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame/sources.xml b/b2g/config/flame/sources.xml index 6a1e8dfb565..5474670009a 100644 --- a/b2g/config/flame/sources.xml +++ b/b2g/config/flame/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml index 40f43edfa66..2de9bf02a1a 100644 --- a/b2g/config/hamachi/sources.xml +++ b/b2g/config/hamachi/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/helix/sources.xml b/b2g/config/helix/sources.xml index 3674a6f0bb2..3ee72bf7dc9 100644 --- a/b2g/config/helix/sources.xml +++ b/b2g/config/helix/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/inari/sources.xml b/b2g/config/inari/sources.xml index c2945bec4fd..49877b29856 100644 --- a/b2g/config/inari/sources.xml +++ b/b2g/config/inari/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/leo/sources.xml b/b2g/config/leo/sources.xml index 34bb306d0f1..7eba9848974 100644 --- a/b2g/config/leo/sources.xml +++ b/b2g/config/leo/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/mako/sources.xml b/b2g/config/mako/sources.xml index 092ec472488..9a87304437a 100644 --- a/b2g/config/mako/sources.xml +++ b/b2g/config/mako/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml index 311464e168f..5d1f0888562 100644 --- a/b2g/config/wasabi/sources.xml +++ b/b2g/config/wasabi/sources.xml @@ -17,7 +17,7 @@ - + From ab08dd4fed7c6d70e5319c051a6247eb916aa5c4 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 15 May 2014 19:15:58 -0700 Subject: [PATCH 62/70] Bumping gaia.json for 2 gaia revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/a1e288daa17c Author: EragonJ Desc: Merge pull request #19221 from EragonJ/bug-1007469 Bug 1007469 - copy-build-stage-manifest should depend on $(XULRUNNER_BAS... ======== https://hg.mozilla.org/integration/gaia-central/rev/9c8e679372a0 Author: EragonJ Desc: Bug 1007469 - copy-build-stage-manifest should depend on $(XULRUNNER_BASE_DIRECTORY) --- b2g/config/gaia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 961c114f289..3d218e6128b 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -4,6 +4,6 @@ "remote": "", "branch": "" }, - "revision": "d0a4b418d877e00dfd5cab7a2a97e54f6e243adf", + "revision": "a1e288daa17c774c9680058ffb661da7d463bdb1", "repo_path": "/integration/gaia-central" } From d7fb39826988aabe590c8f1c28fe588adc129598 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 15 May 2014 19:21:47 -0700 Subject: [PATCH 63/70] Bumping manifests a=b2g-bump --- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame/sources.xml | 2 +- b2g/config/hamachi/sources.xml | 2 +- b2g/config/helix/sources.xml | 2 +- b2g/config/inari/sources.xml | 2 +- b2g/config/leo/sources.xml | 2 +- b2g/config/mako/sources.xml | 2 +- b2g/config/wasabi/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index da442158558..e74ec11173f 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 97300500f6c..d391e9994f7 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 5d3a1d94431..583835d0a77 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index da442158558..e74ec11173f 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame/sources.xml b/b2g/config/flame/sources.xml index 5474670009a..025d0cd7a8c 100644 --- a/b2g/config/flame/sources.xml +++ b/b2g/config/flame/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml index 2de9bf02a1a..9c0ef2d7daa 100644 --- a/b2g/config/hamachi/sources.xml +++ b/b2g/config/hamachi/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/helix/sources.xml b/b2g/config/helix/sources.xml index 3ee72bf7dc9..57ed8339c70 100644 --- a/b2g/config/helix/sources.xml +++ b/b2g/config/helix/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/inari/sources.xml b/b2g/config/inari/sources.xml index 49877b29856..d5e3fd6c839 100644 --- a/b2g/config/inari/sources.xml +++ b/b2g/config/inari/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/leo/sources.xml b/b2g/config/leo/sources.xml index 7eba9848974..cbab92a4f37 100644 --- a/b2g/config/leo/sources.xml +++ b/b2g/config/leo/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/mako/sources.xml b/b2g/config/mako/sources.xml index 9a87304437a..b91adb7c87b 100644 --- a/b2g/config/mako/sources.xml +++ b/b2g/config/mako/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml index 5d1f0888562..b39aea3b998 100644 --- a/b2g/config/wasabi/sources.xml +++ b/b2g/config/wasabi/sources.xml @@ -17,7 +17,7 @@ - + From bbd590bb262b20222d2c55b282cefabd3735ee2d Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 15 May 2014 20:10:59 -0700 Subject: [PATCH 64/70] Bumping gaia.json for 2 gaia revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/b9883321a4fc Author: John Hu Desc: Merge pull request #19278 from ranjithgithub/1000701_FileNameDoneIconSupport Bug 1000701-File name & Done icon support, r=johnhu ======== https://hg.mozilla.org/integration/gaia-central/rev/8798d6bca1e1 Author: ranjith.gutha Desc: Bug 1000701-File name & Done icon support --- b2g/config/gaia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 3d218e6128b..c705bcb3fab 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -4,6 +4,6 @@ "remote": "", "branch": "" }, - "revision": "a1e288daa17c774c9680058ffb661da7d463bdb1", + "revision": "b9883321a4fc93fc7262abad2419e710d64cab6c", "repo_path": "/integration/gaia-central" } From 48059f682e81f70dcb53ee5eda64b2f83954fcaf Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 15 May 2014 20:13:01 -0700 Subject: [PATCH 65/70] Bumping manifests a=b2g-bump --- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame/sources.xml | 2 +- b2g/config/hamachi/sources.xml | 2 +- b2g/config/helix/sources.xml | 2 +- b2g/config/inari/sources.xml | 2 +- b2g/config/leo/sources.xml | 2 +- b2g/config/mako/sources.xml | 2 +- b2g/config/wasabi/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index e74ec11173f..74ccc2b7a54 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index d391e9994f7..9b7751a1895 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 583835d0a77..146872d0504 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index e74ec11173f..74ccc2b7a54 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame/sources.xml b/b2g/config/flame/sources.xml index 025d0cd7a8c..6013d6e5900 100644 --- a/b2g/config/flame/sources.xml +++ b/b2g/config/flame/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml index 9c0ef2d7daa..206127c5457 100644 --- a/b2g/config/hamachi/sources.xml +++ b/b2g/config/hamachi/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/helix/sources.xml b/b2g/config/helix/sources.xml index 57ed8339c70..5446ed10c01 100644 --- a/b2g/config/helix/sources.xml +++ b/b2g/config/helix/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/inari/sources.xml b/b2g/config/inari/sources.xml index d5e3fd6c839..85e572de1ef 100644 --- a/b2g/config/inari/sources.xml +++ b/b2g/config/inari/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/leo/sources.xml b/b2g/config/leo/sources.xml index cbab92a4f37..c3122525a0b 100644 --- a/b2g/config/leo/sources.xml +++ b/b2g/config/leo/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/mako/sources.xml b/b2g/config/mako/sources.xml index b91adb7c87b..81062c00146 100644 --- a/b2g/config/mako/sources.xml +++ b/b2g/config/mako/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml index b39aea3b998..28fea46b855 100644 --- a/b2g/config/wasabi/sources.xml +++ b/b2g/config/wasabi/sources.xml @@ -17,7 +17,7 @@ - + From 675fec6edc325cbecf9a3d72ae98b80b2c0b9094 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 15 May 2014 22:20:59 -0700 Subject: [PATCH 66/70] Bumping gaia.json for 2 gaia revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/4393a8d719e7 Author: Etienne Segonzac Desc: Merge pull request #19301 from etiennesegonzac/bug-1011262 Bug 1011262 - Making sure the layour manager is aware of the new active app before resizing it r=alive ======== https://hg.mozilla.org/integration/gaia-central/rev/521599877afe Author: Etienne Segonzac Desc: Bug 1011262 - Making sure the layour manager is aware of the new active app before resizing it. --- b2g/config/gaia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index c705bcb3fab..a523df6c8e6 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -4,6 +4,6 @@ "remote": "", "branch": "" }, - "revision": "b9883321a4fc93fc7262abad2419e710d64cab6c", + "revision": "4393a8d719e7917f543cd8d906632c9b2164198e", "repo_path": "/integration/gaia-central" } From dd08048fee45823dc27dc7e5df37818627c22e33 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 15 May 2014 22:26:22 -0700 Subject: [PATCH 67/70] Bumping manifests a=b2g-bump --- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame/sources.xml | 2 +- b2g/config/hamachi/sources.xml | 2 +- b2g/config/helix/sources.xml | 2 +- b2g/config/inari/sources.xml | 2 +- b2g/config/leo/sources.xml | 2 +- b2g/config/mako/sources.xml | 2 +- b2g/config/wasabi/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index 74ccc2b7a54..5c4c9edc99a 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 9b7751a1895..867c886e9da 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 146872d0504..507371f08ab 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index 74ccc2b7a54..5c4c9edc99a 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame/sources.xml b/b2g/config/flame/sources.xml index 6013d6e5900..1cd361659de 100644 --- a/b2g/config/flame/sources.xml +++ b/b2g/config/flame/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml index 206127c5457..a5fd743c1a1 100644 --- a/b2g/config/hamachi/sources.xml +++ b/b2g/config/hamachi/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/helix/sources.xml b/b2g/config/helix/sources.xml index 5446ed10c01..6e496b79bc5 100644 --- a/b2g/config/helix/sources.xml +++ b/b2g/config/helix/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/inari/sources.xml b/b2g/config/inari/sources.xml index 85e572de1ef..75d18fc78f7 100644 --- a/b2g/config/inari/sources.xml +++ b/b2g/config/inari/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/leo/sources.xml b/b2g/config/leo/sources.xml index c3122525a0b..0a0051095a7 100644 --- a/b2g/config/leo/sources.xml +++ b/b2g/config/leo/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/mako/sources.xml b/b2g/config/mako/sources.xml index 81062c00146..8c5dc916084 100644 --- a/b2g/config/mako/sources.xml +++ b/b2g/config/mako/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml index 28fea46b855..d5102794f5e 100644 --- a/b2g/config/wasabi/sources.xml +++ b/b2g/config/wasabi/sources.xml @@ -17,7 +17,7 @@ - + From 79170f61880cafd36fa0a5a46b25035d7a1acb0a Mon Sep 17 00:00:00 2001 From: "Szu-Yu Chen [:aknow]" Date: Fri, 16 May 2014 14:05:33 +0800 Subject: [PATCH 68/70] Bug 1003652 - Turn on debugging in marionette test. r=hsinyi, jgriffin --- dom/telephony/test/marionette/head.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/dom/telephony/test/marionette/head.js b/dom/telephony/test/marionette/head.js index 566dc207be1..69a582bec73 100644 --- a/dom/telephony/test/marionette/head.js +++ b/dom/telephony/test/marionette/head.js @@ -5,6 +5,8 @@ let Promise = SpecialPowers.Cu.import("resource://gre/modules/Promise.jsm").Prom let telephony; let conference; +const kPrefRilDebuggingEnabled = "ril.debugging.enabled"; + /** * Emulator helper. */ @@ -1080,9 +1082,18 @@ function _startTest(permissions, test) { } } + let debugPref; + function setUp() { log("== Test SetUp =="); + + // Turn on debugging pref. + debugPref = SpecialPowers.getBoolPref(kPrefRilDebuggingEnabled); + SpecialPowers.setBoolPref(kPrefRilDebuggingEnabled, true); + log("Set debugging pref: " + debugPref + " => true"); + permissionSetUp(); + // Make sure that we get the telephony after adding permission. telephony = window.navigator.mozTelephony; ok(telephony); @@ -1100,7 +1111,13 @@ function _startTest(permissions, test) { log("== Test TearDown =="); restoreTelephonyDial(); emulator.waitFinish() - .then(permissionTearDown) + .then(() => { + permissionTearDown(); + + // Restore debugging pref. + SpecialPowers.setBoolPref(kPrefRilDebuggingEnabled, debugPref); + log("Set debugging pref: true => " + debugPref); + }) .then(function() { originalFinish.apply(this, arguments); }); From 398a6f5a8da1daab4f8f09bee9552a6daaedc2fb Mon Sep 17 00:00:00 2001 From: Chris Peterson Date: Fri, 16 May 2014 00:33:16 -0700 Subject: [PATCH 69/70] Backed out changeset 9c586b1649d7 for wrong bug number: s/1007708/1010706/ --- gfx/layers/opengl/CompositorOGL.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gfx/layers/opengl/CompositorOGL.cpp b/gfx/layers/opengl/CompositorOGL.cpp index c9caf84fc02..79d0c348d2f 100644 --- a/gfx/layers/opengl/CompositorOGL.cpp +++ b/gfx/layers/opengl/CompositorOGL.cpp @@ -920,8 +920,7 @@ static bool SetBlendMode(GLContext* aGL, gfx::CompositionOp aBlendMode, bool aIs dstBlend = LOCAL_GL_ONE_MINUS_SRC_ALPHA; break; default: - MOZ_ASSERT_UNREACHABLE("Unsupported blend mode!"); - return false; + MOZ_ASSERT(0, "Unsupported blend mode!"); } aGL->fBlendFuncSeparate(srcBlend, dstBlend, From 143832c9389f200753390fc991ded3299e44c264 Mon Sep 17 00:00:00 2001 From: Chris Peterson Date: Fri, 16 May 2014 00:35:13 -0700 Subject: [PATCH 70/70] Bug 1010706 - Fix uninitialized variable warnings in gfx/layers/opengl/CompositorOGL.cpp. r=mwoodrow --- gfx/layers/opengl/CompositorOGL.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gfx/layers/opengl/CompositorOGL.cpp b/gfx/layers/opengl/CompositorOGL.cpp index 79d0c348d2f..c9caf84fc02 100644 --- a/gfx/layers/opengl/CompositorOGL.cpp +++ b/gfx/layers/opengl/CompositorOGL.cpp @@ -920,7 +920,8 @@ static bool SetBlendMode(GLContext* aGL, gfx::CompositionOp aBlendMode, bool aIs dstBlend = LOCAL_GL_ONE_MINUS_SRC_ALPHA; break; default: - MOZ_ASSERT(0, "Unsupported blend mode!"); + MOZ_ASSERT_UNREACHABLE("Unsupported blend mode!"); + return false; } aGL->fBlendFuncSeparate(srcBlend, dstBlend,