diff options
author | Jonathan Reams <jbreams@mongodb.com> | 2015-07-29 23:47:50 -0400 |
---|---|---|
committer | Jonathan Reams <jbreams@mongodb.com> | 2015-08-12 14:13:47 -0400 |
commit | 2bef8a3292a00090b8b62e27119481e5b21808cb (patch) | |
tree | ba02d598f735d5a0fdfecbc124ad20a58d464f0d | |
parent | d2fe88e34a93fb6d403307bd6b890d4f45d6935a (diff) | |
download | mongo-2bef8a3292a00090b8b62e27119481e5b21808cb.tar.gz |
SERVER-16703 Improve parsing of JS function bodies and expressions
-rw-r--r-- | src/mongo/dbtests/jstests.cpp | 9 | ||||
-rw-r--r-- | src/mongo/scripting/SConscript | 9 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/implscope.cpp | 17 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/implscope.h | 6 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/mongohelpers.cpp | 94 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/mongohelpers.h | 53 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/mongohelpers.js | 68 |
7 files changed, 234 insertions, 22 deletions
diff --git a/src/mongo/dbtests/jstests.cpp b/src/mongo/dbtests/jstests.cpp index 4a4ebb408a4..19f6ea37a2a 100644 --- a/src/mongo/dbtests/jstests.cpp +++ b/src/mongo/dbtests/jstests.cpp @@ -2139,20 +2139,11 @@ public: s->invoke("String(' return ')", 0, 0); ASSERT_EQUALS(" return ", s->getString("__returnValue")); - // This should fail so we set the expected __returnValue to undefined - s->invoke(";x = 5", 0, 0); - ASSERT_EQUALS("undefined", s->getString("__returnValue")); - s->invoke("String(\"'return\")", 0, 0); ASSERT_EQUALS("'return", s->getString("__returnValue")); s->invoke("String('\"return')", 0, 0); ASSERT_EQUALS("\"return", s->getString("__returnValue")); - - // A fail case - s->invoke("return$ = 0", 0, 0); - // Checks to confirm that the result is NaN - ASSERT(s->getNumber("__returnValue") != s->getNumber("__returnValue")); } }; diff --git a/src/mongo/scripting/SConscript b/src/mongo/scripting/SConscript index f2e51237543..e81f2e4c705 100644 --- a/src/mongo/scripting/SConscript +++ b/src/mongo/scripting/SConscript @@ -101,6 +101,13 @@ elif usemozjs: 'STATIC_JS_API=1', ]) + scriptingEnv.JSHeader( + target='mozjs/mongohelpers_js.cpp', + source=[ + 'mozjs/mongohelpers.js' + ] + ) + scriptingEnv.Library( target='scripting', source=[ @@ -125,6 +132,8 @@ elif usemozjs: 'mozjs/maxkey.cpp', 'mozjs/minkey.cpp', 'mozjs/mongo.cpp', + 'mozjs/mongohelpers.cpp', + 'mozjs/mongohelpers_js.cpp', 'mozjs/nativefunction.cpp', 'mozjs/numberint.cpp', 'mozjs/numberlong.cpp', diff --git a/src/mongo/scripting/mozjs/implscope.cpp b/src/mongo/scripting/mozjs/implscope.cpp index 0c110ec546a..d594623a31e 100644 --- a/src/mongo/scripting/mozjs/implscope.cpp +++ b/src/mongo/scripting/mozjs/implscope.cpp @@ -240,6 +240,7 @@ MozJSImplScope::MozJSImplScope(MozJSScriptEngine* engine) _maxKeyProto(_context), _minKeyProto(_context), _mongoExternalProto(_context), + _mongoHelpersProto(_context), _mongoLocalProto(_context), _nativeFunctionProto(_context), _numberIntProto(_context), @@ -276,6 +277,7 @@ MozJSImplScope::MozJSImplScope(MozJSScriptEngine* engine) // install global utility functions installGlobalUtils(*this); + _mongoHelpersProto.install(_global); } MozJSImplScope::~MozJSImplScope() { @@ -420,22 +422,11 @@ bool hasFunctionIdentifier(StringData code) { return code[8] == ' ' || code[8] == '('; } -// TODO: This function identification code is broken. Fix it up to be more robust -// -// See: SERVER-16703 for more info void MozJSImplScope::_MozJSCreateFunction(const char* raw, ScriptingFunction functionNumber, JS::MutableHandleValue fun) { - std::string code = jsSkipWhiteSpace(raw); - if (!hasFunctionIdentifier(code)) { - if (code.find('\n') == std::string::npos && !hasJSReturn(code) && - (code.find(';') == std::string::npos || code.find(';') == code.size() - 1)) { - code = "return " + code; - } - code = "function(){ " + code + "}"; - } - - code = str::stream() << "_funcs" << functionNumber << " = " << code; + std::string code = str::stream() << "_funcs" << functionNumber << " = " + << parseJSFunctionOrExpression(_context, StringData(raw)); JS::CompileOptions co(_context); setCompileOptions(&co); diff --git a/src/mongo/scripting/mozjs/implscope.h b/src/mongo/scripting/mozjs/implscope.h index 6bd0c6c7411..30106006d43 100644 --- a/src/mongo/scripting/mozjs/implscope.h +++ b/src/mongo/scripting/mozjs/implscope.h @@ -46,6 +46,7 @@ #include "mongo/scripting/mozjs/maxkey.h" #include "mongo/scripting/mozjs/minkey.h" #include "mongo/scripting/mozjs/mongo.h" +#include "mongo/scripting/mozjs/mongohelpers.h" #include "mongo/scripting/mozjs/nativefunction.h" #include "mongo/scripting/mozjs/numberint.h" #include "mongo/scripting/mozjs/numberlong.h" @@ -194,6 +195,10 @@ public: return _mongoExternalProto; } + WrapType<MongoHelpersInfo>& getMongoHelpersProto() { + return _mongoHelpersProto; + } + WrapType<MongoLocalInfo>& getMongoLocalProto() { return _mongoLocalProto; } @@ -310,6 +315,7 @@ private: WrapType<MaxKeyInfo> _maxKeyProto; WrapType<MinKeyInfo> _minKeyProto; WrapType<MongoExternalInfo> _mongoExternalProto; + WrapType<MongoHelpersInfo> _mongoHelpersProto; WrapType<MongoLocalInfo> _mongoLocalProto; WrapType<NativeFunctionInfo> _nativeFunctionProto; WrapType<NumberIntInfo> _numberIntProto; diff --git a/src/mongo/scripting/mozjs/mongohelpers.cpp b/src/mongo/scripting/mozjs/mongohelpers.cpp new file mode 100644 index 00000000000..b27163ce7a3 --- /dev/null +++ b/src/mongo/scripting/mozjs/mongohelpers.cpp @@ -0,0 +1,94 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/mongohelpers.h" + +#include <jsapi.h> + +#include "mongo/scripting/engine.h" +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/objectwrapper.h" +#include "mongo/scripting/mozjs/valuereader.h" +#include "mongo/scripting/mozjs/valuewriter.h" + +namespace mongo { +namespace JSFiles { +extern const JSFile mongohelpers; +} // namespace JSFiles + +namespace mozjs { + +const char* const MongoHelpersInfo::className = "MongoHelpers"; + +namespace { +const char kExportsObjectName[] = "exportToMongoHelpers"; +const char kReflectName[] = "Reflect"; +} // namespace + +std::string parseJSFunctionOrExpression(JSContext* cx, const StringData input) { + JS::RootedValue jsStrOut(cx); + JS::RootedValue jsStrIn(cx); + + ValueReader(cx, &jsStrIn).fromStringData(input); + ObjectWrapper helpersWrapper(cx, getScope(cx)->getMongoHelpersProto().getProto()); + + helpersWrapper.callMethod("functionExpressionParser", JS::HandleValueArray(jsStrIn), &jsStrOut); + + return ValueWriter(cx, jsStrOut).toString(); +} + +void MongoHelpersInfo::postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto) { + ObjectWrapper protoWrapper(cx, proto); + ObjectWrapper globalWrapper(cx, global); + + // Initialize the reflection API and move it under the MongoHelpers object + uassert(ErrorCodes::JSInterpreterFailure, + "Error initializing javascript reflection API", + JS_InitReflect(cx, global)); + JS::RootedValue reflectValue(cx); + globalWrapper.getValue(kReflectName, &reflectValue); + globalWrapper.deleteProperty(kReflectName); + protoWrapper.setValue(kReflectName, reflectValue); + + JS::RootedValue exports(cx); + getScope(cx)->execSetup(JSFiles::mongohelpers); + globalWrapper.getValue(kExportsObjectName, &exports); + globalWrapper.deleteProperty(kExportsObjectName); + + ObjectWrapper exportsWrapper(cx, exports); + JS::RootedValue copyExport(cx); + exportsWrapper.enumerate([&](JS::HandleId _id) { + exportsWrapper.getValue(_id, ©Export); + protoWrapper.setValue(_id, copyExport); + }); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/mongohelpers.h b/src/mongo/scripting/mozjs/mongohelpers.h new file mode 100644 index 00000000000..75a90117d70 --- /dev/null +++ b/src/mongo/scripting/mozjs/mongohelpers.h @@ -0,0 +1,53 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * The "MongoHelpers" Javascript object. + * + * The MongoHelpers object is a special hidden object to attach internal-use + * javascript code to the global object so that we can do things like access the + * javascript parser through SpiderMonkey's reflection API, and parse function + * bodies/expressions from the server code. + */ +struct MongoHelpersInfo : public BaseInfo { + static void postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto); + static const InstallType installType = InstallType::Private; + static const char* const className; +}; + +std::string parseJSFunctionOrExpression(JSContext* cx, StringData input); + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/mongohelpers.js b/src/mongo/scripting/mozjs/mongohelpers.js new file mode 100644 index 00000000000..7864d417d62 --- /dev/null +++ b/src/mongo/scripting/mozjs/mongohelpers.js @@ -0,0 +1,68 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +// This file has JavaScript functions that should be attached to the MongoHelpers object + +// The contents of exportToMongoHelpers will be copied into the MongoHelpers object and +// this dictionary will be removed from the global scope. +exportToMongoHelpers = { + // This function accepts an expression or function body and returns a function definition + 'functionExpressionParser': function functionExpressionParser(fnSrc) { + var parseTree; + try { + parseTree = this.Reflect.parse(fnSrc); + } catch(e) { + if (e == 'SyntaxError: function statement requires a name') { + return fnSrc; + } else if (e == 'SyntaxError: return not in function') { + return 'function() { ' + fnSrc + ' }'; + } else { + throw(e); + } + } + // Input source is a series of expressions. we should prepend the last one with return + var lastStatement = parseTree.body.length - 1; + var lastStatementType = parseTree.body[lastStatement].type; + if (lastStatementType == 'ExpressionStatement') { + var lines = fnSrc.split('\n'); + var loc = parseTree.body[lastStatement].loc.start; + var mod_line = lines[loc.line - 1]; + mod_line = mod_line.substr(0, loc.column) + 'return ' + mod_line.substr(loc.column); + lines[loc.line - 1] = mod_line; + fnSrc = 'function() { ' + lines.join('\n') + ' }' + return fnSrc; + } else if(lastStatementType == 'FunctionDeclaration') { + return fnSrc; + } else { + return 'function() { ' + fnSrc + ' }'; + } + } +} + +// WARNING: Anything outside the exportToMongoHelpers dictionary will be available in the +// global scope and visible to users! |