Files
mongo/jstests/core/js_global_scope.js

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());
}());