Bug 720589 - mMatchCounts may be accessed with a nonexisting index. r=neil

This commit is contained in:
Marco Bonardo 2015-11-10 20:18:24 +01:00
Родитель 651792a7c1
Коммит 2cb37ef1e6
11 изменённых файлов: 201 добавлений и 76 удалений

Просмотреть файл

@ -132,7 +132,6 @@ nsAutoCompleteController::SetInput(nsIAutoCompleteInput *aInput)
aInput->GetSearchCount(&searchCount);
mResults.SetCapacity(searchCount);
mSearches.SetCapacity(searchCount);
mMatchCounts.SetLength(searchCount);
mImmediateSearchesCount = 0;
const char *searchCID = kAutoCompleteSearchCID;
@ -629,7 +628,7 @@ nsAutoCompleteController::HandleDelete(bool *_retval)
RowIndexToSearch(index, &searchIndex, &rowIndex);
NS_ENSURE_TRUE(searchIndex >= 0 && rowIndex >= 0, NS_ERROR_FAILURE);
nsIAutoCompleteResult *result = mResults[searchIndex];
nsIAutoCompleteResult *result = mResults.SafeObjectAt(searchIndex);
NS_ENSURE_TRUE(result, NS_ERROR_FAILURE);
nsAutoString search;
@ -691,7 +690,7 @@ nsAutoCompleteController::GetResultAt(int32_t aIndex, nsIAutoCompleteResult** aR
RowIndexToSearch(aIndex, &searchIndex, aRowIndex);
NS_ENSURE_TRUE(searchIndex >= 0 && *aRowIndex >= 0, NS_ERROR_FAILURE);
*aResult = mResults[searchIndex];
*aResult = mResults.SafeObjectAt(searchIndex);
NS_ENSURE_TRUE(*aResult, NS_ERROR_FAILURE);
return NS_OK;
}
@ -1522,39 +1521,50 @@ nsresult
nsAutoCompleteController::ProcessResult(int32_t aSearchIndex, nsIAutoCompleteResult *aResult)
{
NS_ENSURE_STATE(mInput);
MOZ_ASSERT(aResult, "ProcessResult should always receive a result");
NS_ENSURE_ARG(aResult);
nsCOMPtr<nsIAutoCompleteInput> input(mInput);
uint16_t result = 0;
if (aResult)
aResult->GetSearchResult(&result);
uint16_t searchResult = 0;
aResult->GetSearchResult(&searchResult);
uint32_t oldMatchCount = 0;
uint32_t matchCount = 0;
if (aResult)
aResult->GetMatchCount(&matchCount);
int32_t resultIndex = mResults.IndexOf(aResult);
if (resultIndex == -1) {
// cache the result
mResults.AppendObject(aResult);
mMatchCounts.AppendElement(matchCount);
resultIndex = mResults.Count() - 1;
}
else {
oldMatchCount = mMatchCounts[aSearchIndex];
mMatchCounts[resultIndex] = matchCount;
// The following code supports incremental updating results in 2 ways:
// * The search may reuse the same result, just by adding entries to it.
// * The search may send a new result every time. In this case we merge
// the results and proceed on the same code path as before.
// This way both mSearches and mResults can be indexed by the search index,
// cause we'll always have only one result per search.
if (mResults.IndexOf(aResult) == -1) {
nsIAutoCompleteResult* oldResult = mResults.SafeObjectAt(aSearchIndex);
if (oldResult) {
MOZ_ASSERT(false, "Passing new matches to OnSearchResult with a new "
"nsIAutoCompleteResult every time is deprecated, please "
"update the same result until the search is done");
// Build a new nsIAutocompleteSimpleResult and merge results into it.
RefPtr<nsAutoCompleteSimpleResult> mergedResult =
new nsAutoCompleteSimpleResult();
mergedResult->AppendResult(oldResult);
mergedResult->AppendResult(aResult);
mResults.ReplaceObjectAt(mergedResult, aSearchIndex);
} else {
// This inserts and grows the array if needed.
mResults.ReplaceObjectAt(aResult, aSearchIndex);
}
}
// When found the result should have the same index as the search.
MOZ_ASSERT_IF(mResults.IndexOf(aResult) != -1,
mResults.IndexOf(aResult) == aSearchIndex);
MOZ_ASSERT(mResults.Count() >= aSearchIndex + 1,
"aSearchIndex should always be valid for mResults");
bool isTypeAheadResult = false;
if (aResult) {
aResult->GetTypeAheadResult(&isTypeAheadResult);
}
aResult->GetTypeAheadResult(&isTypeAheadResult);
if (!isTypeAheadResult) {
uint32_t oldRowCount = mRowCount;
// If the search failed, increase the match count to include the error
// description.
if (result == nsIAutoCompleteResult::RESULT_FAILURE) {
if (searchResult == nsIAutoCompleteResult::RESULT_FAILURE) {
nsAutoString error;
aResult->GetErrorDescription(error);
if (!error.IsEmpty()) {
@ -1563,13 +1573,28 @@ nsAutoCompleteController::ProcessResult(int32_t aSearchIndex, nsIAutoCompleteRes
mTree->RowCountChanged(oldRowCount, 1);
}
}
} else if (result == nsIAutoCompleteResult::RESULT_SUCCESS ||
result == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
} else if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS ||
searchResult == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
// Increase the match count for all matches in this result.
mRowCount += matchCount - oldMatchCount;
uint32_t totalMatchCount = 0;
for (uint32_t i = 0; i < mResults.Length(); i++) {
nsIAutoCompleteResult* result = mResults.SafeObjectAt(i);
if (result) {
// not all results implement this, so it can likely fail.
bool typeAhead = false;
result->GetTypeAheadResult(&typeAhead);
if (!typeAhead) {
uint32_t matchCount = 0;
result->GetMatchCount(&matchCount);
totalMatchCount += matchCount;
}
}
}
uint32_t delta = totalMatchCount - oldRowCount;
mRowCount += delta;
if (mTree) {
mTree->RowCountChanged(oldRowCount, matchCount - oldMatchCount);
mTree->RowCountChanged(oldRowCount, delta);
}
}
@ -1592,10 +1617,10 @@ nsAutoCompleteController::ProcessResult(int32_t aSearchIndex, nsIAutoCompleteRes
}
}
if (result == nsIAutoCompleteResult::RESULT_SUCCESS ||
result == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS ||
searchResult == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
// Try to autocomplete the default index for this search.
CompleteDefaultIndex(resultIndex);
CompleteDefaultIndex(aSearchIndex);
}
return NS_OK;
@ -1633,7 +1658,6 @@ nsAutoCompleteController::ClearResults()
int32_t oldRowCount = mRowCount;
mRowCount = 0;
mResults.Clear();
mMatchCounts.Clear();
if (oldRowCount != 0) {
if (mTree)
mTree->RowCountChanged(0, -oldRowCount);
@ -1701,14 +1725,16 @@ nsAutoCompleteController::GetDefaultCompleteResult(int32_t aResultIndex,
// If a result index was not provided, find the first defaultIndex result.
for (int32_t i = 0; resultIndex < 0 && i < mResults.Count(); ++i) {
nsIAutoCompleteResult *result = mResults[i];
nsIAutoCompleteResult *result = mResults.SafeObjectAt(i);
if (result &&
NS_SUCCEEDED(result->GetDefaultIndex(_defaultIndex)) &&
*_defaultIndex >= 0) {
resultIndex = i;
}
}
NS_ENSURE_TRUE(resultIndex >= 0, NS_ERROR_FAILURE);
if (resultIndex < 0) {
return NS_ERROR_FAILURE;
}
*_result = mResults.SafeObjectAt(resultIndex);
NS_ENSURE_TRUE(*_result, NS_ERROR_FAILURE);

Просмотреть файл

@ -123,10 +123,8 @@ protected:
nsCOMPtr<nsIAutoCompleteInput> mInput;
nsCOMArray<nsIAutoCompleteSearch> mSearches;
// This is used as a sparse array, always use SafeObjectAt to access it.
nsCOMArray<nsIAutoCompleteResult> mResults;
// Caches the match counts for the current ongoing results to allow
// incremental results to keep the rowcount up to date.
nsTArray<uint32_t> mMatchCounts;
// Temporarily keeps the results alive while invoking startSearch() for each
// search. This is needed to allow the searches to reuse the previous result,
// since otherwise the first search clears mResults.

Просмотреть файл

@ -22,12 +22,14 @@ struct AutoCompleteSimpleResultMatch
const nsAString& aComment,
const nsAString& aImage,
const nsAString& aStyle,
const nsAString& aFinalCompleteValue)
const nsAString& aFinalCompleteValue,
const nsAString& aLabel)
: mValue(aValue)
, mComment(aComment)
, mImage(aImage)
, mStyle(aStyle)
, mFinalCompleteValue(aFinalCompleteValue)
, mLabel(aLabel)
{
}
@ -36,6 +38,7 @@ struct AutoCompleteSimpleResultMatch
nsString mImage;
nsString mStyle;
nsString mFinalCompleteValue;
nsString mLabel;
};
nsAutoCompleteSimpleResult::nsAutoCompleteSimpleResult() :
@ -45,6 +48,74 @@ nsAutoCompleteSimpleResult::nsAutoCompleteSimpleResult() :
{
}
nsresult
nsAutoCompleteSimpleResult::AppendResult(nsIAutoCompleteResult* aResult)
{
nsAutoString searchString;
nsresult rv = aResult->GetSearchString(searchString);
NS_ENSURE_SUCCESS(rv, rv);
mSearchString = searchString;
uint16_t searchResult;
rv = aResult->GetSearchResult(&searchResult);
NS_ENSURE_SUCCESS(rv, rv);
mSearchResult = searchResult;
nsAutoString errorDescription;
if (NS_SUCCEEDED(aResult->GetErrorDescription(errorDescription)) &&
!errorDescription.IsEmpty()) {
mErrorDescription = errorDescription;
}
bool typeAheadResult = false;
if (NS_SUCCEEDED(aResult->GetTypeAheadResult(&typeAheadResult)) &&
typeAheadResult) {
mTypeAheadResult = typeAheadResult;
}
int32_t defaultIndex = -1;
if (NS_SUCCEEDED(aResult->GetDefaultIndex(&defaultIndex)) &&
defaultIndex >= 0) {
mDefaultIndex = defaultIndex;
}
nsCOMPtr<nsIAutoCompleteSimpleResult> simpleResult =
do_QueryInterface(aResult);
if (simpleResult) {
nsCOMPtr<nsIAutoCompleteSimpleResultListener> listener;
if (NS_SUCCEEDED(simpleResult->GetListener(getter_AddRefs(listener))) &&
listener) {
listener.swap(mListener);
}
}
// Copy matches.
uint32_t matchCount = 0;
rv = aResult->GetMatchCount(&matchCount);
NS_ENSURE_SUCCESS(rv, rv);
for (size_t i = 0; i < matchCount; ++i) {
nsAutoString value, comment, image, style, finalCompleteValue, label;
rv = aResult->GetValueAt(i, value);
NS_ENSURE_SUCCESS(rv, rv);
rv = aResult->GetCommentAt(i, comment);
NS_ENSURE_SUCCESS(rv, rv);
rv = aResult->GetImageAt(i, image);
NS_ENSURE_SUCCESS(rv, rv);
rv = aResult->GetStyleAt(i, style);
NS_ENSURE_SUCCESS(rv, rv);
rv = aResult->GetFinalCompleteValueAt(i, finalCompleteValue);
NS_ENSURE_SUCCESS(rv, rv);
rv = aResult->GetLabelAt(i, label);
NS_ENSURE_SUCCESS(rv, rv);
rv = AppendMatch(value, comment, image, style, finalCompleteValue, label);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
// searchString
NS_IMETHODIMP
nsAutoCompleteSimpleResult::GetSearchString(nsAString &aSearchString)
@ -122,11 +193,12 @@ nsAutoCompleteSimpleResult::InsertMatchAt(int32_t aIndex,
const nsAString& aComment,
const nsAString& aImage,
const nsAString& aStyle,
const nsAString& aFinalCompleteValue)
const nsAString& aFinalCompleteValue,
const nsAString& aLabel)
{
CHECK_MATCH_INDEX(aIndex, true);
AutoCompleteSimpleResultMatch match(aValue, aComment, aImage, aStyle, aFinalCompleteValue);
AutoCompleteSimpleResultMatch match(aValue, aComment, aImage, aStyle, aFinalCompleteValue, aLabel);
if (!mMatches.InsertElementAt(aIndex, match)) {
return NS_ERROR_OUT_OF_MEMORY;
@ -140,10 +212,11 @@ nsAutoCompleteSimpleResult::AppendMatch(const nsAString& aValue,
const nsAString& aComment,
const nsAString& aImage,
const nsAString& aStyle,
const nsAString& aFinalCompleteValue)
const nsAString& aFinalCompleteValue,
const nsAString& aLabel)
{
return InsertMatchAt(mMatches.Length(), aValue, aComment, aImage, aStyle,
aFinalCompleteValue);
aFinalCompleteValue, aLabel);
}
NS_IMETHODIMP
@ -164,7 +237,12 @@ nsAutoCompleteSimpleResult::GetValueAt(int32_t aIndex, nsAString& _retval)
NS_IMETHODIMP
nsAutoCompleteSimpleResult::GetLabelAt(int32_t aIndex, nsAString& _retval)
{
return GetValueAt(aIndex, _retval);
CHECK_MATCH_INDEX(aIndex, false);
_retval = mMatches[aIndex].mLabel;
if (_retval.IsEmpty()) {
_retval = mMatches[aIndex].mValue;
}
return NS_OK;
}
NS_IMETHODIMP
@ -197,8 +275,9 @@ nsAutoCompleteSimpleResult::GetFinalCompleteValueAt(int32_t aIndex,
{
CHECK_MATCH_INDEX(aIndex, false);
_retval = mMatches[aIndex].mFinalCompleteValue;
if (_retval.Length() == 0)
if (_retval.IsEmpty()) {
_retval = mMatches[aIndex].mValue;
}
return NS_OK;
}
@ -209,6 +288,14 @@ nsAutoCompleteSimpleResult::SetListener(nsIAutoCompleteSimpleResultListener* aLi
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteSimpleResult::GetListener(nsIAutoCompleteSimpleResultListener** aListener)
{
nsCOMPtr<nsIAutoCompleteSimpleResultListener> listener(mListener);
listener.forget(aListener);
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteSimpleResult::RemoveValueAt(int32_t aRowIndex,
bool aRemoveFromDb)
@ -218,8 +305,9 @@ nsAutoCompleteSimpleResult::RemoveValueAt(int32_t aRowIndex,
nsString value = mMatches[aRowIndex].mValue;
mMatches.RemoveElementAt(aRowIndex);
if (mListener)
if (mListener) {
mListener->OnValueRemoved(this, value, aRemoveFromDb);
}
return NS_OK;
}

Просмотреть файл

@ -24,6 +24,8 @@ public:
NS_DECL_NSIAUTOCOMPLETERESULT
NS_DECL_NSIAUTOCOMPLETESIMPLERESULT
nsresult AppendResult(nsIAutoCompleteResult* aResult);
private:
~nsAutoCompleteSimpleResult() {}

Просмотреть файл

@ -14,7 +14,7 @@ interface nsIAutoCompleteSimpleResultListener;
* an array.
*/
[scriptable, uuid(457ce8da-9631-45c5-b3b0-293ab0928df1)]
[scriptable, uuid(23de9c96-becb-4d0d-a9bb-1d131ce361b5)]
interface nsIAutoCompleteSimpleResult : nsIAutoCompleteResult
{
/**
@ -69,7 +69,8 @@ interface nsIAutoCompleteSimpleResult : nsIAutoCompleteResult
in AString aComment,
[optional] in AString aImage,
[optional] in AString aStyle,
[optional] in AString aFinalCompleteValue);
[optional] in AString aFinalCompleteValue,
[optional] in AString aLabel);
/**
* Appends a match consisting of the given value, comment, image, style and
@ -90,7 +91,13 @@ interface nsIAutoCompleteSimpleResult : nsIAutoCompleteResult
in AString aComment,
[optional] in AString aImage,
[optional] in AString aStyle,
[optional] in AString aFinalCompleteValue);
[optional] in AString aFinalCompleteValue,
[optional] in AString aLabel);
/**
* Gets the listener for changes in the result.
*/
nsIAutoCompleteSimpleResultListener getListener();
/**
* Sets a listener for changes in the result.

Просмотреть файл

@ -146,6 +146,7 @@ AutoCompleteResult.prototype = {
*/
function AutoCompleteSearch(aName, aResult) {
this.name = aName;
this._result = aResult;
}
AutoCompleteSearch.prototype = {
constructor: AutoCompleteSearch,
@ -154,7 +155,7 @@ AutoCompleteSearch.prototype = {
name: null,
// AutoCompleteResult
_result:null,
_result: null,
/**

Просмотреть файл

@ -1,6 +1,3 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Search object that returns results at different times.
* First, the search that returns results asynchronously.
@ -10,11 +7,16 @@ function AutoCompleteAsyncSearch(aName, aResult) {
this._result = aResult;
}
AutoCompleteAsyncSearch.prototype = Object.create(AutoCompleteSearchBase.prototype);
AutoCompleteAsyncSearch.prototype.startSearch = function(aSearchString,
aSearchParam,
aPreviousResult,
AutoCompleteAsyncSearch.prototype.startSearch = function(aSearchString,
aSearchParam,
aPreviousResult,
aListener) {
setTimeout(this._returnResults.bind(this), 500, aListener);
this._result.searchResult = Ci.nsIAutoCompleteResult.RESULT_NOMATCH_ONGOING;
aListener.onSearchResult(this, this._result);
do_timeout(500, () => {
this._returnResults(aListener);
});
};
AutoCompleteAsyncSearch.prototype._returnResults = function(aListener) {
@ -32,9 +34,9 @@ function AutoCompleteSyncSearch(aName, aResult) {
this._result = aResult;
}
AutoCompleteSyncSearch.prototype = Object.create(AutoCompleteAsyncSearch.prototype);
AutoCompleteSyncSearch.prototype.startSearch = function(aSearchString,
aSearchParam,
aPreviousResult,
AutoCompleteSyncSearch.prototype.startSearch = function(aSearchString,
aSearchParam,
aPreviousResult,
aListener) {
this._returnResults(aListener);
};
@ -49,7 +51,7 @@ function AutoCompleteResult(aValues, aDefaultIndex) {
AutoCompleteResult.prototype = Object.create(AutoCompleteResultBase.prototype);
/**
/**
* Test AutoComplete with multiple AutoCompleteSearch sources, with one of them
* (index != 0) returning before the rest.
*/
@ -60,19 +62,19 @@ function run_test() {
var inputStr = "moz";
// Async search
var asyncSearch = new AutoCompleteAsyncSearch("Async",
var asyncSearch = new AutoCompleteAsyncSearch("Async",
new AutoCompleteResult(results, -1));
// Sync search
var syncSearch = new AutoCompleteSyncSearch("Sync",
new AutoCompleteResult(results, 0));
// Register searches so AutoCompleteController can find them
registerAutoCompleteSearch(asyncSearch);
registerAutoCompleteSearch(syncSearch);
var controller = Cc["@mozilla.org/autocomplete/controller;1"].
getService(Ci.nsIAutoCompleteController);
getService(Ci.nsIAutoCompleteController);
// Make an AutoCompleteInput that uses our searches
// and confirms results on search complete.
// Async search MUST be FIRST to trigger the bug this tests.

Просмотреть файл

@ -285,7 +285,7 @@ FormAutoComplete.prototype = {
result.entries = aEntries;
}
if (aDatalistResult) {
if (aDatalistResult && aDatalistResult.matchCount > 0) {
result = this.mergeResults(result, aDatalistResult);
}

Просмотреть файл

@ -110,7 +110,7 @@ FormAutoCompleteResult.prototype = {
getLabelAt: function(index) {
this._checkIndexBounds(index);
return this._labels[index];
return this._labels[index] || this._values[index];
},
/**

Просмотреть файл

@ -727,11 +727,12 @@ public:
: mObserver(aObserver)
, mSearch(aSearch)
, mResult(aResult)
{}
{
MOZ_ASSERT(mResult, "Should have a valid result");
MOZ_ASSERT(mObserver, "You shouldn't call this runnable with a null observer!");
}
NS_IMETHOD Run() {
NS_ASSERTION(mObserver, "You shouldn't call this runnable with a null observer!");
mObserver->OnUpdateSearchResult(mSearch, mResult);
return NS_OK;
}

Просмотреть файл

@ -16,12 +16,12 @@ InputListAutoComplete.prototype = {
autoCompleteSearch : function (aUntrimmedSearchString, aField) {
let [values, labels] = this.getListSuggestions(aField);
if (values.length === 0)
return null;
let searchResult = values.length > 0 ? Ci.nsIAutoCompleteResult.RESULT_SUCCESS
: Ci.nsIAutoCompleteResult.RESULT_NOMATCH;
let defaultIndex = values.length > 0 ? 0 : -1;
return new FormAutoCompleteResult(aUntrimmedSearchString,
Ci.nsIAutoCompleteResult.RESULT_SUCCESS,
0, "", values, labels,
[], null);
searchResult, defaultIndex, "",
values, labels, [], null);
},
getListSuggestions : function (aField) {