Skip to content

Commit 84cc69a

Browse files
committed
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: - uBlockOrigin/uBlock-discussions#789 (comment)
1 parent bb7779b commit 84cc69a

File tree

3 files changed

+48
-45
lines changed

3 files changed

+48
-45
lines changed

assets/resources/scriptlets.js

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,36 @@ function safeSelf() {
5959
if ( `${args[0]}` === '' ) { return; }
6060
this.log('[uBO]', ...args);
6161
},
62+
'initPattern': function(pattern, options = {}) {
63+
if ( pattern === '' ) {
64+
return { matchAll: true };
65+
}
66+
const expect = (options.canNegate && pattern.startsWith('!') === false);
67+
if ( expect === false ) {
68+
pattern = pattern.slice(1);
69+
}
70+
const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern);
71+
if ( match !== null ) {
72+
return {
73+
re: new this.RegExp(
74+
match[1],
75+
match[2] || options.flags
76+
),
77+
expect,
78+
};
79+
}
80+
return {
81+
re: new this.RegExp(pattern.replace(
82+
/[.*+?^${}()|[\]\\]/g, '\\$&'),
83+
options.flags
84+
),
85+
expect,
86+
};
87+
},
88+
'testPattern': function(details, haystack) {
89+
if ( details.matchAll ) { return true; }
90+
return this.RegExp_test.call(details.re, haystack) === details.expect;
91+
},
6292
};
6393
scriptletGlobals.set('safeSelf', safe);
6494
return safe;
@@ -636,7 +666,7 @@ function objectPrune(
636666
obj,
637667
rawPrunePaths,
638668
rawNeedlePaths,
639-
stackNeedle = ''
669+
stackNeedleDetails = { matchAll: true }
640670
) {
641671
if ( typeof rawPrunePaths !== 'string' ) { return obj; }
642672
const prunePaths = rawPrunePaths !== ''
@@ -657,9 +687,8 @@ function objectPrune(
657687
log = console.log.bind(console);
658688
reLogNeedle = patternToRegex(rawNeedlePaths);
659689
}
660-
if ( stackNeedle !== '' ) {
661-
const reStackNeedle = patternToRegex(stackNeedle);
662-
if ( matchesStackTrace(reStackNeedle, extraArgs.logstack) === false ) {
690+
if ( stackNeedleDetails.matchAll !== true ) {
691+
if ( matchesStackTrace(stackNeedleDetails, extraArgs.logstack) === false ) {
663692
return obj;
664693
}
665694
}
@@ -828,10 +857,9 @@ builtinScriptlets.push({
828857
],
829858
});
830859
function matchesStackTrace(
831-
reNeedle,
860+
needleDetails,
832861
logLevel = 0
833862
) {
834-
if ( reNeedle === undefined ) { return false; }
835863
const safe = safeSelf();
836864
const exceptionToken = getExceptionToken();
837865
const error = new safe.Error(exceptionToken);
@@ -861,7 +889,7 @@ function matchesStackTrace(
861889
}
862890
lines[0] = `stackDepth:${lines.length-1}`;
863891
const stack = lines.join('\t');
864-
const r = safe.RegExp_test.call(reNeedle, stack);
892+
const r = safe.testPattern(needleDetails, stack);
865893
if (
866894
logLevel === 1 ||
867895
logLevel === 2 && r ||
@@ -1004,29 +1032,30 @@ builtinScriptlets.push({
10041032
'get-extra-args.fn',
10051033
'matches-stack-trace.fn',
10061034
'pattern-to-regex.fn',
1035+
'safe-self.fn',
10071036
],
10081037
});
1009-
// Status is currently experimental
10101038
function abortOnStackTrace(
10111039
chain = '',
10121040
needle = ''
10131041
) {
10141042
if ( typeof chain !== 'string' ) { return; }
1015-
const reNeedle = patternToRegex(needle);
1043+
const safe = safeSelf();
1044+
const needleDetails = safe.initPattern(needle, { canNegate: true });
10161045
const extraArgs = getExtraArgs(Array.from(arguments), 2);
10171046
const makeProxy = function(owner, chain) {
10181047
const pos = chain.indexOf('.');
10191048
if ( pos === -1 ) {
10201049
let v = owner[chain];
10211050
Object.defineProperty(owner, chain, {
10221051
get: function() {
1023-
if ( matchesStackTrace(reNeedle, extraArgs.log) ) {
1052+
if ( matchesStackTrace(needleDetails, extraArgs.log) ) {
10241053
throw new ReferenceError(getExceptionToken());
10251054
}
10261055
return v;
10271056
},
10281057
set: function(a) {
1029-
if ( matchesStackTrace(reNeedle, extraArgs.log) ) {
1058+
if ( matchesStackTrace(needleDetails, extraArgs.log) ) {
10301059
throw new ReferenceError(getExceptionToken());
10311060
}
10321061
v = a;
@@ -1132,6 +1161,7 @@ builtinScriptlets.push({
11321161
fn: jsonPrune,
11331162
dependencies: [
11341163
'object-prune.fn',
1164+
'safe-self.fn',
11351165
],
11361166
});
11371167
// When no "prune paths" argument is provided, the scriptlet is
@@ -1145,14 +1175,16 @@ function jsonPrune(
11451175
rawNeedlePaths = '',
11461176
stackNeedle = ''
11471177
) {
1178+
const safe = safeSelf();
1179+
const stackNeedleDetails = safe.initPattern(stackNeedle, { canNegate: true });
11481180
const extraArgs = Array.from(arguments).slice(3);
11491181
JSON.parse = new Proxy(JSON.parse, {
11501182
apply: function(target, thisArg, args) {
11511183
return objectPrune(
11521184
Reflect.apply(target, thisArg, args),
11531185
rawPrunePaths,
11541186
rawNeedlePaths,
1155-
stackNeedle,
1187+
stackNeedleDetails,
11561188
...extraArgs
11571189
);
11581190
},
@@ -1164,7 +1196,7 @@ function jsonPrune(
11641196
o,
11651197
rawPrunePaths,
11661198
rawNeedlePaths,
1167-
stackNeedle,
1199+
stackNeedleDetails,
11681200
...extraArgs
11691201
)
11701202
);

src/js/scriptlet-filtering.js

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ const VERSION = 1;
4141

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

4645
const scriptletDB = new StaticExtFilteringHostnameDB(1, VERSION);
4746

@@ -206,21 +205,11 @@ const patchScriptlet = function(content, arglist) {
206205
if ( content.startsWith('function') && content.endsWith('}') ) {
207206
content = `(${content})({{args}});`;
208207
}
209-
if ( arglist.length === 0 ) {
210-
return content.replace('{{args}}', '');
211-
}
212-
if ( arglist.length === 1 ) {
213-
if ( arglist[0].startsWith('{') && arglist[0].endsWith('}') ) {
214-
return content.replace('{{args}}', arglist[0]);
215-
}
216-
}
217208
for ( let i = 0; i < arglist.length; i++ ) {
218209
content = content.replace(`{{${i+1}}}`, arglist[i]);
219210
}
220211
return content.replace('{{args}}',
221-
arglist.map(a => `'${a.replace(reEscapeScriptArg, '\\$&')}'`)
222-
.join(', ')
223-
.replace(/\$/g, '$$$')
212+
JSON.stringify(arglist).slice(1,-1).replace(/\$/g, '$$$')
224213
);
225214
};
226215

src/js/static-filtering-parser.js

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2266,27 +2266,9 @@ export class AstFilterParser {
22662266
const parentEnd = this.nodes[parent+NODE_END_INDEX];
22672267
if ( parentEnd === parentBeg ) { return 0; }
22682268
const s = this.getNodeString(parent);
2269-
let next = 0;
2270-
// json-based arg?
2271-
const match = this.rePatternScriptletJsonArgs.exec(s);
2272-
if ( match !== null ) {
2273-
next = this.allocTypedNode(
2274-
NODE_TYPE_EXT_PATTERN_SCRIPTLET_ARG,
2275-
parentBeg,
2276-
parentEnd
2277-
);
2278-
try {
2279-
void JSON.parse(s);
2280-
} catch(ex) {
2281-
this.addNodeFlags(next, NODE_FLAG_ERROR);
2282-
this.addFlags(AST_FLAG_HAS_ERROR);
2283-
}
2284-
return next;
2285-
}
2286-
// positional args
2287-
const head = this.allocHeadNode();
22882269
const argsEnd = s.length;
2289-
let prev = head;
2270+
const head = this.allocHeadNode();
2271+
let prev = head, next = 0;
22902272
let decorationBeg = 0;
22912273
let i = 0;
22922274
for (;;) {

0 commit comments

Comments
 (0)