mirror of https://github.com/gorhill/uBlock.git
Support negated pattern for stack test in scriptlets
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: - https://github.com/uBlockOrigin/uBlock-discussions/discussions/789#discussioncomment-6520330
This commit is contained in:
parent
bb7779ba75
commit
84cc69aa10
|
@ -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;
|
||||
|
@ -636,7 +666,7 @@ function objectPrune(
|
|||
obj,
|
||||
rawPrunePaths,
|
||||
rawNeedlePaths,
|
||||
stackNeedle = ''
|
||||
stackNeedleDetails = { matchAll: true }
|
||||
) {
|
||||
if ( typeof rawPrunePaths !== 'string' ) { return obj; }
|
||||
const prunePaths = rawPrunePaths !== ''
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 ||
|
||||
|
@ -1004,15 +1032,16 @@ 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('.');
|
||||
|
@ -1020,13 +1049,13 @@ function abortOnStackTrace(
|
|||
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;
|
||||
|
@ -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
|
||||
|
@ -1145,6 +1175,8 @@ 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) {
|
||||
|
@ -1152,7 +1184,7 @@ function jsonPrune(
|
|||
Reflect.apply(target, thisArg, args),
|
||||
rawPrunePaths,
|
||||
rawNeedlePaths,
|
||||
stackNeedle,
|
||||
stackNeedleDetails,
|
||||
...extraArgs
|
||||
);
|
||||
},
|
||||
|
@ -1164,7 +1196,7 @@ function jsonPrune(
|
|||
o,
|
||||
rawPrunePaths,
|
||||
rawNeedlePaths,
|
||||
stackNeedle,
|
||||
stackNeedleDetails,
|
||||
...extraArgs
|
||||
)
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
@ -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, '$$$')
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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 (;;) {
|
||||
|
|
Loading…
Reference in New Issue