mongo/jstests/aggregation/js/agg_infinite_recursion.js

93 lines
3.2 KiB
JavaScript

// This test checks that an infinite recursion correctly produces an 'InternalError: too much
// recursion' error and does not crash the server.
// @tags: [
// requires_scripting,
// ]
import {FixtureHelpers} from "jstests/libs/fixture_helpers.js";
const makeBinData = () => BinData(4, "gf1UcxdHTJ2HQ/EGQrO7mQ==");
const makeUUID = () => UUID("81fd5473-1747-4c9d-8743-f10642b3bb99");
const makeHexData = () => new HexData(4, "81fd547317474c9d8743f10642b3bb99");
/* eslint-disable */
function whereFnTemplate() {
let testRecursiveFn = (i) => {
__fn_placeholder__();
testRecursiveFn(i + 1);
};
testRecursiveFn(0);
}
/* eslint-enable */
function recursiveFindWhere(db, collectionName, fn) {
return db[collectionName].runCommand("find", {
filter: {$where: whereFnTemplate.toString().replace("__fn_placeholder__", fn.toString())},
});
}
function recursiveAggregateFunction(db, collectionName, fn) {
return db.runCommand({
"aggregate": collectionName,
"pipeline": [
{
$addFields: {
fld: {
$function: {
body: whereFnTemplate.toString().replace("__fn_placeholder__", fn.toString()),
args: ["$name"],
lang: "js",
},
},
},
},
],
cursor: {},
});
}
function assertThrowsInfiniteRecursion(res) {
assert.commandFailedWithCode(res, ErrorCodes.JSInterpreterFailure);
assert(/too much recursion/.test(res.errmsg), `Error wasn't caused by infinite recursion: ${tojson(res)}`);
// The choice of 20 for the number of frames is somewhat arbitrary. We check for there to be
// some reasonable number of stack frames because most regressions would cause the stack to
// contain a single frame or none at all.
const kMinExpectedStack = 20;
assert.gte(
res.errmsg.split("\n").length,
kMinExpectedStack,
`Error didn't preserve the JavaScript stacktrace: ${tojson(res)}`,
);
}
function runTests(db, collectionName, recursiveFn) {
assertThrowsInfiniteRecursion(recursiveFn(db, collectionName, makeBinData));
assertThrowsInfiniteRecursion(recursiveFn(db, collectionName, makeUUID));
assertThrowsInfiniteRecursion(recursiveFn(db, collectionName, makeHexData));
}
const setJsTimeout = function (timeout) {
const commandResArr = FixtureHelpers.runCommandOnEachPrimary({
db: db.getSiblingDB("admin"),
cmdObj: {
setParameter: 1,
internalQueryJavaScriptFnTimeoutMillis: timeout,
},
});
assert.gt(commandResArr.length, 0, "Setting internalQueryJavaScriptFnTimeoutMillis on primaries failed");
return assert.commandWorked(commandResArr[0]).was;
};
const previousJsTimeout = setJsTimeout(600 * 1000);
try {
const collectionName = "agg_infinite_recursion";
const collection = db[collectionName];
collection.drop();
assert.commandWorked(collection.insert({name: "Mo"}));
runTests(db, collectionName, recursiveFindWhere);
runTests(db, collectionName, recursiveAggregateFunction);
} finally {
setJsTimeout(previousJsTimeout);
}