diff --git a/accessible/tests/mochitest/actions/test_general.xul b/accessible/tests/mochitest/actions/test_general.xul index d9bcccb21b84..b14e1c7623e4 100644 --- a/accessible/tests/mochitest/actions/test_general.xul +++ b/accessible/tests/mochitest/actions/test_general.xul @@ -21,7 +21,7 @@ + + + diff --git a/content/base/crashtests/844404.html b/content/base/crashtests/844404.html new file mode 100644 index 000000000000..9022837bf2c8 --- /dev/null +++ b/content/base/crashtests/844404.html @@ -0,0 +1,23 @@ + + + + + + + + +
+ + + diff --git a/content/base/crashtests/crashtests.list b/content/base/crashtests/crashtests.list index bfbf799f2318..c398e7f46e5f 100644 --- a/content/base/crashtests/crashtests.list +++ b/content/base/crashtests/crashtests.list @@ -129,3 +129,5 @@ load 827190.html load 828054.html load 829428.html load 836890.html +load 841205.html +load 844404.html diff --git a/content/base/public/nsContentUtils.h b/content/base/public/nsContentUtils.h index db1a4518c69d..0d2026abd73d 100644 --- a/content/base/public/nsContentUtils.h +++ b/content/base/public/nsContentUtils.h @@ -2205,9 +2205,9 @@ public: bool RePush(nsIDOMEventTarget *aCurrentTarget); // If a null JSContext is passed to Push(), that will cause no // push to happen and false to be returned. - bool Push(JSContext *cx, bool aRequiresScriptContext = true); + void Push(JSContext *cx); // Explicitly push a null JSContext on the the stack - bool PushNull(); + void PushNull(); // Pop() will be a no-op if Push() or PushNull() fail void Pop(); @@ -2215,7 +2215,7 @@ public: nsIScriptContext* GetCurrentScriptContext() { return mScx; } private: // Combined code for PushNull() and Push(JSContext*) - bool DoPush(JSContext* cx); + void DoPush(JSContext* cx); nsCOMPtr mScx; bool mScriptIsRunning; @@ -2302,6 +2302,33 @@ public: SafeAutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); }; +/** + * Use AutoPushJSContext when you want to use a specific JSContext that may or + * may not be already on the stack. This differs from nsCxPusher in that it only + * pushes in the case that the given cx is not the active cx on the JSContext + * stack, which avoids an expensive JS_SaveFrameChain in the common case. + * + * Most consumers of this should probably just use AutoJSContext. But the goal + * here is to preserve the existing behavior while ensure proper cx-stack + * semantics in edge cases where the context being used doesn't match the active + * context. + * + * NB: This will not push a null cx even if aCx is null. Make sure you know what + * you're doing. + */ +class NS_STACK_CLASS AutoPushJSContext { + nsCxPusher mPusher; + JSContext* mCx; + +public: + AutoPushJSContext(JSContext* aCx) : mCx(aCx) { + if (mCx && mCx != nsContentUtils::GetCurrentJSContext()) { + mPusher.Push(mCx); + } + } + operator JSContext*() { return mCx; } +}; + } // namespace mozilla #define NS_INTERFACE_MAP_ENTRY_TEAROFF(_interface, _allocator) \ diff --git a/content/base/public/nsIDocument.h b/content/base/public/nsIDocument.h index 07a68c99bedb..7544c95ac08e 100644 --- a/content/base/public/nsIDocument.h +++ b/content/base/public/nsIDocument.h @@ -95,9 +95,13 @@ class Element; class GlobalObject; class HTMLBodyElement; class Link; +class NodeFilter; class ProcessingInstruction; class UndoManager; template class Sequence; + +template class CallbackObjectHolder; +typedef CallbackObjectHolder NodeFilterHolder; } // namespace dom } // namespace mozilla @@ -1919,10 +1923,19 @@ public: already_AddRefed CreateRange(mozilla::ErrorResult& rv); already_AddRefed CreateNodeIterator(nsINode& aRoot, uint32_t aWhatToShow, - nsIDOMNodeFilter* aFilter, mozilla::ErrorResult& rv) const; + mozilla::dom::NodeFilter* aFilter, + mozilla::ErrorResult& rv) const; + already_AddRefed + CreateNodeIterator(nsINode& aRoot, uint32_t aWhatToShow, + const mozilla::dom::NodeFilterHolder& aFilter, + mozilla::ErrorResult& rv) const; already_AddRefed CreateTreeWalker(nsINode& aRoot, uint32_t aWhatToShow, - nsIDOMNodeFilter* aFilter, mozilla::ErrorResult& rv) const; + mozilla::dom::NodeFilter* aFilter, mozilla::ErrorResult& rv) const; + already_AddRefed + CreateTreeWalker(nsINode& aRoot, uint32_t aWhatToShow, + const mozilla::dom::NodeFilterHolder& aFilter, + mozilla::ErrorResult& rv) const; // Deprecated WebIDL bits already_AddRefed diff --git a/content/base/src/EventSource.cpp b/content/base/src/EventSource.cpp index 61a6a399b2db..1441a0b808ec 100644 --- a/content/base/src/EventSource.cpp +++ b/content/base/src/EventSource.cpp @@ -1227,7 +1227,7 @@ EventSource::DispatchAllMessageEvents() nsIScriptContext* scriptContext = sgo->GetContext(); NS_ENSURE_TRUE_VOID(scriptContext); - JSContext* cx = scriptContext->GetNativeContext(); + AutoPushJSContext cx(scriptContext->GetNativeContext()); NS_ENSURE_TRUE_VOID(cx); while (mMessagesToDispatch.GetSize() > 0) { diff --git a/content/base/src/WebSocket.cpp b/content/base/src/WebSocket.cpp index e79f132327f0..30dd08b5ea22 100644 --- a/content/base/src/WebSocket.cpp +++ b/content/base/src/WebSocket.cpp @@ -881,7 +881,7 @@ WebSocket::CreateAndDispatchMessageEvent(const nsACString& aData, nsIScriptContext* scriptContext = sgo->GetContext(); NS_ENSURE_TRUE(scriptContext, NS_ERROR_FAILURE); - JSContext* cx = scriptContext->GetNativeContext(); + AutoPushJSContext cx(scriptContext->GetNativeContext()); NS_ENSURE_TRUE(cx, NS_ERROR_FAILURE); // Create appropriate JS object for message diff --git a/content/base/src/nsContentUtils.cpp b/content/base/src/nsContentUtils.cpp index 36bd3c5ade10..a74ac1fb11ad 100644 --- a/content/base/src/nsContentUtils.cpp +++ b/content/base/src/nsContentUtils.cpp @@ -3015,19 +3015,14 @@ nsCxPusher::Push(nsIDOMEventTarget *aCurrentTarget) return true; } - JSContext* cx = nullptr; - - if (scx) { - cx = scx->GetNativeContext(); - // Bad, no JSContext from script context! - NS_ENSURE_TRUE(cx, false); - } + JSContext* cx = scx ? scx->GetNativeContext() : nullptr; // If there's no native context in the script context it must be // in the process or being torn down. We don't want to notify the // script context about scripts having been evaluated in such a // case, calling with a null cx is fine in that case. - return Push(cx); + Push(cx); + return true; } bool @@ -3058,37 +3053,26 @@ nsCxPusher::RePush(nsIDOMEventTarget *aCurrentTarget) return Push(aCurrentTarget); } -bool -nsCxPusher::Push(JSContext *cx, bool aRequiresScriptContext) +void +nsCxPusher::Push(JSContext *cx) { - if (mPushedSomething) { - NS_ERROR("Whaaa! No double pushing with nsCxPusher::Push()!"); - - return false; - } - - if (!cx) { - return false; - } + MOZ_ASSERT(!mPushedSomething, "No double pushing with nsCxPusher::Push()!"); + MOZ_ASSERT(cx); // Hold a strong ref to the nsIScriptContext, just in case // XXXbz do we really need to? If we don't get one of these in Pop(), is // that really a problem? Or do we need to do this to effectively root |cx|? mScx = GetScriptContextFromJSContext(cx); - if (!mScx && aRequiresScriptContext) { - // Should probably return false. See bug 416916. - return true; - } - return DoPush(cx); + DoPush(cx); } -bool +void nsCxPusher::DoPush(JSContext* cx) { nsIThreadJSContextStack* stack = nsContentUtils::ThreadJSContextStack(); if (!stack) { - return true; + return; } if (cx && IsContextOnStack(stack, cx)) { @@ -3098,9 +3082,7 @@ nsCxPusher::DoPush(JSContext* cx) } if (NS_FAILED(stack->Push(cx))) { - mScriptIsRunning = false; - mScx = nullptr; - return false; + MOZ_CRASH(); } mPushedSomething = true; @@ -3109,13 +3091,12 @@ nsCxPusher::DoPush(JSContext* cx) if (cx) mCompartmentDepthOnEntry = js::GetEnterCompartmentDepth(cx); #endif - return true; } -bool +void nsCxPusher::PushNull() { - return DoPush(nullptr); + DoPush(nullptr); } void @@ -6431,8 +6412,8 @@ nsContentUtils::IsPatternMatching(nsAString& aValue, nsAString& aPattern, NS_ASSERTION(aDocument, "aDocument should be a valid pointer (not null)"); NS_ENSURE_TRUE(aDocument->GetScriptGlobalObject(), true); - JSContext* cx = aDocument->GetScriptGlobalObject()-> - GetContext()->GetNativeContext(); + AutoPushJSContext cx(aDocument->GetScriptGlobalObject()-> + GetContext()->GetNativeContext()); NS_ENSURE_TRUE(cx, true); JSAutoRequest ar(cx); @@ -6838,10 +6819,7 @@ AutoJSContext::Init(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) if (!mCx) { mCx = nsContentUtils::GetSafeJSContext(); - bool result = mPusher.Push(mCx); - if (!result || !mCx) { - MOZ_CRASH(); - } + mPusher.Push(mCx); } } diff --git a/content/base/src/nsDOMDataChannel.cpp b/content/base/src/nsDOMDataChannel.cpp index 164534ef983c..15ed7abc8c91 100644 --- a/content/base/src/nsDOMDataChannel.cpp +++ b/content/base/src/nsDOMDataChannel.cpp @@ -45,6 +45,8 @@ extern PRLogModuleInfo* GetDataChannelLog(); #undef GetBinaryType #endif +using namespace mozilla; + class nsDOMDataChannel : public nsDOMEventTargetHelper, public nsIDOMDataChannel, public mozilla::DataChannelListener @@ -393,7 +395,7 @@ nsDOMDataChannel::DoOnMessageAvailable(const nsACString& aData, nsIScriptContext* sc = sgo->GetContext(); NS_ENSURE_TRUE(sc, NS_ERROR_FAILURE); - JSContext* cx = sc->GetNativeContext(); + AutoPushJSContext cx(sc->GetNativeContext()); NS_ENSURE_TRUE(cx, NS_ERROR_FAILURE); JSAutoRequest ar(cx); diff --git a/content/base/src/nsDocument.cpp b/content/base/src/nsDocument.cpp index b6e96e68ac8c..e007627735d0 100644 --- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -184,6 +184,7 @@ #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/DocumentFragment.h" #include "mozilla/dom/HTMLBodyElement.h" +#include "mozilla/dom/NodeFilterBinding.h" #include "mozilla/dom/UndoManager.h" #include "nsFrame.h" #include "nsDOMCaretPosition.h" @@ -3983,19 +3984,8 @@ nsDocument::SetScriptGlobalObject(nsIScriptGlobalObject *aScriptGlobalObject) JSObject *obj = GetWrapperPreserveColor(); if (obj) { JSObject *newScope = aScriptGlobalObject->GetGlobalJSObject(); - nsIScriptContext *scx = aScriptGlobalObject->GetContext(); - JSContext *cx = scx ? scx->GetNativeContext() : nullptr; - if (!cx) { - nsContentUtils::ThreadJSContextStack()->Peek(&cx); - if (!cx) { - cx = nsContentUtils::ThreadJSContextStack()->GetSafeJSContext(); - NS_ASSERTION(cx, "Uhoh, no context, this is bad!"); - } - } - if (cx) { - NS_ASSERTION(JS_GetGlobalForObject(cx, obj) == newScope, - "Wrong scope, this is really bad!"); - } + NS_ASSERTION(js::GetGlobalForObjectCrossCompartment(obj) == newScope, + "Wrong scope, this is really bad!"); } } #endif @@ -5365,14 +5355,28 @@ nsDocument::CreateNodeIterator(nsIDOMNode *aRoot, NS_ENSURE_TRUE(root, NS_ERROR_UNEXPECTED); ErrorResult rv; - *_retval = nsIDocument::CreateNodeIterator(*root, aWhatToShow, aFilter, + NodeFilterHolder holder(aFilter); + *_retval = nsIDocument::CreateNodeIterator(*root, aWhatToShow, holder, rv).get(); return rv.ErrorCode(); } already_AddRefed nsIDocument::CreateNodeIterator(nsINode& aRoot, uint32_t aWhatToShow, - nsIDOMNodeFilter* aFilter, + NodeFilter* aFilter, + mozilla::ErrorResult& rv) const +{ + NodeFilterHolder holder(aFilter); + // We don't really know how to handle WebIDL callbacks yet, in + // nsTraversal, so just go ahead and convert to an XPCOM callback. + nsCOMPtr filter = holder.ToXPCOMCallback(); + NodeFilterHolder holder2(filter); + return CreateNodeIterator(aRoot, aWhatToShow, holder2, rv); +} + +already_AddRefed +nsIDocument::CreateNodeIterator(nsINode& aRoot, uint32_t aWhatToShow, + const NodeFilterHolder& aFilter, mozilla::ErrorResult& rv) const { nsINode* root = &aRoot; @@ -5404,14 +5408,28 @@ nsDocument::CreateTreeWalker(nsIDOMNode *aRoot, NS_ENSURE_TRUE(root, NS_ERROR_DOM_NOT_SUPPORTED_ERR); ErrorResult rv; - *_retval = nsIDocument::CreateTreeWalker(*root, aWhatToShow, aFilter, + NodeFilterHolder holder(aFilter); + *_retval = nsIDocument::CreateTreeWalker(*root, aWhatToShow, holder, rv).get(); return rv.ErrorCode(); } already_AddRefed nsIDocument::CreateTreeWalker(nsINode& aRoot, uint32_t aWhatToShow, - nsIDOMNodeFilter* aFilter, + NodeFilter* aFilter, + mozilla::ErrorResult& rv) const +{ + NodeFilterHolder holder(aFilter); + // We don't really know how to handle WebIDL callbacks yet, in + // nsTraversal, so just go ahead and convert to an XPCOM callback. + nsCOMPtr filter = holder.ToXPCOMCallback(); + NodeFilterHolder holder2(filter); + return CreateTreeWalker(aRoot, aWhatToShow, holder2, rv); +} + +already_AddRefed +nsIDocument::CreateTreeWalker(nsINode& aRoot, uint32_t aWhatToShow, + const NodeFilterHolder& aFilter, mozilla::ErrorResult& rv) const { nsINode* root = &aRoot; @@ -6174,6 +6192,7 @@ private: */ static nsresult GetContextAndScope(nsIDocument* aOldDocument, nsIDocument* aNewDocument, + nsCxPusher& aPusher, JSContext** aCx, JSObject** aNewScope) { MOZ_ASSERT(aOldDocument); @@ -6216,6 +6235,9 @@ GetContextAndScope(nsIDocument* aOldDocument, nsIDocument* aNewDocument, } } + if (cx) { + aPusher.Push(cx); + } if (!newScope && cx) { JS::Value v; nsresult rv = nsContentUtils::WrapNative(cx, global, aNewDocument, @@ -6349,8 +6371,9 @@ nsIDocument::AdoptNode(nsINode& aAdoptedNode, ErrorResult& rv) JSContext *cx = nullptr; JSObject *newScope = nullptr; + nsCxPusher pusher; if (!sameDocument) { - rv = GetContextAndScope(oldDocument, this, &cx, &newScope); + rv = GetContextAndScope(oldDocument, this, pusher, &cx, &newScope); if (rv.Failed()) { return nullptr; } @@ -6824,7 +6847,7 @@ nsDocument::IsScriptEnabled() nsIScriptContext *scriptContext = globalObject->GetContext(); NS_ENSURE_TRUE(scriptContext, false); - JSContext* cx = scriptContext->GetNativeContext(); + AutoPushJSContext cx(scriptContext->GetNativeContext()); NS_ENSURE_TRUE(cx, false); bool enabled; @@ -8542,7 +8565,7 @@ nsDocument::GetStateObject(nsIVariant** aState) nsCOMPtr stateObj; if (!mStateObjectCached && mStateObjectContainer) { - JSContext *cx = nsContentUtils::GetContextFromDocument(this); + AutoPushJSContext cx(nsContentUtils::GetContextFromDocument(this)); mStateObjectContainer-> DeserializeToVariant(cx, getter_AddRefs(mStateObjectCached)); } diff --git a/content/base/src/nsFrameLoader.cpp b/content/base/src/nsFrameLoader.cpp index 2b581c4049fd..087feb3af627 100644 --- a/content/base/src/nsFrameLoader.cpp +++ b/content/base/src/nsFrameLoader.cpp @@ -2363,7 +2363,7 @@ nsFrameLoader::EnsureMessageManager() nsIScriptContext* sctx = mOwnerContent->GetContextForEventHandlers(&rv); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_STATE(sctx); - JSContext* cx = sctx->GetNativeContext(); + AutoPushJSContext cx(sctx->GetNativeContext()); NS_ENSURE_STATE(cx); nsCOMPtr chromeWindow = diff --git a/content/base/src/nsFrameMessageManager.cpp b/content/base/src/nsFrameMessageManager.cpp index 6689a1ac03a1..2df216c854ca 100644 --- a/content/base/src/nsFrameMessageManager.cpp +++ b/content/base/src/nsFrameMessageManager.cpp @@ -651,7 +651,7 @@ nsFrameMessageManager::ReceiveMessage(nsISupports* aTarget, continue; } nsCxPusher pusher; - NS_ENSURE_STATE(pusher.Push(ctx, false)); + pusher.Push(ctx); JSAutoRequest ar(ctx); JSAutoCompartment ac(ctx, object); diff --git a/content/base/src/nsNodeIterator.cpp b/content/base/src/nsNodeIterator.cpp index 8bacdab6cb01..fe821a366a5e 100644 --- a/content/base/src/nsNodeIterator.cpp +++ b/content/base/src/nsNodeIterator.cpp @@ -19,6 +19,9 @@ #include "nsDOMClassInfoID.h" #include "nsContentUtils.h" #include "nsCOMPtr.h" +#include "mozilla/dom/NodeFilterBinding.h" + +using namespace mozilla::dom; /* * NodePointer implementation @@ -137,7 +140,7 @@ void nsNodeIterator::NodePointer::MoveBackward(nsINode *aParent, nsINode *aNode) nsNodeIterator::nsNodeIterator(nsINode *aRoot, uint32_t aWhatToShow, - nsIDOMNodeFilter *aFilter) : + const NodeFilterHolder &aFilter) : nsTraversal(aRoot, aWhatToShow, aFilter), mDetached(false), mPointer(mRoot, true) @@ -203,7 +206,7 @@ NS_IMETHODIMP nsNodeIterator::GetFilter(nsIDOMNodeFilter **aFilter) { NS_ENSURE_ARG_POINTER(aFilter); - NS_IF_ADDREF(*aFilter = mFilter); + *aFilter = mFilter.ToXPCOMCallback().get(); return NS_OK; } diff --git a/content/base/src/nsNodeIterator.h b/content/base/src/nsNodeIterator.h index afd0cf0d5c51..e1f727b843c5 100644 --- a/content/base/src/nsNodeIterator.h +++ b/content/base/src/nsNodeIterator.h @@ -30,7 +30,7 @@ public: nsNodeIterator(nsINode *aRoot, uint32_t aWhatToShow, - nsIDOMNodeFilter *aFilter); + const mozilla::dom::NodeFilterHolder &aFilter); virtual ~nsNodeIterator(); NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED diff --git a/content/base/src/nsObjectLoadingContent.cpp b/content/base/src/nsObjectLoadingContent.cpp index 24b61628d454..fe502cf6bd4b 100644 --- a/content/base/src/nsObjectLoadingContent.cpp +++ b/content/base/src/nsObjectLoadingContent.cpp @@ -2570,6 +2570,8 @@ nsObjectLoadingContent::NotifyContentObjectWrapper() return; JSContext *cx = scx->GetNativeContext(); + nsCxPusher pusher; + pusher.Push(cx); nsCOMPtr wrapper; nsContentUtils::XPConnect()-> diff --git a/content/base/src/nsScriptLoader.cpp b/content/base/src/nsScriptLoader.cpp index 3c52647aca2a..48f7a5d30c96 100644 --- a/content/base/src/nsScriptLoader.cpp +++ b/content/base/src/nsScriptLoader.cpp @@ -822,6 +822,7 @@ nsScriptLoader::EvaluateScript(nsScriptLoadRequest* aRequest, if (!context) { return NS_ERROR_FAILURE; } + AutoPushJSContext cx(context->GetNativeContext()); bool oldProcessingScriptTag = context->GetProcessingScriptTag(); context->SetProcessingScriptTag(true); @@ -837,7 +838,7 @@ nsScriptLoader::EvaluateScript(nsScriptLoadRequest* aRequest, JSVersion version = JSVersion(aRequest->mJSVersion); if (version != JSVERSION_UNKNOWN) { - JS::CompileOptions options(context->GetNativeContext()); + JS::CompileOptions options(cx); options.setFileAndLine(url.get(), aRequest->mLineNo) .setVersion(JSVersion(aRequest->mJSVersion)); if (aRequest->mOriginPrincipal) { @@ -850,8 +851,6 @@ nsScriptLoader::EvaluateScript(nsScriptLoadRequest* aRequest, // Put the old script back in case it wants to do anything else. mCurrentScript = oldCurrent; - JSContext *cx = nullptr; // Initialize this to keep GCC happy. - cx = context->GetNativeContext(); JSAutoRequest ar(cx); context->SetProcessingScriptTag(oldProcessingScriptTag); return rv; diff --git a/content/base/src/nsTraversal.cpp b/content/base/src/nsTraversal.cpp index 5f2c3dcfe2a4..2cd51572fff0 100644 --- a/content/base/src/nsTraversal.cpp +++ b/content/base/src/nsTraversal.cpp @@ -10,12 +10,17 @@ #include "nsIDOMNodeFilter.h" #include "nsError.h" #include "nsINode.h" +#include "mozilla/dom/NodeFilterBinding.h" +#include "mozilla/AutoRestore.h" #include "nsGkAtoms.h" +using namespace mozilla; +using namespace mozilla::dom; + nsTraversal::nsTraversal(nsINode *aRoot, uint32_t aWhatToShow, - nsIDOMNodeFilter *aFilter) : + const NodeFilterHolder &aFilter) : mRoot(aRoot), mWhatToShow(aWhatToShow), mFilter(aFilter), @@ -40,8 +45,6 @@ nsresult nsTraversal::TestNode(nsINode* aNode, int16_t* _filtered) { NS_ENSURE_TRUE(!mInAcceptNode, NS_ERROR_DOM_INVALID_STATE_ERR); - nsresult rv; - *_filtered = nsIDOMNodeFilter::FILTER_SKIP; uint16_t nodeType = aNode->NodeType(); @@ -50,14 +53,22 @@ nsresult nsTraversal::TestNode(nsINode* aNode, int16_t* _filtered) return NS_OK; } - if (mFilter) { - nsCOMPtr domNode = do_QueryInterface(aNode); - mInAcceptNode = true; - rv = mFilter->AcceptNode(domNode, _filtered); - mInAcceptNode = false; - return rv; + if (!mFilter.GetISupports()) { + // No filter, just accept + *_filtered = nsIDOMNodeFilter::FILTER_ACCEPT; + return NS_OK; } - *_filtered = nsIDOMNodeFilter::FILTER_ACCEPT; - return NS_OK; + if (mFilter.HasWebIDLCallback()) { + AutoRestore inAcceptNode(mInAcceptNode); + mInAcceptNode = true; + ErrorResult res; + *_filtered = mFilter.GetWebIDLCallback()->AcceptNode(*aNode, res); + return res.ErrorCode(); + } + + nsCOMPtr domNode = do_QueryInterface(aNode); + AutoRestore inAcceptNode(mInAcceptNode); + mInAcceptNode = true; + return mFilter.GetXPCOMCallback()->AcceptNode(domNode, _filtered); } diff --git a/content/base/src/nsTraversal.h b/content/base/src/nsTraversal.h index f753d5885fcc..966972e15524 100644 --- a/content/base/src/nsTraversal.h +++ b/content/base/src/nsTraversal.h @@ -12,6 +12,8 @@ #define nsTraversal_h___ #include "nsCOMPtr.h" +#include "nsIDocument.h" +#include "mozilla/dom/CallbackObject.h" class nsINode; class nsIDOMNodeFilter; @@ -21,13 +23,13 @@ class nsTraversal public: nsTraversal(nsINode *aRoot, uint32_t aWhatToShow, - nsIDOMNodeFilter *aFilter); + const mozilla::dom::NodeFilterHolder &aFilter); virtual ~nsTraversal(); protected: nsCOMPtr mRoot; uint32_t mWhatToShow; - nsCOMPtr mFilter; + mozilla::dom::NodeFilterHolder mFilter; bool mInAcceptNode; /* diff --git a/content/base/src/nsTreeWalker.cpp b/content/base/src/nsTreeWalker.cpp index 20a54833ce8f..c6aaccf7515e 100644 --- a/content/base/src/nsTreeWalker.cpp +++ b/content/base/src/nsTreeWalker.cpp @@ -16,6 +16,9 @@ #include "nsINode.h" #include "nsDOMClassInfoID.h" #include "nsContentUtils.h" +#include "mozilla/dom/NodeFilterBinding.h" + +using namespace mozilla::dom; /* * Factories, constructors and destructors @@ -23,7 +26,7 @@ nsTreeWalker::nsTreeWalker(nsINode *aRoot, uint32_t aWhatToShow, - nsIDOMNodeFilter *aFilter) : + const NodeFilterHolder &aFilter) : nsTraversal(aRoot, aWhatToShow, aFilter), mCurrentNode(aRoot) { @@ -82,7 +85,7 @@ NS_IMETHODIMP nsTreeWalker::GetFilter(nsIDOMNodeFilter * *aFilter) { NS_ENSURE_ARG_POINTER(aFilter); - NS_IF_ADDREF(*aFilter = mFilter); + *aFilter = mFilter.ToXPCOMCallback().get(); return NS_OK; } diff --git a/content/base/src/nsTreeWalker.h b/content/base/src/nsTreeWalker.h index 3a42b42b5143..b2d477ce3106 100644 --- a/content/base/src/nsTreeWalker.h +++ b/content/base/src/nsTreeWalker.h @@ -29,7 +29,7 @@ public: nsTreeWalker(nsINode *aRoot, uint32_t aWhatToShow, - nsIDOMNodeFilter *aFilter); + const mozilla::dom::NodeFilterHolder &aFilter); virtual ~nsTreeWalker(); NS_DECL_CYCLE_COLLECTION_CLASS(nsTreeWalker) diff --git a/content/base/test/Makefile.in b/content/base/test/Makefile.in index 83153db85b86..df1dd041dfc7 100644 --- a/content/base/test/Makefile.in +++ b/content/base/test/Makefile.in @@ -556,9 +556,6 @@ MOCHITEST_FILES_C= \ test_bug708620.html \ file_bug708620.html \ file_bug708620-2.html \ - test_XHR_timeout.html \ - test_XHR_timeout.js \ - file_XHR_timeout.sjs \ test_bug717511.html \ file_bug717511.html \ file_bug717511.html^headers^ \ @@ -668,6 +665,11 @@ MOCHITEST_FILES_PARTS = $(foreach s,A B C,MOCHITEST_FILES_$(s)) # test_bug503473.html \ # file_bug503473-frame.sjs \ +# Disabled for frequent failures (bug 841505, bug 842344, etc) +# test_XHR_timeout.html \ +# test_XHR_timeout.js \ +# file_XHR_timeout.sjs \ + MOCHITEST_BROWSER_FILES = \ browser_bug593387.js \ $(NULL) diff --git a/content/base/test/test_mutationobservers.html b/content/base/test/test_mutationobservers.html index d784f19d1cd4..4f77960a3f6c 100644 --- a/content/base/test/test_mutationobservers.html +++ b/content/base/test/test_mutationobservers.html @@ -18,7 +18,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=641821
 
+  
+  
+
+
+Mozilla Bug 839371
+

+ +
+
+
+ + diff --git a/content/html/content/test/test_fullscreen-api.html b/content/html/content/test/test_fullscreen-api.html index da90b2d61367..1351fc06985b 100644 --- a/content/html/content/test/test_fullscreen-api.html +++ b/content/html/content/test/test_fullscreen-api.html @@ -21,6 +21,8 @@
 
 
 
diff --git a/content/html/document/test/test_bug402680.html b/content/html/document/test/test_bug402680.html
index badd6468a1bf..191d2173d731 100644
--- a/content/html/document/test/test_bug402680.html
+++ b/content/html/document/test/test_bug402680.html
@@ -21,10 +21,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=402680
 
 
   
 
 
diff --git a/dom/tests/mochitest/bugs/test_bug427744.html b/dom/tests/mochitest/bugs/test_bug427744.html index 52f702bb65e9..02311bcf15af 100644 --- a/dom/tests/mochitest/bugs/test_bug427744.html +++ b/dom/tests/mochitest/bugs/test_bug427744.html @@ -17,10 +17,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=427744
 
 
 
diff --git a/dom/tests/mochitest/bugs/test_bug440572.html b/dom/tests/mochitest/bugs/test_bug440572.html
index 8d4ec07a0c97..db6e4a8f3733 100644
--- a/dom/tests/mochitest/bugs/test_bug440572.html
+++ b/dom/tests/mochitest/bugs/test_bug440572.html
@@ -13,10 +13,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=440572
 
 
diff --git a/dom/tests/mochitest/bugs/test_bug583225.html b/dom/tests/mochitest/bugs/test_bug583225.html
index eca3c028ed6e..a174d589b93d 100644
--- a/dom/tests/mochitest/bugs/test_bug583225.html
+++ b/dom/tests/mochitest/bugs/test_bug583225.html
@@ -17,10 +17,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=583225
 
 
 
 
+  
+  
+
+
+Mozilla Bug 844684
+
+ +
+
+
+
+ + diff --git a/intl/build/nsI18nModule.cpp b/intl/build/nsI18nModule.cpp index 2295e27390a0..b71d96902f46 100644 --- a/intl/build/nsI18nModule.cpp +++ b/intl/build/nsI18nModule.cpp @@ -7,7 +7,7 @@ // lwbrk #include "nsLWBrkCIID.h" -#include "nsJISx4501LineBreaker.h" +#include "nsJISx4051LineBreaker.h" #include "nsSampleWordBreaker.h" #include "nsSemanticUnitScanner.h" diff --git a/intl/lwbrk/public/nsILineBreaker.h b/intl/lwbrk/public/nsILineBreaker.h index 13652384b9eb..b61b5c29a19f 100644 --- a/intl/lwbrk/public/nsILineBreaker.h +++ b/intl/lwbrk/public/nsILineBreaker.h @@ -33,7 +33,7 @@ public: virtual int32_t Prev( const PRUnichar* aText, uint32_t aLen, uint32_t aPos) = 0; - // Call this on a word with whitespace at either end. We will apply JISx4501 + // Call this on a word with whitespace at either end. We will apply JISx4051 // rules to find breaks inside the word. aBreakBefore is set to the break- // before status of each character; aBreakBefore[0] will always be false // because we never return a break before the first character. diff --git a/intl/lwbrk/src/Makefile.in b/intl/lwbrk/src/Makefile.in index 8c2b7b74a8b3..a39749f4b655 100644 --- a/intl/lwbrk/src/Makefile.in +++ b/intl/lwbrk/src/Makefile.in @@ -17,7 +17,7 @@ LIBXUL_LIBRARY = 1 CPPSRCS = \ - nsJISx4501LineBreaker.cpp \ + nsJISx4051LineBreaker.cpp \ nsSampleWordBreaker.cpp \ nsSemanticUnitScanner.cpp \ $(NULL) diff --git a/intl/lwbrk/src/jisx4501class.h b/intl/lwbrk/src/jisx4051class.h similarity index 99% rename from intl/lwbrk/src/jisx4501class.h rename to intl/lwbrk/src/jisx4051class.h index dfee5774526c..73be214e5e92 100644 --- a/intl/lwbrk/src/jisx4501class.h +++ b/intl/lwbrk/src/jisx4051class.h @@ -4,7 +4,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* DO NOT EDIT THIS DOCUMENT !!! THIS DOCUMENT IS GENERATED BY - mozilla/intl/lwbrk/tools/anzx4501.pl + mozilla/intl/lwbrk/tools/anzx4051.pl */ static const uint32_t gLBClass00[32] = { 0x55555555, // U+0000 - U+0007 diff --git a/intl/lwbrk/src/nsJISx4501LineBreaker.cpp b/intl/lwbrk/src/nsJISx4051LineBreaker.cpp similarity index 99% rename from intl/lwbrk/src/nsJISx4501LineBreaker.cpp rename to intl/lwbrk/src/nsJISx4051LineBreaker.cpp index b3a83a14b535..be076aa78096 100644 --- a/intl/lwbrk/src/nsJISx4501LineBreaker.cpp +++ b/intl/lwbrk/src/nsJISx4051LineBreaker.cpp @@ -5,9 +5,9 @@ -#include "nsJISx4501LineBreaker.h" +#include "nsJISx4051LineBreaker.h" -#include "jisx4501class.h" +#include "jisx4051class.h" #include "nsComplexBreaker.h" #include "nsTArray.h" #include "nsUnicharUtils.h" diff --git a/intl/lwbrk/src/nsJISx4501LineBreaker.h b/intl/lwbrk/src/nsJISx4051LineBreaker.h similarity index 91% rename from intl/lwbrk/src/nsJISx4501LineBreaker.h rename to intl/lwbrk/src/nsJISx4051LineBreaker.h index 89cee692aed3..1898d6a883fd 100644 --- a/intl/lwbrk/src/nsJISx4501LineBreaker.h +++ b/intl/lwbrk/src/nsJISx4051LineBreaker.h @@ -2,8 +2,8 @@ /* 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 nsJISx4501LineBreaker_h__ -#define nsJISx4501LineBreaker_h__ +#ifndef nsJISx4051LineBreaker_h__ +#define nsJISx4051LineBreaker_h__ #include "nsILineBreaker.h" @@ -32,4 +32,4 @@ private: int8_t aDirection); }; -#endif /* nsJISx4501LineBreaker_h__ */ +#endif /* nsJISx4051LineBreaker_h__ */ diff --git a/intl/lwbrk/src/nsLWIMP.h b/intl/lwbrk/src/nsLWIMP.h deleted file mode 100644 index c5ab96b741d2..000000000000 --- a/intl/lwbrk/src/nsLWIMP.h +++ /dev/null @@ -1,14 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#ifndef nsLWIMP_h__ -#define nsLWIMP_h__ - -#include "nsJISx4501LineBreaker.h" -#include "nsSampleWordBreaker.h" - -#define LINEBREAKER nsJISx4501LineBreaker -#define WORDBREAKER nsSampleWordBreaker - -#endif /* nsLWIMP_h__ */ diff --git a/intl/lwbrk/tools/anzx4501.html b/intl/lwbrk/tools/anzx4051.html similarity index 100% rename from intl/lwbrk/tools/anzx4501.html rename to intl/lwbrk/tools/anzx4051.html diff --git a/intl/lwbrk/tools/anzx4501.pl b/intl/lwbrk/tools/anzx4051.pl similarity index 95% rename from intl/lwbrk/tools/anzx4501.pl rename to intl/lwbrk/tools/anzx4051.pl index 4b994c14b077..f42deaf22ce0 100644 --- a/intl/lwbrk/tools/anzx4501.pl +++ b/intl/lwbrk/tools/anzx4051.pl @@ -26,32 +26,32 @@ open ( UNICODATA , "< ../../unicharutil/tools/UnicodeData-Latest.txt") # Open the JIS X 4051 Class file # ###################################################################### -open ( CLASS , "< jisx4501class.txt") - || die "cannot find jisx4501class.txt"; +open ( CLASS , "< jisx4051class.txt") + || die "cannot find jisx4051class.txt"; ###################################################################### # # Open the JIS X 4051 Class simplified mapping # ###################################################################### -open ( SIMP , "< jisx4501simp.txt") - || die "cannot find jisx4501simp.txt"; +open ( SIMP , "< jisx4051simp.txt") + || die "cannot find jisx4051simp.txt"; ###################################################################### # # Open the output file # ###################################################################### -open ( OUT , "> anzx4501.html") - || die "cannot open output anzx4501.html file"; +open ( OUT , "> anzx4051.html") + || die "cannot open output anzx4051.html file"; ###################################################################### # # Open the output file # ###################################################################### -open ( HEADER , "> ../src/jisx4501class.h") - || die "cannot open output ../src/jisx4501class.h file"; +open ( HEADER , "> ../src/jisx4051class.h") + || die "cannot open output ../src/jisx4051class.h file"; ###################################################################### # @@ -88,7 +88,7 @@ $npl = < struct DefaultHasher : PointerHasher::result> {}; +// For doubles, we can xor the two uint32s. +template <> +struct DefaultHasher +{ + typedef double Lookup; + static HashNumber hash(double d) { + JS_STATIC_ASSERT(sizeof(HashNumber) == 4); + union { + struct { + uint32_t lo; + uint32_t hi; + } s; + double d; + } u; + u.d = d; + return u.s.lo ^ u.s.hi; + } + static bool match(double lhs, double rhs) { + return lhs == rhs; + } +}; + /*****************************************************************************/ // Both HashMap and HashSet are implemented by a single HashTable that is even @@ -1343,6 +1369,12 @@ class HashTable : private AllocPolicy return Ptr(lookup(l, keyHash, 0)); } + Ptr readonlyThreadsafeLookup(const Lookup &l) const + { + HashNumber keyHash = prepareHash(l); + return Ptr(lookup(l, keyHash, 0)); + } + AddPtr lookupForAdd(const Lookup &l) const { ReentrancyGuard g(*this); diff --git a/js/src/assembler/assembler/X86Assembler.h b/js/src/assembler/assembler/X86Assembler.h index 8e2d614ea26f..326baf5da6f7 100644 --- a/js/src/assembler/assembler/X86Assembler.h +++ b/js/src/assembler/assembler/X86Assembler.h @@ -2453,6 +2453,11 @@ public: m_formatter.jumpTablePointer(ptr); } + void doubleConstant(double d) + { + m_formatter.doubleConstant(d); + } + // Linking & patching: // // 'link' and 'patch' methods are for use on unprotected code - such as the code @@ -3012,6 +3017,17 @@ private: #endif } + void doubleConstant(double d) + { + m_buffer.ensureSpace(sizeof(double)); + union { + uint64_t u64; + double d; + } u; + u.d = d; + m_buffer.putInt64Unchecked(u.u64); + } + // Administrative methods: size_t size() const { return m_buffer.size(); } diff --git a/js/src/build/autoconf/config.status.m4 b/js/src/build/autoconf/config.status.m4 index 25ce7db6063c..1a5700082fd5 100644 --- a/js/src/build/autoconf/config.status.m4 +++ b/js/src/build/autoconf/config.status.m4 @@ -113,6 +113,12 @@ sed 's/$/,/' >> $CONFIG_STATUS <> $CONFIG_STATUS +done + cat >> $CONFIG_STATUS <<\EOF ] ] diff --git a/js/src/build/autoconf/subconfigure.m4 b/js/src/build/autoconf/subconfigure.m4 new file mode 100644 index 000000000000..56c00cfce9d7 --- /dev/null +++ b/js/src/build/autoconf/subconfigure.m4 @@ -0,0 +1,46 @@ +dnl We are not running in a real autoconf environment. So we're using real m4 +dnl here, not the crazier environment that autoconf provides. + +dnl Autoconf expects [] for quotes; give it to them +changequote([, ]) + +dnl AC_DEFUN is provided to use instead of define in autoconf. Provide it too. +define([AC_DEFUN], [define($1, [$2])]) + +dnl AC_ARG_ENABLE(FEATURE, HELP-STRING, IF-TRUE[, IF-FALSE]) +dnl We have to ignore the help string due to how help works in autoconf... +AC_DEFUN([AC_ARG_ENABLE], +[#] Check whether --enable-[$1] or --disable-[$1] was given. +[if test "[${enable_]patsubst([$1], -, _)+set}" = set; then + enableval="[$enable_]patsubst([$1], -, _)" + $3 +ifelse([$4], , , [else + $4 +])dnl +fi +]) + +dnl AC_MSG_ERROR(error-description) +AC_DEFUN([AC_MSG_ERROR], [{ echo "configure: error: $1" 1>&2; exit 1; }]) + +AC_DEFUN([AC_MSG_WARN], [ echo "configure: warning: $1" 1>&2 ]) + +dnl Add the variable to the list of substitution variables +AC_DEFUN([AC_SUBST], +[ +_subconfigure_ac_subst_args="$_subconfigure_ac_subst_args $1" +]) + +dnl Override for AC_DEFINE. +AC_DEFUN([AC_DEFINE], +[ +cat >>confdefs.h <<\EOF +[#define] $1 ifelse($#, 2, [$2], $#, 3, [$2], 1) +EOF +cat >> confdefs.pytmp <<\EOF + (''' $1 ''', ifelse($#, 2, [r''' $2 '''], $#, 3, [r''' $2 '''], ' 1 ')) +EOF +]) + +dnl AC_OUTPUT_SUBDIRS(subdirectory) +AC_DEFUN([AC_OUTPUT_SUBDIRS], [do_output_subdirs "$1"]) diff --git a/js/src/builtin/Intl.cpp b/js/src/builtin/Intl.cpp index 78980d4a24dd..2b0bd8a6b5c5 100644 --- a/js/src/builtin/Intl.cpp +++ b/js/src/builtin/Intl.cpp @@ -164,7 +164,7 @@ InitCollatorClass(JSContext *cx, HandleObject Intl, Handle global * passing to methods like Array.prototype.sort). */ RootedValue getter(cx); - if (!cx->global()->getIntrinsicValue(cx, cx->names().CollatorCompare, &getter)) + if (!cx->global()->getIntrinsicValue(cx, cx->names().CollatorCompareGet, &getter)) return NULL; RootedValue undefinedValue(cx, UndefinedValue()); if (!JSObject::defineProperty(cx, proto, cx->names().compare, undefinedValue, @@ -316,7 +316,7 @@ InitNumberFormatClass(JSContext *cx, HandleObject Intl, Handle gl * for passing to methods like Array.prototype.map). */ RootedValue getter(cx); - if (!cx->global()->getIntrinsicValue(cx, cx->names().NumberFormatFormat, &getter)) + if (!cx->global()->getIntrinsicValue(cx, cx->names().NumberFormatFormatGet, &getter)) return NULL; RootedValue undefinedValue(cx, UndefinedValue()); if (!JSObject::defineProperty(cx, proto, cx->names().format, undefinedValue, @@ -468,7 +468,7 @@ InitDateTimeFormatClass(JSContext *cx, HandleObject Intl, Handle * (suitable for passing to methods like Array.prototype.map). */ RootedValue getter(cx); - if (!cx->global()->getIntrinsicValue(cx, cx->names().DateTimeFormatFormat, &getter)) + if (!cx->global()->getIntrinsicValue(cx, cx->names().DateTimeFormatFormatGet, &getter)) return NULL; RootedValue undefinedValue(cx, UndefinedValue()); if (!JSObject::defineProperty(cx, proto, cx->names().format, undefinedValue, diff --git a/js/src/builtin/Intl.js b/js/src/builtin/Intl.js index 5c3acf22c5f0..8d05478ffde1 100644 --- a/js/src/builtin/Intl.js +++ b/js/src/builtin/Intl.js @@ -376,6 +376,43 @@ function CanonicalizeLanguageTag(locale) { } +// mappings from some commonly used old-style language tags to current flavors +// with script codes +var oldStyleLanguageTagMappings = { + "pa-PK": "pa-Arab-PK", + "zh-CN": "zh-Hans-CN", + "zh-HK": "zh-Hant-HK", + "zh-SG": "zh-Hans-SG", + "zh-TW": "zh-Hant-TW" +}; + + +/** + * Returns the BCP 47 language tag for the host environment's current locale. + * + * Spec: ECMAScript Internationalization API Specification, 6.2.4. + */ +function DefaultLocale() { + var localeOfLastResort = "und"; + + var locale = RuntimeDefaultLocale(); + if (!IsStructurallyValidLanguageTag(locale)) + return localeOfLastResort; + + locale = CanonicalizeLanguageTag(locale); + if (callFunction(std_Object_hasOwnProperty, oldStyleLanguageTagMappings, locale)) + locale = oldStyleLanguageTagMappings[locale]; + + if (!(collatorInternalProperties.availableLocales[locale] && + numberFormatInternalProperties.availableLocales[locale] && + dateTimeFormatInternalProperties.availableLocales[locale])) + { + locale = localeOfLastResort; + } + return locale; +} + + /** * Verifies that the given string is a well-formed ISO 4217 currency code. * @@ -401,17 +438,12 @@ function IsWellFormedCurrencyCode(currency) { * Spec: ECMAScript Internationalization API Specification, 9.1. */ function addOldStyleLanguageTags(availableLocales) { - // checking for commonly used old-style language tags only - if (availableLocales["pa-Arab-PK"]) - availableLocales["pa-PK"] = true; - if (availableLocales["zh-Hans-CN"]) - availableLocales["zh-CN"] = true; - if (availableLocales["zh-Hans-SG"]) - availableLocales["zh-SG"] = true; - if (availableLocales["zh-Hant-HK"]) - availableLocales["zh-HK"] = true; - if (availableLocales["zh-Hant-TW"]) - availableLocales["zh-TW"] = true; + var oldStyleLocales = std_Object_getOwnPropertyNames(oldStyleLanguageTagMappings); + for (var i = 0; i < oldStyleLocales.length; i++) { + var oldStyleLocale = oldStyleLocales[i]; + if (availableLocales[oldStyleLanguageTagMappings[oldStyleLocale]]) + availableLocales[oldStyleLocale] = true; + } return availableLocales; } @@ -503,16 +535,16 @@ function LookupMatcher(availableLocales, requestedLocales) { var result = new Record(); if (availableLocale !== undefined) { - result.__locale = availableLocale; + result.locale = availableLocale; if (locale !== noExtensionsLocale) { var extensionMatch = callFunction(std_String_match, locale, unicodeLocaleExtensionSequenceRE); var extension = extensionMatch[0]; var extensionIndex = extensionMatch.index; - result.__extension = extension; - result.__extensionIndex = extensionIndex; + result.extension = extension; + result.extensionIndex = extensionIndex; } } else { - result.__locale = DefaultLocale(); + result.locale = DefaultLocale(); } return result; } @@ -531,3 +563,652 @@ function BestFitMatcher(availableLocales, requestedLocales) { // this implementation doesn't have anything better return LookupMatcher(availableLocales, requestedLocales); } + + +/** + * Compares a BCP 47 language priority list against availableLocales and + * determines the best available language to meet the request. Options specified + * through Unicode extension subsequences are negotiated separately, taking the + * caller's relevant extensions and locale data as well as client-provided + * options into consideration. + * + * Spec: ECMAScript Internationalization API Specification, 9.2.5. + */ +function ResolveLocale(availableLocales, requestedLocales, options, relevantExtensionKeys, localeData) { + /*jshint laxbreak: true */ + + // Steps 1-3. + var matcher = options.localeMatcher; + var r = (matcher === "lookup") + ? LookupMatcher(availableLocales, requestedLocales) + : BestFitMatcher(availableLocales, requestedLocales); + + // Step 4. + var foundLocale = r.locale; + + // Step 5.a. + var extension = r.extension; + var extensionIndex, extensionSubtags, extensionSubtagsLength; + + // Step 5. + if (extension !== undefined) { + // Step 5.b. + extensionIndex = r.extensionIndex; + + // Steps 5.d-e. + extensionSubtags = callFunction(std_String_split, extension, "-"); + extensionSubtagsLength = extensionSubtags.length; + } + + // Steps 6-7. + var result = new Record(); + result.dataLocale = foundLocale; + + // Step 8. + var supportedExtension = "-u"; + + // Steps 9-11. + var i = 0; + var len = relevantExtensionKeys.length; + while (i < len) { + // Steps 11.a-c. + var key = relevantExtensionKeys[i]; + + // In this implementation, localeData is a function, not an object. + var foundLocaleData = localeData(foundLocale); + var keyLocaleData = foundLocaleData[key]; + + // Locale data provides default value. + // Step 11.d. + var value = keyLocaleData[0]; + + // Locale tag may override. + + // Step 11.e. + var supportedExtensionAddition = ""; + + // Step 11.f is implemented by Utilities.js. + + var valuePos; + + // Step 11.g. + if (extensionSubtags !== undefined) { + // Step 11.g.i. + var keyPos = callFunction(std_Array_indexOf, extensionSubtags, key); + + // Step 11.g.ii. + if (keyPos !== -1) { + // Step 11.g.ii.1. + if (keyPos + 1 < extensionSubtagsLength && + extensionSubtags[keyPos + 1].length > 2) + { + // Step 11.g.ii.1.a. + var requestedValue = extensionSubtags[keyPos + 1]; + + // Step 11.g.ii.1.b. + valuePos = callFunction(std_Array_indexOf, keyLocaleData, requestedValue); + + // Step 11.g.ii.1.c. + if (valuePos !== -1) { + value = requestedValue; + supportedExtensionAddition = "-" + key + "-" + value; + } + } else { + // Step 11.g.ii.2. + + // According to the LDML spec, if there's no type value, + // and true is an allowed value, it's used. + + // Step 11.g.ii.2.a. + valuePos = callFunction(std_Array_indexOf, keyLocaleData, "true"); + + // Step 11.g.ii.2.b. + if (valuePos !== -1) + value = "true"; + } + } + } + + // Options override all. + + // Step 11.h.i. + var optionsValue = options[key]; + + // Step 11.h, 11.h.ii. + if (optionsValue !== undefined && + callFunction(std_Array_indexOf, keyLocaleData, optionsValue) !== -1) + { + // Step 11.h.ii.1. + if (optionsValue !== value) { + value = optionsValue; + supportedExtensionAddition = ""; + } + } + + // Steps 11.i-k. + result[key] = value; + supportedExtension += supportedExtensionAddition; + i++; + } + + // Step 12. + if (supportedExtension.length > 2) { + var preExtension = callFunction(std_String_substring, foundLocale, 0, extensionIndex); + var postExtension = callFunction(std_String_substring, foundLocale, extensionIndex); + foundLocale = preExtension + supportedExtension + postExtension; + } + + // Steps 13-14. + result.locale = foundLocale; + return result; +} + + +/** + * Returns the subset of requestedLocales for which availableLocales has a + * matching (possibly fallback) locale. Locales appear in the same order in the + * returned list as in the input list. + * + * Spec: ECMAScript Internationalization API Specification, 9.2.6. + */ +function LookupSupportedLocales(availableLocales, requestedLocales) { + // Steps 1-2. + var len = requestedLocales.length; + var subset = new List(); + + // Steps 3-4. + var k = 0; + while (k < len) { + // Steps 4.a-b. + var locale = requestedLocales[k]; + var noExtensionsLocale = callFunction(std_String_replace, locale, unicodeLocaleExtensionSequenceGlobalRE, ""); + + // Step 4.c-d. + var availableLocale = BestAvailableLocale(availableLocales, noExtensionsLocale); + if (availableLocale !== undefined) + subset.push(locale); + + // Step 4.e. + k++; + } + + // Steps 5-6. + return subset.slice(0); +} + + +/** + * Returns the subset of requestedLocales for which availableLocales has a + * matching (possibly fallback) locale. Locales appear in the same order in the + * returned list as in the input list. + * + * Spec: ECMAScript Internationalization API Specification, 9.2.7. + */ +function BestFitSupportedLocales(availableLocales, requestedLocales) { + // don't have anything better + return LookupSupportedLocales(availableLocales, requestedLocales); +} + + +/** + * Returns the subset of requestedLocales for which availableLocales has a + * matching (possibly fallback) locale. Locales appear in the same order in the + * returned list as in the input list. + * + * Spec: ECMAScript Internationalization API Specification, 9.2.8. + */ +function SupportedLocales(availableLocales, requestedLocales, options) { + /*jshint laxbreak: true */ + + // Step 1. + var matcher; + if (options !== undefined) { + // Steps 1.a-b. + options = ToObject(options); + matcher = options.localeMatcher; + + // Step 1.c. + if (matcher !== undefined) { + matcher = ToString(matcher); + if (matcher !== "lookup" && matcher !== "best fit") + ThrowError(JSMSG_INVALID_LOCALE_MATCHER, matcher); + } + } + + // Steps 2-3. + var subset = (matcher === undefined || matcher === "best fit") + ? BestFitSupportedLocales(availableLocales, requestedLocales) + : LookupSupportedLocales(availableLocales, requestedLocales); + + // Step 4. + for (var i = 0; i < subset.length; i++) + std_Object_defineProperty(subset, i, {value: subset[i], writable: false, enumerable: true, configurable: false}); +// ??? commented out because of SpiderMonkey bugs 591059 and 598996 +// std_Object_defineProperty(subset, "length", {value: subset.length, writable: false, enumerable: false, configurable: false}); + + // Step 5. + return subset; +} + + +/** + * Extracts a property value from the provided options object, converts it to + * the required type, checks whether it is one of a list of allowed values, + * and fills in a fallback value if necessary. + * + * Spec: ECMAScript Internationalization API Specification, 9.2.9. + */ +function GetOption(options, property, type, values, fallback) { + // Step 1. + var value = options[property]; + + // Step 2. + if (value !== undefined) { + // Steps 2.a-c. + if (type === "boolean") + value = ToBoolean(value); + else if (type === "string") + value = ToString(value); + else + assert(false, "GetOption"); + + // Step 2.d. + if (values !== undefined && callFunction(std_Array_indexOf, values, value) === -1) + ThrowError(JSMSG_INVALID_OPTION_VALUE, property, value); + + // Step 2.e. + return value; + } + + // Step 3. + return fallback; +} + +/** + * Extracts a property value from the provided options object, converts it to a + * Number value, checks whether it is in the allowed range, and fills in a + * fallback value if necessary. + * + * Spec: ECMAScript Internationalization API Specification, 9.2.10. + */ +function GetNumberOption(options, property, minimum, maximum, fallback) { + assert(typeof minimum === "number", "GetNumberOption"); + assert(typeof maximum === "number", "GetNumberOption"); + assert(fallback === undefined || (fallback >= minimum && fallback <= maximum), "GetNumberOption"); + + // Step 1. + var value = options[property]; + + // Step 2. + if (value !== undefined) { + value = ToNumber(value); + if (std_isNaN(value) || value < minimum || value > maximum) + ThrowError(JSMSG_INVALID_DIGITS_VALUE, value); + return std_Math_floor(value); + } + + // Step 3. + return fallback; +} + + +// ??? stub +var runtimeAvailableLocales = (function () { + var o = std_Object_create(null); + o[RuntimeDefaultLocale()] = true; + return addOldStyleLanguageTags(o); +}()); + + +/********** Property access for Intl objects **********/ + + +/** + * Set a normal public property p of o to value v, but use Object.defineProperty + * to avoid interference from setters on Object.prototype. + */ +function defineProperty(o, p, v) { + std_Object_defineProperty(o, p, {value: v, writable: true, enumerable: true, configurable: true}); +} + + +/** + * Weak map holding objects with the properties specified as "internal" for + * all Intl API objects. Presence of an object as a key within this map is + * considered equivalent to having the [[initializedIntlObject]] internal + * property set to true on this object. + * + * Ideally we'd be using private symbols for internal properties, but + * SpiderMonkey doesn't have those yet. + */ +var internalsMap = new WeakMap(); + + +/** + * Create an object holding the properties specified as "internal" for + * an Intl API object. This call is equivalent to setting the + * [[initializedIntlObject]] internal property of o to true. + */ +function initializeIntlObject(o) { + assert(IsObject(o), "initializeIntlObject"); + var internals = std_Object_create(null); + callFunction(std_WeakMap_set, internalsMap, o, internals); + return internals; +} + + +/** + * Return whether the object has been initialized as an Intl object, equivalent + * to testing whether the [[initializedIntlObject]] internal property of o is + * true. + */ +function isInitializedIntlObject(o) { + return callFunction(std_WeakMap_has, internalsMap, o); +} + + +/** + * Returns the object holding the internal properties of o. + */ +function getInternals(o) { + return callFunction(std_WeakMap_get, internalsMap, o); +} + + +/** + * Check that the object on which certain functions are called + * meet the requirements for "this Collator object", "this NumberFormat object", + * "this DateTimeFormat object". If it meets the requirements, return the + * object holding its internal properties. + * + * Spec: ECMAScript Internationalization API Specification, 10.3. + * Spec: ECMAScript Internationalization API Specification, 11.3. + * Spec: ECMAScript Internationalization API Specification, 12.3. + */ +function checkIntlAPIObject(o, className, methodName) { + assert(typeof className === "string", "checkIntlAPIObject"); + var internals = getInternals(o); + if (internals === undefined || internals["initialized" + className] !== true) + ThrowError(JSMSG_INTL_OBJECT_NOT_INITED, className, methodName, className); + assert(IsObject(o), "checkIntlAPIObject"); + return internals; +} + + +/********** Intl.Collator **********/ + + +/** + * Mapping from Unicode extension keys for collation to options properties, + * their types and permissible values. + * + * Spec: ECMAScript Internationalization API Specification, 10.1.1. + */ +var collatorKeyMappings = { + kn: {property: "numeric", type: "boolean"}, + kf: {property: "caseFirst", type: "string", values: ["upper", "lower", "false"]} +}; + + +/** + * Initializes an object as a Collator. + * + * Spec: ECMAScript Internationalization API Specification, 10.1.1. + */ +function InitializeCollator(collator, locales, options) { + assert(IsObject(collator), "InitializeCollator"); + + // Step 1. + if (isInitializedIntlObject(collator)) + ThrowError(JSMSG_INTL_OBJECT_REINITED); + + // Step 2. + var internals = initializeIntlObject(collator); + + // Step 3. + var requestedLocales = CanonicalizeLocaleList(locales); + + // Steps 4-5. + if (options === undefined) + options = {}; + else + options = ToObject(options); + + // Compute options that impact interpretation of locale. + // Steps 6-7. + var u = GetOption(options, "usage", "string", ["sort", "search"], "sort"); + internals.usage = u; + + // Step 8. + var Collator = collatorInternalProperties; + + // Step 9. + var localeData = u === "sort" ? Collator.sortLocaleData : Collator.searchLocaleData; + + // Step 10. + var opt = new Record(); + + // Steps 11-12. + var matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit"); + opt.localeMatcher = matcher; + + // Check all allowed options properties and convert them to extension keys. + // Steps 13-13.a. + var key, mapping, property, value; + for (key in collatorKeyMappings) { + if (callFunction(std_Object_hasOwnProperty, collatorKeyMappings, key)) { + mapping = collatorKeyMappings[key]; + + // Step 13.b. + value = GetOption(options, mapping.property, mapping.type, mapping.values, undefined); + + // Step 13.c. + if (mapping.type === "boolean" && value !== undefined) + value = callFunction(std_Boolean_toString, value); + + // Step 13.d. + opt[key] = value; + } + } + + // Compute effective locale. + // Step 14. + var relevantExtensionKeys = Collator.relevantExtensionKeys; + + // Step 15. + var r = ResolveLocale(Collator.availableLocales, + requestedLocales, opt, + relevantExtensionKeys, + localeData); + // Step 16. + internals.locale = r.locale; + + // Steps 17-19. + var i = 0, len = relevantExtensionKeys.length; + while (i < len) { + // Step 19.a. + key = relevantExtensionKeys[i]; + if (key === "co") { + // Step 19.b. + property = "collation"; + value = r.co === null ? "default" : r.co; + } else { + // Step 19.c. + mapping = collatorKeyMappings[key]; + property = mapping.property; + value = r[key]; + if (mapping.type === "boolean") + value = value === "true"; + } + + // Step 19.d. + internals[property] = value; + + // Step 19.e. + i++; + } + + // Compute remaining collation options. + // Steps 20-21. + var s = GetOption(options, "sensitivity", "string", + ["base", "accent", "case", "variant"], undefined); + if (s === undefined) { + if (u === "sort") { + // Step 21.a. + s = "variant"; + } else { + // Step 21.b. + var dataLocale = r.dataLocale; + var dataLocaleData = localeData(dataLocale); + s = dataLocaleData.sensitivity; + } + } + + // Step 22. + internals.sensitivity = s; + + // Steps 23-24. + var ip = GetOption(options, "ignorePunctuation", "boolean", undefined, false); + internals.ignorePunctuation = ip; + + // Step 25. + internals.boundFormat = undefined; + + // Step 26. + internals.initializedCollator = true; +} + + +/** + * Returns the subset of the given locale list for which this locale list has a + * matching (possibly fallback) locale. Locales appear in the same order in the + * returned list as in the input list. + * + * Spec: ECMAScript Internationalization API Specification, 10.2.2. + */ +function Intl_Collator_supportedLocalesOf(locales /*, options*/) { + var options = arguments.length > 1 ? arguments[1] : undefined; + + var availableLocales = collatorInternalProperties.availableLocales; + var requestedLocales = CanonicalizeLocaleList(locales); + return SupportedLocales(availableLocales, requestedLocales, options); +} + + +/** + * Collator internal properties. + * + * Spec: ECMAScript Internationalization API Specification, 9.1 and 10.2.3. + */ +var collatorInternalProperties = { + sortLocaleData: collatorSortLocaleData, + searchLocaleData: collatorSearchLocaleData, + availableLocales: runtimeAvailableLocales, // stub + relevantExtensionKeys: ["co", "kn"] +}; + + +function collatorSortLocaleData(locale) { + // the following data may or may not match any actual locale support + return { + co: [null], + kn: ["false", "true"] + }; +} + + +function collatorSearchLocaleData(locale) { + // the following data may or may not match any actual locale support + return { + co: [null], + kn: ["false", "true"], + sensitivity: "variant" + }; +} + + +/** + * Function to be bound and returned by Intl.Collator.prototype.format. + * + * Spec: ECMAScript Internationalization API Specification, 12.3.2. + */ +function collatorCompareToBind(x, y) { + // Steps 1.a.i-ii implemented by ECMAScript declaration binding instantiation, + // ES5.1 10.5, step 4.d.ii. + + // Step 1.a.iii-v. + var X = ToString(x); + var Y = ToString(y); + return CompareStrings(this, X, Y); +} + + +/** + * Returns a function bound to this Collator that compares x (converted to a + * String value) and y (converted to a String value), + * and returns a number less than 0 if x < y, 0 if x = y, or a number greater + * than 0 if x > y according to the sort order for the locale and collation + * options of this Collator object. + * + * Spec: ECMAScript Internationalization API Specification, 10.3.2. + */ +function Intl_Collator_compare_get() { + // Check "this Collator object" per introduction of section 10.3. + var internals = checkIntlAPIObject(this, "Collator", "compare"); + + // Step 1. + if (internals.boundCompare === undefined) { + // Step 1.a. + var F = collatorCompareToBind; + + // Step 1.b-d. + var bc = callFunction(std_Function_bind, F, this); + internals.boundCompare = bc; + } + + // Step 2. + return internals.boundCompare; +} + + +/** + * Compares x (converted to a String value) and y (converted to a String value), + * and returns a number less than 0 if x < y, 0 if x = y, or a number greater + * than 0 if x > y according to the sort order for the locale and collation + * options of this Collator object. + * + * Spec: ECMAScript Internationalization API Specification, 10.3.2. + */ +function CompareStrings(collator, x, y) { + assert(typeof x === "string", "CompareStrings"); + assert(typeof y === "string", "CompareStrings"); + + // ??? stub + return x.localeCompare(y); +} + + +/** + * Returns the resolved options for a Collator object. + * + * Spec: ECMAScript Internationalization API Specification, 10.3.3 and 10.4. + */ +function Intl_Collator_resolvedOptions() { + // Check "this Collator object" per introduction of section 10.3. + var internals = checkIntlAPIObject(this, "Collator", "resolvedOptions"); + + var result = { + locale: internals.locale, + usage: internals.usage, + sensitivity: internals.sensitivity, + ignorePunctuation: internals.ignorePunctuation + }; + + var relevantExtensionKeys = collatorInternalProperties.relevantExtensionKeys; + for (var i = 0; i < relevantExtensionKeys.length; i++) { + var key = relevantExtensionKeys[i]; + var property = (key === "co") ? "collation" : collatorKeyMappings[key].property; + defineProperty(result, property, internals[property]); + } + return result; +} diff --git a/js/src/builtin/Utilities.js b/js/src/builtin/Utilities.js index bf37ff38a0ee..fa1bb98a085d 100644 --- a/js/src/builtin/Utilities.js +++ b/js/src/builtin/Utilities.js @@ -50,6 +50,9 @@ var std_String_startsWith = String.prototype.startsWith; var std_String_substring = String.prototype.substring; var std_String_toLowerCase = String.prototype.toLowerCase; var std_String_toUpperCase = String.prototype.toUpperCase; +var std_WeakMap_get = WeakMap.prototype.get; +var std_WeakMap_has = WeakMap.prototype.has; +var std_WeakMap_set = WeakMap.prototype.set; /********** List specification type **********/ diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index bfed52d1b25f..66d8d9bdc31d 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -17,6 +17,7 @@ #include "jsinferinlines.h" #include "frontend/ParseMaps-inl.h" +#include "frontend/ParseNode-inl.h" #include "frontend/Parser-inl.h" #include "frontend/SharedContext-inl.h" @@ -47,12 +48,12 @@ SetSourceMap(JSContext *cx, TokenStream &tokenStream, ScriptSource *ss, Unrooted } static bool -CheckArgumentsWithinEval(JSContext *cx, Parser &parser, HandleFunction fun) +CheckArgumentsWithinEval(JSContext *cx, Parser &parser, HandleFunction fun) { if (fun->hasRest()) { // It's an error to use |arguments| in a function that has a rest // parameter. - parser.reportError(NULL, JSMSG_ARGUMENTS_AND_REST); + parser.report(ParseError, false, NULL, JSMSG_ARGUMENTS_AND_REST); return false; } @@ -119,14 +120,14 @@ frontend::CompileScript(JSContext *cx, HandleObject scopeChain, break; } - Parser parser(cx, options, chars, length, /* foldConstants = */ true); + Parser parser(cx, options, chars, length, /* foldConstants = */ true); if (!parser.init()) return UnrootedScript(NULL); parser.sct = sct; GlobalSharedContext globalsc(cx, scopeChain, StrictModeFromContext(cx)); - ParseContext pc(&parser, &globalsc, staticLevel, /* bodyid = */ 0); + ParseContext pc(&parser, &globalsc, staticLevel, /* bodyid = */ 0); if (!pc.init()) return UnrootedScript(NULL); @@ -185,10 +186,9 @@ frontend::CompileScript(JSContext *cx, HandleObject scopeChain, } } - TokenStream &tokenStream = parser.tokenStream; bool canHaveDirectives = true; for (;;) { - TokenKind tt = tokenStream.peekToken(TSF_OPERAND); + TokenKind tt = parser.tokenStream.peekToken(TSF_OPERAND); if (tt <= TOK_EOF) { if (tt == TOK_EOF) break; @@ -213,10 +213,10 @@ frontend::CompileScript(JSContext *cx, HandleObject scopeChain, if (!EmitTree(cx, &bce, pn)) return UnrootedScript(NULL); - parser.freeTree(pn); + parser.handler.freeTree(pn); } - if (!SetSourceMap(cx, tokenStream, ss, script)) + if (!SetSourceMap(cx, parser.tokenStream, ss, script)) return UnrootedScript(NULL); if (evalCaller && evalCaller->functionOrCallerFunction()) { @@ -273,6 +273,46 @@ frontend::CompileScript(JSContext *cx, HandleObject scopeChain, return script; } +bool +frontend::ParseScript(JSContext *cx, HandleObject scopeChain, + const CompileOptions &options, StableCharPtr chars, size_t length) +{ + if (!CheckLength(cx, length)) + return false; + + Parser parser(cx, options, chars.get(), length, /* foldConstants = */ false); + if (!parser.init()) { + cx->clearPendingException(); + return false; + } + + GlobalSharedContext globalsc(cx, scopeChain, StrictModeFromContext(cx)); + + ParseContext pc(&parser, &globalsc, 0, /* bodyid = */ 0); + if (!pc.init()) { + cx->clearPendingException(); + return false; + } + + for (;;) { + TokenKind tt = parser.tokenStream.peekToken(TSF_OPERAND); + if (tt <= TOK_EOF) { + if (tt == TOK_EOF) + break; + JS_ASSERT(tt == TOK_ERROR); + cx->clearPendingException(); + return false; + } + + if (!parser.statement()) { + cx->clearPendingException(); + return false; + } + } + + return true; +} + // Compile a JS function body, which might appear as the value of an event // handler attribute in an HTML tag, or in a Function() constructor. bool @@ -293,7 +333,7 @@ frontend::CompileFunctionBody(JSContext *cx, HandleFunction fun, CompileOptions } options.setCompileAndGo(false); - Parser parser(cx, options, chars, length, /* foldConstants = */ true); + Parser parser(cx, options, chars, length, /* foldConstants = */ true); if (!parser.init()) return false; parser.sct = &sct; @@ -303,7 +343,7 @@ frontend::CompileFunctionBody(JSContext *cx, HandleFunction fun, CompileOptions fun->setArgCount(formals.length()); /* FIXME: make Function format the source for a function definition. */ - ParseNode *fn = CodeNode::create(PNK_FUNCTION, &parser); + ParseNode *fn = CodeNode::create(PNK_FUNCTION, &parser.handler); if (!fn) return false; @@ -311,7 +351,7 @@ frontend::CompileFunctionBody(JSContext *cx, HandleFunction fun, CompileOptions fn->pn_funbox = NULL; fn->pn_cookie.makeFree(); - ParseNode *argsbody = ListNode::create(PNK_ARGSBODY, &parser); + ParseNode *argsbody = ListNode::create(PNK_ARGSBODY, &parser.handler); if (!argsbody) return false; argsbody->setOp(JSOP_NOP); diff --git a/js/src/frontend/BytecodeCompiler.h b/js/src/frontend/BytecodeCompiler.h index 0ea7aa336c37..c05b90b27170 100644 --- a/js/src/frontend/BytecodeCompiler.h +++ b/js/src/frontend/BytecodeCompiler.h @@ -19,6 +19,10 @@ CompileScript(JSContext *cx, HandleObject scopeChain, HandleScript evalCaller, JSString *source_ = NULL, unsigned staticLevel = 0, SourceCompressionToken *extraSct = NULL); +bool +ParseScript(JSContext *cx, HandleObject scopeChain, + const CompileOptions &options, StableCharPtr chars, size_t length); + bool CompileFunctionBody(JSContext *cx, HandleFunction fun, CompileOptions options, const AutoNameVector &formals, const jschar *chars, size_t length); diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 9ac2281614a4..3f2a251c7425 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -86,7 +86,8 @@ struct frontend::StmtInfoBCE : public StmtInfoBase } }; -BytecodeEmitter::BytecodeEmitter(BytecodeEmitter *parent, Parser *parser, SharedContext *sc, +BytecodeEmitter::BytecodeEmitter(BytecodeEmitter *parent, + Parser *parser, SharedContext *sc, HandleScript script, HandleScript evalCaller, bool hasGlobalScope, unsigned lineno, bool selfHostingMode) : sc(sc), @@ -1665,7 +1666,7 @@ BytecodeEmitter::reportError(ParseNode *pn, unsigned errorNumber, ...) { va_list args; va_start(args, errorNumber); - bool result = tokenStream()->reportCompileErrorNumberVA(pn, JSREPORT_ERROR, errorNumber, args); + bool result = tokenStream()->reportCompileErrorNumberVA(pn->pn_pos, JSREPORT_ERROR, errorNumber, args); va_end(args); return result; } @@ -1675,7 +1676,7 @@ BytecodeEmitter::reportStrictWarning(ParseNode *pn, unsigned errorNumber, ...) { va_list args; va_start(args, errorNumber); - bool result = tokenStream()->reportStrictWarningErrorNumberVA(pn, errorNumber, args); + bool result = tokenStream()->reportStrictWarningErrorNumberVA(pn->pn_pos, errorNumber, args); va_end(args); return result; } @@ -1685,7 +1686,7 @@ BytecodeEmitter::reportStrictModeError(ParseNode *pn, unsigned errorNumber, ...) { va_list args; va_start(args, errorNumber); - bool result = tokenStream()->reportStrictModeErrorNumberVA(pn, sc->strict, errorNumber, args); + bool result = tokenStream()->reportStrictModeErrorNumberVA(pn->pn_pos, sc->strict, errorNumber, args); va_end(args); return result; } @@ -1943,8 +1944,8 @@ EmitNameIncDec(JSContext *cx, ParseNode *pn, JSOp op, BytecodeEmitter *bce) return true; } -static bool -EmitElemOp(JSContext *cx, ParseNode *pn, JSOp op, BytecodeEmitter *bce) +bool +frontend::EmitElemOp(JSContext *cx, ParseNode *pn, JSOp op, BytecodeEmitter *bce) { ParseNode *left, *right; @@ -1958,14 +1959,14 @@ EmitElemOp(JSContext *cx, ParseNode *pn, JSOp op, BytecodeEmitter *bce) */ left = pn->maybeExpr(); if (!left) { - left = NullaryNode::create(PNK_STRING, bce->parser); + left = NullaryNode::create(PNK_STRING, &bce->parser->handler); if (!left) return false; left->setOp(JSOP_BINDNAME); left->pn_pos = pn->pn_pos; left->pn_atom = pn->pn_atom; } - right = NullaryNode::create(PNK_STRING, bce->parser); + right = NullaryNode::create(PNK_STRING, &bce->parser->handler); if (!right) return false; right->setOp(JSOP_STRING); @@ -2179,6 +2180,12 @@ EmitSwitch(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) uint32_t tableLength = 0; ScopedJSFreePtr table(NULL); + if (caseCount > JS_BIT(16)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_TOO_MANY_CASES); + return false; + } + if (caseCount == 0 || (caseCount == 1 && (hasDefault = (pn2->pn_head->isKind(PNK_DEFAULT))))) { @@ -4803,6 +4810,13 @@ EmitCallOrNew(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) * will box into the global object). */ uint32_t argc = pn->pn_count - 1; + + if (argc >= ARGC_LIMIT) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + callop ? JSMSG_TOO_MANY_FUN_ARGS : JSMSG_TOO_MANY_CON_ARGS); + return false; + } + bool emitArgs = true; ParseNode *pn2 = pn->pn_head; switch (pn2->getKind()) { diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 0baf03d2e0b6..f8356b05c3ed 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -93,7 +93,8 @@ struct BytecodeEmitter }; EmitSection prolog, main, *current; - Parser *const parser; /* the parser */ + /* the parser */ + Parser *const parser; HandleScript evalCaller; /* scripted caller info for eval and dbgapi */ @@ -143,7 +144,7 @@ struct BytecodeEmitter * tempLifoAlloc and save the pointer beyond the next BytecodeEmitter * destruction. */ - BytecodeEmitter(BytecodeEmitter *parent, Parser *parser, SharedContext *sc, + BytecodeEmitter(BytecodeEmitter *parent, Parser *parser, SharedContext *sc, HandleScript script, HandleScript evalCaller, bool hasGlobalScope, unsigned lineno, bool selfHostingMode = false); bool init(); diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index 3e36dd0dc55d..2d143c7d3ea0 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -108,7 +108,7 @@ FoldType(JSContext *cx, ParseNode *pn, ParseNodeKind kind) */ static bool FoldBinaryNumeric(JSContext *cx, JSOp op, ParseNode *pn1, ParseNode *pn2, - ParseNode *pn, Parser *parser) + ParseNode *pn, Parser *parser) { double d, d2; int32_t i, j; @@ -174,10 +174,6 @@ FoldBinaryNumeric(JSContext *cx, JSOp op, ParseNode *pn1, ParseNode *pn2, } /* Take care to allow pn1 or pn2 to alias pn. */ - if (pn1 != pn) - parser->freeTree(pn1); - if (pn2 != pn) - parser->freeTree(pn2); pn->setKind(PNK_NUMBER); pn->setOp(JSOP_DOUBLE); pn->setArity(PN_NULLARY); @@ -244,9 +240,14 @@ Boolish(ParseNode *pn) } } +namespace js { +namespace frontend { + +template <> bool -frontend::FoldConstants(JSContext *cx, ParseNode **pnp, Parser *parser, bool inGenexpLambda, - bool inCond) +FoldConstants(JSContext *cx, ParseNode **pnp, + Parser *parser, + bool inGenexpLambda, bool inCond) { ParseNode *pn = *pnp; ParseNode *pn1 = NULL, *pn2 = NULL, *pn3 = NULL; @@ -301,7 +302,7 @@ frontend::FoldConstants(JSContext *cx, ParseNode **pnp, Parser *parser, bool inG if (!FoldConstants(cx, &pn->pn_kid2, parser, inGenexpLambda, pn->isKind(PNK_FORHEAD))) return false; if (pn->isKind(PNK_FORHEAD) && pn->pn_kid2->isOp(JSOP_TRUE)) { - parser->freeTree(pn->pn_kid2); + parser->handler.freeTree(pn->pn_kid2); pn->pn_kid2 = NULL; } } @@ -427,7 +428,7 @@ frontend::FoldConstants(JSContext *cx, ParseNode **pnp, Parser *parser, bool inG pn->makeEmpty(); } if (pn3 && pn3 != pn2) - parser->freeTree(pn3); + parser->handler.freeTree(pn3); break; case PNK_OR: @@ -446,7 +447,7 @@ frontend::FoldConstants(JSContext *cx, ParseNode **pnp, Parser *parser, bool inG if ((t == Truthy) == pn->isKind(PNK_OR)) { for (pn2 = pn1->pn_next; pn2; pn2 = pn3) { pn3 = pn2->pn_next; - parser->freeTree(pn2); + parser->handler.freeTree(pn2); --pn->pn_count; } pn1->pn_next = NULL; @@ -456,7 +457,7 @@ frontend::FoldConstants(JSContext *cx, ParseNode **pnp, Parser *parser, bool inG if (pn->pn_count == 1) break; *listp = pn1->pn_next; - parser->freeTree(pn1); + parser->handler.freeTree(pn1); --pn->pn_count; } while ((pn1 = *listp) != NULL); @@ -483,12 +484,12 @@ frontend::FoldConstants(JSContext *cx, ParseNode **pnp, Parser *parser, bool inG Truthiness t = Boolish(pn1); if (t != Unknown) { if ((t == Truthy) == pn->isKind(PNK_OR)) { - parser->freeTree(pn2); + parser->handler.freeTree(pn2); ReplaceNode(pnp, pn1); pn = pn1; } else { JS_ASSERT((t == Truthy) == pn->isKind(PNK_AND)); - parser->freeTree(pn1); + parser->handler.freeTree(pn1); ReplaceNode(pnp, pn2); pn = pn2; } @@ -555,7 +556,7 @@ frontend::FoldConstants(JSContext *cx, ParseNode **pnp, Parser *parser, bool inG } /* Fill the buffer, advancing chars and recycling kids as we go. */ - for (pn2 = pn1; pn2; pn2 = parser->freeTree(pn2)) { + for (pn2 = pn1; pn2; pn2 = parser->handler.freeTree(pn2)) { JSAtom *atom = pn2->pn_atom; size_t length2 = atom->length(); js_strncpy(chars, atom->chars(), length2); @@ -591,8 +592,8 @@ frontend::FoldConstants(JSContext *cx, ParseNode **pnp, Parser *parser, bool inG pn->setKind(PNK_STRING); pn->setOp(JSOP_STRING); pn->setArity(PN_NULLARY); - parser->freeTree(pn1); - parser->freeTree(pn2); + parser->handler.freeTree(pn1); + parser->handler.freeTree(pn2); break; } @@ -686,7 +687,7 @@ frontend::FoldConstants(JSContext *cx, ParseNode **pnp, Parser *parser, bool inG pn->setOp(JSOP_DOUBLE); pn->setArity(PN_NULLARY); pn->pn_dval = d; - parser->freeTree(pn1); + parser->handler.freeTree(pn1); } else if (pn1->isKind(PNK_TRUE) || pn1->isKind(PNK_FALSE)) { if (pn->isOp(JSOP_NOT)) { ReplaceNode(pnp, pn1); @@ -714,7 +715,7 @@ frontend::FoldConstants(JSContext *cx, ParseNode **pnp, Parser *parser, bool inG * a method list corrupts the method list. However, methods are M's in * statements of the form 'this.foo = M;', which we never fold, so we're okay. */ - parser->allocator.prepareNodeForMutation(pn); + parser->handler.prepareNodeForMutation(pn); if (t == Truthy) { pn->setKind(PNK_TRUE); pn->setOp(JSOP_TRUE); @@ -728,3 +729,15 @@ frontend::FoldConstants(JSContext *cx, ParseNode **pnp, Parser *parser, bool inG return true; } + +template <> +bool +FoldConstants(JSContext *cx, SyntaxParseHandler::Node *pnp, + Parser *parser, + bool inGenexpLambda, bool inCond) +{ + return true; +} + +} /* namespace frontend */ +} /* namespace js */ diff --git a/js/src/frontend/FoldConstants.h b/js/src/frontend/FoldConstants.h index bbe83aa954a9..a1bf9d14e9ad 100644 --- a/js/src/frontend/FoldConstants.h +++ b/js/src/frontend/FoldConstants.h @@ -26,9 +26,11 @@ namespace frontend { // return false; // if (!FoldConstants(cx, &pn, parser)) // return false; +template bool -FoldConstants(JSContext *cx, ParseNode **pnp, Parser *parser, bool inGenexpLambda = false, - bool inCond = false); +FoldConstants(JSContext *cx, typename ParseHandler::Node *pnp, + Parser *parser, + bool inGenexpLambda = false, bool inCond = false); } /* namespace frontend */ } /* namespace js */ diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h new file mode 100644 index 000000000000..a38be83634ac --- /dev/null +++ b/js/src/frontend/FullParseHandler.h @@ -0,0 +1,373 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et tw=78: + * + * 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 FullParseHandler_h__ +#define FullParseHandler_h__ + +#include "ParseNode.h" +#include "SharedContext.h" + +namespace js { +namespace frontend { + +class FullParseHandler +{ + ParseNodeAllocator allocator; + TokenStream &tokenStream; + bool foldConstants; + + ParseNode *allocParseNode(size_t size) { + JS_ASSERT(size == sizeof(ParseNode)); + return static_cast(allocator.allocNode()); + } + + ParseNode *cloneNode(const ParseNode &other) { + ParseNode *node = allocParseNode(sizeof(ParseNode)); + if (!node) + return NULL; + PodAssign(node, &other); + return node; + } + + public: + /* new_ methods for creating parse nodes. These report OOM on context. */ + JS_DECLARE_NEW_METHODS(new_, allocParseNode, inline) + + typedef ParseNode *Node; + typedef Definition *DefinitionNode; + + FullParseHandler(JSContext *cx, TokenStream &tokenStream, bool foldConstants) + : allocator(cx), + tokenStream(tokenStream), + foldConstants(foldConstants) + {} + + static Definition *null() { return NULL; } + + ParseNode *freeTree(ParseNode *pn) { return allocator.freeTree(pn); } + void prepareNodeForMutation(ParseNode *pn) { return allocator.prepareNodeForMutation(pn); } + const Token ¤tToken() { return tokenStream.currentToken(); } + + ParseNode *newName(PropertyName *name, ParseContext *pc, + ParseNodeKind kind = PNK_NAME) { + ParseNode *pn = NameNode::create(kind, name, this, pc); + if (!pn) + return NULL; + pn->setOp(JSOP_NAME); + return pn; + } + ParseNode *newAtom(ParseNodeKind kind, JSAtom *atom, JSOp op = JSOP_NOP) { + ParseNode *pn = NullaryNode::create(kind, this); + if (!pn) + return NULL; + pn->setOp(op); + pn->pn_atom = atom; + return pn; + } + ParseNode *newNumber(double value, DecimalPoint decimalPoint = NoDecimal) { + ParseNode *pn = NullaryNode::create(PNK_NUMBER, this); + if (!pn) + return NULL; + pn->initNumber(value, decimalPoint); + return pn; + } + ParseNode *newNumber(const Token &tok) { + return newNumber(tok.number(), tok.decimalPoint()); + } + ParseNode *newBooleanLiteral(bool cond, const TokenPos &pos) { + return new_(cond, pos); + } + ParseNode *newThisLiteral(const TokenPos &pos) { + return new_(pos); + } + ParseNode *newNullLiteral(const TokenPos &pos) { + return new_(pos); + } + ParseNode *newConditional(ParseNode *cond, ParseNode *thenExpr, ParseNode *elseExpr) { + return new_(cond, thenExpr, elseExpr); + } + + ParseNode *newNullary(ParseNodeKind kind) { + return NullaryNode::create(kind, this); + } + + ParseNode *newUnary(ParseNodeKind kind, ParseNode *kid, JSOp op = JSOP_NOP) { + return new_(kind, op, kid->pn_pos, kid); + } + ParseNode *newUnary(ParseNodeKind kind, JSOp op = JSOP_NOP) { + return new_(kind, op, tokenStream.currentToken().pos, (ParseNode *) NULL); + } + void setUnaryKid(ParseNode *pn, ParseNode *kid) { + pn->pn_kid = kid; + pn->pn_pos.end = kid->pn_pos.end; + } + + ParseNode *newBinary(ParseNodeKind kind, JSOp op = JSOP_NOP) { + return new_(kind, op, tokenStream.currentToken().pos, + (ParseNode *) NULL, (ParseNode *) NULL); + } + ParseNode *newBinary(ParseNodeKind kind, ParseNode *left, + JSOp op = JSOP_NOP) { + return new_(kind, op, left->pn_pos, left, (ParseNode *) NULL); + } + ParseNode *newBinary(ParseNodeKind kind, ParseNode *left, ParseNode *right, + JSOp op = JSOP_NOP) { + TokenPos pos = TokenPos::make(left->pn_pos.begin, right->pn_pos.end); + return new_(kind, op, pos, left, right); + } + ParseNode *newBinaryOrAppend(ParseNodeKind kind, ParseNode *left, ParseNode *right, + JSOp op = JSOP_NOP) { + return ParseNode::newBinaryOrAppend(kind, op, left, right, this, foldConstants); + } + void setBinaryRHS(ParseNode *pn, ParseNode *rhs) { + JS_ASSERT(pn->isArity(PN_BINARY)); + pn->pn_right = rhs; + pn->pn_pos.end = rhs->pn_pos.end; + } + + ParseNode *newTernary(ParseNodeKind kind, + ParseNode *first, ParseNode *second, ParseNode *third, + JSOp op = JSOP_NOP) { + return new_(kind, op, first, second, third); + } + + ParseNode *newBreak(PropertyName *label, const TokenPtr &begin, const TokenPtr &end) { + return new_(label, begin, end); + } + ParseNode *newContinue(PropertyName *label, const TokenPtr &begin, const TokenPtr &end) { + return new_(label, begin, end); + } + ParseNode *newDebuggerStatement(const TokenPos &pos) { + return new_(pos); + } + ParseNode *newPropertyAccess(ParseNode *pn, PropertyName *name, const TokenPtr &end) { + return new_(pn, name, pn->pn_pos.begin, end); + } + ParseNode *newPropertyByValue(ParseNode *pn, ParseNode *kid, const TokenPtr &end) { + return new_(pn, kid, pn->pn_pos.begin, end); + } + + inline bool addCatchBlock(ParseNode *catchList, ParseNode *letBlock, + ParseNode *catchName, ParseNode *catchGuard, ParseNode *catchBody); + + inline void morphNameIntoLabel(ParseNode *name, ParseNode *statement); + + inline void setLeaveBlockResult(ParseNode *block, ParseNode *kid, bool leaveBlockExpr); + + inline void setLastFunctionArgumentDefault(ParseNode *funcpn, ParseNode *pn); + inline ParseNode *newFunctionDefinition(); + void setFunctionBody(ParseNode *pn, ParseNode *kid) { + pn->pn_body = kid; + } + void setFunctionBox(ParseNode *pn, FunctionBox *funbox) { + pn->pn_funbox = funbox; + } + bool isOperationWithoutParens(ParseNode *pn, ParseNodeKind kind) { + return pn->isKind(kind) && !pn->isInParens(); + } + + inline void noteLValue(ParseNode *pn); + inline bool finishInitializerAssignment(ParseNode *pn, ParseNode *init, JSOp op); + + void setBeginPosition(ParseNode *pn, ParseNode *oth) { + setBeginPosition(pn, oth->pn_pos.begin); + } + void setBeginPosition(ParseNode *pn, const TokenPtr &begin) { + pn->pn_pos.begin = begin; + JS_ASSERT(pn->pn_pos.begin <= pn->pn_pos.end); + } + + void setEndPosition(ParseNode *pn, ParseNode *oth) { + setEndPosition(pn, oth->pn_pos.end); + } + void setEndPosition(ParseNode *pn, const TokenPtr &end) { + pn->pn_pos.end = end; + JS_ASSERT(pn->pn_pos.begin <= pn->pn_pos.end); + } + + TokenPos getPosition(ParseNode *pn) { + return pn->pn_pos; + } + + ParseNode *newList(ParseNodeKind kind, ParseNode *kid = NULL, JSOp op = JSOP_NOP) { + ParseNode *pn = ListNode::create(kind, this); + if (!pn) + return NULL; + pn->setOp(op); + pn->makeEmpty(); + if (kid) { + pn->pn_pos.begin = kid->pn_pos.begin; + pn->append(kid); + } + return pn; + } + void addList(ParseNode *pn, ParseNode *kid) { + pn->append(kid); + } + + void setOp(ParseNode *pn, JSOp op) { + pn->setOp(op); + } + void setBlockId(ParseNode *pn, unsigned blockid) { + pn->pn_blockid = blockid; + } + void setFlag(ParseNode *pn, unsigned flag) { + pn->pn_dflags |= flag; + } + void setListFlag(ParseNode *pn, unsigned flag) { + JS_ASSERT(pn->isArity(PN_LIST)); + pn->pn_xflags |= flag; + } + ParseNode *setInParens(ParseNode *pn) { + pn->setInParens(true); + return pn; + } + void setPrologue(ParseNode *pn) { + pn->pn_prologue = true; + } + + bool isConstant(ParseNode *pn) { + return pn->isConstant(); + } + PropertyName *isName(ParseNode *pn) { + return pn->isKind(PNK_NAME) ? pn->pn_atom->asPropertyName() : NULL; + } + PropertyName *isGetProp(ParseNode *pn) { + return pn->isOp(JSOP_GETPROP) ? pn->pn_atom->asPropertyName() : NULL; + } + JSAtom *isStringExprStatement(ParseNode *pn, TokenPos *pos) { + if (JSAtom *atom = pn->isStringExprStatement()) { + *pos = pn->pn_pos; + return atom; + } + return NULL; + } + bool isEmptySemicolon(ParseNode *pn) { + return pn->isKind(PNK_SEMI) && !pn->pn_kid; + } + + inline ParseNode *makeAssignment(ParseNode *pn, ParseNode *rhs); +}; + +inline bool +FullParseHandler::addCatchBlock(ParseNode *catchList, ParseNode *letBlock, + ParseNode *catchName, ParseNode *catchGuard, ParseNode *catchBody) +{ + ParseNode *catchpn = newTernary(PNK_CATCH, catchName, catchGuard, catchBody); + if (!catchpn) + return false; + + catchList->append(letBlock); + letBlock->pn_expr = catchpn; + return true; +} + +inline void +FullParseHandler::morphNameIntoLabel(ParseNode *name, ParseNode *statement) +{ + name->setKind(PNK_COLON); + name->pn_pos.end = statement->pn_pos.end; + name->pn_expr = statement; +} + +inline void +FullParseHandler::setLeaveBlockResult(ParseNode *block, ParseNode *kid, bool leaveBlockExpr) +{ + JS_ASSERT(block->isOp(JSOP_LEAVEBLOCK)); + if (leaveBlockExpr) + block->setOp(JSOP_LEAVEBLOCKEXPR); + block->pn_expr = kid; +} + +inline void +FullParseHandler::setLastFunctionArgumentDefault(ParseNode *funcpn, ParseNode *defaultValue) +{ + ParseNode *arg = funcpn->pn_body->last(); + arg->pn_dflags |= PND_DEFAULT; + arg->pn_expr = defaultValue; +} + +inline ParseNode * +FullParseHandler::newFunctionDefinition() +{ + ParseNode *pn = CodeNode::create(PNK_FUNCTION, this); + if (!pn) + return NULL; + pn->pn_body = NULL; + pn->pn_funbox = NULL; + pn->pn_cookie.makeFree(); + pn->pn_dflags = 0; + return pn; +} + +inline void +FullParseHandler::noteLValue(ParseNode *pn) +{ + if (pn->isUsed()) + pn->pn_lexdef->pn_dflags |= PND_ASSIGNED; + + pn->pn_dflags |= PND_ASSIGNED; +} + +inline bool +FullParseHandler::finishInitializerAssignment(ParseNode *pn, ParseNode *init, JSOp op) +{ + if (pn->isUsed()) { + pn = makeAssignment(pn, init); + if (!pn) + return false; + } else { + pn->pn_expr = init; + } + + pn->setOp((pn->pn_dflags & PND_BOUND) + ? JSOP_SETLOCAL + : (op == JSOP_DEFCONST) + ? JSOP_SETCONST + : JSOP_SETNAME); + + noteLValue(pn); + + /* The declarator's position must include the initializer. */ + pn->pn_pos.end = init->pn_pos.end; + return true; +} + +inline ParseNode * +FullParseHandler::makeAssignment(ParseNode *pn, ParseNode *rhs) +{ + ParseNode *lhs = cloneNode(*pn); + if (!lhs) + return NULL; + + if (pn->isUsed()) { + Definition *dn = pn->pn_lexdef; + ParseNode **pnup = &dn->dn_uses; + + while (*pnup != pn) + pnup = &(*pnup)->pn_link; + *pnup = lhs; + lhs->pn_link = pn->pn_link; + pn->pn_link = NULL; + } + + pn->setKind(PNK_ASSIGN); + pn->setOp(JSOP_NOP); + pn->setArity(PN_BINARY); + pn->setInParens(false); + pn->setUsed(false); + pn->setDefn(false); + pn->pn_left = lhs; + pn->pn_right = rhs; + pn->pn_pos.end = rhs->pn_pos.end; + return lhs; +} + +} // frontend +} // js + +#endif /* FullParseHandler_h__ */ diff --git a/js/src/frontend/ParseNode-inl.h b/js/src/frontend/ParseNode-inl.h index e20f9876eeaa..e31ec15f900b 100644 --- a/js/src/frontend/ParseNode-inl.h +++ b/js/src/frontend/ParseNode-inl.h @@ -43,28 +43,8 @@ ParseNode::atom() const return isKind(PNK_MODULE) ? pn_modulebox->module()->atom() : pn_atom; } -inline bool -ParseNode::isConstant() -{ - switch (pn_type) { - case PNK_NUMBER: - case PNK_STRING: - case PNK_NULL: - case PNK_FALSE: - case PNK_TRUE: - return true; - case PNK_ARRAY: - case PNK_OBJECT: - return isOp(JSOP_NEWINIT) && !(pn_xflags & PNX_NONCONST); - default: - return false; - } -} - -struct ParseContext; - inline void -NameNode::initCommon(ParseContext *pc) +NameNode::initCommon(ParseContext *pc) { pn_expr = NULL; pn_cookie.makeFree(); diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index da26b95a38d7..ae91d22c128c 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -254,14 +254,15 @@ ParseNodeAllocator::allocNode() /* used only by static create methods of subclasses */ ParseNode * -ParseNode::create(ParseNodeKind kind, ParseNodeArity arity, Parser *parser) +ParseNode::create(ParseNodeKind kind, ParseNodeArity arity, FullParseHandler *handler) { - const Token &tok = parser->tokenStream.currentToken(); - return parser->new_(kind, JSOP_NOP, arity, tok.pos); + const Token &tok = handler->currentToken(); + return handler->new_(kind, JSOP_NOP, arity, tok.pos); } ParseNode * -ParseNode::append(ParseNodeKind kind, JSOp op, ParseNode *left, ParseNode *right, Parser *parser) +ParseNode::append(ParseNodeKind kind, JSOp op, ParseNode *left, ParseNode *right, + FullParseHandler *handler) { if (!left || !right) return NULL; @@ -273,7 +274,7 @@ ParseNode::append(ParseNodeKind kind, JSOp op, ParseNode *left, ParseNode *right list = &left->as(); } else { ParseNode *pn1 = left->pn_left, *pn2 = left->pn_right; - list = parser->new_(kind, op, pn1); + list = handler->new_(kind, op, pn1); if (!list) return NULL; list->append(pn2); @@ -303,7 +304,7 @@ ParseNode::append(ParseNodeKind kind, JSOp op, ParseNode *left, ParseNode *right ParseNode * ParseNode::newBinaryOrAppend(ParseNodeKind kind, JSOp op, ParseNode *left, ParseNode *right, - Parser *parser) + FullParseHandler *handler, bool foldConstants) { if (!left || !right) return NULL; @@ -313,7 +314,7 @@ ParseNode::newBinaryOrAppend(ParseNodeKind kind, JSOp op, ParseNode *left, Parse * a list to reduce js::FoldConstants and js::frontend::EmitTree recursion. */ if (left->isKind(kind) && left->isOp(op) && (js_CodeSpec[op].format & JOF_LEFTASSOC)) - return append(kind, op, left, right, parser); + return append(kind, op, left, right, handler); /* * Fold constant addition immediately, to conserve node space and, what's @@ -325,24 +326,24 @@ ParseNode::newBinaryOrAppend(ParseNodeKind kind, JSOp op, ParseNode *left, Parse if (kind == PNK_ADD && left->isKind(PNK_NUMBER) && right->isKind(PNK_NUMBER) && - parser->foldConstants) + foldConstants) { left->pn_dval += right->pn_dval; left->pn_pos.end = right->pn_pos.end; - parser->freeTree(right); + handler->freeTree(right); return left; } - return parser->new_(kind, op, left, right); + return handler->new_(kind, op, left, right); } -// Nb: unlike most functions that are passed a Parser, this one gets a -// SharedContext passed in separately, because in this case |pc| may not equal -// |parser->pc|. +// Note: the parse context passed into this may not equal the associated +// parser's current context. NameNode * -NameNode::create(ParseNodeKind kind, JSAtom *atom, Parser *parser, ParseContext *pc) +NameNode::create(ParseNodeKind kind, JSAtom *atom, FullParseHandler *handler, + ParseContext *pc) { - ParseNode *pn = ParseNode::create(kind, PN_NAME, parser); + ParseNode *pn = ParseNode::create(kind, PN_NAME, handler); if (pn) { pn->pn_atom = atom; ((NameNode *)pn)->initCommon(pc); @@ -361,20 +362,22 @@ Definition::kindString(Kind kind) return table[kind]; } +namespace js { +namespace frontend { + #if JS_HAS_DESTRUCTURING /* * This function assumes the cloned tree is for use in the same statement and * binding context as the original tree. */ -static ParseNode * -CloneParseTree(ParseNode *opn, Parser *parser) +template <> +ParseNode * +Parser::cloneParseTree(ParseNode *opn) { - ParseContext *pc = parser->pc; + JS_CHECK_RECURSION(context, return NULL); - JS_CHECK_RECURSION(pc->sc->context, return NULL); - - ParseNode *pn = parser->new_(opn->getKind(), opn->getOp(), opn->getArity(), + ParseNode *pn = handler.new_(opn->getKind(), opn->getOp(), opn->getArity(), opn->pn_pos); if (!pn) return NULL; @@ -391,8 +394,8 @@ CloneParseTree(ParseNode *opn, Parser *parser) return NULL; } else { NULLCHECK(pn->pn_funbox = - parser->newFunctionBox(opn->pn_funbox->function(), pc, opn->pn_funbox->strict)); - NULLCHECK(pn->pn_body = CloneParseTree(opn->pn_body, parser)); + newFunctionBox(opn->pn_funbox->function(), pc, opn->pn_funbox->strict)); + NULLCHECK(pn->pn_body = cloneParseTree(opn->pn_body)); pn->pn_cookie = opn->pn_cookie; pn->pn_dflags = opn->pn_dflags; pn->pn_blockid = opn->pn_blockid; @@ -403,29 +406,29 @@ CloneParseTree(ParseNode *opn, Parser *parser) pn->makeEmpty(); for (ParseNode *opn2 = opn->pn_head; opn2; opn2 = opn2->pn_next) { ParseNode *pn2; - NULLCHECK(pn2 = CloneParseTree(opn2, parser)); + NULLCHECK(pn2 = cloneParseTree(opn2)); pn->append(pn2); } pn->pn_xflags = opn->pn_xflags; break; case PN_TERNARY: - NULLCHECK(pn->pn_kid1 = CloneParseTree(opn->pn_kid1, parser)); - NULLCHECK(pn->pn_kid2 = CloneParseTree(opn->pn_kid2, parser)); - NULLCHECK(pn->pn_kid3 = CloneParseTree(opn->pn_kid3, parser)); + NULLCHECK(pn->pn_kid1 = cloneParseTree(opn->pn_kid1)); + NULLCHECK(pn->pn_kid2 = cloneParseTree(opn->pn_kid2)); + NULLCHECK(pn->pn_kid3 = cloneParseTree(opn->pn_kid3)); break; case PN_BINARY: - NULLCHECK(pn->pn_left = CloneParseTree(opn->pn_left, parser)); + NULLCHECK(pn->pn_left = cloneParseTree(opn->pn_left)); if (opn->pn_right != opn->pn_left) - NULLCHECK(pn->pn_right = CloneParseTree(opn->pn_right, parser)); + NULLCHECK(pn->pn_right = cloneParseTree(opn->pn_right)); else pn->pn_right = pn->pn_left; pn->pn_iflags = opn->pn_iflags; break; case PN_UNARY: - NULLCHECK(pn->pn_kid = CloneParseTree(opn->pn_kid, parser)); + NULLCHECK(pn->pn_kid = cloneParseTree(opn->pn_kid)); pn->pn_hidden = opn->pn_hidden; break; @@ -442,7 +445,7 @@ CloneParseTree(ParseNode *opn, Parser *parser) pn->pn_link = dn->dn_uses; dn->dn_uses = pn; } else if (opn->pn_expr) { - NULLCHECK(pn->pn_expr = CloneParseTree(opn->pn_expr, parser)); + NULLCHECK(pn->pn_expr = cloneParseTree(opn->pn_expr)); /* * If the old name is a definition, the new one has pn_defn set. @@ -476,10 +479,11 @@ CloneParseTree(ParseNode *opn, Parser *parser) * The cloned tree is for use only in the same statement and binding context as * the original tree. */ +template <> ParseNode * -frontend::CloneLeftHandSide(ParseNode *opn, Parser *parser) +Parser::cloneLeftHandSide(ParseNode *opn) { - ParseNode *pn = parser->new_(opn->getKind(), opn->getOp(), opn->getArity(), + ParseNode *pn = handler.new_(opn->getKind(), opn->getOp(), opn->getArity(), opn->pn_pos); if (!pn) return NULL; @@ -497,19 +501,19 @@ frontend::CloneLeftHandSide(ParseNode *opn, Parser *parser) JS_ASSERT(opn2->isArity(PN_BINARY)); JS_ASSERT(opn2->isKind(PNK_COLON)); - ParseNode *tag = CloneParseTree(opn2->pn_left, parser); + ParseNode *tag = cloneParseTree(opn2->pn_left); if (!tag) return NULL; - ParseNode *target = CloneLeftHandSide(opn2->pn_right, parser); + ParseNode *target = cloneLeftHandSide(opn2->pn_right); if (!target) return NULL; - pn2 = parser->new_(PNK_COLON, JSOP_INITPROP, opn2->pn_pos, tag, target); + pn2 = handler.new_(PNK_COLON, JSOP_INITPROP, opn2->pn_pos, tag, target); } else if (opn2->isArity(PN_NULLARY)) { JS_ASSERT(opn2->isKind(PNK_COMMA)); - pn2 = CloneParseTree(opn2, parser); + pn2 = cloneParseTree(opn2); } else { - pn2 = CloneLeftHandSide(opn2, parser); + pn2 = cloneLeftHandSide(opn2); } if (!pn2) @@ -546,6 +550,9 @@ frontend::CloneLeftHandSide(ParseNode *opn, Parser *parser) return pn; } +} /* namespace frontend */ +} /* namespace js */ + #ifdef DEBUG static const char *parseNodeNames[] = { diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index f42c345bfb5d..8dbceea1c8e3 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -18,8 +18,11 @@ namespace js { namespace frontend { +template struct ParseContext; +class FullParseHandler; + /* * Indicates a location in the stack that an upvar value can be retrieved from * as a two tuple of (level, slot). @@ -570,7 +573,7 @@ struct ParseNode { pn_next = pn_link = NULL; } - static ParseNode *create(ParseNodeKind kind, ParseNodeArity arity, Parser *parser); + static ParseNode *create(ParseNodeKind kind, ParseNodeArity arity, FullParseHandler *handler); public: /* @@ -578,7 +581,7 @@ struct ParseNode { * kind and op, and op must be left-associative. */ static ParseNode * - append(ParseNodeKind tt, JSOp op, ParseNode *left, ParseNode *right, Parser *parser); + append(ParseNodeKind tt, JSOp op, ParseNode *left, ParseNode *right, FullParseHandler *handler); /* * Either append right to left, if left meets the conditions necessary to @@ -587,7 +590,7 @@ struct ParseNode { */ static ParseNode * newBinaryOrAppend(ParseNodeKind kind, JSOp op, ParseNode *left, ParseNode *right, - Parser *parser); + FullParseHandler *handler, bool foldConstants); inline PropertyName *name() const; inline JSAtom *atom() const; @@ -688,34 +691,15 @@ struct ParseNode { * themselves be directives (string literals that include escape sequences * or escaped newlines, say). This member function returns true for such * nodes; we use it to determine the extent of the prologue. - * isEscapeFreeStringLiteral, below, checks whether the node itself could be - * a directive. */ - bool isStringExprStatement() const { + JSAtom *isStringExprStatement() const { if (getKind() == PNK_SEMI) { JS_ASSERT(pn_arity == PN_UNARY); ParseNode *kid = pn_kid; - return kid && kid->getKind() == PNK_STRING && !kid->pn_parens; + if (kid && kid->getKind() == PNK_STRING && !kid->pn_parens) + return kid->pn_atom; } - return false; - } - - /* - * Return true if this node, known to be an unparenthesized string literal, - * could be the string of a directive in a Directive Prologue. Directive - * strings never contain escape sequences or line continuations. - */ - bool isEscapeFreeStringLiteral() const { - JS_ASSERT(isKind(PNK_STRING) && !pn_parens); - - /* - * If the string's length in the source code is its length as a value, - * accounting for the quotes, then it must not contain any escape - * sequences or line continuations. - */ - JSString *str = pn_atom; - return (pn_pos.begin.lineno == pn_pos.end.lineno && - pn_pos.begin.index + str->length() + 2 == pn_pos.end.index); + return NULL; } inline bool test(unsigned flag) const; @@ -767,11 +751,11 @@ struct ParseNode { return (ParseNode *)(uintptr_t(pn_tail) - offsetof(ParseNode, pn_next)); } - void initNumber(const Token &tok) { + void initNumber(double value, DecimalPoint decimalPoint) { JS_ASSERT(pn_arity == PN_NULLARY); JS_ASSERT(getKind() == PNK_NUMBER); - pn_u.number.value = tok.number(); - pn_u.number.decimalPoint = tok.decimalPoint(); + pn_u.number.value = value; + pn_u.number.decimalPoint = decimalPoint; } void makeEmpty() { @@ -833,8 +817,8 @@ struct ParseNode { }; struct NullaryNode : public ParseNode { - static inline NullaryNode *create(ParseNodeKind kind, Parser *parser) { - return (NullaryNode *) ParseNode::create(kind, PN_NULLARY, parser); + static inline NullaryNode *create(ParseNodeKind kind, FullParseHandler *handler) { + return (NullaryNode *) ParseNode::create(kind, PN_NULLARY, handler); } static bool test(const ParseNode &node) { @@ -853,8 +837,8 @@ struct UnaryNode : public ParseNode { pn_kid = kid; } - static inline UnaryNode *create(ParseNodeKind kind, Parser *parser) { - return (UnaryNode *) ParseNode::create(kind, PN_UNARY, parser); + static inline UnaryNode *create(ParseNodeKind kind, FullParseHandler *handler) { + return (UnaryNode *) ParseNode::create(kind, PN_UNARY, handler); } static bool test(const ParseNode &node) { @@ -881,8 +865,8 @@ struct BinaryNode : public ParseNode { pn_right = right; } - static inline BinaryNode *create(ParseNodeKind kind, Parser *parser) { - return (BinaryNode *) ParseNode::create(kind, PN_BINARY, parser); + static inline BinaryNode *create(ParseNodeKind kind, FullParseHandler *handler) { + return (BinaryNode *) ParseNode::create(kind, PN_BINARY, handler); } static bool test(const ParseNode &node) { @@ -905,8 +889,8 @@ struct TernaryNode : public ParseNode { pn_kid3 = kid3; } - static inline TernaryNode *create(ParseNodeKind kind, Parser *parser) { - return (TernaryNode *) ParseNode::create(kind, PN_TERNARY, parser); + static inline TernaryNode *create(ParseNodeKind kind, FullParseHandler *handler) { + return (TernaryNode *) ParseNode::create(kind, PN_TERNARY, handler); } static bool test(const ParseNode &node) { @@ -927,8 +911,8 @@ struct ListNode : public ParseNode initList(kid); } - static inline ListNode *create(ParseNodeKind kind, Parser *parser) { - return (ListNode *) ParseNode::create(kind, PN_LIST, parser); + static inline ListNode *create(ParseNodeKind kind, FullParseHandler *handler) { + return (ListNode *) ParseNode::create(kind, PN_LIST, handler); } static bool test(const ParseNode &node) { @@ -941,8 +925,8 @@ struct ListNode : public ParseNode }; struct CodeNode : public ParseNode { - static inline CodeNode *create(ParseNodeKind kind, Parser *parser) { - return (CodeNode *) ParseNode::create(kind, PN_CODE, parser); + static inline CodeNode *create(ParseNodeKind kind, FullParseHandler *handler) { + return (CodeNode *) ParseNode::create(kind, PN_CODE, handler); } static bool test(const ParseNode &node) { @@ -955,9 +939,10 @@ struct CodeNode : public ParseNode { }; struct NameNode : public ParseNode { - static NameNode *create(ParseNodeKind kind, JSAtom *atom, Parser *parser, ParseContext *pc); + static NameNode *create(ParseNodeKind kind, JSAtom *atom, + FullParseHandler *handler, ParseContext *pc); - inline void initCommon(ParseContext *pc); + inline void initCommon(ParseContext *pc); static bool test(const ParseNode &node) { return node.isArity(PN_NAME); @@ -969,8 +954,8 @@ struct NameNode : public ParseNode { }; struct LexicalScopeNode : public ParseNode { - static inline LexicalScopeNode *create(ParseNodeKind kind, Parser *parser) { - return (LexicalScopeNode *) ParseNode::create(kind, PN_NAME, parser); + static inline LexicalScopeNode *create(ParseNodeKind kind, FullParseHandler *handler) { + return (LexicalScopeNode *) ParseNode::create(kind, PN_NAME, handler); } }; @@ -1122,9 +1107,6 @@ class PropertyByValue : public ParseNode { } }; -ParseNode * -CloneLeftHandSide(ParseNode *opn, Parser *parser); - #ifdef DEBUG void DumpParseTree(ParseNode *pn, int indent = 0); #endif @@ -1301,6 +1283,24 @@ ParseNode::resolve() return (Definition *)lexdef(); } +inline bool +ParseNode::isConstant() +{ + switch (pn_type) { + case PNK_NUMBER: + case PNK_STRING: + case PNK_NULL: + case PNK_FALSE: + case PNK_TRUE: + return true; + case PNK_ARRAY: + case PNK_OBJECT: + return isOp(JSOP_NEWINIT) && !(pn_xflags & PNX_NONCONST); + default: + return false; + } +} + inline void LinkUseToDef(ParseNode *pn, Definition *dn) { @@ -1335,6 +1335,15 @@ class ObjectBox { ObjectBox(Module *module, ObjectBox *traceLink); }; +enum ParseReportKind { + ParseError, + ParseWarning, + ParseStrictWarning, + ParseStrictError +}; + +enum FunctionSyntaxKind { Expression, Statement }; + } /* namespace frontend */ } /* namespace js */ diff --git a/js/src/frontend/Parser-inl.h b/js/src/frontend/Parser-inl.h index bad2311904a5..b2dca7fc7003 100644 --- a/js/src/frontend/Parser-inl.h +++ b/js/src/frontend/Parser-inl.h @@ -13,20 +13,24 @@ namespace js { namespace frontend { +template inline unsigned -ParseContext::blockid() +ParseContext::blockid() { return topStmt ? topStmt->blockid : bodyid; } +template inline bool -ParseContext::atBodyLevel() +ParseContext::atBodyLevel() { return !topStmt; } +template inline -ParseContext::ParseContext(Parser *prs, SharedContext *sc, unsigned staticLevel, uint32_t bodyid) +ParseContext::ParseContext(Parser *prs, SharedContext *sc, + unsigned staticLevel, uint32_t bodyid) : sc(sc), bodyid(0), // initialized in init() blockidGen(bodyid), // used to set |bodyid| and subsequently incremented in init() @@ -36,11 +40,11 @@ ParseContext::ParseContext(Parser *prs, SharedContext *sc, unsigned staticLevel, staticLevel(staticLevel), parenDepth(0), yieldCount(0), - blockNode(NULL), + blockNode(ParseHandler::null()), decls_(prs->context), args_(prs->context), vars_(prs->context), - yieldNode(NULL), + yieldNode(ParseHandler::null()), parserPC(&prs->pc), lexdeps(prs->context), parent(prs->pc), @@ -55,8 +59,9 @@ ParseContext::ParseContext(Parser *prs, SharedContext *sc, unsigned staticLevel, prs->pc = this; } +template inline bool -ParseContext::init() +ParseContext::init() { if (!frontend::GenerateBlockId(this, this->bodyid)) return false; @@ -64,8 +69,9 @@ ParseContext::init() return decls_.init() && lexdeps.ensureMap(sc->context); } +template inline -ParseContext::~ParseContext() +ParseContext::~ParseContext() { // |*parserPC| pointed to this object. Now that this object is about to // die, make |*parserPC| point to this object's parent. @@ -74,6 +80,33 @@ ParseContext::~ParseContext() js_delete(funcStmts); } +/* + * Check that it is permitted to introduce a binding for atom. Strict mode + * forbids introducing new definitions for 'eval', 'arguments', or for any + * strict mode reserved keyword. Use pn for reporting error locations, or use + * pc's token stream if pn is NULL. + */ +template +static bool +CheckStrictBinding(JSContext *cx, ParseHandler *handler, ParseContext *pc, + HandlePropertyName name, ParseNode *pn) +{ + if (!pc->sc->needStrictChecks()) + return true; + + if (name == cx->names().eval || + name == cx->names().arguments || + FindKeyword(name->charsZ(), name->length())) + { + JSAutoByteString bytes; + if (!js_AtomToPrintableString(cx, name, &bytes)) + return false; + return handler->report(ParseStrictError, pn, JSMSG_BAD_BINDING, bytes.ptr()); + } + + return true; +} + } // namespace frontend } // namespace js diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index f39c03944edb..bcc89584cc45 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -60,7 +60,9 @@ using namespace js; using namespace js::gc; -using namespace js::frontend; + +namespace js { +namespace frontend { typedef Rooted RootedStaticBlockObject; typedef Handle HandleStaticBlockObject; @@ -74,20 +76,15 @@ typedef MutableHandle MutableHandlePropertyName; #define MUST_MATCH_TOKEN_WITH_FLAGS(tt, errno, __flags) \ JS_BEGIN_MACRO \ if (tokenStream.getToken((__flags)) != tt) { \ - reportError(NULL, errno); \ - return NULL; \ + report(ParseError, false, null(), errno); \ + return null(); \ } \ JS_END_MACRO #define MUST_MATCH_TOKEN(tt, errno) MUST_MATCH_TOKEN_WITH_FLAGS(tt, errno, 0) +template bool -StrictModeGetter::get() const -{ - return parser->pc->sc->strict; -} - -bool -frontend::GenerateBlockId(ParseContext *pc, uint32_t &blockid) +GenerateBlockId(ParseContext *pc, uint32_t &blockid) { if (pc->blockidGen == JS_BIT(20)) { JS_ReportErrorNumber(pc->sc->context, js_GetErrorMessage, NULL, JSMSG_NEED_DIET, "program"); @@ -98,16 +95,25 @@ frontend::GenerateBlockId(ParseContext *pc, uint32_t &blockid) return true; } +template bool +GenerateBlockId(ParseContext *pc, uint32_t &blockid); + +template bool +GenerateBlockId(ParseContext *pc, uint32_t &blockid); + +template static void -PushStatementPC(ParseContext *pc, StmtInfoPC *stmt, StmtType type) +PushStatementPC(ParseContext *pc, StmtInfoPC *stmt, StmtType type) { stmt->blockid = pc->blockid(); PushStatement(pc, stmt, type); } // See comment on member function declaration. +template <> bool -ParseContext::define(JSContext *cx, HandlePropertyName name, ParseNode *pn, Definition::Kind kind) +ParseContext::define(JSContext *cx, HandlePropertyName name, + ParseNode *pn, Definition::Kind kind) { JS_ASSERT(!pn->isUsed()); JS_ASSERT_IF(pn->isDefn(), pn->isPlaceholder()); @@ -185,7 +191,7 @@ ParseContext::define(JSContext *cx, HandlePropertyName name, ParseNode *pn, Defi case Definition::LET: dn->setOp(JSOP_GETLOCAL); dn->pn_dflags |= (PND_LET | PND_BOUND); - JS_ASSERT(dn->pn_cookie.level() == staticLevel); /* see BindLet */ + JS_ASSERT(dn->pn_cookie.level() == staticLevel); /* see bindLet */ if (!decls_.addShadow(name, dn)) return false; break; @@ -199,8 +205,17 @@ ParseContext::define(JSContext *cx, HandlePropertyName name, ParseNode *pn, Defi return true; } +template <> +bool +ParseContext::define(JSContext *cx, HandlePropertyName name, Node pn, + Definition::Kind kind) +{ + return true; +} + +template void -ParseContext::prepareToAddDuplicateArg(Definition *prevDecl) +ParseContext::prepareToAddDuplicateArg(Definition *prevDecl) { JS_ASSERT(prevDecl->kind() == Definition::ARG); JS_ASSERT(decls_.lookupFirst(prevDecl->name()) == prevDecl); @@ -208,8 +223,9 @@ ParseContext::prepareToAddDuplicateArg(Definition *prevDecl) decls_.remove(prevDecl->name()); } +template void -ParseContext::updateDecl(JSAtom *atom, ParseNode *pn) +ParseContext::updateDecl(JSAtom *atom, Node pn) { Definition *oldDecl = decls_.lookupFirst(atom); @@ -238,15 +254,17 @@ ParseContext::updateDecl(JSAtom *atom, ParseNode *pn) } } +template void -ParseContext::popLetDecl(JSAtom *atom) +ParseContext::popLetDecl(JSAtom *atom) { JS_ASSERT(decls_.lookupFirst(atom)->isLet()); decls_.remove(atom); } +template static void -AppendPackedBindings(const ParseContext *pc, const DeclVector &vec, Binding *dst) +AppendPackedBindings(const ParseContext *pc, const DeclVector &vec, Binding *dst) { for (unsigned i = 0; i < vec.length(); ++i, ++dst) { Definition *dn = vec[i]; @@ -283,8 +301,9 @@ AppendPackedBindings(const ParseContext *pc, const DeclVector &vec, Binding *dst } } +template bool -ParseContext::generateFunctionBindings(JSContext *cx, InternalHandle bindings) const +ParseContext::generateFunctionBindings(JSContext *cx, InternalHandle bindings) const { JS_ASSERT(sc->isFunctionBox()); @@ -311,27 +330,56 @@ ParseContext::generateFunctionBindings(JSContext *cx, InternalHandle return true; } -Parser::Parser(JSContext *cx, const CompileOptions &options, - const jschar *chars, size_t length, bool foldConstants) +template +bool +Parser::report(ParseReportKind kind, bool strict, Node pn, unsigned errorNumber, ...) +{ + TokenPos pos = pn ? handler.getPosition(pn) : tokenStream.currentToken().pos; + + va_list args; + va_start(args, errorNumber); + bool result = false; + switch (kind) { + case ParseError: + result = tokenStream.reportCompileErrorNumberVA(pos, JSREPORT_ERROR, errorNumber, args); + break; + case ParseWarning: + result = tokenStream.reportCompileErrorNumberVA(pos, JSREPORT_WARNING, errorNumber, args); + break; + case ParseStrictWarning: + result = tokenStream.reportStrictWarningErrorNumberVA(pos, errorNumber, args); + break; + case ParseStrictError: + result = tokenStream.reportStrictModeErrorNumberVA(pos, strict, errorNumber, args); + break; + } + va_end(args); + return result; +} + +template +Parser::Parser(JSContext *cx, const CompileOptions &options, + const jschar *chars, size_t length, bool foldConstants) : AutoGCRooter(cx, PARSER), context(cx), - strictModeGetter(thisForCtor()), - tokenStream(cx, options, chars, length, &strictModeGetter), + tokenStream(cx, options, chars, length, thisForCtor()), tempPoolMark(NULL), - allocator(cx), traceListHead(NULL), pc(NULL), sct(NULL), keepAtoms(cx->runtime), foldConstants(foldConstants), compileAndGo(options.compileAndGo), - selfHostingMode(options.selfHostingMode) + selfHostingMode(options.selfHostingMode), + unknownResult(false), + handler(cx, tokenStream, foldConstants) { cx->activeCompilations++; } +template bool -Parser::init() +Parser::init() { if (!context->ensureParseMapPool()) return false; @@ -340,15 +388,17 @@ Parser::init() return true; } -Parser::~Parser() +template +Parser::~Parser() { JSContext *cx = context; cx->tempLifoAlloc().release(tempPoolMark); cx->activeCompilations--; } +template ObjectBox * -Parser::newObjectBox(JSObject *obj) +Parser::newObjectBox(JSObject *obj) { JS_ASSERT(obj && !IsPoisonedPtr(obj)); @@ -371,8 +421,9 @@ Parser::newObjectBox(JSObject *obj) return objbox; } +template FunctionBox::FunctionBox(JSContext *cx, ObjectBox* traceListHead, JSFunction *fun, - ParseContext *outerpc, bool strict) + ParseContext *outerpc, bool strict) : ObjectBox(fun, traceListHead), SharedContext(cx, strict), bindings(), @@ -426,8 +477,10 @@ FunctionBox::FunctionBox(JSContext *cx, ObjectBox* traceListHead, JSFunction *fu } } +template FunctionBox * -Parser::newFunctionBox(JSFunction *fun, ParseContext *outerpc, bool strict) +Parser::newFunctionBox(JSFunction *fun, + ParseContext *outerpc, bool strict) { JS_ASSERT(fun && !IsPoisonedPtr(fun)); @@ -450,14 +503,16 @@ Parser::newFunctionBox(JSFunction *fun, ParseContext *outerpc, bool strict) return funbox; } -ModuleBox::ModuleBox(JSContext *cx, ObjectBox *traceListHead, Module *module, ParseContext *pc) +ModuleBox::ModuleBox(JSContext *cx, ObjectBox *traceListHead, Module *module, + ParseContext *pc) : ObjectBox(module, traceListHead), SharedContext(cx, true) { } +template <> ModuleBox * -Parser::newModuleBox(Module *module, ParseContext *outerpc) +Parser::newModuleBox(Module *module, ParseContext *outerpc) { JS_ASSERT(module && !IsPoisonedPtr(module)); @@ -480,29 +535,19 @@ Parser::newModuleBox(Module *module, ParseContext *outerpc) return modulebox; } +template void -Parser::trace(JSTracer *trc) +Parser::trace(JSTracer *trc) { traceListHead->trace(trc); } -static bool -GenerateBlockIdForStmtNode(ParseNode *pn, ParseContext *pc) -{ - JS_ASSERT(pc->topStmt); - JS_ASSERT(pc->topStmt->maybeScope()); - JS_ASSERT(pn->isKind(PNK_STATEMENTLIST) || pn->isKind(PNK_LEXICALSCOPE)); - if (!GenerateBlockId(pc, pc->topStmt->blockid)) - return false; - pn->pn_blockid = pc->topStmt->blockid; - return true; -} - /* * Parse a top-level JS script. */ -ParseNode * -Parser::parse(JSObject *chain) +template +typename ParseHandler::Node +Parser::parse(JSObject *chain) { /* * Protect atoms from being collected by a GC activation, which might @@ -513,18 +558,19 @@ Parser::parse(JSObject *chain) * protected from the GC by a root or a stack frame reference. */ GlobalSharedContext globalsc(context, chain, StrictModeFromContext(context)); - ParseContext globalpc(this, &globalsc, /* staticLevel = */ 0, /* bodyid = */ 0); + ParseContext globalpc(this, &globalsc, /* staticLevel = */ 0, /* bodyid = */ 0); if (!globalpc.init()) - return NULL; + return null(); - ParseNode *pn = statements(); + Node pn = statements(); if (pn) { if (!tokenStream.matchToken(TOK_EOF)) { - reportError(NULL, JSMSG_SYNTAX_ERROR); - pn = NULL; - } else if (foldConstants) { + report(ParseError, false, null(), JSMSG_SYNTAX_ERROR); + return null(); + } + if (foldConstants) { if (!FoldConstants(context, &pn, this)) - pn = NULL; + return null(); } } return pn; @@ -536,9 +582,11 @@ Parser::parse(JSObject *chain) * that contains an early return e1 will get a strict warning. Similarly for * iloops: while (true){...} is treated as though ... returns. */ -#define ENDS_IN_OTHER 0 -#define ENDS_IN_RETURN 1 -#define ENDS_IN_BREAK 2 +enum { + ENDS_IN_OTHER = 0, + ENDS_IN_RETURN = 1, + ENDS_IN_BREAK = 2 +}; static int HasFinalReturn(ParseNode *pn) @@ -655,27 +703,35 @@ HasFinalReturn(ParseNode *pn) } } -static bool -ReportBadReturn(JSContext *cx, Parser *parser, ParseNode *pn, Parser::Reporter reporter, - unsigned errnum, unsigned anonerrnum) +static int +HasFinalReturn(SyntaxParseHandler::Node pn) +{ + return ENDS_IN_RETURN; +} + +template +bool +Parser::reportBadReturn(Node pn, ParseReportKind kind, + unsigned errnum, unsigned anonerrnum) { JSAutoByteString name; - JSAtom *atom = parser->pc->sc->asFunctionBox()->function()->atom(); + JSAtom *atom = pc->sc->asFunctionBox()->function()->atom(); if (atom) { - if (!js_AtomToPrintableString(cx, atom, &name)) + if (!js_AtomToPrintableString(context, atom, &name)) return false; } else { errnum = anonerrnum; } - return (parser->*reporter)(pn, errnum, name.ptr()); + return report(kind, pc->sc->strict, pn, errnum, name.ptr()); } -static bool -CheckFinalReturn(JSContext *cx, Parser *parser, ParseNode *pn) +template +bool +Parser::checkFinalReturn(Node pn) { - JS_ASSERT(parser->pc->sc->isFunctionBox()); + JS_ASSERT(pc->sc->isFunctionBox()); return HasFinalReturn(pn) == ENDS_IN_RETURN || - ReportBadReturn(cx, parser, pn, &Parser::reportStrictWarning, + reportBadReturn(pn, ParseStrictWarning, JSMSG_NO_RETURN_VALUE, JSMSG_ANON_NO_RETURN_VALUE); } @@ -683,18 +739,24 @@ CheckFinalReturn(JSContext *cx, Parser *parser, ParseNode *pn) * Check that it is permitted to assign to lhs. Strict mode code may not * assign to 'eval' or 'arguments'. */ -static bool -CheckStrictAssignment(JSContext *cx, Parser *parser, ParseNode *lhs) +template +bool +Parser::checkStrictAssignment(Node lhs) { - if (parser->pc->sc->needStrictChecks() && lhs->isKind(PNK_NAME)) { - JSAtom *atom = lhs->pn_atom; - if (atom == cx->names().eval || atom == cx->names().arguments) { - JSAutoByteString name; - if (!js_AtomToPrintableString(cx, atom, &name) || - !parser->reportStrictModeError(lhs, JSMSG_DEPRECATED_ASSIGN, name.ptr())) - { - return false; - } + if (!pc->sc->needStrictChecks()) + return true; + + JSAtom *atom = handler.isName(lhs); + if (!atom) + return true; + + if (atom == context->names().eval || atom == context->names().arguments) { + JSAutoByteString name; + if (!js_AtomToPrintableString(context, atom, &name) || + !report(ParseStrictError, pc->sc->strict, lhs, + JSMSG_DEPRECATED_ASSIGN, name.ptr())) + { + return false; } } return true; @@ -706,111 +768,81 @@ CheckStrictAssignment(JSContext *cx, Parser *parser, ParseNode *lhs) * strict mode reserved keyword. Use pn for reporting error locations, or use * pc's token stream if pn is NULL. */ +template bool -CheckStrictBinding(JSContext *cx, Parser *parser, HandlePropertyName name, ParseNode *pn) +Parser::checkStrictBinding(HandlePropertyName name, Node pn) { - if (!parser->pc->sc->needStrictChecks()) + if (!pc->sc->needStrictChecks()) return true; - if (name == cx->names().eval || - name == cx->names().arguments || + if (name == context->names().eval || + name == context->names().arguments || FindKeyword(name->charsZ(), name->length())) { JSAutoByteString bytes; - if (!js_AtomToPrintableString(cx, name, &bytes)) + if (!js_AtomToPrintableString(context, name, &bytes)) return false; - return parser->reportStrictModeError(pn, JSMSG_BAD_BINDING, bytes.ptr()); + return report(ParseStrictError, pc->sc->strict, pn, + JSMSG_BAD_BINDING, bytes.ptr()); } return true; } +template <> +bool +Parser::defineArg(ParseNode *funcpn, HandlePropertyName name, + bool disallowDuplicateArgs, Definition **duplicatedArg); + +template <> ParseNode * -Parser::standaloneFunctionBody(HandleFunction fun, const AutoNameVector &formals, HandleScript script, - ParseNode *fn, FunctionBox **funbox, bool strict, bool *becameStrict) +Parser::standaloneFunctionBody(HandleFunction fun, const AutoNameVector &formals, + HandleScript script, Node fn, FunctionBox **funbox, + bool strict, bool *becameStrict) { if (becameStrict) *becameStrict = false; *funbox = newFunctionBox(fun, /* outerpc = */ NULL, strict); if (!funbox) - return NULL; + return null(); + handler.setFunctionBox(fn, *funbox); - fn->pn_funbox = *funbox; - - ParseContext funpc(this, *funbox, 0, /* staticLevel = */ 0); + ParseContext funpc(this, *funbox, 0, /* staticLevel = */ 0); if (!funpc.init()) - return NULL; + return null(); for (unsigned i = 0; i < formals.length(); i++) { - if (!DefineArg(this, fn, formals[i])) - return NULL; + if (!defineArg(fn, formals[i])) + return null(); } ParseNode *pn = functionBody(StatementListBody); if (!pn) { if (becameStrict && pc->funBecameStrict) *becameStrict = true; - return NULL; + return null(); } if (!tokenStream.matchToken(TOK_EOF)) { - reportError(NULL, JSMSG_SYNTAX_ERROR); - return NULL; + report(ParseError, false, null(), JSMSG_SYNTAX_ERROR); + return null(); } if (!FoldConstants(context, &pn, this)) - return NULL; + return null(); InternalHandle bindings(script, &script->bindings); if (!funpc.generateFunctionBindings(context, bindings)) - return NULL; + return null(); return pn; } -ParseNode * -Parser::functionBody(FunctionBodyType type) +template <> +bool +Parser::checkFunctionArguments() { - JS_ASSERT(pc->sc->isFunctionBox()); - JS_ASSERT(!pc->funHasReturnExpr && !pc->funHasReturnVoid); - - ParseNode *pn; - if (type == StatementListBody) { - pn = statements(); - } else { - JS_ASSERT(type == ExpressionBody); - JS_ASSERT(JS_HAS_EXPR_CLOSURES); - - pn = UnaryNode::create(PNK_RETURN, this); - if (pn) { - pn->pn_kid = assignExpr(); - if (!pn->pn_kid) { - pn = NULL; - } else { - if (pc->sc->asFunctionBox()->isGenerator()) { - ReportBadReturn(context, this, pn, &Parser::reportError, - JSMSG_BAD_GENERATOR_RETURN, - JSMSG_BAD_ANON_GENERATOR_RETURN); - pn = NULL; - } else { - pn->setOp(JSOP_RETURN); - pn->pn_pos.end = pn->pn_kid->pn_pos.end; - } - } - } - } - - if (!pn) - return NULL; - - /* Check for falling off the end of a function that returns a value. */ - if (context->hasStrictOption() && pc->funHasReturnExpr && - !CheckFinalReturn(context, this, pn)) - { - pn = NULL; - } - /* Time to implement the odd semantics of 'arguments'. */ HandlePropertyName arguments = context->names().arguments; @@ -843,7 +875,7 @@ Parser::functionBody(FunctionBodyType type) pc->lexdeps->remove(arguments); dn->pn_dflags |= PND_IMPLICITARGUMENTS; if (!pc->define(context, arguments, dn, Definition::VAR)) - return NULL; + return false; break; } } @@ -857,8 +889,8 @@ Parser::functionBody(FunctionBodyType type) bool argumentsHasLocalBinding = maybeArgDef && maybeArgDef->kind() != Definition::ARG; bool hasRest = pc->sc->asFunctionBox()->function()->hasRest(); if (hasRest && argumentsHasLocalBinding) { - reportError(NULL, JSMSG_ARGUMENTS_AND_REST); - return NULL; + report(ParseError, false, NULL, JSMSG_ARGUMENTS_AND_REST); + return false; } /* @@ -867,11 +899,11 @@ Parser::functionBody(FunctionBodyType type) * parameters are free from 'arguments'. */ if (!argumentsHasBinding && pc->sc->bindingsAccessedDynamically() && !hasRest) { - ParseNode *pn = NameNode::create(PNK_NAME, arguments, this, pc); + ParseNode *pn = NameNode::create(PNK_NAME, arguments, &handler, pc); if (!pn) - return NULL; + return false; if (!pc->define(context, arguments, pn, Definition::VAR)) - return NULL; + return false; argumentsHasBinding = true; argumentsHasLocalBinding = true; } @@ -916,19 +948,65 @@ Parser::functionBody(FunctionBodyType type) DefinitionList &dlist = r.front().value(); for (DefinitionList::Range dr = dlist.all(); !dr.empty(); dr.popFront()) { Definition *dn = dr.front(); - if (dn->kind() == Definition::ARG && dn->isAssigned()) { + if (dn->kind() == Definition::ARG && dn->isAssigned()) funbox->setDefinitelyNeedsArgsObj(); - goto exitLoop; - } } } /* Watch for mutation of arguments through e.g. eval(). */ if (pc->sc->bindingsAccessedDynamically()) funbox->setDefinitelyNeedsArgsObj(); - exitLoop: ; } } + return true; +} + +template <> +bool +Parser::checkFunctionArguments() +{ + return true; +} + +template +typename ParseHandler::Node +Parser::functionBody(FunctionBodyType type) +{ + JS_ASSERT(pc->sc->isFunctionBox()); + JS_ASSERT(!pc->funHasReturnExpr && !pc->funHasReturnVoid); + + Node pn; + if (type == StatementListBody) { + pn = statements(); + if (!pn) + return null(); + } else { + JS_ASSERT(type == ExpressionBody); + JS_ASSERT(JS_HAS_EXPR_CLOSURES); + + Node kid = assignExpr(); + if (!kid) + return null(); + + pn = handler.newUnary(PNK_RETURN, kid, JSOP_RETURN); + if (!pn) + return null(); + + if (pc->sc->asFunctionBox()->isGenerator()) { + reportBadReturn(pn, ParseError, + JSMSG_BAD_GENERATOR_RETURN, + JSMSG_BAD_ANON_GENERATOR_RETURN); + return null(); + } + } + + /* Check for falling off the end of a function that returns a value. */ + if (context->hasStrictOption() && pc->funHasReturnExpr && !checkFinalReturn(pn)) + return null(); + + if (!checkFunctionArguments()) + return null(); + return pn; } @@ -937,9 +1015,9 @@ Parser::functionBody(FunctionBodyType type) // SharedContext passed in separately, because in this case |pc| may not equal // |parser->pc|. static Definition * -MakePlaceholder(ParseNode *pn, Parser *parser, ParseContext *pc) +MakePlaceholder(ParseNode *pn, FullParseHandler *handler, ParseContext *pc) { - Definition *dn = (Definition *) NameNode::create(PNK_NAME, pn->pn_atom, parser, pc); + Definition *dn = (Definition *) NameNode::create(PNK_NAME, pn->pn_atom, handler, pc); if (!dn) return NULL; @@ -965,42 +1043,18 @@ ForgetUse(ParseNode *pn) pn->setUsed(false); } -static ParseNode * -MakeAssignment(ParseNode *pn, ParseNode *rhs, Parser *parser) +static void +ForgetUse(SyntaxParseHandler::Node pn) { - ParseNode *lhs = parser->cloneNode(*pn); - if (!lhs) - return NULL; - - if (pn->isUsed()) { - Definition *dn = pn->pn_lexdef; - ParseNode **pnup = &dn->dn_uses; - - while (*pnup != pn) - pnup = &(*pnup)->pn_link; - *pnup = lhs; - lhs->pn_link = pn->pn_link; - pn->pn_link = NULL; - } - - pn->setKind(PNK_ASSIGN); - pn->setOp(JSOP_NOP); - pn->setArity(PN_BINARY); - pn->setInParens(false); - pn->setUsed(false); - pn->setDefn(false); - pn->pn_left = lhs; - pn->pn_right = rhs; - pn->pn_pos.end = rhs->pn_pos.end; - return lhs; } /* See comment for use in Parser::functionDef. */ -static bool -MakeDefIntoUse(Definition *dn, ParseNode *pn, JSAtom *atom, Parser *parser) +template <> +bool +Parser::makeDefIntoUse(Definition *dn, ParseNode *pn, JSAtom *atom) { /* Turn pn into a definition. */ - parser->pc->updateDecl(atom, pn); + pc->updateDecl(atom, pn); /* Change all uses of dn to be uses of pn. */ for (ParseNode *pnu = dn->dn_uses; pnu; pnu = pnu->pn_link) { @@ -1030,7 +1084,7 @@ MakeDefIntoUse(Definition *dn, ParseNode *pn, JSAtom *atom, Parser *parser) if (dn->getKind() == PNK_FUNCTION) { JS_ASSERT(dn->functionIsHoisted()); pn->dn_uses = dn->pn_link; - parser->prepareNodeForMutation(dn); + handler.prepareNodeForMutation(dn); dn->setKind(PNK_NOP); dn->setArity(PN_NULLARY); return true; @@ -1043,7 +1097,7 @@ MakeDefIntoUse(Definition *dn, ParseNode *pn, JSAtom *atom, Parser *parser) */ if (dn->canHaveInitializer()) { if (ParseNode *rhs = dn->expr()) { - ParseNode *lhs = MakeAssignment(dn, rhs, parser); + ParseNode *lhs = handler.makeAssignment(dn, rhs); if (!lhs) return false; pn->dn_uses = lhs; @@ -1072,20 +1126,18 @@ MakeDefIntoUse(Definition *dn, ParseNode *pn, JSAtom *atom, Parser *parser) * function is called indirectly from the variable declaration parser by way * of CheckDestructuring and its friends. */ -typedef bool -(*Binder)(JSContext *cx, BindData *data, HandlePropertyName name, Parser *parser); -static bool -BindLet(JSContext *cx, BindData *data, HandlePropertyName name, Parser *parser); - -static bool -BindVarOrConst(JSContext *cx, BindData *data, HandlePropertyName name, Parser *parser); - -struct frontend::BindData { +template +struct BindData +{ BindData(JSContext *cx) : let(cx) {} - ParseNode *pn; /* name node for definition processing and - error source coordinates */ + typedef bool + (*Binder)(JSContext *cx, BindData *data, HandlePropertyName name, Parser *parser); + + /* name node for definition processing and error source coordinates */ + typename ParseHandler::Node pn; + JSOp op; /* prolog bytecode or nop */ Binder binder; /* binder, discriminates u */ @@ -1097,9 +1149,9 @@ struct frontend::BindData { } let; void initLet(VarContext varContext, StaticBlockObject &blockObj, unsigned overflow) { - this->pn = NULL; + this->pn = ParseHandler::null(); this->op = JSOP_NOP; - this->binder = BindLet; + this->binder = Parser::bindLet; this->let.varContext = varContext; this->let.blockObj = &blockObj; this->let.overflow = overflow; @@ -1107,12 +1159,14 @@ struct frontend::BindData { void initVarOrConst(JSOp op) { this->op = op; - this->binder = BindVarOrConst; + this->binder = Parser::bindVarOrConst; } }; +template JSFunction * -Parser::newFunction(ParseContext *pc, HandleAtom atom, FunctionSyntaxKind kind) +Parser::newFunction(ParseContext *pc, HandleAtom atom, + FunctionSyntaxKind kind) { JS_ASSERT_IF(kind == Statement, atom != NULL); @@ -1185,13 +1239,13 @@ DeoptimizeUsesWithin(Definition *dn, const TokenPos &pos) * constructor or JSAPI. To always execute code when a function has finished * parsing, use Parser::functionBody. */ -static bool -LeaveFunction(ParseNode *fn, Parser *parser, HandlePropertyName funName, - FunctionSyntaxKind kind = Expression) +template <> +bool +Parser::leaveFunction(ParseNode *fn, HandlePropertyName funName, + FunctionSyntaxKind kind) { - JSContext *cx = parser->context; - ParseContext *funpc = parser->pc; - ParseContext *pc = funpc->parent; + ParseContext *funpc = pc; + ParseContext *pc = funpc->parent; pc->blockidGen = funpc->blockidGen; FunctionBox *funbox = fn->pn_funbox; @@ -1209,7 +1263,7 @@ LeaveFunction(ParseNode *fn, Parser *parser, HandlePropertyName funName, if (atom == funName && kind == Expression) { dn->setOp(JSOP_CALLEE); - if (!dn->pn_cookie.set(cx, funpc->staticLevel, + if (!dn->pn_cookie.set(context, funpc->staticLevel, UpvarCookie::CALLEE_SLOT)) return false; dn->pn_dflags |= PND_BOUND; @@ -1267,7 +1321,7 @@ LeaveFunction(ParseNode *fn, Parser *parser, HandlePropertyName funName, * inherited lexdeps into uses of a new outer definition * allows us to handle both these cases in a natural way. */ - outer_dn = MakePlaceholder(dn, parser, pc); + outer_dn = MakePlaceholder(dn, &handler, pc); if (!outer_dn || !pc->lexdeps->add(p, atom, outer_dn)) return false; } @@ -1309,15 +1363,24 @@ LeaveFunction(ParseNode *fn, Parser *parser, HandlePropertyName funName, InternalHandle bindings = InternalHandle::fromMarkedLocation(&funbox->bindings); - if (!funpc->generateFunctionBindings(cx, bindings)) + if (!funpc->generateFunctionBindings(context, bindings)) return false; - funpc->lexdeps.releaseMap(cx); + funpc->lexdeps.releaseMap(context); + return true; +} + +template <> +bool +Parser::leaveFunction(Node fn, HandlePropertyName funName, + FunctionSyntaxKind kind) +{ + pc->lexdeps.releaseMap(context); return true; } /* - * DefineArg is called for both the arguments of a regular function definition + * defineArg is called for both the arguments of a regular function definition * and the arguments specified by the Function constructor. * * The 'disallowDuplicateArgs' bool indicates whether the use of another @@ -1330,12 +1393,11 @@ LeaveFunction(ParseNode *fn, Parser *parser, HandlePropertyName funName, * argument with the same name. The caller may use this to report an error when * one of the abovementioned features occurs after a duplicate. */ +template <> bool -frontend::DefineArg(Parser *parser, ParseNode *funcpn, HandlePropertyName name, - bool disallowDuplicateArgs, Definition **duplicatedArg) +Parser::defineArg(ParseNode *funcpn, HandlePropertyName name, + bool disallowDuplicateArgs, Definition **duplicatedArg) { - JSContext *cx = parser->context; - ParseContext *pc = parser->pc; SharedContext *sc = pc->sc; /* Handle duplicate argument names. */ @@ -1348,14 +1410,17 @@ frontend::DefineArg(Parser *parser, ParseNode *funcpn, HandlePropertyName name, */ if (sc->needStrictChecks()) { JSAutoByteString bytes; - if (!js_AtomToPrintableString(cx, name, &bytes)) + if (!js_AtomToPrintableString(context, name, &bytes)) return false; - if (!parser->reportStrictModeError(prevDecl, JSMSG_DUPLICATE_FORMAL, bytes.ptr())) + if (!report(ParseStrictError, pc->sc->strict, prevDecl, + JSMSG_DUPLICATE_FORMAL, bytes.ptr())) + { return false; + } } if (disallowDuplicateArgs) { - parser->reportError(prevDecl, JSMSG_BAD_DUP_ARGS); + report(ParseError, false, prevDecl, JSMSG_BAD_DUP_ARGS); return false; } @@ -1366,41 +1431,52 @@ frontend::DefineArg(Parser *parser, ParseNode *funcpn, HandlePropertyName name, pc->prepareToAddDuplicateArg(prevDecl); } - ParseNode *argpn = NameNode::create(PNK_NAME, name, parser, parser->pc); + ParseNode *argpn = handler.newName(name, pc); if (!argpn) return false; - if (!CheckStrictBinding(parser->context, parser, name, argpn)) + if (!checkStrictBinding(name, argpn)) return false; funcpn->pn_body->append(argpn); - return parser->pc->define(parser->context, name, argpn, Definition::ARG); + return pc->define(context, name, argpn, Definition::ARG); +} + +template <> +bool +Parser::defineArg(Node funcpn, HandlePropertyName name, + bool disallowDuplicateArgs, DefinitionNode *duplicatedArg) +{ + return true; } #if JS_HAS_DESTRUCTURING -static bool -BindDestructuringArg(JSContext *cx, BindData *data, HandlePropertyName name, Parser *parser) +template +/* static */ bool +Parser::bindDestructuringArg(JSContext *cx, BindData *data, + HandlePropertyName name, Parser *parser) { - ParseContext *pc = parser->pc; + ParseContext *pc = parser->pc; JS_ASSERT(pc->sc->isFunctionBox()); if (pc->decls().lookupFirst(name)) { - parser->reportError(NULL, JSMSG_BAD_DUP_ARGS); + parser->report(ParseError, false, null(), JSMSG_BAD_DUP_ARGS); return false; } - if (!CheckStrictBinding(cx, parser, name, data->pn)) + if (!parser->checkStrictBinding(name, data->pn)) return false; return pc->define(cx, name, data->pn, Definition::VAR); } #endif /* JS_HAS_DESTRUCTURING */ +template bool -Parser::functionArguments(ParseNode **listp, ParseNode* funcpn, bool &hasRest) +Parser::functionArguments(Node *listp, Node funcpn, bool &hasRest) { if (tokenStream.getToken() != TOK_LP) { - reportError(NULL, JSMSG_PAREN_BEFORE_FORMAL); + report(ParseError, false, null(), JSMSG_PAREN_BEFORE_FORMAL); return false; } @@ -1409,25 +1485,22 @@ Parser::functionArguments(ParseNode **listp, ParseNode* funcpn, bool &hasRest) hasRest = false; - ParseNode *argsbody = ListNode::create(PNK_ARGSBODY, this); + Node argsbody = handler.newList(PNK_ARGSBODY); if (!argsbody) return false; - argsbody->setOp(JSOP_NOP); - argsbody->makeEmpty(); - - funcpn->pn_body = argsbody; + handler.setFunctionBody(funcpn, argsbody); if (!tokenStream.matchToken(TOK_RP)) { bool hasDefaults = false; - Definition *duplicatedArg = NULL; + DefinitionNode duplicatedArg = null(); bool destructuringArg = false; #if JS_HAS_DESTRUCTURING - ParseNode *list = NULL; + Node list = null(); #endif do { if (hasRest) { - reportError(NULL, JSMSG_PARAMETER_AFTER_REST); + report(ParseError, false, null(), JSMSG_PARAMETER_AFTER_REST); return false; } switch (TokenKind tt = tokenStream.getToken()) { @@ -1437,12 +1510,12 @@ Parser::functionArguments(ParseNode **listp, ParseNode* funcpn, bool &hasRest) { /* See comment below in the TOK_NAME case. */ if (duplicatedArg) { - reportError(duplicatedArg, JSMSG_BAD_DUP_ARGS); + report(ParseError, false, duplicatedArg, JSMSG_BAD_DUP_ARGS); return false; } if (hasDefaults) { - reportError(NULL, JSMSG_NONDEFAULT_FORMAL_AFTER_DEFAULT); + report(ParseError, false, null(), JSMSG_NONDEFAULT_FORMAL_AFTER_DEFAULT); return false; } @@ -1454,11 +1527,11 @@ Parser::functionArguments(ParseNode **listp, ParseNode* funcpn, bool &hasRest) * anonymous positional parameter, so here we must tweak our * binder and its data. */ - BindData data(context); - data.pn = NULL; + BindData data(context); + data.pn = ParseHandler::null(); data.op = JSOP_DEFVAR; - data.binder = BindDestructuringArg; - ParseNode *lhs = destructuringExpr(&data, tt); + data.binder = bindDestructuringArg; + Node lhs = destructuringExpr(&data, tt); if (!lhs) return false; @@ -1468,23 +1541,22 @@ Parser::functionArguments(ParseNode **listp, ParseNode* funcpn, bool &hasRest) * left-hand-side expression and accumulate it in list. */ HandlePropertyName name = context->names().empty; - ParseNode *rhs = NameNode::create(PNK_NAME, name, this, this->pc); + Node rhs = handler.newName(name, pc); if (!rhs) return false; if (!pc->define(context, name, rhs, Definition::ARG)) return false; - ParseNode *item = new_(PNK_ASSIGN, JSOP_NOP, lhs->pn_pos, lhs, rhs); + Node item = handler.newBinary(PNK_ASSIGN, lhs, rhs); if (!item) return false; if (list) { - list->append(item); + handler.addList(list, item); } else { - list = ListNode::create(PNK_VAR, this); + list = handler.newList(PNK_VAR, item); if (!list) return false; - list->initList(item); *listp = list; } break; @@ -1497,7 +1569,7 @@ Parser::functionArguments(ParseNode **listp, ParseNode* funcpn, bool &hasRest) tt = tokenStream.getToken(); if (tt != TOK_NAME) { if (tt != TOK_ERROR) - reportError(NULL, JSMSG_NO_REST_NAME); + report(ParseError, false, null(), JSMSG_NO_REST_NAME); return false; } /* Fall through */ @@ -1507,28 +1579,26 @@ Parser::functionArguments(ParseNode **listp, ParseNode* funcpn, bool &hasRest) { RootedPropertyName name(context, tokenStream.currentToken().name()); bool disallowDuplicateArgs = destructuringArg || hasDefaults; - if (!DefineArg(this, funcpn, name, disallowDuplicateArgs, &duplicatedArg)) + if (!defineArg(funcpn, name, disallowDuplicateArgs, &duplicatedArg)) return false; if (tokenStream.matchToken(TOK_ASSIGN)) { if (hasRest) { - reportError(NULL, JSMSG_REST_WITH_DEFAULT); + report(ParseError, false, null(), JSMSG_REST_WITH_DEFAULT); return false; } if (duplicatedArg) { - reportError(duplicatedArg, JSMSG_BAD_DUP_ARGS); + report(ParseError, false, duplicatedArg, JSMSG_BAD_DUP_ARGS); return false; } hasDefaults = true; - ParseNode *def_expr = assignExprWithoutYield(JSMSG_YIELD_IN_DEFAULT); + Node def_expr = assignExprWithoutYield(JSMSG_YIELD_IN_DEFAULT); if (!def_expr) return false; - ParseNode *arg = funcpn->pn_body->last(); - arg->pn_dflags |= PND_DEFAULT; - arg->pn_expr = def_expr; + handler.setLastFunctionArgumentDefault(funcpn, def_expr); funbox->ndefaults++; } else if (!hasRest && hasDefaults) { - reportError(NULL, JSMSG_NONDEFAULT_FORMAL_AFTER_DEFAULT); + report(ParseError, false, null(), JSMSG_NONDEFAULT_FORMAL_AFTER_DEFAULT); return false; } @@ -1536,7 +1606,7 @@ Parser::functionArguments(ParseNode **listp, ParseNode* funcpn, bool &hasRest) } default: - reportError(NULL, JSMSG_MISSING_FORMAL); + report(ParseError, false, null(), JSMSG_MISSING_FORMAL); /* FALL THROUGH */ case TOK_ERROR: return false; @@ -1544,7 +1614,7 @@ Parser::functionArguments(ParseNode **listp, ParseNode* funcpn, bool &hasRest) } while (tokenStream.matchToken(TOK_COMMA)); if (tokenStream.getToken() != TOK_RP) { - reportError(NULL, JSMSG_PAREN_AFTER_FORMAL); + report(ParseError, false, null(), JSMSG_PAREN_AFTER_FORMAL); return false; } } @@ -1552,23 +1622,16 @@ Parser::functionArguments(ParseNode **listp, ParseNode* funcpn, bool &hasRest) return true; } -ParseNode * -Parser::functionDef(HandlePropertyName funName, const TokenStream::Position &start, - FunctionType type, FunctionSyntaxKind kind) +template <> +bool +Parser::checkFunctionDefinition(HandlePropertyName funName, + ParseNode **pn_, FunctionSyntaxKind kind) { - JS_ASSERT_IF(kind == Statement, funName); - - /* Make a TOK_FUNCTION node. */ - ParseNode *pn = CodeNode::create(PNK_FUNCTION, this); - if (!pn) - return NULL; - pn->pn_body = NULL; - pn->pn_funbox = NULL; - pn->pn_cookie.makeFree(); - pn->pn_dflags = 0; + ParseNode *&pn = *pn_; /* Function statements add a binding to the enclosing scope. */ bool bodyLevel = pc->atBodyLevel(); + if (kind == Statement) { /* * Handle redeclaration and optimize cases where we can statically bind the @@ -1580,14 +1643,14 @@ Parser::functionDef(HandlePropertyName funName, const TokenStream::Position &sta if (context->hasStrictOption() || dn->kind() == Definition::CONST) { JSAutoByteString name; - Reporter reporter = (dn->kind() != Definition::CONST) - ? &Parser::reportStrictWarning - : &Parser::reportError; + ParseReportKind reporter = (dn->kind() != Definition::CONST) + ? ParseStrictWarning + : ParseError; if (!js_AtomToPrintableString(context, funName, &name) || - !(this->*reporter)(NULL, JSMSG_REDECLARED_VAR, Definition::kindString(dn->kind()), - name.ptr())) + !report(reporter, false, NULL, JSMSG_REDECLARED_VAR, + Definition::kindString(dn->kind()), name.ptr())) { - return NULL; + return false; } } @@ -1599,8 +1662,8 @@ Parser::functionDef(HandlePropertyName funName, const TokenStream::Position &sta * the function's binding (which is mutable), so turn any existing * declaration into a use. */ - if (bodyLevel && !MakeDefIntoUse(dn, pn, funName, this)) - return NULL; + if (bodyLevel && !makeDefIntoUse(dn, pn, funName)) + return false; } else if (bodyLevel) { /* * If this function was used before it was defined, claim the @@ -1618,12 +1681,12 @@ Parser::functionDef(HandlePropertyName funName, const TokenStream::Position &sta fn->pn_cookie.makeFree(); pc->lexdeps->remove(funName); - freeTree(pn); + handler.freeTree(pn); pn = fn; } if (!pc->define(context, funName, pn, Definition::VAR)) - return NULL; + return false; } /* @@ -1656,10 +1719,10 @@ Parser::functionDef(HandlePropertyName funName, const TokenStream::Position &sta if (!pc->funcStmts) { pc->funcStmts = context->new_(context); if (!pc->funcStmts || !pc->funcStmts->init()) - return NULL; + return false; } if (!pc->funcStmts->put(funName)) - return NULL; + return false; } /* No further binding (in BindNameToSlot) is needed for functions. */ @@ -1669,114 +1732,64 @@ Parser::functionDef(HandlePropertyName funName, const TokenStream::Position &sta pn->setOp(JSOP_LAMBDA); } + return true; +} + +template <> +bool +Parser::checkFunctionDefinition(HandlePropertyName funName, + Node *pn, FunctionSyntaxKind kind) +{ + return true; +} + +template +typename ParseHandler::Node +Parser::functionDef(HandlePropertyName funName, const TokenStream::Position &start, + FunctionType type, FunctionSyntaxKind kind) +{ + JS_ASSERT_IF(kind == Statement, funName); + + /* Make a TOK_FUNCTION node. */ + Node pn = handler.newFunctionDefinition(); + if (!pn) + return null(); + + if (!checkFunctionDefinition(funName, &pn, kind)) + return null(); + RootedFunction fun(context, newFunction(pc, funName, kind)); if (!fun) - return NULL; + return null(); // If the outer scope is strict, immediately parse the function in strict // mode. Otherwise, we parse it normally. If we see a "use strict" // directive, we backup and reparse it as strict. - pn->pn_body = NULL; + handler.setFunctionBody(pn, null()); bool initiallyStrict = pc->sc->strict; bool becameStrict; if (!functionArgsAndBody(pn, fun, funName, type, kind, initiallyStrict, &becameStrict)) { if (initiallyStrict || !becameStrict || tokenStream.hadError()) - return NULL; + return null(); // Reparse the function in strict mode. tokenStream.seek(start); if (funName && tokenStream.getToken() == TOK_ERROR) - return NULL; - pn->pn_body = NULL; + return null(); + handler.setFunctionBody(pn, null()); if (!functionArgsAndBody(pn, fun, funName, type, kind, true)) - return NULL; + return null(); } return pn; } +template <> bool -Parser::functionArgsAndBody(ParseNode *pn, HandleFunction fun, HandlePropertyName funName, FunctionType type, - FunctionSyntaxKind kind, bool strict, bool *becameStrict) +Parser::finishFunctionDefinition(ParseNode *pn, FunctionBox *funbox, + ParseNode *prelude, ParseNode *body, + ParseContext *outerpc) { - if (becameStrict) - *becameStrict = false; - ParseContext *outerpc = pc; - - // Create box for fun->object early to protect against last-ditch GC. - FunctionBox *funbox = newFunctionBox(fun, pc, strict); - if (!funbox) - return false; - - /* Initialize early for possible flags mutation via destructuringExpr. */ - ParseContext funpc(this, funbox, outerpc->staticLevel + 1, outerpc->blockidGen); - if (!funpc.init()) - return false; - - /* Now parse formal argument list and compute fun->nargs. */ - ParseNode *prelude = NULL; - bool hasRest; - if (!functionArguments(&prelude, pn, hasRest)) - return false; - - fun->setArgCount(funpc.numArgs()); - if (funbox->ndefaults) - fun->setHasDefaults(); - if (hasRest) - fun->setHasRest(); - - if (type == Getter && fun->nargs > 0) { - reportError(NULL, JSMSG_ACCESSOR_WRONG_ARGS, "getter", "no", "s"); - return false; - } - if (type == Setter && fun->nargs != 1) { - reportError(NULL, JSMSG_ACCESSOR_WRONG_ARGS, "setter", "one", ""); - return false; - } - - FunctionBodyType bodyType = StatementListBody; -#if JS_HAS_EXPR_CLOSURES - if (tokenStream.getToken(TSF_OPERAND) != TOK_LC) { - tokenStream.ungetToken(); - bodyType = ExpressionBody; - fun->setIsExprClosure(); - } -#else - if (!tokenStream.matchToken(TOK_LC)) { - reportError(NULL, JSMSG_CURLY_BEFORE_BODY); - return false; - } -#endif - - ParseNode *body = functionBody(bodyType); - if (!body) { - // Notify the caller if this function was discovered to be strict. - if (becameStrict && pc->funBecameStrict) - *becameStrict = true; - return false; - } - - if (funName && !CheckStrictBinding(context, this, funName, pn)) - return false; - -#if JS_HAS_EXPR_CLOSURES - if (bodyType == StatementListBody) { -#endif - if (!tokenStream.matchToken(TOK_RC)) { - reportError(NULL, JSMSG_CURLY_AFTER_BODY); - return false; - } - funbox->bufEnd = tokenStream.offsetOfToken(tokenStream.currentToken()) + 1; -#if JS_HAS_EXPR_CLOSURES - } else { - // We shouldn't call endOffset if the tokenizer got an error. - if (tokenStream.hadError()) - return false; - funbox->bufEnd = tokenStream.endOffset(tokenStream.currentToken()); - if (kind == Statement && !MatchOrInsertSemicolon(context, &tokenStream)) - return false; - } -#endif pn->pn_pos.end = tokenStream.currentToken().pos.end; /* @@ -1801,7 +1814,7 @@ Parser::functionArgsAndBody(ParseNode *pn, HandleFunction fun, HandlePropertyNam if (!body->isArity(PN_LIST)) { ParseNode *block; - block = ListNode::create(PNK_SEQ, this); + block = ListNode::create(PNK_SEQ, &handler); if (!block) return false; block->pn_pos = body->pn_pos; @@ -1810,7 +1823,7 @@ Parser::functionArgsAndBody(ParseNode *pn, HandleFunction fun, HandlePropertyNam body = block; } - ParseNode *item = UnaryNode::create(PNK_SEMI, this); + ParseNode *item = UnaryNode::create(PNK_SEMI, &handler); if (!item) return false; @@ -1825,37 +1838,126 @@ Parser::functionArgsAndBody(ParseNode *pn, HandleFunction fun, HandlePropertyNam } #endif - /* - * If any nested function scope does a dynamic scope access, all enclosing - * scopes may be accessed dynamically. - */ - if (funbox->bindingsAccessedDynamically()) - outerpc->sc->setBindingsAccessedDynamically(); - if (funbox->hasDebuggerStatement()) - outerpc->sc->setHasDebuggerStatement(); - pn->pn_funbox = funbox; pn->pn_body->append(body); pn->pn_body->pn_pos = body->pn_pos; pn->pn_blockid = outerpc->blockid(); - if (!LeaveFunction(pn, this, funName, kind)) - return false; - return true; } +template <> +bool +Parser::finishFunctionDefinition(Node pn, FunctionBox *funbox, + Node prelude, Node body, + ParseContext *outerpc) +{ + return true; +} + +template +bool +Parser::functionArgsAndBody(Node pn, HandleFunction fun, + HandlePropertyName funName, FunctionType type, + FunctionSyntaxKind kind, bool strict, bool *becameStrict) +{ + if (becameStrict) + *becameStrict = false; + ParseContext *outerpc = pc; + + // Create box for fun->object early to protect against last-ditch GC. + FunctionBox *funbox = newFunctionBox(fun, pc, strict); + if (!funbox) + return false; + + /* Initialize early for possible flags mutation via destructuringExpr. */ + ParseContext funpc(this, funbox, outerpc->staticLevel + 1, outerpc->blockidGen); + if (!funpc.init()) + return false; + + /* Now parse formal argument list and compute fun->nargs. */ + Node prelude = null(); + bool hasRest; + if (!functionArguments(&prelude, pn, hasRest)) + return false; + + fun->setArgCount(funpc.numArgs()); + if (funbox->ndefaults) + fun->setHasDefaults(); + if (hasRest) + fun->setHasRest(); + + if (type == Getter && fun->nargs > 0) { + report(ParseError, false, null(), JSMSG_ACCESSOR_WRONG_ARGS, "getter", "no", "s"); + return false; + } + if (type == Setter && fun->nargs != 1) { + report(ParseError, false, null(), JSMSG_ACCESSOR_WRONG_ARGS, "setter", "one", ""); + return false; + } + + FunctionBodyType bodyType = StatementListBody; +#if JS_HAS_EXPR_CLOSURES + if (tokenStream.getToken(TSF_OPERAND) != TOK_LC) { + tokenStream.ungetToken(); + bodyType = ExpressionBody; + fun->setIsExprClosure(); + } +#else + if (!tokenStream.matchToken(TOK_LC)) { + report(ParseError, false, null(), JSMSG_CURLY_BEFORE_BODY); + return false; + } +#endif + + Node body = functionBody(bodyType); + if (!body) { + // Notify the caller if this function was discovered to be strict. + if (becameStrict && pc->funBecameStrict) + *becameStrict = true; + return false; + } + + if (funName && !checkStrictBinding(funName, pn)) + return false; + +#if JS_HAS_EXPR_CLOSURES + if (bodyType == StatementListBody) { +#endif + if (!tokenStream.matchToken(TOK_RC)) { + report(ParseError, false, null(), JSMSG_CURLY_AFTER_BODY); + return false; + } + funbox->bufEnd = tokenStream.offsetOfToken(tokenStream.currentToken()) + 1; +#if JS_HAS_EXPR_CLOSURES + } else { + // We shouldn't call endOffset if the tokenizer got an error. + if (tokenStream.hadError()) + return false; + funbox->bufEnd = tokenStream.endOffset(tokenStream.currentToken()); + if (kind == Statement && !MatchOrInsertSemicolon(context, &tokenStream)) + return false; + } +#endif + + if (!finishFunctionDefinition(pn, funbox, prelude, body, outerpc)) + return false; + + return leaveFunction(pn, funName, kind); +} + +template <> ParseNode * -Parser::moduleDecl() +Parser::moduleDecl() { JS_ASSERT(tokenStream.currentToken().name() == context->runtime->atomState.module); if (!((pc->sc->isGlobalSharedContext() || pc->sc->isModuleBox()) && pc->atBodyLevel())) { - reportError(NULL, JSMSG_MODULE_STATEMENT); + report(ParseError, false, NULL, JSMSG_MODULE_STATEMENT); return NULL; } - ParseNode *pn = CodeNode::create(PNK_MODULE, this); + ParseNode *pn = CodeNode::create(PNK_MODULE, &handler); if (!pn) return NULL; JS_ALWAYS_TRUE(tokenStream.matchToken(TOK_STRING)); @@ -1868,7 +1970,7 @@ Parser::moduleDecl() return NULL; pn->pn_modulebox = modulebox; - ParseContext modulepc(this, modulebox, pc->staticLevel + 1, pc->blockidGen); + ParseContext modulepc(this, modulebox, pc->staticLevel + 1, pc->blockidGen); if (!modulepc.init()) return NULL; MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_MODULE); @@ -1880,8 +1982,17 @@ Parser::moduleDecl() return pn; } -ParseNode * -Parser::functionStmt() +template <> +SyntaxParseHandler::Node +Parser::moduleDecl() +{ + setUnknownResult(); + return SyntaxParseHandler::NodeFailure; +} + +template +typename ParseHandler::Node +Parser::functionStmt() { JS_ASSERT(tokenStream.currentToken().type == TOK_FUNCTION); RootedPropertyName name(context); @@ -1889,8 +2000,8 @@ Parser::functionStmt() name = tokenStream.currentToken().name(); } else { /* Unnamed function expressions are forbidden in statement context. */ - reportError(NULL, JSMSG_UNNAMED_FUNCTION_STMT); - return NULL; + report(ParseError, false, null(), JSMSG_UNNAMED_FUNCTION_STMT); + return null(); } TokenStream::Position start; @@ -1898,14 +2009,15 @@ Parser::functionStmt() /* We forbid function statements in strict mode code. */ if (!pc->atBodyLevel() && pc->sc->needStrictChecks() && - !reportStrictModeError(NULL, JSMSG_STRICT_FUNCTION_STATEMENT)) - return NULL; + !report(ParseStrictError, pc->sc->strict, null(), JSMSG_STRICT_FUNCTION_STATEMENT)) + return null(); return functionDef(name, start, Normal, Statement); } -ParseNode * -Parser::functionExpr() +template +typename ParseHandler::Node +Parser::functionExpr() { RootedPropertyName name(context); JS_ASSERT(tokenStream.currentToken().type == TOK_FUNCTION); @@ -1918,6 +2030,25 @@ Parser::functionExpr() return functionDef(name, start, Normal, Expression); } +/* + * Return true if this node, known to be an unparenthesized string literal, + * could be the string of a directive in a Directive Prologue. Directive + * strings never contain escape sequences or line continuations. + * isEscapeFreeStringLiteral, below, checks whether the node itself could be + * a directive. + */ +static inline bool +IsEscapeFreeStringLiteral(const TokenPos &pos, JSAtom *str) +{ + /* + * If the string's length in the source code is its length as a value, + * accounting for the quotes, then it must not contain any escape + * sequences or line continuations. + */ + return (pos.begin.lineno == pos.end.lineno && + pos.begin.index + str->length() + 2 == pos.end.index); +} + /* * Recognize Directive Prologue members and directives. Assuming |pn| is a * candidate for membership in a directive prologue, recognize directives and @@ -1937,15 +2068,18 @@ Parser::functionExpr() * future (because of the hex escape), the Directive Prologue extends through it * to the "use strict" statement, which is indeed a directive. */ +template bool -Parser::maybeParseDirective(ParseNode *pn, bool *cont) +Parser::maybeParseDirective(Node pn, bool *cont) { - *cont = pn->isStringExprStatement(); + TokenPos directivePos; + JSAtom *directive = handler.isStringExprStatement(pn, &directivePos); + + *cont = !!directive; if (!*cont) return true; - ParseNode *string = pn->pn_kid; - if (string->isEscapeFreeStringLiteral()) { + if (IsEscapeFreeStringLiteral(directivePos, directive)) { // Mark this statement as being a possibly legitimate part of a // directive prologue, so the bytecode emitter won't warn about it being // useless code. (We mustn't just omit the statement entirely yet, as it @@ -1956,9 +2090,8 @@ Parser::maybeParseDirective(ParseNode *pn, bool *cont) // directive in the future. We don't want to interfere with people // taking advantage of directive-prologue-enabled features that appear // in other browsers first. - pn->pn_prologue = true; + handler.setPrologue(pn); - JSAtom *directive = string->pn_atom; if (directive == context->runtime->atomState.useStrict) { // We're going to be in strict mode. Note that this scope explicitly // had "use strict"; @@ -1973,7 +2106,7 @@ Parser::maybeParseDirective(ParseNode *pn, bool *cont) // one possible strict violation that could occur in the // directive prologue -- octal escapes -- and complain now. if (tokenStream.sawOctalEscape()) { - reportError(NULL, JSMSG_DEPRECATED_OCTAL); + report(ParseError, false, null(), JSMSG_DEPRECATED_OCTAL); return false; } pc->sc->strict = true; @@ -1984,24 +2117,64 @@ Parser::maybeParseDirective(ParseNode *pn, bool *cont) return true; } +template <> +void +Parser::addStatementToList(ParseNode *pn, ParseNode *kid, bool *hasFunctionStmt) +{ + JS_ASSERT(pn->isKind(PNK_STATEMENTLIST)); + + if (kid->isKind(PNK_FUNCTION)) { + /* + * PNX_FUNCDEFS notifies the emitter that the block contains body- + * level function definitions that should be processed before the + * rest of nodes. + * + * |hasFunctionStmt| is for the TOK_LC case in Statement. It + * is relevant only for function definitions not at body-level, + * which we call function statements. + */ + if (pc->atBodyLevel()) { + pn->pn_xflags |= PNX_FUNCDEFS; + } else { + /* + * General deoptimization was done in functionDef, here we just + * need to tell TOK_LC in Parser::statement to add braces. + */ + JS_ASSERT_IF(pc->sc->isFunctionBox(), pc->sc->asFunctionBox()->hasExtensibleScope()); + if (hasFunctionStmt) + *hasFunctionStmt = true; + } + } + + pn->append(kid); + pn->pn_pos.end = kid->pn_pos.end; +} + +template <> +void +Parser::addStatementToList(Node pn, Node kid, bool *hasFunctionStmt) +{ +} + /* * Parse the statements in a block, creating a TOK_LC node that lists the * statements' trees. If called from block-parsing code, the caller must * match { before and } after. */ -ParseNode * -Parser::statements(bool *hasFunctionStmt) +template +typename ParseHandler::Node +Parser::statements(bool *hasFunctionStmt) { - JS_CHECK_RECURSION(context, return NULL); + JS_CHECK_RECURSION(context, return null()); if (hasFunctionStmt) *hasFunctionStmt = false; - ParseNode *pn = ListNode::create(PNK_STATEMENTLIST, this); + Node pn = handler.newList(PNK_STATEMENTLIST); if (!pn) - return NULL; - pn->makeEmpty(); - pn->pn_blockid = pc->blockid(); - ParseNode *saveBlock = pc->blockNode; + return null(); + handler.setBlockId(pn, pc->blockid()); + + Node saveBlock = pc->blockNode; pc->blockNode = pn; bool canHaveDirectives = pc->atBodyLevel(); @@ -2011,45 +2184,23 @@ Parser::statements(bool *hasFunctionStmt) if (tt == TOK_ERROR) { if (tokenStream.isEOF()) tokenStream.setUnexpectedEOF(); - return NULL; + return null(); } break; } - ParseNode *next = statement(); + Node next = statement(); if (!next) { if (tokenStream.isEOF()) tokenStream.setUnexpectedEOF(); - return NULL; + return null(); } if (canHaveDirectives) { if (!maybeParseDirective(next, &canHaveDirectives)) - return NULL; + return null(); } - if (next->isKind(PNK_FUNCTION)) { - /* - * PNX_FUNCDEFS notifies the emitter that the block contains body- - * level function definitions that should be processed before the - * rest of nodes. - * - * |hasFunctionStmt| is for the TOK_LC case in Statement. It - * is relevant only for function definitions not at body-level, - * which we call function statements. - */ - if (pc->atBodyLevel()) { - pn->pn_xflags |= PNX_FUNCDEFS; - } else { - /* - * General deoptimization was done in functionDef, here we just - * need to tell TOK_LC in Parser::statement to add braces. - */ - JS_ASSERT_IF(pc->sc->isFunctionBox(), pc->sc->asFunctionBox()->hasExtensibleScope()); - if (hasFunctionStmt) - *hasFunctionStmt = true; - } - } - pn->append(next); + addStatementToList(pn, next, hasFunctionStmt); } /* @@ -2061,27 +2212,25 @@ Parser::statements(bool *hasFunctionStmt) pn = pc->blockNode; pc->blockNode = saveBlock; - pn->pn_pos.end = tokenStream.currentToken().pos.end; - JS_ASSERT(pn->pn_pos.begin <= pn->pn_pos.end); + handler.setEndPosition(pn, tokenStream.currentToken().pos.end); return pn; } -ParseNode * -Parser::condition() +template +typename ParseHandler::Node +Parser::condition() { MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_COND); - ParseNode *pn = parenExpr(); + Node pn = parenExpr(); if (!pn) - return NULL; + return null(); MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_COND); /* Check for (a = b) and warn about possible (a == b) mistype. */ - JS_ASSERT_IF(pn->isKind(PNK_ASSIGN), pn->isOp(JSOP_NOP)); - if (pn->isKind(PNK_ASSIGN) && - !pn->isInParens() && - !reportStrictWarning(NULL, JSMSG_EQUAL_AS_ASSIGN)) + if (handler.isOperationWithoutParens(pn, PNK_ASSIGN) && + !report(ParseStrictWarning, false, null(), JSMSG_EQUAL_AS_ASSIGN)) { - return NULL; + return null(); } return pn; } @@ -2101,12 +2250,13 @@ MatchLabel(JSContext *cx, TokenStream *ts, MutableHandlePropertyName label) return true; } -static bool -ReportRedeclaration(JSContext *cx, Parser *parser, ParseNode *pn, bool isConst, JSAtom *atom) +template +bool +Parser::reportRedeclaration(Node pn, bool isConst, JSAtom *atom) { JSAutoByteString name; - if (js_AtomToPrintableString(cx, atom, &name)) - parser->reportError(pn, JSMSG_REDECLARED_VAR, isConst ? "const" : "variable", name.ptr()); + if (js_AtomToPrintableString(context, atom, &name)) + report(ParseError, false, pn, JSMSG_REDECLARED_VAR, isConst ? "const" : "variable", name.ptr()); return false; } @@ -2119,18 +2269,20 @@ ReportRedeclaration(JSContext *cx, Parser *parser, ParseNode *pn, bool isConst, * populate data->pn->pn_{op,cookie,defn,dflags}; and stash a pointer to * data->pn in a slot of the block object. */ -static bool -BindLet(JSContext *cx, BindData *data, HandlePropertyName name, Parser *parser) +template <> +/* static */ bool +Parser::bindLet(JSContext *cx, BindData *data, + HandlePropertyName name, Parser *parser) { - ParseContext *pc = parser->pc; + ParseContext *pc = parser->pc; ParseNode *pn = data->pn; - if (!CheckStrictBinding(cx, parser, name, pn)) + if (!parser->checkStrictBinding(name, pn)) return false; Rooted blockObj(cx, data->let.blockObj); unsigned blockCount = blockObj->slotCount(); if (blockCount == JS_BIT(16)) { - parser->reportError(pn, data->let.overflow); + parser->report(ParseError, false, pn, data->let.overflow); return false; } @@ -2152,7 +2304,7 @@ BindLet(JSContext *cx, BindData *data, HandlePropertyName name, Parser *parser) JS_ASSERT(!pc->atBodyLevel()); Definition *dn = pc->decls().lookupFirst(name); if (dn && dn->pn_blockid == pc->blockid()) - return ReportRedeclaration(cx, parser, pn, dn->isConst(), name); + return parser->reportRedeclaration(pn, dn->isConst(), name); if (!pc->define(cx, name, pn, Definition::LET)) return false; } @@ -2166,7 +2318,7 @@ BindLet(JSContext *cx, BindData *data, HandlePropertyName name, Parser *parser) RootedShape shape(cx, StaticBlockObject::addVar(cx, blockObj, id, blockCount, &redeclared)); if (!shape) { if (redeclared) - ReportRedeclaration(cx, parser, pn, false, name); + parser->reportRedeclaration(pn, false, name); return false; } @@ -2175,9 +2327,18 @@ BindLet(JSContext *cx, BindData *data, HandlePropertyName name, Parser *parser) return true; } -template +template <> +/* static */ bool +Parser::bindLet(JSContext *cx, BindData *data, + HandlePropertyName name, Parser *parser) +{ + return true; +} + +template static inline bool -ForEachLetDef(JSContext *cx, ParseContext *pc, HandleStaticBlockObject blockObj, Op op) +ForEachLetDef(JSContext *cx, ParseContext *pc, + HandleStaticBlockObject blockObj, Op op) { for (Shape::Range r = blockObj->lastProperty()->all(); !r.empty(); r.popFront()) { Shape::Range::AutoRooter rooter(cx, &r); @@ -2193,17 +2354,19 @@ ForEachLetDef(JSContext *cx, ParseContext *pc, HandleStaticBlockObject blockObj, return true; } +template struct PopLetDecl { - bool operator()(JSContext *, ParseContext *pc, HandleStaticBlockObject, const Shape &, - JSAtom *atom) + bool operator()(JSContext *, ParseContext *pc, HandleStaticBlockObject, + const Shape &, JSAtom *atom) { pc->popLetDecl(atom); return true; } }; +template static void -PopStatementPC(JSContext *cx, ParseContext *pc) +PopStatementPC(JSContext *cx, ParseContext *pc) { RootedStaticBlockObject blockObj(cx, pc->topStmt->blockObj); JS_ASSERT(!!blockObj == (pc->topStmt->isBlockScope)); @@ -2212,13 +2375,14 @@ PopStatementPC(JSContext *cx, ParseContext *pc) if (blockObj) { JS_ASSERT(!blockObj->inDictionaryMode()); - ForEachLetDef(cx, pc, blockObj, PopLetDecl()); + ForEachLetDef(cx, pc, blockObj, PopLetDecl()); blockObj->resetPrevBlockChainFromParser(); } } +template static inline bool -OuterLet(ParseContext *pc, StmtInfoPC *stmt, HandleAtom atom) +OuterLet(ParseContext *pc, StmtInfoPC *stmt, HandleAtom atom) { while (stmt->downScope) { stmt = LexicalLookup(pc, atom, NULL, stmt->downScope); @@ -2230,17 +2394,19 @@ OuterLet(ParseContext *pc, StmtInfoPC *stmt, HandleAtom atom) return false; } -static bool -BindVarOrConst(JSContext *cx, BindData *data, HandlePropertyName name, Parser *parser) +template <> +/* static */ bool +Parser::bindVarOrConst(JSContext *cx, BindData *data, + HandlePropertyName name, Parser *parser) { - ParseContext *pc = parser->pc; + ParseContext *pc = parser->pc; ParseNode *pn = data->pn; bool isConstDecl = data->op == JSOP_DEFCONST; /* Default best op for pn is JSOP_NAME; we'll try to improve below. */ pn->setOp(JSOP_NAME); - if (!CheckStrictBinding(cx, parser, name, pn)) + if (!parser->checkStrictBinding(name, pn)) return false; StmtInfoPC *stmt = LexicalLookup(pc, name, NULL, (StmtInfoPC *)NULL); @@ -2273,10 +2439,10 @@ BindVarOrConst(JSContext *cx, BindData *data, HandlePropertyName name, Parser *p return false; if (isConstDecl) { - parser->reportError(pn, JSMSG_REDECLARED_PARAM, bytes.ptr()); + parser->report(ParseError, false, pn, JSMSG_REDECLARED_PARAM, bytes.ptr()); return false; } - if (!parser->reportStrictWarning(pn, JSMSG_VAR_HIDES_ARG, bytes.ptr())) + if (!parser->report(ParseStrictWarning, false, pn, JSMSG_VAR_HIDES_ARG, bytes.ptr())) return false; } else { bool error = (isConstDecl || @@ -2289,11 +2455,10 @@ BindVarOrConst(JSContext *cx, BindData *data, HandlePropertyName name, Parser *p : error) { JSAutoByteString bytes; - Parser::Reporter reporter = - error ? &Parser::reportError : &Parser::reportStrictWarning; + ParseReportKind reporter = error ? ParseError : ParseStrictWarning; if (!js_AtomToPrintableString(cx, name, &bytes) || - !(parser->*reporter)(pn, JSMSG_REDECLARED_VAR, - Definition::kindString(dn_kind), bytes.ptr())) + !parser->report(reporter, false, pn, JSMSG_REDECLARED_VAR, + Definition::kindString(dn_kind), bytes.ptr())) { return false; } @@ -2304,46 +2469,48 @@ BindVarOrConst(JSContext *cx, BindData *data, HandlePropertyName name, Parser *p return true; } -static bool -MakeSetCall(JSContext *cx, ParseNode *pn, Parser *parser, unsigned msg) +template <> +/* static */ bool +Parser::bindVarOrConst(JSContext *cx, BindData *data, + HandlePropertyName name, + Parser *parser) +{ + return true; +} + +template <> +bool +Parser::makeSetCall(ParseNode *pn, unsigned msg) { JS_ASSERT(pn->isArity(PN_LIST)); JS_ASSERT(pn->isOp(JSOP_CALL) || pn->isOp(JSOP_EVAL) || pn->isOp(JSOP_FUNCALL) || pn->isOp(JSOP_FUNAPPLY)); - if (!parser->reportStrictModeError(pn, msg)) + if (!report(ParseStrictError, pc->sc->strict, pn, msg)) return false; ParseNode *pn2 = pn->pn_head; if (pn2->isKind(PNK_FUNCTION) && (pn2->pn_funbox->inGenexpLambda)) { - parser->reportError(pn, msg); + report(ParseError, false, pn, msg); return false; } pn->pn_xflags |= PNX_SETCALL; return true; } -static void -NoteLValue(ParseNode *pn) +template <> +bool +Parser::noteNameUse(ParseNode *pn) { - if (pn->isUsed()) - pn->pn_lexdef->pn_dflags |= PND_ASSIGNED; + RootedPropertyName name(context, pn->pn_atom->asPropertyName()); + StmtInfoPC *stmt = LexicalLookup(pc, name, NULL, (StmtInfoPC *)NULL); - pn->pn_dflags |= PND_ASSIGNED; -} - -static bool -NoteNameUse(ParseNode *pn, Parser *parser) -{ - RootedPropertyName name(parser->context, pn->pn_atom->asPropertyName()); - StmtInfoPC *stmt = LexicalLookup(parser->pc, name, NULL, (StmtInfoPC *)NULL); - - DefinitionList::Range defs = parser->pc->decls().lookupMulti(name); + DefinitionList::Range defs = pc->decls().lookupMulti(name); Definition *dn; if (!defs.empty()) { dn = defs.front(); } else { - if (AtomDefnAddPtr p = parser->pc->lexdeps->lookupForAdd(name)) { + if (AtomDefnAddPtr p = pc->lexdeps->lookupForAdd(name)) { dn = p.value(); } else { /* @@ -2354,8 +2521,8 @@ NoteNameUse(ParseNode *pn, Parser *parser) * - Be left as a free variable definition if we never * see the real definition. */ - dn = MakePlaceholder(pn, parser, parser->pc); - if (!dn || !parser->pc->lexdeps->add(p, name, dn)) + dn = MakePlaceholder(pn, &handler, pc); + if (!dn || !pc->lexdeps->add(p, name, dn)) return false; } } @@ -2369,17 +2536,25 @@ NoteNameUse(ParseNode *pn, Parser *parser) return true; } +template <> +bool +Parser::noteNameUse(Node pn) +{ + return true; +} + #if JS_HAS_DESTRUCTURING -static bool -BindDestructuringVar(JSContext *cx, BindData *data, ParseNode *pn, Parser *parser) +template <> +bool +Parser::bindDestructuringVar(BindData *data, ParseNode *pn) { JS_ASSERT(pn->isKind(PNK_NAME)); - RootedPropertyName name(cx, pn->pn_atom->asPropertyName()); + RootedPropertyName name(context, pn->pn_atom->asPropertyName()); data->pn = pn; - if (!data->binder(cx, data, name, parser)) + if (!data->binder(context, data, name, this)) return false; /* @@ -2396,7 +2571,7 @@ BindDestructuringVar(JSContext *cx, BindData *data, ParseNode *pn, Parser *parse if (data->op == JSOP_DEFCONST) pn->pn_dflags |= PND_CONST; - NoteLValue(pn); + handler.noteLValue(pn); return true; } @@ -2418,12 +2593,13 @@ BindDestructuringVar(JSContext *cx, BindData *data, ParseNode *pn, Parser *parse * JSOP_ENUMELEM yet, because the LHS may turn out to be an arg or local var, * which can be optimized further. So we select JSOP_SETNAME. */ -static bool -BindDestructuringLHS(JSContext *cx, ParseNode *pn, Parser *parser) +template <> +bool +Parser::bindDestructuringLHS(ParseNode *pn) { switch (pn->getKind()) { case PNK_NAME: - NoteLValue(pn); + handler.noteLValue(pn); /* FALL THROUGH */ case PNK_DOT: @@ -2438,12 +2614,12 @@ BindDestructuringLHS(JSContext *cx, ParseNode *pn, Parser *parser) break; case PNK_CALL: - if (!MakeSetCall(cx, pn, parser, JSMSG_BAD_LEFTSIDE_OF_ASS)) + if (!makeSetCall(pn, JSMSG_BAD_LEFTSIDE_OF_ASS)) return false; break; default: - parser->reportError(pn, JSMSG_BAD_LEFTSIDE_OF_ASS); + report(ParseError, false, pn, JSMSG_BAD_LEFTSIDE_OF_ASS); return false; } @@ -2489,19 +2665,20 @@ BindDestructuringLHS(JSContext *cx, ParseNode *pn, Parser *parser) * The 'toplevel' is a private detail of the recursive strategy used by * CheckDestructuring and callers should use the default value. */ -static bool -CheckDestructuring(JSContext *cx, BindData *data, ParseNode *left, Parser *parser, - bool toplevel = true) +template <> +bool +Parser::checkDestructuring(BindData *data, + ParseNode *left, bool toplevel) { bool ok; if (left->isKind(PNK_ARRAYCOMP)) { - parser->reportError(left, JSMSG_ARRAY_COMP_LEFTSIDE); + report(ParseError, false, left, JSMSG_ARRAY_COMP_LEFTSIDE); return false; } - Rooted blockObj(cx); - blockObj = data && data->binder == BindLet ? data->let.blockObj.get() : NULL; + Rooted blockObj(context); + blockObj = data && data->binder == bindLet ? data->let.blockObj.get() : NULL; uint32_t blockCountBefore = blockObj ? blockObj->slotCount() : 0; if (left->isKind(PNK_ARRAY)) { @@ -2509,16 +2686,16 @@ CheckDestructuring(JSContext *cx, BindData *data, ParseNode *left, Parser *parse /* Nullary comma is an elision; binary comma is an expression.*/ if (!pn->isArrayHole()) { if (pn->isKind(PNK_ARRAY) || pn->isKind(PNK_OBJECT)) { - ok = CheckDestructuring(cx, data, pn, parser, false); + ok = checkDestructuring(data, pn, false); } else { if (data) { if (!pn->isKind(PNK_NAME)) { - parser->reportError(pn, JSMSG_NO_VARIABLE_NAME); + report(ParseError, false, pn, JSMSG_NO_VARIABLE_NAME); return false; } - ok = BindDestructuringVar(cx, data, pn, parser); + ok = bindDestructuringVar(data, pn); } else { - ok = BindDestructuringLHS(cx, pn, parser); + ok = bindDestructuringLHS(pn); } } if (!ok) @@ -2532,13 +2709,13 @@ CheckDestructuring(JSContext *cx, BindData *data, ParseNode *left, Parser *parse ParseNode *pn = pair->pn_right; if (pn->isKind(PNK_ARRAY) || pn->isKind(PNK_OBJECT)) { - ok = CheckDestructuring(cx, data, pn, parser, false); + ok = checkDestructuring(data, pn, false); } else if (data) { if (!pn->isKind(PNK_NAME)) { - parser->reportError(pn, JSMSG_NO_VARIABLE_NAME); + report(ParseError, false, pn, JSMSG_NO_VARIABLE_NAME); return false; } - ok = BindDestructuringVar(cx, data, pn, parser); + ok = bindDestructuringVar(data, pn); } else { /* * If right and left point to the same node, then this is @@ -2547,9 +2724,9 @@ CheckDestructuring(JSContext *cx, BindData *data, ParseNode *left, Parser *parse * officially linked to its def or registered in lexdeps. Do * that now. */ - if (pair->pn_right == pair->pn_left && !NoteNameUse(pn, parser)) + if (pair->pn_right == pair->pn_left && !noteNameUse(pn)) return false; - ok = BindDestructuringLHS(cx, pn, parser); + ok = bindDestructuringLHS(pn); } if (!ok) return false; @@ -2581,8 +2758,8 @@ CheckDestructuring(JSContext *cx, BindData *data, ParseNode *left, Parser *parse */ if (toplevel && blockObj && blockCountBefore == blockObj->slotCount()) { bool redeclared; - RootedId id(cx, INT_TO_JSID(blockCountBefore)); - if (!StaticBlockObject::addVar(cx, blockObj, id, blockCountBefore, &redeclared)) + RootedId id(context, INT_TO_JSID(blockCountBefore)); + if (!StaticBlockObject::addVar(context, blockObj, id, blockCountBefore, &redeclared)) return false; JS_ASSERT(!redeclared); JS_ASSERT(blockObj->slotCount() == blockCountBefore + 1); @@ -2591,36 +2768,50 @@ CheckDestructuring(JSContext *cx, BindData *data, ParseNode *left, Parser *parse return true; } -ParseNode * -Parser::destructuringExpr(BindData *data, TokenKind tt) +template <> +bool +Parser::checkDestructuring(BindData *data, + Node left, bool toplevel) +{ + setUnknownResult(); + return false; +} + +template +typename ParseHandler::Node +Parser::destructuringExpr(BindData *data, TokenKind tt) { JS_ASSERT(tokenStream.isCurrentTokenType(tt)); pc->inDeclDestructuring = true; - ParseNode *pn = primaryExpr(tt); + Node pn = primaryExpr(tt); pc->inDeclDestructuring = false; if (!pn) - return NULL; - if (!CheckDestructuring(context, data, pn, this)) - return NULL; + return null(); + if (!checkDestructuring(data, pn)) + return null(); return pn; } #endif /* JS_HAS_DESTRUCTURING */ -ParseNode * -Parser::returnOrYield(bool useAssignExpr) +template +typename ParseHandler::Node +Parser::returnOrYield(bool useAssignExpr) { TokenKind tt = tokenStream.currentToken().type; if (!pc->sc->isFunctionBox()) { - reportError(NULL, JSMSG_BAD_RETURN_OR_YIELD, - (tt == TOK_RETURN) ? js_return_str : js_yield_str); - return NULL; + report(ParseError, false, null(), JSMSG_BAD_RETURN_OR_YIELD, + (tt == TOK_RETURN) ? js_return_str : js_yield_str); + return null(); } - ParseNode *pn = UnaryNode::create((tt == TOK_RETURN) ? PNK_RETURN : PNK_YIELD, this); + ParseNodeKind kind = (tt == TOK_RETURN) ? PNK_RETURN : PNK_YIELD; + JSOp op = (tt == TOK_RETURN) ? JSOP_RETURN : JSOP_YIELD; + + Node pn = handler.newUnary(kind, op); if (!pn) - return NULL; + return null(); #if JS_HAS_GENERATORS if (tt == TOK_YIELD) { @@ -2640,7 +2831,7 @@ Parser::returnOrYield(bool useAssignExpr) /* This is ugly, but we don't want to require a semicolon. */ TokenKind tt2 = tokenStream.peekTokenSameLine(TSF_OPERAND); if (tt2 == TOK_ERROR) - return NULL; + return null(); if (tt2 != TOK_EOF && tt2 != TOK_EOL && tt2 != TOK_SEMI && tt2 != TOK_RC #if JS_HAS_GENERATORS @@ -2650,15 +2841,14 @@ Parser::returnOrYield(bool useAssignExpr) #endif ) { - ParseNode *pn2 = useAssignExpr ? assignExpr() : expr(); + Node pn2 = useAssignExpr ? assignExpr() : expr(); if (!pn2) - return NULL; + return null(); #if JS_HAS_GENERATORS if (tt == TOK_RETURN) #endif pc->funHasReturnExpr = true; - pn->pn_pos.end = pn2->pn_pos.end; - pn->pn_kid = pn2; + handler.setUnaryKid(pn, pn2); } else { #if JS_HAS_GENERATORS if (tt == TOK_RETURN) @@ -2668,35 +2858,33 @@ Parser::returnOrYield(bool useAssignExpr) if (pc->funHasReturnExpr && pc->sc->asFunctionBox()->isGenerator()) { /* As in Python (see PEP-255), disallow return v; in generators. */ - ReportBadReturn(context, this, pn, &Parser::reportError, JSMSG_BAD_GENERATOR_RETURN, + reportBadReturn(pn, ParseError, JSMSG_BAD_GENERATOR_RETURN, JSMSG_BAD_ANON_GENERATOR_RETURN); - return NULL; + return null(); } if (context->hasStrictOption() && pc->funHasReturnExpr && pc->funHasReturnVoid && - !ReportBadReturn(context, this, pn, &Parser::reportStrictWarning, + !reportBadReturn(pn, ParseStrictWarning, JSMSG_NO_RETURN_VALUE, JSMSG_ANON_NO_RETURN_VALUE)) { - return NULL; + return null(); } return pn; } -static ParseNode * -PushLexicalScope(JSContext *cx, Parser *parser, HandleStaticBlockObject blockObj, StmtInfoPC *stmt) +template <> +ParseNode * +Parser::pushLexicalScope(HandleStaticBlockObject blockObj, StmtInfoPC *stmt) { JS_ASSERT(blockObj); - - ParseNode *pn = LexicalScopeNode::create(PNK_LEXICALSCOPE, parser); + ParseNode *pn = LexicalScopeNode::create(PNK_LEXICALSCOPE, &handler); if (!pn) - return NULL; + return null(); - ObjectBox *blockbox = parser->newObjectBox(blockObj); + ObjectBox *blockbox = newObjectBox(blockObj); if (!blockbox) - return NULL; - - ParseContext *pc = parser->pc; + return null(); PushStatementPC(pc, stmt, STMT_BLOCK); blockObj->initPrevBlockChainFromParser(pc->blockChain); @@ -2707,19 +2895,28 @@ PushLexicalScope(JSContext *cx, Parser *parser, HandleStaticBlockObject blockObj pn->pn_cookie.makeFree(); pn->pn_dflags = 0; if (!GenerateBlockId(pc, stmt->blockid)) - return NULL; + return null(); pn->pn_blockid = stmt->blockid; return pn; } -static ParseNode * -PushLexicalScope(JSContext *cx, Parser *parser, StmtInfoPC *stmt) +template <> +SyntaxParseHandler::Node +Parser::pushLexicalScope(HandleStaticBlockObject blockObj, StmtInfoPC *stmt) { - RootedStaticBlockObject blockObj(cx, StaticBlockObject::create(cx)); - if (!blockObj) - return NULL; + setUnknownResult(); + return SyntaxParseHandler::NodeFailure; +} - return PushLexicalScope(cx, parser, blockObj, stmt); +template +typename ParseHandler::Node +Parser::pushLexicalScope(StmtInfoPC *stmt) +{ + RootedStaticBlockObject blockObj(context, StaticBlockObject::create(context)); + if (!blockObj) + return null(); + + return pushLexicalScope(blockObj, stmt); } #if JS_HAS_BLOCK_SCOPE @@ -2730,9 +2927,9 @@ struct AddLetDecl AddLetDecl(uint32_t blockid) : blockid(blockid) {} - bool operator()(JSContext *cx, ParseContext *pc, HandleStaticBlockObject blockObj, - const Shape &shape, JSAtom *) - { + bool operator()(JSContext *cx, ParseContext *pc, + HandleStaticBlockObject blockObj, const Shape &shape, JSAtom *) + { ParseNode *def = (ParseNode *) blockObj->getSlot(shape.slot()).toPrivate(); def->pn_blockid = blockid; RootedPropertyName name(cx, def->name()); @@ -2740,59 +2937,69 @@ struct AddLetDecl } }; -static ParseNode * -PushLetScope(JSContext *cx, Parser *parser, HandleStaticBlockObject blockObj, StmtInfoPC *stmt) +template <> +ParseNode * +Parser::pushLetScope(HandleStaticBlockObject blockObj, StmtInfoPC *stmt) { JS_ASSERT(blockObj); - ParseNode *pn = PushLexicalScope(cx, parser, blockObj, stmt); + ParseNode *pn = pushLexicalScope(blockObj, stmt); if (!pn) - return NULL; + return null(); /* Tell codegen to emit JSOP_ENTERLETx (not JSOP_ENTERBLOCK). */ pn->pn_dflags |= PND_LET; /* Populate the new scope with decls found in the head with updated blockid. */ - if (!ForEachLetDef(cx, parser->pc, blockObj, AddLetDecl(stmt->blockid))) - return NULL; + if (!ForEachLetDef(context, pc, blockObj, AddLetDecl(stmt->blockid))) + return null(); return pn; } +template <> +SyntaxParseHandler::Node +Parser::pushLetScope(HandleStaticBlockObject blockObj, StmtInfoPC *stmt) +{ + setUnknownResult(); + return SyntaxParseHandler::NodeFailure; +} + /* * Parse a let block statement or let expression (determined by 'letContext'). * In both cases, bindings are not hoisted to the top of the enclosing block * and thus must be carefully injected between variables() and the let body. */ -ParseNode * -Parser::letBlock(LetContext letContext) +template +typename ParseHandler::Node +Parser::letBlock(LetContext letContext) { JS_ASSERT(tokenStream.currentToken().type == TOK_LET); - ParseNode *pnlet = BinaryNode::create(PNK_LET, this); - if (!pnlet) - return NULL; - RootedStaticBlockObject blockObj(context, StaticBlockObject::create(context)); if (!blockObj) - return NULL; + return null(); + + TokenPtr begin = tokenStream.currentToken().pos.begin; MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_LET); - ParseNode *vars = variables(PNK_LET, blockObj, DontHoistVars); + Node vars = variables(PNK_LET, blockObj, DontHoistVars); if (!vars) - return NULL; + return null(); MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_LET); StmtInfoPC stmtInfo(context); - ParseNode *block = PushLetScope(context, this, blockObj, &stmtInfo); + Node block = pushLetScope(blockObj, &stmtInfo); if (!block) - return NULL; + return null(); - pnlet->pn_left = vars; - pnlet->pn_right = block; + Node pnlet = handler.newBinary(PNK_LET, vars, block); + if (!pnlet) + return null(); + handler.setBeginPosition(pnlet, begin); - ParseNode *ret; + Node ret; if (letContext == LetStatement && !tokenStream.matchToken(TOK_LC, TSF_OPERAND)) { /* * Strict mode eliminates a grammar ambiguity with unparenthesized @@ -2802,20 +3009,18 @@ Parser::letBlock(LetContext letContext) * * See bug 569464. */ - if (!reportStrictModeError(pnlet, JSMSG_STRICT_CODE_LET_EXPR_STMT)) - return NULL; + if (!report(ParseStrictError, pc->sc->strict, pnlet, + JSMSG_STRICT_CODE_LET_EXPR_STMT)) + { + return null(); + } /* * If this is really an expression in let statement guise, then we * need to wrap the TOK_LET node in a TOK_SEMI node so that we pop * the return value of the expression. */ - ParseNode *semi = UnaryNode::create(PNK_SEMI, this); - if (!semi) - return NULL; - - semi->pn_kid = pnlet; - semi->pn_pos = pnlet->pn_pos; + Node semi = handler.newUnary(PNK_SEMI, pnlet); letContext = LetExpresion; ret = semi; @@ -2823,22 +3028,25 @@ Parser::letBlock(LetContext letContext) ret = pnlet; } + Node expr; if (letContext == LetStatement) { - JS_ASSERT(block->getOp() == JSOP_LEAVEBLOCK); - block->pn_expr = statements(); - if (!block->pn_expr) - return NULL; + expr = statements(); + if (!expr) + return null(); MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_LET); } else { JS_ASSERT(letContext == LetExpresion); - block->setOp(JSOP_LEAVEBLOCKEXPR); - block->pn_expr = assignExpr(); - if (!block->pn_expr) - return NULL; + expr = assignExpr(); + if (!expr) + return null(); } + handler.setLeaveBlockResult(block, expr, letContext != LetStatement); - ret->pn_pos.begin = pnlet->pn_pos.begin = pnlet->pn_left->pn_pos.begin; - ret->pn_pos.end = pnlet->pn_pos.end = pnlet->pn_right->pn_pos.end; + handler.setBeginPosition(ret, vars); + handler.setEndPosition(ret, expr); + + handler.setBeginPosition(pnlet, vars); + handler.setEndPosition(pnlet, expr); PopStatementPC(context, pc); return ret; @@ -2846,161 +3054,162 @@ Parser::letBlock(LetContext letContext) #endif /* JS_HAS_BLOCK_SCOPE */ +template static bool -PushBlocklikeStatement(StmtInfoPC *stmt, StmtType type, ParseContext *pc) +PushBlocklikeStatement(StmtInfoPC *stmt, StmtType type, ParseContext *pc) { PushStatementPC(pc, stmt, type); return GenerateBlockId(pc, stmt->blockid); } -static ParseNode * -NewBindingNode(JSAtom *atom, Parser *parser, VarContext varContext = HoistVars) +template <> +ParseNode * +Parser::newBindingNode(PropertyName *name, VarContext varContext) { - ParseContext *pc = parser->pc; - /* * If this name is being injected into an existing block/function, see if * it has already been declared or if it resolves an outstanding lexdep. * Otherwise, this is a let block/expr that introduces a new scope and thus * shadows existing decls and doesn't resolve existing lexdeps. Duplicate - * names are caught by BindLet. + * names are caught by bindLet. */ if (varContext == HoistVars) { - if (AtomDefnPtr p = pc->lexdeps->lookup(atom)) { + if (AtomDefnPtr p = pc->lexdeps->lookup(name)) { ParseNode *lexdep = p.value(); JS_ASSERT(lexdep->isPlaceholder()); if (lexdep->pn_blockid >= pc->blockid()) { lexdep->pn_blockid = pc->blockid(); pc->lexdeps->remove(p); - lexdep->pn_pos = parser->tokenStream.currentToken().pos; + lexdep->pn_pos = tokenStream.currentToken().pos; return lexdep; } } } /* Make a new node for this declarator name (or destructuring pattern). */ - JS_ASSERT(parser->tokenStream.currentToken().type == TOK_NAME); - return NameNode::create(PNK_NAME, atom, parser, parser->pc); + JS_ASSERT(tokenStream.currentToken().type == TOK_NAME); + return handler.newName(name, pc); } -ParseNode * -Parser::switchStatement() +template <> +SyntaxParseHandler::Node +Parser::newBindingNode(PropertyName *name, VarContext varContext) +{ + setUnknownResult(); + return SyntaxParseHandler::NodeFailure; +} + +template +typename ParseHandler::Node +Parser::switchStatement() { JS_ASSERT(tokenStream.currentToken().type == TOK_SWITCH); - ParseNode *pn = BinaryNode::create(PNK_SWITCH, this); - if (!pn) - return NULL; + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_SWITCH); - /* pn1 points to the switch's discriminant. */ - ParseNode *pn1 = parenExpr(); - if (!pn1) - return NULL; + Node discriminant = parenExpr(); + if (!discriminant) + return null(); MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_SWITCH); MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_SWITCH); - /* - * NB: we must push stmtInfo before calling GenerateBlockIdForStmtNode - * because that function states pc->topStmt->blockid. - */ StmtInfoPC stmtInfo(context); PushStatementPC(pc, &stmtInfo, STMT_SWITCH); - /* pn2 is a list of case nodes. The default case has pn_left == NULL */ - ParseNode *pn2 = ListNode::create(PNK_STATEMENTLIST, this); - if (!pn2) - return NULL; - pn2->makeEmpty(); - if (!GenerateBlockIdForStmtNode(pn2, pc)) - return NULL; - ParseNode *saveBlock = pc->blockNode; - pc->blockNode = pn2; + if (!GenerateBlockId(pc, pc->topStmt->blockid)) + return null(); + + /* The default case has pn_left == NULL */ + Node caseList = handler.newList(PNK_STATEMENTLIST); + if (!caseList) + return null(); + handler.setBlockId(caseList, pc->blockid()); + + Node saveBlock = pc->blockNode; + pc->blockNode = caseList; bool seenDefault = false; TokenKind tt; while ((tt = tokenStream.getToken()) != TOK_RC) { - ParseNode *pn3; + Node casepn; switch (tt) { case TOK_DEFAULT: if (seenDefault) { - reportError(NULL, JSMSG_TOO_MANY_DEFAULTS); - return NULL; + report(ParseError, false, null(), JSMSG_TOO_MANY_DEFAULTS); + return null(); } seenDefault = true; - pn3 = BinaryNode::create(PNK_DEFAULT, this); - if (!pn3) - return NULL; + casepn = handler.newBinary(PNK_DEFAULT); + if (!casepn) + return null(); break; case TOK_CASE: { - pn3 = BinaryNode::create(PNK_CASE, this); - if (!pn3) - return NULL; - pn3->pn_left = expr(); - if (!pn3->pn_left) - return NULL; + Node left = expr(); + if (!left) + return null(); + casepn = handler.newBinary(PNK_CASE, left); + if (!casepn) + return null(); break; } case TOK_ERROR: - return NULL; + return null(); default: - reportError(NULL, JSMSG_BAD_SWITCH); - return NULL; + report(ParseError, false, null(), JSMSG_BAD_SWITCH); + return null(); } - pn2->append(pn3); - if (pn2->pn_count == JS_BIT(16)) { - reportError(NULL, JSMSG_TOO_MANY_CASES); - return NULL; - } + handler.addList(caseList, casepn); MUST_MATCH_TOKEN(TOK_COLON, JSMSG_COLON_AFTER_CASE); - ParseNode *pn4 = ListNode::create(PNK_STATEMENTLIST, this); - if (!pn4) - return NULL; - pn4->makeEmpty(); + Node body = handler.newList(PNK_STATEMENTLIST); + if (!body) + return null(); + handler.setBlockId(body, pc->blockid()); + while ((tt = tokenStream.peekToken(TSF_OPERAND)) != TOK_RC && tt != TOK_CASE && tt != TOK_DEFAULT) { if (tt == TOK_ERROR) - return NULL; - ParseNode *pn5 = statement(); - if (!pn5) - return NULL; - pn4->pn_pos.end = pn5->pn_pos.end; - pn4->append(pn5); + return null(); + Node stmt = statement(); + if (!stmt) + return null(); + handler.addList(body, stmt); } - /* Fix the PN_LIST so it doesn't begin at the TOK_COLON. */ - if (pn4->pn_head) - pn4->pn_pos.begin = pn4->pn_head->pn_pos.begin; - pn3->pn_pos.end = pn4->pn_pos.end; - pn3->pn_right = pn4; + handler.setBinaryRHS(casepn, body); } /* * Handle the case where there was a let declaration in any case in * the switch body, but not within an inner block. If it replaced - * pc->blockNode with a new block node then we must refresh pn2 and + * pc->blockNode with a new block node then we must refresh caseList and * then restore pc->blockNode. */ - if (pc->blockNode != pn2) - pn2 = pc->blockNode; + if (pc->blockNode != caseList) + caseList = pc->blockNode; pc->blockNode = saveBlock; + PopStatementPC(context, pc); - pn->pn_pos.end = pn2->pn_pos.end = tokenStream.currentToken().pos.end; - pn->pn_left = pn1; - pn->pn_right = pn2; + Node pn = handler.newBinary(PNK_SWITCH, discriminant, caseList); + if (!pn) + return null(); + + handler.setEndPosition(pn, tokenStream.currentToken().pos.end); + handler.setEndPosition(caseList, tokenStream.currentToken().pos.end); return pn; } +template bool -Parser::matchInOrOf(bool *isForOfp) +Parser::matchInOrOf(bool *isForOfp) { if (tokenStream.matchToken(TOK_IN)) { *isForOfp = false; @@ -3016,8 +3225,10 @@ Parser::matchInOrOf(bool *isForOfp) return false; } -static bool -IsValidForStatementLHS(ParseNode *pn1, JSVersion version, bool forDecl, bool forEach, bool forOf) +template <> +bool +Parser::isValidForStatementLHS(ParseNode *pn1, JSVersion version, + bool forDecl, bool forEach, bool forOf) { if (forDecl) { if (pn1->pn_count > 1) @@ -3063,21 +3274,23 @@ IsValidForStatementLHS(ParseNode *pn1, JSVersion version, bool forDecl, bool for } } +template <> ParseNode * -Parser::forStatement() +Parser::forStatement() { JS_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR)); - /* A FOR node is binary, left is loop control and right is the body. */ - ParseNode *pn = BinaryNode::create(PNK_FOR, this); - if (!pn) - return NULL; - StmtInfoPC forStmt(context); PushStatementPC(pc, &forStmt, STMT_FOR_LOOP); + /* A FOR node is binary, left is loop control and right is the body. */ + ParseNode *pn = BinaryNode::create(PNK_FOR, &handler); + if (!pn) + return null(); + pn->setOp(JSOP_ITER); pn->pn_iflags = 0; + if (allowsForEachIn() && tokenStream.matchToken(TOK_NAME)) { if (tokenStream.currentToken().name() == context->names().each) pn->pn_iflags = JSITER_FOREACH; @@ -3104,8 +3317,8 @@ Parser::forStatement() TokenKind tt = tokenStream.peekToken(TSF_OPERAND); if (tt == TOK_SEMI) { if (pn->pn_iflags & JSITER_FOREACH) { - reportError(pn, JSMSG_BAD_FOR_EACH_LOOP); - return NULL; + report(ParseError, false, null(), JSMSG_BAD_FOR_EACH_LOOP); + return null(); } pn1 = NULL; @@ -3138,7 +3351,7 @@ Parser::forStatement() forDecl = true; blockObj = StaticBlockObject::create(context); if (!blockObj) - return NULL; + return null(); pn1 = variables(PNK_LET, blockObj, DontHoistVars); } } @@ -3148,7 +3361,7 @@ Parser::forStatement() } pc->parsingForInit = false; if (!pn1) - return NULL; + return null(); } } @@ -3184,16 +3397,16 @@ Parser::forStatement() /* Set pn_iflags and rule out invalid combinations. */ if (forOf && pn->pn_iflags != 0) { JS_ASSERT(pn->pn_iflags == JSITER_FOREACH); - reportError(NULL, JSMSG_BAD_FOR_EACH_LOOP); - return NULL; + report(ParseError, false, null(), JSMSG_BAD_FOR_EACH_LOOP); + return null(); } pn->pn_iflags |= (forOf ? JSITER_FOR_OF : JSITER_ENUMERATE); /* Check that the left side of the 'in' or 'of' is valid. */ bool forEach = bool(pn->pn_iflags & JSITER_FOREACH); - if (!IsValidForStatementLHS(pn1, versionNumber(), forDecl, forEach, forOf)) { - reportError(pn1, JSMSG_BAD_FOR_LEFTSIDE); - return NULL; + if (!isValidForStatementLHS(pn1, versionNumber(), forDecl, forEach, forOf)) { + report(ParseError, false, pn1, JSMSG_BAD_FOR_LEFTSIDE); + return null(); } /* @@ -3223,14 +3436,14 @@ Parser::forStatement() */ #if JS_HAS_BLOCK_SCOPE if (blockObj) { - reportError(pn2, JSMSG_INVALID_FOR_IN_INIT); - return NULL; + report(ParseError, false, pn2, JSMSG_INVALID_FOR_IN_INIT); + return null(); } #endif /* JS_HAS_BLOCK_SCOPE */ - ParseNode *pnseq = ListNode::create(PNK_SEQ, this); + ParseNode *pnseq = handler.newList(PNK_SEQ, pn1); if (!pnseq) - return NULL; + return null(); /* * All of 'var x = i' is hoisted above 'for (x in o)', @@ -3242,7 +3455,6 @@ Parser::forStatement() */ pn1->pn_xflags &= ~PNX_FORINVAR; pn1->pn_xflags |= PNX_POPVAR; - pnseq->initList(pn1); pn1 = NULL; #if JS_HAS_DESTRUCTURING @@ -3263,12 +3475,12 @@ Parser::forStatement() pn1 = NULL; if (!setAssignmentLhsOps(pn2, JSOP_NOP)) - return NULL; + return null(); } pn3 = expr(); if (!pn3) - return NULL; + return null(); if (blockObj) { /* @@ -3277,9 +3489,9 @@ Parser::forStatement() * created by PushLetScope around the for's initializer. This also * serves to indicate the let-decl to the emitter. */ - ParseNode *block = PushLetScope(context, this, blockObj, &letStmt); + ParseNode *block = pushLetScope(blockObj, &letStmt); if (!block) - return NULL; + return null(); letStmt.isForLetBlock = true; block->pn_expr = pn1; block->pn_pos = pn1->pn_pos; @@ -3291,15 +3503,15 @@ Parser::forStatement() * pn2 is part of a declaration. Make a copy that can be passed to * EmitAssignment. Take care to do this after PushLetScope. */ - pn2 = CloneLeftHandSide(pn2, this); + pn2 = cloneLeftHandSide(pn2); if (!pn2) - return NULL; + return null(); } switch (pn2->getKind()) { case PNK_NAME: /* Beware 'for (arguments in ...)' with or without a 'var'. */ - NoteLValue(pn2); + handler.noteLValue(pn2); break; #if JS_HAS_DESTRUCTURING @@ -3324,23 +3536,23 @@ Parser::forStatement() default:; } - forHead = TernaryNode::create(PNK_FORIN, this); + forHead = TernaryNode::create(PNK_FORIN, &handler); if (!forHead) - return NULL; + return null(); } else { if (blockObj) { /* * Desugar 'for (let A; B; C) D' into 'let (A) { for (; B; C) D }' * to induce the correct scoping for A. */ - ParseNode *block = PushLetScope(context, this, blockObj, &letStmt); + ParseNode *block = pushLetScope(blockObj, &letStmt); if (!block) - return NULL; + return null(); letStmt.isForLetBlock = true; - ParseNode *let = new_(PNK_LET, JSOP_NOP, pos, pn1, block); + ParseNode *let = handler.newBinary(PNK_LET, pn1, block); if (!let) - return NULL; + return null(); pn1 = NULL; block->pn_expr = pn; @@ -3348,8 +3560,8 @@ Parser::forStatement() } if (pn->pn_iflags & JSITER_FOREACH) { - reportError(pn, JSMSG_BAD_FOR_EACH_LOOP); - return NULL; + report(ParseError, false, pn, JSMSG_BAD_FOR_EACH_LOOP); + return null(); } pn->setOp(JSOP_NOP); @@ -3360,7 +3572,7 @@ Parser::forStatement() } else { pn2 = expr(); if (!pn2) - return NULL; + return null(); } /* Parse the update expression or null into pn3. */ @@ -3370,12 +3582,12 @@ Parser::forStatement() } else { pn3 = expr(); if (!pn3) - return NULL; + return null(); } - forHead = TernaryNode::create(PNK_FORHEAD, this); + forHead = TernaryNode::create(PNK_FORHEAD, &handler); if (!forHead) - return NULL; + return null(); } forHead->pn_pos = pos; @@ -3392,7 +3604,7 @@ Parser::forStatement() /* Parse the loop body. */ ParseNode *body = statement(); if (!body) - return NULL; + return null(); /* Record the absolute line number for source note emission. */ pn->pn_pos.end = body->pn_pos.end; @@ -3411,10 +3623,21 @@ Parser::forStatement() return forParent ? forParent : pn; } -ParseNode * -Parser::tryStatement() +template <> +SyntaxParseHandler::Node +Parser::forStatement() +{ + // XXX bug 835587 for statement needs a rewrite for syntax parsing. + setUnknownResult(); + return SyntaxParseHandler::NodeFailure; +} + +template +typename ParseHandler::Node +Parser::tryStatement() { JS_ASSERT(tokenStream.isCurrentTokenType(TOK_TRY)); + TokenPtr begin = tokenStream.currentToken().pos.begin; /* * try nodes are ternary. @@ -3433,48 +3656,42 @@ Parser::tryStatement() * * finally nodes are TOK_LC statement lists. */ - ParseNode *pn = TernaryNode::create(PNK_TRY, this); - if (!pn) - return NULL; - pn->setOp(JSOP_NOP); MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_TRY); StmtInfoPC stmtInfo(context); if (!PushBlocklikeStatement(&stmtInfo, STMT_TRY, pc)) - return NULL; - pn->pn_kid1 = statements(); - if (!pn->pn_kid1) - return NULL; + return null(); + Node innerBlock = statements(); + if (!innerBlock) + return null(); MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_TRY); PopStatementPC(context, pc); - ParseNode *lastCatch; - ParseNode *catchList = NULL; + bool hasUnconditionalCatch = false; + Node catchList = null(); TokenKind tt = tokenStream.getToken(); if (tt == TOK_CATCH) { - catchList = ListNode::create(PNK_CATCHLIST, this); + catchList = handler.newList(PNK_CATCH); if (!catchList) - return NULL; - catchList->makeEmpty(); - lastCatch = NULL; + return null(); do { - ParseNode *pnblock; - BindData data(context); + Node pnblock; + BindData data(context); /* Check for another catch after unconditional catch. */ - if (lastCatch && !lastCatch->pn_kid2) { - reportError(NULL, JSMSG_CATCH_AFTER_GENERAL); - return NULL; + if (hasUnconditionalCatch) { + report(ParseError, false, null(), JSMSG_CATCH_AFTER_GENERAL); + return null(); } /* * Create a lexical scope node around the whole catch clause, * including the head. */ - pnblock = PushLexicalScope(context, this, &stmtInfo); + pnblock = pushLexicalScope(&stmtInfo); if (!pnblock) - return NULL; + return null(); stmtInfo.type = STMT_CATCH; /* @@ -3484,10 +3701,6 @@ Parser::tryStatement() * where lhs is a name or a destructuring left-hand side. * (the latter is legal only #ifdef JS_HAS_CATCH_GUARD) */ - ParseNode *pn2 = TernaryNode::create(PNK_CATCH, this); - if (!pn2) - return NULL; - pnblock->pn_expr = pn2; MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_CATCH); /* @@ -3496,38 +3709,38 @@ Parser::tryStatement() * an intentional change that anticipates ECMA Ed. 4. */ data.initLet(HoistVars, *pc->blockChain, JSMSG_TOO_MANY_CATCH_VARS); - JS_ASSERT(data.let.blockObj && data.let.blockObj == pnblock->pn_objbox->object); + JS_ASSERT(data.let.blockObj); tt = tokenStream.getToken(); - ParseNode *pn3; + Node catchName; switch (tt) { #if JS_HAS_DESTRUCTURING case TOK_LB: case TOK_LC: - pn3 = destructuringExpr(&data, tt); - if (!pn3) - return NULL; + catchName = destructuringExpr(&data, tt); + if (!catchName) + return null(); break; #endif case TOK_NAME: { RootedPropertyName label(context, tokenStream.currentToken().name()); - pn3 = NewBindingNode(label, this); - if (!pn3) - return NULL; - data.pn = pn3; + catchName = newBindingNode(label); + if (!catchName) + return null(); + data.pn = catchName; if (!data.binder(context, &data, label, this)) - return NULL; + return null(); break; } default: - reportError(NULL, JSMSG_CATCH_IDENTIFIER); - return NULL; + report(ParseError, false, null(), JSMSG_CATCH_IDENTIFIER); + return null(); } - pn2->pn_kid1 = pn3; + Node catchGuard = null(); #if JS_HAS_CATCH_GUARD /* * We use 'catch (x if x === 5)' (not 'catch (x : x === 5)') @@ -3535,53 +3748,66 @@ Parser::tryStatement() * catchguard syntax. */ if (tokenStream.matchToken(TOK_IF)) { - pn2->pn_kid2 = expr(); - if (!pn2->pn_kid2) - return NULL; + catchGuard = expr(); + if (!catchGuard) + return null(); } #endif MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_CATCH); MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_CATCH); - pn2->pn_kid3 = statements(); - if (!pn2->pn_kid3) - return NULL; + Node catchBody = statements(); + if (!catchBody) + return null(); MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_CATCH); PopStatementPC(context, pc); - pnblock->pn_pos.end = pn2->pn_pos.end = tokenStream.currentToken().pos.end; + if (!catchGuard) + hasUnconditionalCatch = true; + + if (!handler.addCatchBlock(catchList, pnblock, catchName, catchGuard, catchBody)) + return null(); + handler.setEndPosition(catchList, tokenStream.currentToken().pos.end); + handler.setEndPosition(pnblock, tokenStream.currentToken().pos.end); - catchList->append(pnblock); - lastCatch = pn2; tt = tokenStream.getToken(TSF_OPERAND); } while (tt == TOK_CATCH); } - pn->pn_kid2 = catchList; + + Node finallyBlock = null(); if (tt == TOK_FINALLY) { MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_FINALLY); if (!PushBlocklikeStatement(&stmtInfo, STMT_FINALLY, pc)) - return NULL; - pn->pn_kid3 = statements(); - if (!pn->pn_kid3) - return NULL; + return null(); + finallyBlock = statements(); + if (!finallyBlock) + return null(); MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_FINALLY); PopStatementPC(context, pc); } else { tokenStream.ungetToken(); } - if (!catchList && !pn->pn_kid3) { - reportError(NULL, JSMSG_CATCH_OR_FINALLY); - return NULL; + if (!catchList && !finallyBlock) { + report(ParseError, false, null(), JSMSG_CATCH_OR_FINALLY); + return null(); } - pn->pn_pos.end = (pn->pn_kid3 ? pn->pn_kid3 : catchList)->pn_pos.end; + + Node pn = handler.newTernary(PNK_TRY, innerBlock, catchList, finallyBlock); + if (!pn) + return null(); + + handler.setBeginPosition(pn, begin); + handler.setEndPosition(pn, finallyBlock ? finallyBlock : catchList); return pn; } -ParseNode * -Parser::withStatement() +template +typename ParseHandler::Node +Parser::withStatement() { JS_ASSERT(tokenStream.isCurrentTokenType(TOK_WITH)); + TokenPtr begin = tokenStream.currentToken().pos.begin; // In most cases, we want the constructs forbidden in strict mode code to be // a subset of those that JSOPTION_STRICT warns about, and we should use @@ -3589,32 +3815,25 @@ Parser::withStatement() // construct that is forbidden in strict mode code, but doesn't even merit a // warning under JSOPTION_STRICT. See // https://bugzilla.mozilla.org/show_bug.cgi?id=514576#c1. - if (pc->sc->strict && !reportStrictModeError(NULL, JSMSG_STRICT_CODE_WITH)) - return NULL; + if (pc->sc->strict && !report(ParseStrictError, true, null(), JSMSG_STRICT_CODE_WITH)) + return null(); - ParseNode *pn = BinaryNode::create(PNK_WITH, this); - if (!pn) - return NULL; MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_WITH); - ParseNode *pn2 = parenExpr(); - if (!pn2) - return NULL; + Node objectExpr = parenExpr(); + if (!objectExpr) + return null(); MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_WITH); - pn->pn_left = pn2; bool oldParsingWith = pc->parsingWith; pc->parsingWith = true; StmtInfoPC stmtInfo(context); PushStatementPC(pc, &stmtInfo, STMT_WITH); - pn2 = statement(); - if (!pn2) - return NULL; + Node innerBlock = statement(); + if (!innerBlock) + return null(); PopStatementPC(context, pc); - pn->pn_pos.end = pn2->pn_pos.end; - pn->pn_right = pn2; - pc->sc->setBindingsAccessedDynamically(); pc->parsingWith = oldParsingWith; @@ -3625,15 +3844,22 @@ Parser::withStatement() for (AtomDefnRange r = pc->lexdeps->all(); !r.empty(); r.popFront()) { Definition *defn = r.front().value(); Definition *lexdep = defn->resolve(); - DeoptimizeUsesWithin(lexdep, pn->pn_pos); + DeoptimizeUsesWithin(lexdep, TokenPos::make(begin, tokenStream.currentToken().pos.begin)); } + Node pn = handler.newBinary(PNK_WITH, objectExpr, innerBlock); + if (!pn) + return null(); + + handler.setBeginPosition(pn, begin); + handler.setEndPosition(pn, innerBlock); return pn; } #if JS_HAS_BLOCK_SCOPE +template <> ParseNode * -Parser::letStatement() +Parser::letStatement() { ParseNode *pn; do { @@ -3641,7 +3867,7 @@ Parser::letStatement() if (tokenStream.peekToken() == TOK_LP) { pn = letBlock(LetStatement); if (!pn) - return NULL; + return null(); JS_ASSERT(pn->isKind(PNK_LET) || pn->isKind(PNK_SEMI)); if (pn->isKind(PNK_LET) && pn->pn_expr->getOp() == JSOP_LEAVEBLOCK) @@ -3665,8 +3891,8 @@ Parser::letStatement() */ StmtInfoPC *stmt = pc->topStmt; if (stmt && (!stmt->maybeScope() || stmt->isForLetBlock)) { - reportError(NULL, JSMSG_LET_DECL_NOT_IN_BLOCK); - return NULL; + report(ParseError, false, null(), JSMSG_LET_DECL_NOT_IN_BLOCK); + return null(); } if (stmt && stmt->isBlockScope) { @@ -3679,7 +3905,7 @@ Parser::letStatement() */ pn = variables(PNK_VAR); if (!pn) - return NULL; + return null(); pn->pn_xflags |= PNX_POPVAR; break; } @@ -3700,11 +3926,11 @@ Parser::letStatement() /* Convert the block statement into a scope statement. */ StaticBlockObject *blockObj = StaticBlockObject::create(context); if (!blockObj) - return NULL; + return null(); ObjectBox *blockbox = newObjectBox(blockObj); if (!blockbox) - return NULL; + return null(); /* * Insert stmt on the pc->topScopeStmt/stmtInfo.downScope linked @@ -3726,9 +3952,9 @@ Parser::letStatement() #endif /* Create a new lexical scope node for these statements. */ - ParseNode *pn1 = LexicalScopeNode::create(PNK_LEXICALSCOPE, this); + ParseNode *pn1 = LexicalScopeNode::create(PNK_LEXICALSCOPE, &handler); if (!pn1) - return NULL; + return null(); pn1->setOp(JSOP_LEAVEBLOCK); pn1->pn_pos = pc->blockNode->pn_pos; @@ -3740,33 +3966,43 @@ Parser::letStatement() pn = variables(PNK_LET, pc->blockChain, HoistVars); if (!pn) - return NULL; + return null(); pn->pn_xflags = PNX_POPVAR; } while (0); /* Check termination of this primitive statement. */ return MatchOrInsertSemicolon(context, &tokenStream) ? pn : NULL; } -#endif -ParseNode * -Parser::expressionStatement() +template <> +SyntaxParseHandler::Node +Parser::letStatement() +{ + setUnknownResult(); + return SyntaxParseHandler::NodeFailure; +} + +#endif // JS_HAS_BLOCK_SCOPE + +template +typename ParseHandler::Node +Parser::expressionStatement() { tokenStream.ungetToken(); - ParseNode *pn2 = expr(); + Node pn2 = expr(); if (!pn2) - return NULL; + return null(); if (tokenStream.peekToken() == TOK_COLON) { - if (!pn2->isKind(PNK_NAME)) { - reportError(NULL, JSMSG_BAD_LABEL); - return NULL; + RootedAtom label(context, handler.isName(pn2)); + if (!label) { + report(ParseError, false, null(), JSMSG_BAD_LABEL); + return null(); } - RootedAtom label(context, pn2->pn_atom); for (StmtInfoPC *stmt = pc->topStmt; stmt; stmt = stmt->down) { if (stmt->type == STMT_LABEL && stmt->label == label) { - reportError(NULL, JSMSG_DUPLICATE_LABEL); - return NULL; + report(ParseError, false, null(), JSMSG_DUPLICATE_LABEL); + return null(); } } ForgetUse(pn2); @@ -3777,34 +4013,30 @@ Parser::expressionStatement() StmtInfoPC stmtInfo(context); PushStatementPC(pc, &stmtInfo, STMT_LABEL); stmtInfo.label = label; - ParseNode *pn = statement(); + Node pn = statement(); if (!pn) - return NULL; + return null(); /* Pop the label, set pn_expr, and return early. */ PopStatementPC(context, pc); - pn2->setKind(PNK_COLON); - pn2->pn_pos.end = pn->pn_pos.end; - pn2->pn_expr = pn; + + handler.morphNameIntoLabel(pn2, pn); return pn2; } - ParseNode *pn = UnaryNode::create(PNK_SEMI, this); - if (!pn) - return NULL; - pn->pn_pos = pn2->pn_pos; - pn->pn_kid = pn2; + Node pn = handler.newUnary(PNK_SEMI, pn2); /* Check termination of this primitive statement. */ - return MatchOrInsertSemicolon(context, &tokenStream) ? pn : NULL; + return MatchOrInsertSemicolon(context, &tokenStream) ? pn : null(); } -ParseNode * -Parser::statement() +template +typename ParseHandler::Node +Parser::statement() { - ParseNode *pn; + Node pn; - JS_CHECK_RECURSION(context, return NULL); + JS_CHECK_RECURSION(context, return null()); switch (tokenStream.getToken(TSF_OPERAND)) { case TOK_FUNCTION: @@ -3812,42 +4044,40 @@ Parser::statement() case TOK_IF: { + TokenPtr begin = tokenStream.currentToken().pos.begin; + /* An IF node has three kids: condition, then, and optional else. */ - pn = TernaryNode::create(PNK_IF, this); - if (!pn) - return NULL; - ParseNode *pn1 = condition(); - if (!pn1) - return NULL; + Node cond = condition(); + if (!cond) + return null(); StmtInfoPC stmtInfo(context); PushStatementPC(pc, &stmtInfo, STMT_IF); - ParseNode *pn2 = statement(); - if (!pn2) - return NULL; + Node thenBranch = statement(); + if (!thenBranch) + return null(); - if (pn2->isKind(PNK_SEMI) && - !pn2->pn_kid && - !reportStrictWarning(NULL, JSMSG_EMPTY_CONSEQUENT)) + if (handler.isEmptySemicolon(thenBranch) && + !report(ParseStrictWarning, false, null(), JSMSG_EMPTY_CONSEQUENT)) { - return NULL; + return null(); } - ParseNode *pn3; + Node elseBranch; if (tokenStream.matchToken(TOK_ELSE, TSF_OPERAND)) { stmtInfo.type = STMT_ELSE; - pn3 = statement(); - if (!pn3) - return NULL; - pn->pn_pos.end = pn3->pn_pos.end; + elseBranch = statement(); + if (!elseBranch) + return null(); } else { - pn3 = NULL; - pn->pn_pos.end = pn2->pn_pos.end; + elseBranch = null(); } + PopStatementPC(context, pc); - pn->pn_kid1 = pn1; - pn->pn_kid2 = pn2; - pn->pn_kid3 = pn3; + pn = handler.newTernary(PNK_IF, cond, thenBranch, elseBranch); + if (!pn) + return null(); + handler.setBeginPosition(pn, begin); return pn; } @@ -3856,42 +4086,42 @@ Parser::statement() case TOK_WHILE: { - pn = BinaryNode::create(PNK_WHILE, this); - if (!pn) - return NULL; + TokenPtr begin = tokenStream.currentToken().pos.begin; StmtInfoPC stmtInfo(context); PushStatementPC(pc, &stmtInfo, STMT_WHILE_LOOP); - ParseNode *pn2 = condition(); - if (!pn2) - return NULL; - pn->pn_left = pn2; - ParseNode *pn3 = statement(); - if (!pn3) - return NULL; + Node cond = condition(); + if (!cond) + return null(); + Node body = statement(); + if (!body) + return null(); PopStatementPC(context, pc); - pn->pn_pos.end = pn3->pn_pos.end; - pn->pn_right = pn3; + pn = handler.newBinary(PNK_WHILE, cond, body); + if (!pn) + return null(); + handler.setBeginPosition(pn, begin); return pn; } case TOK_DO: { - pn = BinaryNode::create(PNK_DOWHILE, this); - if (!pn) - return NULL; + TokenPtr begin = tokenStream.currentToken().pos.begin; StmtInfoPC stmtInfo(context); PushStatementPC(pc, &stmtInfo, STMT_DO_LOOP); - ParseNode *pn2 = statement(); - if (!pn2) - return NULL; - pn->pn_left = pn2; + Node body = statement(); + if (!body) + return null(); MUST_MATCH_TOKEN(TOK_WHILE, JSMSG_WHILE_AFTER_DO); - ParseNode *pn3 = condition(); - if (!pn3) - return NULL; + Node cond = condition(); + if (!cond) + return null(); PopStatementPC(context, pc); - pn->pn_pos.end = pn3->pn_pos.end; - pn->pn_right = pn3; + + pn = handler.newBinary(PNK_DOWHILE, body, cond); + if (!pn) + return null(); + handler.setBeginPosition(pn, begin); + if (versionNumber() != JSVERSION_ECMA_3) { /* * All legacy and extended versions must do automatic semicolon @@ -3912,53 +4142,53 @@ Parser::statement() case TOK_THROW: { - pn = UnaryNode::create(PNK_THROW, this); - if (!pn) - return NULL; + TokenPtr begin = tokenStream.currentToken().pos.begin; /* ECMA-262 Edition 3 says 'throw [no LineTerminator here] Expr'. */ TokenKind tt = tokenStream.peekTokenSameLine(TSF_OPERAND); if (tt == TOK_ERROR) - return NULL; + return null(); if (tt == TOK_EOF || tt == TOK_EOL || tt == TOK_SEMI || tt == TOK_RC) { - reportError(NULL, JSMSG_SYNTAX_ERROR); - return NULL; + report(ParseError, false, null(), JSMSG_SYNTAX_ERROR); + return null(); } - ParseNode *pn2 = expr(); - if (!pn2) - return NULL; - pn->pn_pos.end = pn2->pn_pos.end; - pn->setOp(JSOP_THROW); - pn->pn_kid = pn2; + Node pnexp = expr(); + if (!pnexp) + return null(); + + pn = handler.newUnary(PNK_THROW, pnexp, JSOP_THROW); + if (!pn) + return null(); + handler.setBeginPosition(pn, begin); break; } /* TOK_CATCH and TOK_FINALLY are both handled in the TOK_TRY case */ case TOK_CATCH: - reportError(NULL, JSMSG_CATCH_WITHOUT_TRY); - return NULL; + report(ParseError, false, null(), JSMSG_CATCH_WITHOUT_TRY); + return null(); case TOK_FINALLY: - reportError(NULL, JSMSG_FINALLY_WITHOUT_TRY); - return NULL; + report(ParseError, false, null(), JSMSG_FINALLY_WITHOUT_TRY); + return null(); case TOK_BREAK: { TokenPtr begin = tokenStream.currentToken().pos.begin; RootedPropertyName label(context); if (!MatchLabel(context, &tokenStream, &label)) - return NULL; + return null(); TokenPtr end = tokenStream.currentToken().pos.end; - pn = new_(label.get(), begin, end); + pn = handler.newBreak(label, begin, end); if (!pn) - return NULL; + return null(); StmtInfoPC *stmt = pc->topStmt; if (label) { for (; ; stmt = stmt->down) { if (!stmt) { - reportError(NULL, JSMSG_LABEL_NOT_FOUND); - return NULL; + report(ParseError, false, null(), JSMSG_LABEL_NOT_FOUND); + return null(); } if (stmt->type == STMT_LABEL && stmt->label == label) break; @@ -3966,8 +4196,8 @@ Parser::statement() } else { for (; ; stmt = stmt->down) { if (!stmt) { - reportError(NULL, JSMSG_TOUGH_BREAK); - return NULL; + report(ParseError, false, null(), JSMSG_TOUGH_BREAK); + return null(); } if (stmt->isLoop() || stmt->type == STMT_SWITCH) break; @@ -3981,23 +4211,23 @@ Parser::statement() TokenPtr begin = tokenStream.currentToken().pos.begin; RootedPropertyName label(context); if (!MatchLabel(context, &tokenStream, &label)) - return NULL; + return null(); TokenPtr end = tokenStream.currentToken().pos.begin; - pn = new_(label.get(), begin, end); + pn = handler.newContinue(label, begin, end); if (!pn) - return NULL; + return null(); StmtInfoPC *stmt = pc->topStmt; if (label) { for (StmtInfoPC *stmt2 = NULL; ; stmt = stmt->down) { if (!stmt) { - reportError(NULL, JSMSG_LABEL_NOT_FOUND); - return NULL; + report(ParseError, false, null(), JSMSG_LABEL_NOT_FOUND); + return null(); } if (stmt->type == STMT_LABEL) { if (stmt->label == label) { if (!stmt2 || !stmt2->isLoop()) { - reportError(NULL, JSMSG_BAD_CONTINUE); - return NULL; + report(ParseError, false, null(), JSMSG_BAD_CONTINUE); + return null(); } break; } @@ -4008,8 +4238,8 @@ Parser::statement() } else { for (; ; stmt = stmt->down) { if (!stmt) { - reportError(NULL, JSMSG_BAD_CONTINUE); - return NULL; + report(ParseError, false, null(), JSMSG_BAD_CONTINUE); + return null(); } if (stmt->isLoop()) break; @@ -4024,19 +4254,19 @@ Parser::statement() case TOK_VAR: pn = variables(PNK_VAR); if (!pn) - return NULL; + return null(); /* Tell js_EmitTree to generate a final POP. */ - pn->pn_xflags |= PNX_POPVAR; + handler.setListFlag(pn, PNX_POPVAR); break; case TOK_CONST: pn = variables(PNK_CONST); if (!pn) - return NULL; + return null(); /* Tell js_EmitTree to generate a final POP. */ - pn->pn_xflags |= PNX_POPVAR; + handler.setListFlag(pn, PNX_POPVAR); break; #if JS_HAS_BLOCK_SCOPE @@ -4047,18 +4277,18 @@ Parser::statement() case TOK_RETURN: pn = returnOrYield(false); if (!pn) - return NULL; + return null(); break; case TOK_LC: { StmtInfoPC stmtInfo(context); if (!PushBlocklikeStatement(&stmtInfo, STMT_BLOCK, pc)) - return NULL; + return null(); bool hasFunctionStmt; pn = statements(&hasFunctionStmt); if (!pn) - return NULL; + return null(); MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_IN_COMPOUND); PopStatementPC(context, pc); @@ -4066,21 +4296,18 @@ Parser::statement() } case TOK_SEMI: - pn = UnaryNode::create(PNK_SEMI, this); - if (!pn) - return NULL; - return pn; + return handler.newUnary(PNK_SEMI); case TOK_DEBUGGER: - pn = new_(tokenStream.currentToken().pos); + pn = handler.newDebuggerStatement(tokenStream.currentToken().pos); if (!pn) - return NULL; + return null(); pc->sc->setBindingsAccessedDynamically(); pc->sc->setHasDebuggerStatement(); break; case TOK_ERROR: - return NULL; + return null(); case TOK_NAME: if (tokenStream.currentToken().name() == context->names().module && @@ -4094,7 +4321,7 @@ Parser::statement() } /* Check termination of this primitive statement. */ - return MatchOrInsertSemicolon(context, &tokenStream) ? pn : NULL; + return MatchOrInsertSemicolon(context, &tokenStream) ? pn : null(); } /* @@ -4102,8 +4329,9 @@ Parser::statement() * expression, block statement, non-top-level let declaration in statement * context, and the let-initializer of a for-statement. */ -ParseNode * -Parser::variables(ParseNodeKind kind, StaticBlockObject *blockObj, VarContext varContext) +template +typename ParseHandler::Node +Parser::variables(ParseNodeKind kind, StaticBlockObject *blockObj, VarContext varContext) { /* * The four options here are: @@ -4114,25 +4342,24 @@ Parser::variables(ParseNodeKind kind, StaticBlockObject *blockObj, VarContext va */ JS_ASSERT(kind == PNK_VAR || kind == PNK_CONST || kind == PNK_LET || kind == PNK_CALL); - ParseNode *pn = ListNode::create(kind, this); - if (!pn) - return NULL; + JSOp op = blockObj ? JSOP_NOP : kind == PNK_VAR ? JSOP_DEFVAR : JSOP_DEFCONST; - pn->setOp(blockObj ? JSOP_NOP : kind == PNK_VAR ? JSOP_DEFVAR : JSOP_DEFCONST); - pn->makeEmpty(); + Node pn = handler.newList(kind, null(), op); + if (!pn) + return null(); /* * SpiderMonkey const is really "write once per initialization evaluation" * var, whereas let is block scoped. ES-Harmony wants block-scoped const so * this code will change soon. */ - BindData data(context); + BindData data(context); if (blockObj) data.initLet(varContext, *blockObj, JSMSG_TOO_MANY_LOCALS); else - data.initVarOrConst(pn->getOp()); + data.initVarOrConst(op); - ParseNode *pn2; + Node pn2; do { TokenKind tt = tokenStream.getToken(); #if JS_HAS_DESTRUCTURING @@ -4141,89 +4368,73 @@ Parser::variables(ParseNodeKind kind, StaticBlockObject *blockObj, VarContext va pn2 = primaryExpr(tt); pc->inDeclDestructuring = false; if (!pn2) - return NULL; + return null(); - if (!CheckDestructuring(context, &data, pn2, this)) - return NULL; + if (!checkDestructuring(&data, pn2)) + return null(); bool ignored; if (pc->parsingForInit && matchInOrOf(&ignored)) { tokenStream.ungetToken(); - pn->append(pn2); + handler.addList(pn, pn2); continue; } MUST_MATCH_TOKEN(TOK_ASSIGN, JSMSG_BAD_DESTRUCT_DECL); JS_ASSERT(tokenStream.currentToken().t_op == JSOP_NOP); - ParseNode *init = assignExpr(); + Node init = assignExpr(); if (!init) - return NULL; + return null(); - pn2 = ParseNode::newBinaryOrAppend(PNK_ASSIGN, JSOP_NOP, pn2, init, this); + pn2 = handler.newBinaryOrAppend(PNK_ASSIGN, pn2, init); if (!pn2) - return NULL; - pn->append(pn2); + return null(); + handler.addList(pn, pn2); continue; } #endif /* JS_HAS_DESTRUCTURING */ if (tt != TOK_NAME) { if (tt != TOK_ERROR) - reportError(NULL, JSMSG_NO_VARIABLE_NAME); - return NULL; + report(ParseError, false, null(), JSMSG_NO_VARIABLE_NAME); + return null(); } RootedPropertyName name(context, tokenStream.currentToken().name()); - pn2 = NewBindingNode(name, this, varContext); + pn2 = newBindingNode(name, varContext); if (!pn2) - return NULL; + return null(); if (data.op == JSOP_DEFCONST) - pn2->pn_dflags |= PND_CONST; + handler.setFlag(pn2, PND_CONST); data.pn = pn2; if (!data.binder(context, &data, name, this)) - return NULL; - pn->append(pn2); + return null(); + handler.addList(pn, pn2); if (tokenStream.matchToken(TOK_ASSIGN)) { JS_ASSERT(tokenStream.currentToken().t_op == JSOP_NOP); - ParseNode *init = assignExpr(); + Node init = assignExpr(); if (!init) - return NULL; + return null(); - if (pn2->isUsed()) { - pn2 = MakeAssignment(pn2, init, this); - if (!pn2) - return NULL; - } else { - pn2->pn_expr = init; - } - - pn2->setOp((pn2->pn_dflags & PND_BOUND) - ? JSOP_SETLOCAL - : (data.op == JSOP_DEFCONST) - ? JSOP_SETCONST - : JSOP_SETNAME); - - NoteLValue(pn2); - - /* The declarator's position must include the initializer. */ - pn2->pn_pos.end = init->pn_pos.end; + if (!handler.finishInitializerAssignment(pn2, init, data.op)) + return null(); } } while (tokenStream.matchToken(TOK_COMMA)); - pn->pn_pos.end = pn->last()->pn_pos.end; return pn; } +template <> ParseNode * -Parser::expr() +Parser::expr() { ParseNode *pn = assignExpr(); if (pn && tokenStream.matchToken(TOK_COMMA)) { - ParseNode *pn2 = ListNode::create(PNK_COMMA, this); + ParseNode *pn2 = ListNode::create(PNK_COMMA, &handler); if (!pn2) - return NULL; + return null(); pn2->pn_pos.begin = pn->pn_pos.begin; pn2->initList(pn); pn = pn2; @@ -4231,13 +4442,13 @@ Parser::expr() #if JS_HAS_GENERATORS pn2 = pn->last(); if (pn2->isKind(PNK_YIELD) && !pn2->isInParens()) { - reportError(pn2, JSMSG_BAD_GENERATOR_SYNTAX, js_yield_str); - return NULL; + report(ParseError, false, pn2, JSMSG_BAD_GENERATOR_SYNTAX, js_yield_str); + return null(); } #endif pn2 = assignExpr(); if (!pn2) - return NULL; + return null(); pn->append(pn2); } while (tokenStream.matchToken(TOK_COMMA)); pn->pn_pos.end = pn->last()->pn_pos.end; @@ -4245,6 +4456,21 @@ Parser::expr() return pn; } +template <> +SyntaxParseHandler::Node +Parser::expr() +{ + Node pn = assignExpr(); + if (pn && tokenStream.matchToken(TOK_COMMA)) { + do { + if (!assignExpr()) + return null(); + } while (tokenStream.matchToken(TOK_COMMA)); + return SyntaxParseHandler::NodeGeneric; + } + return pn; +} + /* * For a number of the expression parsers we define an always-inlined version * and a never-inlined version (which just calls the always-inlined version). @@ -4252,18 +4478,20 @@ Parser::expr() * speedup. These macros help avoid some boilerplate code. */ #define BEGIN_EXPR_PARSER(name) \ - JS_ALWAYS_INLINE ParseNode * \ - Parser::name##i() + template \ + JS_ALWAYS_INLINE typename ParseHandler::Node \ + Parser::name##i() #define END_EXPR_PARSER(name) \ - JS_NEVER_INLINE ParseNode * \ - Parser::name##n() { \ + template \ + JS_NEVER_INLINE typename ParseHandler::Node \ + Parser::name##n() { \ return name##i(); \ } BEGIN_EXPR_PARSER(mulExpr1) { - ParseNode *pn = unaryExpr(); + Node pn = unaryExpr(); /* * Note: unlike addExpr1() et al, we use getToken() here instead of @@ -4278,7 +4506,7 @@ BEGIN_EXPR_PARSER(mulExpr1) ? PNK_DIV : PNK_MOD; JSOp op = tokenStream.currentToken().t_op; - pn = ParseNode::newBinaryOrAppend(kind, op, pn, unaryExpr(), this); + pn = handler.newBinaryOrAppend(kind, pn, unaryExpr(), op); } return pn; } @@ -4286,12 +4514,12 @@ END_EXPR_PARSER(mulExpr1) BEGIN_EXPR_PARSER(addExpr1) { - ParseNode *pn = mulExpr1i(); + Node pn = mulExpr1i(); while (pn && tokenStream.isCurrentTokenType(TOK_PLUS, TOK_MINUS)) { TokenKind tt = tokenStream.currentToken().type; JSOp op = (tt == TOK_PLUS) ? JSOP_ADD : JSOP_SUB; ParseNodeKind kind = (tt == TOK_PLUS) ? PNK_ADD : PNK_SUB; - pn = ParseNode::newBinaryOrAppend(kind, op, pn, mulExpr1n(), this); + pn = handler.newBinaryOrAppend(kind, pn, mulExpr1n(), op); } return pn; } @@ -4313,14 +4541,14 @@ ShiftTokenToParseNodeKind(const Token &token) BEGIN_EXPR_PARSER(shiftExpr1) { - ParseNode *left = addExpr1i(); + Node left = addExpr1i(); while (left && tokenStream.isCurrentTokenShift()) { ParseNodeKind kind = ShiftTokenToParseNodeKind(tokenStream.currentToken()); JSOp op = tokenStream.currentToken().t_op; - ParseNode *right = addExpr1n(); + Node right = addExpr1n(); if (!right) - return NULL; - left = new_(kind, op, left, right); + return null(); + left = handler.newBinary(kind, left, right, op); } return left; } @@ -4355,7 +4583,7 @@ BEGIN_EXPR_PARSER(relExpr1) bool oldParsingForInit = pc->parsingForInit; pc->parsingForInit = false; - ParseNode *pn = shiftExpr1i(); + Node pn = shiftExpr1i(); while (pn && (tokenStream.isCurrentTokenRelational() || /* @@ -4366,7 +4594,7 @@ BEGIN_EXPR_PARSER(relExpr1) tokenStream.isCurrentTokenType(TOK_INSTANCEOF))) { ParseNodeKind kind = RelationalTokenToParseNodeKind(tokenStream.currentToken()); JSOp op = tokenStream.currentToken().t_op; - pn = ParseNode::newBinaryOrAppend(kind, op, pn, shiftExpr1n(), this); + pn = handler.newBinaryOrAppend(kind, pn, shiftExpr1n(), op); } /* Restore previous state of parsingForInit flag. */ pc->parsingForInit |= oldParsingForInit; @@ -4393,14 +4621,14 @@ EqualityTokenToParseNodeKind(const Token &token) BEGIN_EXPR_PARSER(eqExpr1) { - ParseNode *left = relExpr1i(); + Node left = relExpr1i(); while (left && tokenStream.isCurrentTokenEquality()) { ParseNodeKind kind = EqualityTokenToParseNodeKind(tokenStream.currentToken()); JSOp op = tokenStream.currentToken().t_op; - ParseNode *right = relExpr1n(); + Node right = relExpr1n(); if (!right) - return NULL; - left = new_(kind, op, left, right); + return null(); + left = handler.newBinary(kind, left, right, op); } return left; } @@ -4408,53 +4636,55 @@ END_EXPR_PARSER(eqExpr1) BEGIN_EXPR_PARSER(bitAndExpr1) { - ParseNode *pn = eqExpr1i(); + Node pn = eqExpr1i(); while (pn && tokenStream.isCurrentTokenType(TOK_BITAND)) - pn = ParseNode::newBinaryOrAppend(PNK_BITAND, JSOP_BITAND, pn, eqExpr1n(), this); + pn = handler.newBinaryOrAppend(PNK_BITAND, pn, eqExpr1n(), JSOP_BITAND); return pn; } END_EXPR_PARSER(bitAndExpr1) BEGIN_EXPR_PARSER(bitXorExpr1) { - ParseNode *pn = bitAndExpr1i(); + Node pn = bitAndExpr1i(); while (pn && tokenStream.isCurrentTokenType(TOK_BITXOR)) - pn = ParseNode::newBinaryOrAppend(PNK_BITXOR, JSOP_BITXOR, pn, bitAndExpr1n(), this); + pn = handler.newBinaryOrAppend(PNK_BITXOR, pn, bitAndExpr1n(), JSOP_BITXOR); return pn; } END_EXPR_PARSER(bitXorExpr1) BEGIN_EXPR_PARSER(bitOrExpr1) { - ParseNode *pn = bitXorExpr1i(); + Node pn = bitXorExpr1i(); while (pn && tokenStream.isCurrentTokenType(TOK_BITOR)) - pn = ParseNode::newBinaryOrAppend(PNK_BITOR, JSOP_BITOR, pn, bitXorExpr1n(), this); + pn = handler.newBinaryOrAppend(PNK_BITOR, pn, bitXorExpr1n(), JSOP_BITOR); return pn; } END_EXPR_PARSER(bitOrExpr1) BEGIN_EXPR_PARSER(andExpr1) { - ParseNode *pn = bitOrExpr1i(); + Node pn = bitOrExpr1i(); while (pn && tokenStream.isCurrentTokenType(TOK_AND)) - pn = ParseNode::newBinaryOrAppend(PNK_AND, JSOP_AND, pn, bitOrExpr1n(), this); + pn = handler.newBinaryOrAppend(PNK_AND, pn, bitOrExpr1n(), JSOP_AND); return pn; } END_EXPR_PARSER(andExpr1) -JS_ALWAYS_INLINE ParseNode * -Parser::orExpr1() +template +JS_ALWAYS_INLINE typename ParseHandler::Node +Parser::orExpr1() { - ParseNode *pn = andExpr1i(); + Node pn = andExpr1i(); while (pn && tokenStream.isCurrentTokenType(TOK_OR)) - pn = ParseNode::newBinaryOrAppend(PNK_OR, JSOP_OR, pn, andExpr1n(), this); + pn = handler.newBinaryOrAppend(PNK_OR, pn, andExpr1n(), JSOP_OR); return pn; } -JS_ALWAYS_INLINE ParseNode * -Parser::condExpr1() +template +JS_ALWAYS_INLINE typename ParseHandler::Node +Parser::condExpr1() { - ParseNode *condition = orExpr1(); + Node condition = orExpr1(); if (!condition || !tokenStream.isCurrentTokenType(TOK_HOOK)) return condition; @@ -4465,30 +4695,31 @@ Parser::condExpr1() */ bool oldParsingForInit = pc->parsingForInit; pc->parsingForInit = false; - ParseNode *thenExpr = assignExpr(); + Node thenExpr = assignExpr(); pc->parsingForInit = oldParsingForInit; if (!thenExpr) - return NULL; + return null(); MUST_MATCH_TOKEN(TOK_COLON, JSMSG_COLON_IN_COND); - ParseNode *elseExpr = assignExpr(); + Node elseExpr = assignExpr(); if (!elseExpr) - return NULL; + return null(); tokenStream.getToken(); /* read one token past the end */ - return new_(condition, thenExpr, elseExpr); + return handler.newConditional(condition, thenExpr, elseExpr); } +template <> bool -Parser::setAssignmentLhsOps(ParseNode *pn, JSOp op) +Parser::setAssignmentLhsOps(ParseNode *pn, JSOp op) { switch (pn->getKind()) { case PNK_NAME: - if (!CheckStrictAssignment(context, this, pn)) + if (!checkStrictAssignment(pn)) return false; pn->setOp(pn->isOp(JSOP_GETLOCAL) ? JSOP_SETLOCAL : JSOP_SETNAME); - NoteLValue(pn); + handler.noteLValue(pn); break; case PNK_DOT: pn->setOp(JSOP_SETPROP); @@ -4500,37 +4731,50 @@ Parser::setAssignmentLhsOps(ParseNode *pn, JSOp op) case PNK_ARRAY: case PNK_OBJECT: if (op != JSOP_NOP) { - reportError(NULL, JSMSG_BAD_DESTRUCT_ASS); + report(ParseError, false, null(), JSMSG_BAD_DESTRUCT_ASS); return false; } - if (!CheckDestructuring(context, NULL, pn, this)) + if (!checkDestructuring(NULL, pn)) return false; break; #endif case PNK_CALL: - if (!MakeSetCall(context, pn, this, JSMSG_BAD_LEFTSIDE_OF_ASS)) + if (!makeSetCall(pn, JSMSG_BAD_LEFTSIDE_OF_ASS)) return false; break; default: - reportError(NULL, JSMSG_BAD_LEFTSIDE_OF_ASS); + report(ParseError, false, null(), JSMSG_BAD_LEFTSIDE_OF_ASS); return false; } return true; } -ParseNode * -Parser::assignExpr() +template <> +bool +Parser::setAssignmentLhsOps(Node pn, JSOp op) { - JS_CHECK_RECURSION(context, return NULL); + /* Full syntax checking of valid assignment LHS terms requires a parse tree. */ + if (pn != SyntaxParseHandler::NodeName && pn != SyntaxParseHandler::NodeLValue) { + setUnknownResult(); + return false; + } + return checkStrictAssignment(pn); +} + +template +typename ParseHandler::Node +Parser::assignExpr() +{ + JS_CHECK_RECURSION(context, return null()); #if JS_HAS_GENERATORS if (tokenStream.matchToken(TOK_YIELD, TSF_OPERAND)) return returnOrYield(true); #endif - ParseNode *lhs = condExpr1(); + Node lhs = condExpr1(); if (!lhs) - return NULL; + return null(); ParseNodeKind kind; switch (tokenStream.currentToken().type) { @@ -4554,18 +4798,17 @@ Parser::assignExpr() JSOp op = tokenStream.currentToken().t_op; if (!setAssignmentLhsOps(lhs, op)) - return NULL; + return null(); - ParseNode *rhs = assignExpr(); + Node rhs = assignExpr(); if (!rhs) - return NULL; + return null(); - return ParseNode::newBinaryOrAppend(kind, op, lhs, rhs, this); + return handler.newBinaryOrAppend(kind, lhs, rhs, op); } -static bool -SetLvalKid(JSContext *cx, Parser *parser, ParseNode *pn, ParseNode *kid, - const char *name) +template <> bool +Parser::setLvalKid(ParseNode *pn, ParseNode *kid, const char *name) { if (!kid->isKind(PNK_NAME) && !kid->isKind(PNK_DOT) && @@ -4574,10 +4817,10 @@ SetLvalKid(JSContext *cx, Parser *parser, ParseNode *pn, ParseNode *kid, !kid->isOp(JSOP_FUNCALL) && !kid->isOp(JSOP_FUNAPPLY))) && !kid->isKind(PNK_ELEM)) { - parser->reportError(NULL, JSMSG_BAD_OPERAND, name); + report(ParseError, false, null(), JSMSG_BAD_OPERAND, name); return false; } - if (!CheckStrictAssignment(cx, parser, kid)) + if (!checkStrictAssignment(kid)) return false; pn->pn_kid = kid; return true; @@ -4585,20 +4828,21 @@ SetLvalKid(JSContext *cx, Parser *parser, ParseNode *pn, ParseNode *kid, static const char incop_name_str[][10] = {"increment", "decrement"}; -static bool -SetIncOpKid(JSContext *cx, Parser *parser, ParseNode *pn, ParseNode *kid, - TokenKind tt, bool preorder) +template <> +bool +Parser::setIncOpKid(ParseNode *pn, ParseNode *kid, TokenKind tt, bool preorder) { JSOp op; - if (!SetLvalKid(cx, parser, pn, kid, incop_name_str[tt == TOK_DEC])) + if (!setLvalKid(pn, kid, incop_name_str[tt == TOK_DEC])) return false; + switch (kid->getKind()) { case PNK_NAME: op = (tt == TOK_INC) ? (preorder ? JSOP_INCNAME : JSOP_NAMEINC) : (preorder ? JSOP_DECNAME : JSOP_NAMEDEC); - NoteLValue(kid); + handler.noteLValue(kid); break; case PNK_DOT: @@ -4608,7 +4852,7 @@ SetIncOpKid(JSContext *cx, Parser *parser, ParseNode *pn, ParseNode *kid, break; case PNK_CALL: - if (!MakeSetCall(cx, kid, parser, JSMSG_BAD_INCOP_OPERAND)) + if (!makeSetCall(kid, JSMSG_BAD_INCOP_OPERAND)) return false; /* FALL THROUGH */ case PNK_ELEM: @@ -4625,22 +4869,77 @@ SetIncOpKid(JSContext *cx, Parser *parser, ParseNode *pn, ParseNode *kid, return true; } -ParseNode * -Parser::unaryOpExpr(ParseNodeKind kind, JSOp op) +template <> +bool +Parser::setIncOpKid(Node pn, Node kid, TokenKind tt, bool preorder) { - TokenPtr begin = tokenStream.currentToken().pos.begin; - ParseNode *kid = unaryExpr(); - if (!kid) - return NULL; - return new_(kind, op, TokenPos::make(begin, kid->pn_pos.end), kid); + return setAssignmentLhsOps(kid, JSOP_NOP); } -ParseNode * -Parser::unaryExpr() +template +typename ParseHandler::Node +Parser::unaryOpExpr(ParseNodeKind kind, JSOp op) { - ParseNode *pn, *pn2; + Node kid = unaryExpr(); + if (!kid) + return null(); + return handler.newUnary(kind, kid, op); +} - JS_CHECK_RECURSION(context, return NULL); +template <> +bool +Parser::checkDeleteExpression(ParseNode **pn_) +{ + ParseNode *&pn = *pn_; + + /* + * Under ECMA3, deleting any unary expression is valid -- it simply + * returns true. Here we fold constants before checking for a call + * expression, in order to rule out delete of a generator expression. + */ + if (foldConstants && !FoldConstants(context, &pn, this)) + return false; + switch (pn->getKind()) { + case PNK_CALL: + if (!(pn->pn_xflags & PNX_SETCALL)) { + /* + * Call MakeSetCall to check for errors, but clear PNX_SETCALL + * because the optimizer will eliminate the useless delete. + */ + if (!makeSetCall(pn, JSMSG_BAD_DELETE_OPERAND)) + return false; + pn->pn_xflags &= ~PNX_SETCALL; + } + break; + case PNK_NAME: + if (!report(ParseStrictError, pc->sc->strict, pn, JSMSG_DEPRECATED_DELETE_OPERAND)) + return null(); + pc->sc->setBindingsAccessedDynamically(); + pn->pn_dflags |= PND_DEOPTIMIZED; + pn->setOp(JSOP_DELNAME); + break; + default:; + } + return true; +} + +template <> +bool +Parser::checkDeleteExpression(Node *pn) +{ + PropertyName *name = handler.isName(*pn); + if (name) + return report(ParseStrictError, pc->sc->strict, *pn, JSMSG_DEPRECATED_DELETE_OPERAND); + return true; +} + +template +typename ParseHandler::Node +Parser::unaryExpr() +{ + Node pn, pn2; + + JS_CHECK_RECURSION(context, return null()); switch (TokenKind tt = tokenStream.getToken(TSF_OPERAND)) { case TOK_TYPEOF: @@ -4658,80 +4957,55 @@ Parser::unaryExpr() case TOK_INC: case TOK_DEC: - pn = UnaryNode::create((tt == TOK_INC) ? PNK_PREINCREMENT : PNK_PREDECREMENT, this); - if (!pn) - return NULL; + { + TokenPtr begin = tokenStream.currentToken().pos.begin; pn2 = memberExpr(true); if (!pn2) - return NULL; - if (!SetIncOpKid(context, this, pn, pn2, tt, true)) - return NULL; - pn->pn_pos.end = pn2->pn_pos.end; + return null(); + pn = handler.newUnary((tt == TOK_INC) ? PNK_PREINCREMENT : PNK_PREDECREMENT, pn2); + if (!pn) + return null(); + handler.setBeginPosition(pn, begin); + if (!setIncOpKid(pn, pn2, tt, true)) + return null(); break; + } case TOK_DELETE: { - pn = UnaryNode::create(PNK_DELETE, this); - if (!pn) - return NULL; + TokenPtr begin = tokenStream.currentToken().pos.begin; pn2 = unaryExpr(); if (!pn2) - return NULL; - pn->pn_pos.end = pn2->pn_pos.end; + return null(); - /* - * Under ECMA3, deleting any unary expression is valid -- it simply - * returns true. Here we fold constants before checking for a call - * expression, in order to rule out delete of a generator expression. - */ - if (foldConstants && !FoldConstants(context, &pn2, this)) - return NULL; - switch (pn2->getKind()) { - case PNK_CALL: - if (!(pn2->pn_xflags & PNX_SETCALL)) { - /* - * Call MakeSetCall to check for errors, but clear PNX_SETCALL - * because the optimizer will eliminate the useless delete. - */ - if (!MakeSetCall(context, pn2, this, JSMSG_BAD_DELETE_OPERAND)) - return NULL; - pn2->pn_xflags &= ~PNX_SETCALL; - } - break; - case PNK_NAME: - if (!reportStrictModeError(pn, JSMSG_DEPRECATED_DELETE_OPERAND)) - return NULL; - pc->sc->setBindingsAccessedDynamically(); - pn2->pn_dflags |= PND_DEOPTIMIZED; - pn2->setOp(JSOP_DELNAME); - break; - default:; - } - pn->pn_kid = pn2; + if (!checkDeleteExpression(&pn2)) + return null(); + + pn = handler.newUnary(PNK_DELETE, pn2); + if (!pn) + return null(); + handler.setBeginPosition(pn, begin); break; } case TOK_ERROR: - return NULL; + return null(); default: tokenStream.ungetToken(); pn = memberExpr(true); if (!pn) - return NULL; + return null(); /* Don't look across a newline boundary for a postfix incop. */ - if (tokenStream.onCurrentLine(pn->pn_pos)) { - tt = tokenStream.peekTokenSameLine(TSF_OPERAND); - if (tt == TOK_INC || tt == TOK_DEC) { - tokenStream.consumeKnownToken(tt); - pn2 = UnaryNode::create((tt == TOK_INC) ? PNK_POSTINCREMENT : PNK_POSTDECREMENT, this); - if (!pn2) - return NULL; - if (!SetIncOpKid(context, this, pn2, pn, tt, false)) - return NULL; - pn2->pn_pos.begin = pn->pn_pos.begin; - pn = pn2; - } + tt = tokenStream.peekTokenSameLine(TSF_OPERAND); + if (tt == TOK_INC || tt == TOK_DEC) { + tokenStream.consumeKnownToken(tt); + pn2 = handler.newUnary((tt == TOK_INC) ? PNK_POSTINCREMENT : PNK_POSTDECREMENT, pn); + if (!pn2) + return null(); + if (!setIncOpKid(pn2, pn, tt, false)) + return null(); + pn = pn2; } break; } @@ -4761,15 +5035,16 @@ Parser::unaryExpr() * NB: This is not a general tree transplanter -- it knows in particular that * the one or more bindings induced by V have not yet been created. */ -class CompExprTransplanter { +class CompExprTransplanter +{ ParseNode *root; - Parser *parser; + Parser *parser; bool genexp; unsigned adjust; HashSet visitedImplicitArguments; public: - CompExprTransplanter(ParseNode *pn, Parser *parser, bool ge, unsigned adj) + CompExprTransplanter(ParseNode *pn, Parser *parser, bool ge, unsigned adj) : root(pn), parser(parser), genexp(ge), adjust(adj), visitedImplicitArguments(parser->context) {} @@ -4798,30 +5073,35 @@ class CompExprTransplanter { * - checkValidBody() if this *did* turn out to be a generator expression * - maybeNoteGenerator() if this *did not* turn out to be a generator expression */ -class GenexpGuard { - Parser *parser; - uint32_t startYieldCount; +template +class GenexpGuard +{ + Parser *parser; + uint32_t startYieldCount; + + typedef typename ParseHandler::Node Node; public: - explicit GenexpGuard(Parser *parser) + explicit GenexpGuard(Parser *parser) : parser(parser) { - ParseContext *pc = parser->pc; + ParseContext *pc = parser->pc; if (pc->parenDepth == 0) { pc->yieldCount = 0; - pc->yieldNode = NULL; + pc->yieldNode = ParseHandler::null(); } startYieldCount = pc->yieldCount; pc->parenDepth++; } void endBody(); - bool checkValidBody(ParseNode *pn, unsigned err); - bool maybeNoteGenerator(ParseNode *pn); + bool checkValidBody(Node pn, unsigned err = JSMSG_BAD_GENEXP_BODY); + bool maybeNoteGenerator(Node pn); }; +template void -GenexpGuard::endBody() +GenexpGuard::endBody() { parser->pc->parenDepth--; } @@ -4833,15 +5113,16 @@ GenexpGuard::endBody() * Call this after endBody() when determining that the body *was* in a * generator expression. */ +template bool -GenexpGuard::checkValidBody(ParseNode *pn, unsigned err = JSMSG_BAD_GENEXP_BODY) +GenexpGuard::checkValidBody(Node pn, unsigned err) { - ParseContext *pc = parser->pc; + ParseContext *pc = parser->pc; if (pc->yieldCount > startYieldCount) { - ParseNode *errorNode = pc->yieldNode; + Node errorNode = pc->yieldNode; if (!errorNode) errorNode = pn; - parser->reportError(errorNode, err, js_yield_str); + parser->report(ParseError, false, errorNode, err, js_yield_str); return false; } @@ -4855,20 +5136,22 @@ GenexpGuard::checkValidBody(ParseNode *pn, unsigned err = JSMSG_BAD_GENEXP_BODY) * Call this after endBody() when determining that the body *was not* in a * generator expression. */ +template bool -GenexpGuard::maybeNoteGenerator(ParseNode *pn) +GenexpGuard::maybeNoteGenerator(Node pn) { - ParseContext *pc = parser->pc; + ParseContext *pc = parser->pc; if (pc->yieldCount > 0) { if (!pc->sc->isFunctionBox()) { - parser->reportError(NULL, JSMSG_BAD_RETURN_OR_YIELD, js_yield_str); + parser->report(ParseError, false, ParseHandler::null(), + JSMSG_BAD_RETURN_OR_YIELD, js_yield_str); return false; } pc->sc->asFunctionBox()->setIsGenerator(); if (pc->funHasReturnExpr) { /* At the time we saw the yield, we might not have set isGenerator yet. */ - ReportBadReturn(pc->sc->context, parser, pn, &Parser::reportError, - JSMSG_BAD_GENERATOR_RETURN, JSMSG_BAD_ANON_GENERATOR_RETURN); + parser->reportBadReturn(pn, ParseError, + JSMSG_BAD_GENERATOR_RETURN, JSMSG_BAD_ANON_GENERATOR_RETURN); return false; } } @@ -4880,8 +5163,9 @@ GenexpGuard::maybeNoteGenerator(ParseNode *pn) * expression must move "down" one static level, which of course increases the * upvar-frame-skip count. */ +template static bool -BumpStaticLevel(ParseNode *pn, ParseContext *pc) +BumpStaticLevel(ParseNode *pn, ParseContext *pc) { if (pn->pn_cookie.isFree()) return true; @@ -4891,8 +5175,9 @@ BumpStaticLevel(ParseNode *pn, ParseContext *pc) return pn->pn_cookie.set(pc->sc->context, level, pn->pn_cookie.slot()); } +template static bool -AdjustBlockId(ParseNode *pn, unsigned adjust, ParseContext *pc) +AdjustBlockId(ParseNode *pn, unsigned adjust, ParseContext *pc) { JS_ASSERT(pn->isArity(PN_LIST) || pn->isArity(PN_CODE) || pn->isArity(PN_NAME)); if (JS_BIT(20) - pn->pn_blockid <= adjust + 1) { @@ -4908,7 +5193,7 @@ AdjustBlockId(ParseNode *pn, unsigned adjust, ParseContext *pc) bool CompExprTransplanter::transplant(ParseNode *pn) { - ParseContext *pc = parser->pc; + ParseContext *pc = parser->pc; if (!pn) return true; @@ -4995,7 +5280,7 @@ CompExprTransplanter::transplant(ParseNode *pn) * generator) a use of a new placeholder in the generator's * lexdeps. */ - Definition *dn2 = MakePlaceholder(pn, parser, parser->pc); + Definition *dn2 = MakePlaceholder(pn, &parser->handler, parser->pc); if (!dn2) return false; dn2->pn_pos = root->pn_pos; @@ -5069,14 +5354,15 @@ CompExprTransplanter::transplant(ParseNode *pn) * comprehension or generator expression, with a unary node as the body of the * (possibly nested) for-loop, initialized by |kind, op, kid|. */ +template <> ParseNode * -Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bool isGenexp, - ParseNodeKind kind, JSOp op) +Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bool isGenexp, + ParseNodeKind kind, JSOp op) { unsigned adjust; ParseNode *pn, *pn2, *pn3, **pnp; StmtInfoPC stmtInfo(context); - BindData data(context); + BindData data(context); TokenKind tt; JS_ASSERT(tokenStream.currentToken().type == TOK_FOR); @@ -5087,9 +5373,9 @@ Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bool isGenexp, * yields the next value from a for-in loop (possibly nested, and with * optional if guard). Make pn be the TOK_LC body node. */ - pn = PushLexicalScope(context, this, &stmtInfo); + pn = pushLexicalScope(&stmtInfo); if (!pn) - return NULL; + return null(); adjust = pn->pn_blockid - blockid; } else { JS_ASSERT(kind == PNK_ARRAYPUSH); @@ -5107,9 +5393,9 @@ Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bool isGenexp, * block scope. */ adjust = pc->blockid(); - pn = PushLexicalScope(context, this, &stmtInfo); + pn = pushLexicalScope(&stmtInfo); if (!pn) - return NULL; + return null(); JS_ASSERT(blockid <= pn->pn_blockid); JS_ASSERT(blockid < pc->blockidGen); @@ -5123,10 +5409,10 @@ Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bool isGenexp, CompExprTransplanter transplanter(kid, this, kind == PNK_SEMI, adjust); if (!transplanter.init()) - return NULL; + return null(); if (!transplanter.transplant(kid)) - return NULL; + return null(); JS_ASSERT(pc->blockChain && pc->blockChain == pn->pn_objbox->object); data.initLet(HoistVars, *pc->blockChain, JSMSG_ARRAY_INIT_TOO_BIG); @@ -5137,9 +5423,9 @@ Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bool isGenexp, * index to count each block-local let-variable on the left-hand side * of the in/of. */ - pn2 = BinaryNode::create(PNK_FOR, this); + pn2 = BinaryNode::create(PNK_FOR, &handler); if (!pn2) - return NULL; + return null(); pn2->setOp(JSOP_ITER); pn2->pn_iflags = JSITER_ENUMERATE; @@ -5151,7 +5437,7 @@ Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bool isGenexp, } MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR); - GenexpGuard guard(this); + GenexpGuard guard(this); RootedPropertyName name(context); tt = tokenStream.getToken(); @@ -5163,7 +5449,7 @@ Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bool isGenexp, pn3 = primaryExpr(tt); pc->inDeclDestructuring = false; if (!pn3) - return NULL; + return null(); break; #endif @@ -5177,53 +5463,53 @@ Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bool isGenexp, * and it tries to bind all names to slots, so we must let it do * the deed. */ - pn3 = NewBindingNode(name, this); + pn3 = newBindingNode(name); if (!pn3) - return NULL; + return null(); break; default: - reportError(NULL, JSMSG_NO_VARIABLE_NAME); + report(ParseError, false, null(), JSMSG_NO_VARIABLE_NAME); case TOK_ERROR: - return NULL; + return null(); } bool forOf; if (!matchInOrOf(&forOf)) { - reportError(NULL, JSMSG_IN_AFTER_FOR_NAME); - return NULL; + report(ParseError, false, null(), JSMSG_IN_AFTER_FOR_NAME); + return null(); } if (forOf) { if (pn2->pn_iflags != JSITER_ENUMERATE) { JS_ASSERT(pn2->pn_iflags == (JSITER_FOREACH | JSITER_ENUMERATE)); - reportError(NULL, JSMSG_BAD_FOR_EACH_LOOP); - return NULL; + report(ParseError, false, null(), JSMSG_BAD_FOR_EACH_LOOP); + return null(); } pn2->pn_iflags = JSITER_FOR_OF; } ParseNode *pn4 = expr(); if (!pn4) - return NULL; + return null(); MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_CTRL); guard.endBody(); if (isGenexp) { if (!guard.checkValidBody(pn2)) - return NULL; + return null(); } else { if (!guard.maybeNoteGenerator(pn2)) - return NULL; + return null(); } switch (tt) { #if JS_HAS_DESTRUCTURING case TOK_LB: case TOK_LC: - if (!CheckDestructuring(context, &data, pn3, this)) - return NULL; + if (!checkDestructuring(&data, pn3)) + return null(); if (versionNumber() == JSVERSION_1_7 && !(pn2->pn_iflags & JSITER_FOREACH) && @@ -5231,8 +5517,8 @@ Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bool isGenexp, { /* Destructuring requires [key, value] enumeration in JS1.7. */ if (!pn3->isKind(PNK_ARRAY) || pn3->pn_count != 2) { - reportError(NULL, JSMSG_BAD_FOR_LEFTSIDE); - return NULL; + report(ParseError, false, null(), JSMSG_BAD_FOR_LEFTSIDE); + return null(); } JS_ASSERT(pn2->isOp(JSOP_ITER)); @@ -5245,7 +5531,7 @@ Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bool isGenexp, case TOK_NAME: data.pn = pn3; if (!data.binder(context, &data, name, this)) - return NULL; + return null(); break; default:; @@ -5255,9 +5541,9 @@ Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bool isGenexp, * Synthesize a declaration. Every definition must appear in the parse * tree in order for ComprehensionTranslator to work. */ - ParseNode *vars = ListNode::create(PNK_VAR, this); + ParseNode *vars = ListNode::create(PNK_VAR, &handler); if (!vars) - return NULL; + return null(); vars->setOp(JSOP_NOP); vars->pn_pos = pn3->pn_pos; vars->makeEmpty(); @@ -5265,31 +5551,31 @@ Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bool isGenexp, vars->pn_xflags |= PNX_FORINVAR; /* Definitions can't be passed directly to EmitAssignment as lhs. */ - pn3 = CloneLeftHandSide(pn3, this); + pn3 = cloneLeftHandSide(pn3); if (!pn3) - return NULL; + return null(); - pn2->pn_left = new_(PNK_FORIN, JSOP_NOP, vars, pn3, pn4); + pn2->pn_left = handler.newTernary(PNK_FORIN, vars, pn3, pn4); if (!pn2->pn_left) - return NULL; + return null(); *pnp = pn2; pnp = &pn2->pn_right; } while (tokenStream.matchToken(TOK_FOR)); if (tokenStream.matchToken(TOK_IF)) { - pn2 = TernaryNode::create(PNK_IF, this); + pn2 = TernaryNode::create(PNK_IF, &handler); if (!pn2) - return NULL; + return null(); pn2->pn_kid1 = condition(); if (!pn2->pn_kid1) - return NULL; + return null(); *pnp = pn2; pnp = &pn2->pn_kid2; } - pn2 = UnaryNode::create(kind, this); + pn2 = UnaryNode::create(kind, &handler); if (!pn2) - return NULL; + return null(); pn2->setOp(op); pn2->pn_kid = kid; *pnp = pn2; @@ -5298,6 +5584,40 @@ Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bool isGenexp, return pn; } +template <> +bool +Parser::arrayInitializerComprehensionTail(ParseNode *pn) +{ + /* Relabel pn as an array comprehension node. */ + pn->setKind(PNK_ARRAYCOMP); + + /* + * Remove the comprehension expression from pn's linked list + * and save it via pnexp. We'll re-install it underneath the + * ARRAYPUSH node after we parse the rest of the comprehension. + */ + ParseNode *pnexp = pn->last(); + JS_ASSERT(pn->pn_count == 1); + pn->pn_count = 0; + pn->pn_tail = &pn->pn_head; + *pn->pn_tail = NULL; + + ParseNode *pntop = comprehensionTail(pnexp, pn->pn_blockid, false, + PNK_ARRAYPUSH, JSOP_ARRAYPUSH); + if (!pntop) + return false; + pn->append(pntop); + return true; +} + +template <> +bool +Parser::arrayInitializerComprehensionTail(Node pn) +{ + setUnknownResult(); + return false; +} + #if JS_HAS_GENERATOR_EXPRS /* @@ -5315,15 +5635,16 @@ Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bool isGenexp, * the first |in| in the chain of |for| heads. Instead, a generator expression * is merely sugar for a generator function expression and its application. */ +template <> ParseNode * -Parser::generatorExpr(ParseNode *kid) +Parser::generatorExpr(ParseNode *kid) { JS_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR)); /* Create a |yield| node for |kid|. */ - ParseNode *pn = UnaryNode::create(PNK_YIELD, this); + ParseNode *pn = UnaryNode::create(PNK_YIELD, &handler); if (!pn) - return NULL; + return null(); pn->setOp(JSOP_YIELD); pn->setInParens(true); pn->pn_pos = kid->pn_pos; @@ -5331,28 +5652,28 @@ Parser::generatorExpr(ParseNode *kid) pn->pn_hidden = true; /* Make a new node for the desugared generator function. */ - ParseNode *genfn = CodeNode::create(PNK_FUNCTION, this); + ParseNode *genfn = CodeNode::create(PNK_FUNCTION, &handler); if (!genfn) - return NULL; + return null(); genfn->setOp(JSOP_LAMBDA); JS_ASSERT(!genfn->pn_body); genfn->pn_dflags = 0; { - ParseContext *outerpc = pc; + ParseContext *outerpc = pc; RootedFunction fun(context, newFunction(outerpc, /* atom = */ NullPtr(), Expression)); if (!fun) - return NULL; + return null(); /* Create box for fun->object early to protect against last-ditch GC. */ FunctionBox *genFunbox = newFunctionBox(fun, outerpc, outerpc->sc->strict); if (!genFunbox) - return NULL; + return null(); - ParseContext genpc(this, genFunbox, outerpc->staticLevel + 1, outerpc->blockidGen); + ParseContext genpc(this, genFunbox, outerpc->staticLevel + 1, outerpc->blockidGen); if (!genpc.init()) - return NULL; + return null(); /* * We assume conservatively that any deoptimization flags in pc->sc @@ -5371,7 +5692,7 @@ Parser::generatorExpr(ParseNode *kid) ParseNode *body = comprehensionTail(pn, outerpc->blockid(), true); if (!body) - return NULL; + return null(); JS_ASSERT(!genfn->pn_body); genfn->pn_body = body; genfn->pn_pos.begin = body->pn_pos.begin = kid->pn_pos.begin; @@ -5380,73 +5701,80 @@ Parser::generatorExpr(ParseNode *kid) if (AtomDefnPtr p = genpc.lexdeps->lookup(context->names().arguments)) { Definition *dn = p.value(); ParseNode *errorNode = dn->dn_uses ? dn->dn_uses : body; - reportError(errorNode, JSMSG_BAD_GENEXP_BODY, js_arguments_str); - return NULL; + report(ParseError, false, errorNode, JSMSG_BAD_GENEXP_BODY, js_arguments_str); + return null(); } RootedPropertyName funName(context); - if (!LeaveFunction(genfn, this, funName)) - return NULL; + if (!leaveFunction(genfn, funName)) + return null(); } /* * Our result is a call expression that invokes the anonymous generator * function object. */ - ParseNode *result = ListNode::create(PNK_GENEXP, this); + ParseNode *result = ListNode::create(PNK_GENEXP, &handler); if (!result) - return NULL; + return null(); result->setOp(JSOP_CALL); result->pn_pos.begin = genfn->pn_pos.begin; result->initList(genfn); return result; } +template <> +SyntaxParseHandler::Node +Parser::generatorExpr(Node kid) +{ + setUnknownResult(); + return SyntaxParseHandler::NodeFailure; +} + static const char js_generator_str[] = "generator"; #endif /* JS_HAS_GENERATOR_EXPRS */ #endif /* JS_HAS_GENERATORS */ -ParseNode * -Parser::assignExprWithoutYield(unsigned msg) +template +typename ParseHandler::Node +Parser::assignExprWithoutYield(unsigned msg) { #ifdef JS_HAS_GENERATORS - GenexpGuard yieldGuard(this); + GenexpGuard yieldGuard(this); #endif - ParseNode *res = assignExpr(); + Node res = assignExpr(); yieldGuard.endBody(); if (res) { #ifdef JS_HAS_GENERATORS - if (!yieldGuard.checkValidBody(res, msg)) { - freeTree(res); - res = NULL; - } + if (!yieldGuard.checkValidBody(res, msg)) + return null(); #endif } return res; } +template bool -Parser::argumentList(ParseNode *listNode) +Parser::argumentList(Node listNode) { if (tokenStream.matchToken(TOK_RP, TSF_OPERAND)) return true; - GenexpGuard guard(this); + GenexpGuard guard(this); bool arg0 = true; do { - ParseNode *argNode = assignExpr(); + Node argNode = assignExpr(); if (!argNode) return false; if (arg0) guard.endBody(); #if JS_HAS_GENERATORS - if (argNode->isKind(PNK_YIELD) && - !argNode->isInParens() && + if (handler.isOperationWithoutParens(argNode, PNK_YIELD) && tokenStream.peekToken() == TOK_COMMA) { - reportError(argNode, JSMSG_BAD_GENERATOR_SYNTAX, js_yield_str); + report(ParseError, false, argNode, JSMSG_BAD_GENERATOR_SYNTAX, js_yield_str); return false; } #endif @@ -5457,9 +5785,8 @@ Parser::argumentList(ParseNode *listNode) argNode = generatorExpr(argNode); if (!argNode) return false; - if (listNode->pn_count > 1 || - tokenStream.peekToken() == TOK_COMMA) { - reportError(argNode, JSMSG_BAD_GENERATOR_SYNTAX, js_generator_str); + if (!arg0 || tokenStream.peekToken() == TOK_COMMA) { + report(ParseError, false, argNode, JSMSG_BAD_GENERATOR_SYNTAX, js_generator_str); return false; } } else @@ -5469,128 +5796,137 @@ Parser::argumentList(ParseNode *listNode) arg0 = false; - listNode->append(argNode); + handler.addList(listNode, argNode); } while (tokenStream.matchToken(TOK_COMMA)); if (tokenStream.getToken() != TOK_RP) { - reportError(NULL, JSMSG_PAREN_AFTER_ARGS); + report(ParseError, false, null(), JSMSG_PAREN_AFTER_ARGS); return false; } return true; } -ParseNode * -Parser::memberExpr(bool allowCallSyntax) +template <> +PropertyName * +Parser::foldPropertyByValue(ParseNode *pn) { - ParseNode *lhs; + /* + * Optimize property name lookups. If the name is a PropertyName, + * then make a name-based node so the emitter will use a name-based + * bytecode. Otherwise make a node using the property expression + * by value. If the node is a string containing an index, convert + * it to a number to save work later. + */ - JS_CHECK_RECURSION(context, return NULL); + uint32_t index; + if (foldConstants) { + if (pn->isKind(PNK_STRING)) { + JSAtom *atom = pn->pn_atom; + if (atom->isIndex(&index)) { + pn->setKind(PNK_NUMBER); + pn->setOp(JSOP_DOUBLE); + pn->pn_dval = index; + } else { + return atom->asPropertyName(); + } + } else if (pn->isKind(PNK_NUMBER)) { + double number = pn->pn_dval; + if (number != ToUint32(number)) { + JSAtom *atom = ToAtom(context, DoubleValue(number)); + if (!atom) + return NULL; + return atom->asPropertyName(); + } + } + } + + return NULL; +} + +template <> +PropertyName * +Parser::foldPropertyByValue(Node pn) +{ + return NULL; +} + +template +typename ParseHandler::Node +Parser::memberExpr(bool allowCallSyntax) +{ + Node lhs; + + JS_CHECK_RECURSION(context, return null()); /* Check for new expression first. */ TokenKind tt = tokenStream.getToken(TSF_OPERAND); if (tt == TOK_NEW) { - lhs = ListNode::create(PNK_NEW, this); + lhs = handler.newList(PNK_NEW, null(), JSOP_NEW); if (!lhs) - return NULL; - ParseNode *ctorExpr = memberExpr(false); + return null(); + + Node ctorExpr = memberExpr(false); if (!ctorExpr) - return NULL; - lhs->setOp(JSOP_NEW); - lhs->initList(ctorExpr); - lhs->pn_pos.begin = ctorExpr->pn_pos.begin; + return null(); + + handler.addList(lhs, ctorExpr); if (tokenStream.matchToken(TOK_LP) && !argumentList(lhs)) - return NULL; - if (lhs->pn_count > ARGC_LIMIT) { - JS_ReportErrorNumber(context, js_GetErrorMessage, NULL, - JSMSG_TOO_MANY_CON_ARGS); - return NULL; - } - lhs->pn_pos.end = lhs->last()->pn_pos.end; + return null(); } else { lhs = primaryExpr(tt); if (!lhs) - return NULL; + return null(); } while ((tt = tokenStream.getToken()) > TOK_EOF) { - ParseNode *nextMember; + Node nextMember; if (tt == TOK_DOT) { tt = tokenStream.getToken(TSF_KEYWORD_IS_NAME); if (tt == TOK_ERROR) - return NULL; + return null(); if (tt == TOK_NAME) { PropertyName *field = tokenStream.currentToken().name(); - nextMember = new_(lhs, field, - lhs->pn_pos.begin, - tokenStream.currentToken().pos.end); + TokenPtr end = tokenStream.currentToken().pos.end; + nextMember = handler.newPropertyAccess(lhs, field, end); if (!nextMember) - return NULL; + return null(); } else { - reportError(NULL, JSMSG_NAME_AFTER_DOT); - return NULL; + report(ParseError, false, null(), JSMSG_NAME_AFTER_DOT); + return null(); } } else if (tt == TOK_LB) { - ParseNode *propExpr = expr(); + Node propExpr = expr(); if (!propExpr) - return NULL; + return null(); MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_IN_INDEX); - TokenPtr begin = lhs->pn_pos.begin, end = tokenStream.currentToken().pos.end; /* * Do folding so we don't have roundtrip changes for cases like: * function (obj) { return obj["a" + "b"] } */ if (foldConstants && !FoldConstants(context, &propExpr, this)) - return NULL; + return null(); - /* - * Optimize property name lookups. If the name is a PropertyName, - * then make a name-based node so the emitter will use a name-based - * bytecode. Otherwise make a node using the property expression - * by value. If the node is a string containing an index, convert - * it to a number to save work later. - */ - uint32_t index; - PropertyName *name = NULL; - if (foldConstants) { - if (propExpr->isKind(PNK_STRING)) { - JSAtom *atom = propExpr->pn_atom; - if (atom->isIndex(&index)) { - propExpr->setKind(PNK_NUMBER); - propExpr->setOp(JSOP_DOUBLE); - propExpr->pn_dval = index; - } else { - name = atom->asPropertyName(); - } - } else if (propExpr->isKind(PNK_NUMBER)) { - double number = propExpr->pn_dval; - if (number != ToUint32(number)) { - JSAtom *atom = ToAtom(context, DoubleValue(number)); - if (!atom) - return NULL; - name = atom->asPropertyName(); - } - } - } + PropertyName *name = foldPropertyByValue(propExpr); + TokenPtr end = tokenStream.currentToken().pos.end; if (name) - nextMember = new_(lhs, name, begin, end); + nextMember = handler.newPropertyAccess(lhs, name, end); else - nextMember = new_(lhs, propExpr, begin, end); + nextMember = handler.newPropertyByValue(lhs, propExpr, end); if (!nextMember) - return NULL; + return null(); } else if (allowCallSyntax && tt == TOK_LP) { - nextMember = ListNode::create(PNK_CALL, this); + nextMember = handler.newList(PNK_CALL, null(), JSOP_CALL); if (!nextMember) - return NULL; - nextMember->setOp(JSOP_CALL); + return null(); - if (lhs->isOp(JSOP_NAME)) { - if (lhs->pn_atom == context->names().eval) { + if (JSAtom *atom = handler.isName(lhs)) { + if (atom == context->names().eval) { /* Select JSOP_EVAL and flag pc as heavyweight. */ - nextMember->setOp(JSOP_EVAL); + handler.setOp(nextMember, JSOP_EVAL); pc->sc->setBindingsAccessedDynamically(); /* @@ -5600,25 +5936,19 @@ Parser::memberExpr(bool allowCallSyntax) if (pc->sc->isFunctionBox() && !pc->sc->strict) pc->sc->asFunctionBox()->setHasExtensibleScope(); } - } else if (lhs->isOp(JSOP_GETPROP)) { + } else if (JSAtom *atom = handler.isGetProp(lhs)) { /* Select JSOP_FUNAPPLY given foo.apply(...). */ - if (lhs->pn_atom == context->names().apply) - nextMember->setOp(JSOP_FUNAPPLY); - else if (lhs->pn_atom == context->names().call) - nextMember->setOp(JSOP_FUNCALL); + if (atom == context->names().apply) + handler.setOp(nextMember, JSOP_FUNAPPLY); + else if (atom == context->names().call) + handler.setOp(nextMember, JSOP_FUNCALL); } - nextMember->initList(lhs); - nextMember->pn_pos.begin = lhs->pn_pos.begin; + handler.setBeginPosition(nextMember, lhs); + handler.addList(nextMember, lhs); if (!argumentList(nextMember)) - return NULL; - if (nextMember->pn_count > ARGC_LIMIT) { - JS_ReportErrorNumber(context, js_GetErrorMessage, NULL, - JSMSG_TOO_MANY_FUN_ARGS); - return NULL; - } - nextMember->pn_pos.end = tokenStream.currentToken().pos.end; + return null(); } else { tokenStream.ungetToken(); return lhs; @@ -5627,12 +5957,13 @@ Parser::memberExpr(bool allowCallSyntax) lhs = nextMember; } if (tt == TOK_ERROR) - return NULL; + return null(); return lhs; } -ParseNode * -Parser::bracketedExpr() +template +typename ParseHandler::Node +Parser::bracketedExpr() { /* * Always accept the 'in' operator in a parenthesized expression, @@ -5641,90 +5972,129 @@ Parser::bracketedExpr() */ bool oldParsingForInit = pc->parsingForInit; pc->parsingForInit = false; - ParseNode *pn = expr(); + Node pn = expr(); pc->parsingForInit = oldParsingForInit; return pn; } -ParseNode * -Parser::identifierName() +template +typename ParseHandler::Node +Parser::identifierName() { JS_ASSERT(tokenStream.isCurrentTokenType(TOK_NAME)); PropertyName *name = tokenStream.currentToken().name(); - ParseNode *node = NameNode::create(PNK_NAME, name, this, this->pc); - if (!node) - return NULL; - JS_ASSERT(tokenStream.currentToken().t_op == JSOP_NAME); - node->setOp(JSOP_NAME); + Node pn = handler.newName(name, pc); + if (!pn) + return null(); - if (!pc->inDeclDestructuring && !NoteNameUse(node, this)) - return NULL; + if (!pc->inDeclDestructuring && !noteNameUse(pn)) + return null(); - return node; + return pn; } -ParseNode * -Parser::atomNode(ParseNodeKind kind, JSOp op) +template +typename ParseHandler::Node +Parser::atomNode(ParseNodeKind kind, JSOp op) { - ParseNode *node = NullaryNode::create(kind, this); - if (!node) - return NULL; - node->setOp(op); - const Token &tok = tokenStream.currentToken(); - node->pn_atom = tok.atom(); + JSAtom *atom = tokenStream.currentToken().atom(); + Node pn = handler.newAtom(kind, atom, op); + if (!pn) + return null(); // Large strings are fast to parse but slow to compress. Stop compression on // them, so we don't wait for a long time for compression to finish at the // end of compilation. const size_t HUGE_STRING = 50000; - if (sct && sct->active() && kind == PNK_STRING && node->pn_atom->length() >= HUGE_STRING) + if (sct && sct->active() && kind == PNK_STRING && atom->length() >= HUGE_STRING) sct->abort(); - return node; + return pn; } +template <> ParseNode * -Parser::primaryExpr(TokenKind tt) +Parser::newRegExp(const jschar *buf, size_t length, RegExpFlag flags) +{ + ParseNode *pn = NullaryNode::create(PNK_REGEXP, &handler); + if (!pn) + return NULL; + + const StableCharPtr chars(buf, length); + RegExpStatics *res = context->regExpStatics(); + + Rooted reobj(context); + if (context->hasfp()) + reobj = RegExpObject::create(context, res, chars.get(), length, flags, &tokenStream); + else + reobj = RegExpObject::createNoStatics(context, chars.get(), length, flags, &tokenStream); + + if (!reobj) + return NULL; + + if (!compileAndGo) { + if (!JSObject::clearParent(context, reobj)) + return NULL; + if (!JSObject::clearType(context, reobj)) + return NULL; + } + + pn->pn_objbox = newObjectBox(reobj); + if (!pn->pn_objbox) + return NULL; + + pn->setOp(JSOP_REGEXP); + return pn; +} + +template <> +SyntaxParseHandler::Node +Parser::newRegExp(const jschar *buf, size_t length, RegExpFlag flags) +{ + return SyntaxParseHandler::NodeGeneric; +} + +template +typename ParseHandler::Node +Parser::primaryExpr(TokenKind tt) { JS_ASSERT(tokenStream.isCurrentTokenType(tt)); - ParseNode *pn, *pn2, *pn3; + Node pn, pn2, pn3; JSOp op; - JS_CHECK_RECURSION(context, return NULL); + JS_CHECK_RECURSION(context, return null()); switch (tt) { case TOK_FUNCTION: pn = functionExpr(); if (!pn) - return NULL; + return null(); break; case TOK_LB: { - pn = ListNode::create(PNK_ARRAY, this); + pn = handler.newList(PNK_ARRAY, null(), JSOP_NEWINIT); if (!pn) - return NULL; - pn->setOp(JSOP_NEWINIT); - pn->makeEmpty(); - + return null(); #if JS_HAS_GENERATORS - pn->pn_blockid = pc->blockidGen; + handler.setBlockId(pn, pc->blockidGen); #endif + if (tokenStream.matchToken(TOK_RB, TSF_OPERAND)) { /* * Mark empty arrays as non-constant, since we cannot easily * determine their type. */ - pn->pn_xflags |= PNX_NONCONST; + handler.setListFlag(pn, PNX_NONCONST); } else { - bool spread = false; + bool spread = false, missingTrailingComma = false; unsigned index = 0; for (; ; index++) { if (index == StackSpace::ARGS_LENGTH_MAX) { - reportError(NULL, JSMSG_ARRAY_INIT_TOO_BIG); - return NULL; + report(ParseError, false, null(), JSMSG_ARRAY_INIT_TOO_BIG); + return null(); } tt = tokenStream.peekToken(TSF_OPERAND); @@ -5734,44 +6104,46 @@ Parser::primaryExpr(TokenKind tt) if (tt == TOK_COMMA) { /* So CURRENT_TOKEN gets TOK_COMMA and not TOK_LB. */ tokenStream.matchToken(TOK_COMMA); - pn2 = NullaryNode::create(PNK_COMMA, this); - pn->pn_xflags |= PNX_SPECIALARRAYINIT | PNX_NONCONST; + pn2 = handler.newNullary(PNK_COMMA); + if (!pn2) + return null(); + handler.setListFlag(pn, PNX_SPECIALARRAYINIT | PNX_NONCONST); + } else if (tt == TOK_TRIPLEDOT) { + spread = true; + handler.setListFlag(pn, PNX_SPECIALARRAYINIT | PNX_NONCONST); + + tokenStream.getToken(); + + Node inner = assignExpr(); + if (!inner) + return null(); + + pn2 = handler.newUnary(PNK_SPREAD, inner); + if (!pn2) + return null(); } else { - ParseNode *spreadNode = NULL; - if (tt == TOK_TRIPLEDOT) { - spread = true; - spreadNode = UnaryNode::create(PNK_SPREAD, this); - if (!spreadNode) - return NULL; - tokenStream.getToken(); - } pn2 = assignExpr(); - if (pn2) { - if (foldConstants && !FoldConstants(context, &pn2, this)) - return NULL; - if (!pn2->isConstant() || spreadNode) - pn->pn_xflags |= PNX_NONCONST; - if (spreadNode) { - pn->pn_xflags |= PNX_SPECIALARRAYINIT; - spreadNode->pn_kid = pn2; - pn2 = spreadNode; - } - } + if (!pn2) + return null(); + if (foldConstants && !FoldConstants(context, &pn2, this)) + return null(); + if (!handler.isConstant(pn2)) + handler.setListFlag(pn, PNX_NONCONST); } - if (!pn2) - return NULL; - pn->append(pn2); + handler.addList(pn, pn2); if (tt != TOK_COMMA) { /* If we didn't already match TOK_COMMA in above case. */ - if (!tokenStream.matchToken(TOK_COMMA)) + if (!tokenStream.matchToken(TOK_COMMA)) { + missingTrailingComma = true; break; + } } } #if JS_HAS_GENERATORS /* - * At this point, (index == 0 && pn->pn_count != 0) implies one + * At this point, (index == 0 && missingTrailingComma) implies one * element initialiser was parsed. * * An array comprehension of the form: @@ -5811,40 +6183,21 @@ Parser::primaryExpr(TokenKind tt) * the example above, is done by ; JSOP_ARRAYPUSH , * where is the index of array's stack slot. */ - if (index == 0 && !spread && pn->pn_count != 0 && tokenStream.matchToken(TOK_FOR)) { - ParseNode *pnexp, *pntop; - - /* Relabel pn as an array comprehension node. */ - pn->setKind(PNK_ARRAYCOMP); - - /* - * Remove the comprehension expression from pn's linked list - * and save it via pnexp. We'll re-install it underneath the - * ARRAYPUSH node after we parse the rest of the comprehension. - */ - pnexp = pn->last(); - JS_ASSERT(pn->pn_count == 1); - pn->pn_count = 0; - pn->pn_tail = &pn->pn_head; - *pn->pn_tail = NULL; - - pntop = comprehensionTail(pnexp, pn->pn_blockid, false, - PNK_ARRAYPUSH, JSOP_ARRAYPUSH); - if (!pntop) - return NULL; - pn->append(pntop); + if (index == 0 && !spread && tokenStream.matchToken(TOK_FOR) && missingTrailingComma) { + if (!arrayInitializerComprehensionTail(pn)) + return null(); } #endif /* JS_HAS_GENERATORS */ MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_AFTER_LIST); } - pn->pn_pos.end = tokenStream.currentToken().pos.end; + handler.setEndPosition(pn, tokenStream.currentToken().pos.end); return pn; } case TOK_LC: { - ParseNode *pnval; + Node pnval; /* * A map from property names we've seen thus far to a mask of property @@ -5858,25 +6211,19 @@ Parser::primaryExpr(TokenKind tt) VALUE = 0x4 | GET | SET }; - pn = ListNode::create(PNK_OBJECT, this); + pn = handler.newList(PNK_OBJECT, null(), JSOP_NEWINIT); if (!pn) - return NULL; - pn->setOp(JSOP_NEWINIT); - pn->makeEmpty(); + return null(); for (;;) { JSAtom *atom; TokenKind ltok = tokenStream.getToken(TSF_KEYWORD_IS_NAME); - TokenPtr begin = tokenStream.currentToken().pos.begin; switch (ltok) { case TOK_NUMBER: - pn3 = NullaryNode::create(PNK_NUMBER, this); - if (!pn3) - return NULL; - pn3->initNumber(tokenStream.currentToken()); - atom = ToAtom(context, DoubleValue(pn3->pn_dval)); + atom = ToAtom(context, DoubleValue(tokenStream.currentToken().number())); if (!atom) - return NULL; + return null(); + pn3 = handler.newNumber(tokenStream.currentToken()); break; case TOK_NAME: { @@ -5886,54 +6233,51 @@ Parser::primaryExpr(TokenKind tt) } else if (atom == context->names().set) { op = JSOP_SETTER; } else { - pn3 = NullaryNode::create(PNK_NAME, this); + pn3 = handler.newAtom(PNK_NAME, atom); if (!pn3) - return NULL; - pn3->pn_atom = atom; + return null(); break; } tt = tokenStream.getToken(TSF_KEYWORD_IS_NAME); if (tt == TOK_NAME) { atom = tokenStream.currentToken().name(); - pn3 = NameNode::create(PNK_NAME, atom, this, this->pc); + pn3 = handler.newName(atom->asPropertyName(), pc); if (!pn3) - return NULL; + return null(); } else if (tt == TOK_STRING) { atom = tokenStream.currentToken().atom(); uint32_t index; if (atom->isIndex(&index)) { - pn3 = NullaryNode::create(PNK_NUMBER, this); + pn3 = handler.newNumber(index); if (!pn3) - return NULL; - pn3->pn_dval = index; - atom = ToAtom(context, DoubleValue(pn3->pn_dval)); + return null(); + atom = ToAtom(context, DoubleValue(index)); if (!atom) - return NULL; + return null(); } else { - pn3 = NameNode::create(PNK_STRING, atom, this, this->pc); + pn3 = handler.newName(atom->asPropertyName(), pc, PNK_STRING); if (!pn3) - return NULL; + return null(); } } else if (tt == TOK_NUMBER) { - pn3 = NullaryNode::create(PNK_NUMBER, this); - if (!pn3) - return NULL; - pn3->initNumber(tokenStream.currentToken()); - atom = ToAtom(context, DoubleValue(pn3->pn_dval)); + double number = tokenStream.currentToken().number(); + atom = ToAtom(context, DoubleValue(number)); if (!atom) - return NULL; + return null(); + pn3 = handler.newNumber(tokenStream.currentToken()); + if (!pn3) + return null(); } else { tokenStream.ungetToken(); - pn3 = NullaryNode::create(PNK_NAME, this); + pn3 = handler.newAtom(PNK_NAME, atom); if (!pn3) - return NULL; - pn3->pn_atom = atom; + return null(); break; } - pn->pn_xflags |= PNX_NONCONST; + handler.setListFlag(pn, PNX_NONCONST); /* NB: Getter function in { get x(){} } is unnamed. */ Rooted funName(context, NULL); @@ -5942,32 +6286,29 @@ Parser::primaryExpr(TokenKind tt) pn2 = functionDef(funName, start, op == JSOP_GETTER ? Getter : Setter, Expression); if (!pn2) - return NULL; - TokenPos pos = {begin, pn2->pn_pos.end}; - pn2 = new_(PNK_COLON, op, pos, pn3, pn2); + return null(); + pn2 = handler.newBinary(PNK_COLON, pn3, pn2, op); goto skip; } case TOK_STRING: { atom = tokenStream.currentToken().atom(); uint32_t index; if (atom->isIndex(&index)) { - pn3 = NullaryNode::create(PNK_NUMBER, this); + pn3 = handler.newNumber(index); if (!pn3) - return NULL; - pn3->pn_dval = index; + return null(); } else { - pn3 = NullaryNode::create(PNK_STRING, this); + pn3 = handler.newAtom(PNK_STRING, atom); if (!pn3) - return NULL; - pn3->pn_atom = atom; + return null(); } break; } case TOK_RC: goto end_obj_init; default: - reportError(NULL, JSMSG_BAD_PROP_ID); - return NULL; + report(ParseError, false, null(), JSMSG_BAD_PROP_ID); + return null(); } op = JSOP_INITPROP; @@ -5975,18 +6316,18 @@ Parser::primaryExpr(TokenKind tt) if (tt == TOK_COLON) { pnval = assignExpr(); if (!pnval) - return NULL; + return null(); if (foldConstants && !FoldConstants(context, &pnval, this)) - return NULL; + return null(); /* * Treat initializers which mutate __proto__ as non-constant, * so that we can later assume singleton objects delegate to * the default Object.prototype. */ - if (!pnval->isConstant() || atom == context->names().proto) - pn->pn_xflags |= PNX_NONCONST; + if (!handler.isConstant(pnval) || atom == context->names().proto) + handler.setListFlag(pn, PNX_NONCONST); } #if JS_HAS_DESTRUCTURING_SHORTHAND else if (ltok == TOK_NAME && (tt == TOK_COMMA || tt == TOK_RC)) { @@ -5996,27 +6337,26 @@ Parser::primaryExpr(TokenKind tt) */ tokenStream.ungetToken(); if (!tokenStream.checkForKeyword(atom->charsZ(), atom->length(), NULL, NULL)) - return NULL; - pn->pn_xflags |= PNX_DESTRUCT | PNX_NONCONST; + return null(); + handler.setListFlag(pn, PNX_DESTRUCT | PNX_NONCONST); + PropertyName *name = handler.isName(pn3); + JS_ASSERT(atom); + pn3 = handler.newName(name, pc); + if (!pn3) + return null(); pnval = pn3; - JS_ASSERT(pnval->isKind(PNK_NAME)); - pnval->setArity(PN_NAME); - ((NameNode *)pnval)->initCommon(pc); } #endif else { - reportError(NULL, JSMSG_COLON_AFTER_ID); - return NULL; + report(ParseError, false, null(), JSMSG_COLON_AFTER_ID); + return null(); } - { - TokenPos pos = {begin, pnval->pn_pos.end}; - pn2 = new_(PNK_COLON, op, pos, pn3, pnval); - } + pn2 = handler.newBinary(PNK_COLON, pn3, pnval, op); skip: if (!pn2) - return NULL; - pn->append(pn2); + return null(); + handler.addList(pn, pn2); /* * Check for duplicate property names. Duplicate data properties @@ -6045,32 +6385,35 @@ Parser::primaryExpr(TokenKind tt) { JSAutoByteString name; if (!js_AtomToPrintableString(context, atom, &name)) - return NULL; + return null(); - Reporter reporter = + ParseReportKind reportKind = (oldAssignType == VALUE && assignType == VALUE && !pc->sc->needStrictChecks()) - ? &Parser::reportWarning - : (pc->sc->needStrictChecks() ? &Parser::reportStrictModeError : &Parser::reportError); - if (!(this->*reporter)(NULL, JSMSG_DUPLICATE_PROPERTY, name.ptr())) - return NULL; + ? ParseWarning + : (pc->sc->needStrictChecks() ? ParseStrictError : ParseError); + if (!report(reportKind, pc->sc->strict, null(), + JSMSG_DUPLICATE_PROPERTY, name.ptr())) + { + return null(); + } } p.value() = assignType | oldAssignType; } else { if (!seen.add(p, atom, assignType)) - return NULL; + return null(); } tt = tokenStream.getToken(); if (tt == TOK_RC) goto end_obj_init; if (tt != TOK_COMMA) { - reportError(NULL, JSMSG_CURLY_AFTER_LIST); - return NULL; + report(ParseError, false, null(), JSMSG_CURLY_AFTER_LIST); + return null(); } } end_obj_init: - pn->pn_pos.end = tokenStream.currentToken().pos.end; + handler.setEndPosition(pn, tokenStream.currentToken().pos.end); return pn; } @@ -6078,7 +6421,7 @@ Parser::primaryExpr(TokenKind tt) case TOK_LET: pn = letBlock(LetExpresion); if (!pn) - return NULL; + return null(); break; #endif @@ -6088,8 +6431,9 @@ Parser::primaryExpr(TokenKind tt) pn = parenExpr(&genexp); if (!pn) - return NULL; - pn->setInParens(true); + return null(); + pn = handler.setInParens(pn); + if (!genexp) MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_IN_PAREN); break; @@ -6098,7 +6442,7 @@ Parser::primaryExpr(TokenKind tt) case TOK_STRING: pn = atomNode(PNK_STRING, JSOP_STRING); if (!pn) - return NULL; + return null(); break; case TOK_NAME: @@ -6106,114 +6450,85 @@ Parser::primaryExpr(TokenKind tt) break; case TOK_REGEXP: - { - pn = NullaryNode::create(PNK_REGEXP, this); - if (!pn) - return NULL; - - size_t length = tokenStream.getTokenbuf().length(); - const jschar *chars = tokenStream.getTokenbuf().begin(); - RegExpFlag flags = tokenStream.currentToken().regExpFlags(); - RegExpStatics *res = context->regExpStatics(); - - Rooted reobj(context); - if (context->hasfp()) - reobj = RegExpObject::create(context, res, chars, length, flags, &tokenStream); - else - reobj = RegExpObject::createNoStatics(context, chars, length, flags, &tokenStream); - - if (!reobj) - return NULL; - - if (!compileAndGo) { - if (!JSObject::clearParent(context, reobj)) - return NULL; - if (!JSObject::clearType(context, reobj)) - return NULL; - } - - pn->pn_objbox = newObjectBox(reobj); - if (!pn->pn_objbox) - return NULL; - - pn->setOp(JSOP_REGEXP); + pn = newRegExp(tokenStream.getTokenbuf().begin(), + tokenStream.getTokenbuf().length(), + tokenStream.currentToken().regExpFlags()); break; - } case TOK_NUMBER: - pn = NullaryNode::create(PNK_NUMBER, this); - if (!pn) - return NULL; - pn->setOp(JSOP_DOUBLE); - pn->initNumber(tokenStream.currentToken()); + pn = handler.newNumber(tokenStream.currentToken()); break; case TOK_TRUE: - return new_(true, tokenStream.currentToken().pos); + return handler.newBooleanLiteral(true, tokenStream.currentToken().pos); case TOK_FALSE: - return new_(false, tokenStream.currentToken().pos); + return handler.newBooleanLiteral(false, tokenStream.currentToken().pos); case TOK_THIS: - return new_(tokenStream.currentToken().pos); + return handler.newThisLiteral(tokenStream.currentToken().pos); case TOK_NULL: - return new_(tokenStream.currentToken().pos); + return handler.newNullLiteral(tokenStream.currentToken().pos); case TOK_ERROR: /* The scanner or one of its subroutines reported the error. */ - return NULL; + return null(); default: - reportError(NULL, JSMSG_SYNTAX_ERROR); - return NULL; + report(ParseError, false, null(), JSMSG_SYNTAX_ERROR); + return null(); } return pn; } -ParseNode * -Parser::parenExpr(bool *genexp) +template +typename ParseHandler::Node +Parser::parenExpr(bool *genexp) { - TokenPtr begin; - ParseNode *pn; - JS_ASSERT(tokenStream.currentToken().type == TOK_LP); - begin = tokenStream.currentToken().pos.begin; + TokenPtr begin = tokenStream.currentToken().pos.begin; if (genexp) *genexp = false; - GenexpGuard guard(this); + GenexpGuard guard(this); - pn = bracketedExpr(); + Node pn = bracketedExpr(); if (!pn) - return NULL; + return null(); guard.endBody(); #if JS_HAS_GENERATOR_EXPRS if (tokenStream.matchToken(TOK_FOR)) { if (!guard.checkValidBody(pn)) - return NULL; - JS_ASSERT(!pn->isKind(PNK_YIELD)); - if (pn->isKind(PNK_COMMA) && !pn->isInParens()) { - reportError(pn->last(), JSMSG_BAD_GENERATOR_SYNTAX, js_generator_str); - return NULL; + return null(); + if (handler.isOperationWithoutParens(pn, PNK_COMMA)) { + report(ParseError, false, null(), + JSMSG_BAD_GENERATOR_SYNTAX, js_generator_str); + return null(); } pn = generatorExpr(pn); if (!pn) - return NULL; - pn->pn_pos.begin = begin; + return null(); + handler.setBeginPosition(pn, begin); if (genexp) { if (tokenStream.getToken() != TOK_RP) { - reportError(NULL, JSMSG_BAD_GENERATOR_SYNTAX, js_generator_str); - return NULL; + report(ParseError, false, null(), + JSMSG_BAD_GENERATOR_SYNTAX, js_generator_str); + return null(); } - pn->pn_pos.end = tokenStream.currentToken().pos.end; + handler.setEndPosition(pn, tokenStream.currentToken().pos.end); *genexp = true; } } else #endif /* JS_HAS_GENERATOR_EXPRS */ if (!guard.maybeNoteGenerator(pn)) - return NULL; + return null(); return pn; } +template class Parser; +template class Parser; + +} /* namespace frontend */ +} /* namespace js */ diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 078004c87cda..89b1b51a19e6 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -18,9 +18,12 @@ #include "jsscript.h" #include "jswin.h" +#include "frontend/FoldConstants.h" +#include "frontend/FullParseHandler.h" #include "frontend/ParseMaps.h" #include "frontend/ParseNode.h" #include "frontend/SharedContext.h" +#include "frontend/SyntaxParseHandler.h" namespace js { namespace frontend { @@ -35,7 +38,6 @@ struct StmtInfoPC : public StmtInfoBase { }; typedef HashSet FuncStmtSet; -struct Parser; class SharedContext; typedef Vector DeclVector; @@ -48,9 +50,11 @@ typedef Vector DeclVector; * ParseContext, makes it the new current context, and sets its parent to the * context in which it encountered the definition. */ +template struct ParseContext /* tree context for semantic checks */ { typedef StmtInfoPC StmtInfo; + typedef typename ParseHandler::Node Node; SharedContext *sc; /* context shared between parsing and bytecode generation */ @@ -68,7 +72,7 @@ struct ParseContext /* tree context for semantic checks */ to be generator expressions */ uint32_t yieldCount; /* number of |yield| tokens encountered at non-zero depth in current paren tree */ - ParseNode *blockNode; /* parse node for a block with let declarations + Node blockNode; /* parse node for a block with let declarations (block with its own lexical scope) */ private: AtomDecls decls_; /* function, const, and var declarations */ @@ -116,7 +120,7 @@ struct ParseContext /* tree context for semantic checks */ * 'pn' if they are in the scope of 'pn'. * + Pre-existing placeholders in the scope of 'pn' have been removed. */ - bool define(JSContext *cx, HandlePropertyName name, ParseNode *pn, Definition::Kind); + bool define(JSContext *cx, HandlePropertyName name, Node pn, Definition::Kind); /* * Let definitions may shadow same-named definitions in enclosing scopes. @@ -128,11 +132,11 @@ struct ParseContext /* tree context for semantic checks */ */ void popLetDecl(JSAtom *atom); - /* See the sad story in DefineArg. */ + /* See the sad story in defineArg. */ void prepareToAddDuplicateArg(Definition *prevDecl); /* See the sad story in MakeDefIntoUse. */ - void updateDecl(JSAtom *atom, ParseNode *newDecl); + void updateDecl(JSAtom *atom, Node newDecl); /* * After a function body has been parsed, the parser generates the @@ -151,7 +155,7 @@ struct ParseContext /* tree context for semantic checks */ bool generateFunctionBindings(JSContext *cx, InternalHandle bindings) const; public: - ParseNode *yieldNode; /* parse node for a yield expression that might + Node yieldNode; /* parse node for a yield expression that might be an error if we turn out to be inside a generator expression */ @@ -198,7 +202,7 @@ struct ParseContext /* tree context for semantic checks */ // strict before. bool funBecameStrict:1; - inline ParseContext(Parser *prs, SharedContext *sc, unsigned staticLevel, uint32_t bodyid); + inline ParseContext(Parser *prs, SharedContext *sc, unsigned staticLevel, uint32_t bodyid); inline ~ParseContext(); inline bool init(); @@ -215,25 +219,36 @@ struct ParseContext /* tree context for semantic checks */ bool atBodyLevel(); }; +template bool -GenerateBlockId(ParseContext *pc, uint32_t &blockid); +GenerateBlockId(ParseContext *pc, uint32_t &blockid); +template struct BindData; -enum FunctionSyntaxKind { Expression, Statement }; +class CompExprTransplanter; + +template +class GenexpGuard; + +// XXX fix this backdoor. +bool EmitElemOp(JSContext *cx, ParseNode *pn, JSOp op, BytecodeEmitter *bce); + enum LetContext { LetExpresion, LetStatement }; enum VarContext { HoistVars, DontHoistVars }; -struct Parser : private AutoGCRooter +template +struct Parser : private AutoGCRooter, public StrictModeGetter { JSContext *const context; /* FIXME Bug 551291: use AutoGCRooter::context? */ - StrictModeGetter strictModeGetter; /* used by tokenStream to test for strict mode */ TokenStream tokenStream; void *tempPoolMark; /* initial JSContext.tempLifoAlloc mark */ - ParseNodeAllocator allocator; - ObjectBox *traceListHead; /* list of parsed object for GC tracing */ - ParseContext *pc; /* innermost parse context (stack-allocated) */ + /* list of parsed objects for GC tracing */ + ObjectBox *traceListHead; + + /* innermost parse context (stack-allocated) */ + ParseContext *pc; SourceCompressionToken *sct; /* compression token for aborting */ @@ -263,7 +278,22 @@ struct Parser : private AutoGCRooter */ const bool selfHostingMode:1; + /* + * Not all language constructs can be handled during syntax parsing. If it + * is not known whether the parse succeeds or fails, this bit is set and + * the parse will return false. + */ + bool unknownResult; + + typedef typename ParseHandler::Node Node; + typedef typename ParseHandler::DefinitionNode DefinitionNode; + public: + /* State specific to the kind of parse being performed. */ + ParseHandler handler; + + bool report(ParseReportKind kind, bool strict, Node pn, unsigned errorNumber, ...); + Parser(JSContext *cx, const CompileOptions &options, const jschar *chars, size_t length, bool foldConstants); ~Parser(); @@ -284,78 +314,63 @@ struct Parser : private AutoGCRooter /* * Parse a top-level JS script. */ - ParseNode *parse(JSObject *chain); + Node parse(JSObject *chain); /* * Allocate a new parsed object or function container from * cx->tempLifoAlloc. */ ObjectBox *newObjectBox(JSObject *obj); - ModuleBox *newModuleBox(Module *module, ParseContext *pc); - FunctionBox *newFunctionBox(JSFunction *fun, ParseContext *pc, bool strict); + ModuleBox *newModuleBox(Module *module, ParseContext *pc); + FunctionBox *newFunctionBox(JSFunction *fun, ParseContext *pc, bool strict); /* * Create a new function object given parse context (pc) and a name (which * is optional if this is a function expression). */ - JSFunction *newFunction(ParseContext *pc, HandleAtom atom, FunctionSyntaxKind kind); + JSFunction *newFunction(ParseContext *pc, HandleAtom atom, FunctionSyntaxKind kind); void trace(JSTracer *trc); - /* - * Report a parse (compile) error. - */ - inline bool reportError(ParseNode *pn, unsigned errorNumber, ...); - inline bool reportUcError(ParseNode *pn, unsigned errorNumber, ...); - inline bool reportWarning(ParseNode *pn, unsigned errorNumber, ...); - inline bool reportStrictWarning(ParseNode *pn, unsigned errorNumber, ...); - inline bool reportStrictModeError(ParseNode *pn, unsigned errorNumber, ...); - typedef bool (Parser::*Reporter)(ParseNode *pn, unsigned errorNumber, ...); + bool hadUnknownResult() { + return unknownResult; + } private: Parser *thisForCtor() { return this; } - ParseNode *allocParseNode(size_t size) { - JS_ASSERT(size == sizeof(ParseNode)); - return static_cast(allocator.allocNode()); - } - /* * Create a parse node with the given kind and op using the current token's * atom. */ - ParseNode *atomNode(ParseNodeKind kind, JSOp op); + Node atomNode(ParseNodeKind kind, JSOp op); - public: - ParseNode *freeTree(ParseNode *pn) { return allocator.freeTree(pn); } - void prepareNodeForMutation(ParseNode *pn) { return allocator.prepareNodeForMutation(pn); } - - /* new_ methods for creating parse nodes. These report OOM on context. */ - JS_DECLARE_NEW_METHODS(new_, allocParseNode, inline) - - ParseNode *cloneNode(const ParseNode &other) { - ParseNode *node = allocParseNode(sizeof(ParseNode)); - if (!node) - return NULL; - PodAssign(node, &other); - return node; + void setUnknownResult() { + unknownResult = true; } + public: + /* Public entry points for parsing. */ - ParseNode *statement(); - bool maybeParseDirective(ParseNode *pn, bool *cont); + Node statement(); + bool maybeParseDirective(Node pn, bool *cont); // Parse a function, given only its body. Used for the Function constructor. - ParseNode *standaloneFunctionBody(HandleFunction fun, const AutoNameVector &formals, HandleScript script, - ParseNode *fn, FunctionBox **funbox, bool strict, - bool *becameStrict = NULL); + Node standaloneFunctionBody(HandleFunction fun, const AutoNameVector &formals, HandleScript script, + Node fn, FunctionBox **funbox, bool strict, + bool *becameStrict = NULL); /* * Parse a function body. Pass StatementListBody if the body is a list of * statements; pass ExpressionBody if the body is a single expression. */ enum FunctionBodyType { StatementListBody, ExpressionBody }; - ParseNode *functionBody(FunctionBodyType type); + Node functionBody(FunctionBodyType type); + + virtual bool strictMode() + { + return pc->sc->strict; + } private: /* @@ -374,74 +389,75 @@ struct Parser : private AutoGCRooter * Some parsers have two versions: an always-inlined version (with an 'i' * suffix) and a never-inlined version (with an 'n' suffix). */ - ParseNode *moduleDecl(); - ParseNode *functionStmt(); - ParseNode *functionExpr(); - ParseNode *statements(bool *hasFunctionStmt = NULL); + Node moduleDecl(); + Node functionStmt(); + Node functionExpr(); + Node statements(bool *hasFunctionStmt = NULL); - ParseNode *switchStatement(); - ParseNode *forStatement(); - ParseNode *tryStatement(); - ParseNode *withStatement(); + Node switchStatement(); + Node forStatement(); + Node tryStatement(); + Node withStatement(); #if JS_HAS_BLOCK_SCOPE - ParseNode *letStatement(); + Node letStatement(); #endif - ParseNode *expressionStatement(); - ParseNode *variables(ParseNodeKind kind, StaticBlockObject *blockObj = NULL, - VarContext varContext = HoistVars); - ParseNode *expr(); - ParseNode *assignExpr(); - ParseNode *assignExprWithoutYield(unsigned err); - ParseNode *condExpr1(); - ParseNode *orExpr1(); - ParseNode *andExpr1i(); - ParseNode *andExpr1n(); - ParseNode *bitOrExpr1i(); - ParseNode *bitOrExpr1n(); - ParseNode *bitXorExpr1i(); - ParseNode *bitXorExpr1n(); - ParseNode *bitAndExpr1i(); - ParseNode *bitAndExpr1n(); - ParseNode *eqExpr1i(); - ParseNode *eqExpr1n(); - ParseNode *relExpr1i(); - ParseNode *relExpr1n(); - ParseNode *shiftExpr1i(); - ParseNode *shiftExpr1n(); - ParseNode *addExpr1i(); - ParseNode *addExpr1n(); - ParseNode *mulExpr1i(); - ParseNode *mulExpr1n(); - ParseNode *unaryExpr(); - ParseNode *memberExpr(bool allowCallSyntax); - ParseNode *primaryExpr(TokenKind tt); - ParseNode *parenExpr(bool *genexp = NULL); + Node expressionStatement(); + Node variables(ParseNodeKind kind, StaticBlockObject *blockObj = NULL, + VarContext varContext = HoistVars); + Node expr(); + Node assignExpr(); + Node assignExprWithoutYield(unsigned err); + Node condExpr1(); + Node orExpr1(); + Node andExpr1i(); + Node andExpr1n(); + Node bitOrExpr1i(); + Node bitOrExpr1n(); + Node bitXorExpr1i(); + Node bitXorExpr1n(); + Node bitAndExpr1i(); + Node bitAndExpr1n(); + Node eqExpr1i(); + Node eqExpr1n(); + Node relExpr1i(); + Node relExpr1n(); + Node shiftExpr1i(); + Node shiftExpr1n(); + Node addExpr1i(); + Node addExpr1n(); + Node mulExpr1i(); + Node mulExpr1n(); + Node unaryExpr(); + Node memberExpr(bool allowCallSyntax); + Node primaryExpr(TokenKind tt); + Node parenExpr(bool *genexp = NULL); /* * Additional JS parsers. */ enum FunctionType { Getter, Setter, Normal }; - bool functionArguments(ParseNode **list, ParseNode *funcpn, bool &hasRest); + bool functionArguments(Node *list, Node funcpn, bool &hasRest); - ParseNode *functionDef(HandlePropertyName name, const TokenStream::Position &start, - FunctionType type, FunctionSyntaxKind kind); - bool functionArgsAndBody(ParseNode *pn, HandleFunction fun, HandlePropertyName funName, + Node functionDef(HandlePropertyName name, const TokenStream::Position &start, + FunctionType type, FunctionSyntaxKind kind); + bool functionArgsAndBody(Node pn, HandleFunction fun, HandlePropertyName funName, FunctionType type, FunctionSyntaxKind kind, bool strict, bool *becameStrict = NULL); - ParseNode *unaryOpExpr(ParseNodeKind kind, JSOp op); + Node unaryOpExpr(ParseNodeKind kind, JSOp op); - ParseNode *condition(); - ParseNode *comprehensionTail(ParseNode *kid, unsigned blockid, bool isGenexp, - ParseNodeKind kind = PNK_SEMI, JSOp op = JSOP_NOP); - ParseNode *generatorExpr(ParseNode *kid); - bool argumentList(ParseNode *listNode); - ParseNode *bracketedExpr(); - ParseNode *letBlock(LetContext letContext); - ParseNode *returnOrYield(bool useAssignExpr); - ParseNode *destructuringExpr(BindData *data, TokenKind tt); + Node condition(); + Node comprehensionTail(Node kid, unsigned blockid, bool isGenexp, + ParseNodeKind kind = PNK_SEMI, JSOp op = JSOP_NOP); + bool arrayInitializerComprehensionTail(Node pn); + Node generatorExpr(Node kid); + bool argumentList(Node listNode); + Node bracketedExpr(); + Node letBlock(LetContext letContext); + Node returnOrYield(bool useAssignExpr); + Node destructuringExpr(BindData *data, TokenKind tt); - ParseNode *identifierName(); + Node identifierName(); bool allowsForEachIn() { #if !JS_HAS_FOR_EACH_IN @@ -451,65 +467,73 @@ struct Parser : private AutoGCRooter #endif } - bool setAssignmentLhsOps(ParseNode *pn, JSOp op); + bool setAssignmentLhsOps(Node pn, JSOp op); bool matchInOrOf(bool *isForOfp); + + void addStatementToList(Node pn, Node kid, bool *hasFunctionStmt); + bool checkFunctionArguments(); + bool makeDefIntoUse(Definition *dn, Node pn, JSAtom *atom); + bool checkFunctionDefinition(HandlePropertyName funName, Node *pn, FunctionSyntaxKind kind); + bool finishFunctionDefinition(Node pn, FunctionBox *funbox, + Node prelude, Node body, + ParseContext *outerpc); + + bool isValidForStatementLHS(Node pn1, JSVersion version, + bool forDecl, bool forEach, bool forOf); + bool setLvalKid(Node pn, Node kid, const char *name); + bool setIncOpKid(Node pn, Node kid, TokenKind tt, bool preorder); + bool checkStrictAssignment(Node lhs); + bool checkStrictBinding(HandlePropertyName name, Node pn); + bool checkDeleteExpression(Node *pn); + bool defineArg(Node funcpn, HandlePropertyName name, + bool disallowDuplicateArgs = false, DefinitionNode *duplicatedArg = NULL); + Node pushLexicalScope(StmtInfoPC *stmt); + Node pushLexicalScope(Handle blockObj, StmtInfoPC *stmt); + Node pushLetScope(Handle blockObj, StmtInfoPC *stmt); + bool noteNameUse(Node pn); + Node newRegExp(const jschar *chars, size_t length, RegExpFlag flags); + Node newBindingNode(PropertyName *name, VarContext varContext = HoistVars); + bool checkDestructuring(BindData *data, Node left, bool toplevel = true); + bool bindDestructuringVar(BindData *data, Node pn); + bool bindDestructuringLHS(Node pn); + bool makeSetCall(Node pn, unsigned msg); + PropertyName *foldPropertyByValue(Node pn); + Node cloneLeftHandSide(Node opn); + Node cloneParseTree(Node opn); + + static bool + bindDestructuringArg(JSContext *cx, BindData *data, + HandlePropertyName name, Parser *parser); + + static bool + bindLet(JSContext *cx, BindData *data, + HandlePropertyName name, Parser *parser); + + static bool + bindVarOrConst(JSContext *cx, BindData *data, + HandlePropertyName name, Parser *parser); + + static DefinitionNode null() { return ParseHandler::null(); } + + bool reportRedeclaration(Node pn, bool isConst, JSAtom *atom); + bool reportBadReturn(Node pn, ParseReportKind kind, unsigned errnum, unsigned anonerrnum); + bool checkFinalReturn(Node pn); + + bool leaveFunction(Node fn, HandlePropertyName funName, + FunctionSyntaxKind kind = Expression); + + friend class CompExprTransplanter; + friend class GenexpGuard; + friend struct BindData; }; -inline bool -Parser::reportError(ParseNode *pn, unsigned errorNumber, ...) -{ - va_list args; - va_start(args, errorNumber); - bool result = tokenStream.reportCompileErrorNumberVA(pn, JSREPORT_ERROR, errorNumber, args); - va_end(args); - return result; -} - -inline bool -Parser::reportUcError(ParseNode *pn, unsigned errorNumber, ...) -{ - va_list args; - va_start(args, errorNumber); - bool result = tokenStream.reportCompileErrorNumberVA(pn, JSREPORT_UC | JSREPORT_ERROR, - errorNumber, args); - va_end(args); - return result; -} - -inline bool -Parser::reportWarning(ParseNode *pn, unsigned errorNumber, ...) -{ - va_list args; - va_start(args, errorNumber); - bool result = tokenStream.reportCompileErrorNumberVA(pn, JSREPORT_WARNING, errorNumber, args); - va_end(args); - return result; -} - -inline bool -Parser::reportStrictWarning(ParseNode *pn, unsigned errorNumber, ...) -{ - va_list args; - va_start(args, errorNumber); - bool result = tokenStream.reportStrictWarningErrorNumberVA(pn, errorNumber, args); - va_end(args); - return result; -} - -inline bool -Parser::reportStrictModeError(ParseNode *pn, unsigned errorNumber, ...) -{ - va_list args; - va_start(args, errorNumber); - bool result = - tokenStream.reportStrictModeErrorNumberVA(pn, pc->sc->strict, errorNumber, args); - va_end(args); - return result; -} +template <> +ParseNode * +Parser::expr(); +template <> bool -DefineArg(Parser *parser, ParseNode *funcpn, HandlePropertyName name, - bool disallowDuplicateArgs = false, Definition **duplicatedArg = NULL); +Parser::setAssignmentLhsOps(ParseNode *pn, JSOp op); } /* namespace frontend */ } /* namespace js */ diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h index fa72fb81e804..d7f4ae714393 100644 --- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -189,7 +189,8 @@ class ModuleBox : public ObjectBox, public SharedContext { public: Bindings bindings; - ModuleBox(JSContext *cx, ObjectBox *traceListHead, Module *module, ParseContext *pc); + ModuleBox(JSContext *cx, ObjectBox *traceListHead, Module *module, + ParseContext *pc); ObjectBox *toObjectBox() { return this; } Module *module() const { return &object->asModule(); } }; @@ -206,7 +207,8 @@ class FunctionBox : public ObjectBox, public SharedContext FunctionContextFlags funCxFlags; - FunctionBox(JSContext *cx, ObjectBox* traceListHead, JSFunction *fun, ParseContext *pc, + template + FunctionBox(JSContext *cx, ObjectBox* traceListHead, JSFunction *fun, ParseContext *pc, bool strict); ObjectBox *toObjectBox() { return this; } diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h new file mode 100644 index 000000000000..ab70bba7ecdd --- /dev/null +++ b/js/src/frontend/SyntaxParseHandler.h @@ -0,0 +1,160 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et tw=78: + * + * 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 SyntaxParseHandler_h__ +#define SyntaxParseHandler_h__ + +namespace js { +namespace frontend { + +class SyntaxParseHandler +{ + /* Remember the last encountered name or string literal during syntax parses. */ + JSAtom *lastAtom; + TokenPos lastStringPos; + TokenStream &tokenStream; + + public: + enum Node { + NodeFailure = 0, + NodeGeneric, + NodeName, + NodeString, + NodeStringExprStatement, + NodeLValue + }; + typedef Node DefinitionNode; + + SyntaxParseHandler(JSContext *cx, TokenStream &tokenStream, bool foldConstants) + : lastAtom(NULL), + tokenStream(tokenStream) + {} + + static Node null() { return NodeFailure; } + + void trace(JSTracer *trc) {} + + Node newName(PropertyName *name, ParseContext *pc, + ParseNodeKind kind = PNK_NAME) { + lastAtom = name; + return NodeName; + } + Node newAtom(ParseNodeKind kind, JSAtom *atom, JSOp op = JSOP_NOP) { + if (kind == PNK_STRING) { + lastAtom = atom; + lastStringPos = tokenStream.currentToken().pos; + } + return NodeString; + } + Node newNumber(double value, DecimalPoint decimalPoint = NoDecimal) { return NodeGeneric; } + Node newNumber(const Token &tok) { return NodeGeneric; } + Node newBooleanLiteral(bool cond, const TokenPos &pos) { return NodeGeneric; } + Node newThisLiteral(const TokenPos &pos) { return NodeGeneric; } + Node newNullLiteral(const TokenPos &pos) { return NodeGeneric; } + Node newConditional(Node cond, Node thenExpr, Node elseExpr) { return NodeGeneric; } + + Node newNullary(ParseNodeKind kind) { return NodeGeneric; } + + Node newUnary(ParseNodeKind kind, Node kid, JSOp op = JSOP_NOP) { + if (kind == PNK_SEMI && kid == NodeString) + return NodeStringExprStatement; + return NodeGeneric; + } + Node newUnary(ParseNodeKind kind, JSOp op = JSOP_NOP) { return NodeGeneric; } + void setUnaryKid(Node pn, Node kid) {} + + Node newBinary(ParseNodeKind kind, JSOp op = JSOP_NOP) { return NodeGeneric; } + Node newBinary(ParseNodeKind kind, Node left, JSOp op = JSOP_NOP) { return NodeGeneric; } + Node newBinary(ParseNodeKind kind, Node left, Node right, JSOp op = JSOP_NOP) { + return NodeGeneric; + } + Node newBinaryOrAppend(ParseNodeKind kind, Node left, Node right, JSOp op = JSOP_NOP) { + return NodeGeneric; + } + void setBinaryRHS(Node pn, Node rhs) {} + + Node newTernary(ParseNodeKind kind, Node first, Node second, Node third, JSOp op = JSOP_NOP) { + return NodeGeneric; + } + + Node newBreak(PropertyName *label, const TokenPtr &begin, const TokenPtr &end) { + return NodeGeneric; + } + Node newContinue(PropertyName *label, const TokenPtr &begin, const TokenPtr &end) { + return NodeGeneric; + } + Node newDebuggerStatement(const TokenPos &pos) { return NodeGeneric; } + Node newPropertyAccess(Node pn, PropertyName *name, const TokenPtr &end) { return NodeLValue; } + Node newPropertyByValue(Node pn, Node kid, const TokenPtr &end) { return NodeLValue; } + + bool addCatchBlock(Node catchList, Node letBlock, + Node catchName, Node catchGuard, Node catchBody) { return true; } + + void morphNameIntoLabel(Node name, Node statement) {} + void setLeaveBlockResult(Node block, Node kid, bool leaveBlockExpr) {} + + void setLastFunctionArgumentDefault(Node funcpn, Node pn) {} + Node newFunctionDefinition() { return NodeGeneric; } + void setFunctionBody(Node pn, Node kid) {} + void setFunctionBox(Node pn, FunctionBox *funbox) {} + bool isOperationWithoutParens(Node pn, ParseNodeKind kind) { + // It is OK to return false here, callers should only use this method + // for reporting strict option warnings and parsing code which the + // syntax parser does not handle. + return false; + } + + void noteLValue(Node pn) {} + bool finishInitializerAssignment(Node pn, Node init, JSOp op) { return true; } + + void setBeginPosition(Node pn, Node oth) {} + void setBeginPosition(Node pn, const TokenPtr &begin) {} + + void setEndPosition(Node pn, Node oth) {} + void setEndPosition(Node pn, const TokenPtr &end) {} + + TokenPos getPosition(Node pn) { + return tokenStream.currentToken().pos; + } + + Node newList(ParseNodeKind kind, Node kid = NodeGeneric, JSOp op = JSOP_NOP) { + return NodeGeneric; + } + void addList(Node pn, Node kid) {} + + void setOp(Node pn, JSOp op) {} + void setBlockId(Node pn, unsigned blockid) {} + void setFlag(Node pn, unsigned flag) {} + void setListFlag(Node pn, unsigned flag) {} + Node setInParens(Node pn) { + // String literals enclosed by parentheses are ignored during + // strict mode parsing. + return NodeGeneric; + } + void setPrologue(Node pn) {} + + bool isConstant(Node pn) { return false; } + PropertyName *isName(Node pn) { + return (pn == NodeName) ? lastAtom->asPropertyName() : NULL; + } + PropertyName *isGetProp(Node pn) { return NULL; } + JSAtom *isStringExprStatement(Node pn, TokenPos *pos) { + if (pn == NodeStringExprStatement) { + *pos = lastStringPos; + return lastAtom; + } + return NULL; + } + bool isEmptySemicolon(Node pn) { return false; } + + Node makeAssignment(Node pn, Node rhs) { return NodeGeneric; } +}; + +} // namespace frontend +} // namespace js + +#endif /* SyntaxParseHandler_h__ */ diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index 8286d7d66889..99cae273ba98 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -421,7 +421,7 @@ TokenStream::positionAfterLastFunctionKeyword(Position &pos) } bool -TokenStream::reportStrictModeErrorNumberVA(ParseNode *pn, bool strictMode, unsigned errorNumber, +TokenStream::reportStrictModeErrorNumberVA(const TokenPos &pos, bool strictMode, unsigned errorNumber, va_list args) { /* In strict mode code, this is an error, not merely a warning. */ @@ -433,7 +433,7 @@ TokenStream::reportStrictModeErrorNumberVA(ParseNode *pn, bool strictMode, unsig else return true; - return reportCompileErrorNumberVA(pn, flags, errorNumber, args); + return reportCompileErrorNumberVA(pos, flags, errorNumber, args); } void @@ -489,7 +489,7 @@ CompileError::~CompileError() } bool -TokenStream::reportCompileErrorNumberVA(ParseNode *pn, unsigned flags, unsigned errorNumber, +TokenStream::reportCompileErrorNumberVA(const TokenPos &pos, unsigned flags, unsigned errorNumber, va_list args) { bool warning = JSREPORT_IS_WARNING(flags); @@ -501,13 +501,11 @@ TokenStream::reportCompileErrorNumberVA(ParseNode *pn, unsigned flags, unsigned CompileError err(cx); - const TokenPos *const tp = pn ? &pn->pn_pos : ¤tToken().pos; - err.report.flags = flags; err.report.errorNumber = errorNumber; err.report.filename = filename; err.report.originPrincipals = originPrincipals; - err.report.lineno = tp->begin.lineno; + err.report.lineno = pos.begin.lineno; err.argumentsType = (flags & JSREPORT_UC) ? ArgumentsAreUnicode : ArgumentsAreASCII; @@ -528,7 +526,7 @@ TokenStream::reportCompileErrorNumberVA(ParseNode *pn, unsigned flags, unsigned * multi-line string literal) won't have a context printed. */ if (err.report.lineno == lineno) { - const jschar *tokptr = linebase + tp->begin.index; + const jschar *tokptr = linebase + pos.begin.index; // We show only a portion (a "window") of the line around the erroneous // token -- the first char in the token, plus |windowRadius| chars @@ -542,7 +540,7 @@ TokenStream::reportCompileErrorNumberVA(ParseNode *pn, unsigned flags, unsigned ? tokptr - windowRadius : linebase; size_t nTrunc = windowBase - linebase; - uint32_t windowIndex = tp->begin.index - nTrunc; + uint32_t windowIndex = pos.begin.index - nTrunc; // Find EOL, or truncate at the back if necessary. const jschar *windowLimit = userbuf.findEOLMax(tokptr, windowRadius); @@ -565,7 +563,7 @@ TokenStream::reportCompileErrorNumberVA(ParseNode *pn, unsigned flags, unsigned return false; // The lineno check above means we should only see single-line tokens here. - JS_ASSERT(tp->begin.lineno == tp->end.lineno); + JS_ASSERT(pos.begin.lineno == pos.end.lineno); err.report.tokenptr = err.report.linebuf + windowIndex; err.report.uctokenptr = err.report.uclinebuf + windowIndex; } @@ -580,7 +578,7 @@ TokenStream::reportStrictModeError(unsigned errorNumber, ...) { va_list args; va_start(args, errorNumber); - bool result = reportStrictModeErrorNumberVA(NULL, strictMode(), errorNumber, args); + bool result = reportStrictModeErrorNumberVA(currentToken().pos, strictMode(), errorNumber, args); va_end(args); return result; } @@ -590,7 +588,7 @@ TokenStream::reportError(unsigned errorNumber, ...) { va_list args; va_start(args, errorNumber); - bool result = reportCompileErrorNumberVA(NULL, JSREPORT_ERROR, errorNumber, args); + bool result = reportCompileErrorNumberVA(currentToken().pos, JSREPORT_ERROR, errorNumber, args); va_end(args); return result; } @@ -600,18 +598,18 @@ TokenStream::reportWarning(unsigned errorNumber, ...) { va_list args; va_start(args, errorNumber); - bool result = reportCompileErrorNumberVA(NULL, JSREPORT_WARNING, errorNumber, args); + bool result = reportCompileErrorNumberVA(currentToken().pos, JSREPORT_WARNING, errorNumber, args); va_end(args); return result; } bool -TokenStream::reportStrictWarningErrorNumberVA(ParseNode *pn, unsigned errorNumber, va_list args) +TokenStream::reportStrictWarningErrorNumberVA(const TokenPos &pos, unsigned errorNumber, va_list args) { if (!cx->hasStrictOption()) return true; - return reportCompileErrorNumberVA(NULL, JSREPORT_STRICT | JSREPORT_WARNING, errorNumber, args); + return reportCompileErrorNumberVA(pos, JSREPORT_STRICT | JSREPORT_WARNING, errorNumber, args); } /* diff --git a/js/src/frontend/TokenStream.h b/js/src/frontend/TokenStream.h index 89608c922392..c45aab3334f4 100644 --- a/js/src/frontend/TokenStream.h +++ b/js/src/frontend/TokenStream.h @@ -379,8 +379,6 @@ enum TokenStreamFlags TSF_IN_HTML_COMMENT = 0x2000 }; -struct Parser; - struct CompileError { JSContext *cx; JSErrorReport report; @@ -406,15 +404,11 @@ StrictModeFromContext(JSContext *cx) // TokenStream needs to see it. // // This class is a tiny back-channel from TokenStream to the strict mode flag -// that avoids exposing the rest of SharedContext to TokenStream. get() -// returns the current strictness. +// that avoids exposing the rest of SharedContext to TokenStream. // class StrictModeGetter { - Parser *parser; public: - StrictModeGetter(Parser *p) : parser(p) { } - - bool get() const; + virtual bool strictMode() = 0; }; class TokenStream @@ -489,17 +483,18 @@ class TokenStream // General-purpose error reporters. You should avoid calling these // directly, and instead use the more succinct alternatives (e.g. // reportError()) in TokenStream, Parser, and BytecodeEmitter. - bool reportCompileErrorNumberVA(ParseNode *pn, unsigned flags, unsigned errorNumber, + bool reportCompileErrorNumberVA(const TokenPos &pos, unsigned flags, unsigned errorNumber, va_list args); - bool reportStrictModeErrorNumberVA(ParseNode *pn, bool strictMode, unsigned errorNumber, + bool reportStrictModeErrorNumberVA(const TokenPos &pos, bool strictMode, unsigned errorNumber, va_list args); - bool reportStrictWarningErrorNumberVA(ParseNode *pn, unsigned errorNumber, va_list args); + bool reportStrictWarningErrorNumberVA(const TokenPos &pos, unsigned errorNumber, + va_list args); private: // These are private because they should only be called by the tokenizer // while tokenizing not by, for example, BytecodeEmitter. bool reportStrictModeError(unsigned errorNumber, ...); - bool strictMode() const { return strictModeGetter && strictModeGetter->get(); } + bool strictMode() const { return strictModeGetter && strictModeGetter->strictMode(); } void onError(); static JSAtom *atomize(JSContext *cx, CharBuffer &cb); diff --git a/js/src/gc/RootMarking.cpp b/js/src/gc/RootMarking.cpp index 5f08d74eb8b7..30e3160c0479 100644 --- a/js/src/gc/RootMarking.cpp +++ b/js/src/gc/RootMarking.cpp @@ -385,7 +385,7 @@ AutoGCRooter::trace(JSTracer *trc) return; case PARSER: - static_cast(this)->trace(trc); + static_cast *>(this)->trace(trc); return; case IDARRAY: { diff --git a/js/src/ion/CodeGenerator.cpp b/js/src/ion/CodeGenerator.cpp index f3db92fa9a93..a91578e06aad 100644 --- a/js/src/ion/CodeGenerator.cpp +++ b/js/src/ion/CodeGenerator.cpp @@ -4423,8 +4423,6 @@ CodeGenerator::link() if (executionMode == ParallelExecution) ionScript->zeroParallelInvalidatedScripts(); - linkAbsoluteLabels(); - // The correct state for prebarriers is unknown until the end of compilation, // since a GC can occur during code generation. All barriers are emitted // off-by-default, and are toggled on here if necessary. diff --git a/js/src/ion/EdgeCaseAnalysis.cpp b/js/src/ion/EdgeCaseAnalysis.cpp index 25bfe1aa0732..18a7547581e9 100644 --- a/js/src/ion/EdgeCaseAnalysis.cpp +++ b/js/src/ion/EdgeCaseAnalysis.cpp @@ -46,43 +46,3 @@ EdgeCaseAnalysis::analyzeLate() return true; } - -int -EdgeCaseAnalysis::AllUsesTruncate(MInstruction *m) -{ - // If all uses truncate, the return value must be at least 1. If anything doesn't truncate - // 0 is explicitly returned. - int ret = 1; - for (MUseIterator use = m->usesBegin(); use != m->usesEnd(); use++) { - // See #809485 why this is allowed - if (use->consumer()->isResumePoint()) - continue; - - MDefinition *def = use->consumer()->toDefinition(); - if (def->isTruncateToInt32()) - continue; - if (def->isBitAnd()) - continue; - if (def->isBitOr()) - continue; - if (def->isBitXor()) - continue; - if (def->isLsh()) - continue; - if (def->isRsh()) - continue; - if (def->isBitNot()) - continue; - if (def->isAdd() && def->toAdd()->isTruncated()) { - ret = Max(ret, def->toAdd()->isTruncated() + 1); - continue; - } - if (def->isSub() && def->toSub()->isTruncated()) { - ret = Max(ret, def->toSub()->isTruncated() + 1); - continue; - } - // cannot use divide, since |truncate(int32(x/y) + int32(a/b)) != truncate(x/y+a/b)| - return 0; - } - return ret; -} diff --git a/js/src/ion/EdgeCaseAnalysis.h b/js/src/ion/EdgeCaseAnalysis.h index 6ffe465d86de..bb744dd40eab 100644 --- a/js/src/ion/EdgeCaseAnalysis.h +++ b/js/src/ion/EdgeCaseAnalysis.h @@ -21,7 +21,6 @@ class EdgeCaseAnalysis public: EdgeCaseAnalysis(MIRGenerator *mir, MIRGraph &graph); bool analyzeLate(); - static int AllUsesTruncate(MInstruction *m); }; diff --git a/js/src/ion/Ion.cpp b/js/src/ion/Ion.cpp index 8e0034941f95..0014c154bd63 100644 --- a/js/src/ion/Ion.cpp +++ b/js/src/ion/Ion.cpp @@ -290,7 +290,7 @@ IonCompartment::getVMWrapper(const VMFunction &f) JS_ASSERT(rt->functionWrappers_); JS_ASSERT(rt->functionWrappers_->initialized()); - IonRuntime::VMWrapperMap::Ptr p = rt->functionWrappers_->lookup(&f); + IonRuntime::VMWrapperMap::Ptr p = rt->functionWrappers_->readonlyThreadsafeLookup(&f); JS_ASSERT(p); return p->value; @@ -938,6 +938,14 @@ OptimizeMIR(MIRGenerator *mir) if (mir->shouldCancel("RA De-Beta")) return false; + + if (!r.truncate()) + return false; + IonSpewPass("Truncate Doubles"); + AssertExtendedGraphCoherency(graph); + + if (mir->shouldCancel("Truncate Doubles")) + return false; } if (!EliminateDeadCode(mir, graph)) diff --git a/js/src/ion/IonAnalysis.cpp b/js/src/ion/IonAnalysis.cpp index 2b7917606af0..7e6cddea03dd 100644 --- a/js/src/ion/IonAnalysis.cpp +++ b/js/src/ion/IonAnalysis.cpp @@ -374,7 +374,6 @@ class TypeAnalyzer bool respecialize(MPhi *phi, MIRType type); bool propagateSpecialization(MPhi *phi); bool specializePhis(); - bool specializeTruncatedInstructions(); void replaceRedundantPhi(MPhi *phi); void adjustPhiInputs(MPhi *phi); bool adjustInputs(MDefinition *def); @@ -608,8 +607,6 @@ TypeAnalyzer::analyze() { if (!specializePhis()) return false; - if (!specializeTruncatedInstructions()) - return false; if (!insertConversions()) return false; return true; @@ -1462,35 +1459,3 @@ LinearSum::print(Sprinter &sp) const else if (constant_ < 0) sp.printf("%d", constant_); } - -bool -TypeAnalyzer::specializeTruncatedInstructions() -{ - // This specialization is a two step process: First we loop over the - // instruction stream forwards, marking all of the instructions that - // are computed purely from integers. The theory is that we can observe - // values that don't fit into a 32 bit integer that can still be treated as - // integers. - for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); block++) { - if (mir->shouldCancel("recoverBigInts (forwards loop)")) - return false; - - for (MDefinitionIterator iter(*block); iter; iter++) { - iter->recalculateBigInt(); - } - } - - // Now, if these adds of doubles-that-are-really-big-ints get truncated - // on all reads, then we know that we don't care that any of these operations - // produces a value that is not an integer. To achieve this, loop over the instruction - // stream backwards, marking every instruction where all reads are operations that truncate - // If we have a double operation that is marked both "bigInt" and "truncated", then we can - // safely convert it into an integer instruction - for (PostorderIterator block(graph.poBegin()); block != graph.poEnd(); block++) { - if (mir->shouldCancel("Propagate Truncates (backwards loop)")) - return false; - for (MInstructionReverseIterator riter(block->rbegin()); riter != block->rend(); riter++) - riter->analyzeTruncateBackward(); - } - return true; -} diff --git a/js/src/ion/IonBuilder.cpp b/js/src/ion/IonBuilder.cpp index f9a7d8a1389f..51900326a818 100644 --- a/js/src/ion/IonBuilder.cpp +++ b/js/src/ion/IonBuilder.cpp @@ -3031,9 +3031,7 @@ IonBuilder::addTypeBarrier(uint32_t i, CallInfo &callinfo, types::StackTypeSet * } while (excluded) { - if (excluded->target == calleeObs) { - JS_ASSERT(callerObs->hasType(excluded->type)); - + if (excluded->target == calleeObs && callerObs->hasType(excluded->type)) { if (excluded->type == types::Type::DoubleType() && calleeObs->hasType(types::Type::Int32Type())) { // The double type also implies int32, so this implies that diff --git a/js/src/ion/IonCaches.cpp b/js/src/ion/IonCaches.cpp index 1b678950054a..5d632a3e4d75 100644 --- a/js/src/ion/IonCaches.cpp +++ b/js/src/ion/IonCaches.cpp @@ -1618,7 +1618,13 @@ GetElementIC::attachTypedArrayElement(JSContext *cx, IonScript *ion, JSObject *o MacroAssembler masm; // The array type is the object within the table of typed array classes. - int arrayType = obj->getClass() - &TypedArray::classes[0]; + int arrayType = TypedArray::type(obj); + + // The output register is not yet specialized as a float register, the only + // way to accept float typed arrays for now is to return a Value type. + bool floatOutput = arrayType == TypedArray::TYPE_FLOAT32 || + arrayType == TypedArray::TYPE_FLOAT64; + JS_ASSERT_IF(!output().hasValue(), !floatOutput); Register tmpReg = output().scratchReg().gpr(); JS_ASSERT(tmpReg != InvalidReg); @@ -1723,9 +1729,14 @@ GetElementIC::update(JSContext *cx, size_t cacheIndex, HandleObject obj, return false; attachedStub = true; } else if (obj->isTypedArray() && idval.isInt32()) { - if (!cache.attachTypedArrayElement(cx, ion, obj, idval)) - return false; - attachedStub = true; + int arrayType = TypedArray::type(obj); + bool floatOutput = arrayType == TypedArray::TYPE_FLOAT32 || + arrayType == TypedArray::TYPE_FLOAT64; + if (!floatOutput || cache.output().hasValue()) { + if (!cache.attachTypedArrayElement(cx, ion, obj, idval)) + return false; + attachedStub = true; + } } } diff --git a/js/src/ion/IonLinker.h b/js/src/ion/IonLinker.h index 5b44705e61a6..4e50ed5e5db8 100644 --- a/js/src/ion/IonLinker.h +++ b/js/src/ion/IonLinker.h @@ -14,6 +14,7 @@ #include "ion/IonCompartment.h" #include "assembler/jit/ExecutableAllocator.h" #include "ion/IonMacroAssembler.h" +#include "jsgcinlines.h" namespace js { namespace ion { @@ -30,6 +31,7 @@ class Linker IonCode *newCode(JSContext *cx, IonCompartment *comp) { AssertCanGC(); + gc::AutoSuppressGC suppressGC(cx); if (masm.oom()) return fail(cx); diff --git a/js/src/ion/JSONSpewer.cpp b/js/src/ion/JSONSpewer.cpp index 7eb0200c2189..f24a28e5374b 100644 --- a/js/src/ion/JSONSpewer.cpp +++ b/js/src/ion/JSONSpewer.cpp @@ -256,13 +256,17 @@ JSONSpewer::spewMDef(MDefinition *def) integerValue(use.def()->id()); endList(); + bool isTruncated = false; + if (def->isAdd() || def->isSub() || def->isMod() || def->isMul() || def->isDiv()) + isTruncated = static_cast(def)->isTruncated(); + if (def->range()) { Sprinter sp(GetIonContext()->cx); sp.init(); def->range()->print(sp); - stringProperty("type", "%s : %s", sp.string(), StringFromMIRType(def->type())); + stringProperty("type", "%s : %s%s", sp.string(), StringFromMIRType(def->type()), (isTruncated ? " (t)" : "")); } else { - stringProperty("type", "%s", StringFromMIRType(def->type())); + stringProperty("type", "%s%s", StringFromMIRType(def->type()), (isTruncated ? " (t)" : "")); } if (def->isInstruction()) { diff --git a/js/src/ion/MCallOptimize.cpp b/js/src/ion/MCallOptimize.cpp index 0e40f04e3e9c..3988a906bca1 100644 --- a/js/src/ion/MCallOptimize.cpp +++ b/js/src/ion/MCallOptimize.cpp @@ -466,15 +466,23 @@ IonBuilder::inlineMathAbs(CallInfo &callInfo) MIRType argType = getInlineArgType(callInfo, 0); if (argType != MIRType_Int32 && argType != MIRType_Double) return InliningStatus_NotInlined; - if (argType != returnType) + + if (argType != returnType && returnType != MIRType_Int32) return InliningStatus_NotInlined; callInfo.unwrapArgs(); - MAbs *ins = MAbs::New(callInfo.getArg(0), returnType); + MInstruction *ins = MAbs::New(callInfo.getArg(0), argType); current->add(ins); - current->push(ins); + if (argType != returnType) { + MToInt32 *toInt = MToInt32::New(ins); + toInt->setCanBeNegativeZero(false); + current->add(toInt); + ins = toInt; + } + + current->push(ins); return InliningStatus_Inlined; } diff --git a/js/src/ion/MIR.cpp b/js/src/ion/MIR.cpp index 20231f3a4d22..669340b45db0 100644 --- a/js/src/ion/MIR.cpp +++ b/js/src/ion/MIR.cpp @@ -175,18 +175,11 @@ MDefinition::foldsTo(bool useValueNumbers) void MDefinition::analyzeEdgeCasesForward() { - return; } void MDefinition::analyzeEdgeCasesBackward() { - return; -} -void -MDefinition::analyzeTruncateBackward() -{ - return; } static bool @@ -334,18 +327,6 @@ MConstant::MConstant(const js::Value &vp) setMovable(); } -void -MConstant::analyzeTruncateBackward() -{ - if (js::ion::EdgeCaseAnalysis::AllUsesTruncate(this) && - value_.isDouble() && isBigIntOutput()) - { - // Truncate the double to int, since all uses truncates it. - value_.setInt32(ToInt32(value_.toDouble())); - setResultType(MIRType_Int32); - } -} - HashNumber MConstant::valueHash() const { @@ -891,7 +872,7 @@ MBinaryArithInstruction::foldsTo(bool useValueNumbers) bool MAbs::fallible() const { - return !range() || !range()->isFinite(); + return !range() || !range()->isInt32(); } MDefinition * @@ -945,27 +926,6 @@ MDiv::analyzeEdgeCasesBackward() setCanBeNegativeZero(false); } -void -MDiv::analyzeTruncateBackward() -{ - if (!isTruncated()) - setTruncated(js::ion::EdgeCaseAnalysis::AllUsesTruncate(this)); -} - -bool -MDiv::updateForReplacement(MDefinition *ins_) -{ - JS_ASSERT(ins_->isDiv()); - MDiv *ins = ins_->toDiv(); - // Since EdgeCaseAnalysis is not being run before GVN, its information does - // not need to be merged here. - if (isTruncated() && ins->isTruncated()) - setTruncated(Max(isTruncated(), ins->isTruncated())); - else - setTruncated(0); - return true; -} - bool MDiv::fallible() { @@ -992,92 +952,21 @@ MMod::foldsTo(bool useValueNumbers) return this; } -void -MMod::analyzeTruncateBackward() -{ - if (!isTruncated()) - setTruncated(js::ion::EdgeCaseAnalysis::AllUsesTruncate(this)); -} - -bool -MMod::updateForReplacement(MDefinition *ins_) -{ - JS_ASSERT(ins_->isMod()); - MMod *ins = ins_->toMod(); - if (isTruncated() && ins->isTruncated()) - setTruncated(Max(isTruncated(), ins->isTruncated())); - else - setTruncated(0); - return true; -} - bool MMod::fallible() { return !isTruncated(); } -void -MAdd::analyzeTruncateBackward() -{ - if (!isTruncated()) - setTruncated(js::ion::EdgeCaseAnalysis::AllUsesTruncate(this)); - if (isTruncated() && isTruncated() < 20) { - // Super obvious optimization... If this operation is a double - // BUT it happens to look like a large precision int that eventually - // gets truncated, then just call it an int. - // This can arise if we have x+y | 0, and x and y are both INT_MAX, - // TI will observe an overflow, thus marking the addition as double-like - // but we'll have MTruncate(MAddD(toDouble(x), toDouble(y))), which we know - // we'll be able to convert to MAddI(x,y) - if (isBigInt_ && type() == MIRType_Double) { - specialization_ = MIRType_Int32; - setResultType(MIRType_Int32); - } - } -} - -bool -MAdd::updateForReplacement(MDefinition *ins_) -{ - JS_ASSERT(ins_->isAdd()); - MAdd *ins = ins_->toAdd(); - if (isTruncated() && ins->isTruncated()) - setTruncated(Max(isTruncated(), ins->isTruncated())); - else - setTruncated(0); - return true; -} - bool MAdd::fallible() { // the add is fallible if range analysis does not say that it is finite, AND - // either the truncation analysis shows that there are non-truncated uses, or - // there are more than 20 operations before it gets truncated. 20 was chosen - // for two reasons. First, it is a nice sane number. Second, the largest int32 - // can be (about) 2^31. The smallest integer that cannot be exactly represented - // as a double is 2^53 + 1 by doing something simple, like x = x + x, it takes - // 23 additions toget from 2^31 to 2^53 + 1. 20 is simply a conservative estimate of that. - return (!isTruncated() || isTruncated() > 20) && (!range() || !range()->isFinite()); -} - -void -MSub::analyzeTruncateBackward() -{ - if (!isTruncated()) - setTruncated(js::ion::EdgeCaseAnalysis::AllUsesTruncate(this)); -} - -bool -MSub::updateForReplacement(MDefinition *ins_) -{ - JS_ASSERT(ins_->isSub()); - MSub *ins = ins_->toSub(); - if (isTruncated() && ins->isTruncated()) - setTruncated(Max(isTruncated(), ins->isTruncated())); - else - setTruncated(0); + // either the truncation analysis shows that there are non-truncated uses. + if (isTruncated()) + return false; + if (range() && range()->isInt32()) + return false; return true; } @@ -1085,7 +974,11 @@ bool MSub::fallible() { // see comment in MAdd::fallible() - return (!isTruncated() || isTruncated() > 20) && (!range() || !range()->isFinite()); + if (isTruncated()) + return false; + if (range() && range()->isInt32()) + return false; + return true; } MDefinition * @@ -1125,7 +1018,6 @@ MMul::analyzeEdgeCasesForward() if (val.isInt32() && val.toInt32() > 0) setCanBeNegativeZero(false); } - } void @@ -1135,23 +1027,11 @@ MMul::analyzeEdgeCasesBackward() setCanBeNegativeZero(false); } -void -MMul::analyzeTruncateBackward() -{ - if (!isPossibleTruncated() && js::ion::EdgeCaseAnalysis::AllUsesTruncate(this)) - setPossibleTruncated(true); -} - bool MMul::updateForReplacement(MDefinition *ins_) { - JS_ASSERT(ins_->isMul()); MMul *ins = ins_->toMul(); - // setPossibleTruncated can reset the canBenegativeZero check, - // therefore first query the state, before setting the new state. - bool truncated = isPossibleTruncated() && ins->isPossibleTruncated(); bool negativeZero = canBeNegativeZero() || ins->canBeNegativeZero(); - setPossibleTruncated(truncated); setCanBeNegativeZero(negativeZero); return true; } @@ -1159,9 +1039,9 @@ MMul::updateForReplacement(MDefinition *ins_) bool MMul::canOverflow() { - if (implicitTruncate_) + if (isTruncated()) return false; - return !range() || !range()->isFinite(); + return !range() || !range()->isInt32(); } void diff --git a/js/src/ion/MIR.h b/js/src/ion/MIR.h index fde47c081927..2026869e79eb 100644 --- a/js/src/ion/MIR.h +++ b/js/src/ion/MIR.h @@ -302,7 +302,7 @@ class MDefinition : public MNode MDefinition() : id_(0), valueNumber_(NULL), - range_(), + range_(NULL), resultType_(MIRType_None), flags_(0), dependency_(NULL), @@ -338,7 +338,10 @@ class MDefinition : public MNode virtual MDefinition *foldsTo(bool useValueNumbers); virtual void analyzeEdgeCasesForward(); virtual void analyzeEdgeCasesBackward(); - virtual void analyzeTruncateBackward(); + + virtual bool truncate(); + virtual bool isOperandTruncated(size_t index) const; + bool earlyAbortCheck(); // Compute an absolute or symbolic range for the value of this node. @@ -498,14 +501,6 @@ class MDefinition : public MNode JS_ASSERT(getAliasSet().flags() & store->getAliasSet().flags()); return true; } - // This indicates if this instruction is "integral at heart". This will - // be the case if - // a) its result type is int32 - // b) it is an instruction that is very likely to produce an integer or integer-truncatable - // result (add, mul, sub), and both of its inputs are ints. (currently only implemented for add) - virtual bool isBigIntOutput() { return resultType_ == MIRType_Int32; } - virtual void recalculateBigInt() {} - }; // An MUseDefIterator walks over uses in a definition, skipping any use that is @@ -715,27 +710,8 @@ class MConstant : public MNullaryInstruction return AliasSet::None(); } - void analyzeTruncateBackward(); - - // Returns true if constant is integer between -2^33 & 2^33, - // Max cap could be 2^53, if not for the 20 additions hack. - bool isBigIntOutput() { - if (value_.isInt32()) - return true; - if (value_.isDouble()) { - double value = value_.toDouble(); - int64_t valint = value; - int64_t max = 1LL<<33; - if (double(valint) != value) - return false; - if (valint < 0) - valint = -valint; - return valint < max; - } - return false; - } - void computeRange(); + bool truncate(); }; class MParameter : public MNullaryInstruction @@ -2099,6 +2075,10 @@ class MToDouble AliasSet getAliasSet() const { return AliasSet::None(); } + + void computeRange(); + bool truncate(); + bool isOperandTruncated(size_t index) const; }; // Converts a primitive (either typed or untyped) to an int32. If the input is @@ -2146,6 +2126,7 @@ class MToInt32 : public MUnaryInstruction AliasSet getAliasSet() const { return AliasSet::None(); } + void computeRange(); }; // Converts a value or typed input to a truncated int32, for use with bitwise @@ -2178,6 +2159,9 @@ class MTruncateToInt32 : public MUnaryInstruction AliasSet getAliasSet() const { return AliasSet::None(); } + + void computeRange(); + bool isOperandTruncated(size_t index) const; }; // Converts any type to a string @@ -2338,6 +2322,8 @@ class MBinaryBitwiseInstruction return AliasSet::Store(AliasSet::Any); return AliasSet::None(); } + + bool isOperandTruncated(size_t index) const; }; class MBitAnd : public MBinaryBitwiseInstruction @@ -2511,9 +2497,20 @@ class MBinaryArithInstruction : public MBinaryInstruction, public ArithPolicy { + // Implicit truncate flag is set by the truncate backward range analysis + // optimization phase, and by asm.js pre-processing. It is used in + // NeedNegativeZeroCheck to check if the result of a multiplication needs to + // produce -0 double value, and for avoiding overflow checks. + + // This optimization happens when the multiplication cannot be truncated + // even if all uses are truncating its result, such as when the range + // analysis detect a precision loss in the multiplication. + bool implicitTruncate_; + public: MBinaryArithInstruction(MDefinition *left, MDefinition *right) - : MBinaryInstruction(left, right) + : MBinaryInstruction(left, right), + implicitTruncate_(false) { setMovable(); } @@ -2544,6 +2541,13 @@ class MBinaryArithInstruction return AliasSet::Store(AliasSet::Any); return AliasSet::None(); } + + bool isTruncated() const { + return implicitTruncate_; + } + void setTruncated(bool truncate) { + implicitTruncate_ = truncate; + } }; class MMinMax @@ -2799,13 +2803,9 @@ class MMathFunction class MAdd : public MBinaryArithInstruction { - int implicitTruncate_; // Is this instruction really an int at heart? - bool isBigInt_; MAdd(MDefinition *left, MDefinition *right) - : MBinaryArithInstruction(left, right), - implicitTruncate_(0), - isBigInt_(left->isBigIntOutput() && right->isBigIntOutput()) + : MBinaryArithInstruction(left, right) { setResultType(MIRType_Value); } @@ -2815,39 +2815,21 @@ class MAdd : public MBinaryArithInstruction static MAdd *New(MDefinition *left, MDefinition *right) { return new MAdd(left, right); } - void analyzeTruncateBackward(); - int isTruncated() const { - return implicitTruncate_; - } - void setTruncated(int truncate) { - implicitTruncate_ = truncate; - } - bool updateForReplacement(MDefinition *ins); double getIdentity() { return 0; } bool fallible(); void computeRange(); - // This is an add, so the return value is from only - // integer sources if we know we return an int32 - // or it has been explicitly marked as being a large int. - bool isBigIntOutput() { - return (type() == MIRType_Int32) || isBigInt_; - } - // An add will produce a big int if both of its sources are big ints. - void recalculateBigInt() { - isBigInt_ = (lhs()->isBigIntOutput() && rhs()->isBigIntOutput()); - } + bool truncate(); + bool isOperandTruncated(size_t index) const; }; class MSub : public MBinaryArithInstruction { - int implicitTruncate_; MSub(MDefinition *left, MDefinition *right) - : MBinaryArithInstruction(left, right), - implicitTruncate_(0) + : MBinaryArithInstruction(left, right) { setResultType(MIRType_Value); } @@ -2858,21 +2840,14 @@ class MSub : public MBinaryArithInstruction return new MSub(left, right); } - void analyzeTruncateBackward(); - int isTruncated() const { - return implicitTruncate_; - } - void setTruncated(int truncate) { - implicitTruncate_ = truncate; - } - bool updateForReplacement(MDefinition *ins); - double getIdentity() { return 0; } bool fallible(); void computeRange(); + bool truncate(); + bool isOperandTruncated(size_t index) const; }; class MMul : public MBinaryArithInstruction @@ -2888,29 +2863,18 @@ class MMul : public MBinaryArithInstruction // and we need to guard this during execution. bool canBeNegativeZero_; - // Annotation the result of this Mul is only used in int32 domain - // and we could possible truncate the result. - bool possibleTruncate_; - - // Annotation the Mul can truncate. This is only set after range analysis, - // because the result could be in the imprecise double range. - // In that case the truncated result isn't correct. - bool implicitTruncate_; - Mode mode_; MMul(MDefinition *left, MDefinition *right, MIRType type, Mode mode) : MBinaryArithInstruction(left, right), canBeNegativeZero_(true), - possibleTruncate_(false), - implicitTruncate_(false), mode_(mode) { if (mode == Integer) { // This implements the required behavior for Math.imul, which // can never fail and always truncates its output to int32. canBeNegativeZero_ = false; - possibleTruncate_ = implicitTruncate_ = true; + setTruncated(true); } JS_ASSERT_IF(mode != Integer, mode == Normal); @@ -2931,7 +2895,6 @@ class MMul : public MBinaryArithInstruction MDefinition *foldsTo(bool useValueNumbers); void analyzeEdgeCasesForward(); void analyzeEdgeCasesBackward(); - void analyzeTruncateBackward(); double getIdentity() { return 1; @@ -2953,21 +2916,8 @@ class MMul : public MBinaryArithInstruction } void computeRange(); - - bool isPossibleTruncated() const { - return possibleTruncate_; - } - - void setPossibleTruncated(bool truncate) { - possibleTruncate_ = truncate; - - // We can remove the negative zero check, because op if it is only used truncated. - // The "Possible" in the function name means that we are not sure, - // that "integer mul and disregarding overflow" == "double mul and ToInt32" - // Note: when removing truncated state, we have to add negative zero check again, - // because we are not sure if it was removed by this or other passes. - canBeNegativeZero_ = !truncate; - } + bool truncate(); + bool isOperandTruncated(size_t index) const; Mode mode() { return mode_; } }; @@ -2977,14 +2927,12 @@ class MDiv : public MBinaryArithInstruction bool canBeNegativeZero_; bool canBeNegativeOverflow_; bool canBeDivideByZero_; - int implicitTruncate_; MDiv(MDefinition *left, MDefinition *right, MIRType type) : MBinaryArithInstruction(left, right), canBeNegativeZero_(true), canBeNegativeOverflow_(true), - canBeDivideByZero_(true), - implicitTruncate_(0) + canBeDivideByZero_(true) { if (type != MIRType_Value) specialization_ = type; @@ -3003,20 +2951,12 @@ class MDiv : public MBinaryArithInstruction MDefinition *foldsTo(bool useValueNumbers); void analyzeEdgeCasesForward(); void analyzeEdgeCasesBackward(); - void analyzeTruncateBackward(); double getIdentity() { JS_NOT_REACHED("not used"); return 1; } - int isTruncated() const { - return implicitTruncate_; - } - void setTruncated(int truncate) { - implicitTruncate_ = truncate; - } - bool canBeNegativeZero() { return canBeNegativeZero_; } @@ -3032,17 +2972,14 @@ class MDiv : public MBinaryArithInstruction return canBeDivideByZero_; } - bool updateForReplacement(MDefinition *ins); bool fallible(); + bool truncate(); }; class MMod : public MBinaryArithInstruction { - int implicitTruncate_; - MMod(MDefinition *left, MDefinition *right) - : MBinaryArithInstruction(left, right), - implicitTruncate_(0) + : MBinaryArithInstruction(left, right) { setResultType(MIRType_Value); } @@ -3054,23 +2991,16 @@ class MMod : public MBinaryArithInstruction } MDefinition *foldsTo(bool useValueNumbers); - void analyzeTruncateBackward(); double getIdentity() { JS_NOT_REACHED("not used"); return 1; } - int isTruncated() const { - return implicitTruncate_; - } - void setTruncated(int truncate) { - implicitTruncate_ = truncate; - } - - bool updateForReplacement(MDefinition *ins); - void computeRange(); bool fallible(); + + void computeRange(); + bool truncate(); }; class MConcat diff --git a/js/src/ion/RangeAnalysis.cpp b/js/src/ion/RangeAnalysis.cpp index a9f977c5ef1c..2bbc6237912f 100644 --- a/js/src/ion/RangeAnalysis.cpp +++ b/js/src/ion/RangeAnalysis.cpp @@ -5,8 +5,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include #include +#include "vm/NumericConversions.h" + #include "Ion.h" #include "IonAnalysis.h" #include "MIR.h" @@ -163,12 +166,18 @@ RangeAnalysis::addBetaNobes() } if (smaller && greater) { MBeta *beta; - beta = MBeta::New(smaller, new Range(JSVAL_INT_MIN, JSVAL_INT_MAX-1)); + beta = MBeta::New(smaller, new Range(JSVAL_INT_MIN, JSVAL_INT_MAX-1, + smaller->type() != MIRType_Int32, + Range::MaxDoubleExponent)); block->insertBefore(*block->begin(), beta); replaceDominatedUsesWith(smaller, beta, block); - beta = MBeta::New(greater, new Range(JSVAL_INT_MIN+1, JSVAL_INT_MAX)); + IonSpew(IonSpew_Range, "Adding beta node for smaller %d", smaller->id()); + beta = MBeta::New(greater, new Range(JSVAL_INT_MIN+1, JSVAL_INT_MAX, + greater->type() != MIRType_Int32, + Range::MaxDoubleExponent)); block->insertBefore(*block->begin(), beta); replaceDominatedUsesWith(greater, beta, block); + IonSpew(IonSpew_Range, "Adding beta node for greater %d", greater->id()); } continue; } @@ -177,6 +186,8 @@ RangeAnalysis::addBetaNobes() Range comp; + if (val->type() == MIRType_Int32) + comp.setInt32(); switch (jsop) { case JSOP_LE: comp.setUpper(bound); @@ -251,6 +262,12 @@ Range::print(Sprinter &sp) const JS_ASSERT_IF(lower_infinite_, lower_ == JSVAL_INT_MIN); JS_ASSERT_IF(upper_infinite_, upper_ == JSVAL_INT_MAX); + // Real or Natural subset. + if (decimal_) + sp.printf("R"); + else + sp.printf("N"); + sp.printf("["); if (lower_infinite_) @@ -276,6 +293,7 @@ Range::print(Sprinter &sp) const } sp.printf("]"); + sp.printf(" (%db)", numBits()); } Range * @@ -293,7 +311,9 @@ Range::intersect(const Range *lhs, const Range *rhs, bool *emptyRange) Range *r = new Range( Max(lhs->lower_, rhs->lower_), - Min(lhs->upper_, rhs->upper_)); + Min(lhs->upper_, rhs->upper_), + lhs->decimal_ && rhs->decimal_, + Min(lhs->max_exponent_, rhs->max_exponent_)); r->lower_infinite_ = lhs->lower_infinite_ && rhs->lower_infinite_; r->upper_infinite_ = lhs->upper_infinite_ && rhs->upper_infinite_; @@ -323,76 +343,78 @@ Range::intersect(const Range *lhs, const Range *rhs, bool *emptyRange) void Range::unionWith(const Range *other) { - setLower(Min(lower_, other->lower_)); lower_infinite_ |= other->lower_infinite_; - setUpper(Max(upper_, other->upper_)); upper_infinite_ |= other->upper_infinite_; + decimal_ |= other->decimal_; + max_exponent_ = Max(max_exponent_, other->max_exponent_); + setLower(Min(lower_, other->lower_)); + setUpper(Max(upper_, other->upper_)); } static const Range emptyRange; -static inline void -EnsureRange(const Range **prange) +Range::Range(const MDefinition *def) + : symbolicLower_(NULL), + symbolicUpper_(NULL) { - if (!*prange) - *prange = &emptyRange; + const Range *other = def->range(); + if (!other) + other = &emptyRange; + + lower_ = other->lower_; + lower_infinite_ = other->lower_infinite_; + upper_ = other->upper_; + upper_infinite_ = other->upper_infinite_; + decimal_ = other->decimal_; + max_exponent_ = other->max_exponent_; + + if (def->type() == MIRType_Int32) + truncate(); +} + +const int64_t RANGE_INF_MAX = (int64_t) JSVAL_INT_MAX + 1; +const int64_t RANGE_INF_MIN = (int64_t) JSVAL_INT_MIN - 1; + +static inline bool +HasInfinite(const Range *lhs, const Range *rhs) +{ + return lhs->isLowerInfinite() || lhs->isUpperInfinite() || + rhs->isLowerInfinite() || rhs->isUpperInfinite(); } Range * Range::add(const Range *lhs, const Range *rhs) { - EnsureRange(&lhs); - EnsureRange(&rhs); - return new Range( - (int64_t)lhs->lower_ + (int64_t)rhs->lower_, - (int64_t)lhs->upper_ + (int64_t)rhs->upper_); + int64_t l = (int64_t) lhs->lower_ + (int64_t) rhs->lower_; + if (lhs->isLowerInfinite() || rhs->isLowerInfinite()) + l = RANGE_INF_MIN; + + int64_t h = (int64_t) lhs->upper_ + (int64_t) rhs->upper_; + if (lhs->isUpperInfinite() || rhs->isUpperInfinite()) + h = RANGE_INF_MAX; + + return new Range(l, h, lhs->isDecimal() || rhs->isDecimal(), + Max(lhs->exponent(), rhs->exponent()) + 1); } Range * Range::sub(const Range *lhs, const Range *rhs) { - EnsureRange(&lhs); - EnsureRange(&rhs); - return new Range( - (int64_t)lhs->lower_ - (int64_t)rhs->upper_, - (int64_t)lhs->upper_ - (int64_t)rhs->lower_); -} + int64_t l = (int64_t) lhs->lower_ - (int64_t) rhs->upper_; + if (lhs->isLowerInfinite() || rhs->isUpperInfinite()) + l = RANGE_INF_MIN; -/* static */ Range * -Range::Truncate(int64_t l, int64_t h) -{ - Range *ret = new Range(l, h); - if (!ret->isFinite()) { - ret->makeLowerInfinite(); - ret->makeUpperInfinite(); - } - return ret; -} + int64_t h = (int64_t) lhs->upper_ - (int64_t) rhs->lower_; + if (lhs->isUpperInfinite() || rhs->isLowerInfinite()) + h = RANGE_INF_MAX; -Range * -Range::addTruncate(const Range *lhs, const Range *rhs) -{ - EnsureRange(&lhs); - EnsureRange(&rhs); - return Truncate((int64_t)lhs->lower_ + (int64_t)rhs->lower_, - (int64_t)lhs->upper_ + (int64_t)rhs->upper_); -} - -Range * -Range::subTruncate(const Range *lhs, const Range *rhs) -{ - EnsureRange(&lhs); - EnsureRange(&rhs); - return Truncate((int64_t)lhs->lower_ - (int64_t)rhs->upper_, - (int64_t)lhs->upper_ - (int64_t)rhs->lower_); + return new Range(l, h, lhs->isDecimal() || rhs->isDecimal(), + Max(lhs->exponent(), rhs->exponent()) + 1); } Range * Range::and_(const Range *lhs, const Range *rhs) { - EnsureRange(&lhs); - EnsureRange(&rhs); - int64_t lower; int64_t upper; @@ -423,21 +445,23 @@ Range::and_(const Range *lhs, const Range *rhs) Range * Range::mul(const Range *lhs, const Range *rhs) { - EnsureRange(&lhs); - EnsureRange(&rhs); + bool decimal = lhs->isDecimal() || rhs->isDecimal(); + uint16_t exponent = lhs->numBits() + rhs->numBits() - 1; + if (HasInfinite(lhs, rhs)) + return new Range(RANGE_INF_MIN, RANGE_INF_MAX, decimal, exponent); int64_t a = (int64_t)lhs->lower_ * (int64_t)rhs->lower_; int64_t b = (int64_t)lhs->lower_ * (int64_t)rhs->upper_; int64_t c = (int64_t)lhs->upper_ * (int64_t)rhs->lower_; int64_t d = (int64_t)lhs->upper_ * (int64_t)rhs->upper_; return new Range( Min( Min(a, b), Min(c, d) ), - Max( Max(a, b), Max(c, d) )); + Max( Max(a, b), Max(c, d) ), + decimal, exponent); } Range * Range::shl(const Range *lhs, int32_t c) { - EnsureRange(&lhs); int32_t shift = c & 0x1f; return new Range( (int64_t)lhs->lower_ << shift, @@ -447,39 +471,15 @@ Range::shl(const Range *lhs, int32_t c) Range * Range::shr(const Range *lhs, int32_t c) { - EnsureRange(&lhs); int32_t shift = c & 0x1f; return new Range( (int64_t)lhs->lower_ >> shift, (int64_t)lhs->upper_ >> shift); } -bool -Range::precisionLossMul(const Range *lhs, const Range *rhs) -{ - EnsureRange(&lhs); - EnsureRange(&rhs); - int64_t loss = 1LL<<53; // result must be lower than 2^53 - int64_t a = (int64_t)lhs->lower_ * (int64_t)rhs->lower_; - int64_t b = (int64_t)lhs->lower_ * (int64_t)rhs->upper_; - int64_t c = (int64_t)lhs->upper_ * (int64_t)rhs->lower_; - int64_t d = (int64_t)lhs->upper_ * (int64_t)rhs->upper_; - int64_t lower = Min( Min(a, b), Min(c, d) ); - int64_t upper = Max( Max(a, b), Max(c, d) ); - if (lower < 0) - lower = -lower; - if (upper < 0) - upper = -upper; - - return lower > loss || upper > loss; -} - bool Range::negativeZeroMul(const Range *lhs, const Range *rhs) { - EnsureRange(&lhs); - EnsureRange(&rhs); - // Both values are positive if (lhs->lower_ >= 0 && rhs->lower_ >= 0) return false; @@ -502,12 +502,16 @@ Range::update(const Range *other) lower_ != other->lower_ || lower_infinite_ != other->lower_infinite_ || upper_ != other->upper_ || - upper_infinite_ != other->upper_infinite_; + upper_infinite_ != other->upper_infinite_ || + decimal_ != other->decimal_ || + max_exponent_ != other->max_exponent_; if (changed) { lower_ = other->lower_; lower_infinite_ = other->lower_infinite_; upper_ = other->upper_; upper_infinite_ = other->upper_infinite_; + decimal_ = other->decimal_; + max_exponent_ = other->max_exponent_; } return changed; @@ -520,7 +524,7 @@ Range::update(const Range *other) void MPhi::computeRange() { - if (type() != MIRType_Int32) + if (type() != MIRType_Int32 && type() != MIRType_Double) return; Range *range = NULL; @@ -556,8 +560,58 @@ MPhi::computeRange() void MConstant::computeRange() { - if (type() == MIRType_Int32) + if (type() == MIRType_Int32) { setRange(new Range(value().toInt32(), value().toInt32())); + return; + } + + if (type() != MIRType_Double) + return; + + double d = value().toDouble(); + int exp = Range::MaxDoubleExponent; + + // NaN is estimated as a Double which covers everything. + if (MOZ_DOUBLE_IS_NaN(d)) { + setRange(new Range(RANGE_INF_MIN, RANGE_INF_MAX, true, exp)); + return; + } + + // Infinity is used to set both lower and upper to the range boundaries. + if (MOZ_DOUBLE_IS_INFINITE(d)) { + if (MOZ_DOUBLE_IS_NEGATIVE(d)) + setRange(new Range(RANGE_INF_MIN, RANGE_INF_MIN, false, exp)); + else + setRange(new Range(RANGE_INF_MAX, RANGE_INF_MAX, false, exp)); + return; + } + + // Extract the exponent, to approximate it with the range analysis. + exp = MOZ_DOUBLE_EXPONENT(d); + if (exp < 0) { + // This double only has a decimal part. + if (MOZ_DOUBLE_IS_NEGATIVE(d)) + setRange(new Range(-1, 0, true, 0)); + else + setRange(new Range(0, 1, true, 0)); + } else if (exp < Range::MaxTruncatableExponent) { + // Extract the integral part. + int64_t integral = ToInt64(d); + // Extract the decimal part. + double rest = d - (double) integral; + // Estimate the smallest integral boundaries. + // Safe double comparisons, because there is no precision loss. + int64_t l = integral - ((rest < 0) ? 1 : 0); + int64_t h = integral + ((rest > 0) ? 1 : 0); + setRange(new Range(l, h, (rest != 0), exp)); + } else { + // This double has a precision loss. This also mean that it cannot + // encode any decimals. + if (MOZ_DOUBLE_IS_NEGATIVE(d)) + setRange(new Range(RANGE_INF_MIN, RANGE_INF_MIN, false, exp)); + else + setRange(new Range(RANGE_INF_MAX, RANGE_INF_MAX, false, exp)); + } } void @@ -576,9 +630,9 @@ MClampToUint8::computeRange() void MBitAnd::computeRange() { - Range *left = getOperand(0)->range(); - Range *right = getOperand(1)->range(); - setRange(Range::and_(left, right)); + Range left(getOperand(0)); + Range right(getOperand(1)); + setRange(Range::and_(&left, &right)); } void @@ -589,8 +643,8 @@ MLsh::computeRange() return; int32_t c = right->toConstant()->value().toInt32(); - const Range *other = getOperand(0)->range(); - setRange(Range::shl(other, c)); + Range other(getOperand(0)); + setRange(Range::shl(&other, c)); } void @@ -601,74 +655,93 @@ MRsh::computeRange() return; int32_t c = right->toConstant()->value().toInt32(); - Range *other = getOperand(0)->range(); - setRange(Range::shr(other, c)); + Range other(getOperand(0)); + setRange(Range::shr(&other, c)); } void MAbs::computeRange() { - if (specialization_ != MIRType_Int32) + if (specialization_ != MIRType_Int32 && specialization_ != MIRType_Double) return; - const Range *other = getOperand(0)->range(); - EnsureRange(&other); + Range other(getOperand(0)); Range *range = new Range(0, - Max(Range::abs64((int64_t)other->lower()), - Range::abs64((int64_t)other->upper()))); + Max(Range::abs64((int64_t) other.lower()), + Range::abs64((int64_t) other.upper())), + other.isDecimal(), + other.exponent()); setRange(range); } void MAdd::computeRange() { - if (specialization() != MIRType_Int32) + if (specialization() != MIRType_Int32 && specialization() != MIRType_Double) return; - Range *left = getOperand(0)->range(); - Range *right = getOperand(1)->range(); - Range *next = isTruncated() ? Range::addTruncate(left,right) : Range::add(left, right); + Range left(getOperand(0)); + Range right(getOperand(1)); + Range *next = Range::add(&left, &right); setRange(next); } void MSub::computeRange() { - if (specialization() != MIRType_Int32) + if (specialization() != MIRType_Int32 && specialization() != MIRType_Double) return; - Range *left = getOperand(0)->range(); - Range *right = getOperand(1)->range(); - Range *next = isTruncated() ? Range::subTruncate(left,right) : Range::sub(left, right); + Range left(getOperand(0)); + Range right(getOperand(1)); + Range *next = Range::sub(&left, &right); setRange(next); } void MMul::computeRange() { - if (specialization() != MIRType_Int32) + if (specialization() != MIRType_Int32 && specialization() != MIRType_Double) return; - Range *left = getOperand(0)->range(); - Range *right = getOperand(1)->range(); - if (!implicitTruncate_ && isPossibleTruncated()) - implicitTruncate_ = !Range::precisionLossMul(left, right); + Range left(getOperand(0)); + Range right(getOperand(1)); if (canBeNegativeZero()) - canBeNegativeZero_ = Range::negativeZeroMul(left, right); - setRange(Range::mul(left, right)); + canBeNegativeZero_ = Range::negativeZeroMul(&left, &right); + setRange(Range::mul(&left, &right)); } void MMod::computeRange() { - if (specialization() != MIRType_Int32) + if (specialization() != MIRType_Int32 && specialization() != MIRType_Double) return; - const Range *rhs = getOperand(1)->range(); - EnsureRange(&rhs); - int64_t a = Range::abs64((int64_t)rhs->lower()); - int64_t b = Range::abs64((int64_t)rhs->upper()); + Range lhs(getOperand(0)); + Range rhs(getOperand(1)); + int64_t a = Range::abs64((int64_t) rhs.lower()); + int64_t b = Range::abs64((int64_t) rhs.upper()); if (a == 0 && b == 0) return; int64_t bound = Max(1-a, b-1); - setRange(new Range(-bound, bound)); + setRange(new Range(-bound, bound, lhs.isDecimal() || rhs.isDecimal())); +} + +void +MToDouble::computeRange() +{ + setRange(new Range(getOperand(0))); +} + +void +MTruncateToInt32::computeRange() +{ + Range input(getOperand(0)); + setRange(new Range(input.lower(), input.upper())); +} + +void +MToInt32::computeRange() +{ + Range input(getOperand(0)); + setRange(new Range(input.lower(), input.upper())); } /////////////////////////////////////////////////////////////////////////////// @@ -1124,3 +1197,277 @@ RangeAnalysis::analyze() return true; } + +/////////////////////////////////////////////////////////////////////////////// +// Range based Truncation +/////////////////////////////////////////////////////////////////////////////// + +void +Range::truncate() +{ + if (isInt32()) + return; + int64_t l = isLowerInfinite() ? JSVAL_INT_MIN : lower(); + int64_t h = isUpperInfinite() ? JSVAL_INT_MAX : upper(); + set(l, h, false, 32); +} + +bool +MDefinition::truncate() +{ + // No procedure defined for truncating this instruction. + return false; +} + +bool +MConstant::truncate() +{ + if (!value_.isDouble()) + return false; + + // Truncate the double to int, since all uses truncates it. + value_.setInt32(ToInt32(value_.toDouble())); + setResultType(MIRType_Int32); + if (range()) + range()->truncate(); + return true; +} + +bool +MAdd::truncate() +{ + // Remember analysis, needed for fallible checks. + setTruncated(true); + + // Modify the instruction if needed. + if (type() != MIRType_Double) + return false; + + specialization_ = MIRType_Int32; + setResultType(MIRType_Int32); + if (range()) + range()->truncate(); + return true; +} + +bool +MSub::truncate() +{ + // Remember analysis, needed for fallible checks. + setTruncated(true); + + // Modify the instruction if needed. + if (type() != MIRType_Double) + return false; + + specialization_ = MIRType_Int32; + setResultType(MIRType_Int32); + if (range()) + range()->truncate(); + return true; +} + +bool +MMul::truncate() +{ + // Remember analysis, needed to remove negative zero checks. + setTruncated(true); + + // Modify the instruction. + bool truncated = type() == MIRType_Int32; + if (type() == MIRType_Double) { + specialization_ = MIRType_Int32; + setResultType(MIRType_Int32); + truncated = true; + JS_ASSERT(range()); + } + + if (truncated && range()) { + range()->truncate(); + setTruncated(true); + setCanBeNegativeZero(false); + } + + return truncated; +} + +bool +MDiv::truncate() +{ + // Remember analysis, needed to remove negative zero checks. + setTruncated(true); + + // No modifications. + return false; +} + +bool +MMod::truncate() +{ + // Remember analysis, needed to remove negative zero checks. + setTruncated(true); + + // No modifications. + return false; +} + +bool +MToDouble::truncate() +{ + JS_ASSERT(type() == MIRType_Double); + + // We use the return type to flag that this MToDouble sould be replaced by a + // MTruncateToInt32 when modifying the graph. + setResultType(MIRType_Int32); + if (range()) + range()->truncate(); + + return true; +} + +bool +MDefinition::isOperandTruncated(size_t index) const +{ + return false; +} + +bool +MTruncateToInt32::isOperandTruncated(size_t index) const +{ + return true; +} + +bool +MBinaryBitwiseInstruction::isOperandTruncated(size_t index) const +{ + return true; +} + +bool +MAdd::isOperandTruncated(size_t index) const +{ + return isTruncated(); +} + +bool +MSub::isOperandTruncated(size_t index) const +{ + return isTruncated(); +} + +bool +MMul::isOperandTruncated(size_t index) const +{ + return isTruncated(); +} + +bool +MToDouble::isOperandTruncated(size_t index) const +{ + // The return type is used to flag that we are replacing this Double by a + // Truncate of its operand if needed. + return type() == MIRType_Int32; +} + +// Ensure that all observables (non-resume point) uses can work with a truncated +// version of the |candidate|'s result. +static bool +AllUsesTruncate(MInstruction *candidate) +{ + for (MUseDefIterator use(candidate); use; use++) { + if (!use.def()->isOperandTruncated(use.index())) + return false; + } + + return true; +} + +static void +RemoveTruncatesOnOutput(MInstruction *truncated) +{ + JS_ASSERT(truncated->type() == MIRType_Int32); + JS_ASSERT_IF(truncated->range(), truncated->range()->isInt32()); + + for (MUseDefIterator use(truncated); use; use++) { + MDefinition *def = use.def(); + if (!def->isTruncateToInt32() || !def->isToInt32()) + continue; + + def->replaceAllUsesWith(truncated); + } +} + +void +AdjustTruncatedInputs(MInstruction *truncated) +{ + MBasicBlock *block = truncated->block(); + for (size_t i = 0; i < truncated->numOperands(); i++) { + if (!truncated->isOperandTruncated(i)) + continue; + if (truncated->getOperand(i)->type() == MIRType_Int32) + continue; + + MTruncateToInt32 *op = MTruncateToInt32::New(truncated->getOperand(i)); + block->insertBefore(truncated, op); + truncated->replaceOperand(i, op); + } + + if (truncated->isToDouble()) { + truncated->replaceAllUsesWith(truncated->getOperand(0)); + block->discard(truncated); + } +} + +// Iterate backward on all instruction and attempt to truncate operations for +// each instruction which respect the following list of predicates: Has been +// analyzed by range analysis, the range has no rounding errors, all uses cases +// are truncating the result. +// +// If the truncation of the operation is successful, then the instruction is +// queue for later updating the graph to restore the type correctness by +// converting the operands that need to be truncated. +// +// We iterate backward because it is likely that a truncated operation truncates +// some of its operands. +bool +RangeAnalysis::truncate() +{ + IonSpew(IonSpew_Range, "Do range-base truncation (backward loop)"); + + Vector worklist; + + for (PostorderIterator block(graph_.poBegin()); block != graph_.poEnd(); block++) { + for (MInstructionReverseIterator iter(block->rbegin()); iter != block->rend(); iter++) { + // Set truncated flag if range analysis ensure that it has no + // rounding errors and no freactional part. + const Range *r = iter->range(); + if (!r || r->hasRoundingErrors()) + continue; + + // Ensure all observable uses are truncated. + if (!AllUsesTruncate(*iter)) + continue; + + // Truncate this instruction if possible. + if (!iter->truncate()) + continue; + + // Delay updates of inputs/outputs to avoid creating node which + // would be removed by the truncation of the next operations. + iter->setInWorklist(); + if (!worklist.append(*iter)) + return false; + } + } + + // Update inputs/outputs of truncated instructions. + IonSpew(IonSpew_Range, "Do graph type fixup (dequeue)"); + while (!worklist.empty()) { + MInstruction *ins = worklist.popCopy(); + ins->setNotInWorklist(); + RemoveTruncatesOnOutput(ins); + AdjustTruncatedInputs(ins); + } + + return true; +} diff --git a/js/src/ion/RangeAnalysis.h b/js/src/ion/RangeAnalysis.h index aa824d3f2092..21c95966d4d3 100644 --- a/js/src/ion/RangeAnalysis.h +++ b/js/src/ion/RangeAnalysis.h @@ -8,6 +8,8 @@ #ifndef jsion_range_analysis_h__ #define jsion_range_analysis_h__ +#include "mozilla/FloatingPoint.h" + #include "wtf/Platform.h" #include "MIR.h" #include "CompileInfo.h" @@ -80,6 +82,7 @@ class RangeAnalysis bool addBetaNobes(); bool analyze(); bool removeBetaNobes(); + bool truncate(); private: void analyzeLoop(MBasicBlock *header); @@ -91,6 +94,19 @@ class RangeAnalysis }; class Range : public TempObject { + public: + // Int32 are signed, so the value needs 31 bits except for INT_MIN value + // which needs 32 bits. + static const uint16_t MaxInt32Exponent = 31; + + // Maximal exponenent under which we have no precission loss on double + // operations. Double has 52 bits of mantissa, so 2^52+1 cannot be + // represented without loss. + static const uint16_t MaxTruncatableExponent = MOZ_DOUBLE_EXPONENT_SHIFT; + + // 11 bits of signed exponent, so the max is encoded on 10 bits. + static const uint16_t MaxDoubleExponent = MOZ_DOUBLE_EXPONENT_BIAS; + private: // Absolute ranges. // @@ -115,11 +131,22 @@ class Range : public TempObject { // To facilitate this trick, we maintain the invariants that: // 1) lower_infinite == true implies lower_ == JSVAL_INT_MIN // 2) upper_infinite == true implies upper_ == JSVAL_INT_MAX + // + // As a second and less precise range analysis, we represent the maximal + // exponent taken by a value. The exponent is calculated by taking the + // absolute value and looking at the position of the highest bit. All + // exponent computation have to be over-estimations of the actual result. On + // the Int32 this over approximation is rectified. + int32_t lower_; bool lower_infinite_; + int32_t upper_; bool upper_infinite_; + bool decimal_; + uint16_t max_exponent_; + // Any symbolic lower or upper bound computed for this term. const SymbolicBound *symbolicLower_; const SymbolicBound *symbolicUpper_; @@ -130,16 +157,28 @@ class Range : public TempObject { lower_infinite_(true), upper_(JSVAL_INT_MAX), upper_infinite_(true), + decimal_(true), + max_exponent_(MaxDoubleExponent), symbolicLower_(NULL), symbolicUpper_(NULL) - {} + { + JS_ASSERT_IF(lower_infinite_, lower_ == JSVAL_INT_MIN); + JS_ASSERT_IF(upper_infinite_, upper_ == JSVAL_INT_MAX); + } - Range(int64_t l, int64_t h) - : symbolicLower_(NULL), + Range(int64_t l, int64_t h, bool d = false, uint16_t e = MaxInt32Exponent) + : lower_infinite_(true), + upper_infinite_(true), + decimal_(d), + max_exponent_(e), + symbolicLower_(NULL), symbolicUpper_(NULL) { - setLower(l); - setUpper(h); + setLowerInit(l); + setUpperInit(h); + rectifyExponent(); + JS_ASSERT_IF(lower_infinite_, lower_ == JSVAL_INT_MIN); + JS_ASSERT_IF(upper_infinite_, upper_ == JSVAL_INT_MAX); } Range(const Range &other) @@ -147,9 +186,16 @@ class Range : public TempObject { lower_infinite_(other.lower_infinite_), upper_(other.upper_), upper_infinite_(other.upper_infinite_), + decimal_(other.decimal_), + max_exponent_(other.max_exponent_), symbolicLower_(NULL), symbolicUpper_(NULL) - {} + { + JS_ASSERT_IF(lower_infinite_, lower_ == JSVAL_INT_MIN); + JS_ASSERT_IF(upper_infinite_, upper_ == JSVAL_INT_MAX); + } + + Range(const MDefinition *def); static Range *Truncate(int64_t l, int64_t h); @@ -173,8 +219,6 @@ class Range : public TempObject { // nodes. void unionWith(const Range *other); static Range * intersect(const Range *lhs, const Range *rhs, bool *emptyRange); - static Range * addTruncate(const Range *lhs, const Range *rhs); - static Range * subTruncate(const Range *lhs, const Range *rhs); static Range * add(const Range *lhs, const Range *rhs); static Range * sub(const Range *lhs, const Range *rhs); static Range * mul(const Range *lhs, const Range *rhs); @@ -182,20 +226,24 @@ class Range : public TempObject { static Range * shl(const Range *lhs, int32_t c); static Range * shr(const Range *lhs, int32_t c); - static bool precisionLossMul(const Range *lhs, const Range *rhs); static bool negativeZeroMul(const Range *lhs, const Range *rhs); inline void makeLowerInfinite() { lower_infinite_ = true; lower_ = JSVAL_INT_MIN; + if (max_exponent_ < MaxInt32Exponent) + max_exponent_ = MaxInt32Exponent; } inline void makeUpperInfinite() { upper_infinite_ = true; upper_ = JSVAL_INT_MAX; + if (max_exponent_ < MaxInt32Exponent) + max_exponent_ = MaxInt32Exponent; } inline void makeRangeInfinite() { makeLowerInfinite(); makeUpperInfinite(); + max_exponent_ = MaxDoubleExponent; } inline bool isLowerInfinite() const { @@ -205,10 +253,30 @@ class Range : public TempObject { return upper_infinite_; } - inline bool isFinite() const { + inline bool isInt32() const { return !isLowerInfinite() && !isUpperInfinite(); } + inline bool hasRoundingErrors() const { + return isDecimal() || exponent() >= MaxTruncatableExponent; + } + + inline bool isInfinite() const { + return exponent() >= MaxDoubleExponent; + } + + inline bool isDecimal() const { + return decimal_; + } + + inline uint16_t exponent() const { + return max_exponent_; + } + + inline uint16_t numBits() const { + return max_exponent_ + 1; // 2^0 -> 1 + } + inline int32_t lower() const { return lower_; } @@ -217,9 +285,10 @@ class Range : public TempObject { return upper_; } - inline void setLower(int64_t x) { + inline void setLowerInit(int64_t x) { if (x > JSVAL_INT_MAX) { // c.c lower_ = JSVAL_INT_MAX; + lower_infinite_ = false; } else if (x < JSVAL_INT_MIN) { makeLowerInfinite(); } else { @@ -227,19 +296,66 @@ class Range : public TempObject { lower_infinite_ = false; } } - inline void setUpper(int64_t x) { + inline void setLower(int64_t x) { + setLowerInit(x); + rectifyExponent(); + JS_ASSERT_IF(lower_infinite_, lower_ == JSVAL_INT_MIN); + } + inline void setUpperInit(int64_t x) { if (x > JSVAL_INT_MAX) { makeUpperInfinite(); } else if (x < JSVAL_INT_MIN) { // c.c upper_ = JSVAL_INT_MIN; + upper_infinite_ = false; } else { upper_ = (int32_t)x; upper_infinite_ = false; } } - void set(int64_t l, int64_t h) { - setLower(l); - setUpper(h); + inline void setUpper(int64_t x) { + setUpperInit(x); + rectifyExponent(); + JS_ASSERT_IF(upper_infinite_, upper_ == JSVAL_INT_MAX); + } + + inline void setInt32() { + lower_infinite_ = false; + upper_infinite_ = false; + decimal_ = false; + max_exponent_ = MaxInt32Exponent; + } + + inline void set(int64_t l, int64_t h, bool d, uint16_t e) { + setLowerInit(l); + setUpperInit(h); + decimal_ = d; + max_exponent_ = e; + rectifyExponent(); + JS_ASSERT_IF(lower_infinite_, lower_ == JSVAL_INT_MIN); + JS_ASSERT_IF(upper_infinite_, upper_ == JSVAL_INT_MAX); + } + + // Truncate the range to an Int32 range. + void truncate(); + + // Set the exponent by using the precise range analysis on the full + // range of Int32 values. This might shrink the exponent after some + // operations. + // + // Note: + // exponent of JSVAL_INT_MIN == 32 + // exponent of JSVAL_INT_MAX == 31 + inline void rectifyExponent() { + if (!isInt32()) { + JS_ASSERT(max_exponent_ >= MaxInt32Exponent); + return; + } + + uint32_t max = Max(abs64((int64_t) lower()), abs64((int64_t) upper())); + JS_ASSERT_IF(lower() == JSVAL_INT_MIN, max == (uint32_t) JSVAL_INT_MIN); + JS_ASSERT(max <= (uint32_t) JSVAL_INT_MIN); + // The number of bits needed to encode |max| is the power of 2 plus one. + max_exponent_ = max ? js_FloorLog2wImpl(max) : max; } const SymbolicBound *symbolicLower() const { @@ -249,10 +365,10 @@ class Range : public TempObject { return symbolicUpper_; } - void setSymbolicLower(SymbolicBound *bound) { + inline void setSymbolicLower(SymbolicBound *bound) { symbolicLower_ = bound; } - void setSymbolicUpper(SymbolicBound *bound) { + inline void setSymbolicUpper(SymbolicBound *bound) { symbolicUpper_ = bound; } }; diff --git a/js/src/ion/arm/CodeGenerator-arm.cpp b/js/src/ion/arm/CodeGenerator-arm.cpp index 879cd31bc138..901edab8aa80 100644 --- a/js/src/ion/arm/CodeGenerator-arm.cpp +++ b/js/src/ion/arm/CodeGenerator-arm.cpp @@ -1155,26 +1155,6 @@ CodeGeneratorARM::visitUnbox(LUnbox *unbox) return true; } -void -CodeGeneratorARM::linkAbsoluteLabels() -{ - // arm doesn't have deferred doubles, so this whole thing should be a NOP (right?) - // deferred doubles are an x86 mechanism for loading doubles into registers by storing - // them after the function body, then referring to them by their absolute address. - // On arm, everything should just go in a pool. -# if 0 - JS_NOT_REACHED("Absolute Labels NYI"); - UnrootedScript script = gen->info().script(); - IonCode *method = script->ion->method(); - - for (size_t i = 0; i < deferredDoubles_.length(); i++) { - DeferredDouble *d = deferredDoubles_[i]; - const Value &v = script->ion->getConstant(d->index()); - MacroAssembler::Bind(method, d->label(), &v); - } -#endif -} - bool CodeGeneratorARM::visitDouble(LDouble *ins) { diff --git a/js/src/ion/arm/CodeGenerator-arm.h b/js/src/ion/arm/CodeGenerator-arm.h index 4b82925e2845..87ef62b6b70f 100644 --- a/js/src/ion/arm/CodeGenerator-arm.h +++ b/js/src/ion/arm/CodeGenerator-arm.h @@ -117,9 +117,6 @@ class CodeGeneratorARM : public CodeGeneratorShared void storeElementTyped(const LAllocation *value, MIRType valueType, MIRType elementType, const Register &elements, const LAllocation *index); - protected: - void linkAbsoluteLabels(); - public: CodeGeneratorARM(MIRGenerator *gen, LIRGraph *graph); diff --git a/js/src/ion/shared/Assembler-shared.h b/js/src/ion/shared/Assembler-shared.h index c144385595ee..6a98c98f6d61 100644 --- a/js/src/ion/shared/Assembler-shared.h +++ b/js/src/ion/shared/Assembler-shared.h @@ -331,6 +331,9 @@ class CodeLabel public: CodeLabel() { } + CodeLabel(const AbsoluteLabel &dest) + : dest_(dest) + { } AbsoluteLabel *dest() { return &dest_; } diff --git a/js/src/ion/shared/Assembler-x86-shared.h b/js/src/ion/shared/Assembler-x86-shared.h index 04782a11ce71..1b6fc5cd7ee8 100644 --- a/js/src/ion/shared/Assembler-x86-shared.h +++ b/js/src/ion/shared/Assembler-x86-shared.h @@ -28,8 +28,8 @@ class AssemblerX86Shared { } }; - js::Vector codeLabels_; - js::Vector jumps_; + Vector codeLabels_; + Vector jumps_; CompactBufferWriter jumpRelocations_; CompactBufferWriter dataRelocations_; CompactBufferWriter preBarriers_; @@ -207,6 +207,10 @@ class AssemblerX86Shared masm.jumpTablePointer(label->prev()); label->setPrev(masm.size()); } + void writeDoubleConstant(double d, Label *label) { + label->bind(masm.size()); + masm.doubleConstant(d); + } void movl(const Imm32 &imm32, const Register &dest) { masm.movl_i32r(imm32.value, dest.code()); } diff --git a/js/src/ion/shared/CodeGenerator-shared.h b/js/src/ion/shared/CodeGenerator-shared.h index a3d8a61c8d18..bc215e0c7bda 100644 --- a/js/src/ion/shared/CodeGenerator-shared.h +++ b/js/src/ion/shared/CodeGenerator-shared.h @@ -313,9 +313,6 @@ class CodeGeneratorShared : public LInstructionVisitor bool addOutOfLineCode(OutOfLineCode *code); bool generateOutOfLineCode(); - void linkAbsoluteLabels() { - } - private: void generateInvalidateEpilogue(); diff --git a/js/src/ion/shared/CodeGenerator-x86-shared.cpp b/js/src/ion/shared/CodeGenerator-x86-shared.cpp index 22a195ed3ffd..533f88026113 100644 --- a/js/src/ion/shared/CodeGenerator-x86-shared.cpp +++ b/js/src/ion/shared/CodeGenerator-x86-shared.cpp @@ -88,6 +88,14 @@ CodeGeneratorX86Shared::emitBranch(Assembler::Condition cond, MBasicBlock *mirTr } } +bool +CodeGeneratorX86Shared::visitDouble(LDouble *ins) +{ + const LDefinition *out = ins->getDef(0); + masm.loadConstantDouble(ins->getDouble(), ToFloatRegister(out)); + return true; +} + bool CodeGeneratorX86Shared::visitTestIAndBranch(LTestIAndBranch *test) { diff --git a/js/src/ion/shared/CodeGenerator-x86-shared.h b/js/src/ion/shared/CodeGenerator-x86-shared.h index a21be78c22d4..f3c24f32f57a 100644 --- a/js/src/ion/shared/CodeGenerator-x86-shared.h +++ b/js/src/ion/shared/CodeGenerator-x86-shared.h @@ -77,6 +77,7 @@ class CodeGeneratorX86Shared : public CodeGeneratorShared public: // Instruction visitors. + virtual bool visitDouble(LDouble *ins); virtual bool visitMinMaxD(LMinMaxD *ins); virtual bool visitAbsD(LAbsD *ins); virtual bool visitSqrtD(LSqrtD *ins); diff --git a/js/src/ion/shared/LIR-x86-shared.h b/js/src/ion/shared/LIR-x86-shared.h index 7db2bb57782c..d928daaf07d7 100644 --- a/js/src/ion/shared/LIR-x86-shared.h +++ b/js/src/ion/shared/LIR-x86-shared.h @@ -205,6 +205,23 @@ class LMulI : public LBinaryMath<0, 1> } }; +// Constant double. +class LDouble : public LInstructionHelper<1, 0, 0> +{ + double d_; + + public: + LIR_HEADER(Double) + + LDouble(double d) + : d_(d) + { } + + double getDouble() const { + return d_; + } +}; + } // namespace ion } // namespace js diff --git a/js/src/ion/shared/Lowering-x86-shared.cpp b/js/src/ion/shared/Lowering-x86-shared.cpp index f48efb0ba8be..1568175683fd 100644 --- a/js/src/ion/shared/Lowering-x86-shared.cpp +++ b/js/src/ion/shared/Lowering-x86-shared.cpp @@ -125,3 +125,23 @@ LIRGeneratorX86Shared::lowerUrshD(MUrsh *mir) LUrshD *lir = new LUrshD(lhsUse, rhsAlloc, tempCopy(lhs, 0)); return define(lir, mir); } + +bool +LIRGeneratorX86Shared::lowerConstantDouble(double d, MInstruction *mir) +{ + return define(new LDouble(d), mir); +} + +bool +LIRGeneratorX86Shared::visitConstant(MConstant *ins) +{ + if (ins->type() == MIRType_Double) + return lowerConstantDouble(ins->value().toDouble(), ins); + + // Emit non-double constants at their uses. + if (ins->canEmitAtUses()) + return emitAtUses(ins); + + return LIRGeneratorShared::visitConstant(ins); +} + diff --git a/js/src/ion/shared/Lowering-x86-shared.h b/js/src/ion/shared/Lowering-x86-shared.h index 2a3005ad87c5..35e71137c609 100644 --- a/js/src/ion/shared/Lowering-x86-shared.h +++ b/js/src/ion/shared/Lowering-x86-shared.h @@ -28,10 +28,12 @@ class LIRGeneratorX86Shared : public LIRGeneratorShared bool visitInterruptCheck(MInterruptCheck *ins); bool visitGuardShape(MGuardShape *ins); bool visitPowHalf(MPowHalf *ins); + bool visitConstant(MConstant *ins); bool lowerMulI(MMul *mul, MDefinition *lhs, MDefinition *rhs); bool lowerDivI(MDiv *div); bool lowerModI(MMod *mod); bool lowerUrshD(MUrsh *mir); + bool lowerConstantDouble(double d, MInstruction *ins); }; } // namespace ion diff --git a/js/src/ion/x64/CodeGenerator-x64.cpp b/js/src/ion/x64/CodeGenerator-x64.cpp index 074d92147809..e56c5330ca6e 100644 --- a/js/src/ion/x64/CodeGenerator-x64.cpp +++ b/js/src/ion/x64/CodeGenerator-x64.cpp @@ -41,14 +41,6 @@ CodeGeneratorX64::ToTempValue(LInstruction *ins, size_t pos) return ValueOperand(ToRegister(ins->getTemp(pos))); } -bool -CodeGeneratorX64::visitDouble(LDouble *ins) -{ - const LDefinition *out = ins->output(); - masm.loadConstantDouble(ins->getDouble(), ToFloatRegister(out)); - return true; -} - FrameSizeClass FrameSizeClass::FromDepth(uint32_t frameDepth) { diff --git a/js/src/ion/x64/CodeGenerator-x64.h b/js/src/ion/x64/CodeGenerator-x64.h index f3b4e871f1d5..9e1b06b15468 100644 --- a/js/src/ion/x64/CodeGenerator-x64.h +++ b/js/src/ion/x64/CodeGenerator-x64.h @@ -41,7 +41,6 @@ class CodeGeneratorX64 : public CodeGeneratorX86Shared bool visitOsrValue(LOsrValue *value); bool visitBox(LBox *box); bool visitUnbox(LUnbox *unbox); - bool visitDouble(LDouble *ins); bool visitLoadSlotV(LLoadSlotV *ins); bool visitLoadSlotT(LLoadSlotT *load); bool visitStoreSlotT(LStoreSlotT *store); diff --git a/js/src/ion/x64/LIR-x64.h b/js/src/ion/x64/LIR-x64.h index 6879e9c4f580..6b3c4cf0478a 100644 --- a/js/src/ion/x64/LIR-x64.h +++ b/js/src/ion/x64/LIR-x64.h @@ -66,23 +66,6 @@ class LUnboxDouble : public LUnboxBase { { } }; -// Constant double. -class LDouble : public LInstructionHelper<1, 0, 0> -{ - double d_; - - public: - LIR_HEADER(Double) - - LDouble(double d) - : d_(d) - { } - - double getDouble() const { - return d_; - } -}; - } // namespace ion } // namespace js diff --git a/js/src/ion/x64/Lowering-x64.cpp b/js/src/ion/x64/Lowering-x64.cpp index 4258f47e8de7..282df031f90d 100644 --- a/js/src/ion/x64/Lowering-x64.cpp +++ b/js/src/ion/x64/Lowering-x64.cpp @@ -36,25 +36,6 @@ LIRGeneratorX64::useBoxFixed(LInstruction *lir, size_t n, MDefinition *mir, Regi return true; } -bool -LIRGeneratorX64::lowerConstantDouble(double d, MInstruction *mir) -{ - return define(new LDouble(d), mir); -} - -bool -LIRGeneratorX64::visitConstant(MConstant *ins) -{ - if (ins->type() == MIRType_Double) - return lowerConstantDouble(ins->value().toDouble(), ins); - - // Emit non-double constants at their uses. - if (ins->canEmitAtUses()) - return emitAtUses(ins); - - return LIRGeneratorShared::visitConstant(ins); -} - bool LIRGeneratorX64::visitBox(MBox *box) { diff --git a/js/src/ion/x64/Lowering-x64.h b/js/src/ion/x64/Lowering-x64.h index fa9c35c92f6c..de6490ad3c0b 100644 --- a/js/src/ion/x64/Lowering-x64.h +++ b/js/src/ion/x64/Lowering-x64.h @@ -37,10 +37,7 @@ class LIRGeneratorX64 : public LIRGeneratorX86Shared MDefinition *rhs); bool lowerForFPU(LMathD *ins, MDefinition *mir, MDefinition *lhs, MDefinition *rhs); - bool lowerConstantDouble(double d, MInstruction *ins); - public: - bool visitConstant(MConstant *ins); bool visitBox(MBox *box); bool visitUnbox(MUnbox *unbox); bool visitReturn(MReturn *ret); diff --git a/js/src/ion/x86/Assembler-x86.h b/js/src/ion/x86/Assembler-x86.h index f7372b2caaf9..be1ca9f0d0c3 100644 --- a/js/src/ion/x86/Assembler-x86.h +++ b/js/src/ion/x86/Assembler-x86.h @@ -235,10 +235,6 @@ class Assembler : public AssemblerX86Shared static void TraceJumpRelocations(JSTracer *trc, IonCode *code, CompactBufferReader &reader); - // The buffer is about to be linked, make sure any constant pools or excess - // bookkeeping has been flushed to the instruction stream. - void finish() { } - // Copy the assembly code to the given buffer, and perform any pending // relocations relying on the target address. void executableCopy(uint8_t *buffer); @@ -405,13 +401,6 @@ class Assembler : public AssemblerX86Shared void movsd(const double *dp, const FloatRegister &dest) { masm.movsd_mr((const void *)dp, dest.code()); } - void movsd(AbsoluteLabel *label, const FloatRegister &dest) { - JS_ASSERT(!label->bound()); - // Thread the patch list through the unpatched address word in the - // instruction stream. - masm.movsd_mr(reinterpret_cast(label->prev()), dest.code()); - label->setPrev(masm.size()); - } }; // Get a register in which we plan to put a quantity that will be used as an diff --git a/js/src/ion/x86/CodeGenerator-x86.cpp b/js/src/ion/x86/CodeGenerator-x86.cpp index 0c6a0576d4d6..ae2411d788aa 100644 --- a/js/src/ion/x86/CodeGenerator-x86.cpp +++ b/js/src/ion/x86/CodeGenerator-x86.cpp @@ -139,45 +139,6 @@ CodeGeneratorX86::visitUnbox(LUnbox *unbox) return true; } -void -CodeGeneratorX86::linkAbsoluteLabels() -{ - ExecutionMode executionMode = gen->info().executionMode(); - UnrootedScript script = gen->info().script(); - IonScript *ionScript = GetIonScript(script, executionMode); - IonCode *method = ionScript->method(); - - for (size_t i = 0; i < deferredDoubles_.length(); i++) { - DeferredDouble *d = deferredDoubles_[i]; - const Value &v = ionScript->getConstant(d->index()); - MacroAssembler::Bind(method, d->label(), &v); - } -} - -bool -CodeGeneratorX86::visitDouble(LDouble *ins) -{ - const LDefinition *out = ins->getDef(0); - const LConstantIndex *cindex = ins->getOperand(0)->toConstantIndex(); - const Value &v = graph.getConstant(cindex->index()); - - union DoublePun { - uint64_t u; - double d; - } dpun; - dpun.d = v.toDouble(); - - if (masm.maybeInlineDouble(dpun.u, ToFloatRegister(out))) - return true; - - DeferredDouble *d = new DeferredDouble(cindex->index()); - if (!deferredDoubles_.append(d)) - return false; - - masm.movsd(d->label(), ToFloatRegister(out)); - return true; -} - bool CodeGeneratorX86::visitLoadSlotV(LLoadSlotV *load) { diff --git a/js/src/ion/x86/CodeGenerator-x86.h b/js/src/ion/x86/CodeGenerator-x86.h index c1a710d031cf..a408ba2358f1 100644 --- a/js/src/ion/x86/CodeGenerator-x86.h +++ b/js/src/ion/x86/CodeGenerator-x86.h @@ -16,26 +16,7 @@ namespace ion { class CodeGeneratorX86 : public CodeGeneratorX86Shared { - class DeferredDouble : public TempObject - { - AbsoluteLabel label_; - uint32_t index_; - - public: - DeferredDouble(uint32_t index) : index_(index) - { } - - AbsoluteLabel *label() { - return &label_; - } - uint32_t index() const { - return index_; - } - }; - private: - js::Vector deferredDoubles_; - CodeGeneratorX86 *thisFromCtor() { return this; } @@ -48,9 +29,6 @@ class CodeGeneratorX86 : public CodeGeneratorX86Shared void storeElementTyped(const LAllocation *value, MIRType valueType, MIRType elementType, const Register &elements, const LAllocation *index); - protected: - void linkAbsoluteLabels(); - public: CodeGeneratorX86(MIRGenerator *gen, LIRGraph *graph); @@ -60,7 +38,6 @@ class CodeGeneratorX86 : public CodeGeneratorX86Shared bool visitUnbox(LUnbox *unbox); bool visitValue(LValue *value); bool visitOsrValue(LOsrValue *value); - bool visitDouble(LDouble *ins); bool visitLoadSlotV(LLoadSlotV *load); bool visitLoadSlotT(LLoadSlotT *load); bool visitStoreSlotT(LStoreSlotT *store); diff --git a/js/src/ion/x86/LIR-x86.h b/js/src/ion/x86/LIR-x86.h index 98c2458ee366..7f744623a006 100644 --- a/js/src/ion/x86/LIR-x86.h +++ b/js/src/ion/x86/LIR-x86.h @@ -70,17 +70,6 @@ class LUnboxDouble : public LInstructionHelper<1, 2, 0> } }; -// Constant double. -class LDouble : public LInstructionHelper<1, 1, 0> -{ - public: - LIR_HEADER(Double); - - LDouble(const LConstantIndex &cindex) { - setOperand(0, cindex); - } -}; - } // namespace ion } // namespace js diff --git a/js/src/ion/x86/Lowering-x86.cpp b/js/src/ion/x86/Lowering-x86.cpp index 751405fc49d6..b4e8f62540cd 100644 --- a/js/src/ion/x86/Lowering-x86.cpp +++ b/js/src/ion/x86/Lowering-x86.cpp @@ -40,35 +40,6 @@ LIRGeneratorX86::useBoxFixed(LInstruction *lir, size_t n, MDefinition *mir, Regi return true; } -bool -LIRGeneratorX86::lowerConstantDouble(double d, MInstruction *mir) -{ - uint32_t index; - if (!lirGraph_.addConstantToPool(DoubleValue(d), &index)) - return false; - - LDouble *lir = new LDouble(LConstantIndex::FromIndex(index)); - return define(lir, mir); -} - -bool -LIRGeneratorX86::visitConstant(MConstant *ins) -{ - if (ins->type() == MIRType_Double) { - uint32_t index; - if (!lirGraph_.addConstantToPool(ins->value(), &index)) - return false; - LDouble *lir = new LDouble(LConstantIndex::FromIndex(index)); - return define(lir, ins); - } - - // Emit non-double constants at their uses. - if (ins->canEmitAtUses()) - return emitAtUses(ins); - - return LIRGeneratorShared::visitConstant(ins); -} - bool LIRGeneratorX86::visitBox(MBox *box) { diff --git a/js/src/ion/x86/Lowering-x86.h b/js/src/ion/x86/Lowering-x86.h index 89ffa3df5feb..bf7d5749d4d9 100644 --- a/js/src/ion/x86/Lowering-x86.h +++ b/js/src/ion/x86/Lowering-x86.h @@ -39,10 +39,7 @@ class LIRGeneratorX86 : public LIRGeneratorX86Shared bool lowerForFPU(LInstructionHelper<1, 2, 0> *ins, MDefinition *mir, MDefinition *lhs, MDefinition *rhs); - bool lowerConstantDouble(double d, MInstruction *ins); - public: - bool visitConstant(MConstant *ins); bool visitBox(MBox *box); bool visitUnbox(MUnbox *unbox); bool visitReturn(MReturn *ret); diff --git a/js/src/ion/x86/MacroAssembler-x86.cpp b/js/src/ion/x86/MacroAssembler-x86.cpp index 39cef1576675..908bcc25c02e 100644 --- a/js/src/ion/x86/MacroAssembler-x86.cpp +++ b/js/src/ion/x86/MacroAssembler-x86.cpp @@ -14,6 +14,54 @@ using namespace js; using namespace js::ion; +void +MacroAssemblerX86::loadConstantDouble(double d, const FloatRegister &dest) +{ + union DoublePun { + uint64_t u; + double d; + } dpun; + dpun.d = d; + if (maybeInlineDouble(dpun.u, dest)) + return; + + if (!doubleMap_.initialized()) { + enoughMemory_ &= doubleMap_.init(); + if (!enoughMemory_) + return; + } + size_t doubleIndex; + DoubleMap::AddPtr p = doubleMap_.lookupForAdd(d); + if (p) { + doubleIndex = p->value; + } else { + doubleIndex = doubles_.length(); + enoughMemory_ &= doubles_.append(Double(d)); + enoughMemory_ &= doubleMap_.add(p, d, doubleIndex); + if (!enoughMemory_) + return; + } + Double &dbl = doubles_[doubleIndex]; + masm.movsd_mr(reinterpret_cast(dbl.uses.prev()), dest.code()); + dbl.uses.setPrev(masm.size()); +} + +void +MacroAssemblerX86::finish() +{ + if (doubles_.empty()) + return; + + masm.align(sizeof(double)); + for (size_t i = 0; i < doubles_.length(); i++) { + CodeLabel cl(doubles_[i].uses); + writeDoubleConstant(doubles_[i].value, cl.src()); + enoughMemory_ &= addCodeLabel(cl); + if (!enoughMemory_) + return; + } +} + void MacroAssemblerX86::setupABICall(uint32_t args) { diff --git a/js/src/ion/x86/MacroAssembler-x86.h b/js/src/ion/x86/MacroAssembler-x86.h index 05282a7f376d..cde0b0302b35 100644 --- a/js/src/ion/x86/MacroAssembler-x86.h +++ b/js/src/ion/x86/MacroAssembler-x86.h @@ -28,6 +28,16 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared bool dynamicAlignment_; bool enoughMemory_; + struct Double { + double value; + AbsoluteLabel uses; + Double(double value) : value(value) {} + }; + Vector doubles_; + + typedef HashMap, SystemAllocPolicy> DoubleMap; + DoubleMap doubleMap_; + protected: MoveResolver moveResolver_; @@ -62,6 +72,10 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared { } + // The buffer is about to be linked, make sure any constant pools or excess + // bookkeeping has been flushed to the instruction stream. + void finish(); + bool oom() const { return MacroAssemblerX86Shared::oom() || !enoughMemory_; } @@ -639,6 +653,7 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared cvtsi2sd(operand.payloadReg(), dest); } + void loadConstantDouble(double d, const FloatRegister &dest); void loadStaticDouble(const double *dp, const FloatRegister &dest) { movsd(dp, dest); } diff --git a/js/src/jit-test/jit_test.py b/js/src/jit-test/jit_test.py index 2d8da27b1bbc..c891eec78ebc 100755 --- a/js/src/jit-test/jit_test.py +++ b/js/src/jit-test/jit_test.py @@ -152,12 +152,12 @@ def main(argv): # Below, equivalents the old shell flags: ,m,am,amd,n,mn,amn,amdn,mdn ['--no-ion', '--no-jm', '--no-ti'], ['--no-ion', '--no-ti'], - ['--no-ion', '--no-ti', '-a', '-d'], + ['--no-ion', '--no-ti', '--always-mjit', '--debugjit'], ['--no-ion', '--no-jm'], ['--no-ion'], - ['--no-ion', '-a'], - ['--no-ion', '-a', '-d'], - ['--no-ion', '-d'] + ['--no-ion', '--always-mjit'], + ['--no-ion', '--always-mjit', '--debugjit'], + ['--no-ion', '--debugjit'] ] for test in test_list: for variant in flags: diff --git a/js/src/jit-test/tests/ion/bug844059.js b/js/src/jit-test/tests/ion/bug844059.js new file mode 100644 index 000000000000..d4bbe5c66414 --- /dev/null +++ b/js/src/jit-test/tests/ion/bug844059.js @@ -0,0 +1,12 @@ + +function assertArraysFirstEqual(a, b) { + assertEq(a[0], b[0]); +} + +function check(b) { + var a = deserialize(serialize(b)); + assertArraysFirstEqual(a, b); +} + +check(new Int8Array(1)); +check(new Float64Array(1)); diff --git a/js/src/jit-test/tests/ion/range-analysis.js b/js/src/jit-test/tests/ion/range-analysis.js new file mode 100644 index 000000000000..343b2c11768b --- /dev/null +++ b/js/src/jit-test/tests/ion/range-analysis.js @@ -0,0 +1,36 @@ +// |jit-test| no-jm +// Disable JM until it got investigated in Bug 843902. + +// Only fails with Ion. +function add_xors_1() { + var res = 0; + var step = 4; + for (var i = 0x7fffffff | 0; i >= (1 << step); i -= (i >> step)) { + var x = i ^ (i << 1); + res += (((x + x) + res + res) | 0); + } + return res; +} + +var r1 = add_xors_1(); +for (var i = 0; i < 100; i++) { + var r2 = add_xors_1(); + assertEq(r2, r1); +} + +// Only fails with JM +function add_xors_2() { + var res = 0; + var step = 4; + for (var i = 0x7fffffff | 0; i >= (1 << step); i -= (i >> step)) { + var x = i ^ (i << 1); + res += ((x + x) + res + res) | 0; + } + return res; +} + +var r1 = add_xors_2(); +for (var i = 0; i < 100; i++) { + var r2 = add_xors_2(); + assertEq(r1, r2); +} diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 232ffd03e86d..1b7fa67abc55 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -5307,7 +5307,8 @@ JS_BufferIsCompilableUnit(JSContext *cx, JSObject *objArg, const char *utf8, siz { CompileOptions options(cx); options.setCompileAndGo(false); - Parser parser(cx, options, chars, length, /* foldConstants = */ true); + Parser parser(cx, options, chars, length, + /* foldConstants = */ true); if (parser.init()) { older = JS_SetErrorReporter(cx, NULL); if (!parser.parse(obj) && diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index d3b7f33e26b7..c555029b764d 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -569,7 +569,7 @@ struct MallocProvider namespace gc { class MarkingValidator; } // namespace gc -class AutoEnterPolicy; +class JS_FRIEND_API(AutoEnterPolicy); } // namespace js struct JSRuntime : js::RuntimeFriendFields, diff --git a/js/src/jsproxy.cpp b/js/src/jsproxy.cpp index d344998c2aa3..b9318ff54a9a 100644 --- a/js/src/jsproxy.cpp +++ b/js/src/jsproxy.cpp @@ -2561,7 +2561,7 @@ Proxy::iterate(JSContext *cx, HandleObject proxy, unsigned flags, MutableHandleV vp.setUndefined(); // default result if we refuse to perform this action if (!handler->hasPrototype()) { AutoEnterPolicy policy(cx, handler, proxy, JSID_VOID, - BaseProxyHandler::GET, /* mayThrow = */ false); + BaseProxyHandler::GET, true); // If the policy denies access but wants us to return true, we need // to hand a valid (empty) iterator object to the caller. if (!policy.allowed()) { diff --git a/js/src/jsprvtd.h b/js/src/jsprvtd.h index 6d9a2061f549..f15b1cf13947 100644 --- a/js/src/jsprvtd.h +++ b/js/src/jsprvtd.h @@ -160,10 +160,12 @@ struct Token; struct TokenPos; struct TokenPtr; class TokenStream; -struct Parser; class ParseMapPool; struct ParseNode; +template +struct Parser; + } /* namespace frontend */ namespace analyze { diff --git a/js/src/jsreflect.cpp b/js/src/jsreflect.cpp index e54c85a88597..2e1b74839a7e 100644 --- a/js/src/jsreflect.cpp +++ b/js/src/jsreflect.cpp @@ -1457,7 +1457,7 @@ NodeBuilder::function(ASTType type, TokenPos *pos, class ASTSerializer { JSContext *cx; - Parser *parser; + Parser *parser; NodeBuilder builder; DebugOnly lineno; @@ -1551,7 +1551,7 @@ class ASTSerializer return builder.init(userobj); } - void setParser(Parser *p) { + void setParser(Parser *p) { parser = p; } @@ -2523,7 +2523,7 @@ ASTSerializer::expression(ParseNode *pn, MutableHandleValue dst) { /* The parser notes any uninitialized properties by setting the PNX_DESTRUCT flag. */ if (pn->pn_xflags & PNX_DESTRUCT) { - parser->reportError(pn, JSMSG_BAD_OBJECT_INIT); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_OBJECT_INIT); return false; } NodeVector elts(cx); @@ -3031,7 +3031,7 @@ reflect_parse(JSContext *cx, uint32_t argc, jsval *vp) size_t length = stable->length(); CompileOptions options(cx); options.setFileAndLine(filename, lineno); - Parser parser(cx, options, chars.get(), length, /* foldConstants = */ false); + Parser parser(cx, options, chars.get(), length, /* foldConstants = */ false); if (!parser.init()) return JS_FALSE; diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 4894234bdc09..e62687c45321 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -629,16 +629,8 @@ class JSScript : public js::gc::Cell JSFunction *function() const { return function_; } void setFunction(JSFunction *fun); - JSFunction *originalFunction() const { - if (!isCallsiteClone) - return NULL; - return enclosingScopeOrOriginalFunction_->toFunction(); - } - void setOriginalFunctionObject(JSObject *fun) { - JS_ASSERT(isCallsiteClone); - JS_ASSERT(fun->isFunction()); - enclosingScopeOrOriginalFunction_ = fun; - } + JSFunction *originalFunction() const; + void setOriginalFunctionObject(JSObject *fun); JSFlatString *sourceData(JSContext *cx); diff --git a/js/src/jsscriptinlines.h b/js/src/jsscriptinlines.h index 9dd5bfa18cc7..8eeddb0edb8e 100644 --- a/js/src/jsscriptinlines.h +++ b/js/src/jsscriptinlines.h @@ -206,4 +206,18 @@ JSScript::principals() return compartment()->principals; } +inline JSFunction * +JSScript::originalFunction() const { + if (!isCallsiteClone) + return NULL; + return enclosingScopeOrOriginalFunction_->toFunction(); +} + +inline void +JSScript::setOriginalFunctionObject(JSObject *fun) { + JS_ASSERT(isCallsiteClone); + JS_ASSERT(fun->isFunction()); + enclosingScopeOrOriginalFunction_ = fun; +} + #endif /* jsscriptinlines_h___ */ diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index b4c8cecc6553..325b91e1efb7 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -648,7 +648,7 @@ Version(JSContext *cx, unsigned argc, jsval *vp) jsval *argv = JS_ARGV(cx, vp); if (argc == 0 || JSVAL_IS_VOID(argv[0])) { /* Get version. */ - *vp = INT_TO_JSVAL(JS_GetVersion(cx)); + JS_SET_RVAL(cx, vp, INT_TO_JSVAL(JS_GetVersion(cx))); } else { /* Set version. */ int32_t v = -1; @@ -663,7 +663,7 @@ Version(JSContext *cx, unsigned argc, jsval *vp) JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_INVALID_ARGS, "version"); return false; } - *vp = INT_TO_JSVAL(JS_SetVersion(cx, JSVersion(v))); + JS_SET_RVAL(cx, vp, INT_TO_JSVAL(JS_SetVersion(cx, JSVersion(v)))); } return true; } @@ -672,7 +672,7 @@ static JSBool RevertVersion(JSContext *cx, unsigned argc, jsval *vp) { js_RevertVersion(cx); - JS_SET_RVAL(cx, vp, JSVAL_VOID); + JS_SET_RVAL(cx, vp, UndefinedValue()); return true; } @@ -722,7 +722,7 @@ Options(JSContext *cx, unsigned argc, jsval *vp) free(names); if (!str) return false; - *vp = STRING_TO_JSVAL(str); + JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(str)); return true; } @@ -752,7 +752,7 @@ Load(JSContext *cx, unsigned argc, jsval *vp) } } - JS_SET_RVAL(cx, vp, JSVAL_VOID); + JS_SET_RVAL(cx, vp, UndefinedValue()); return true; } @@ -953,7 +953,7 @@ Evaluate(JSContext *cx, unsigned argc, jsval *vp) } if (!JS_ExecuteScript(cx, global, script, vp)) { if (catchTermination && !JS_IsExceptionPending(cx)) { - *vp = StringValue(JS_NewStringCopyZ(cx, "terminated")); + JS_SET_RVAL(cx, vp, StringValue(JS_NewStringCopyZ(cx, "terminated"))); return true; } return false; @@ -1152,7 +1152,7 @@ ReadLine(JSContext *cx, unsigned argc, jsval *vp) /* Treat the empty string specially. */ if (buflength == 0) { - *vp = feof(from) ? JSVAL_NULL : JS_GetEmptyStringValue(cx); + JS_SET_RVAL(cx, vp, feof(from) ? NullValue() : JS_GetEmptyStringValue(cx)); JS_free(cx, buf); return true; } @@ -1175,7 +1175,7 @@ ReadLine(JSContext *cx, unsigned argc, jsval *vp) if (!str) return false; - *vp = STRING_TO_JSVAL(str); + JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(str)); return true; } @@ -1199,7 +1199,7 @@ PutStr(JSContext *cx, unsigned argc, jsval *vp) fflush(gOutFile); } - JS_SET_RVAL(cx, vp, JSVAL_VOID); + JS_SET_RVAL(cx, vp, UndefinedValue()); return true; } @@ -1237,7 +1237,7 @@ PrintInternal(JSContext *cx, unsigned argc, jsval *vp, FILE *file) fputc('\n', file); fflush(file); - JS_SET_RVAL(cx, vp, JSVAL_VOID); + JS_SET_RVAL(cx, vp, UndefinedValue()); return true; } @@ -1312,7 +1312,7 @@ AssertEq(JSContext *cx, unsigned argc, jsval *vp) } return false; } - JS_SET_RVAL(cx, vp, JSVAL_VOID); + JS_SET_RVAL(cx, vp, UndefinedValue()); return true; } @@ -1353,7 +1353,7 @@ SetDebug(JSContext *cx, unsigned argc, jsval *vp) bool ok = !!JS_SetDebugMode(cx, JSVAL_TO_BOOLEAN(argv[0])); if (ok) - JS_SET_RVAL(cx, vp, JSVAL_TRUE); + JS_SET_RVAL(cx, vp, BooleanValue(true)); return ok; } @@ -1451,7 +1451,7 @@ Trap(JSContext *cx, unsigned argc, jsval *vp) JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_TRAP_USAGE); return false; } - JS_SET_RVAL(cx, vp, JSVAL_VOID); + JS_SET_RVAL(cx, vp, UndefinedValue()); return JS_SetTrap(cx, script, script->code + i, TrapHandler, STRING_TO_JSVAL(str)); } @@ -1464,7 +1464,7 @@ Untrap(JSContext *cx, unsigned argc, jsval *vp) if (!GetScriptAndPCArgs(cx, argc, JS_ARGV(cx, vp), &script, &i)) return false; JS_ClearTrap(cx, script, script->code + i, NULL, NULL); - JS_SET_RVAL(cx, vp, JSVAL_VOID); + JS_SET_RVAL(cx, vp, UndefinedValue()); return true; } @@ -1490,7 +1490,7 @@ SetDebuggerHandler(JSContext *cx, unsigned argc, jsval *vp) return false; JS_SetDebuggerHandler(cx->runtime, DebuggerAndThrowHandler, str); - JS_SET_RVAL(cx, vp, JSVAL_VOID); + JS_SET_RVAL(cx, vp, UndefinedValue()); return true; } @@ -1509,7 +1509,7 @@ SetThrowHook(JSContext *cx, unsigned argc, jsval *vp) return false; JS_SetThrowHook(cx->runtime, DebuggerAndThrowHandler, str); - JS_SET_RVAL(cx, vp, JSVAL_VOID); + JS_SET_RVAL(cx, vp, UndefinedValue()); return true; } @@ -1540,7 +1540,7 @@ LineToPC(JSContext *cx, unsigned argc, jsval *vp) pc = JS_LineNumberToPC(cx, script, lineno); if (!pc) return false; - *vp = INT_TO_JSVAL(pc - script->code); + JS_SET_RVAL(cx, vp, INT_TO_JSVAL(pc - script->code)); return true; } @@ -1556,7 +1556,7 @@ PCToLine(JSContext *cx, unsigned argc, jsval *vp) lineno = JS_PCToLineNumber(cx, script, script->code + i); if (!lineno) return false; - *vp = INT_TO_JSVAL(lineno); + JS_SET_RVAL(cx, vp, INT_TO_JSVAL(lineno)); return true; } @@ -1869,7 +1869,7 @@ Disassemble(JSContext *cx, unsigned argc, jsval *vp) return false; fprintf(stdout, "%s\n", sprinter.string()); - JS_SET_RVAL(cx, vp, JSVAL_VOID); + JS_SET_RVAL(cx, vp, UndefinedValue()); return true; } @@ -1882,7 +1882,7 @@ DisassFile(JSContext *cx, unsigned argc, jsval *vp) return false; if (!p.argc) { - JS_SET_RVAL(cx, vp, JSVAL_VOID); + JS_SET_RVAL(cx, vp, UndefinedValue()); return true; } @@ -1916,7 +1916,7 @@ DisassFile(JSContext *cx, unsigned argc, jsval *vp) if (!ok) return false; - JS_SET_RVAL(cx, vp, JSVAL_VOID); + JS_SET_RVAL(cx, vp, UndefinedValue()); return true; } @@ -2010,7 +2010,7 @@ DisassWithSrc(JSContext *cx, unsigned argc, jsval *vp) bail: fclose(file); } - JS_SET_RVAL(cx, vp, JSVAL_VOID); + JS_SET_RVAL(cx, vp, UndefinedValue()); return ok; #undef LINE_BUF_LEN } @@ -2113,7 +2113,7 @@ DumpHeap(JSContext *cx, unsigned argc, jsval *vp) JS_ReportOutOfMemory(cx); return false; } - JS_SET_RVAL(cx, vp, JSVAL_VOID); + JS_SET_RVAL(cx, vp, UndefinedValue()); return true; not_traceable_arg: @@ -2131,7 +2131,7 @@ DumpObject(JSContext *cx, unsigned argc, jsval *vp) js_DumpObject(arg0); - JS_SET_RVAL(cx, vp, JSVAL_VOID); + JS_SET_RVAL(cx, vp, UndefinedValue()); return true; } @@ -2145,14 +2145,14 @@ BuildDate(JSContext *cx, unsigned argc, jsval *vp) sprintf(version, " for version %d\n", JS_VERSION); #endif fprintf(gOutFile, "built on %s at %s%s", __DATE__, __TIME__, version); - *vp = JSVAL_VOID; + JS_SET_RVAL(cx, vp, UndefinedValue()); return true; } static JSBool Intern(JSContext *cx, unsigned argc, jsval *vp) { - JSString *str = JS_ValueToString(cx, argc == 0 ? JSVAL_VOID : vp[2]); + JSString *str = JS_ValueToString(cx, argc == 0 ? UndefinedValue() : vp[2]); if (!str) return false; @@ -2164,7 +2164,7 @@ Intern(JSContext *cx, unsigned argc, jsval *vp) if (!JS_InternUCStringN(cx, chars, length)) return false; - JS_SET_RVAL(cx, vp, JSVAL_VOID); + JS_SET_RVAL(cx, vp, UndefinedValue()); return true; } @@ -2217,7 +2217,7 @@ Clone(JSContext *cx, unsigned argc, jsval *vp) JSObject *clone = JS_CloneFunctionObject(cx, funobj, parent); if (!clone) return false; - *vp = OBJECT_TO_JSVAL(clone); + JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(clone)); return true; } @@ -2230,17 +2230,17 @@ GetPDA(JSContext *cx, unsigned argc, jsval *vp) JSPropertyDesc *pd; jsval v; - if (!JS_ValueToObject(cx, argc == 0 ? JSVAL_VOID : vp[2], vobj.address())) + if (!JS_ValueToObject(cx, argc == 0 ? UndefinedValue() : vp[2], vobj.address())) return false; if (!vobj) { - *vp = JSVAL_VOID; + JS_SET_RVAL(cx, vp, UndefinedValue()); return true; } RootedObject aobj(cx, JS_NewArrayObject(cx, 0, NULL)); if (!aobj) return false; - *vp = OBJECT_TO_JSVAL(aobj); + JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(aobj)); ok = !!JS_GetPropertyDescArray(cx, vobj, &pda); if (!ok) @@ -2277,10 +2277,10 @@ GetSLX(JSContext *cx, unsigned argc, jsval *vp) { RootedScript script(cx); - script = ValueToScript(cx, argc == 0 ? JSVAL_VOID : vp[2]); + script = ValueToScript(cx, argc == 0 ? UndefinedValue() : vp[2]); if (!script) return false; - *vp = INT_TO_JSVAL(js_GetScriptLineExtent(script)); + JS_SET_RVAL(cx, vp, INT_TO_JSVAL(js_GetScriptLineExtent(script))); return true; } @@ -2289,9 +2289,9 @@ ToInt32(JSContext *cx, unsigned argc, jsval *vp) { int32_t i; - if (!JS_ValueToInt32(cx, argc == 0 ? JSVAL_VOID : vp[2], &i)) + if (!JS_ValueToInt32(cx, argc == 0 ? UndefinedValue() : vp[2], &i)) return false; - *vp = JS_NumberValue(i); + JS_SET_RVAL(cx, vp, JS_NumberValue(i)); return true; } @@ -2515,7 +2515,7 @@ ShapeOf(JSContext *cx, unsigned argc, JS::Value *vp) return false; } JSObject *obj = &v.toObject(); - *vp = JS_NumberValue((double) ((uintptr_t)obj->lastProperty() >> 3)); + JS_SET_RVAL(cx, vp, JS_NumberValue((double) ((uintptr_t)obj->lastProperty() >> 3))); return true; } @@ -2662,7 +2662,7 @@ Sleep_fn(JSContext *cx, unsigned argc, jsval *vp) } else { double t_secs; - if (!JS_ValueToNumber(cx, argc == 0 ? JSVAL_VOID : vp[2], &t_secs)) + if (!JS_ValueToNumber(cx, argc == 0 ? UndefinedValue() : vp[2], &t_secs)) return false; /* NB: The next condition also filter out NaNs. */ @@ -2914,7 +2914,7 @@ static JSBool Timeout(JSContext *cx, unsigned argc, jsval *vp) { if (argc == 0) { - *vp = JS_NumberValue(gTimeoutInterval); + JS_SET_RVAL(cx, vp, JS_NumberValue(gTimeoutInterval)); return true; } @@ -2936,7 +2936,7 @@ Timeout(JSContext *cx, unsigned argc, jsval *vp) gTimeoutFunc = value; } - *vp = JSVAL_VOID; + JS_SET_RVAL(cx, vp, UndefinedValue()); return SetTimeoutValue(cx, t); } @@ -2948,7 +2948,7 @@ Elapsed(JSContext *cx, unsigned argc, jsval *vp) JSShellContextData *data = GetContextData(cx); if (data) d = PRMJ_Now() - data->startTime; - *vp = JS_NumberValue(d); + JS_SET_RVAL(cx, vp, JS_NumberValue(d)); return true; } JS_ReportError(cx, "Wrong number of arguments"); @@ -2970,12 +2970,12 @@ Parent(JSContext *cx, unsigned argc, jsval *vp) } Rooted parent(cx, JS_GetParent(&v.toObject())); - *vp = OBJECT_TO_JSVAL(parent); + JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(parent)); /* Outerize if necessary. Embrace the ugliness! */ if (parent) { if (JSObjectOp op = parent->getClass()->ext.outerObject) - *vp = OBJECT_TO_JSVAL(op(cx, parent)); + JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(op(cx, parent))); } return true; @@ -3055,7 +3055,7 @@ Compile(JSContext *cx, unsigned argc, jsval *vp) JS_GetStringLength(scriptContents), "", 1); JS_SetOptions(cx, oldopts); - JS_SET_RVAL(cx, vp, JSVAL_VOID); + JS_SET_RVAL(cx, vp, UndefinedValue()); return ok; } @@ -3064,26 +3064,27 @@ Parse(JSContext *cx, unsigned argc, jsval *vp) { using namespace js::frontend; - if (argc < 1) { + CallArgs args = CallArgsFromVp(argc, vp); + + if (args.length() < 1) { JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED, - "compile", "0", "s"); + "parse", "0", "s"); return false; } - jsval arg0 = JS_ARGV(cx, vp)[0]; - if (!JSVAL_IS_STRING(arg0)) { - const char *typeName = JS_GetTypeName(cx, JS_TypeOfValue(cx, arg0)); + if (!args[0].isString()) { + const char *typeName = JS_GetTypeName(cx, JS_TypeOfValue(cx, args[0])); JS_ReportError(cx, "expected string to parse, got %s", typeName); return false; } - JSString *scriptContents = JSVAL_TO_STRING(arg0); + JSString *scriptContents = args[0].toString(); CompileOptions options(cx); options.setFileAndLine("", 1) .setCompileAndGo(false); - Parser parser(cx, options, - JS_GetStringCharsZ(cx, scriptContents), - JS_GetStringLength(scriptContents), - /* foldConstants = */ true); + Parser parser(cx, options, + JS_GetStringCharsZ(cx, scriptContents), + JS_GetStringLength(scriptContents), + /* foldConstants = */ true); if (!parser.init()) return false; @@ -3094,7 +3095,52 @@ Parse(JSContext *cx, unsigned argc, jsval *vp) DumpParseTree(pn); fputc('\n', stderr); #endif - JS_SET_RVAL(cx, vp, JSVAL_VOID); + args.rval().setUndefined(); + return true; +} + +static JSBool +SyntaxParse(JSContext *cx, unsigned argc, jsval *vp) +{ + using namespace js::frontend; + + CallArgs args = CallArgsFromVp(argc, vp); + + if (args.length() < 1) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED, + "parse", "0", "s"); + return false; + } + if (!args[0].isString()) { + const char *typeName = JS_GetTypeName(cx, JS_TypeOfValue(cx, args[0])); + JS_ReportError(cx, "expected string to parse, got %s", typeName); + return false; + } + + JSString *scriptContents = args[0].toString(); + CompileOptions options(cx); + options.setFileAndLine("", 1) + .setCompileAndGo(false); + + const jschar *chars = JS_GetStringCharsZ(cx, scriptContents); + size_t length = JS_GetStringLength(scriptContents); + Parser parser(cx, options, chars, length, + /* foldConstants = */ false); + if (!parser.init()) + return false; + + bool succeeded = parser.parse(NULL); + if (cx->isExceptionPending()) + return false; + + if (!succeeded && !parser.hadUnknownResult()) { + // If no exception is posted, either there was an OOM or a language + // feature unhandled by the syntax parser was encountered. + JS_ASSERT(cx->runtime->hadOutOfMemory); + return false; + } + + args.rval().setBoolean(succeeded); return true; } @@ -3161,14 +3207,14 @@ Snarf(JSContext *cx, unsigned argc, jsval *vp) JSObject *obj; if (!(obj = FileAsTypedArray(cx, pathname))) return false; - *vp = OBJECT_TO_JSVAL(obj); + JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(obj)); return true; } } if (!(str = FileAsString(cx, pathname))) return false; - *vp = STRING_TO_JSVAL(str); + JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(str)); return true; } @@ -3192,7 +3238,7 @@ System(JSContext *cx, unsigned argc, jsval *vp) int result = system(command.ptr()); - *vp = Int32Value(result); + JS_SET_RVAL(cx, vp, Int32Value(result)); return true; } @@ -3259,7 +3305,7 @@ ThisFilename(JSContext *cx, unsigned argc, Value *vp) static JSBool Wrap(JSContext *cx, unsigned argc, jsval *vp) { - jsval v = argc > 0 ? JS_ARGV(cx, vp)[0] : JSVAL_VOID; + jsval v = argc > 0 ? JS_ARGV(cx, vp)[0] : UndefinedValue(); if (JSVAL_IS_PRIMITIVE(v)) { JS_SET_RVAL(cx, vp, v); return true; @@ -3281,7 +3327,7 @@ Wrap(JSContext *cx, unsigned argc, jsval *vp) static JSBool WrapWithProto(JSContext *cx, unsigned argc, jsval *vp) { - Value obj = JSVAL_VOID, proto = JSVAL_VOID; + Value obj = UndefinedValue(), proto = UndefinedValue(); if (argc == 2) { obj = JS_ARGV(cx, vp)[0]; proto = JS_ARGV(cx, vp)[1]; @@ -3305,10 +3351,10 @@ WrapWithProto(JSContext *cx, unsigned argc, jsval *vp) static JSBool Serialize(JSContext *cx, unsigned argc, jsval *vp) { - jsval v = argc > 0 ? JS_ARGV(cx, vp)[0] : JSVAL_VOID; + jsval v = argc > 0 ? JS_ARGV(cx, vp)[0] : UndefinedValue(); uint64_t *datap; size_t nbytes; - if (!JS_WriteStructuredClone(cx, v, &datap, &nbytes, NULL, NULL, JSVAL_VOID)) + if (!JS_WriteStructuredClone(cx, v, &datap, &nbytes, NULL, NULL, UndefinedValue())) return false; JSObject *array = JS_NewUint8Array(cx, nbytes); @@ -3327,7 +3373,7 @@ Serialize(JSContext *cx, unsigned argc, jsval *vp) static JSBool Deserialize(JSContext *cx, unsigned argc, jsval *vp) { - Rooted v(cx, argc > 0 ? JS_ARGV(cx, vp)[0] : JSVAL_VOID); + Rooted v(cx, argc > 0 ? JS_ARGV(cx, vp)[0] : UndefinedValue()); JSObject *obj; if (JSVAL_IS_PRIMITIVE(v) || !(obj = JSVAL_TO_OBJECT(v))->isTypedArray()) { JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_INVALID_ARGS, "deserialize"); @@ -3402,7 +3448,7 @@ EnableStackWalkingAssertion(JSContext *cx, unsigned argc, jsval *vp) cx->stackIterAssertionEnabled = JSVAL_TO_BOOLEAN(JS_ARGV(cx, vp)[0]); #endif - JS_SET_RVAL(cx, vp, JSVAL_VOID); + JS_SET_RVAL(cx, vp, UndefinedValue()); return true; } @@ -3703,6 +3749,10 @@ static JSFunctionSpecWithHelp shell_functions[] = { "parse(code)", " Parses a string, potentially throwing."), + JS_FN_HELP("syntaxParse", SyntaxParse, 1, 0, +"syntaxParse(code)", +" Check the syntax of a string, returning success value"), + JS_FN_HELP("timeout", Timeout, 1, 0, "timeout([seconds], [func])", " Get/Set the limit in seconds for the execution time for the current context.\n" @@ -3881,7 +3931,7 @@ Help(JSContext *cx, unsigned argc, jsval *vp) } } - JS_SET_RVAL(cx, vp, JSVAL_VOID); + JS_SET_RVAL(cx, vp, UndefinedValue()); return true; } @@ -4028,7 +4078,7 @@ its_enumerate(JSContext *cx, HandleObject obj, JSIterateOp enum_op, case JSENUMERATE_DESTROY: /* Allow our iterator object to be GC'd. */ - *statep = JSVAL_NULL; + *statep = NullValue(); break; } @@ -4059,34 +4109,26 @@ its_convert(JSContext *cx, HandleObject obj, JSType type, MutableHandleValue vp) static void its_finalize(JSFreeOp *fop, JSObject *obj) { - jsval *rootedVal; if (its_noisy) fprintf(gOutFile, "finalizing it\n"); - rootedVal = (jsval *) JS_GetPrivate(obj); - if (rootedVal) { - JS_RemoveValueRootRT(fop->runtime(), rootedVal); - JS_SetPrivate(obj, NULL); - delete rootedVal; - } } static JSClass its_class = { - "It", JSCLASS_NEW_RESOLVE | JSCLASS_NEW_ENUMERATE | JSCLASS_HAS_PRIVATE, + "It", JSCLASS_NEW_RESOLVE | JSCLASS_NEW_ENUMERATE | JSCLASS_HAS_RESERVED_SLOTS(1), its_addProperty, its_delProperty, its_getProperty, its_setProperty, (JSEnumerateOp)its_enumerate, (JSResolveOp)its_resolve, - its_convert, its_finalize + its_convert, its_finalize, }; static JSBool its_getter(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp) { - if (JS_GetClass(obj) == &its_class) { - jsval *val = (jsval *) JS_GetPrivate(obj); - vp.set(val ? *val : JSVAL_VOID); - } else { - vp.set(JSVAL_VOID); + if (JS_GetClass(obj) != &its_class) { + vp.set(UndefinedValue()); + return true; } + vp.set(JS_GetReservedSlot(obj, 0)); return true; } @@ -4096,26 +4138,9 @@ its_setter(JSContext *cx, HandleObject obj, HandleId id, JSBool strict, MutableH if (JS_GetClass(obj) != &its_class) return true; - jsval *val = (jsval *) JS_GetPrivate(obj); - if (val) { - *val = vp; - return true; - } + JS_SetReservedSlot(obj, 0, vp); - val = new jsval; - if (!val) { - JS_ReportOutOfMemory(cx); - return false; - } - - if (!JS_AddValueRoot(cx, val)) { - delete val; - return false; - } - - JS_SetPrivate(obj, (void*)val); - - *val = vp; + vp.set(UndefinedValue()); return true; } @@ -4126,13 +4151,12 @@ its_get_customNative(JSContext *cx, unsigned argc, jsval *vp) if (!obj) return false; - if (JS_GetClass(obj) == &its_class) { - jsval *val = (jsval *) JS_GetPrivate(obj); - *vp = val ? *val : JSVAL_VOID; - } else { - *vp = JSVAL_VOID; + if (JS_GetClass(obj) != &its_class) { + JS_SET_RVAL(cx, vp, UndefinedValue()); + return true; } + JS_SET_RVAL(cx, vp, JS_GetReservedSlot(obj, 0)); return true; } @@ -4146,28 +4170,8 @@ its_set_customNative(JSContext *cx, unsigned argc, jsval *vp) if (JS_GetClass(obj) != &its_class) return true; - jsval *argv = JS_ARGV(cx, vp); - - jsval *val = (jsval *) JS_GetPrivate(obj); - if (val) { - *val = *argv; - return true; - } - - val = new jsval; - if (!val) { - JS_ReportOutOfMemory(cx); - return false; - } - - if (!JS_AddValueRoot(cx, val)) { - delete val; - return false; - } - - JS_SetPrivate(obj, (void *)val); - - *val = *argv; + JS_SetReservedSlot(obj, 0, argc >= 1 ? JS_ARGV(cx, vp)[0] : UndefinedValue()); + JS_SET_RVAL(cx, vp, UndefinedValue()); return true; } @@ -4212,7 +4216,7 @@ Exec(JSContext *cx, unsigned argc, jsval *vp) pid_t pid; int status; - JS_SET_RVAL(cx, vp, JSVAL_VOID); + JS_SET_RVAL(cx, vp, UndefinedValue()); fun = JS_ValueToFunction(cx, vp[0]); if (!fun) @@ -4481,7 +4485,7 @@ dom_get_x(JSContext* cx, JSHandleObject obj, void *self, JS::Value *vp) { JS_ASSERT(JS_GetClass(obj) == GetDomClass()); JS_ASSERT(self == (void *)0x1234); - *vp = JS_NumberValue(double(3.14)); + JS_SET_RVAL(cx, vp, JS_NumberValue(double(3.14))); return true; } @@ -4578,7 +4582,7 @@ dom_genericGetter(JSContext *cx, unsigned argc, JS::Value *vp) return false; if (JS_GetClass(obj) != &dom_class) { - *vp = JSVAL_VOID; + JS_SET_RVAL(cx, vp, UndefinedValue()); return true; } @@ -4600,7 +4604,7 @@ dom_genericSetter(JSContext* cx, unsigned argc, JS::Value* vp) JS_ASSERT(argc == 1); if (JS_GetClass(obj) != &dom_class) { - *vp = JSVAL_VOID; + JS_SET_RVAL(cx, vp, UndefinedValue()); return true; } @@ -4612,7 +4616,7 @@ dom_genericSetter(JSContext* cx, unsigned argc, JS::Value* vp) JSJitPropertyOp setter = info->op; if (!setter(cx, obj, val.toPrivate(), argv)) return false; - *vp = JSVAL_VOID; + JS_SET_RVAL(cx, vp, UndefinedValue()); return true; } @@ -4624,7 +4628,7 @@ dom_genericMethod(JSContext* cx, unsigned argc, JS::Value *vp) return false; if (JS_GetClass(obj) != &dom_class) { - *vp = JSVAL_VOID; + JS_SET_RVAL(cx, vp, UndefinedValue()); return true; } @@ -4769,10 +4773,10 @@ NewGlobalObject(JSContext *cx) if (!JS_DefineProperties(cx, it, its_props)) return NULL; - if (!JS_DefineProperty(cx, glob, "custom", JSVAL_VOID, its_getter, + if (!JS_DefineProperty(cx, glob, "custom", UndefinedValue(), its_getter, its_setter, 0)) return NULL; - if (!JS_DefineProperty(cx, glob, "customRdOnly", JSVAL_VOID, its_getter, + if (!JS_DefineProperty(cx, glob, "customRdOnly", UndefinedValue(), its_getter, its_setter, JSPROP_READONLY)) return NULL; @@ -5248,7 +5252,7 @@ main(int argc, char **argv, char **envp) rt = JS_NewRuntime(32L * 1024L * 1024L, JS_USE_HELPER_THREADS); if (!rt) return 1; - gTimeoutFunc = JSVAL_NULL; + gTimeoutFunc = NullValue(); if (!JS_AddNamedValueRootRT(rt, &gTimeoutFunc, "gTimeoutFunc")) return 1; diff --git a/js/src/tests/lib/jittests.py b/js/src/tests/lib/jittests.py index 1f49d4c69653..af7f69fbb987 100755 --- a/js/src/tests/lib/jittests.py +++ b/js/src/tests/lib/jittests.py @@ -104,15 +104,17 @@ class Test: elif name == 'tz-pacific': test.tz_pacific = True elif name == 'mjitalways': - test.jitflags.append('-a') + test.jitflags.append('--always-mjit') elif name == 'debug': - test.jitflags.append('-d') + test.jitflags.append('--debugjit') elif name == 'mjit': - test.jitflags.append('-m') + test.jitflags.append('--jm') + elif name == 'no-jm': + test.jitflags.append('--no-jm') elif name == 'ion-eager': test.jitflags.append('--ion-eager') elif name == 'dump-bytecode': - test.jitflags.append('-D') + test.jitflags.append('--dump-bytecode') else: print('warning: unrecognized |jit-test| attribute %s' % part) diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index 7a84fc551a9a..ee417bca037c 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -28,14 +28,14 @@ macro(callFunction, callFunction, "callFunction") \ macro(classPrototype, classPrototype, "prototype") \ macro(Collator, Collator, "Collator") \ - macro(CollatorCompare, CollatorCompare, "Intl_Collator_compare") \ + macro(CollatorCompareGet, CollatorCompareGet, "Intl_Collator_compare_get") \ macro(columnNumber, columnNumber, "columnNumber") \ macro(compare, compare, "compare") \ macro(configurable, configurable, "configurable") \ macro(construct, construct, "construct") \ macro(constructor, constructor, "constructor") \ macro(DateTimeFormat, DateTimeFormat, "DateTimeFormat") \ - macro(DateTimeFormatFormat, DateTimeFormatFormat, "Intl_DateTimeFormat_format") \ + macro(DateTimeFormatFormatGet, DateTimeFormatFormatGet, "Intl_DateTimeFormat_format_get") \ macro(decodeURI, decodeURI, "decodeURI") \ macro(decodeURIComponent, decodeURIComponent, "decodeURIComponent") \ macro(defineProperty, defineProperty, "defineProperty") \ @@ -94,7 +94,7 @@ macro(next, next, "next") \ macro(noSuchMethod, noSuchMethod, "__noSuchMethod__") \ macro(NumberFormat, NumberFormat, "NumberFormat") \ - macro(NumberFormatFormat, NumberFormatFormat, "Intl_NumberFormat_format") \ + macro(NumberFormatFormatGet, NumberFormatFormatGet, "Intl_NumberFormat_format_get") \ macro(objectNull, objectNull, "[object Null]") \ macro(objectUndefined, objectUndefined, "[object Undefined]") \ macro(of, of, "of") \ diff --git a/js/xpconnect/idl/nsIXPConnect.idl b/js/xpconnect/idl/nsIXPConnect.idl index e489db24d2d5..96f7d5ccb403 100644 --- a/js/xpconnect/idl/nsIXPConnect.idl +++ b/js/xpconnect/idl/nsIXPConnect.idl @@ -289,7 +289,7 @@ interface nsIXPCFunctionThisTranslator : nsISupports { 0xbd, 0xd6, 0x0, 0x0, 0x64, 0x65, 0x73, 0x74 } } %} -[uuid(3e825850-3a5f-11e2-81c1-0800200c9a66)] +[uuid(19e869b4-23cc-417d-b877-f3d46b8bc2d2)] interface nsIXPConnect : nsISupports { %{ C++ @@ -437,10 +437,6 @@ interface nsIXPConnect : nsISupports getNativeOfWrapper(in JSContextPtr aJSContext, in JSObjectPtr aJSObj); - JSObjectPtr - getJSObjectOfWrapper(in JSContextPtr aJSContext, - in JSObjectPtr aJSObj); - void setSecurityManagerForJSContext(in JSContextPtr aJSContext, in nsIXPCSecurityManager aManager, in uint16_t flags); diff --git a/js/xpconnect/src/XPCComponents.cpp b/js/xpconnect/src/XPCComponents.cpp index 0a09bd940ced..ba060e4cdbd2 100644 --- a/js/xpconnect/src/XPCComponents.cpp +++ b/js/xpconnect/src/XPCComponents.cpp @@ -3865,6 +3865,8 @@ xpc_EvalInSandbox(JSContext *cx, JSObject *sandbox, const nsAString& source, JSVersion jsVersion, bool returnStringOnly, jsval *rval) { JS_AbortIfWrongThread(JS_GetRuntime(cx)); + JSAutoRequest ar(cx); + *rval = JS::UndefinedValue(); bool waiveXray = xpc::WrapperFactory::HasWaiveXrayFlag(sandbox); sandbox = js::UnwrapObjectChecked(sandbox); @@ -3876,10 +3878,7 @@ xpc_EvalInSandbox(JSContext *cx, JSObject *sandbox, const nsAString& source, (nsIScriptObjectPrincipal*)xpc_GetJSPrivate(sandbox); NS_ASSERTION(sop, "Invalid sandbox passed"); nsCOMPtr prin = sop->GetPrincipal(); - - if (!prin) { - return NS_ERROR_FAILURE; - } + NS_ENSURE_TRUE(prin, NS_ERROR_FAILURE); nsAutoCString filenameBuf; if (!filename) { @@ -3889,114 +3888,81 @@ xpc_EvalInSandbox(JSContext *cx, JSObject *sandbox, const nsAString& source, lineNo = 1; } - JSObject *callingScope; + // We create a separate cx to do the sandbox evaluation. Scope it. + JS::Value v = JS::UndefinedValue(); + JS::Value exn = JS::UndefinedValue(); + bool ok = true; { - JSAutoRequest req(cx); - - callingScope = JS_GetGlobalForScopeChain(cx); - if (!callingScope) { - return NS_ERROR_FAILURE; + // Make a special cx for the sandbox and push it. + // NB: As soon as the RefPtr goes away, the cx goes away. So declare + // it first so that it disappears last. + nsRefPtr sandcxHolder = new ContextHolder(cx, sandbox, prin); + JSContext *sandcx = sandcxHolder->GetJSContext(); + if (!sandcx) { + JS_ReportError(cx, "Can't prepare context for evalInSandbox"); + return NS_ERROR_OUT_OF_MEMORY; } - } + nsCxPusher pusher; + pusher.Push(sandcx); - nsRefPtr sandcx = new ContextHolder(cx, sandbox, prin); - if (!sandcx || !sandcx->GetJSContext()) { - JS_ReportError(cx, "Can't prepare context for evalInSandbox"); - return NS_ERROR_OUT_OF_MEMORY; - } + JSAutoRequest req(sandcx); + JSAutoCompartment ac(sandcx, sandbox); - if (jsVersion != JSVERSION_DEFAULT) - JS_SetVersion(sandcx->GetJSContext(), jsVersion); + if (jsVersion != JSVERSION_DEFAULT) + JS_SetVersion(sandcx, jsVersion); - XPCJSContextStack *stack = XPCJSRuntime::Get()->GetJSContextStack(); - MOZ_ASSERT(stack); - if (!stack->Push(sandcx->GetJSContext())) { - JS_ReportError(cx, "Unable to initialize XPConnect with the sandbox context"); - return NS_ERROR_FAILURE; - } - - nsresult rv = NS_OK; - - { - JSAutoRequest req(sandcx->GetJSContext()); - JSAutoCompartment ac(sandcx->GetJSContext(), sandbox); - - jsval v; - JSString *str = nullptr; - JS::CompileOptions options(sandcx->GetJSContext()); + JS::CompileOptions options(sandcx); options.setPrincipals(nsJSPrincipals::get(prin)) .setFileAndLine(filename, lineNo); - js::RootedObject rootedSandbox(sandcx->GetJSContext(), sandbox); - bool ok = JS::Evaluate(sandcx->GetJSContext(), rootedSandbox, options, - PromiseFlatString(source).get(), source.Length(), &v); + js::RootedObject rootedSandbox(sandcx, sandbox); + ok = JS::Evaluate(sandcx, rootedSandbox, options, + PromiseFlatString(source).get(), source.Length(), &v); if (ok && returnStringOnly && !(JSVAL_IS_VOID(v))) { - ok = !!(str = JS_ValueToString(sandcx->GetJSContext(), v)); + JSString *str = JS_ValueToString(sandcx, v); + ok = !!str; + v = ok ? JS::StringValue(str) : JS::UndefinedValue(); } - if (!ok) { - // The sandbox threw an exception, convert it to a string (if - // asked) or convert it to a SJOW. - - jsval exn; - if (JS_GetPendingException(sandcx->GetJSContext(), &exn)) { - JS_ClearPendingException(sandcx->GetJSContext()); - - if (returnStringOnly) { - // The caller asked for strings only, convert the - // exception into a string. - str = JS_ValueToString(sandcx->GetJSContext(), exn); - - if (str) { - // We converted the exception to a string. Use that - // as the value exception. - exn = STRING_TO_JSVAL(str); - if (JS_WrapValue(cx, &exn)) { - JS_SetPendingException(cx, exn); - } else { - JS_ClearPendingException(cx); - rv = NS_ERROR_FAILURE; - } - } else { - JS_ClearPendingException(cx); - rv = NS_ERROR_FAILURE; - } - } else { - if (JS_WrapValue(cx, &exn)) { - JS_SetPendingException(cx, exn); - } - } - - - // Clear str so we don't confuse callers. - str = nullptr; - } else { - rv = NS_ERROR_OUT_OF_MEMORY; - } - } else { - // Convert the result into something safe for our caller. - JSAutoRequest req(cx); - JSAutoCompartment ac(cx, callingScope); - - if (str) { - v = STRING_TO_JSVAL(str); - } - - // Transitively apply Xray waivers if |sb| was waived. - if (waiveXray && !xpc::WrapperFactory::WaiveXrayAndWrap(cx, &v)) - rv = NS_ERROR_FAILURE; - if (!waiveXray && !JS_WrapValue(cx, &v)) - rv = NS_ERROR_FAILURE; - - if (NS_SUCCEEDED(rv)) { - *rval = v; + // If the sandbox threw an exception, grab it off the context. + if (JS_GetPendingException(sandcx, &exn)) { + MOZ_ASSERT(!ok); + JS_ClearPendingException(sandcx); + if (returnStringOnly) { + // The caller asked for strings only, convert the + // exception into a string. + JSString *str = JS_ValueToString(sandcx, exn); + exn = str ? JS::StringValue(str) : JS::UndefinedValue(); } } } - if (stack) - unused << stack->Pop(); + // + // Alright, we're back on the caller's cx. If an error occured, try to + // wrap and set the exception. Otherwise, wrap the return value. + // - return rv; + if (!ok) { + // If we end up without an exception, it was probably due to OOM along + // the way, in which case we thow. Otherwise, wrap it. + if (exn.isUndefined() || !JS_WrapValue(cx, &exn)) + return NS_ERROR_OUT_OF_MEMORY; + + // Set the exception on our caller's cx. + JS_SetPendingException(cx, exn); + return NS_OK; + } + + // Transitively apply Xray waivers if |sb| was waived. + if (waiveXray) { + ok = xpc::WrapperFactory::WaiveXrayAndWrap(cx, &v); + } else { + ok = JS_WrapValue(cx, &v); + } + NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); + + // Whew! + *rval = v; + return NS_OK; } /* JSObject import (in AUTF8String registryLocation, diff --git a/js/xpconnect/src/XPCWrappedJSClass.cpp b/js/xpconnect/src/XPCWrappedJSClass.cpp index caa69a3a76c5..9ed1f5830f77 100644 --- a/js/xpconnect/src/XPCWrappedJSClass.cpp +++ b/js/xpconnect/src/XPCWrappedJSClass.cpp @@ -492,6 +492,7 @@ nsXPCWrappedJSClass::IsWrappedJS(nsISupports* aPtr) result == WrappedJSIdentity::GetSingleton(); } +// NB: This returns null unless there's nothing on the JSContext stack. static JSContext * GetContextFromObject(JSObject *obj) { diff --git a/js/xpconnect/src/dictionary_helper_gen.py b/js/xpconnect/src/dictionary_helper_gen.py index bff771a88494..454f3b9f8923 100644 --- a/js/xpconnect/src/dictionary_helper_gen.py +++ b/js/xpconnect/src/dictionary_helper_gen.py @@ -406,7 +406,7 @@ def write_cpp(iface, fd): " }\n\n" " JSObject* obj = &aVal->toObject();\n" " nsCxPusher pusher;\n" - " NS_ENSURE_STATE(pusher.Push(aCx, false));\n" + " pusher.Push(aCx);\n" " JSAutoRequest ar(aCx);\n" " JSAutoCompartment ac(aCx, obj);\n") diff --git a/js/xpconnect/src/nsXPConnect.cpp b/js/xpconnect/src/nsXPConnect.cpp index d25939b8b6f9..0bcc6bf85716 100644 --- a/js/xpconnect/src/nsXPConnect.cpp +++ b/js/xpconnect/src/nsXPConnect.cpp @@ -1372,41 +1372,6 @@ nsXPConnect::GetNativeOfWrapper(JSContext * aJSContext, return canonical; } -/* JSObjectPtr getJSObjectOfWrapper (in JSContextPtr aJSContext, in JSObjectPtr aJSObj); */ -NS_IMETHODIMP -nsXPConnect::GetJSObjectOfWrapper(JSContext * aJSContext, - JSObject * aJSObj, - JSObject **_retval) -{ - NS_ASSERTION(aJSContext, "bad param"); - NS_ASSERTION(aJSObj, "bad param"); - NS_ASSERTION(_retval, "bad param"); - - XPCCallContext ccx(NATIVE_CALLER, aJSContext); - if (!ccx.IsValid()) - return UnexpectedFailure(NS_ERROR_FAILURE); - - JSObject* obj2 = nullptr; - nsIXPConnectWrappedNative* wrapper = - XPCWrappedNative::GetWrappedNativeOfJSObject(aJSContext, aJSObj, nullptr, - &obj2); - if (wrapper) { - wrapper->GetJSObject(_retval); - return NS_OK; - } - if (obj2) { - *_retval = obj2; - return NS_OK; - } - if (mozilla::dom::IsDOMObject(aJSObj)) { - *_retval = aJSObj; - return NS_OK; - } - // else... - *_retval = nullptr; - return NS_ERROR_FAILURE; -} - /* nsIXPConnectWrappedNative getWrappedNativeOfNativeObject (in JSContextPtr aJSContext, in JSObjectPtr aScope, in nsISupports aCOMObj, in nsIIDRef aIID); */ NS_IMETHODIMP nsXPConnect::GetWrappedNativeOfNativeObject(JSContext * aJSContext, diff --git a/js/xpconnect/tests/mochitest/test_bug393269.html b/js/xpconnect/tests/mochitest/test_bug393269.html index c2b0f5cd5d6b..ebd5a83ef58a 100644 --- a/js/xpconnect/tests/mochitest/test_bug393269.html +++ b/js/xpconnect/tests/mochitest/test_bug393269.html @@ -14,7 +14,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=393269