summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndreas Stöckel <astoecke@techfak.uni-bielefeld.de>2014-10-26 01:17:02 +0000
committerandreas <andreas@daaaf23c-2e50-4459-9457-1e69db5a47bf>2014-10-26 01:17:02 +0000
commiteb7211bb8d199b60d1e15cc84e16e08f1e8c0f23 (patch)
tree41b0483045ad266ba41ad54f64b0c36f8b10522d
parent250d6a4dbe61b6798cd090abeabdc0ece8237dd3 (diff)
Fully implemented reading values from MozJs (except for all bugs that are still in it); added generic unit test for java script engine implementations; added suppressions for valgrind (as the mozjs library produced some leaks that can safely be ignored); added a bunch of TODOs
git-svn-id: file:///var/local/svn/basicwriter@79 daaaf23c-2e50-4459-9457-1e69db5a47bf
-rw-r--r--CMakeLists.txt1
-rw-r--r--src/core/script/ScriptEngine.hpp8
-rw-r--r--src/core/script/Variant.hpp21
-rw-r--r--src/plugins/mozjs/MozJsScriptEngine.cpp97
-rw-r--r--src/plugins/mozjs/MozJsScriptEngine.hpp54
-rw-r--r--test/plugins/genericjs/GenericJsScriptEngineTest.hpp134
-rw-r--r--test/plugins/mozjs/MozJsScriptEngineTest.cpp39
-rw-r--r--valgrind_suppressions.supp8
8 files changed, 343 insertions, 19 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2f8da7a..60fb273 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -110,6 +110,7 @@ ADD_LIBRARY(ousia_plugin_mozjs
TARGET_LINK_LIBRARIES(ousia_plugin_mozjs
ousia_core
+ ${MOZJS_LIBRARIES}
)
# If testing is enabled, build the unit tests
diff --git a/src/core/script/ScriptEngine.hpp b/src/core/script/ScriptEngine.hpp
index 2341beb..6c048aa 100644
--- a/src/core/script/ScriptEngine.hpp
+++ b/src/core/script/ScriptEngine.hpp
@@ -27,6 +27,14 @@
#include "Variant.hpp"
+// TODO: Provide more Exception classes than ScriptEngineException -- one for
+// internal errors, one for script errors
+
+// TODO: Allow reporting multiple exceptions (e.g. to report all syntax errors
+// at once)
+
+// TODO: Add API that allow pre-compilation of scripts
+
namespace ousia {
namespace script {
diff --git a/src/core/script/Variant.hpp b/src/core/script/Variant.hpp
index 465793f..8cb0e8f 100644
--- a/src/core/script/Variant.hpp
+++ b/src/core/script/Variant.hpp
@@ -26,6 +26,12 @@
#include <string>
#include <vector>
+// TODO: Replace VariantType::number with VariantType::double
+// TODO: Convert VariantType::integer to int32_t
+// TODO: Use std::unique_ptr for *Function
+// TODO: Move semantic in complex constructors
+// TODO: Delete default constructors/assignment operators in pretty much everything
+
namespace ousia {
namespace script {
@@ -124,6 +130,9 @@ private:
};
public:
+
+ using Int = int32_t;
+
/**
* Copy constructor of the Variant class.
*
@@ -182,7 +191,8 @@ public:
Variant(const std::vector<Variant> &a);
/**
- * Constructor for map values. The given map is copied and managed by the new
+ * Constructor for map values. The given map is copied and managed by the
+ *new
* Variant instance.
*
* @param m is a reference to the map.
@@ -190,7 +200,8 @@ public:
Variant(const std::map<std::string, Variant> &m);
/**
- * Constructor for function values. The given pointer to the function object is cloned and managed by the new Variant instance.
+ * Constructor for function values. The given pointer to the function object
+ *is cloned and managed by the new Variant instance.
*
* @param f is a reference to the function.
*/
@@ -224,10 +235,7 @@ public:
*
* @return the current type of the Variant.
*/
- VariantType getType() const
- {
- return type;
- }
+ VariantType getType() const { return type; }
bool getBooleanValue() const;
int64_t getIntegerValue() const;
@@ -253,7 +261,6 @@ public:
*/
friend std::ostream &operator<<(std::ostream &os, const Variant &v);
};
-
}
}
diff --git a/src/plugins/mozjs/MozJsScriptEngine.cpp b/src/plugins/mozjs/MozJsScriptEngine.cpp
index b7ff002..e3f72b1 100644
--- a/src/plugins/mozjs/MozJsScriptEngine.cpp
+++ b/src/plugins/mozjs/MozJsScriptEngine.cpp
@@ -16,6 +16,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <jsapi.h>
+
#include "MozJsScriptEngine.hpp"
namespace ousia {
@@ -39,6 +41,36 @@ namespace script {
static const uint32_t MOZJS_RT_MEMSIZE = 64L * 1024L * 1024L;
static const uint32_t MOZJS_CTX_STACK_CHUNK_SIZE = 8192;
+/* Class MozJsScriptEngineFunction */
+
+MozJsScriptEngineFunction::MozJsScriptEngineFunction(
+ MozJsScriptEngineScope &scope, JS::Value &fun, JSObject *parent)
+ : scope(scope)
+{
+ this->fun = new JS::RootedValue(scope.cx, fun);
+ this->parent = new JS::RootedObject(scope.cx, parent);
+}
+
+MozJsScriptEngineFunction::~MozJsScriptEngineFunction()
+{
+ delete parent;
+ delete fun;
+}
+
+MozJsScriptEngineFunction *MozJsScriptEngineFunction::clone() const
+{
+ return new MozJsScriptEngineFunction(scope, fun->get(), parent->get());
+}
+
+Variant MozJsScriptEngineFunction::call(const std::vector<Variant> &args) const
+{
+ // TODO: Input parameter
+ JS::Value val;
+ scope.handleErr(JS_CallFunctionValue(scope.cx, parent->get(), fun->get(), 0,
+ nullptr, &val));
+ return scope.valueToVariant(val);
+}
+
/* Class MozJsScriptEngineScope */
/**
@@ -94,8 +126,51 @@ MozJsScriptEngineScope::~MozJsScriptEngineScope()
JS_DestroyContext(cx);
}
-Variant MozJsScriptEngineScope::toVariant(const JS::Value &val)
+Variant MozJsScriptEngineScope::arrayToVariant(JSObject *obj)
{
+ // Retrieve the array length
+ uint32_t len = 0;
+ handleErr(JS_GetArrayLength(cx, obj, &len));
+
+ // Create the result vector and reserve as much memory as needed
+ std::vector<Variant> array;
+ array.reserve(len);
+
+ // Fill the result vector
+ JS::Value arrayVal;
+ for (uint32_t i = 0; i < len; i++) {
+ handleErr(JS_GetElement(cx, obj, i, &arrayVal));
+ array.push_back(valueToVariant(arrayVal, obj));
+ }
+ return Variant{array};
+}
+
+Variant MozJsScriptEngineScope::objectToVariant(JSObject *obj)
+{
+ // Enumerate all object properties, perform error handling
+ JS::AutoIdArray ids(cx, JS_Enumerate(cx, obj));
+ if (!ids) {
+ handleErr();
+ }
+
+ // Iterate over all ids, add them to a map
+ std::map<std::string, Variant> map;
+ JS::Value key;
+ JS::Value val;
+ for (size_t i = 0; i < ids.length(); i++) {
+ handleErr(JS_IdToValue(cx, ids[i], &key));
+ handleErr(JS_GetPropertyById(cx, obj, ids[i], &val));
+ map.insert(std::make_pair<std::string, Variant>(
+ toString(key), valueToVariant(val, obj)));
+ }
+ return Variant{map};
+}
+
+Variant MozJsScriptEngineScope::valueToVariant(JS::Value &val, JSObject *parent)
+{
+ if (val.isNull()) {
+ return Variant::Null;
+ }
if (val.isBoolean()) {
return Variant{val.toBoolean()};
}
@@ -109,6 +184,22 @@ Variant MozJsScriptEngineScope::toVariant(const JS::Value &val)
// TODO: Remove the need for using "c_str"!
return Variant{toString(val.toString()).c_str()};
}
+ if (val.isObject()) {
+ JSObject &obj = val.toObject();
+
+ if (JS_IsArrayObject(cx, &obj)) {
+ return arrayToVariant(&obj);
+ }
+
+ if (JS_ObjectIsFunction(cx, &obj)) {
+ // TODO: Variant of the Variant function constructor which grants
+ // ownership of the pointer
+ MozJsScriptEngineFunction fun(*this, val, parent);
+ return Variant{&fun};
+ }
+
+ return objectToVariant(&obj);
+ }
return Variant::Null;
}
@@ -143,7 +234,7 @@ void MozJsScriptEngineScope::handleErr(bool ok)
}
}
-std::string MozJsScriptEngineScope::toString(const JS::Value &val)
+std::string MozJsScriptEngineScope::toString(JS::Value &val)
{
// If the given value already is a Javascript string, return it directly.
if (val.isString()) {
@@ -178,7 +269,7 @@ Variant MozJsScriptEngineScope::doRun(const std::string &code)
JS::Value rval;
handleErr(JS_EvaluateScript(cx, *global, code.c_str(), code.length(), "", 0,
&rval));
- return toVariant(rval);
+ return valueToVariant(rval);
}
void MozJsScriptEngineScope::doSetVariable(const std::string &name,
diff --git a/src/plugins/mozjs/MozJsScriptEngine.hpp b/src/plugins/mozjs/MozJsScriptEngine.hpp
index 68eee46..f98c871 100644
--- a/src/plugins/mozjs/MozJsScriptEngine.hpp
+++ b/src/plugins/mozjs/MozJsScriptEngine.hpp
@@ -19,26 +19,66 @@
#ifndef _MOZ_JS_SCRIPT_ENGINE_HPP_
#define _MOZ_JS_SCRIPT_ENGINE_HPP_
-#include <jsapi.h>
-
#include <core/script/ScriptEngine.hpp>
+#include <core/script/Function.hpp>
+#include <core/script/Object.hpp>
+
+/* Forward declarations from header jsapi.h */
+
+class JSRuntime;
+class JSContext;
+class JSCompartment;
+class JSString;
+class JSObject;
+
+namespace JS {
+class Value;
+template <typename T>
+class Rooted;
+typedef Rooted<JSObject *> RootedObject;
+typedef Rooted<Value> RootedValue;
+}
namespace ousia {
namespace script {
+class MozJsScriptEngineScope;
+
+class MozJsScriptEngineFunction : public Function {
+private:
+ MozJsScriptEngineScope &scope;
+ JS::RootedValue *fun;
+ JS::RootedObject *parent;
+
+public:
+ MozJsScriptEngineFunction(MozJsScriptEngineScope &scope, JS::Value &fun, JSObject *parent);
+
+ ~MozJsScriptEngineFunction();
+
+ MozJsScriptEngineFunction *clone() const override;
+
+ Variant call(const std::vector<Variant> &args) const override;
+};
+
class MozJsScriptEngineScope : public ScriptEngineScope {
+friend MozJsScriptEngineFunction;
+
private:
JSRuntime *rt;
JSContext *cx;
JSCompartment *oldCompartment;
JS::RootedObject *global;
- void handleErr(bool ok);
+ void handleErr(bool ok = false);
+
+ Variant arrayToVariant(JSObject *obj);
+
+ Variant objectToVariant(JSObject *obj);
- Variant toVariant(const JS::Value &val);
+ Variant valueToVariant(JS::Value &val, JSObject *parent = nullptr);
- std::string toString(const JS::Value &val);
+ std::string toString(JS::Value &val);
std::string toString(JSString *str);
@@ -52,22 +92,18 @@ public:
MozJsScriptEngineScope(JSRuntime *rt);
~MozJsScriptEngineScope() override;
-
};
class MozJsScriptEngine : public ScriptEngine {
-
private:
JSRuntime *rt;
public:
-
MozJsScriptEngine();
~MozJsScriptEngine();
MozJsScriptEngineScope *createScope() override;
-
};
}
}
diff --git a/test/plugins/genericjs/GenericJsScriptEngineTest.hpp b/test/plugins/genericjs/GenericJsScriptEngineTest.hpp
new file mode 100644
index 0000000..8e3ffea
--- /dev/null
+++ b/test/plugins/genericjs/GenericJsScriptEngineTest.hpp
@@ -0,0 +1,134 @@
+/*
+ Ousía
+ Copyright (C) 2014, 2015 Benjamin Paaßen, Andreas Stöckel
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * This file contains generic tests for JavaScript script engines. JavaScript
+ * script engine bindings should have their own test project in which the
+ * GENERIC_JS_TEST_NAME, GENERIC_JS_TEST_SCOPE macros are defined and this
+ * file is included.
+ *
+ * TODO: The macros are more of a dirty hack -- probably use gtest fixtures and
+ * templates here.
+ */
+
+#ifndef _GENERIC_JS_SCRIPT_ENGINE_TEST_HPP_
+#define _GENERIC_JS_SCRIPT_ENGINE_TEST_HPP_
+
+#ifndef GENERIC_JS_TEST_NAME
+#error "Macro GENERIC_JS_TEST_NAME is not set!"
+#endif /* GENERIC_JS_TEST_NAME */
+
+#ifndef GENERIC_JS_TEST_SCOPE
+#error "Macro GENERIC_JS_TEST_SCOPE is not set!"
+#endif /* GENERIC_JS_TEST_SCOPE */
+
+TEST(GENERIC_JS_TEST_NAME, returnNull)
+{
+ GENERIC_JS_TEST_SCOPE
+ Variant res = scope->run("null;");
+ ASSERT_EQ(VariantType::null, res.getType());
+}
+
+TEST(GENERIC_JS_TEST_NAME, returnBoolean)
+{
+ GENERIC_JS_TEST_SCOPE
+ Variant res = scope->run("true;");
+ ASSERT_EQ(VariantType::boolean, res.getType());
+ ASSERT_TRUE(res.getBooleanValue());
+}
+
+TEST(GENERIC_JS_TEST_NAME, returnInteger)
+{
+ GENERIC_JS_TEST_SCOPE
+ Variant res = scope->run("42;");
+ ASSERT_EQ(VariantType::integer, res.getType());
+ ASSERT_EQ(42, res.getIntegerValue());
+}
+
+TEST(GENERIC_JS_TEST_NAME, returnNumber)
+{
+ GENERIC_JS_TEST_SCOPE
+ Variant res = scope->run("42.5;");
+ ASSERT_EQ(VariantType::number, res.getType());
+ ASSERT_EQ(42.5, res.getNumberValue());
+}
+
+TEST(GENERIC_JS_TEST_NAME, returnString)
+{
+ GENERIC_JS_TEST_SCOPE
+ Variant res = scope->run("\"Hello World\";");
+ ASSERT_EQ(VariantType::string, res.getType());
+ ASSERT_EQ("Hello World", res.getStringValue());
+}
+
+TEST(GENERIC_JS_TEST_NAME, returnArray)
+{
+ GENERIC_JS_TEST_SCOPE
+ Variant res = scope->run("[42, \"Hello World\", false];");
+ ASSERT_EQ(VariantType::array, res.getType());
+
+ std::vector<Variant> a = res.getArrayValue();
+ ASSERT_EQ(3, a.size());
+
+ ASSERT_EQ(VariantType::integer, a[0].getType());
+ ASSERT_EQ(42, a[0].getIntegerValue());
+
+ ASSERT_EQ(VariantType::string, a[1].getType());
+ ASSERT_EQ("Hello World", a[1].getStringValue());
+
+ ASSERT_EQ(VariantType::boolean, a[2].getType());
+ ASSERT_FALSE(a[2].getBooleanValue());
+}
+
+TEST(GENERIC_JS_TEST_NAME, returnObject)
+{
+ GENERIC_JS_TEST_SCOPE
+ Variant res = scope->run(
+ "var obj = {\"key1\": 42, \"key2\": \"Hello World\", \"key3\": false}; obj");
+ ASSERT_EQ(VariantType::map, res.getType());
+
+ std::map<std::string, Variant> m = res.getMapValue();
+ ASSERT_EQ(3, m.size());
+
+ ASSERT_TRUE(m.find("key1") != m.end());
+ ASSERT_TRUE(m.find("key2") != m.end());
+ ASSERT_TRUE(m.find("key3") != m.end());
+
+ ASSERT_EQ(VariantType::integer, m["key1"].getType());
+ ASSERT_EQ(42, m["key1"].getIntegerValue());
+
+ ASSERT_EQ(VariantType::string, m["key2"].getType());
+ ASSERT_EQ("Hello World", m["key2"].getStringValue());
+
+ ASSERT_EQ(VariantType::boolean, m["key3"].getType());
+ ASSERT_FALSE(m["key3"].getBooleanValue());
+}
+
+TEST(GENERIC_JS_TEST_NAME, returnFunction)
+{
+ GENERIC_JS_TEST_SCOPE
+ Variant res = scope->run("(function () {return \"Hello World\";})");
+ ASSERT_EQ(VariantType::function, res.getType());
+
+ Variant fres = res.getFunctionValue()->call();
+ ASSERT_EQ(VariantType::string, fres.getType());
+ ASSERT_EQ("Hello World", fres.getStringValue());
+}
+
+#endif /* _GENERIC_JS_SCRIPT_ENGINE_TEST_HPP_ */
+
diff --git a/test/plugins/mozjs/MozJsScriptEngineTest.cpp b/test/plugins/mozjs/MozJsScriptEngineTest.cpp
new file mode 100644
index 0000000..f8cba95
--- /dev/null
+++ b/test/plugins/mozjs/MozJsScriptEngineTest.cpp
@@ -0,0 +1,39 @@
+/*
+ Ousía
+ Copyright (C) 2014, 2015 Benjamin Paaßen, Andreas Stöckel
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <memory>
+
+#include <gtest/gtest.h>
+
+#include <plugins/mozjs/MozJsScriptEngine.hpp>
+
+namespace ousia {
+namespace script {
+
+/* Global engine object */
+MozJsScriptEngine engine;
+auto scope = std::unique_ptr<MozJsScriptEngineScope>{engine.createScope()};
+
+#define GENERIC_JS_TEST_NAME MozJsScriptEngine
+#define GENERIC_JS_TEST_SCOPE
+
+#include "../genericjs/GenericJsScriptEngineTest.hpp"
+
+}
+}
+
diff --git a/valgrind_suppressions.supp b/valgrind_suppressions.supp
new file mode 100644
index 0000000..dbecc46
--- /dev/null
+++ b/valgrind_suppressions.supp
@@ -0,0 +1,8 @@
+{
+ libnspr4/PR_NewLock
+ Memcheck:Leak
+ match-leak-kinds: reachable
+ ...
+ obj:/usr/lib64/libnspr4.so
+ ...
+}