209 lines
6.7 KiB
JavaScript
209 lines
6.7 KiB
JavaScript
// Tests that the properties available in the global scope during js execution are a limited, known
|
|
// set. This should help prevent accidental additions or leaks of functions.
|
|
// @tags: [
|
|
// # mapReduce does not support afterClusterTime.
|
|
// does_not_support_causal_consistency,
|
|
// # Global variables depend on the version of JavaScript engine being used, which can change from
|
|
// # one MongoDB version to another. There is a little value checking list of JavaScript global
|
|
// # variables in multiversion setup, so we just disable the test from multiversion suites
|
|
// # instead.
|
|
// multiversion_incompatible,
|
|
// ]
|
|
(function() {
|
|
"use strict";
|
|
|
|
load("jstests/aggregation/extras/utils.js"); // For 'resultsEq'.
|
|
|
|
// Note: It's important to use our own database here to avoid sharing a javascript execution context
|
|
// (Scope) with other tests which could pollute the global scope. This context is cached and shared
|
|
// per database in a pool for every operation using JS in the same database.
|
|
const testDB = db.getSiblingDB("js_global_scope");
|
|
const coll = testDB.test;
|
|
coll.drop();
|
|
|
|
assert.commandWorked(coll.insert({_id: 0, a: 1}));
|
|
|
|
const expectedGlobalVars = [
|
|
"AggregateError",
|
|
"Array",
|
|
"ArrayBuffer",
|
|
"BigInt",
|
|
"BigInt64Array",
|
|
"BigUint64Array",
|
|
"BinData",
|
|
"Boolean",
|
|
"Code",
|
|
"DBPointer",
|
|
"DBRef",
|
|
"DataView",
|
|
"Date",
|
|
"Error",
|
|
"EvalError",
|
|
"Float32Array",
|
|
"Float64Array",
|
|
"Function",
|
|
"HexData",
|
|
"ISODate",
|
|
"Infinity",
|
|
"Int16Array",
|
|
"Int32Array",
|
|
"Int8Array",
|
|
"InternalError",
|
|
"JSON",
|
|
"MD5",
|
|
"Map",
|
|
"Math",
|
|
"MaxKey",
|
|
"MinKey",
|
|
"MongoURI",
|
|
"NaN",
|
|
"Number",
|
|
"NumberDecimal",
|
|
"NumberInt",
|
|
"NumberLong",
|
|
"Object",
|
|
"ObjectId",
|
|
"Promise",
|
|
"Proxy",
|
|
"RangeError",
|
|
"ReferenceError",
|
|
"RegExp",
|
|
"Set",
|
|
"String",
|
|
"Symbol",
|
|
"SyntaxError",
|
|
"Timestamp",
|
|
"TypeError",
|
|
"URIError",
|
|
"UUID",
|
|
"Uint16Array",
|
|
"Uint32Array",
|
|
"Uint8Array",
|
|
"Uint8ClampedArray",
|
|
"WeakMap",
|
|
"WeakSet",
|
|
"__lastres__",
|
|
"_convertExceptionToReturnStatus",
|
|
"assert",
|
|
"bsonBinaryEqual",
|
|
"bsonObjToArray",
|
|
"bsonUnorderedFieldsCompare",
|
|
"bsonWoCompare",
|
|
"buildInfo",
|
|
"decodeURI",
|
|
"decodeURIComponent",
|
|
"doassert",
|
|
"encodeURI",
|
|
"encodeURIComponent",
|
|
"escape",
|
|
"eval",
|
|
"gc",
|
|
"getJSHeapLimitMB",
|
|
"globalThis",
|
|
"hex_md5",
|
|
"isFinite",
|
|
"isNaN",
|
|
"isNumber",
|
|
"isObject",
|
|
"isString",
|
|
"parseFloat",
|
|
"parseInt",
|
|
"print",
|
|
"printjson",
|
|
"printjsononeline",
|
|
"sleep",
|
|
"sortDoc",
|
|
"tojson",
|
|
"tojsonObject",
|
|
"tojsononeline",
|
|
"tostrictjson",
|
|
"undefined",
|
|
"unescape",
|
|
"version",
|
|
];
|
|
|
|
// Symbols in 'optionalGlobalVars' may appear in the global property list, but this test does not
|
|
// require them and will not fail if they are missing.
|
|
const optionalGlobalVars = [
|
|
// Not all platforms support WebAssembly, and it is possible to compile the JavaScript engine
|
|
// without WebAssembly included, in which case, this "WebAssembly" symbol will be missing.
|
|
"WebAssembly",
|
|
];
|
|
|
|
// Note: it is important that this is sorted to compare to sorted variable names below.
|
|
expectedGlobalVars.sort();
|
|
|
|
function getGlobalProps() {
|
|
const global = function() {
|
|
return this;
|
|
}();
|
|
return Object.getOwnPropertyNames(global);
|
|
}
|
|
|
|
const props =
|
|
coll.aggregate([
|
|
{$replaceWith: {varName: {$function: {lang: "js", args: [], body: getGlobalProps}}}},
|
|
{$unwind: "$varName"},
|
|
{$match: {varName: {$nin: optionalGlobalVars}}},
|
|
{$sort: {varName: 1}},
|
|
])
|
|
.toArray();
|
|
|
|
// Because both are sorted, we can pinpoint any that are missing by going in order.
|
|
for (let i = 0; i < Math.min(expectedGlobalVars.length, props.length); ++i) {
|
|
const foundProp = props[i].varName;
|
|
const expectedProp = expectedGlobalVars[i];
|
|
assert.eq(foundProp, expectedProp, () => {
|
|
if (foundProp < expectedProp) {
|
|
return `Found an unexpected extra global property during JS execution: "${
|
|
foundProp}".\n Expected only ${tojson(expectedGlobalVars)}.\n Found ${
|
|
tojson(props)}.`;
|
|
} else {
|
|
return `Did not find an expected global property during JS execution: "${
|
|
expectedProp}".\n Expected only ${tojson(expectedGlobalVars)}.\n Found ${
|
|
tojson(props)}.`;
|
|
}
|
|
});
|
|
}
|
|
assert.lte(expectedGlobalVars.length,
|
|
props.length,
|
|
() => `Did not find expected global properties during JS execution: ${
|
|
tojson(expectedGlobalVars.slice(props.length))}.\n Full list expected: ${
|
|
tojson(expectedGlobalVars)}.\n Full list found: ${tojson(props)}.`);
|
|
assert.lte(props.length,
|
|
expectedGlobalVars.length,
|
|
() => `Found extra global properties during JS execution: ${
|
|
tojson(props.slice(expectedGlobalVars.length))}`);
|
|
|
|
// Now test the same properties appear in a $where. We have two additional expected properties which
|
|
// are defined by $where itself before executing the filter function.
|
|
const expectedVarsInWhere = expectedGlobalVars.concat(["obj", "fullObject"]);
|
|
assert.eq(
|
|
coll.find().itcount(),
|
|
coll.find({
|
|
$where: "const global = function() { return this; }();\n" +
|
|
"printjsononeline(Object.getOwnPropertyNames(global));\n" +
|
|
`printjsononeline(${tojsononeline(expectedVarsInWhere)});\n` +
|
|
`const optional = ${tojsononeline(optionalGlobalVars)};\n` +
|
|
"const found = new Set(Object.getOwnPropertyNames(global)\n" +
|
|
" .filter(varName => !optional.includes(varName)));\n" +
|
|
`const expected = new Set(${tojsononeline(expectedVarsInWhere)});\n` +
|
|
"for (let foundItem of found) {\n" +
|
|
// __returnValue is a special case that we allow. This is populated _after_ we call
|
|
// the function in order to communicate the return value. Thus, we don't expect it
|
|
// on the first invocation, but it could happen if this test is run multiple times
|
|
// in a row.
|
|
" if (!expected.has(foundItem) && foundItem != '__returnValue') {\n" +
|
|
" print('found extra item: ' + foundItem); return false;\n" +
|
|
" }\n" +
|
|
"}\n" +
|
|
"for (let expectedItem of expected) {\n" +
|
|
" if (!found.has(expectedItem)) {\n" +
|
|
" print('did not find expected item: ' + expectedItem); return false;\n" +
|
|
" }\n" +
|
|
"}\n" +
|
|
"return true;"
|
|
})
|
|
.itcount());
|
|
}());
|