summaryrefslogtreecommitdiff
path: root/jstests/core/js_global_scope.js
blob: c5fa98963f46b72070ea0522f64db6fb1912259b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
// 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());
}());