Bug 687744 and bug 573078 - Make buffer allocations in the HTML parser fallible; deal with allocation failures; reuse the buffers of strings passed to the parser. r=Olli.Pettay.
This commit is contained in:
@@ -61,6 +61,7 @@
|
||||
#include "nsHtml5Parser.h"
|
||||
#include "nsHtml5AtomTable.h"
|
||||
#include "nsIDOMDocumentFragment.h"
|
||||
#include "nsHtml5DependentUTF16Buffer.h"
|
||||
|
||||
NS_INTERFACE_TABLE_HEAD(nsHtml5Parser)
|
||||
NS_INTERFACE_TABLE2(nsHtml5Parser, nsIParser, nsISupportsWeakReference)
|
||||
@@ -85,7 +86,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsHtml5Parser)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
nsHtml5Parser::nsHtml5Parser()
|
||||
: mFirstBuffer(new nsHtml5UTF16Buffer(0))
|
||||
: mFirstBuffer(new nsHtml5OwningUTF16Buffer((void*)nsnull))
|
||||
, mLastBuffer(mFirstBuffer)
|
||||
, mExecutor(new nsHtml5TreeOpExecutor())
|
||||
, mTreeBuilder(new nsHtml5TreeBuilder(mExecutor, nsnull))
|
||||
@@ -243,6 +244,13 @@ nsHtml5Parser::Parse(const nsAString& aSourceBuffer,
|
||||
{
|
||||
NS_PRECONDITION(!mExecutor->IsFragmentMode(),
|
||||
"Document.write called in fragment mode!");
|
||||
if (mExecutor->IsBroken()) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
if (aSourceBuffer.Length() > PR_INT32_MAX) {
|
||||
mExecutor->MarkAsBroken();
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// Maintain a reference to ourselves so we don't go away
|
||||
// till we're completely done. The old parser grips itself in this method.
|
||||
@@ -295,68 +303,12 @@ nsHtml5Parser::Parse(const nsAString& aSourceBuffer,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsRefPtr<nsHtml5UTF16Buffer> buffer =
|
||||
new nsHtml5UTF16Buffer(aSourceBuffer.Length());
|
||||
memcpy(buffer->getBuffer(),
|
||||
aSourceBuffer.BeginReading(),
|
||||
aSourceBuffer.Length() * sizeof(PRUnichar));
|
||||
buffer->setEnd(aSourceBuffer.Length());
|
||||
nsHtml5DependentUTF16Buffer stackBuffer(aSourceBuffer);
|
||||
|
||||
// The buffer is inserted to the stream here in case it won't be parsed
|
||||
// to completion.
|
||||
// The script is identified by aKey. If there's nothing in the buffer
|
||||
// chain for that key, we'll insert at the head of the queue.
|
||||
// When the script leaves something in the queue, a zero-length
|
||||
// key-holder "buffer" is inserted in the queue. If the same script
|
||||
// leaves something in the chain again, it will be inserted immediately
|
||||
// before the old key holder belonging to the same script.
|
||||
nsHtml5UTF16Buffer* prevSearchBuf = nsnull;
|
||||
nsHtml5UTF16Buffer* searchBuf = mFirstBuffer;
|
||||
|
||||
// after document.open, the first level of document.write has null key
|
||||
if (aKey) {
|
||||
while (searchBuf != mLastBuffer) {
|
||||
if (searchBuf->key == aKey) {
|
||||
// found a key holder
|
||||
// now insert the new buffer between the previous buffer
|
||||
// and the key holder.
|
||||
buffer->next = searchBuf;
|
||||
if (prevSearchBuf) {
|
||||
prevSearchBuf->next = buffer;
|
||||
} else {
|
||||
mFirstBuffer = buffer;
|
||||
}
|
||||
break;
|
||||
}
|
||||
prevSearchBuf = searchBuf;
|
||||
searchBuf = searchBuf->next;
|
||||
}
|
||||
if (searchBuf == mLastBuffer) {
|
||||
// key was not found
|
||||
nsHtml5UTF16Buffer* keyHolder = new nsHtml5UTF16Buffer(aKey);
|
||||
keyHolder->next = mFirstBuffer;
|
||||
buffer->next = keyHolder;
|
||||
mFirstBuffer = buffer;
|
||||
}
|
||||
} else {
|
||||
// we have a first level document.write after document.open()
|
||||
// insert immediately before mLastBuffer
|
||||
while (searchBuf != mLastBuffer) {
|
||||
prevSearchBuf = searchBuf;
|
||||
searchBuf = searchBuf->next;
|
||||
}
|
||||
buffer->next = mLastBuffer;
|
||||
if (prevSearchBuf) {
|
||||
prevSearchBuf->next = buffer;
|
||||
} else {
|
||||
mFirstBuffer = buffer;
|
||||
}
|
||||
}
|
||||
|
||||
while (!mBlocked && buffer->hasMore()) {
|
||||
buffer->adjust(mLastWasCR);
|
||||
while (!mBlocked && stackBuffer.hasMore()) {
|
||||
stackBuffer.adjust(mLastWasCR);
|
||||
mLastWasCR = PR_FALSE;
|
||||
if (buffer->hasMore()) {
|
||||
if (stackBuffer.hasMore()) {
|
||||
PRInt32 lineNumberSave;
|
||||
bool inRootContext = (!mStreamParser && (aKey == mRootContextKey));
|
||||
if (inRootContext) {
|
||||
@@ -367,7 +319,7 @@ nsHtml5Parser::Parse(const nsAString& aSourceBuffer,
|
||||
lineNumberSave = mTokenizer->getLineNumber();
|
||||
}
|
||||
|
||||
mLastWasCR = mTokenizer->tokenizeBuffer(buffer);
|
||||
mLastWasCR = mTokenizer->tokenizeBuffer(&stackBuffer);
|
||||
|
||||
if (inRootContext) {
|
||||
mRootContextLineNumber = mTokenizer->getLineNumber();
|
||||
@@ -383,12 +335,82 @@ nsHtml5Parser::Parse(const nsAString& aSourceBuffer,
|
||||
}
|
||||
}
|
||||
|
||||
nsRefPtr<nsHtml5OwningUTF16Buffer> heapBuffer;
|
||||
if (stackBuffer.hasMore()) {
|
||||
// The buffer wasn't tokenized to completion. Create a copy of the tail
|
||||
// on the heap.
|
||||
heapBuffer = stackBuffer.FalliblyCopyAsOwningBuffer();
|
||||
if (!heapBuffer) {
|
||||
// Allocation failed. The parser is now broken.
|
||||
mExecutor->MarkAsBroken();
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
}
|
||||
|
||||
// The buffer is inserted to the stream here in case it won't be parsed
|
||||
// to completion.
|
||||
// The script is identified by aKey. If there's nothing in the buffer
|
||||
// chain for that key, we'll insert at the head of the queue.
|
||||
// When the script leaves something in the queue, a zero-length
|
||||
// key-holder "buffer" is inserted in the queue. If the same script
|
||||
// leaves something in the chain again, it will be inserted immediately
|
||||
// before the old key holder belonging to the same script.
|
||||
nsHtml5OwningUTF16Buffer* prevSearchBuf = nsnull;
|
||||
nsHtml5OwningUTF16Buffer* searchBuf = mFirstBuffer;
|
||||
|
||||
// after document.open, the first level of document.write has null key
|
||||
if (aKey) {
|
||||
while (searchBuf != mLastBuffer) {
|
||||
if (searchBuf->key == aKey) {
|
||||
// found a key holder
|
||||
// now insert the new buffer between the previous buffer
|
||||
// and the key holder if we have a buffer left.
|
||||
if (heapBuffer) {
|
||||
heapBuffer->next = searchBuf;
|
||||
if (prevSearchBuf) {
|
||||
prevSearchBuf->next = heapBuffer;
|
||||
} else {
|
||||
mFirstBuffer = heapBuffer;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
prevSearchBuf = searchBuf;
|
||||
searchBuf = searchBuf->next;
|
||||
}
|
||||
if (searchBuf == mLastBuffer) {
|
||||
// key was not found
|
||||
nsHtml5OwningUTF16Buffer* keyHolder = new nsHtml5OwningUTF16Buffer(aKey);
|
||||
keyHolder->next = mFirstBuffer;
|
||||
if (heapBuffer) {
|
||||
heapBuffer->next = keyHolder;
|
||||
mFirstBuffer = heapBuffer;
|
||||
} else {
|
||||
mFirstBuffer = keyHolder;
|
||||
}
|
||||
}
|
||||
} else if (heapBuffer) {
|
||||
// we have a first level document.write after document.open()
|
||||
// insert immediately before mLastBuffer
|
||||
while (searchBuf != mLastBuffer) {
|
||||
prevSearchBuf = searchBuf;
|
||||
searchBuf = searchBuf->next;
|
||||
}
|
||||
heapBuffer->next = mLastBuffer;
|
||||
if (prevSearchBuf) {
|
||||
prevSearchBuf->next = heapBuffer;
|
||||
} else {
|
||||
mFirstBuffer = heapBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mBlocked) { // buffer was tokenized to completion
|
||||
NS_ASSERTION(!buffer->hasMore(), "Buffer wasn't tokenized to completion?");
|
||||
NS_ASSERTION(!stackBuffer.hasMore(),
|
||||
"Buffer wasn't tokenized to completion?");
|
||||
// Scripting semantics require a forced tree builder flush here
|
||||
mTreeBuilder->Flush(); // Move ops to the executor
|
||||
mExecutor->FlushDocumentWrite(); // run the ops
|
||||
} else if (buffer->hasMore()) {
|
||||
} else if (stackBuffer.hasMore()) {
|
||||
// The buffer wasn't tokenized to completion. Tokenize the untokenized
|
||||
// content in order to preload stuff. This content will be retokenized
|
||||
// later for normal parsing.
|
||||
@@ -416,15 +438,16 @@ nsHtml5Parser::Parse(const nsAString& aSourceBuffer,
|
||||
// that the speculative loads aren't exactly right. The content will be
|
||||
// reparsed anyway for non-preload purposes.
|
||||
|
||||
PRInt32 originalStart = buffer->getStart();
|
||||
while (buffer->hasMore()) {
|
||||
buffer->adjust(mDocWriteSpeculativeLastWasCR);
|
||||
if (buffer->hasMore()) {
|
||||
// The buffer position for subsequent non-speculative parsing now lives
|
||||
// in heapBuffer, so it's ok to let the buffer position of stackBuffer
|
||||
// to be overwritten and not restored below.
|
||||
while (stackBuffer.hasMore()) {
|
||||
stackBuffer.adjust(mDocWriteSpeculativeLastWasCR);
|
||||
if (stackBuffer.hasMore()) {
|
||||
mDocWriteSpeculativeLastWasCR =
|
||||
mDocWriteSpeculativeTokenizer->tokenizeBuffer(buffer);
|
||||
mDocWriteSpeculativeTokenizer->tokenizeBuffer(&stackBuffer);
|
||||
}
|
||||
}
|
||||
buffer->setStart(originalStart);
|
||||
|
||||
mDocWriteSpeculativeTreeBuilder->Flush();
|
||||
mDocWriteSpeculativeTreeBuilder->DropHandles();
|
||||
@@ -477,6 +500,8 @@ nsHtml5Parser::ParseHtml5Fragment(const nsAString& aSourceBuffer,
|
||||
bool aQuirks,
|
||||
bool aPreventScriptExecution)
|
||||
{
|
||||
NS_ENSURE_TRUE(aSourceBuffer.Length() <= PR_INT32_MAX,
|
||||
NS_ERROR_OUT_OF_MEMORY);
|
||||
nsIDocument* doc = aTargetNode->GetOwnerDoc();
|
||||
NS_ENSURE_TRUE(doc, NS_ERROR_NOT_AVAILABLE);
|
||||
|
||||
@@ -514,11 +539,7 @@ nsHtml5Parser::ParseHtml5Fragment(const nsAString& aSourceBuffer,
|
||||
mExecutor->Start(); // Don't call WillBuildModel in fragment case
|
||||
if (!aSourceBuffer.IsEmpty()) {
|
||||
bool lastWasCR = false;
|
||||
nsHtml5UTF16Buffer buffer(aSourceBuffer.Length());
|
||||
memcpy(buffer.getBuffer(),
|
||||
aSourceBuffer.BeginReading(),
|
||||
aSourceBuffer.Length() * sizeof(PRUnichar));
|
||||
buffer.setEnd(aSourceBuffer.Length());
|
||||
nsHtml5DependentUTF16Buffer buffer(aSourceBuffer);
|
||||
while (buffer.hasMore()) {
|
||||
buffer.adjust(lastWasCR);
|
||||
lastWasCR = PR_FALSE;
|
||||
@@ -629,11 +650,7 @@ nsHtml5Parser::ParseUntilBlocked()
|
||||
NS_PRECONDITION(!mExecutor->IsFragmentMode(),
|
||||
"ParseUntilBlocked called in fragment mode.");
|
||||
|
||||
if (mBlocked) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mExecutor->IsComplete()) {
|
||||
if (mBlocked || mExecutor->IsComplete() || mExecutor->IsBroken()) {
|
||||
return;
|
||||
}
|
||||
NS_ASSERTION(mExecutor->HasStarted(), "Bad life cycle.");
|
||||
|
||||
Reference in New Issue
Block a user