From be2a950541522096526b2c7e7c3c47aca73cf6b5 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Wed, 19 Jun 2019 10:00:19 -0400 Subject: [PATCH] Code review of HNTrie/staticNetFilteringEngine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove HNTrieContainer class from global context by storing it as a property of µBlock. - Use block scope to isolate HNTrie-related constants from global context. - Prevent filters which are pure IP address from being stored in an HNTrie instance -- as this could cause false positives. --- src/js/hntrie.js | 143 ++++++++++++++++++--------------- src/js/static-net-filtering.js | 22 ++--- 2 files changed, 90 insertions(+), 75 deletions(-) diff --git a/src/js/hntrie.js b/src/js/hntrie.js index d2d903549..d1c0db5fb 100644 --- a/src/js/hntrie.js +++ b/src/js/hntrie.js @@ -20,10 +20,14 @@ */ /* globals WebAssembly */ -/* exported HNTrieContainer */ 'use strict'; +// ***************************************************************************** +// start of local namespace + +{ + /******************************************************************************* The original prototype was to develop an idea I had about using jump indices @@ -115,26 +119,26 @@ */ -const HNTRIE_PAGE_SIZE = 65536; - // i32 / i8 -const HNTRIE_TRIE0_SLOT = 256 >>> 2; // 64 / 256 -const HNTRIE_TRIE1_SLOT = HNTRIE_TRIE0_SLOT + 1; // 65 / 260 -const HNTRIE_CHAR0_SLOT = HNTRIE_TRIE0_SLOT + 2; // 66 / 264 -const HNTRIE_CHAR1_SLOT = HNTRIE_TRIE0_SLOT + 3; // 67 / 268 -const HNTRIE_TRIE0_START = HNTRIE_TRIE0_SLOT + 4 << 2; // 272 +const PAGE_SIZE = 65536; + // i32 / i8 +const TRIE0_SLOT = 256 >>> 2; // 64 / 256 +const TRIE1_SLOT = TRIE0_SLOT + 1; // 65 / 260 +const CHAR0_SLOT = TRIE0_SLOT + 2; // 66 / 264 +const CHAR1_SLOT = TRIE0_SLOT + 3; // 67 / 268 +const TRIE0_START = TRIE0_SLOT + 4 << 2; // 272 const HNTrieContainer = class { constructor(details) { if ( details instanceof Object === false ) { details = {}; } - let len = (details.byteLength || 0) + HNTRIE_PAGE_SIZE-1 & ~(HNTRIE_PAGE_SIZE-1); + let len = (details.byteLength || 0) + PAGE_SIZE-1 & ~(PAGE_SIZE-1); this.buf = new Uint8Array(Math.max(len, 131072)); this.buf32 = new Uint32Array(this.buf.buffer); this.needle = ''; - this.buf32[HNTRIE_TRIE0_SLOT] = HNTRIE_TRIE0_START; - this.buf32[HNTRIE_TRIE1_SLOT] = this.buf32[HNTRIE_TRIE0_SLOT]; - this.buf32[HNTRIE_CHAR0_SLOT] = details.char0 || 65536; - this.buf32[HNTRIE_CHAR1_SLOT] = this.buf32[HNTRIE_CHAR0_SLOT]; + this.buf32[TRIE0_SLOT] = TRIE0_START; + this.buf32[TRIE1_SLOT] = this.buf32[TRIE0_SLOT]; + this.buf32[CHAR0_SLOT] = details.char0 || 65536; + this.buf32[CHAR1_SLOT] = this.buf32[CHAR0_SLOT]; this.wasmInstancePromise = null; this.wasmMemory = null; this.readyToUse(); @@ -145,8 +149,8 @@ const HNTrieContainer = class { //-------------------------------------------------------------------------- reset() { - this.buf32[HNTRIE_TRIE1_SLOT] = this.buf32[HNTRIE_TRIE0_SLOT]; - this.buf32[HNTRIE_CHAR1_SLOT] = this.buf32[HNTRIE_CHAR0_SLOT]; + this.buf32[TRIE1_SLOT] = this.buf32[TRIE0_SLOT]; + this.buf32[CHAR1_SLOT] = this.buf32[CHAR0_SLOT]; } readyToUse() { @@ -162,7 +166,7 @@ const HNTrieContainer = class { if ( needle !== this.needle ) { const buf = this.buf; let i = needle.length; - if ( i > 254 ) { i = 254; } + if ( i > 255 ) { i = 255; } buf[255] = i; while ( i-- ) { buf[i] = needle.charCodeAt(i); @@ -173,21 +177,23 @@ const HNTrieContainer = class { } matchesJS(iroot) { - const char0 = this.buf32[HNTRIE_CHAR0_SLOT]; - let ineedle = this.buf[255]; - let icell = this.buf32[iroot+0]; + const buf32 = this.buf32; + const buf8 = this.buf; + const char0 = buf32[CHAR0_SLOT]; + let ineedle = buf8[255]; + let icell = buf32[iroot+0]; if ( icell === 0 ) { return -1; } for (;;) { if ( ineedle === 0 ) { return -1; } ineedle -= 1; - let c = this.buf[ineedle]; + let c = buf8[ineedle]; let v, i0; // find first segment with a first-character match for (;;) { - v = this.buf32[icell+2]; + v = buf32[icell+2]; i0 = char0 + (v & 0x00FFFFFF); - if ( this.buf[i0] === c ) { break; } - icell = this.buf32[icell+0]; + if ( buf8[i0] === c ) { break; } + icell = buf32[icell+0]; if ( icell === 0 ) { return -1; } } // all characters in segment must match @@ -199,21 +205,21 @@ const HNTrieContainer = class { const i1 = i0 + n; do { ineedle -= 1; - if ( this.buf[i0] !== this.buf[ineedle] ) { return -1; } + if ( buf8[i0] !== buf8[ineedle] ) { return -1; } i0 += 1; } while ( i0 < i1 ); } // next segment - icell = this.buf32[icell+1]; + icell = buf32[icell+1]; if ( icell === 0 ) { break; } - if ( this.buf32[icell+2] === 0 ) { - if ( ineedle === 0 || this.buf[ineedle-1] === 0x2E ) { + if ( buf32[icell+2] === 0 ) { + if ( ineedle === 0 || buf8[ineedle-1] === 0x2E ) { return ineedle; } - icell = this.buf32[icell+1]; + icell = buf32[icell+1]; } } - return ineedle === 0 || this.buf[ineedle-1] === 0x2E ? ineedle : -1; + return ineedle === 0 || buf8[ineedle-1] === 0x2E ? ineedle : -1; } createOne(args) { @@ -221,11 +227,11 @@ const HNTrieContainer = class { return new this.HNTrieRef(this, args[0], args[1]); } // grow buffer if needed - if ( (this.buf32[HNTRIE_CHAR0_SLOT] - this.buf32[HNTRIE_TRIE1_SLOT]) < 12 ) { + if ( (this.buf32[CHAR0_SLOT] - this.buf32[TRIE1_SLOT]) < 12 ) { this.growBuf(12, 0); } - const iroot = this.buf32[HNTRIE_TRIE1_SLOT] >>> 2; - this.buf32[HNTRIE_TRIE1_SLOT] += 12; + const iroot = this.buf32[TRIE1_SLOT] >>> 2; + this.buf32[TRIE1_SLOT] += 12; this.buf32[iroot+0] = 0; this.buf32[iroot+1] = 0; this.buf32[iroot+2] = 0; @@ -241,8 +247,8 @@ const HNTrieContainer = class { if ( lhnchar === 0 ) { return 0; } // grow buffer if needed if ( - (this.buf32[HNTRIE_CHAR0_SLOT] - this.buf32[HNTRIE_TRIE1_SLOT]) < 24 || - (this.buf.length - this.buf32[HNTRIE_CHAR1_SLOT]) < 256 + (this.buf32[CHAR0_SLOT] - this.buf32[TRIE1_SLOT]) < 24 || + (this.buf.length - this.buf32[CHAR1_SLOT]) < 256 ) { this.growBuf(24, 256); } @@ -253,7 +259,7 @@ const HNTrieContainer = class { return 1; } // - const char0 = this.buf32[HNTRIE_CHAR0_SLOT]; + const char0 = this.buf32[CHAR0_SLOT]; let inext; // find a matching cell: move down for (;;) { @@ -344,7 +350,7 @@ const HNTrieContainer = class { this.shrinkBuf(); return { byteLength: this.buf.byteLength, - char0: this.buf32[HNTRIE_CHAR0_SLOT], + char0: this.buf32[CHAR0_SLOT], }; } @@ -361,14 +367,14 @@ const HNTrieContainer = class { if ( encoder instanceof Object ) { return encoder.encode( this.buf32.buffer, - this.buf32[HNTRIE_CHAR1_SLOT] + this.buf32[CHAR1_SLOT] ); } return Array.from( new Uint32Array( this.buf32.buffer, 0, - this.buf32[HNTRIE_CHAR1_SLOT] + 3 >>> 2 + this.buf32[CHAR1_SLOT] + 3 >>> 2 ) ); } @@ -380,7 +386,7 @@ const HNTrieContainer = class { ? decoder.decodeSize(selfie) : selfie.length << 2; if ( byteLength === 0 ) { return false; } - byteLength = byteLength + HNTRIE_PAGE_SIZE-1 & ~(HNTRIE_PAGE_SIZE-1); + byteLength = byteLength + PAGE_SIZE-1 & ~(PAGE_SIZE-1); if ( this.wasmMemory !== null ) { const pageCountBefore = this.buf.length >>> 16; const pageCountAfter = byteLength >>> 16; @@ -406,8 +412,8 @@ const HNTrieContainer = class { //-------------------------------------------------------------------------- addCell(idown, iright, v) { - let icell = this.buf32[HNTRIE_TRIE1_SLOT]; - this.buf32[HNTRIE_TRIE1_SLOT] = icell + 12; + let icell = this.buf32[TRIE1_SLOT]; + this.buf32[TRIE1_SLOT] = icell + 12; icell >>>= 2; this.buf32[icell+0] = idown; this.buf32[icell+1] = iright; @@ -417,24 +423,24 @@ const HNTrieContainer = class { addSegment(lsegchar) { if ( lsegchar === 0 ) { return 0; } - let char1 = this.buf32[HNTRIE_CHAR1_SLOT]; - const isegchar = char1 - this.buf32[HNTRIE_CHAR0_SLOT]; + let char1 = this.buf32[CHAR1_SLOT]; + const isegchar = char1 - this.buf32[CHAR0_SLOT]; let i = lsegchar; do { this.buf[char1++] = this.buf[--i]; } while ( i !== 0 ); - this.buf32[HNTRIE_CHAR1_SLOT] = char1; + this.buf32[CHAR1_SLOT] = char1; return (lsegchar << 24) | isegchar; } growBuf(trieGrow, charGrow) { const char0 = Math.max( - (this.buf32[HNTRIE_TRIE1_SLOT] + trieGrow + HNTRIE_PAGE_SIZE-1) & ~(HNTRIE_PAGE_SIZE-1), - this.buf32[HNTRIE_CHAR0_SLOT] + (this.buf32[TRIE1_SLOT] + trieGrow + PAGE_SIZE-1) & ~(PAGE_SIZE-1), + this.buf32[CHAR0_SLOT] ); - const char1 = char0 + this.buf32[HNTRIE_CHAR1_SLOT] - this.buf32[HNTRIE_CHAR0_SLOT]; + const char1 = char0 + this.buf32[CHAR1_SLOT] - this.buf32[CHAR0_SLOT]; const bufLen = Math.max( - (char1 + charGrow + HNTRIE_PAGE_SIZE-1) & ~(HNTRIE_PAGE_SIZE-1), + (char1 + charGrow + PAGE_SIZE-1) & ~(PAGE_SIZE-1), this.buf.length ); this.resizeBuf(bufLen, char0); @@ -443,21 +449,21 @@ const HNTrieContainer = class { shrinkBuf() { // Can't shrink WebAssembly.Memory if ( this.wasmMemory !== null ) { return; } - const char0 = this.buf32[HNTRIE_TRIE1_SLOT] + 24; - const char1 = char0 + this.buf32[HNTRIE_CHAR1_SLOT] - this.buf32[HNTRIE_CHAR0_SLOT]; + const char0 = this.buf32[TRIE1_SLOT] + 24; + const char1 = char0 + this.buf32[CHAR1_SLOT] - this.buf32[CHAR0_SLOT]; const bufLen = char1 + 256; this.resizeBuf(bufLen, char0); } resizeBuf(bufLen, char0) { - bufLen = bufLen + HNTRIE_PAGE_SIZE-1 & ~(HNTRIE_PAGE_SIZE-1); + bufLen = bufLen + PAGE_SIZE-1 & ~(PAGE_SIZE-1); if ( bufLen === this.buf.length && - char0 === this.buf32[HNTRIE_CHAR0_SLOT] + char0 === this.buf32[CHAR0_SLOT] ) { return; } - const charDataLen = this.buf32[HNTRIE_CHAR1_SLOT] - this.buf32[HNTRIE_CHAR0_SLOT]; + const charDataLen = this.buf32[CHAR1_SLOT] - this.buf32[CHAR0_SLOT]; if ( this.wasmMemory !== null ) { const pageCount = (bufLen >>> 16) - (this.buf.byteLength >>> 16); if ( pageCount > 0 ) { @@ -471,34 +477,34 @@ const HNTrieContainer = class { new Uint8Array( this.buf.buffer, 0, - this.buf32[HNTRIE_TRIE1_SLOT] + this.buf32[TRIE1_SLOT] ), 0 ); newBuf.set( new Uint8Array( this.buf.buffer, - this.buf32[HNTRIE_CHAR0_SLOT], + this.buf32[CHAR0_SLOT], charDataLen ), char0 ); this.buf = newBuf; this.buf32 = new Uint32Array(this.buf.buffer); - this.buf32[HNTRIE_CHAR0_SLOT] = char0; - this.buf32[HNTRIE_CHAR1_SLOT] = char0 + charDataLen; + this.buf32[CHAR0_SLOT] = char0; + this.buf32[CHAR1_SLOT] = char0 + charDataLen; } - if ( char0 !== this.buf32[HNTRIE_CHAR0_SLOT] ) { + if ( char0 !== this.buf32[CHAR0_SLOT] ) { this.buf.set( new Uint8Array( this.buf.buffer, - this.buf32[HNTRIE_CHAR0_SLOT], + this.buf32[CHAR0_SLOT], charDataLen ), char0 ); - this.buf32[HNTRIE_CHAR0_SLOT] = char0; - this.buf32[HNTRIE_CHAR1_SLOT] = char0 + charDataLen; + this.buf32[CHAR0_SLOT] = char0; + this.buf32[CHAR1_SLOT] = char0 + charDataLen; } } @@ -520,7 +526,7 @@ const HNTrieContainer = class { this.wasmInstancePromise.then(instance => { this.wasmMemory = memory; const curPageCount = memory.buffer.byteLength >>> 16; - const newPageCount = this.buf.byteLength + HNTRIE_PAGE_SIZE-1 >>> 16; + const newPageCount = this.buf.byteLength + PAGE_SIZE-1 >>> 16; if ( newPageCount > curPageCount ) { memory.grow(newPageCount - curPageCount); } @@ -644,7 +650,7 @@ HNTrieContainer.prototype.HNTrieRef = class { this.forks.push(idown, this.charPtr); } const v = this.container.buf32[this.icell+2]; - let i0 = this.container.buf32[HNTRIE_CHAR0_SLOT] + (v & 0x00FFFFFF); + let i0 = this.container.buf32[CHAR0_SLOT] + (v & 0x00FFFFFF); const i1 = i0 + (v >>> 24); while ( i0 < i1 ) { this.charPtr -= 1; @@ -690,7 +696,7 @@ HNTrieContainer.prototype.HNTrieRef.prototype.needle = ''; // The WASM module is entirely optional, the JS implementations will be // used should the WASM module be unavailable for whatever reason. -(function() { +(( ) => { HNTrieContainer.wasmModulePromise = null; if ( @@ -748,3 +754,12 @@ HNTrieContainer.prototype.HNTrieRef.prototype.needle = ''; log.info(reason); }); })(); + +/******************************************************************************/ + +µBlock.HNTrieContainer = HNTrieContainer; + +// end of local namespace +// ***************************************************************************** + +} diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js index 9972a2b65..e79f980c3 100644 --- a/src/js/static-net-filtering.js +++ b/src/js/static-net-filtering.js @@ -20,7 +20,7 @@ */ /* jshint bitwise: false */ -/* global punycode, HNTrieContainer */ +/* global punycode */ 'use strict'; @@ -959,7 +959,7 @@ const filterOrigin = new (class { ); } catch(ex) { } - this.trieContainer = new HNTrieContainer(trieDetails); + this.trieContainer = new µBlock.HNTrieContainer(trieDetails); this.strSlots = []; this.strToSlotId = new Map(); this.gcTimer = undefined; @@ -1452,7 +1452,7 @@ const FilterHostnameDict = class { } }; -FilterHostnameDict.trieContainer = (function() { +FilterHostnameDict.trieContainer = (( ) => { let trieDetails; try { trieDetails = JSON.parse( @@ -1460,7 +1460,7 @@ FilterHostnameDict.trieContainer = (function() { ); } catch(ex) { } - return new HNTrieContainer(trieDetails); + return new µBlock.HNTrieContainer(trieDetails); })(); registerFilterClass(FilterHostnameDict); @@ -1816,8 +1816,8 @@ registerFilterClass(FilterBucket); const FilterParser = function() { this.cantWebsocket = vAPI.cantWebsocket; this.reBadDomainOptChars = /[*+?^${}()[\]\\]/; - this.reHostnameRule1 = /^[0-9a-z][0-9a-z.-]*[0-9a-z]$/i; - this.reHostnameRule2 = /^[0-9a-z][0-9a-z.-]*[0-9a-z]\^?$/i; + this.reHostnameRule1 = /^\w[\w.-]*[a-z]$/i; + this.reHostnameRule2 = /^\w[\w.-]*[a-z]\^?$/i; this.reCanTrimCarets1 = /^[^*]*$/; this.reCanTrimCarets2 = /^\^?[^^]+[^^][^^]+\^?$/; this.reIsolateHostname = /^(\*?\.)?([^\x00-\x24\x26-\x2C\x2F\x3A-\x5E\x60\x7B-\x7F]+)(.*)/; @@ -1879,8 +1879,8 @@ FilterParser.prototype.reset = function() { this.thirdParty = false; this.party = AnyParty; this.fopts = ''; - this.hostnamePure = false; this.domainOpt = ''; + this.isPureHostname = false; this.isRegex = false; this.raw = ''; this.redirect = false; @@ -2080,7 +2080,7 @@ FilterParser.prototype.parse = function(raw) { // plain hostname? (from HOSTS file) if ( this.reHostnameRule1.test(s) ) { this.f = s.toLowerCase(); - this.hostnamePure = true; + this.isPureHostname = true; this.anchor |= 0x4; return this; } @@ -2173,7 +2173,7 @@ FilterParser.prototype.parse = function(raw) { s = s.slice(0, -1); } this.f = s.toLowerCase(); - this.hostnamePure = true; + this.isPureHostname = true; return this; } } @@ -2651,7 +2651,7 @@ FilterContainer.prototype.compile = function(raw, writer) { // https://github.com/chrisaljoudi/uBlock/issues/665 // Create a dict keyed on request type etc. if ( - parsed.hostnamePure && + parsed.isPureHostname && parsed.domainOpt === '' && parsed.dataType === undefined ) { @@ -2665,7 +2665,7 @@ FilterContainer.prototype.compile = function(raw, writer) { let fdata; if ( parsed.isRegex ) { fdata = FilterRegex.compile(parsed); - } else if ( parsed.hostnamePure ) { + } else if ( parsed.isPureHostname ) { fdata = FilterPlainHostname.compile(parsed); } else if ( parsed.f === '*' ) { if ( parsed.isJustOrigin() ) {