mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 05:29:44 +02:00
fix(core): Charge element content for comparing array methods in native evaluation (no-changelog) (#41079)
This commit is contained in:
parent
f5924004ca
commit
fbe743b9df
|
|
@ -301,6 +301,31 @@ function assertPrimitiveElements(receiver: unknown[]): void {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* toSorted() stringifies both operands of every comparison, and includes(),
|
||||
* indexOf() and lastIndexOf() compare each element with the search value, so
|
||||
* their work follows element content, not element count. Returns that
|
||||
* content, which the caller charges on top of the preflight bound.
|
||||
*/
|
||||
function elementContentWork(receiver: unknown[], method: string): number {
|
||||
if (method === 'toSorted') {
|
||||
let content = 0;
|
||||
for (const element of receiver) content += toStr(element).length;
|
||||
|
||||
// Each comparison reads both operands, so a pass can read the content twice.
|
||||
return 2 * content * Math.ceil(Math.log2(receiver.length + 1));
|
||||
}
|
||||
|
||||
if (method === 'includes' || method === 'indexOf' || method === 'lastIndexOf') {
|
||||
let content = 0;
|
||||
for (const element of receiver) content += typeof element === 'string' ? element.length : 1;
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Content size of a JSON value: string lengths plus one per element and
|
||||
* key, stopping early past the budget. Deeper than the grammar's own depth
|
||||
|
|
@ -391,6 +416,7 @@ function evalCall(node: Extract<SimpleNode, { kind: 'call' }>, env: Env): unknow
|
|||
if (node.method === 'concat') assertConcatWeight(receiver, args);
|
||||
}
|
||||
env.charge(preflightSize(receiver, node.method, args));
|
||||
if (isArray(receiver)) env.charge(elementContentWork(receiver, node.method));
|
||||
|
||||
return bounded(method.apply(receiver, args));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -307,6 +307,10 @@ export const MAX_STEPS = 100_000;
|
|||
// visit does (a body can scan a MAX_RESULT_LENGTH string per element); this
|
||||
// bounds the product, in units the interpreter already computes, so it does
|
||||
// not depend on the clock or the machine.
|
||||
//
|
||||
// Rule for every allowlisted method: its charge is an upper bound on the
|
||||
// characters it touches, not the elements it counts. A method that compares
|
||||
// or stringifies elements is charged by their content.
|
||||
export const MAX_WORK = 10 * MAX_RESULT_LENGTH;
|
||||
|
||||
// Nesting depth of the subset grammar. Parsing and evaluation both recurse
|
||||
|
|
|
|||
|
|
@ -483,6 +483,28 @@ describe('Expression - fast native evaluation parity', () => {
|
|||
});
|
||||
});
|
||||
|
||||
// Few elements, much content: an element count would charge almost
|
||||
// nothing for work that compares or stringifies every character.
|
||||
test('element-comparing methods are charged by element content', () => {
|
||||
const big = 'x'.repeat(MAX_RESULT_LENGTH);
|
||||
const fits = MAX_WORK / MAX_RESULT_LENGTH - 1;
|
||||
const list = (count: number) => ({ $json: { list: new Array<string>(count).fill(big) } });
|
||||
for (const method of ['includes', 'indexOf', 'lastIndexOf']) {
|
||||
const expr = `{{ $json.list.${method}("y") }}`;
|
||||
expect(nativeOn(expr, list(fits + 2))).toEqual({ handled: false });
|
||||
expect(nativeOn(expr, list(fits)).handled).toBe(true);
|
||||
}
|
||||
// Twice the content times log2 of the count: 3 elements cost 12
|
||||
// receivers, 2 cost 8.
|
||||
expect(nativeOn('{{ $json.list.toSorted().length }}', list(3))).toEqual({
|
||||
handled: false,
|
||||
});
|
||||
expect(nativeOn('{{ $json.list.toSorted().length }}', list(2))).toEqual({
|
||||
handled: true,
|
||||
value: 2,
|
||||
});
|
||||
});
|
||||
|
||||
// concat can reference one payload string many times; the clone of the
|
||||
// result and every downstream method then pay for each reference, so it
|
||||
// is bounded by content size, not element count.
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user