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:
Henri Sivonen
2011-09-28 15:45:17 +03:00
parent fe6d43e51f
commit 1cde100e2a
21 changed files with 601 additions and 174 deletions

View File

@@ -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.");