Skip to content

Commit

Permalink
Support negated pattern for stack test in scriptlets
Browse files Browse the repository at this point in the history
Prepend pattern with `!` to test for unmatched patterns in
stack trace. This applies to sctiplet parameters which purpose
is to test against the stack, i.e. `aost` and `json-prune`.

Additionally, dropped support for JSON notation in favor of
optional variable arguments notation.

Related discussion:
- uBlockOrigin/uBlock-discussions#789 (comment)
  • Loading branch information
gorhill committed Jul 31, 2023
1 parent bb7779b commit 84cc69a
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 45 deletions.
58 changes: 45 additions & 13 deletions assets/resources/scriptlets.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,36 @@ function safeSelf() {
if ( `${args[0]}` === '' ) { return; }
this.log('[uBO]', ...args);
},
'initPattern': function(pattern, options = {}) {
if ( pattern === '' ) {
return { matchAll: true };
}
const expect = (options.canNegate && pattern.startsWith('!') === false);
if ( expect === false ) {
pattern = pattern.slice(1);
}
const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern);
if ( match !== null ) {
return {
re: new this.RegExp(
match[1],
match[2] || options.flags
),
expect,
};
}
return {
re: new this.RegExp(pattern.replace(
/[.*+?^${}()|[\]\\]/g, '\\$&'),
options.flags
),
expect,
};
},
'testPattern': function(details, haystack) {
if ( details.matchAll ) { return true; }
return this.RegExp_test.call(details.re, haystack) === details.expect;
},
};
scriptletGlobals.set('safeSelf', safe);
return safe;
Expand Down Expand Up @@ -636,7 +666,7 @@ function objectPrune(
obj,
rawPrunePaths,
rawNeedlePaths,
stackNeedle = ''
stackNeedleDetails = { matchAll: true }
) {
if ( typeof rawPrunePaths !== 'string' ) { return obj; }
const prunePaths = rawPrunePaths !== ''
Expand All @@ -657,9 +687,8 @@ function objectPrune(
log = console.log.bind(console);
reLogNeedle = patternToRegex(rawNeedlePaths);
}
if ( stackNeedle !== '' ) {
const reStackNeedle = patternToRegex(stackNeedle);
if ( matchesStackTrace(reStackNeedle, extraArgs.logstack) === false ) {
if ( stackNeedleDetails.matchAll !== true ) {
if ( matchesStackTrace(stackNeedleDetails, extraArgs.logstack) === false ) {
return obj;
}
}
Expand Down Expand Up @@ -828,10 +857,9 @@ builtinScriptlets.push({
],
});
function matchesStackTrace(
reNeedle,
needleDetails,
logLevel = 0
) {
if ( reNeedle === undefined ) { return false; }
const safe = safeSelf();
const exceptionToken = getExceptionToken();
const error = new safe.Error(exceptionToken);
Expand Down Expand Up @@ -861,7 +889,7 @@ function matchesStackTrace(
}
lines[0] = `stackDepth:${lines.length-1}`;
const stack = lines.join('\t');
const r = safe.RegExp_test.call(reNeedle, stack);
const r = safe.testPattern(needleDetails, stack);
if (
logLevel === 1 ||
logLevel === 2 && r ||
Expand Down Expand Up @@ -1004,29 +1032,30 @@ builtinScriptlets.push({
'get-extra-args.fn',
'matches-stack-trace.fn',
'pattern-to-regex.fn',
'safe-self.fn',
],
});
// Status is currently experimental
function abortOnStackTrace(
chain = '',
needle = ''
) {
if ( typeof chain !== 'string' ) { return; }
const reNeedle = patternToRegex(needle);
const safe = safeSelf();
const needleDetails = safe.initPattern(needle, { canNegate: true });
const extraArgs = getExtraArgs(Array.from(arguments), 2);
const makeProxy = function(owner, chain) {
const pos = chain.indexOf('.');
if ( pos === -1 ) {
let v = owner[chain];
Object.defineProperty(owner, chain, {
get: function() {
if ( matchesStackTrace(reNeedle, extraArgs.log) ) {
if ( matchesStackTrace(needleDetails, extraArgs.log) ) {
throw new ReferenceError(getExceptionToken());
}
return v;
},
set: function(a) {
if ( matchesStackTrace(reNeedle, extraArgs.log) ) {
if ( matchesStackTrace(needleDetails, extraArgs.log) ) {
throw new ReferenceError(getExceptionToken());
}
v = a;
Expand Down Expand Up @@ -1132,6 +1161,7 @@ builtinScriptlets.push({
fn: jsonPrune,
dependencies: [
'object-prune.fn',
'safe-self.fn',
],
});
// When no "prune paths" argument is provided, the scriptlet is
Expand All @@ -1145,14 +1175,16 @@ function jsonPrune(
rawNeedlePaths = '',
stackNeedle = ''
) {
const safe = safeSelf();
const stackNeedleDetails = safe.initPattern(stackNeedle, { canNegate: true });
const extraArgs = Array.from(arguments).slice(3);
JSON.parse = new Proxy(JSON.parse, {
apply: function(target, thisArg, args) {
return objectPrune(
Reflect.apply(target, thisArg, args),
rawPrunePaths,
rawNeedlePaths,
stackNeedle,
stackNeedleDetails,
...extraArgs
);
},
Expand All @@ -1164,7 +1196,7 @@ function jsonPrune(
o,
rawPrunePaths,
rawNeedlePaths,
stackNeedle,
stackNeedleDetails,
...extraArgs
)
);
Expand Down
13 changes: 1 addition & 12 deletions src/js/scriptlet-filtering.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ const VERSION = 1;

const duplicates = new Set();
const scriptletCache = new µb.MRUCache(32);
const reEscapeScriptArg = /[\\'"]/g;

const scriptletDB = new StaticExtFilteringHostnameDB(1, VERSION);

Expand Down Expand Up @@ -206,21 +205,11 @@ const patchScriptlet = function(content, arglist) {
if ( content.startsWith('function') && content.endsWith('}') ) {
content = `(${content})({{args}});`;
}
if ( arglist.length === 0 ) {
return content.replace('{{args}}', '');
}
if ( arglist.length === 1 ) {
if ( arglist[0].startsWith('{') && arglist[0].endsWith('}') ) {
return content.replace('{{args}}', arglist[0]);
}
}
for ( let i = 0; i < arglist.length; i++ ) {
content = content.replace(`{{${i+1}}}`, arglist[i]);
}
return content.replace('{{args}}',
arglist.map(a => `'${a.replace(reEscapeScriptArg, '\\$&')}'`)
.join(', ')
.replace(/\$/g, '$$$')
JSON.stringify(arglist).slice(1,-1).replace(/\$/g, '$$$')
);
};

Expand Down
22 changes: 2 additions & 20 deletions src/js/static-filtering-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -2266,27 +2266,9 @@ export class AstFilterParser {
const parentEnd = this.nodes[parent+NODE_END_INDEX];
if ( parentEnd === parentBeg ) { return 0; }
const s = this.getNodeString(parent);
let next = 0;
// json-based arg?
const match = this.rePatternScriptletJsonArgs.exec(s);
if ( match !== null ) {
next = this.allocTypedNode(
NODE_TYPE_EXT_PATTERN_SCRIPTLET_ARG,
parentBeg,
parentEnd
);
try {
void JSON.parse(s);
} catch(ex) {
this.addNodeFlags(next, NODE_FLAG_ERROR);
this.addFlags(AST_FLAG_HAS_ERROR);
}
return next;
}
// positional args
const head = this.allocHeadNode();
const argsEnd = s.length;
let prev = head;
const head = this.allocHeadNode();
let prev = head, next = 0;
let decorationBeg = 0;
let i = 0;
for (;;) {
Expand Down

0 comments on commit 84cc69a

Please sign in to comment.