summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan Reams <jbreams@mongodb.com>2015-07-29 23:47:50 -0400
committerJonathan Reams <jbreams@mongodb.com>2015-08-12 14:13:47 -0400
commit2bef8a3292a00090b8b62e27119481e5b21808cb (patch)
treeba02d598f735d5a0fdfecbc124ad20a58d464f0d
parentd2fe88e34a93fb6d403307bd6b890d4f45d6935a (diff)
downloadmongo-2bef8a3292a00090b8b62e27119481e5b21808cb.tar.gz
SERVER-16703 Improve parsing of JS function bodies and expressions
-rw-r--r--src/mongo/dbtests/jstests.cpp9
-rw-r--r--src/mongo/scripting/SConscript9
-rw-r--r--src/mongo/scripting/mozjs/implscope.cpp17
-rw-r--r--src/mongo/scripting/mozjs/implscope.h6
-rw-r--r--src/mongo/scripting/mozjs/mongohelpers.cpp94
-rw-r--r--src/mongo/scripting/mozjs/mongohelpers.h53
-rw-r--r--src/mongo/scripting/mozjs/mongohelpers.js68
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, &copyExport);
+ 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!