1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-10-04 16:47:15 +02:00

Fix race condition when loading from selfie

All the auxiliary data structures must be fully loaded before
the data structure used as entry point is populated. The race
condition could lead to a case of the entry point data structure
being populated while the auxiliary data structures are still
unpopulated, potentially causing exceptions to be thrown at
launch when the static network filtering engine is queried.

I haven't been able to reproduce such exceptions -- but it
could happen on browsers which do not support being suspended
at launch time (i.e. chromium-based browsers).

Additionally, added convenience methods to easily
serialize/unserialize when SNFE is used as a npm package.
This commit is contained in:
Raymond Hill 2021-12-05 14:05:32 -05:00
parent 5c66310c0a
commit 7888e49c00
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2

View File

@ -3580,9 +3580,6 @@ FilterContainer.prototype.prime = function() {
FilterContainer.prototype.reset = function() {
this.processedFilterCount = 0;
this.acceptedCount = 0;
this.rejectedCount = 0;
this.allowFilterCount = 0;
this.blockFilterCount = 0;
this.discardedCount = 0;
this.goodFilters.clear();
this.badFilters.clear();
@ -3793,11 +3790,11 @@ FilterContainer.prototype.toSelfie = function(storage, path) {
return Promise.all([
storage.put(
`${path}/FilterHostnameDict.trieContainer`,
`${path}/destHNTrieContainer`,
destHNTrieContainer.serialize(sparseBase64)
),
storage.put(
`${path}/FilterOrigin.trieContainer`,
`${path}/origHNTrieContainer`,
origHNTrieContainer.serialize(sparseBase64)
),
storage.put(
@ -3818,9 +3815,6 @@ FilterContainer.prototype.toSelfie = function(storage, path) {
version: this.selfieVersion,
processedFilterCount: this.processedFilterCount,
acceptedCount: this.acceptedCount,
rejectedCount: this.rejectedCount,
allowFilterCount: this.allowFilterCount,
blockFilterCount: this.blockFilterCount,
discardedCount: this.discardedCount,
bitsToBucketIndices: this.bitsToBucketIndices,
buckets: bucketsToSelfie(),
@ -3830,27 +3824,35 @@ FilterContainer.prototype.toSelfie = function(storage, path) {
]);
};
FilterContainer.prototype.serialize = async function() {
const selfie = [];
const storage = {
put(name, data) {
selfie.push([ name, data ]);
}
};
await this.toSelfie(storage, '');
return JSON.stringify(selfie);
};
/******************************************************************************/
FilterContainer.prototype.fromSelfie = function(storage, path) {
FilterContainer.prototype.fromSelfie = async function(storage, path) {
if (
storage instanceof Object === false ||
storage.get instanceof Function === false
) {
return Promise.resolve();
return;
}
const bucketsFromSelfie = selfie => {
for ( let i = 0; i < selfie.length; i++ ) {
this.buckets[i] = new Map(selfie[i]);
}
};
this.reset();
return Promise.all([
storage.get(`${path}/FilterHostnameDict.trieContainer`).then(details =>
const results = await Promise.all([
storage.get(`${path}/main`),
storage.get(`${path}/destHNTrieContainer`).then(details =>
destHNTrieContainer.unserialize(details.content, sparseBase64)
),
storage.get(`${path}/FilterOrigin.trieContainer`).then(details =>
storage.get(`${path}/origHNTrieContainer`).then(details =>
origHNTrieContainer.unserialize(details.content, sparseBase64)
),
storage.get(`${path}/bidiTrie`).then(details =>
@ -3862,28 +3864,45 @@ FilterContainer.prototype.fromSelfie = function(storage, path) {
storage.get(`${path}/filterRefs`).then(details =>
filterRefsFromSelfie(details.content)
),
storage.get(`${path}/main`).then(details => {
let selfie;
try {
selfie = JSON.parse(details.content);
} catch (ex) {
}
if ( selfie instanceof Object === false ) { return false; }
if ( selfie.version !== this.selfieVersion ) { return false; }
this.processedFilterCount = selfie.processedFilterCount;
this.acceptedCount = selfie.acceptedCount;
this.rejectedCount = selfie.rejectedCount;
this.allowFilterCount = selfie.allowFilterCount;
this.blockFilterCount = selfie.blockFilterCount;
this.discardedCount = selfie.discardedCount;
this.bitsToBucketIndices = selfie.bitsToBucketIndices;
bucketsFromSelfie(selfie.buckets);
urlTokenizer.fromSelfie(selfie.urlTokenizer);
return true;
}),
]).then(results =>
results.every(v => v === true)
);
]);
if ( results.slice(1).every(v => v === true) === false ) { return false; }
const bucketsFromSelfie = selfie => {
for ( let i = 0; i < selfie.length; i++ ) {
this.buckets[i] = new Map(selfie[i]);
}
};
const details = results[0];
if ( details instanceof Object === false ) { return false; }
if ( typeof details.content !== 'string' ) { return false; }
if ( details.content === '' ) { return false; }
let selfie;
try {
selfie = JSON.parse(details.content);
} catch (ex) {
}
if ( selfie instanceof Object === false ) { return false; }
if ( selfie.version !== this.selfieVersion ) { return false; }
this.processedFilterCount = selfie.processedFilterCount;
this.acceptedCount = selfie.acceptedCount;
this.discardedCount = selfie.discardedCount;
this.bitsToBucketIndices = selfie.bitsToBucketIndices;
bucketsFromSelfie(selfie.buckets);
urlTokenizer.fromSelfie(selfie.urlTokenizer);
return true;
};
FilterContainer.prototype.unserialize = async function(s) {
const selfie = new Map(JSON.parse(s));
const storage = {
get(name) {
return Promise.resolve(selfie.get(name));
}
};
return this.fromSelfie(storage, '');
};
/******************************************************************************/