Index: ps/trunk/libraries/source/spidermonkey/FixVersionDetectionConfigure.diff =================================================================== --- ps/trunk/libraries/source/spidermonkey/FixVersionDetectionConfigure.diff (revision 22626) +++ ps/trunk/libraries/source/spidermonkey/FixVersionDetectionConfigure.diff (nonexistent) @@ -1,178 +0,0 @@ -Created from the patched (FixVersionDetection.diff) configure.in, ignoring -unrelated hunks (thus invalidating some hard-coded line numbers). This way -we do not add a dependency on autoconf-2.13. - -diff --git a/js/src/configure b/js/src/configure ---- a/js/src/configure -+++ b/js/src/configure -@@ -1662,70 +1662,6 @@ esac - - fi - --MOZILLA_VERSION=`$PYTHON $srcdir/python/mozbuild/mozbuild/milestone.py --topsrcdir $srcdir` --MOZILLA_UAVERSION=`$PYTHON $srcdir/python/mozbuild/mozbuild/milestone.py --topsrcdir $srcdir --uaversion` --MOZILLA_SYMBOLVERSION=`$PYTHON $srcdir/python/mozbuild/mozbuild/milestone.py --topsrcdir $srcdir --symbolversion` -- --cat >> confdefs.pytmp <> confdefs.h <> confdefs.pytmp <> confdefs.h <> confdefs.pytmp <> confdefs.h <> confdefs.pytmp <> confdefs.h <> confdefs.pytmp <> confdefs.h <> confdefs.pytmp <> confdefs.h <> confdefs.pytmp <> confdefs.h <> confdefs.pytmp <> confdefs.h <> confdefs.pytmp <> confdefs.h <> confdefs.pytmp <> confdefs.h <> $CONFIG_STATUS <> $CONFIG_STATUS < -# Date 1426889983 25200 -# Node ID 4f8bbef857155fbee1d064e014b22dd72512b389 -# Parent 6f42f8ee82468d18acd65e0c2b5bf6c040696224 -Bug 1145882 - Part 1/2 - Only use $PYTHON after defined by MOZ_PYTHON. r=glandium - -diff --git a/js/src/configure.in b/js/src/configure.in ---- a/js/src/configure.in -+++ b/js/src/configure.in -@@ -228,61 +228,16 @@ if test -n "$gonkdir" ; then - fi - - AC_DEFINE(ANDROID) - AC_DEFINE(GONK) - else - MOZ_ANDROID_NDK - fi - --dnl ============================================================== --dnl Get mozilla version from central milestone file --dnl ============================================================== --MOZILLA_VERSION=`$PYTHON $srcdir/python/mozbuild/mozbuild/milestone.py --topsrcdir $srcdir` --MOZILLA_UAVERSION=`$PYTHON $srcdir/python/mozbuild/mozbuild/milestone.py --topsrcdir $srcdir --uaversion` --MOZILLA_SYMBOLVERSION=`$PYTHON $srcdir/python/mozbuild/mozbuild/milestone.py --topsrcdir $srcdir --symbolversion` -- --AC_DEFINE_UNQUOTED(MOZILLA_VERSION,"$MOZILLA_VERSION") --AC_DEFINE_UNQUOTED(MOZILLA_VERSION_U,$MOZILLA_VERSION) --AC_DEFINE_UNQUOTED(MOZILLA_UAVERSION,"$MOZILLA_UAVERSION") --AC_SUBST(MOZILLA_SYMBOLVERSION) -- --# Separate version into components for use in shared object naming etc --changequote(,) --MOZJS_MAJOR_VERSION=`echo $MOZILLA_VERSION | sed "s|\(^[0-9]*\)\.[0-9]*.*|\1|"` --MOZJS_MINOR_VERSION=`echo $MOZILLA_VERSION | sed "s|^[0-9]*\.\([0-9]*\).*|\1|"` --MOZJS_PATCH_VERSION=`echo $MOZILLA_VERSION | sed "s|^[0-9]*\.[0-9]*[^0-9]*||"` --IS_ALPHA=`echo $MOZILLA_VERSION | grep '[ab]'` -- --dnl XXX in a temporary bid to avoid developer anger at renaming files --dnl XXX before "js" symlinks exist, don't change names. --dnl --dnl if test -n "$JS_STANDALONE"; then --dnl JS_SHELL_NAME=js$MOZJS_MAJOR_VERSION --dnl JS_CONFIG_NAME=js$MOZJS_MAJOR_VERSION-config --dnl else --JS_SHELL_NAME=js --JS_CONFIG_NAME=js-config --dnl fi -- --changequote([,]) --if test -n "$IS_ALPHA"; then -- changequote(,) -- MOZJS_ALPHA=`echo $MOZILLA_VERSION | sed "s|^[0-9]*\.[0-9\.]*\([^0-9]\).*|\1|"` -- changequote([,]) --fi --AC_DEFINE_UNQUOTED(MOZJS_MAJOR_VERSION,$MOZJS_MAJOR_VERSION) --AC_DEFINE_UNQUOTED(MOZJS_MINOR_VERSION,$MOZJS_MINOR_VERSION) --AC_SUBST(JS_SHELL_NAME) --AC_SUBST(JS_CONFIG_NAME) --AC_SUBST(MOZJS_MAJOR_VERSION) --AC_SUBST(MOZJS_MINOR_VERSION) --AC_SUBST(MOZJS_PATCH_VERSION) --AC_SUBST(MOZJS_ALPHA) -- - dnl ======================================================== - dnl Checks for compilers. - dnl ======================================================== - - dnl AR_FLAGS set here so HOST_AR_FLAGS can be set correctly (see bug 538269) - AR_FLAGS='crs $@' - - if test "$COMPILE_ENVIRONMENT"; then -@@ -733,16 +688,62 @@ fi - if test "$COMPILE_ENVIRONMENT"; then - - AC_PATH_XTRA - - XCFLAGS="$X_CFLAGS" - - fi # COMPILE_ENVIRONMENT - -+dnl ============================================================== -+dnl Get mozilla version from central milestone file -+dnl ============================================================== -+MOZILLA_VERSION=`$PYTHON $srcdir/python/mozbuild/mozbuild/milestone.py --topsrcdir $srcdir` -+MOZILLA_UAVERSION=`$PYTHON $srcdir/python/mozbuild/mozbuild/milestone.py --topsrcdir $srcdir --uaversion` -+MOZILLA_SYMBOLVERSION=`$PYTHON $srcdir/python/mozbuild/mozbuild/milestone.py --topsrcdir $srcdir --symbolversion` -+ -+AC_DEFINE_UNQUOTED(MOZILLA_VERSION,"$MOZILLA_VERSION") -+AC_DEFINE_UNQUOTED(MOZILLA_VERSION_U,$MOZILLA_VERSION) -+AC_DEFINE_UNQUOTED(MOZILLA_UAVERSION,"$MOZILLA_UAVERSION") -+AC_SUBST(MOZILLA_SYMBOLVERSION) -+ -+# Separate version into components for use in shared object naming etc -+changequote(,) -+MOZJS_MAJOR_VERSION=`echo $MOZILLA_VERSION | sed "s|\(^[0-9]*\)\.[0-9]*.*|\1|"` -+MOZJS_MINOR_VERSION=`echo $MOZILLA_VERSION | sed "s|^[0-9]*\.\([0-9]*\).*|\1|"` -+MOZJS_PATCH_VERSION=`echo $MOZILLA_VERSION | sed "s|^[0-9]*\.[0-9]*[^0-9]*||"` -+IS_ALPHA=`echo $MOZILLA_VERSION | grep '[ab]'` -+ -+dnl XXX in a temporary bid to avoid developer anger at renaming files -+dnl XXX before "js" symlinks exist, don't change names. -+dnl -+dnl if test -n "$JS_STANDALONE"; then -+dnl JS_SHELL_NAME=js$MOZJS_MAJOR_VERSION -+dnl JS_CONFIG_NAME=js$MOZJS_MAJOR_VERSION-config -+dnl else -+JS_SHELL_NAME=js -+JS_CONFIG_NAME=js-config -+dnl fi -+ -+changequote([,]) -+if test -n "$IS_ALPHA"; then -+ changequote(,) -+ MOZJS_ALPHA=`echo $MOZILLA_VERSION | sed "s|^[0-9]*\.[0-9\.]*\([^0-9]\).*|\1|"` -+ changequote([,]) -+fi -+AC_DEFINE_UNQUOTED(MOZJS_MAJOR_VERSION,$MOZJS_MAJOR_VERSION) -+AC_DEFINE_UNQUOTED(MOZJS_MINOR_VERSION,$MOZJS_MINOR_VERSION) -+AC_SUBST(JS_SHELL_NAME) -+AC_SUBST(JS_CONFIG_NAME) -+AC_SUBST(MOZJS_MAJOR_VERSION) -+AC_SUBST(MOZJS_MINOR_VERSION) -+AC_SUBST(MOZJS_PATCH_VERSION) -+AC_SUBST(MOZJS_ALPHA) -+ -+ - dnl ======================================================== - dnl set the defaults first - dnl ======================================================== - AS_BIN=$AS - AR_LIST='$(AR) t' - AR_EXTRACT='$(AR) x' - AR_DELETE='$(AR) d' - AS='$(CC)' - Index: ps/trunk/source/simulation2/scripting/EngineScriptConversions.cpp =================================================================== --- ps/trunk/source/simulation2/scripting/EngineScriptConversions.cpp (revision 22626) +++ ps/trunk/source/simulation2/scripting/EngineScriptConversions.cpp (revision 22627) @@ -1,366 +1,369 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "scriptinterface/ScriptConversions.h" #include "graphics/Color.h" #include "maths/Fixed.h" #include "maths/FixedVector2D.h" #include "maths/FixedVector3D.h" #include "ps/CLogger.h" #include "ps/Shapes.h" #include "ps/utf16string.h" #include "simulation2/helpers/CinemaPath.h" #include "simulation2/helpers/Grid.h" #include "simulation2/system/IComponent.h" #include "simulation2/system/ParamNode.h" #define FAIL(msg) STMT(JS_ReportError(cx, msg); return false) #define FAIL_VOID(msg) STMT(JS_ReportError(cx, msg); return) template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, IComponent* const& val) { JSAutoRequest rq(cx); if (val == NULL) { ret.setNull(); return; } // If this is a scripted component, just return the JS object directly JS::RootedValue instance(cx, val->GetJSInstance()); if (!instance.isNull()) { ret.set(instance); return; } // Otherwise we need to construct a wrapper object // (TODO: cache wrapper objects?) JS::RootedObject obj(cx); if (!val->NewJSObject(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface, &obj)) { // Report as an error, since scripts really shouldn't try to use unscriptable interfaces LOGERROR("IComponent does not have a scriptable interface"); ret.setUndefined(); return; } JS_SetPrivate(obj, static_cast(val)); ret.setObject(*obj); } template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, CParamNode const& val) { JSAutoRequest rq(cx); val.ToJSVal(cx, true, ret); // Prevent modifications to the object, so that it's safe to share between // components and to reconstruct on deserialization if (ret.isObject()) { JS::RootedObject obj(cx, &ret.toObject()); JS_DeepFreezeObject(cx, obj); } } template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CParamNode* const& val) { if (val) ToJSVal(cx, ret, *val); else ret.setUndefined(); } template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CColor& out) { if (!v.isObject()) FAIL("JS::HandleValue not an object"); JSAutoRequest rq(cx); JS::RootedObject obj(cx, &v.toObject()); JS::RootedValue r(cx); JS::RootedValue g(cx); JS::RootedValue b(cx); JS::RootedValue a(cx); if (!JS_GetProperty(cx, obj, "r", &r) || !FromJSVal(cx, r, out.r)) FAIL("Failed to get property CColor.r"); if (!JS_GetProperty(cx, obj, "g", &g) || !FromJSVal(cx, g, out.g)) FAIL("Failed to get property CColor.g"); if (!JS_GetProperty(cx, obj, "b", &b) || !FromJSVal(cx, b, out.b)) FAIL("Failed to get property CColor.b"); if (!JS_GetProperty(cx, obj, "a", &a) || !FromJSVal(cx, a, out.a)) FAIL("Failed to get property CColor.a"); return true; } template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, CColor const& val) { JSAutoRequest rq(cx); JS::RootedObject obj(cx, JS_NewPlainObject(cx)); if (!obj) { ret.setUndefined(); return; } JS::RootedValue r(cx); JS::RootedValue g(cx); JS::RootedValue b(cx); JS::RootedValue a(cx); ToJSVal(cx, &r, val.r); ToJSVal(cx, &g, val.g); ToJSVal(cx, &b, val.b); ToJSVal(cx, &a, val.a); JS_SetProperty(cx, obj, "r", r); JS_SetProperty(cx, obj, "g", g); JS_SetProperty(cx, obj, "b", b); JS_SetProperty(cx, obj, "a", a); ret.setObject(*obj); } template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, fixed& out) { JSAutoRequest rq(cx); double ret; if (!JS::ToNumber(cx, v, &ret)) return false; out = fixed::FromDouble(ret); // double can precisely represent the full range of fixed, so this is a non-lossy conversion return true; } template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::MutableHandleValue ret, const fixed& val) { ret.set(JS::NumberValue(val.ToDouble())); } template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CFixedVector3D& out) { if (!v.isObject()) return false; // TODO: report type error JSAutoRequest rq(cx); JS::RootedObject obj(cx, &v.toObject()); JS::RootedValue p(cx); if (!JS_GetProperty(cx, obj, "x", &p)) return false; // TODO: report type errors if (!FromJSVal(cx, p, out.X)) return false; if (!JS_GetProperty(cx, obj, "y", &p)) return false; if (!FromJSVal(cx, p, out.Y)) return false; if (!JS_GetProperty(cx, obj, "z", &p)) return false; if (!FromJSVal(cx, p, out.Z)) return false; return true; } template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CFixedVector3D& val) { JSAutoRequest rq(cx); ScriptInterface::CxPrivate* pCxPrivate = ScriptInterface::GetScriptInterfaceAndCBData(cx); JS::RootedObject global(cx, &pCxPrivate->pScriptInterface->GetGlobalObject().toObject()); JS::RootedValue valueVector3D(cx); if (!JS_GetProperty(cx, global, "Vector3D", &valueVector3D)) FAIL_VOID("Failed to get Vector3D constructor"); JS::AutoValueArray<3> args(cx); args[0].setNumber(val.X.ToDouble()); args[1].setNumber(val.Y.ToDouble()); args[2].setNumber(val.Z.ToDouble()); if (!JS::Construct(cx, valueVector3D, args, ret)) FAIL_VOID("Failed to construct Vector3D object"); } template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CFixedVector2D& out) { JSAutoRequest rq(cx); if (!v.isObject()) return false; // TODO: report type error JS::RootedObject obj(cx, &v.toObject()); JS::RootedValue p(cx); if (!JS_GetProperty(cx, obj, "x", &p)) return false; // TODO: report type errors if (!FromJSVal(cx, p, out.X)) return false; if (!JS_GetProperty(cx, obj, "y", &p)) return false; if (!FromJSVal(cx, p, out.Y)) return false; return true; } template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CFixedVector2D& val) { JSAutoRequest rq(cx); ScriptInterface::CxPrivate* pCxPrivate = ScriptInterface::GetScriptInterfaceAndCBData(cx); JS::RootedObject global(cx, &pCxPrivate->pScriptInterface->GetGlobalObject().toObject()); JS::RootedValue valueVector2D(cx); if (!JS_GetProperty(cx, global, "Vector2D", &valueVector2D)) FAIL_VOID("Failed to get Vector2D constructor"); JS::AutoValueArray<2> args(cx); args[0].setNumber(val.X.ToDouble()); args[1].setNumber(val.Y.ToDouble()); if (!JS::Construct(cx, valueVector2D, args, ret)) FAIL_VOID("Failed to construct Vector2D object"); } template<> void ScriptInterface::ToJSVal >(JSContext* cx, JS::MutableHandleValue ret, const Grid& val) { JSAutoRequest rq(cx); u32 length = (u32)(val.m_W * val.m_H); u32 nbytes = (u32)(length * sizeof(u8)); JS::RootedObject objArr(cx, JS_NewUint8Array(cx, length)); // Copy the array data and then remove the no-GC check to allow further changes to the JS data { JS::AutoCheckCannotGC nogc; - memcpy((void*)JS_GetUint8ArrayData(objArr, nogc), val.m_Data, nbytes); + bool sharedMemory; + memcpy((void*)JS_GetUint8ArrayData(objArr, &sharedMemory, nogc), val.m_Data, nbytes); } JS::RootedValue data(cx, JS::ObjectValue(*objArr)); JS::RootedValue w(cx); JS::RootedValue h(cx); ScriptInterface::ToJSVal(cx, &w, val.m_W); ScriptInterface::ToJSVal(cx, &h, val.m_H); JS::RootedObject obj(cx, JS_NewPlainObject(cx)); JS_SetProperty(cx, obj, "width", w); JS_SetProperty(cx, obj, "height", h); JS_SetProperty(cx, obj, "data", data); ret.setObject(*obj); } template<> void ScriptInterface::ToJSVal >(JSContext* cx, JS::MutableHandleValue ret, const Grid& val) { JSAutoRequest rq(cx); u32 length = (u32)(val.m_W * val.m_H); u32 nbytes = (u32)(length * sizeof(u16)); JS::RootedObject objArr(cx, JS_NewUint16Array(cx, length)); // Copy the array data and then remove the no-GC check to allow further changes to the JS data { JS::AutoCheckCannotGC nogc; - memcpy((void*)JS_GetUint16ArrayData(objArr, nogc), val.m_Data, nbytes); + bool sharedMemory; + memcpy((void*)JS_GetUint16ArrayData(objArr, &sharedMemory, nogc), val.m_Data, nbytes); } JS::RootedValue data(cx, JS::ObjectValue(*objArr)); JS::RootedValue w(cx); JS::RootedValue h(cx); ScriptInterface::ToJSVal(cx, &w, val.m_W); ScriptInterface::ToJSVal(cx, &h, val.m_H); JS::RootedObject obj(cx, JS_NewPlainObject(cx)); JS_SetProperty(cx, obj, "width", w); JS_SetProperty(cx, obj, "height", h); JS_SetProperty(cx, obj, "data", data); ret.setObject(*obj); } template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, TNSpline& out) { if (!v.isObject()) FAIL("Argument must be an object"); JSAutoRequest rq(cx); JS::RootedObject obj(cx, &v.toObject()); - if (!JS_IsArrayObject(cx, obj)) + bool isArray; + if (!JS_IsArrayObject(cx, obj, &isArray) || !isArray) FAIL("Argument must be an array"); u32 numberOfNodes = 0; if (!JS_GetArrayLength(cx, obj, &numberOfNodes)) FAIL("Failed to get array length"); for (u32 i = 0; i < numberOfNodes; ++i) { JS::RootedValue node(cx); if (!JS_GetElement(cx, obj, i, &node)) FAIL("Failed to read array element"); fixed deltaTime; if (!FromJSProperty(cx, node, "deltaTime", deltaTime)) FAIL("Failed to read Spline.deltaTime property"); CFixedVector3D position; if (!FromJSProperty(cx, node, "position", position)) FAIL("Failed to read Spline.position property"); out.AddNode(position, CFixedVector3D(), deltaTime); } if (out.GetAllNodes().empty()) FAIL("Spline must contain at least one node"); return true; } template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CCinemaPath& out) { if (!v.isObject()) FAIL("Argument must be an object"); JSAutoRequest rq(cx); JS::RootedObject obj(cx, &v.toObject()); CCinemaData pathData; TNSpline positionSpline, targetSpline; if (!FromJSProperty(cx, v, "name", pathData.m_Name)) FAIL("Failed to get CCinemaPath.name property"); if (!FromJSProperty(cx, v, "orientation", pathData.m_Orientation)) FAIL("Failed to get CCinemaPath.orientation property"); if (!FromJSProperty(cx, v, "positionNodes", positionSpline)) FAIL("Failed to get CCinemaPath.positionNodes property"); if (pathData.m_Orientation == L"target" && !FromJSProperty(cx, v, "targetNodes", targetSpline)) FAIL("Failed to get CCinemaPath.targetNodes property"); // Other properties are not necessary to be defined if (!FromJSProperty(cx, v, "timescale", pathData.m_Timescale)) pathData.m_Timescale = fixed::FromInt(1); if (!FromJSProperty(cx, v, "mode", pathData.m_Mode)) pathData.m_Mode = L"ease_inout"; if (!FromJSProperty(cx, v, "style", pathData.m_Style)) pathData.m_Style = L"default"; out = CCinemaPath(pathData, positionSpline, targetSpline); return true; } // define vectors JSVAL_VECTOR(CFixedVector2D) #undef FAIL #undef FAIL_VOID Index: ps/trunk/source/simulation2/serialization/StdDeserializer.cpp =================================================================== --- ps/trunk/source/simulation2/serialization/StdDeserializer.cpp (revision 22626) +++ ps/trunk/source/simulation2/serialization/StdDeserializer.cpp (revision 22627) @@ -1,522 +1,522 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "StdDeserializer.h" #include "SerializedScriptTypes.h" #include "StdSerializer.h" // for DEBUG_SERIALIZER_ANNOTATE #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptExtraHeaders.h" // for typed arrays #include "lib/byte_order.h" CStdDeserializer::CStdDeserializer(const ScriptInterface& scriptInterface, std::istream& stream) : m_ScriptInterface(scriptInterface), m_Stream(stream), m_dummyObject(scriptInterface.GetJSRuntime()) { JSContext* cx = m_ScriptInterface.GetContext(); JSAutoRequest rq(cx); JS_AddExtraGCRootsTracer(m_ScriptInterface.GetJSRuntime(), CStdDeserializer::Trace, this); // Add a dummy tag because the serializer uses the tag 0 to indicate that a value // needs to be serialized and then tagged m_dummyObject = JS_NewPlainObject(cx); m_ScriptBackrefs.push_back(JS::Heap(m_dummyObject)); } CStdDeserializer::~CStdDeserializer() { JS_RemoveExtraGCRootsTracer(m_ScriptInterface.GetJSRuntime(), CStdDeserializer::Trace, this); } void CStdDeserializer::Trace(JSTracer *trc, void *data) { reinterpret_cast(data)->TraceMember(trc); } void CStdDeserializer::TraceMember(JSTracer *trc) { for (size_t i=0; i>& proto : m_SerializablePrototypes) JS_CallObjectTracer(trc, &proto.second, "StdDeserializer::m_SerializablePrototypes"); } void CStdDeserializer::Get(const char* name, u8* data, size_t len) { #if DEBUG_SERIALIZER_ANNOTATE std::string strName; char c = m_Stream.get(); ENSURE(c == '<'); while (1) { c = m_Stream.get(); if (c == '>') break; else strName += c; } ENSURE(strName == name); #else UNUSED2(name); #endif m_Stream.read((char*)data, (std::streamsize)len); if (!m_Stream.good()) { // hit eof before len, or other errors // NOTE: older libc++ versions incorrectly set eofbit on the last char; test gcount as a workaround // see https://llvm.org/bugs/show_bug.cgi?id=9335 if (m_Stream.bad() || m_Stream.fail() || (m_Stream.eof() && m_Stream.gcount() != (std::streamsize)len)) throw PSERROR_Deserialize_ReadFailed(); } } std::istream& CStdDeserializer::GetStream() { return m_Stream; } void CStdDeserializer::RequireBytesInStream(size_t numBytes) { // It would be nice to do: // if (numBytes > (size_t)m_Stream.rdbuf()->in_avail()) // throw PSERROR_Deserialize_OutOfBounds("RequireBytesInStream"); // but that doesn't work (at least on MSVC) since in_avail isn't // guaranteed to return the actual number of bytes available; see e.g. // http://social.msdn.microsoft.com/Forums/en/vclanguage/thread/13009a88-933f-4be7-bf3d-150e425e66a6#70ea562d-8605-4742-8851-1bae431ce6ce // Instead we'll just verify that it's not an extremely large number: if (numBytes > 64*MiB) throw PSERROR_Deserialize_OutOfBounds("RequireBytesInStream"); } void CStdDeserializer::AddScriptBackref(JS::HandleObject obj) { m_ScriptBackrefs.push_back(JS::Heap(obj)); } void CStdDeserializer::GetScriptBackref(u32 tag, JS::MutableHandleObject ret) { ENSURE(m_ScriptBackrefs.size() > tag); ret.set(m_ScriptBackrefs[tag]); } //////////////////////////////////////////////////////////////// JS::Value CStdDeserializer::ReadScriptVal(const char* UNUSED(name), JS::HandleObject appendParent) { JSContext* cx = m_ScriptInterface.GetContext(); JSAutoRequest rq(cx); uint8_t type; NumberU8_Unbounded("type", type); switch (type) { case SCRIPT_TYPE_VOID: return JS::UndefinedValue(); case SCRIPT_TYPE_NULL: return JS::NullValue(); case SCRIPT_TYPE_ARRAY: case SCRIPT_TYPE_OBJECT: case SCRIPT_TYPE_OBJECT_PROTOTYPE: { JS::RootedObject obj(cx); if (appendParent) { obj.set(appendParent); } else if (type == SCRIPT_TYPE_ARRAY) { u32 length; NumberU32_Unbounded("array length", length); obj.set(JS_NewArrayObject(cx, length)); } else if (type == SCRIPT_TYPE_OBJECT) { obj.set(JS_NewPlainObject(cx)); } else // SCRIPT_TYPE_OBJECT_PROTOTYPE { std::wstring prototypeName; String("proto name", prototypeName, 0, 256); // Get constructor object JS::RootedObject proto(cx); GetSerializablePrototype(prototypeName, &proto); if (!proto) throw PSERROR_Deserialize_ScriptError("Failed to find serializable prototype for object"); obj.set(JS_NewObjectWithGivenProto(cx, nullptr, proto)); if (!obj) throw PSERROR_Deserialize_ScriptError("JS_NewObject failed"); // Does it have custom Deserialize function? // if so, we let it handle the deserialized data, rather than adding properties directly bool hasCustomDeserialize, hasCustomSerialize; if (!JS_HasProperty(cx, obj, "Serialize", &hasCustomSerialize) || !JS_HasProperty(cx, obj, "Deserialize", &hasCustomDeserialize)) throw PSERROR_Serialize_ScriptError("JS_HasProperty failed"); if (hasCustomDeserialize) { AddScriptBackref(obj); JS::RootedValue serialize(cx); if (!JS_GetProperty(cx, obj, "Serialize", &serialize)) throw PSERROR_Serialize_ScriptError("JS_GetProperty failed"); bool hasNullSerialize = hasCustomSerialize && serialize.isNull(); // If Serialize is null, we'll still call Deserialize but with undefined argument JS::RootedValue data(cx); if (!hasNullSerialize) ScriptVal("data", &data); JS::RootedValue objVal(cx, JS::ObjectValue(*obj)); m_ScriptInterface.CallFunctionVoid(objVal, "Deserialize", data); return JS::ObjectValue(*obj); } } if (!obj) throw PSERROR_Deserialize_ScriptError("Deserializer failed to create new object"); AddScriptBackref(obj); uint32_t numProps; NumberU32_Unbounded("num props", numProps); bool isLatin1; for (uint32_t i = 0; i < numProps; ++i) { Bool("isLatin1", isLatin1); if (isLatin1) { std::vector propname; ReadStringLatin1("prop name", propname); - JS::RootedValue propval(cx, ReadScriptVal("prop value", JS::NullPtr())); + JS::RootedValue propval(cx, ReadScriptVal("prop value", nullptr)); utf16string prp(propname.begin(), propname.end());; // TODO: Should ask upstream about getting a variant of JS_SetProperty with a length param. if (!JS_SetUCProperty(cx, obj, (const char16_t*)prp.data(), prp.length(), propval)) throw PSERROR_Deserialize_ScriptError(); } else { utf16string propname; ReadStringUTF16("prop name", propname); - JS::RootedValue propval(cx, ReadScriptVal("prop value", JS::NullPtr())); + JS::RootedValue propval(cx, ReadScriptVal("prop value", nullptr)); if (!JS_SetUCProperty(cx, obj, (const char16_t*)propname.data(), propname.length(), propval)) throw PSERROR_Deserialize_ScriptError(); } } return JS::ObjectValue(*obj); } case SCRIPT_TYPE_STRING: { JS::RootedString str(cx); ScriptString("string", &str); return JS::StringValue(str); } case SCRIPT_TYPE_INT: { int32_t value; NumberI32("value", value, JSVAL_INT_MIN, JSVAL_INT_MAX); return JS::NumberValue(value); } case SCRIPT_TYPE_DOUBLE: { double value; NumberDouble_Unbounded("value", value); JS::RootedValue rval(cx, JS::NumberValue(value)); if (rval.isNull()) throw PSERROR_Deserialize_ScriptError("JS_NewNumberValue failed"); return rval; } case SCRIPT_TYPE_BOOLEAN: { uint8_t value; NumberU8("value", value, 0, 1); return JS::BooleanValue(value ? true : false); } case SCRIPT_TYPE_BACKREF: { u32 tag; NumberU32_Unbounded("tag", tag); JS::RootedObject obj(cx); GetScriptBackref(tag, &obj); if (!obj) throw PSERROR_Deserialize_ScriptError("Invalid backref tag"); return JS::ObjectValue(*obj); } case SCRIPT_TYPE_OBJECT_NUMBER: { double value; NumberDouble_Unbounded("value", value); JS::RootedValue val(cx, JS::NumberValue(value)); JS::RootedObject ctorobj(cx); if (!JS_GetClassObject(cx, JSProto_Number, &ctorobj)) throw PSERROR_Deserialize_ScriptError("JS_GetClassObject failed"); JS::RootedObject obj(cx, JS_New(cx, ctorobj, JS::HandleValueArray(val))); if (!obj) throw PSERROR_Deserialize_ScriptError("JS_New failed"); AddScriptBackref(obj); return JS::ObjectValue(*obj); } case SCRIPT_TYPE_OBJECT_STRING: { JS::RootedString str(cx); ScriptString("value", &str); if (!str) throw PSERROR_Deserialize_ScriptError(); JS::RootedValue val(cx, JS::StringValue(str)); JS::RootedObject ctorobj(cx); if (!JS_GetClassObject(cx, JSProto_String, &ctorobj)) throw PSERROR_Deserialize_ScriptError("JS_GetClassObject failed"); JS::RootedObject obj(cx, JS_New(cx, ctorobj, JS::HandleValueArray(val))); if (!obj) throw PSERROR_Deserialize_ScriptError("JS_New failed"); AddScriptBackref(obj); return JS::ObjectValue(*obj); } case SCRIPT_TYPE_OBJECT_BOOLEAN: { bool value; Bool("value", value); JS::RootedValue val(cx, JS::BooleanValue(value)); JS::RootedObject ctorobj(cx); if (!JS_GetClassObject(cx, JSProto_Boolean, &ctorobj)) throw PSERROR_Deserialize_ScriptError("JS_GetClassObject failed"); JS::RootedObject obj(cx, JS_New(cx, ctorobj, JS::HandleValueArray(val))); if (!obj) throw PSERROR_Deserialize_ScriptError("JS_New failed"); AddScriptBackref(obj); return JS::ObjectValue(*obj); } case SCRIPT_TYPE_TYPED_ARRAY: { u8 arrayType; u32 byteOffset, length; NumberU8_Unbounded("array type", arrayType); NumberU32_Unbounded("byte offset", byteOffset); NumberU32_Unbounded("length", length); // To match the serializer order, we reserve the typed array's backref tag here JS::RootedObject arrayObj(cx); AddScriptBackref(arrayObj); // Get buffer object - JS::RootedValue bufferVal(cx, ReadScriptVal("buffer", JS::NullPtr())); + JS::RootedValue bufferVal(cx, ReadScriptVal("buffer", nullptr)); if (!bufferVal.isObject()) throw PSERROR_Deserialize_ScriptError(); JS::RootedObject bufferObj(cx, &bufferVal.toObject()); if (!JS_IsArrayBufferObject(bufferObj)) throw PSERROR_Deserialize_ScriptError("js_IsArrayBuffer failed"); switch(arrayType) { case SCRIPT_TYPED_ARRAY_INT8: arrayObj = JS_NewInt8ArrayWithBuffer(cx, bufferObj, byteOffset, length); break; case SCRIPT_TYPED_ARRAY_UINT8: arrayObj = JS_NewUint8ArrayWithBuffer(cx, bufferObj, byteOffset, length); break; case SCRIPT_TYPED_ARRAY_INT16: arrayObj = JS_NewInt16ArrayWithBuffer(cx, bufferObj, byteOffset, length); break; case SCRIPT_TYPED_ARRAY_UINT16: arrayObj = JS_NewUint16ArrayWithBuffer(cx, bufferObj, byteOffset, length); break; case SCRIPT_TYPED_ARRAY_INT32: arrayObj = JS_NewInt32ArrayWithBuffer(cx, bufferObj, byteOffset, length); break; case SCRIPT_TYPED_ARRAY_UINT32: arrayObj = JS_NewUint32ArrayWithBuffer(cx, bufferObj, byteOffset, length); break; case SCRIPT_TYPED_ARRAY_FLOAT32: arrayObj = JS_NewFloat32ArrayWithBuffer(cx, bufferObj, byteOffset, length); break; case SCRIPT_TYPED_ARRAY_FLOAT64: arrayObj = JS_NewFloat64ArrayWithBuffer(cx, bufferObj, byteOffset, length); break; case SCRIPT_TYPED_ARRAY_UINT8_CLAMPED: arrayObj = JS_NewUint8ClampedArrayWithBuffer(cx, bufferObj, byteOffset, length); break; default: throw PSERROR_Deserialize_ScriptError("Failed to deserialize unrecognized typed array view"); } if (!arrayObj) throw PSERROR_Deserialize_ScriptError("js_CreateTypedArrayWithBuffer failed"); return JS::ObjectValue(*arrayObj); } case SCRIPT_TYPE_ARRAY_BUFFER: { u32 length; NumberU32_Unbounded("buffer length", length); #if BYTE_ORDER != LITTLE_ENDIAN #error TODO: need to convert JS ArrayBuffer data from little-endian #endif void* contents = malloc(length); ENSURE(contents); RawBytes("buffer data", (u8*)contents, length); JS::RootedObject bufferObj(cx, JS_NewArrayBufferWithContents(cx, length, contents)); AddScriptBackref(bufferObj); return JS::ObjectValue(*bufferObj); } case SCRIPT_TYPE_OBJECT_MAP: { JS::RootedObject obj(cx, JS::NewMapObject(cx)); AddScriptBackref(obj); u32 mapSize; NumberU32_Unbounded("map size", mapSize); for (u32 i=0; i& str) { uint32_t len; NumberU32_Unbounded("string length", len); RequireBytesInStream(len); str.resize(len); Get(name, (u8*)str.data(), len); } void CStdDeserializer::ReadStringUTF16(const char* name, utf16string& str) { uint32_t len; NumberU32_Unbounded("string length", len); RequireBytesInStream(len*2); str.resize(len); Get(name, (u8*)str.data(), len*2); } void CStdDeserializer::ScriptString(const char* name, JS::MutableHandleString out) { #if BYTE_ORDER != LITTLE_ENDIAN #error TODO: probably need to convert JS strings from little-endian #endif JSContext* cx = m_ScriptInterface.GetContext(); JSAutoRequest rq(cx); bool isLatin1; Bool("isLatin1", isLatin1); if (isLatin1) { std::vector str; ReadStringLatin1(name, str); out.set(JS_NewStringCopyN(cx, (const char*)str.data(), str.size())); if (!out) throw PSERROR_Deserialize_ScriptError("JS_NewStringCopyN failed"); } else { utf16string str; ReadStringUTF16(name, str); out.set(JS_NewUCStringCopyN(cx, (const char16_t*)str.data(), str.length())); if (!out) throw PSERROR_Deserialize_ScriptError("JS_NewUCStringCopyN failed"); } } void CStdDeserializer::ScriptVal(const char* name, JS::MutableHandleValue out) { - out.set(ReadScriptVal(name, JS::NullPtr())); + out.set(ReadScriptVal(name, nullptr)); } void CStdDeserializer::ScriptObjectAppend(const char* name, JS::HandleValue objVal) { JSContext* cx = m_ScriptInterface.GetContext(); JSAutoRequest rq(cx); if (!objVal.isObject()) throw PSERROR_Deserialize_ScriptError(); JS::RootedObject obj(cx, &objVal.toObject()); ReadScriptVal(name, obj); } void CStdDeserializer::SetSerializablePrototypes(std::map >& prototypes) { m_SerializablePrototypes = prototypes; } bool CStdDeserializer::IsSerializablePrototype(const std::wstring& name) { return m_SerializablePrototypes.find(name) != m_SerializablePrototypes.end(); } void CStdDeserializer::GetSerializablePrototype(const std::wstring& name, JS::MutableHandleObject ret) { std::map >::iterator it = m_SerializablePrototypes.find(name); if (it != m_SerializablePrototypes.end()) ret.set(it->second); else ret.set(NULL); } Index: ps/trunk/build/premake/extern_libs5.lua =================================================================== --- ps/trunk/build/premake/extern_libs5.lua (revision 22626) +++ ps/trunk/build/premake/extern_libs5.lua (revision 22627) @@ -1,711 +1,705 @@ -- this file provides project_add_extern_libs, which takes care of the -- dirty details of adding the libraries' include and lib paths. -- -- TYPICAL TASK: add new library. Instructions: -- 1) add a new extern_lib_defs entry -- 2) add library name to extern_libs tables in premake.lua for all 'projects' that want to use it -- directory in which OS-specific library subdirectories reside. if os.istarget("macosx") then libraries_dir = rootdir.."/libraries/osx/" elseif os.istarget("windows") then libraries_dir = rootdir.."/libraries/win32/" else -- No Unix-specific libs yet (use source directory instead!) end -- directory for shared, bundled libraries libraries_source_dir = rootdir.."/libraries/source/" third_party_source_dir = rootdir.."/source/third_party/" local function add_default_lib_paths(extern_lib) libdirs { libraries_dir .. extern_lib .. "/lib" } end local function add_source_lib_paths(extern_lib) libdirs { libraries_source_dir .. extern_lib .. "/lib" } end local function add_default_include_paths(extern_lib) sysincludedirs { libraries_dir .. extern_lib .. "/include" } end local function add_source_include_paths(extern_lib) sysincludedirs { libraries_source_dir .. extern_lib .. "/include" } end local function add_third_party_include_paths(extern_lib) sysincludedirs { third_party_source_dir .. extern_lib .. "/include" } end pkgconfig = require "pkgconfig" local function add_delayload(name, suffix, def) if def["no_delayload"] then return end -- currently only supported by VC; nothing to do on other platforms. if not os.istarget("windows") then return end -- no extra debug version; use same library in all configs if suffix == "" then linkoptions { "/DELAYLOAD:"..name..".dll" } -- extra debug version available; use in debug config else local dbg_cmd = "/DELAYLOAD:" .. name .. suffix .. ".dll" local cmd = "/DELAYLOAD:" .. name .. ".dll" filter "Debug" linkoptions { dbg_cmd } filter "Release" linkoptions { cmd } filter { } end end local function add_default_links(def) -- careful: make sure to only use *_names when on the correct platform. local names = {} if os.istarget("windows") then if def.win_names then names = def.win_names end elseif _OPTIONS["android"] and def.android_names then names = def.android_names elseif os.istarget("linux") and def.linux_names then names = def.linux_names elseif os.istarget("macosx") and (def.osx_names or def.osx_frameworks) then if def.osx_names then names = def.osx_names end -- OS X "Frameworks" are added to the links as "name.framework" if def.osx_frameworks then for i,name in pairs(def.osx_frameworks) do links { name .. ".framework" } end end elseif os.istarget("bsd") and def.bsd_names then names = def.bsd_names elseif def.unix_names then names = def.unix_names end local suffix = "d" -- library is overriding default suffix (typically "" to indicate there is none) if def["dbg_suffix"] then suffix = def["dbg_suffix"] end -- non-Windows doesn't have the distinction of debug vs. release libraries -- (to be more specific, they do, but the two are binary compatible; -- usually only one type - debug or release - is installed at a time). if not os.istarget("windows") then suffix = "" end for i,name in pairs(names) do filter "Debug" links { name .. suffix } filter "Release" links { name } filter { } add_delayload(name, suffix, def) end end -- Library definitions -- In a perfect world, libraries would have a common installation template, -- i.e. location of include directory, naming convention for .lib, etc. -- this table provides a means of working around each library's differences. -- -- The basic approach is defining two functions per library: -- -- 1. compile_settings -- This function should set all settings requred during the compile-phase like -- includedirs, defines etc... -- -- 2. link_settings -- This function should set all settings required during the link-phase like -- libdirs, linkflag etc... -- -- The main reason for separating those settings is different linking behaviour -- on osx and xcode. For more details, read the comment in project_add_extern_libs. -- -- There are some helper functions for the most common tasks. You should use them -- if they can be used in your situation to make the definitions more consistent and -- use their default beviours as a guideline. -- -- -- add_default_lib_paths(extern_lib) -- Description: Add '//lib'to the libpaths -- Parameters: -- * extern_lib: to be used in the libpath. -- -- add_default_include_paths(extern_lib) -- Description: Add '//include' to the includepaths -- Parameters: -- * extern_lib: to be used in the libpath. -- -- add_default_links -- Description: Adds links to libraries and configures delayloading. -- If the *_names parameter for a plattform is missing, no linking will be done -- on that plattform. -- The default assumptions are: -- * debug import library and DLL are distinguished with a "d" suffix -- * the library should be marked for delay-loading. -- Parameters: -- * win_names: table of import library / DLL names (no extension) when -- running on Windows. -- * unix_names: as above; shared object names when running on non-Windows. -- * osx_names, osx_frameworks: for OS X specifically; if any of those is -- specified, unix_names is ignored. Using both is possible if needed. -- * osx_names: as above. -- * osx_frameworks: as above, for system libraries that need to be linked -- as "name.framework". -- * bsd_names: as above; for BSD specifically (overrides unix_names if both are -- specified) -- * linux_names: ditto for Linux (overrides unix_names if both given) -- * dbg_suffix: changes the debug suffix from the above default. -- can be "" to indicate the library doesn't have a debug build; -- in that case, the same library (without suffix) is used in -- all build configurations. -- * no_delayload: indicate the library is not to be delay-loaded. -- this is necessary for some libraries that do not support it, -- e.g. Xerces (which is so stupid as to export variables). extern_lib_defs = { boost = { compile_settings = function() if os.istarget("windows") then add_default_include_paths("boost") elseif os.istarget("macosx") then -- Suppress all the Boost warnings on OS X by including it as a system directory buildoptions { "-isystem../" .. libraries_dir .. "boost/include" } end -- TODO: This actually applies to most libraries we use on BSDs, make this a global setting. if os.istarget("bsd") then sysincludedirs { "/usr/local/include" } end end, link_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_lib_paths("boost") end add_default_links({ -- The following are not strictly link dependencies on all systems, but -- are included for compatibility with different versions of Boost android_names = { "boost_filesystem-gcc-mt", "boost_system-gcc-mt" }, unix_names = { os.findlib("boost_filesystem-mt") and "boost_filesystem-mt" or "boost_filesystem", os.findlib("boost_system-mt") and "boost_system-mt" or "boost_system" }, osx_names = { "boost_filesystem-mt", "boost_system-mt" }, }) end, }, comsuppw = { link_settings = function() add_default_links({ win_names = { "comsuppw" }, dbg_suffix = "d", no_delayload = 1, }) end, }, cxxtest = { compile_settings = function() sysincludedirs { libraries_source_dir .. "cxxtest-4.4" } end, }, enet = { compile_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_include_paths("enet") end end, link_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_lib_paths("enet") end add_default_links({ win_names = { "enet" }, unix_names = { "enet" }, }) end, }, fcollada = { compile_settings = function() add_source_include_paths("fcollada") end, link_settings = function() add_source_lib_paths("fcollada") if os.istarget("windows") then filter "Debug" links { "FColladaD" } filter "Release" links { "FCollada" } filter { } else filter "Debug" links { "FColladaSD" } filter "Release" links { "FColladaSR" } filter { } end end, }, gloox = { compile_settings = function() if os.istarget("windows") then add_default_include_paths("gloox") else -- Support GLOOX_CONFIG for overriding the default (pkg-config --cflags gloox) -- i.e. on OSX where it gets set in update-workspaces.sh pkgconfig.add_includes("gloox", os.getenv("GLOOX_CONFIG")) end end, link_settings = function() if os.istarget("windows") then add_default_lib_paths("gloox") add_default_links({ win_names = { "gloox-1.0" }, no_delayload = 1, }) else pkgconfig.add_links("gloox", os.getenv("GLOOX_CONFIG")) if os.istarget("macosx") then -- Manually add gnutls dependencies, those are not present in gloox's pkg-config add_default_lib_paths("nettle") add_default_lib_paths("gmp") add_default_links({ osx_names = { "nettle", "hogweed", "gmp" }, }) end end end, }, iconv = { compile_settings = function() if os.istarget("windows") then add_default_include_paths("iconv") defines { "HAVE_ICONV_CONST" } defines { "ICONV_CONST=const" } defines { "LIBICONV_STATIC" } elseif os.istarget("macosx") then add_default_include_paths("iconv") defines { "LIBICONV_STATIC" } elseif os.getversion().description == "FreeBSD" then defines { "HAVE_ICONV_CONST" } defines { "ICONV_CONST=const" } end end, link_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_lib_paths("iconv") end add_default_links({ win_names = { "libiconv" }, osx_names = { "iconv" }, dbg_suffix = "", no_delayload = 1, }) -- glibc (used on Linux and GNU/kFreeBSD) has iconv -- FreeBSD 10+ has iconv as a part of libc if os.istarget("bsd") and not (os.getversion().description == "FreeBSD" and os.getversion().majorversion >= 10 or os.getversion().description == "GNU/kFreeBSD") then add_default_links({ bsd_names = { "iconv" }, }) end end, }, icu = { compile_settings = function() if os.istarget("windows") then add_default_include_paths("icu") else -- Support ICU_CONFIG for overriding the default (pkg-config --cflags icu-i18n) -- i.e. on OSX where it gets set in update-workspaces.sh pkgconfig.add_includes("icu-i18n", os.getenv("ICU_CONFIG"), "--cppflags") end end, link_settings = function() if os.istarget("windows") then add_default_lib_paths("icu") add_default_links({ win_names = { "icuuc", "icuin" }, dbg_suffix = "", no_delayload = 1, }) else pkgconfig.add_links("icu-i18n", os.getenv("ICU_CONFIG"), "--ldflags-searchpath --ldflags-libsonly --ldflags-system") end end, }, libcurl = { compile_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_include_paths("libcurl") end end, link_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_lib_paths("libcurl") end add_default_links({ win_names = { "libcurl" }, unix_names = { "curl" }, osx_names = { "curl", "z" }, osx_frameworks = { "Security" } }) end, }, libpng = { compile_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_include_paths("libpng") end if os.getversion().description == "OpenBSD" then sysincludedirs { "/usr/local/include/libpng" } end end, link_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_lib_paths("libpng") end add_default_links({ win_names = { "libpng16" }, unix_names = { "png" }, -- Otherwise ld will sometimes pull in ancient 1.2 from the SDK, which breaks the build :/ -- TODO: Figure out why that happens osx_names = { "png16" }, }) end, }, libsodium = { compile_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_include_paths("libsodium") end end, link_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_lib_paths("libsodium") end add_default_links({ win_names = { "libsodium" }, unix_names = { "sodium" }, }) end, }, libxml2 = { compile_settings = function() if os.istarget("windows") then add_default_include_paths("libxml2") else -- Support XML2_CONFIG for overriding the default (pkg-config --cflags libxml-2.0) -- i.e. on OSX where it gets set in update-workspaces.sh pkgconfig.add_includes("libxml-2.0", os.getenv("XML2_CONFIG")) end if os.istarget("macosx") then -- libxml2 needs _REENTRANT or __MT__ for thread support; -- OS X doesn't get either set by default, so do it manually defines { "_REENTRANT" } end end, link_settings = function() if os.istarget("windows") then add_default_lib_paths("libxml2") filter "Debug" links { "libxml2" } filter "Release" links { "libxml2" } filter { } else pkgconfig.add_links("libxml-2.0", os.getenv("XML2_CONFIG")) end end, }, miniupnpc = { compile_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_include_paths("miniupnpc") end end, link_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_lib_paths("miniupnpc") end add_default_links({ win_names = { "miniupnpc" }, unix_names = { "miniupnpc" }, }) end, }, nvtt = { compile_settings = function() if not _OPTIONS["with-system-nvtt"] then add_source_include_paths("nvtt") end defines { "NVTT_SHARED=1" } end, link_settings = function() if not _OPTIONS["with-system-nvtt"] then add_source_lib_paths("nvtt") end add_default_links({ win_names = { "nvtt" }, unix_names = { "nvcore", "nvmath", "nvimage", "nvtt" }, osx_names = { "nvcore", "nvmath", "nvimage", "nvtt", "squish" }, dbg_suffix = "", -- for performance we always use the release-mode version }) end, }, openal = { compile_settings = function() if os.istarget("windows") then add_default_include_paths("openal") end end, link_settings = function() if os.istarget("windows") then add_default_lib_paths("openal") end add_default_links({ win_names = { "openal32" }, unix_names = { "openal" }, osx_frameworks = { "OpenAL" }, dbg_suffix = "", no_delayload = 1, -- delayload seems to cause errors on startup }) end, }, opengl = { compile_settings = function() if os.istarget("windows") then add_default_include_paths("opengl") end end, link_settings = function() if os.istarget("windows") then add_default_lib_paths("opengl") end if _OPTIONS["gles"] then add_default_links({ unix_names = { "GLESv2" }, dbg_suffix = "", }) else add_default_links({ win_names = { "opengl32", "gdi32" }, unix_names = { "GL" }, osx_frameworks = { "OpenGL" }, dbg_suffix = "", no_delayload = 1, -- delayload seems to cause errors on startup }) end end, }, sdl = { compile_settings = function() if os.istarget("windows") then includedirs { libraries_dir .. "sdl2/include/SDL" } elseif not _OPTIONS["android"] then -- Support SDL2_CONFIG for overriding the default (pkg-config sdl2) -- i.e. on OSX where it gets set in update-workspaces.sh pkgconfig.add_includes("sdl2", os.getenv("SDL2_CONFIG")) end end, link_settings = function() if os.istarget("windows") then add_default_lib_paths("sdl2") elseif not _OPTIONS["android"] then pkgconfig.add_links("sdl2", os.getenv("SDL2_CONFIG")) end end, }, spidermonkey = { compile_settings = function() - if _OPTIONS["with-system-mozjs38"] then + if _OPTIONS["with-system-mozjs45"] then if not _OPTIONS["android"] then - pkgconfig.add_includes("mozjs-38") + pkgconfig.add_includes("mozjs-45") end else if os.istarget("windows") then include_dir = "include-win32" buildoptions { "/FI\"js/RequiredDefines.h\"" } else include_dir = "include-unix" end filter "Debug" sysincludedirs { libraries_source_dir.."spidermonkey/"..include_dir.."-debug" } defines { "DEBUG" } filter "Release" sysincludedirs { libraries_source_dir.."spidermonkey/"..include_dir.."-release" } filter { } end end, link_settings = function() - if _OPTIONS["with-system-mozjs38"] then + if _OPTIONS["with-system-mozjs45"] then if _OPTIONS["android"] then - links { "mozjs-38" } + links { "mozjs-45" } else - pkgconfig.add_links("nspr") - pkgconfig.add_links("mozjs-38") + pkgconfig.add_links("mozjs-45") end else - if os.istarget("macosx") then - add_default_lib_paths("nspr") - links { "nspr4", "plc4", "plds4" } - end - filter { "Debug", "action:vs2013" } - links { "mozjs38-ps-debug-vc120" } + links { "mozjs45-ps-debug-vc120" } filter { "Release", "action:vs2013" } - links { "mozjs38-ps-release-vc120" } + links { "mozjs45-ps-release-vc120" } filter { "Debug", "action:vs2015" } - links { "mozjs38-ps-debug-vc140" } + links { "mozjs45-ps-debug-vc140" } filter { "Release", "action:vs2015" } - links { "mozjs38-ps-release-vc140" } + links { "mozjs45-ps-release-vc140" } filter { "Debug", "action:not vs*" } - links { "mozjs38-ps-debug" } + links { "mozjs45-ps-debug" } filter { "Release", "action:not vs*" } - links { "mozjs38-ps-release" } + links { "mozjs45-ps-release" } filter { } add_source_lib_paths("spidermonkey") end end, }, tinygettext = { compile_settings = function() add_third_party_include_paths("tinygettext") end, }, valgrind = { compile_settings = function() add_source_include_paths("valgrind") end, }, vorbis = { compile_settings = function() if os.istarget("windows") then add_default_include_paths("vorbis") elseif os.istarget("macosx") then add_default_include_paths("libogg") add_default_include_paths("vorbis") end end, link_settings = function() if os.istarget("windows") then add_default_lib_paths("vorbis") elseif os.istarget("macosx") then add_default_lib_paths("libogg") add_default_lib_paths("vorbis") end -- TODO: We need to force linking with these as currently -- they need to be loaded explicitly on execution if os.getversion().description == "OpenBSD" then add_default_links({ unix_names = { "ogg", "vorbis" }, }) end add_default_links({ win_names = { "libvorbisfile" }, unix_names = { "vorbisfile" }, osx_names = { "vorbis", "vorbisenc", "vorbisfile", "ogg" }, }) end, }, wxwidgets = { compile_settings = function() if os.istarget("windows") then includedirs { libraries_dir.."wxwidgets/include/msvc" } add_default_include_paths("wxwidgets") else -- wxwidgets does not come with a definition file for pkg-config, -- so we have to use wxwidgets' own config tool wx_config_path = os.getenv("WX_CONFIG") or "wx-config" pkgconfig.add_includes(nil, wx_config_path, "--unicode=yes --cxxflags") end end, link_settings = function() if os.istarget("windows") then libdirs { libraries_dir.."wxwidgets/lib/vc_lib" } else wx_config_path = os.getenv("WX_CONFIG") or "wx-config" pkgconfig.add_links(nil, wx_config_path, "--unicode=yes --libs std,gl") end end, }, x11 = { compile_settings = function() if not os.istarget("windows") and not os.istarget("macosx") then pkgconfig.add_includes("x11") end end, link_settings = function() if not os.istarget("windows") and not os.istarget("macosx") then pkgconfig.add_links("x11") end end, }, xcursor = { link_settings = function() add_default_links({ unix_names = { "Xcursor" }, }) end, }, zlib = { compile_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_include_paths("zlib") end end, link_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_lib_paths("zlib") end add_default_links({ win_names = { "zlib1" }, unix_names = { "z" }, no_delayload = 1, }) end, }, } -- add a set of external libraries to the project; takes care of -- include / lib path and linking against the import library. -- extern_libs: table of library names [string] -- target_type: String defining the projects kind [string] function project_add_extern_libs(extern_libs, target_type) for i,extern_lib in pairs(extern_libs) do local def = extern_lib_defs[extern_lib] assert(def, "external library " .. extern_lib .. " not defined") if def.compile_settings then def.compile_settings() end -- Linking to external libraries will only be done in the main executable and not in the -- static libraries. Premake would silently skip linking into static libraries for some -- actions anyway (e.g. vs2010). -- On osx using xcode, if this linking would be defined in the static libraries, it would fail to -- link if only dylibs are available. If both *.a and *.dylib are available, it would link statically. -- I couldn't find any problems with that approach. if target_type ~= "StaticLib" and def.link_settings then def.link_settings() end end end Index: ps/trunk/build/workspaces/clean-workspaces.sh =================================================================== --- ps/trunk/build/workspaces/clean-workspaces.sh (revision 22626) +++ ps/trunk/build/workspaces/clean-workspaces.sh (revision 22627) @@ -1,64 +1,65 @@ #!/bin/sh # Some of our makefiles depend on GNU make, so we set some sane defaults if MAKE # is not set. case "`uname -s`" in "FreeBSD" | "OpenBSD" ) MAKE=${MAKE:="gmake"} ;; * ) MAKE=${MAKE:="make"} ;; esac # Check the preserve-libs CL option preserve_libs=false for i in "$@"; do case "$i" in --preserve-libs ) preserve_libs=true ;; esac done # (We don't attempt to clean up every last file here - output in # binaries/system/ will still be there, etc. This is mostly just # to quickly fix problems in the bundled dependencies.) cd "$(dirname $0)" # Now in build/workspaces/ (where we assume this script resides) if [ "$preserve_libs" != "true" ]; then echo "Cleaning bundled third-party dependencies..." (cd ../../libraries/source/fcollada/src && rm -rf ./output) (cd ../../libraries/source/nvtt/src && rm -rf ./build) (cd ../../libraries/source/spidermonkey && rm -f .already-built) - (cd ../../libraries/source/spidermonkey && rm -rf ./mozjs-38.0.0) + (cd ../../libraries/source/spidermonkey && rm -rf ./mozjs-45.0.2) fi # Still delete the directory of previous SpiderMonkey versions to # avoid wasting disk space if people clean workspaces after updating. +(cd ../../libraries/source/spidermonkey && rm -rf ./mozjs-38.0.0) (cd ../../libraries/source/spidermonkey && rm -rf ./mozjs31) (cd ../../libraries/source/spidermonkey && rm -rf ./mozjs24) (cd ../premake/premake5/build/gmake.bsd && ${MAKE} clean) (cd ../premake/premake5/build/gmake.macosx && ${MAKE} clean) (cd ../premake/premake5/build/gmake.unix && ${MAKE} clean) echo "Removing generated test files..." find ../../source -name "test_*.cpp" -type f -not -name "test_setup.cpp" -exec rm {} \; echo "Cleaning build output..." # Remove workspaces/gcc if present rm -rf ./gcc # Remove workspaces/codeblocks if present rm -rf ./codeblocks # Remove workspaces/xcode3 if present rm -rf ./xcode3 # Remove workspaces/xcode4 if present rm -rf ./xcode4 echo echo "Done. Try running update-workspaces.sh again now." Index: ps/trunk/libraries/osx/build-osx-libs.sh =================================================================== --- ps/trunk/libraries/osx/build-osx-libs.sh (revision 22626) +++ ps/trunk/libraries/osx/build-osx-libs.sh (revision 22627) @@ -1,1097 +1,1060 @@ #!/bin/bash # # Script for acquiring and building OS X dependencies for 0 A.D. # # The script checks whether a source tarball exists for each # dependency, if not it will download the correct version from # the project's website, then it removes previous build files, # extracts the tarball, configures and builds the lib. The script # should die on any errors to ease troubleshooting. # # make install is used to copy the compiled libs to each specific # directory and also the config tools (e.g. sdl-config). Because # of this, OS X developers must run this script at least once, # to configure the correct lib directories. It must be run again # if the libraries are moved. # # Building against an SDK is an option, though not required, # as not all build environments contain the Developer SDKs # (Xcode does, but the Command Line Tools package does not) # # -------------------------------------------------------------- # Library versions for ease of updating: ZLIB_VERSION="zlib-1.2.11" CURL_VERSION="curl-7.59.0" ICONV_VERSION="libiconv-1.15" XML2_VERSION="libxml2-2.9.8" SDL2_VERSION="SDL2-2.0.5" BOOST_VERSION="boost_1_64_0" # NOTE: remember to also update LIB_URL below when changing version WXWIDGETS_VERSION="wxWidgets-3.0.3.1" # libpng was included as part of X11 but that's removed from Mountain Lion # (also the Snow Leopard version was ancient 1.2) PNG_VERSION="libpng-1.6.34" OGG_VERSION="libogg-1.3.3" VORBIS_VERSION="libvorbis-1.3.6" # gloox requires GnuTLS, GnuTLS requires Nettle and GMP GMP_VERSION="gmp-6.1.2" NETTLE_VERSION="nettle-3.5.1" # NOTE: remember to also update LIB_URL below when changing version GNUTLS_VERSION="gnutls-3.6.8" GLOOX_VERSION="gloox-1.0.22" -# NSPR is necessary for threadsafe Spidermonkey -NSPR_VERSION="4.15" # OS X only includes part of ICU, and only the dylib # NOTE: remember to also update LIB_URL below when changing version ICU_VERSION="icu4c-59_1" ENET_VERSION="enet-1.3.13" MINIUPNPC_VERSION="miniupnpc-2.0.20180222" SODIUM_VERSION="libsodium-1.0.16" # -------------------------------------------------------------- # Bundled with the game: -# * SpiderMonkey 38 +# * SpiderMonkey 45 # * NVTT # * FCollada # -------------------------------------------------------------- # Provided by OS X: # * OpenAL # * OpenGL # -------------------------------------------------------------- # Force build architecture, as sometimes environment is broken. # For a universal fat binary, the approach would be to build every # dependency with both archs and combine them with lipo, then do the # same thing with the game itself. # Choices are "x86_64" or "i386" (ppc and ppc64 not supported) ARCH=${ARCH:="x86_64"} # Define compiler as "clang", this is all Mavericks supports. # gcc symlinks may still exist, but they are simply clang with # slightly different config, which confuses build scripts. # llvm-gcc and gcc 4.2 are no longer supported by SpiderMonkey. export CC=${CC:="clang"} CXX=${CXX:="clang++"} export MIN_OSX_VERSION=${MIN_OSX_VERSION:="10.9"} # The various libs offer inconsistent configure options, some allow # setting sysroot and OS X-specific options, others don't. Adding to # the confusion, Apple moved /Developer/SDKs into the Xcode app bundle # so the path can't be guessed by clever build tools (like Boost.Build). # Sometimes configure gets it wrong anyway, especially on cross compiles. # This is why we prefer using (OBJ)CFLAGS, (OBJ)CXXFLAGS, and LDFLAGS. # Check if SYSROOT is set and not empty if [[ $SYSROOT && ${SYSROOT-_} ]]; then C_FLAGS="-isysroot $SYSROOT" LDFLAGS="$LDFLAGS -Wl,-syslibroot,$SYSROOT" fi # Check if MIN_OSX_VERSION is set and not empty if [[ $MIN_OSX_VERSION && ${MIN_OSX_VERSION-_} ]]; then C_FLAGS="$C_FLAGS -mmacosx-version-min=$MIN_OSX_VERSION" # clang and llvm-gcc look at mmacosx-version-min to determine link target # and CRT version, and use it to set the macosx_version_min linker flag LDFLAGS="$LDFLAGS -mmacosx-version-min=$MIN_OSX_VERSION" fi # Force using libc++ since it has better C++11 support required by the game # but pre-Mavericks still use libstdc++ by default # Also enable c++0x for consistency with the game build C_FLAGS="$C_FLAGS -arch $ARCH -fvisibility=hidden" LDFLAGS="$LDFLAGS -arch $ARCH -stdlib=libc++" CFLAGS="$CFLAGS $C_FLAGS" CXXFLAGS="$CXXFLAGS $C_FLAGS -stdlib=libc++ -std=c++0x" OBJCFLAGS="$OBJCFLAGS $C_FLAGS" OBJCXXFLAGS="$OBJCXXFLAGS $C_FLAGS" JOBS=${JOBS:="-j2"} set -e die() { echo ERROR: $* exit 1 } download_lib() { local url=$1 local filename=$2 if [ ! -e $filename ]; then echo "Downloading $filename" curl -fLo ${filename} ${url}${filename} || die "Download of $url$filename failed" fi } already_built() { echo -e "Skipping - already built (use --force-rebuild to override)" } # Check that we're actually on OS X if [ "`uname -s`" != "Darwin" ]; then die "This script is intended for OS X only" fi # Parse command-line options: force_rebuild=false for i in "$@" do case $i in --force-rebuild ) force_rebuild=true;; -j* ) JOBS=$i ;; esac done cd "$(dirname $0)" # Now in libraries/osx/ (where we assume this script resides) # -------------------------------------------------------------- echo -e "Building zlib..." LIB_VERSION="${ZLIB_VERSION}" LIB_ARCHIVE="$LIB_VERSION.tar.gz" LIB_DIRECTORY=$LIB_VERSION LIB_URL="http://zlib.net/" mkdir -p zlib pushd zlib > /dev/null ZLIB_DIR="$(pwd)" if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ .already-built -ot $LIB_DIRECTORY ]] then rm -f .already-built download_lib $LIB_URL $LIB_ARCHIVE rm -rf $LIB_DIRECTORY include lib share tar -xf $LIB_ARCHIVE pushd $LIB_DIRECTORY # patch zlib's configure script to use our CFLAGS and LDFLAGS (patch -Np0 -i ../../patches/zlib_flags.diff \ && CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" \ ./configure --prefix="$ZLIB_DIR" \ --static \ && make ${JOBS} && make install) || die "zlib build failed" popd touch .already-built else already_built fi popd > /dev/null # -------------------------------------------------------------- echo -e "Building libcurl..." LIB_VERSION="${CURL_VERSION}" LIB_ARCHIVE="$LIB_VERSION.tar.bz2" LIB_DIRECTORY="$LIB_VERSION" LIB_URL="http://curl.haxx.se/download/" mkdir -p libcurl pushd libcurl > /dev/null if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ .already-built -ot $LIB_DIRECTORY ]] then INSTALL_DIR="$(pwd)" rm -f .already-built download_lib $LIB_URL $LIB_ARCHIVE rm -rf $LIB_DIRECTORY bin include lib share tar -xf $LIB_ARCHIVE pushd $LIB_DIRECTORY (./configure CFLAGS="$CFLAGS" \ LDFLAGS="$LDFLAGS" \ --prefix="$INSTALL_DIR" \ --enable-ipv6 \ --with-darwinssl \ --without-gssapi \ --without-libmetalink \ --without-libpsl \ --without-librtmp \ --without-libssh2 \ --without-nghttp2 \ --without-nss \ --without-polarssl \ --without-ssl \ --without-gnutls \ --without-brotli \ --without-cyassl \ --without-winssl \ --without-mbedtls \ --without-wolfssl \ --without-spnego \ --disable-ares \ --disable-ldap \ --disable-ldaps \ --without-libidn2 \ --with-zlib="${ZLIB_DIR}" \ --enable-shared=no \ && make ${JOBS} && make install) || die "libcurl build failed" popd touch .already-built else already_built fi popd > /dev/null # -------------------------------------------------------------- echo -e "Building libiconv..." LIB_VERSION="${ICONV_VERSION}" LIB_ARCHIVE="$LIB_VERSION.tar.gz" LIB_DIRECTORY="$LIB_VERSION" LIB_URL="http://ftp.gnu.org/pub/gnu/libiconv/" mkdir -p iconv pushd iconv > /dev/null ICONV_DIR="$(pwd)" if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ .already-built -ot $LIB_DIRECTORY ]] then rm -f .already-built download_lib $LIB_URL $LIB_ARCHIVE rm -rf $LIB_DIRECTORY bin include lib share tar -xf $LIB_ARCHIVE pushd $LIB_DIRECTORY (./configure CFLAGS="$CFLAGS" \ LDFLAGS="$LDFLAGS" \ --prefix="$ICONV_DIR" \ --without-libiconv-prefix \ --without-libintl-prefix \ --disable-nls \ --enable-shared=no \ && make ${JOBS} && make install) || die "libiconv build failed" popd touch .already-built else already_built fi popd > /dev/null # -------------------------------------------------------------- echo -e "Building libxml2..." LIB_VERSION="${XML2_VERSION}" LIB_ARCHIVE="$LIB_VERSION.tar.gz" LIB_DIRECTORY="$LIB_VERSION" LIB_URL="ftp://xmlsoft.org/libxml2/" mkdir -p libxml2 pushd libxml2 > /dev/null if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ .already-built -ot $LIB_DIRECTORY ]] then INSTALL_DIR="$(pwd)" rm -f .already-built download_lib $LIB_URL $LIB_ARCHIVE rm -rf $LIB_DIRECTORY bin include lib share tar -xf $LIB_ARCHIVE pushd $LIB_DIRECTORY (./configure CFLAGS="$CFLAGS" \ LDFLAGS="$LDFLAGS" \ --prefix="$INSTALL_DIR" \ --without-lzma \ --without-python \ --with-iconv="${ICONV_DIR}" \ --with-zlib="${ZLIB_DIR}" \ --enable-shared=no \ && make ${JOBS} && make install) || die "libxml2 build failed" popd touch .already-built else already_built fi popd > /dev/null # -------------------------------------------------------------- echo -e "Building SDL2..." LIB_VERSION="${SDL2_VERSION}" LIB_ARCHIVE="$LIB_VERSION.tar.gz" LIB_DIRECTORY=$LIB_VERSION LIB_URL="https://libsdl.org/release/" mkdir -p sdl2 pushd sdl2 > /dev/null if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ .already-built -ot $LIB_DIRECTORY ]] then INSTALL_DIR="$(pwd)" rm -f .already-built download_lib $LIB_URL $LIB_ARCHIVE rm -rf $LIB_DIRECTORY bin include lib share tar -xf $LIB_ARCHIVE pushd $LIB_DIRECTORY # We don't want SDL2 to pull in system iconv, force it to detect ours with flags. # Don't use X11 - we don't need it and Mountain Lion removed it (./configure CPPFLAGS="-I${ICONV_DIR}/include" \ CFLAGS="$CFLAGS" \ CXXFLAGS="$CXXFLAGS" \ LDFLAGS="$LDFLAGS -L${ICONV_DIR}/lib" \ --prefix="$INSTALL_DIR" \ --disable-video-x11 \ --without-x \ --enable-shared=no \ && make $JOBS && make install) || die "SDL2 build failed" popd touch .already-built else already_built fi popd > /dev/null # -------------------------------------------------------------- echo -e "Building Boost..." LIB_VERSION="${BOOST_VERSION}" LIB_ARCHIVE="$LIB_VERSION.tar.bz2" LIB_DIRECTORY="$LIB_VERSION" LIB_URL="http://download.sourceforge.net/boost/" mkdir -p boost pushd boost > /dev/null if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ .already-built -ot $LIB_DIRECTORY ]] then INSTALL_DIR="$(pwd)" rm -f .already-built download_lib $LIB_URL $LIB_ARCHIVE rm -rf $LIB_DIRECTORY include lib tar -xf $LIB_ARCHIVE pushd $LIB_DIRECTORY # Can't use macosx-version, see above comment. (./bootstrap.sh --with-libraries=filesystem,system \ --prefix=$INSTALL_DIR \ && ./b2 cflags="$CFLAGS" \ toolset=clang \ cxxflags="$CXXFLAGS" \ linkflags="$LDFLAGS" ${JOBS} \ -d2 \ --layout=tagged \ --debug-configuration \ link=static \ threading=multi \ variant=release,debug install \ ) || die "Boost build failed" popd touch .already-built else already_built fi popd > /dev/null # -------------------------------------------------------------- # TODO: This build takes ages, anything we can exclude? echo -e "Building wxWidgets..." LIB_VERSION="${WXWIDGETS_VERSION}" LIB_ARCHIVE="$LIB_VERSION.tar.bz2" LIB_DIRECTORY="$LIB_VERSION" LIB_URL="http://github.com/wxWidgets/wxWidgets/releases/download/v3.0.3.1/" mkdir -p wxwidgets pushd wxwidgets > /dev/null if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ .already-built -ot $LIB_DIRECTORY ]] then INSTALL_DIR="$(pwd)" rm -f .already-built download_lib $LIB_URL $LIB_ARCHIVE rm -rf $LIB_DIRECTORY bin include lib share tar -xf $LIB_ARCHIVE pushd $LIB_DIRECTORY mkdir -p build-release pushd build-release CONF_OPTS="--prefix=$INSTALL_DIR --disable-shared --enable-macosx_arch=$ARCH --enable-unicode --with-cocoa --with-opengl --with-libiconv-prefix=${ICONV_DIR} --with-expat=builtin --with-libpng=builtin --without-libtiff --without-sdl --without-x --disable-webview --disable-webkit --disable-webviewwebkit --disable-webviewie --without-libjpeg" # wxWidgets configure now defaults to targeting 10.5, if not specified, # but that conflicts with our flags if [[ $MIN_OSX_VERSION && ${MIN_OSX_VERSION-_} ]]; then CONF_OPTS="$CONF_OPTS --with-macosx-version-min=$MIN_OSX_VERSION" fi (../configure CFLAGS="$CFLAGS" \ CXXFLAGS="$CXXFLAGS" \ CPPFLAGS="-stdlib=libc++ -D__ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES=1" \ LDFLAGS="$LDFLAGS" $CONF_OPTS \ && make ${JOBS} && make install) || die "wxWidgets build failed" popd popd touch .already-built else already_built fi popd > /dev/null # -------------------------------------------------------------- echo -e "Building libpng..." LIB_VERSION="${PNG_VERSION}" LIB_ARCHIVE="$LIB_VERSION.tar.gz" LIB_DIRECTORY="$LIB_VERSION" LIB_URL="http://download.sourceforge.net/libpng/" mkdir -p libpng pushd libpng > /dev/null if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ .already-built -ot $LIB_DIRECTORY ]] then INSTALL_DIR="$(pwd)" rm -f .already-built download_lib $LIB_URL $LIB_ARCHIVE rm -rf $LIB_DIRECTORY bin include lib share tar -xf $LIB_ARCHIVE pushd $LIB_DIRECTORY (./configure CFLAGS="$CFLAGS" \ LDFLAGS="$LDFLAGS" \ --prefix=$INSTALL_DIR \ --enable-shared=no \ && make ${JOBS} && make install) || die "libpng build failed" popd touch .already-built else already_built fi popd > /dev/null # -------------------------------------------------------------- echo -e "Building libogg..." LIB_VERSION="${OGG_VERSION}" LIB_ARCHIVE="$LIB_VERSION.tar.gz" LIB_DIRECTORY="$LIB_VERSION" LIB_URL="http://downloads.xiph.org/releases/ogg/" # Dependency of vorbis # we can install them in the same directory for convenience mkdir -p libogg mkdir -p vorbis pushd libogg > /dev/null OGG_DIR="$(pwd)" if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ .already-built -ot $LIB_DIRECTORY ]] then rm -f .already-built download_lib $LIB_URL $LIB_ARCHIVE rm -rf $LIB_DIRECTORY include lib share tar -xf $LIB_ARCHIVE pushd $LIB_DIRECTORY (./configure CFLAGS="$CFLAGS" \ LDFLAGS="$LDFLAGS" \ --prefix=$OGG_DIR \ --enable-shared=no \ && make ${JOBS} && make install) || die "libogg build failed" popd touch .already-built else already_built fi popd > /dev/null # -------------------------------------------------------------- echo -e "Building libvorbis..." LIB_VERSION="${VORBIS_VERSION}" LIB_ARCHIVE="$LIB_VERSION.tar.gz" LIB_DIRECTORY="$LIB_VERSION" LIB_URL="http://downloads.xiph.org/releases/vorbis/" pushd vorbis > /dev/null if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ .already-built -ot $LIB_DIRECTORY ]] then INSTALL_DIR="$(pwd)" rm -f .already-built download_lib $LIB_URL $LIB_ARCHIVE rm -rf $LIB_DIRECTORY include lib share tar -xf $LIB_ARCHIVE pushd $LIB_DIRECTORY (./configure CFLAGS="$CFLAGS" \ LDFLAGS="$LDFLAGS" \ --prefix="$INSTALL_DIR" \ --enable-shared=no \ --with-ogg="$OGG_DIR" \ && make ${JOBS} && make install) || die "libvorbis build failed" popd touch .already-built else already_built fi popd > /dev/null # -------------------------------------------------------------- echo -e "Building GMP..." LIB_VERSION="${GMP_VERSION}" LIB_ARCHIVE="$LIB_VERSION.tar.bz2" LIB_DIRECTORY="$LIB_VERSION" LIB_URL="https://gmplib.org/download/gmp/" mkdir -p gmp pushd gmp > /dev/null GMP_DIR="$(pwd)" if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ .already-built -ot $LIB_DIRECTORY ]] then INSTALL_DIR="$(pwd)" rm -f .already-built download_lib $LIB_URL $LIB_ARCHIVE rm -rf $LIB_DIRECTORY bin include lib tar -xf $LIB_ARCHIVE pushd $LIB_DIRECTORY # NOTE: enable-fat in this case allows building and running on different CPUS. # Otherwise CPU-specific instructions will be used with no fallback for older CPUs. (./configure CFLAGS="$CFLAGS" \ CXXFLAGS="$CXXFLAGS" \ LDFLAGS="$LDFLAGS" \ --prefix="$INSTALL_DIR" \ --enable-fat \ --disable-shared \ --with-pic \ && make ${JOBS} && make install) || die "GMP build failed" popd touch .already-built else already_built fi popd > /dev/null # -------------------------------------------------------------- echo -e "Building Nettle..." LIB_VERSION="${NETTLE_VERSION}" LIB_ARCHIVE="$LIB_VERSION.tar.gz" LIB_DIRECTORY="$LIB_VERSION" LIB_URL="https://ftp.gnu.org/gnu/nettle/" mkdir -p nettle pushd nettle > /dev/null NETTLE_DIR="$(pwd)" if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ .already-built -ot $LIB_DIRECTORY ]] then INSTALL_DIR="$(pwd)" rm -f .already-built download_lib $LIB_URL $LIB_ARCHIVE rm -rf $LIB_DIRECTORY bin include lib tar -xf $LIB_ARCHIVE pushd $LIB_DIRECTORY # NOTE: enable-fat in this case allows building and running on different CPUS. # Otherwise CPU-specific instructions will be used with no fallback for older CPUs. (./configure CFLAGS="$CFLAGS" \ CXXFLAGS="$CXXFLAGS" \ LDFLAGS="$LDFLAGS" \ --with-include-path="${GMP_DIR}/include" \ --with-lib-path="${GMP_DIR}/lib" \ --prefix="$INSTALL_DIR" \ --enable-fat \ --disable-shared \ --disable-documentation \ --disable-openssl \ --disable-assembler \ && make ${JOBS} && make install) || die "Nettle build failed" popd touch .already-built else already_built fi popd > /dev/null # -------------------------------------------------------------- echo -e "Building GnuTLS..." LIB_VERSION="${GNUTLS_VERSION}" LIB_ARCHIVE="$LIB_VERSION.tar.xz" LIB_DIRECTORY="$LIB_VERSION" LIB_URL="https://www.gnupg.org/ftp/gcrypt/gnutls/v3.6/" mkdir -p gnutls pushd gnutls > /dev/null GNUTLS_DIR="$(pwd)" if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ .already-built -ot $LIB_DIRECTORY ]] then INSTALL_DIR="$(pwd)" rm -f .already-built download_lib $LIB_URL $LIB_ARCHIVE rm -rf $LIB_DIRECTORY bin include lib tar -xf $LIB_ARCHIVE pushd $LIB_DIRECTORY # GnuTLS 3.6.8 added the TCP Fast Open feature, which requires connectx # but that's only available on OS X 10.11+ (GnuTLS doesn't support SDK based builds yet) # So we disable that functionality (patch -Np0 -i ../../patches/gnutls-disable-tcpfastopen.diff \ && ./configure CFLAGS="$CFLAGS" \ CXXFLAGS="$CXXFLAGS" \ LDFLAGS="$LDFLAGS" \ LIBS="-L${GMP_DIR}/lib -lgmp" \ NETTLE_CFLAGS="-I${NETTLE_DIR}/include" \ NETTLE_LIBS="-L${NETTLE_DIR}/lib -lnettle" \ HOGWEED_CFLAGS="-I${NETTLE_DIR}/include" \ HOGWEED_LIBS="-L${NETTLE_DIR}/lib -lhogweed" \ GMP_CFLAGS="-I${GMP_DIR}/include" \ GMP_LIBS="-L${GMP_DIR}/lib -lgmp" \ --prefix="$INSTALL_DIR" \ --enable-shared=no \ --without-idn \ --with-included-unistring \ --with-included-libtasn1 \ --without-p11-kit \ --disable-tests \ --disable-nls \ && make ${JOBS} && make install) || die "GnuTLS build failed" popd touch .already-built else already_built fi popd > /dev/null # -------------------------------------------------------------- echo -e "Building gloox..." LIB_VERSION="${GLOOX_VERSION}" LIB_ARCHIVE="$LIB_VERSION.tar.bz2" LIB_DIRECTORY="$LIB_VERSION" LIB_URL="http://camaya.net/download/" mkdir -p gloox pushd gloox > /dev/null if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ .already-built -ot $LIB_DIRECTORY ]] then INSTALL_DIR="$(pwd)" rm -f .already-built download_lib $LIB_URL $LIB_ARCHIVE rm -rf $LIB_DIRECTORY bin include lib tar -xf $LIB_ARCHIVE pushd $LIB_DIRECTORY # TODO: pulls in libresolv dependency from /usr/lib (./configure CFLAGS="$CFLAGS" \ CXXFLAGS="$CXXFLAGS" \ LDFLAGS="$LDFLAGS" \ --prefix="$INSTALL_DIR" \ GNUTLS_CFLAGS="-I${GNUTLS_DIR}/include" \ GNUTLS_LIBS="-L${GNUTLS_DIR}/lib -lgnutls" \ --enable-shared=no \ --with-zlib="${ZLIB_DIR}" \ --without-libidn \ --with-gnutls="yes" \ --without-openssl \ --without-tests \ --without-examples \ --disable-getaddrinfo \ && make ${JOBS} && make install) || die "gloox build failed" popd touch .already-built else already_built fi popd > /dev/null # -------------------------------------------------------------- -echo -e "Building NSPR..." - -LIB_VERSION="${NSPR_VERSION}" -LIB_ARCHIVE="nspr-$LIB_VERSION.tar.gz" -LIB_DIRECTORY="nspr-$LIB_VERSION" -LIB_URL="https://ftp.mozilla.org/pub/mozilla.org/nspr/releases/v$LIB_VERSION/src/" - -mkdir -p nspr -pushd nspr > /dev/null - -NSPR_DIR="$(pwd)" - -if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ .already-built -ot $LIB_DIRECTORY ]] -then - rm -f .already-built - download_lib $LIB_URL $LIB_ARCHIVE - - rm -rf $LIB_DIRECTORY bin include lib share - tar -xf $LIB_ARCHIVE - pushd $LIB_DIRECTORY/nspr - - (CFLAGS="$CFLAGS" CXXFLAGS="$CXXFLAGS" LDFLAGS="$LDFLAGS" \ - ./configure --prefix="$NSPR_DIR" \ - --enable-64bit \ - && make ${JOBS} && make install) || die "NSPR build failed" - popd - # TODO: how can we not build the dylibs? - rm -f lib/*.dylib - touch .already-built -else - already_built -fi -popd > /dev/null - -# -------------------------------------------------------------- echo -e "Building ICU..." LIB_VERSION="${ICU_VERSION}" LIB_ARCHIVE="$LIB_VERSION-src.tgz" LIB_DIRECTORY="icu" LIB_URL="http://download.icu-project.org/files/icu4c/59.1/" mkdir -p $LIB_DIRECTORY pushd icu > /dev/null if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ .already-built -ot $LIB_DIRECTORY ]] then INSTALL_DIR="$(pwd)" rm -f .already-built download_lib $LIB_URL $LIB_ARCHIVE rm -rf $LIB_DIRECTORY bin include lib sbin share tar -xf $LIB_ARCHIVE pushd $LIB_DIRECTORY mkdir -p source/build pushd source/build (CFLAGS="$CFLAGS" CXXFLAGS="$CXXFLAGS" LDFLAGS="$LDFLAGS" \ ../runConfigureICU MacOSX \ --prefix=$INSTALL_DIR \ --disable-shared \ --enable-static \ --disable-samples \ --enable-extras \ --enable-icuio \ --enable-tools \ && make ${JOBS} && make install) || die "ICU build failed" popd popd touch .already-built else already_built fi popd > /dev/null # -------------------------------------------------------------- echo -e "Building ENet..." LIB_VERSION="${ENET_VERSION}" LIB_ARCHIVE="$LIB_VERSION.tar.gz" LIB_DIRECTORY="$LIB_VERSION" LIB_URL="http://enet.bespin.org/download/" mkdir -p enet pushd enet > /dev/null if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ .already-built -ot $LIB_DIRECTORY ]] then INSTALL_DIR="$(pwd)" rm -f .already-built download_lib $LIB_URL $LIB_ARCHIVE rm -rf $LIB_DIRECTORY bin include lib sbin share tar -xf $LIB_ARCHIVE pushd $LIB_DIRECTORY (./configure CFLAGS="$CFLAGS" \ LDFLAGS="$LDFLAGS" \ --prefix=${INSTALL_DIR} \ --enable-shared=no \ && make clean && make ${JOBS} && make install) || die "ENet build failed" popd touch .already-built else already_built fi popd > /dev/null # -------------------------------------------------------------- echo -e "Building MiniUPnPc..." LIB_VERSION="${MINIUPNPC_VERSION}" LIB_ARCHIVE="$LIB_VERSION.tar.gz" LIB_DIRECTORY="$LIB_VERSION" LIB_URL="http://miniupnp.tuxfamily.org/files/download.php?file=" mkdir -p miniupnpc pushd miniupnpc > /dev/null if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ .already-built -ot $LIB_DIRECTORY ]] then INSTALL_DIR="$(pwd)" rm -f .already-built download_lib $LIB_URL $LIB_ARCHIVE rm -rf $LIB_DIRECTORY bin include lib share tar -xf $LIB_ARCHIVE pushd $LIB_DIRECTORY (make clean \ && CFLAGS=$CFLAGS LDFLAGS=$LDFLAGS make ${JOBS} \ && INSTALLPREFIX="$INSTALL_DIR" make install \ ) || die "MiniUPnPc build failed" popd # TODO: how can we not build the dylibs? rm -f lib/*.dylib touch .already-built else already_built fi popd > /dev/null # -------------------------------------------------------------- echo -e "Building libsodium..." LIB_VERSION="${SODIUM_VERSION}" LIB_ARCHIVE="$SODIUM_VERSION.tar.gz" LIB_DIRECTORY="$LIB_VERSION" LIB_URL="https://download.libsodium.org/libsodium/releases/" mkdir -p libsodium pushd libsodium > /dev/null if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ .already-built -ot $LIB_DIRECTORY ]] then INSTALL_DIR="$(pwd)" rm -f .already-built download_lib $LIB_URL $LIB_ARCHIVE rm -rf $LIB_DIRECTORY include lib tar -xf $LIB_ARCHIVE pushd $LIB_DIRECTORY (./configure CFLAGS="$CFLAGS" \ LDFLAGS="$LDFLAGS" \ --prefix=${INSTALL_DIR} \ --enable-shared=no \ && make clean \ && CFLAGS=$CFLAGS LDFLAGS=$LDFLAGS make ${JOBS} \ && make check \ && INSTALLPREFIX="$INSTALL_DIR" make install \ ) || die "libsodium build failed" popd touch .already-built else already_built fi popd > /dev/null # -------------------------------------------------------------------- # The following libraries are shared on different OSes and may # be customized, so we build and install them from bundled sources # -------------------------------------------------------------------- echo -e "Building Spidermonkey..." -LIB_VERSION="mozjs-38.2.1" -LIB_ARCHIVE="$LIB_VERSION.rc0.tar.bz2" -LIB_DIRECTORY="mozjs-38.0.0" +LIB_VERSION="mozjs-45.0.2" +LIB_ARCHIVE="$LIB_VERSION.tar.bz2" +LIB_DIRECTORY="mozjs-45.0.2" pushd ../source/spidermonkey/ > /dev/null if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ .already-built -ot $LIB_DIRECTORY ]] || [[ .already-built -ot README.txt ]] then INSTALL_DIR="$(pwd)" INCLUDE_DIR_DEBUG=$INSTALL_DIR/include-unix-debug INCLUDE_DIR_RELEASE=$INSTALL_DIR/include-unix-release rm -f .already-built rm -f lib/*.a rm -rf $LIB_DIRECTORY $INCLUDE_DIR_DEBUG $INCLUDE_DIR_RELEASE tar -xf $LIB_ARCHIVE # Apply patches pushd $LIB_DIRECTORY . ../patch.sh popd pushd $LIB_DIRECTORY/js/src - # We want separate debug/release versions of the library, so change their install name in the Makefile - perl -i.bak -pe 's/(^STATIC_LIBRARY_NAME\s+=).*/$1'\''mozjs38-ps-debug'\''/' moz.build CONF_OPTS="--target=$ARCH-apple-darwin --prefix=${INSTALL_DIR} - --with-system-nspr - --with-nspr-prefix=${NSPR_DIR} + --enable-posix-nspr-emulation --with-system-zlib=${ZLIB_DIR} --disable-tests - --disable-shared-js" + --disable-shared-js + --disable-jemalloc + --without-intl-api" # Change the default location where the tracelogger should store its output, which is /tmp/ on OSX. TLCXXFLAGS='-DTRACE_LOG_DIR="\"../../source/tools/tracelogger/\""' - # Uncomment this line for 32-bit 10.5 cross compile: - #CONF_OPTS="$CONF_OPTS --target=i386-apple-darwin9.0.0" if [[ $MIN_OSX_VERSION && ${MIN_OSX_VERSION-_} ]]; then CONF_OPTS="$CONF_OPTS --enable-macos-target=$MIN_OSX_VERSION" fi if [[ $SYSROOT && ${SYSROOT-_} ]]; then CONF_OPTS="$CONF_OPTS --with-macosx-sdk=$SYSROOT" fi + # We want separate debug/release versions of the library, so change their install name in the Makefile + perl -i.bak -pe 's/(^STATIC_LIBRARY_NAME\s+=).*/$1'\''mozjs45-ps-debug'\''/' moz.build mkdir -p build-debug pushd build-debug (CC="clang" CXX="clang++" CXXFLAGS="${TLCXXFLAGS}" AR=ar CROSS_COMPILE=1 \ ../configure $CONF_OPTS \ --enable-debug \ --disable-optimize \ --enable-js-diagnostics \ --enable-gczeal \ && make ${JOBS}) || die "Spidermonkey build failed" # js-config.h is different for debug and release builds, so we need different include directories for both mkdir -p $INCLUDE_DIR_DEBUG cp -R -L dist/include/* $INCLUDE_DIR_DEBUG/ - cp dist/lib/*.a $INSTALL_DIR/lib + cp dist/sdk/lib/*.a $INSTALL_DIR/lib popd mv moz.build.bak moz.build - perl -i.bak -pe 's/(^STATIC_LIBRARY_NAME\s+=).*/$1'\''mozjs38-ps-release'\''/' moz.build + perl -i.bak -pe 's/(^STATIC_LIBRARY_NAME\s+=).*/$1'\''mozjs45-ps-release'\''/' moz.build mkdir -p build-release pushd build-release (CC="clang" CXX="clang++" CXXFLAGS="${TLCXXFLAGS}" AR=ar CROSS_COMPILE=1 \ ../configure $CONF_OPTS \ --enable-optimize \ && make ${JOBS}) || die "Spidermonkey build failed" # js-config.h is different for debug and release builds, so we need different include directories for both mkdir -p $INCLUDE_DIR_RELEASE cp -R -L dist/include/* $INCLUDE_DIR_RELEASE/ - cp dist/lib/*.a $INSTALL_DIR/lib + cp dist/sdk/lib/*.a $INSTALL_DIR/lib + cp js/src/*.a $INSTALL_DIR/lib popd mv moz.build.bak moz.build popd touch .already-built else already_built fi popd > /dev/null # -------------------------------------------------------------- # NVTT - no install echo -e "Building NVTT..." pushd ../source/nvtt > /dev/null if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] then rm -f .already-built rm -f lib/*.a pushd src rm -rf build mkdir -p build pushd build # Could use CMAKE_OSX_DEPLOYMENT_TARGET and CMAKE_OSX_SYSROOT # but they're not as flexible for cross-compiling # Disable optional libs that we don't need (avoids some conflicts with MacPorts) (cmake .. \ -DCMAKE_LINK_FLAGS="$LDFLAGS" \ -DCMAKE_C_FLAGS="$CFLAGS" \ -DCMAKE_CXX_FLAGS="$CXXFLAGS" \ -DCMAKE_BUILD_TYPE=Release \ -DBINDIR=bin \ -DLIBDIR=lib \ -DGLUT=0 \ -DGLEW=0 \ -DCG=0 \ -DCUDA=0 \ -DOPENEXR=0 \ -DJPEG=0 \ -DPNG=0 \ -DTIFF=0 \ -G "Unix Makefiles" \ && make clean && make nvtt ${JOBS}) || die "NVTT build failed" popd mkdir -p ../lib cp build/src/nv*/libnv*.a ../lib/ cp build/src/nvtt/squish/libsquish.a ../lib/ popd touch .already-built else already_built fi popd > /dev/null # -------------------------------------------------------------- # FCollada - no install echo -e "Building FCollada..." pushd ../source/fcollada > /dev/null if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] then rm -f .already-built rm -f lib/*.a pushd src rm -rf output mkdir -p ../lib # The Makefile refers to pkg-config for libxml2, but we # don't have that (replace with xml2-config instead) sed -i.bak -e 's/pkg-config libxml-2.0/xml2-config/' Makefile (make clean && CXXFLAGS=$CXXFLAGS make ${JOBS}) || die "FCollada build failed" # Undo Makefile change mv Makefile.bak Makefile popd touch .already-built else already_built fi popd > /dev/null Index: ps/trunk/libraries/source/spidermonkey/ExportJSPropertyDescriptor.diff =================================================================== --- ps/trunk/libraries/source/spidermonkey/ExportJSPropertyDescriptor.diff (nonexistent) +++ ps/trunk/libraries/source/spidermonkey/ExportJSPropertyDescriptor.diff (revision 22627) @@ -0,0 +1,26 @@ +diff --git a/js/src/jsapi.h b/js/src/jsapi.h +index 29406243..2370457f 100644 +--- a/js/src/jsapi.h ++++ b/js/src/jsapi.h +@@ -2399,7 +2399,7 @@ JS_FreezeObject(JSContext* cx, JS::Handle obj); + + /*** Property descriptors ************************************************************************/ + +-struct JSPropertyDescriptor : public JS::Traceable { ++struct JS_PUBLIC_API(JSPropertyDescriptor) : public JS::Traceable { + JSObject* obj; + unsigned attrs; + JSGetterOp getter; +diff --git a/js/src/jspubtd.h b/js/src/jspubtd.h +index ba592525..3c185527 100644 +--- a/js/src/jspubtd.h ++++ b/js/src/jspubtd.h +@@ -97,7 +97,7 @@ struct JSFunctionSpec; + struct JSLocaleCallbacks; + struct JSObjectMap; + struct JSPrincipals; +-struct JSPropertyDescriptor; ++struct JS_PUBLIC_API(JSPropertyDescriptor); + struct JSPropertyName; + struct JSPropertySpec; + struct JSRuntime; Index: ps/trunk/libraries/source/spidermonkey/FixLinking.diff =================================================================== --- ps/trunk/libraries/source/spidermonkey/FixLinking.diff (nonexistent) +++ ps/trunk/libraries/source/spidermonkey/FixLinking.diff (revision 22627) @@ -0,0 +1,49 @@ +From dcf520da15d940c900d7e8ffd5a9b05427c54dc8 Mon Sep 17 00:00:00 2001 +From: Philip Chimento +Date: Wed, 5 Jul 2017 22:47:44 -0700 +Subject: [PATCH 4/9] headers: Fix symbols visibility + +Some symbols that need to be public are not marked as such. +--- + js/public/Utility.h | 2 +- + js/src/jsalloc.h | 4 +++- + 2 files changed, 4 insertions(+), 2 deletions(-) + +diff --git a/js/public/Utility.h b/js/public/Utility.h +index 75214c32..f50fd8dd 100644 +--- a/js/public/Utility.h ++++ b/js/public/Utility.h +@@ -77,7 +77,7 @@ enum ThreadType { + # if defined(DEBUG) || defined(JS_OOM_BREAKPOINT) + extern bool InitThreadType(void); + extern void SetThreadType(ThreadType); +-extern uint32_t GetThreadType(void); ++extern JS_FRIEND_API(uint32_t) GetThreadType(void); + # else + inline bool InitThreadType(void) { return true; } + inline void SetThreadType(ThreadType t) {}; +diff --git a/js/src/jsalloc.h b/js/src/jsalloc.h +index b9ae5190..234ea9dc 100644 +--- a/js/src/jsalloc.h ++++ b/js/src/jsalloc.h +@@ -17,6 +17,8 @@ + #include "js/TypeDecls.h" + #include "js/Utility.h" + ++extern JS_PUBLIC_API(void) JS_ReportOutOfMemory(JSContext* cx); ++ + namespace js { + + enum class AllocFunction { +@@ -130,7 +132,7 @@ class TempAllocPolicy + + bool checkSimulatedOOM() const { + if (js::oom::ShouldFailWithOOM()) { +- js::ReportOutOfMemory(reinterpret_cast(cx_)); ++ JS_ReportOutOfMemory(reinterpret_cast(cx_)); + return false; + } + +-- +2.11.0 (Apple Git-81) + Index: ps/trunk/libraries/source/spidermonkey/FixMozglueStatic.diff =================================================================== --- ps/trunk/libraries/source/spidermonkey/FixMozglueStatic.diff (nonexistent) +++ ps/trunk/libraries/source/spidermonkey/FixMozglueStatic.diff (revision 22627) @@ -0,0 +1,20 @@ +diff --git a/mozglue/build/moz.build b/mozglue/build/moz.build +index 58e2db6..1cfb504 100644 +--- a/mozglue/build/moz.build ++++ b/mozglue/build/moz.build +@@ -4,12 +4,9 @@ + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + +-# Build mozglue as a shared lib on Windows, OSX and Android. +-# If this is ever changed, update MOZ_SHARED_MOZGLUE in browser/installer/Makefile.in +-if CONFIG['OS_TARGET'] in ('WINNT', 'Darwin', 'Android'): +- SharedLibrary('mozglue') +-else: +- Library('mozglue') ++# Build mozglue as a static lib into js for 0 A.D. ++Library('mozglue') ++FINAL_LIBRARY = 'js' + + if not CONFIG['MOZ_CRT']: + SDK_LIBRARY = True Index: ps/trunk/libraries/source/spidermonkey/FixNonx86.diff =================================================================== --- ps/trunk/libraries/source/spidermonkey/FixNonx86.diff (nonexistent) +++ ps/trunk/libraries/source/spidermonkey/FixNonx86.diff (revision 22627) @@ -0,0 +1,206 @@ +diff --git a/js/src/gc/Memory.cpp b/js/src/gc/Memory.cpp +--- a/js/src/gc/Memory.cpp ++++ b/js/src/gc/Memory.cpp +@@ -430,17 +430,17 @@ InitMemorySubsystem() + if (pageSize == 0) + pageSize = allocGranularity = size_t(sysconf(_SC_PAGESIZE)); + } + + static inline void* + MapMemoryAt(void* desired, size_t length, int prot = PROT_READ | PROT_WRITE, + int flags = MAP_PRIVATE | MAP_ANON, int fd = -1, off_t offset = 0) + { +-#if defined(__ia64__) || (defined(__sparc64__) && defined(__NetBSD__)) ++#if defined(__ia64__) || (defined(__sparc64__) && defined(__NetBSD__)) || defined(__aarch64__) + MOZ_ASSERT(0xffff800000000000ULL & (uintptr_t(desired) + length - 1) == 0); + #endif + void* region = mmap(desired, length, prot, flags, fd, offset); + if (region == MAP_FAILED) + return nullptr; + /* + * mmap treats the given address as a hint unless the MAP_FIXED flag is + * used (which isn't usually what you want, as this overrides existing +@@ -480,16 +480,51 @@ MapMemory(size_t length, int prot = PROT + * as out of memory. + */ + if ((uintptr_t(region) + (length - 1)) & 0xffff800000000000) { + if (munmap(region, length)) + MOZ_ASSERT(errno == ENOMEM); + return nullptr; + } + return region; ++#elif defined(__aarch64__) ++ /* ++ * There might be similar virtual address issue on arm64 which depends on ++ * hardware and kernel configurations. But the work around is slightly ++ * different due to the different mmap behavior. ++ * ++ * TODO: Merge with the above code block if this implementation works for ++ * ia64 and sparc64. ++ */ ++ const uintptr_t start = UINT64_C(0x0000070000000000); ++ const uintptr_t end = UINT64_C(0x0000800000000000); ++ const uintptr_t step = js::gc::ChunkSize; ++ /* ++ * Optimization options if there are too many retries in practice: ++ * 1. Examine /proc/self/maps to find an available address. This file is ++ * not always available, however. In addition, even if we examine ++ * /proc/self/maps, we may still need to retry several times due to ++ * racing with other threads. ++ * 2. Use a global/static variable with lock to track the addresses we have ++ * allocated or tried. ++ */ ++ uintptr_t hint; ++ void* region = MAP_FAILED; ++ for (hint = start; region == MAP_FAILED && hint + length <= end; hint += step) { ++ region = mmap((void*)hint, length, prot, flags, fd, offset); ++ if (region != MAP_FAILED) { ++ if ((uintptr_t(region) + (length - 1)) & 0xffff800000000000) { ++ if (munmap(region, length)) { ++ MOZ_ASSERT(errno == ENOMEM); ++ } ++ region = MAP_FAILED; ++ } ++ } ++ } ++ return region == MAP_FAILED ? nullptr : region; + #else + void* region = MozTaggedAnonymousMmap(nullptr, length, prot, flags, fd, offset, "js-gc-heap"); + if (region == MAP_FAILED) + return nullptr; + return region; + #endif + } + +diff --git a/js/src/jsapi-tests/testGCAllocator.cpp b/js/src/jsapi-tests/testGCAllocator.cpp +--- a/js/src/jsapi-tests/testGCAllocator.cpp ++++ b/js/src/jsapi-tests/testGCAllocator.cpp +@@ -307,48 +307,72 @@ void* mapMemoryAt(void* desired, size_t + void* mapMemory(size_t length) { return nullptr; } + void unmapPages(void* p, size_t size) { } + + #elif defined(XP_UNIX) + + void* + mapMemoryAt(void* desired, size_t length) + { +-#if defined(__ia64__) || (defined(__sparc64__) && defined(__NetBSD__)) ++#if defined(__ia64__) || (defined(__sparc64__) && defined(__NetBSD__)) || defined(__aarch64__) + MOZ_RELEASE_ASSERT(0xffff800000000000ULL & (uintptr_t(desired) + length - 1) == 0); + #endif + void* region = mmap(desired, length, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + if (region == MAP_FAILED) + return nullptr; + if (region != desired) { + if (munmap(region, length)) + MOZ_RELEASE_ASSERT(errno == ENOMEM); + return nullptr; + } + return region; + } + + void* + mapMemory(size_t length) + { +- void* hint = nullptr; ++ int prot = PROT_READ | PROT_WRITE; ++ int flags = MAP_PRIVATE | MAP_ANON; ++ int fd = -1; ++ off_t offset = 0; ++ // The test code must be aligned with the implementation in gc/Memory.cpp. + #if defined(__ia64__) || (defined(__sparc64__) && defined(__NetBSD__)) +- hint = (void*)0x0000070000000000ULL; +-#endif +- void* region = mmap(hint, length, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); ++ void* region = mmap((void*)0x0000070000000000, length, prot, flags, fd, offset); + if (region == MAP_FAILED) + return nullptr; +-#if defined(__ia64__) || (defined(__sparc64__) && defined(__NetBSD__)) +- if ((uintptr_t(region) + (length - 1)) & 0xffff800000000000ULL) { ++ if ((uintptr_t(region) + (length - 1)) & 0xffff800000000000) { + if (munmap(region, length)) + MOZ_RELEASE_ASSERT(errno == ENOMEM); + return nullptr; + } ++ return region; ++#elif defined(__aarch64__) ++ const uintptr_t start = UINT64_C(0x0000070000000000); ++ const uintptr_t end = UINT64_C(0x0000800000000000); ++ const uintptr_t step = js::gc::ChunkSize; ++ uintptr_t hint; ++ void* region = MAP_FAILED; ++ for (hint = start; region == MAP_FAILED && hint + length <= end; hint += step) { ++ region = mmap((void*)hint, length, prot, flags, fd, offset); ++ if (region != MAP_FAILED) { ++ if ((uintptr_t(region) + (length - 1)) & 0xffff800000000000) { ++ if (munmap(region, length)) { ++ MOZ_RELEASE_ASSERT(errno == ENOMEM); ++ } ++ region = MAP_FAILED; ++ } ++ } ++ } ++ return region == MAP_FAILED ? nullptr : region; ++#else ++ void* region = mmap(nullptr, length, prot, flags, fd, offset); ++ if (region == MAP_FAILED) ++ return nullptr; ++ return region; + #endif +- return region; + } + + void + unmapPages(void* p, size_t size) + { + if (munmap(p, size)) + MOZ_RELEASE_ASSERT(errno == ENOMEM); + } + +diff --git a/js/src/jit/AtomicOperations.h b/js/src/jit/AtomicOperations.h +index 8606a18..60af775 100644 +--- a/js/src/jit/AtomicOperations.h ++++ b/js/src/jit/AtomicOperations.h +@@ -302,7 +302,9 @@ AtomicOperations::isLockfree(int32_t size) + # include "jit/mips-shared/AtomicOperations-mips-shared.h" + #elif defined(__ppc64__) || defined(__PPC64_) \ + || defined(__ppc64le__) || defined(__PPC64LE__) \ +- || defined(__ppc__) || defined(__PPC__) ++ || defined(__ppc__) || defined(__PPC__) \ ++ || defined(__aarch64__) || defined(__arm__) \ ++ || defined(__s390x__) || defined(__mips__) + # include "jit/none/AtomicOperations-ppc.h" + #elif defined(JS_CODEGEN_NONE) + # include "jit/none/AtomicOperations-none.h" + +diff --git a/js/src/tests/js1_5/Array/regress-157652.js b/js/src/tests/js1_5/Array/regress-157652.js +index 0bdba8f..9d77802 100644 +--- a/js/src/tests/js1_5/Array/regress-157652.js ++++ b/js/src/tests/js1_5/Array/regress-157652.js +@@ -1,4 +1,4 @@ +-// |reftest| skip-if(xulRuntime.XPCOMABI.match(/x86_64/)||Android) -- No test results ++// |reftest| skip-if(xulRuntime.XPCOMABI.match(/x86_64|aarch64|ppc64|ppc64le|s390x/)||Android) -- No test results + /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this +diff --git a/js/src/tests/js1_5/Array/regress-330812.js b/js/src/tests/js1_5/Array/regress-330812.js +index 3a39297..c48f4c8 100644 +--- a/js/src/tests/js1_5/Array/regress-330812.js ++++ b/js/src/tests/js1_5/Array/regress-330812.js +@@ -1,4 +1,4 @@ +-// |reftest| skip-if(xulRuntime.XPCOMABI.match(/x86_64/)||Android) -- No test results ++// |reftest| skip-if(xulRuntime.XPCOMABI.match(/x86_64|aarch64|ppc64|ppc64le|s390x/)||Android) -- No test results + /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this +diff --git a/js/src/tests/js1_5/Regress/regress-422348.js b/js/src/tests/js1_5/Regress/regress-422348.js +index f2443c2..7ae83f4 100644 +--- a/js/src/tests/js1_5/Regress/regress-422348.js ++++ b/js/src/tests/js1_5/Regress/regress-422348.js +@@ -1,4 +1,4 @@ +-// |reftest| skip-if(xulRuntime.XPCOMABI.match(/x86_64/)) -- On 64-bit, takes forever rather than throwing ++// |reftest| skip-if(xulRuntime.XPCOMABI.match(/x86_64|aarch64|ppc64|ppc64le|s390x/)) -- On 64-bit, takes forever rather than throwing + /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + Index: ps/trunk/libraries/source/spidermonkey/FixTracelogger.diff =================================================================== --- ps/trunk/libraries/source/spidermonkey/FixTracelogger.diff (revision 22626) +++ ps/trunk/libraries/source/spidermonkey/FixTracelogger.diff (revision 22627) @@ -1,565 +1,223 @@ -diff --git a/js/src/jit-test/tests/tracelogger/bug1231170.js b/js/src/jit-test/tests/tracelogger/bug1231170.js -new file mode 100644 -index 0000000..023e93e ---- /dev/null -+++ b/js/src/jit-test/tests/tracelogger/bug1231170.js -@@ -0,0 +1,3 @@ -+var du = new Debugger(); -+if (typeof du.drainTraceLogger === "function") -+ du.drainTraceLogger(); diff --git a/js/src/jit-test/tests/tracelogger/bug1266649.js b/js/src/jit-test/tests/tracelogger/bug1266649.js new file mode 100644 -index 0000000..81ae7ad +index 0000000..2878e0c --- /dev/null +++ b/js/src/jit-test/tests/tracelogger/bug1266649.js -@@ -0,0 +1,10 @@ +@@ -0,0 +1,8 @@ + +var du = new Debugger(); -+if (typeof du.setupTraceLogger === "function" && -+ typeof oomTest === 'function') -+{ ++if (typeof du.setupTraceLogger === "function") { + du.setupTraceLogger({ + Scripts: true + }) + oomTest(() => function(){}); +} -diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp -index 93e2fda..09049d6 100644 ---- a/js/src/jit/Ion.cpp -+++ b/js/src/jit/Ion.cpp -@@ -1055,6 +1055,8 @@ IonScript::Destroy(FreeOp* fop, IonScript* script) - - script->destroyCaches(); - script->unlinkFromRuntime(fop); -+ // Frees the potential event we have set. -+ script->traceLoggerScriptEvent_ = TraceLoggerEvent(); - fop->free_(script); - } - -diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp -index 26262fd..af7f313 100644 ---- a/js/src/vm/Debugger.cpp -+++ b/js/src/vm/Debugger.cpp -@@ -369,10 +369,10 @@ Debugger::Debugger(JSContext* cx, NativeObject* dbg) - objects(cx), - environments(cx), - #ifdef NIGHTLY_BUILD -- traceLoggerLastDrainedId(0), -+ traceLoggerLastDrainedSize(0), - traceLoggerLastDrainedIteration(0), - #endif -- traceLoggerScriptedCallsLastDrainedId(0), -+ traceLoggerScriptedCallsLastDrainedSize(0), - traceLoggerScriptedCallsLastDrainedIteration(0) - { - assertSameCompartment(cx, dbg); -@@ -3907,9 +3907,9 @@ Debugger::drainTraceLogger(JSContext* cx, unsigned argc, Value* vp) - size_t num; - TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime()); - bool lostEvents = logger->lostEvents(dbg->traceLoggerLastDrainedIteration, -- dbg->traceLoggerLastDrainedId); -+ dbg->traceLoggerLastDrainedSize); - EventEntry* events = logger->getEventsStartingAt(&dbg->traceLoggerLastDrainedIteration, -- &dbg->traceLoggerLastDrainedId, -+ &dbg->traceLoggerLastDrainedSize, - &num); - - RootedObject array(cx, NewDenseEmptyArray(cx)); -@@ -4002,10 +4002,10 @@ Debugger::drainTraceLoggerScriptCalls(JSContext* cx, unsigned argc, Value* vp) - size_t num; - TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime()); - bool lostEvents = logger->lostEvents(dbg->traceLoggerScriptedCallsLastDrainedIteration, -- dbg->traceLoggerScriptedCallsLastDrainedId); -+ dbg->traceLoggerScriptedCallsLastDrainedSize); - EventEntry* events = logger->getEventsStartingAt( - &dbg->traceLoggerScriptedCallsLastDrainedIteration, -- &dbg->traceLoggerScriptedCallsLastDrainedId, -+ &dbg->traceLoggerScriptedCallsLastDrainedSize, - &num); - - RootedObject array(cx, NewDenseEmptyArray(cx)); -diff --git a/js/src/vm/Debugger.h b/js/src/vm/Debugger.h -index 8cac36a..c92d685 100644 ---- a/js/src/vm/Debugger.h -+++ b/js/src/vm/Debugger.h -@@ -314,10 +314,10 @@ class Debugger : private mozilla::LinkedListElement - * lost events. - */ - #ifdef NIGHTLY_BUILD -- uint32_t traceLoggerLastDrainedId; -+ uint32_t traceLoggerLastDrainedSize; - uint32_t traceLoggerLastDrainedIteration; - #endif -- uint32_t traceLoggerScriptedCallsLastDrainedId; -+ uint32_t traceLoggerScriptedCallsLastDrainedSize; - uint32_t traceLoggerScriptedCallsLastDrainedIteration; - - class FrameRange; diff --git a/js/src/vm/TraceLogging.cpp b/js/src/vm/TraceLogging.cpp -index 6715b36..9766a6f 100644 +index ce7acc6..8f8aca4 100644 --- a/js/src/vm/TraceLogging.cpp +++ b/js/src/vm/TraceLogging.cpp -@@ -131,7 +131,7 @@ TraceLoggerThread::init() - { - if (!pointerMap.init()) - return false; -- if (!extraTextId.init()) -+ if (!textIdPayloads.init()) - return false; - if (!events.init()) - return false; -@@ -185,10 +185,10 @@ TraceLoggerThread::~TraceLoggerThread() - graph = nullptr; - } - -- for (TextIdHashMap::Range r = extraTextId.all(); !r.empty(); r.popFront()) -- js_delete(r.front().value()); -- extraTextId.finish(); -- pointerMap.finish(); -+ if (textIdPayloads.initialized()) { -+ for (TextIdHashMap::Range r = textIdPayloads.all(); !r.empty(); r.popFront()) -+ js_delete(r.front().value()); -+ } - } - - bool -@@ -287,7 +287,7 @@ TraceLoggerThread::eventText(uint32_t id) - if (id < TraceLogger_Last) - return TLTextIdString(static_cast(id)); - -- TextIdHashMap::Ptr p = extraTextId.lookup(id); -+ TextIdHashMap::Ptr p = textIdPayloads.lookup(id); - MOZ_ASSERT(p); - - return p->value()->string(); -@@ -341,13 +341,15 @@ TraceLoggerThread::extractScriptDetails(uint32_t textId, const char** filename, - TraceLoggerEventPayload* - TraceLoggerThread::getOrCreateEventPayload(TraceLoggerTextId textId) - { -- TextIdHashMap::AddPtr p = extraTextId.lookupForAdd(textId); -- if (p) -+ TextIdHashMap::AddPtr p = textIdPayloads.lookupForAdd(textId); -+ if (p) { -+ MOZ_ASSERT(p->value()->textId() == textId); // Sanity check. - return p->value(); -+ } - - TraceLoggerEventPayload* payload = js_new(textId, (char*)nullptr); - -- if (!extraTextId.add(p, textId, payload)) -+ if (!textIdPayloads.add(p, textId, payload)) - return nullptr; - - return payload; -@@ -357,8 +359,10 @@ TraceLoggerEventPayload* - TraceLoggerThread::getOrCreateEventPayload(const char* text) - { - PointerHashMap::AddPtr p = pointerMap.lookupForAdd((const void*)text); -- if (p) -+ if (p) { -+ MOZ_ASSERT(p->value()->textId() < nextTextId); // Sanity check. - return p->value(); -+ } - - size_t len = strlen(text); - char* str = js_pod_malloc(len + 1); -@@ -369,7 +373,7 @@ TraceLoggerThread::getOrCreateEventPayload(const char* text) - MOZ_ASSERT(ret == len); - MOZ_ASSERT(strlen(str) == len); - -- uint32_t textId = extraTextId.count() + TraceLogger_Last; -+ uint32_t textId = nextTextId; - - TraceLoggerEventPayload* payload = js_new(textId, str); - if (!payload) { -@@ -377,17 +381,19 @@ TraceLoggerThread::getOrCreateEventPayload(const char* text) - return nullptr; - } - -- if (!extraTextId.putNew(textId, payload)) { -+ if (!textIdPayloads.putNew(textId, payload)) { - js_delete(payload); +@@ -393,14 +393,14 @@ TraceLoggerThread::getOrCreateEventPayload(const char* text) return nullptr; } - if (!pointerMap.add(p, text, payload)) - return nullptr; - if (graph.get()) graph->addTextId(textId, str); -+ nextTextId++; -+ + nextTextId++; + + if (!pointerMap.add(p, text, payload)) + return nullptr; + return payload; } -@@ -407,9 +413,14 @@ TraceLoggerThread::getOrCreateEventPayload(TraceLoggerTextId type, const char* f +@@ -420,10 +420,13 @@ TraceLoggerThread::getOrCreateEventPayload(TraceLoggerTextId type, const char* f if (!traceLoggerState->isTextIdEnabled(type)) return getOrCreateEventPayload(type); - PointerHashMap::AddPtr p = pointerMap.lookupForAdd(ptr); -- if (p) +- if (p) { +- MOZ_ASSERT(p->value()->textId() < nextTextId); // Sanity check. - return p->value(); + PointerHashMap::AddPtr p; + if (ptr) { + p = pointerMap.lookupForAdd(ptr); + if (p) { + MOZ_ASSERT(p->value()->textId() < nextTextId); // Sanity check. + return p->value(); + } -+ } - - // Compute the length of the string to create. - size_t lenFilename = strlen(filename); -@@ -428,24 +439,28 @@ TraceLoggerThread::getOrCreateEventPayload(TraceLoggerTextId type, const char* f - MOZ_ASSERT(ret == len); - MOZ_ASSERT(strlen(str) == len); - -- uint32_t textId = extraTextId.count() + TraceLogger_Last; -+ uint32_t textId = nextTextId; - TraceLoggerEventPayload* payload = js_new(textId, str); - if (!payload) { - js_free(str); - return nullptr; } -- if (!extraTextId.putNew(textId, payload)) { -+ if (!textIdPayloads.putNew(textId, payload)) { - js_delete(payload); + // Compute the length of the string to create. +@@ -455,14 +458,16 @@ TraceLoggerThread::getOrCreateEventPayload(TraceLoggerTextId type, const char* f return nullptr; } - if (!pointerMap.add(p, ptr, payload)) - return nullptr; - if (graph.get()) graph->addTextId(textId, str); -+ nextTextId++; -+ + nextTextId++; + + if (ptr) { + if (!pointerMap.add(p, ptr, payload)) + return nullptr; + } + return payload; } -@@ -453,14 +468,14 @@ TraceLoggerEventPayload* - TraceLoggerThread::getOrCreateEventPayload(TraceLoggerTextId type, JSScript* script) - { - return getOrCreateEventPayload(type, script->filename(), script->lineno(), script->column(), -- script); -+ nullptr); - } - - TraceLoggerEventPayload* +@@ -477,7 +482,7 @@ TraceLoggerEventPayload* TraceLoggerThread::getOrCreateEventPayload(TraceLoggerTextId type, const JS::ReadOnlyCompileOptions& script) { - return getOrCreateEventPayload(type, script.filename(), script.lineno, script.column, &script); + return getOrCreateEventPayload(type, script.filename(), script.lineno, script.column, nullptr); } void -@@ -485,7 +500,7 @@ TraceLoggerThread::startEvent(uint32_t id) - if (!traceLoggerState->isTextIdEnabled(id)) - return; - -- logTimestamp(id); -+ log(id); - } - - void -@@ -510,7 +525,7 @@ TraceLoggerThread::stopEvent(uint32_t id) - if (!traceLoggerState->isTextIdEnabled(id)) - return; - -- logTimestamp(TraceLogger_Stop); -+ log(TraceLogger_Stop); - } - - void -@@ -522,23 +537,57 @@ TraceLoggerThread::logTimestamp(TraceLoggerTextId id) - void - TraceLoggerThread::logTimestamp(uint32_t id) - { -+ MOZ_ASSERT(id > TraceLogger_LastTreeItem && id < TraceLogger_Last); -+ log(id); -+} -+ -+void -+TraceLoggerThread::log(uint32_t id) -+{ - if (enabled == 0) +@@ -550,19 +555,42 @@ TraceLoggerThread::log(uint32_t id) return; MOZ_ASSERT(traceLoggerState); - if (!events.ensureSpaceBeforeAdd()) { -+ -+ // We request for 3 items to add, since if we don't have enough room -+ // we record the time it took to make more place. To log this information -+ // we need 2 extra free entries. + if (!events.hasSpaceForAdd(3)) { uint64_t start = rdtsc() - traceLoggerState->startupTime; - if (graph.get()) - graph->log(events); + if (!events.ensureSpaceBeforeAdd(3)) { + if (graph.get()) + graph->log(events); -+ + +- iteration_++; +- events.clear(); + iteration_++; + events.clear(); + + // Remove the item in the pointerMap for which the payloads + // have no uses anymore + for (PointerHashMap::Enum e(pointerMap); !e.empty(); e.popFront()) { + if (e.front().value()->uses() != 0) + continue; + + TextIdHashMap::Ptr p = textIdPayloads.lookup(e.front().value()->textId()); + MOZ_ASSERT(p); + textIdPayloads.remove(p); + + e.removeFront(); + } - -- iteration_++; -- events.clear(); ++ + // Free all payloads that have no uses anymore. + for (TextIdHashMap::Enum e(textIdPayloads); !e.empty(); e.popFront()) { + if (e.front().value()->uses() == 0) { + js_delete(e.front().value()); + e.removeFront(); + } + } + } // Log the time it took to flush the events as being from the // Tracelogger. if (graph.get()) { - MOZ_ASSERT(events.capacity() > 2); -+ MOZ_ASSERT(events.hasSpaceForAdd(2)); ++ MOZ_ASSERT(events.capacity() - events.size() > 2); EventEntry& entryStart = events.pushUninitialized(); entryStart.time = start; entryStart.textId = TraceLogger_Internal; -@@ -548,13 +597,6 @@ TraceLoggerThread::logTimestamp(uint32_t id) +@@ -572,26 +600,6 @@ TraceLoggerThread::log(uint32_t id) entryStop.textId = TraceLogger_Stop; } -- // Free all TextEvents that have no uses anymore. -- for (TextIdHashMap::Enum e(extraTextId); !e.empty(); e.popFront()) { +- // Remove the item in the pointerMap for which the payloads +- // have no uses anymore +- for (PointerHashMap::Enum e(pointerMap); !e.empty(); e.popFront()) { +- if (e.front().value()->uses() != 0) +- continue; +- +- TextIdHashMap::Ptr p = textIdPayloads.lookup(e.front().value()->textId()); +- MOZ_ASSERT(p); +- textIdPayloads.remove(p); +- +- e.removeFront(); +- } +- +- // Free all payloads that have no uses anymore. +- for (TextIdHashMap::Enum e(textIdPayloads); !e.empty(); e.popFront()) { - if (e.front().value()->uses() == 0) { - js_delete(e.front().value()); - e.removeFront(); - } - } } uint64_t time = rdtsc() - traceLoggerState->startupTime; -@@ -956,3 +998,16 @@ TraceLoggerEvent::~TraceLoggerEvent() - if (payload_) - payload_->release(); - } -+ -+TraceLoggerEvent& -+TraceLoggerEvent::operator=(const TraceLoggerEvent& other) -+{ -+ if (hasPayload()) -+ payload()->release(); -+ if (other.hasPayload()) -+ other.payload()->use(); -+ -+ payload_ = other.payload_; -+ -+ return *this; -+} diff --git a/js/src/vm/TraceLogging.h b/js/src/vm/TraceLogging.h -index a124dcb..91a1eb0 100644 +index 270478c..313950e 100644 --- a/js/src/vm/TraceLogging.h +++ b/js/src/vm/TraceLogging.h -@@ -110,6 +110,9 @@ class TraceLoggerEvent { - bool hasPayload() const { - return !!payload_; - } -+ -+ TraceLoggerEvent& operator=(const TraceLoggerEvent& other); -+ TraceLoggerEvent(const TraceLoggerEvent& event) = delete; - }; - - /** -@@ -130,6 +133,10 @@ class TraceLoggerEventPayload { - uses_(0) - { } +@@ -242,7 +242,7 @@ class TraceLoggerThread -+ ~TraceLoggerEventPayload() { -+ MOZ_ASSERT(uses_ == 0); -+ } -+ - uint32_t textId() { - return textId_; - } -@@ -166,7 +173,8 @@ class TraceLoggerThread - mozilla::UniquePtr graph; - - PointerHashMap pointerMap; -- TextIdHashMap extraTextId; -+ TextIdHashMap textIdPayloads; -+ uint32_t nextTextId; - - ContinuousSpace events; - -@@ -181,6 +189,7 @@ class TraceLoggerThread - : enabled(0), - failed(false), - graph(), -+ nextTextId(TraceLogger_Last), - iteration_(0), - top(nullptr) - { } -@@ -195,22 +204,22 @@ class TraceLoggerThread - bool enable(JSContext* cx); - bool disable(); - -- // Given the previous iteration and lastEntryId, return an array of events -+ // Given the previous iteration and size, return an array of events - // (there could be lost events). At the same time update the iteration and -- // lastEntry and gives back how many events there are. -- EventEntry* getEventsStartingAt(uint32_t* lastIteration, uint32_t* lastEntryId, size_t* num) { -+ // size and gives back how many events there are. -+ EventEntry* getEventsStartingAt(uint32_t* lastIteration, uint32_t* lastSize, size_t* num) { - EventEntry* start; - if (iteration_ == *lastIteration) { -- MOZ_ASSERT(events.lastEntryId() >= *lastEntryId); -- *num = events.lastEntryId() - *lastEntryId; -- start = events.data() + *lastEntryId + 1; -+ MOZ_ASSERT(*lastSize <= events.size()); -+ *num = events.size() - *lastSize; -+ start = events.data() + *lastSize; - } else { -- *num = events.lastEntryId() + 1; -+ *num = events.size(); - start = events.data(); - } - - *lastIteration = iteration_; -- *lastEntryId = events.lastEntryId(); -+ *lastSize = events.size(); - return start; - } - -@@ -220,16 +229,16 @@ class TraceLoggerThread - const char** lineno, size_t* lineno_len, const char** colno, - size_t* colno_len); - -- bool lostEvents(uint32_t lastIteration, uint32_t lastEntryId) { -+ bool lostEvents(uint32_t lastIteration, uint32_t lastSize) { - // If still logging in the same iteration, there are no lost events. - if (lastIteration == iteration_) { -- MOZ_ASSERT(lastEntryId <= events.lastEntryId()); -+ MOZ_ASSERT(lastSize <= events.size()); - return false; - } - -- // When proceeded to the next iteration and lastEntryId points to -- // the maximum capacity there are no logs that are lost. -- if (lastIteration + 1 == iteration_ && lastEntryId == events.capacity()) -+ // If we are in a consecutive iteration we are only sure we didn't lose any events, -+ // when the lastSize equals the maximum size 'events' can get. -+ if (lastIteration == iteration_ - 1 && lastSize == events.maxSize()) + // If we are in a consecutive iteration we are only sure we didn't lose any events, + // when the lastSize equals the maximum size 'events' can get. +- if (lastIteration == iteration_ - 1 && lastSize == CONTINUOUSSPACE_LIMIT) ++ if (lastIteration == iteration_ - 1 && lastSize == events.max_items()) return false; return true; -@@ -268,6 +277,7 @@ class TraceLoggerThread - void stopEvent(uint32_t id); - private: - void stopEvent(); -+ void log(uint32_t id); - - public: - static unsigned offsetOfEnabled() { -diff --git a/js/src/vm/TraceLoggingGraph.cpp b/js/src/vm/TraceLoggingGraph.cpp -index d1b7f2e..a4eb273 100644 ---- a/js/src/vm/TraceLoggingGraph.cpp -+++ b/js/src/vm/TraceLoggingGraph.cpp -@@ -276,7 +276,7 @@ TraceLoggerGraph::flush() - if (bytesWritten < tree.size()) - return false; - -- treeOffset += tree.lastEntryId(); -+ treeOffset += tree.size(); - tree.clear(); - } - -@@ -359,7 +359,7 @@ TraceLoggerGraph::startEventInternal(uint32_t id, uint64_t timestamp) - - if (parent.lastChildId() == 0) { - MOZ_ASSERT(!entry.hasChildren()); -- MOZ_ASSERT(parent.treeId() == tree.lastEntryId() + treeOffset); -+ MOZ_ASSERT(parent.treeId() == treeOffset + tree.size() - 1); - - if (!updateHasChildren(parent.treeId())) - return false; diff --git a/js/src/vm/TraceLoggingTypes.h b/js/src/vm/TraceLoggingTypes.h -index f1c9d0c..10b76d6 100644 +index bb8ccc7..1d98bf6 100644 --- a/js/src/vm/TraceLoggingTypes.h +++ b/js/src/vm/TraceLoggingTypes.h -@@ -21,7 +21,6 @@ - _(Internal) \ - _(Interpreter) \ - _(InlinedScripts) \ -- _(Invalidation) \ - _(IonCompilation) \ - _(IonCompilationPaused) \ - _(IonLinking) \ -@@ -60,6 +59,7 @@ - - #define TRACELOGGER_LOG_ITEMS(_) \ - _(Bailout) \ -+ _(Invalidation) \ - _(Disable) \ - _(Enable) \ - _(Stop) -@@ -130,6 +130,9 @@ class ContinuousSpace { +@@ -130,15 +130,15 @@ TLTextIdIsTreeEvent(uint32_t id) + id >= TraceLogger_Last; + } + +-// The maximum amount of ram memory a continuous space structure can take (in bytes). +-static const uint32_t CONTINUOUSSPACE_LIMIT = 200 * 1024 * 1024; +- + template + class ContinuousSpace { + T* data_; uint32_t size_; uint32_t capacity_; + // The maximum amount of ram memory a continuous space structure can take (in bytes). + static const uint32_t LIMIT = 200 * 1024 * 1024; + public: ContinuousSpace () : data_(nullptr) -@@ -151,6 +154,10 @@ class ContinuousSpace { +@@ -160,6 +160,10 @@ class ContinuousSpace { data_ = nullptr; } -+ static uint32_t maxSize() { ++ uint32_t max_items() { + return LIMIT / sizeof(T); + } + T* data() { return data_; } -@@ -187,11 +194,14 @@ class ContinuousSpace { - if (hasSpaceForAdd(count)) +@@ -197,11 +201,14 @@ class ContinuousSpace { return true; -+ // Limit the size of a continuous buffer. -+ if (size_ + count > maxSize()) -+ return false; -+ uint32_t nCapacity = capacity_ * 2; -- if (size_ + count > nCapacity) -- nCapacity = size_ + count; -- T* entries = (T*) js_realloc(data_, nCapacity * sizeof(T)); -+ nCapacity = (nCapacity < maxSize()) ? nCapacity : maxSize(); - -+ T* entries = (T*) js_realloc(data_, nCapacity * sizeof(T)); - if (!entries) - return false; +- if (size_ + count > nCapacity || nCapacity * sizeof(T) > CONTINUOUSSPACE_LIMIT) { ++ if (size_ + count > nCapacity) + nCapacity = size_ + count; + ++ if (nCapacity > max_items()) { ++ nCapacity = max_items(); ++ + // Limit the size of a continuous buffer. +- if (nCapacity * sizeof(T) > CONTINUOUSSPACE_LIMIT) ++ if (size_ + count > nCapacity) + return false; + } Index: ps/trunk/libraries/source/spidermonkey/FixZLibMozBuild.diff =================================================================== --- ps/trunk/libraries/source/spidermonkey/FixZLibMozBuild.diff (revision 22626) +++ ps/trunk/libraries/source/spidermonkey/FixZLibMozBuild.diff (revision 22627) @@ -1,11 +1,11 @@ --- a/config/external/zlib/moz.build +++ b/config/external/zlib/moz.build @@ -15,7 +15,7 @@ # USE_LIBS += [ # 'mozglue' # ] pass DIRS += [ - '../../../modules/zlib', -+ '../../../modules/zlib/src', ++ '../../../modules/src', ] Index: ps/trunk/libraries/source/spidermonkey/README.txt =================================================================== --- ps/trunk/libraries/source/spidermonkey/README.txt (revision 22626) +++ ps/trunk/libraries/source/spidermonkey/README.txt (revision 22627) @@ -1,61 +1,55 @@ Important notice: ----------------- This version of SpiderMonkey comes from -https://people.mozilla.org/~sstangl/mozjs-38.2.1.rc0.tar.bz2 +https://ftp.mozilla.org/pub/spidermonkey/releases/45.0.2/mozjs-45.0.2.tar.bz2 The game must be compiled with precisely this version since SpiderMonkey does not guarantee API stability and may have behavioural changes that cause subtle bugs or network out-of-sync errors. A standard system-provided version of the library may only be used if it's exactly the same version or if it's another minor release that does not change the behaviour of the scripts executed by SpiderMonkey. Also it's crucial that "--enable-gcgenerational" was used for building the system provided libraries and that exact stack rooting was not disabled. Using different settings for compiling SpiderMonkey and 0 A.D. causes incompatibilities on the ABI (binary) level and can lead to crashes at runtime! Building on Linux: ------------------ -To build SpiderMonkey for use in 0 A.D. on Linux, you need libnspr4-dev, which -should be installed from the distribution's package management system. -As an alternative you can build nspr yourself, but we don't provide a guide for -that here. When you have nspr, just run build.sh. - -NSPR is available here: -https://ftp.mozilla.org/pub/mozilla.org/nspr/releases/ +To build SpiderMonkey for use in 0 A.D. on Linux, just run build.sh. Building on Mac OS X: --------------------- Use the build-osx-libs.sh script in libraries/osx. Building on Windows: -------------------- We provide precompiled binaries for Windows. If you still need to build on Windows, here's a short guide. Setting up the build environment: 1. Get https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Build_Instructions/Windows_Prerequisites#MozillaBuild Building NSPR: -1. Get nspr. We are using nspr-4.12 which is the newest version at the moment. +1. Get nspr. We are using nspr-4.12 which was the newest version when SM45.0.2 was out. Newer versions should probably work too. Download link: https://ftp.mozilla.org/pub/mozilla.org/nspr/releases/ -2. Run mozillabuild (start-shell-msvc2013.bat) as administrator +2. Run mozillabuild (start-shell-msvc2015.bat) as administrator 3. Extract nspr to libraries/source/spidermonkey tar -xzvf nspr-4.12.tar.gz cd nspr-4.12 cd nspr 4. Patch nspr with https://bugzilla.mozilla.org/show_bug.cgi?id=1238154#c7 5. Call configure. I've used this command: ./configure --disable-debug --enable-optimize --enable-win32-target=WIN95 6. Call make Building SpiderMonkey: 1. Adjust the absolute paths to nspr in the build.sh file to match your environment. -2. Run mozillabuild (start-shell-msvc2013.bat) as administrator and run ./build.sh. +2. Run mozillabuild (start-shell-msvc2015.bat) as administrator and run ./build.sh. Index: ps/trunk/libraries/source/spidermonkey/RemoveNSPRDependency.diff =================================================================== --- ps/trunk/libraries/source/spidermonkey/RemoveNSPRDependency.diff (nonexistent) +++ ps/trunk/libraries/source/spidermonkey/RemoveNSPRDependency.diff (revision 22627) @@ -0,0 +1,51 @@ + +# HG changeset patch +# User Philip Chimento +# Date 1501017350 25200 +# Node ID 5d95b2833b30582ab3df4e28373d749ddbf7c04e +# Parent c24e6fc9f689aeb32de0bf7916b1d0098d4a2bb9 +Bug 1379539 - Remove unnecessary NSPR dependency. r=glandium, a=jcristau + +diff --git a/build/autoconf/nspr-build.m4 b/build/autoconf/nspr-build.m4 +index 970e1e7..66d96b1 100644 +--- a/build/autoconf/nspr-build.m4 ++++ b/build/autoconf/nspr-build.m4 +@@ -167,11 +167,8 @@ fi + + AC_SUBST_LIST(NSPR_CFLAGS) + +-NSPR_PKGCONF_CHECK="nspr" ++PKGCONF_REQUIRES_PRIVATE="Requires.private: nspr" + if test -n "$MOZ_NATIVE_NSPR"; then +- # piggy back on $MOZ_NATIVE_NSPR to set a variable for the nspr check for js.pc +- NSPR_PKGCONF_CHECK="nspr >= $NSPR_MINVER" +- + _SAVE_CFLAGS=$CFLAGS + CFLAGS="$CFLAGS $NSPR_CFLAGS" + AC_TRY_COMPILE([#include "prlog.h"], +@@ -181,8 +178,12 @@ if test -n "$MOZ_NATIVE_NSPR"; then + , + AC_MSG_ERROR([system NSPR does not support PR_STATIC_ASSERT])) + CFLAGS=$_SAVE_CFLAGS ++ # piggy back on $MOZ_NATIVE_NSPR to set a variable for the nspr check for js.pc ++ PKGCONF_REQUIRES_PRIVATE="Requires.private: nspr >= $NSPR_MINVER" ++elif test -n "$JS_POSIX_NSPR"; then ++ PKGCONF_REQUIRES_PRIVATE= + fi +-AC_SUBST(NSPR_PKGCONF_CHECK) ++AC_SUBST([PKGCONF_REQUIRES_PRIVATE]) + + fi # _IS_OUTER_CONFIGURE + +diff --git a/js/src/js.pc.in b/js/src/js.pc.in +index 1efea33..2eae393 100644 +--- a/js/src/js.pc.in ++++ b/js/src/js.pc.in +@@ -6,6 +6,6 @@ includedir=@includedir@ + Name: SpiderMonkey @MOZILLA_VERSION@ + Description: The Mozilla library for JavaScript + Version: @MOZILLA_VERSION@ +-Requires.private: @NSPR_PKGCONF_CHECK@ ++@PKGCONF_REQUIRES_PRIVATE@ + Libs: -L${libdir} -l@JS_LIBRARY_NAME@ + Cflags: -include ${includedir}/@JS_LIBRARY_NAME@/js/RequiredDefines.h -I${includedir}/@JS_LIBRARY_NAME@ Index: ps/trunk/libraries/source/spidermonkey/build.sh =================================================================== --- ps/trunk/libraries/source/spidermonkey/build.sh (revision 22626) +++ ps/trunk/libraries/source/spidermonkey/build.sh (revision 22627) @@ -1,180 +1,206 @@ #!/bin/sh set -e # Since this script is called by update-workspaces.sh, we want to quickly # avoid doing any work if SpiderMonkey is already built and up-to-date. # Running SM's Makefile is a bit slow and noisy, so instead we'll make a # special file and only rebuild if it's older than SVN. # README.txt should be updated whenever we update SM, so use that as # a time comparison. if [ -e .already-built -a .already-built -nt README.txt ] then echo "SpiderMonkey is already up to date" exit fi echo "Building SpiderMonkey..." echo # Use Mozilla make on Windows if [ "${OS}" = "Windows_NT" ] then MAKE="mozmake" else MAKE=${MAKE:="make"} fi MAKE_OPTS="${JOBS}" -CONF_OPTS="--enable-shared-js --disable-tests --without-intl-api" +# jemalloc is outdated on SM45, do not use it +CONF_OPTS="--disable-tests --disable-jemalloc --enable-shared-js --without-intl-api" # Bug 1269319 -# When compiled with GCC 6 (or later), SpiderMonkey 38 (and versions up to 49) is +# When compiled with GCC 6 (or later), SpiderMonkey 45 (and versions up to 49) is # subject to segfaults. Disabling a few optimizations fixes that. # See also #4053 if [ "${OS}" != "Windows_NT" ] then if [ "`${CXX:=g++} -dumpversion | cut -f1 -d.`" -ge "6" ] then CXXFLAGS="${CXXFLAGS} -fno-schedule-insns2 -fno-delete-null-pointer-checks" fi fi # Change the default location where the tracelogger should store its output. # The default location is . on Windows and /tmp/ on *nix. TLCXXFLAGS='-DTRACE_LOG_DIR="\"../../source/tools/tracelogger/\""' -# We bundle prebuilt binaries for Windows and the .libs for nspr aren't included. +# NSPR is needed on Windows for POSIX emulation. # If you want to build on Windows, check README.txt and edit the absolute paths # to match your environment. if [ "${OS}" = "Windows_NT" ] then - NSPR_INCLUDES="-IC:/Projects/nspr/nspr-4.12/nspr/dist/include/nspr" + NSPR_INCLUDES="-ID:/nspr-4.12/nspr/dist/include/nspr" NSPR_LIBS=" \ - C:/Projects/nspr/nspr-4.12/nspr/dist/lib/nspr4 \ - C:/Projects/nspr/nspr-4.12/nspr/dist/lib/plds4 \ - C:/Projects/nspr/nspr-4.12/nspr/dist/lib/plc4" + D:/nspr-4.12/nspr/dist/lib/nspr4 \ + D:/nspr-4.12/nspr/dist/lib/plds4 \ + D:/nspr-4.12/nspr/dist/lib/plc4" else - NSPR_INCLUDES="`pkg-config nspr --cflags`" - NSPR_LIBS="`pkg-config nspr --libs`" + CONF_OPTS="${CONF_OPTS} --enable-posix-nspr-emulation" fi # If Valgrind looks like it's installed, then set up SM to support it # (else the JITs will interact poorly with it) if [ -e /usr/include/valgrind/valgrind.h ] then CONF_OPTS="${CONF_OPTS} --enable-valgrind" fi # We need to be able to override CHOST in case it is 32bit userland on 64bit kernel CONF_OPTS="${CONF_OPTS} \ ${CBUILD:+--build=${CBUILD}} \ ${CHOST:+--host=${CHOST}} \ ${CTARGET:+--target=${CTARGET}}" echo "SpiderMonkey build options: ${CONF_OPTS}" echo ${CONF_OPTS} -FOLDER=mozjs-38.0.0 +FOLDER=mozjs-45.0.2 # Delete the existing directory to avoid conflicts and extract the tarball rm -rf $FOLDER -tar xjf mozjs-38.2.1.rc0.tar.bz2 +tar xjf mozjs-45.0.2.tar.bz2 # Clean up header files that may be left over by earlier versions of SpiderMonkey rm -rf include-unix-* cd $FOLDER # Apply patches . ../patch.sh cd js/src # Clean up data generated by previous builds that could cause problems rm -rf build-debug rm -rf build-release # We want separate debug/release versions of the library, so we have to change # the LIBRARY_NAME for each build. # (We use perl instead of sed so that it works with MozillaBuild on Windows, # which has an ancient sed.) -perl -i.bak -pe 's/(SHARED_LIBRARY_NAME\s+=).*/$1 '\''mozjs38-ps-debug'\''/' moz.build +perl -i.bak -pe 's/(SHARED_LIBRARY_NAME\s+=).*/$1 '\''mozjs45-ps-debug'\''/' moz.build mkdir -p build-debug cd build-debug -CXXFLAGS="${CXXFLAGS} ${TLCXXFLAGS}" ../configure ${CONF_OPTS} --with-nspr-libs="$NSPR_LIBS" --with-nspr-cflags="$NSPR_INCLUDES" --enable-debug --disable-optimize --enable-js-diagnostics --enable-gczeal +if [ "${OS}" = "Windows_NT" ] +then + CXXFLAGS="${CXXFLAGS} ${TLCXXFLAGS}" ../configure ${CONF_OPTS} \ + --with-nspr-cflags="${NSPR_INCLUDES}" --with-nspr-libs="${NSPR_LIBS}" \ + --enable-debug \ + --disable-optimize \ + --enable-js-diagnostics \ + --enable-gczeal +else + CXXFLAGS="${CXXFLAGS} ${TLCXXFLAGS}" ../configure ${CONF_OPTS} \ + --enable-debug \ + --disable-optimize \ + --enable-js-diagnostics \ + --enable-gczeal +fi ${MAKE} ${MAKE_OPTS} cd .. -perl -i.bak -pe 's/(SHARED_LIBRARY_NAME\s+=).*/$1 '\''mozjs38-ps-release'\''/' moz.build +perl -i.bak -pe 's/(SHARED_LIBRARY_NAME\s+=).*/$1 '\''mozjs45-ps-release'\''/' moz.build mkdir -p build-release cd build-release -CXXFLAGS="${CXXFLAGS} ${TLCXXFLAGS}" ../configure ${CONF_OPTS} --with-nspr-libs="$NSPR_LIBS" --with-nspr-cflags="$NSPR_INCLUDES" --enable-optimize # --enable-gczeal --enable-debug-symbols +if [ "${OS}" = "Windows_NT" ] +then + CXXFLAGS="${CXXFLAGS} ${TLCXXFLAGS}" ../configure ${CONF_OPTS} \ + --with-nspr-cflags="${NSPR_INCLUDES}" --with-nspr-libs="${NSPR_LIBS}" \ + --enable-optimize \ + #--enable-gczeal \ + #--enable-debug-symbols +else + CXXFLAGS="${CXXFLAGS} ${TLCXXFLAGS}" ../configure ${CONF_OPTS} \ + --enable-optimize \ + #--enable-gczeal \ + #--enable-debug-symbols +fi ${MAKE} ${MAKE_OPTS} cd .. cd ../../.. if [ "${OS}" = "Windows_NT" ] then INCLUDE_DIR_DEBUG=include-win32-debug INCLUDE_DIR_RELEASE=include-win32-release DLL_SRC_SUFFIX=.dll DLL_DST_SUFFIX=.dll LIB_PREFIX= LIB_SRC_SUFFIX=.lib LIB_DST_SUFFIX=.lib else INCLUDE_DIR_DEBUG=include-unix-debug INCLUDE_DIR_RELEASE=include-unix-release DLL_SRC_SUFFIX=.so DLL_DST_SUFFIX=.so LIB_PREFIX=lib LIB_SRC_SUFFIX=.so LIB_DST_SUFFIX=.so if [ "`uname -s`" = "OpenBSD" ] then DLL_SRC_SUFFIX=.so.1.0 DLL_DST_SUFFIX=.so.1.0 LIB_SRC_SUFFIX=.so.1.0 LIB_DST_SUFFIX=:so.1.0 fi fi if [ "${OS}" = "Windows_NT" ] then # Bug #776126 # SpiderMonkey uses a tweaked zlib when building, and it wrongly copies its own files to include dirs # afterwards, so we have to remove them to not have them conflicting with the regular zlib - cd ${FOLDER}/js/src/build-release/dist/include + pushd ${FOLDER}/js/src/build-release/dist/include rm mozzconf.h zconf.h zlib.h - cd ../../../../../.. - cd ${FOLDER}/js/src/build-debug/dist/include + popd + pushd ${FOLDER}/js/src/build-debug/dist/include rm mozzconf.h zconf.h zlib.h - cd ../../../../../.. + popd fi # Copy files into the necessary locations for building and running the game # js-config.h is different for debug and release builds, so we need different include directories for both mkdir -p ${INCLUDE_DIR_DEBUG} mkdir -p ${INCLUDE_DIR_RELEASE} cp -R -L ${FOLDER}/js/src/build-release/dist/include/* ${INCLUDE_DIR_RELEASE}/ cp -R -L ${FOLDER}/js/src/build-debug/dist/include/* ${INCLUDE_DIR_DEBUG}/ mkdir -p lib/ -cp -L ${FOLDER}/js/src/build-debug/dist/lib/${LIB_PREFIX}mozjs38-ps-debug${LIB_SRC_SUFFIX} lib/${LIB_PREFIX}mozjs38-ps-debug${LIB_DST_SUFFIX} -cp -L ${FOLDER}/js/src/build-release/dist/lib/${LIB_PREFIX}mozjs38-ps-release${LIB_SRC_SUFFIX} lib/${LIB_PREFIX}mozjs38-ps-release${LIB_DST_SUFFIX} -cp -L ${FOLDER}/js/src/build-debug/dist/bin/${LIB_PREFIX}mozjs38-ps-debug${DLL_SRC_SUFFIX} ../../../binaries/system/${LIB_PREFIX}mozjs38-ps-debug${DLL_DST_SUFFIX} -cp -L ${FOLDER}/js/src/build-release/dist/bin/${LIB_PREFIX}mozjs38-ps-release${DLL_SRC_SUFFIX} ../../../binaries/system/${LIB_PREFIX}mozjs38-ps-release${DLL_DST_SUFFIX} +cp -L ${FOLDER}/js/src/build-debug/dist/sdk/lib/${LIB_PREFIX}mozjs45-ps-debug${LIB_SRC_SUFFIX} lib/${LIB_PREFIX}mozjs45-ps-debug${LIB_DST_SUFFIX} +cp -L ${FOLDER}/js/src/build-release/dist/sdk/lib/${LIB_PREFIX}mozjs45-ps-release${LIB_SRC_SUFFIX} lib/${LIB_PREFIX}mozjs45-ps-release${LIB_DST_SUFFIX} +cp -L ${FOLDER}/js/src/build-debug/dist/bin/${LIB_PREFIX}mozjs45-ps-debug${DLL_SRC_SUFFIX} ../../../binaries/system/${LIB_PREFIX}mozjs45-ps-debug${DLL_DST_SUFFIX} +cp -L ${FOLDER}/js/src/build-release/dist/bin/${LIB_PREFIX}mozjs45-ps-release${DLL_SRC_SUFFIX} ../../../binaries/system/${LIB_PREFIX}mozjs45-ps-release${DLL_DST_SUFFIX} # On Windows, also copy debugging symbols files if [ "${OS}" = "Windows_NT" ] then - cp -L ${FOLDER}/js/src/build-debug/js/src/${LIB_PREFIX}mozjs38-ps-debug.pdb ../../../binaries/system/${LIB_PREFIX}mozjs38-ps-debug.pdb - cp -L ${FOLDER}/js/src/build-release/js/src/${LIB_PREFIX}mozjs38-ps-release.pdb ../../../binaries/system/${LIB_PREFIX}mozjs38-ps-release.pdb + cp -L ${FOLDER}/js/src/build-debug/js/src/${LIB_PREFIX}mozjs45-ps-debug-vc140.pdb ../../../binaries/system/${LIB_PREFIX}mozjs45-ps-debug-vc140.pdb + cp -L ${FOLDER}/js/src/build-release/js/src/${LIB_PREFIX}mozjs45-ps-release-vc140.pdb ../../../binaries/system/${LIB_PREFIX}mozjs45-ps-release-vc140.pdb fi # Flag that it's already been built successfully so we can skip it next time touch .already-built Index: ps/trunk/libraries/source/spidermonkey/mozjs-45.0.2.tar.bz2 =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Index: ps/trunk/libraries/source/spidermonkey/mozjs-45.0.2.tar.bz2 =================================================================== --- ps/trunk/libraries/source/spidermonkey/mozjs-45.0.2.tar.bz2 (nonexistent) +++ ps/trunk/libraries/source/spidermonkey/mozjs-45.0.2.tar.bz2 (revision 22627) Property changes on: ps/trunk/libraries/source/spidermonkey/mozjs-45.0.2.tar.bz2 ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +application/octet-stream \ No newline at end of property Index: ps/trunk/libraries/source/spidermonkey/patch.sh =================================================================== --- ps/trunk/libraries/source/spidermonkey/patch.sh (revision 22626) +++ ps/trunk/libraries/source/spidermonkey/patch.sh (revision 22627) @@ -1,55 +1,53 @@ #!/bin/sh # Apply patches if needed # This script gets called from build-osx-libs.sh and build.sh. -# Fix the version specification code which used PYTHON before it was set. -# The second patch is required to not add autoconf as a dependency. -patch -p1 < ../FixVersionDetection.diff -patch -p1 < ../FixVersionDetectionConfigure.diff +# Remove the unnecessary NSPR dependency. +# Will be included in SM52. +# https://bugzilla.mozilla.org/show_bug.cgi?id=1379539 +patch -p1 < ../RemoveNSPRDependency.diff # Fix the path to the moz.build file in the zlib module patch -p1 < ../FixZLibMozBuild.diff -# === Fix the SM38 tracelogger === +# === Fix the SM45 tracelogger === # This patch is a squashed version of several patches that were adapted # to fix failing hunks. # # Applied in the following order, they are: -# * https://bugzilla.mozilla.org/show_bug.cgi?id=1223767 -# Assertion failure: i < size_, at js/src/vm/TraceLoggingTypes.h:210 -# Also fix stop-information to make reduce.py work correctly. -# * https://bugzilla.mozilla.org/show_bug.cgi?id=1227914 -# Limit the memory tracelogger can take. -# This causes tracelogger to flush data to the disk regularly and prevents out of -# memory issues if a lot of data gets logged. -# * https://bugzilla.mozilla.org/show_bug.cgi?id=1155618 -# Fix tracelogger destructor that touches possibly uninitialised hash table. -# * https://bugzilla.mozilla.org/show_bug.cgi?id=1223636 -# Don't treat extraTextId as containing only extra ids. -# This fixes an assertion failure: id == nextTextId at js/src/vm/TraceLoggingGraph.cpp -# * https://bugzilla.mozilla.org/show_bug.cgi?id=1227028 -# Fix when to keep the payload of a TraceLogger event. -# This fixes an assertion failure: textId < uint32_t(1 << 31) at js/src/vm/TraceLoggingGraph.h # * https://bugzilla.mozilla.org/show_bug.cgi?id=1266649 # Handle failing to add to pointermap gracefully. # * https://bugzilla.mozilla.org/show_bug.cgi?id=1280648 # Don't cache based on pointers to movable GC things. -# * https://bugzilla.mozilla.org/show_bug.cgi?id=1224123 -# Fix the use of LastEntryId in tracelogger.h. -# * https://bugzilla.mozilla.org/show_bug.cgi?id=1231170 -# Use size in debugger instead of the current id to track last logged item. -# * https://bugzilla.mozilla.org/show_bug.cgi?id=1221844 -# Move TraceLogger_Invalidation to LOG_ITEM. -# Add some debug checks to logTimestamp. # * https://bugzilla.mozilla.org/show_bug.cgi?id=1255766 # Also mark resizing of memory. # * https://bugzilla.mozilla.org/show_bug.cgi?id=1259403 # Only increase capacity by multiples of 2. # Always make sure there are 3 free slots for events. # === patch -p1 < ../FixTracelogger.diff # Patch embedded python psutil to work with FreeBSD 12 after revision 315662 # Based on: https://svnweb.freebsd.org/ports/head/sysutils/py-psutil121/files/patch-_psutil_bsd.c?revision=436575&view=markup -# Related: https://bugzilla.mozilla.org/show_bug.cgi?id=1238983 +# psutil will be upgraded in SM60: https://bugzilla.mozilla.org/show_bug.cgi?id=1436857 patch -p0 < ../FixpsutilFreeBSD.diff + +# Patch some parts of the code to support extra processor architectures +# Includes https://bugzilla.mozilla.org/show_bug.cgi?id=1143022 and https://bugzilla.mozilla.org/show_bug.cgi?id=1277742 +patch -p1 < ../FixNonx86.diff + +# Always link mozglue into the shared library when building standalone. +# Will be included in SM60. Custom version of the patch for SM45, which doesn't have the same build system. +# https://bugzilla.mozilla.org/show_bug.cgi?id=1176787 +patch -p1 < ../FixMozglueStatic.diff + +# JSPropertyDescriptor is not public in SM45. +# Will be fixed in SM52. +# https://bugzilla.mozilla.org/show_bug.cgi?id=1316079 +patch -p1 < ../ExportJSPropertyDescriptor.diff + +# When trying to link pyrogenesis, js::oom::GetThreadType() and js::ReportOutOfMemory() +# are marked as unresolved symbols. +# Will be included in SM52. +# https://bugzilla.mozilla.org/show_bug.cgi?id=1379538 +patch -p1 < ../FixLinking.diff Index: ps/trunk/source/gui/IGUIObject.cpp =================================================================== --- ps/trunk/source/gui/IGUIObject.cpp (revision 22626) +++ ps/trunk/source/gui/IGUIObject.cpp (revision 22627) @@ -1,492 +1,492 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "GUI.h" #include "gui/scripting/JSInterface_GUITypes.h" #include "gui/scripting/JSInterface_IGUIObject.h" #include "ps/GameSetup/Config.h" #include "ps/CLogger.h" #include "ps/Profile.h" #include "scriptinterface/ScriptInterface.h" IGUIObject::IGUIObject(CGUI* pGUI) : m_pGUI(pGUI), m_pParent(NULL), m_MouseHovering(false), m_LastClickTime() { AddSetting("enabled"); AddSetting("hidden"); AddSetting("size"); AddSetting("style"); AddSetting("hotkey"); AddSetting("z"); AddSetting("absolute"); AddSetting("ghost"); AddSetting("aspectratio"); AddSetting("tooltip"); AddSetting("tooltip_style"); // Setup important defaults GUI::SetSetting(this, "hidden", false); GUI::SetSetting(this, "ghost", false); GUI::SetSetting(this, "enabled", true); GUI::SetSetting(this, "absolute", true); } IGUIObject::~IGUIObject() { for (const std::pair& p : m_Settings) delete p.second; if (!m_ScriptHandlers.empty()) JS_RemoveExtraGCRootsTracer(m_pGUI->GetScriptInterface()->GetJSRuntime(), Trace, this); } //------------------------------------------------------------------- // Functions //------------------------------------------------------------------- void IGUIObject::AddChild(IGUIObject* pChild) { // ENSURE(pChild); pChild->SetParent(this); m_Children.push_back(pChild); // If this (not the child) object is already attached // to a CGUI, it pGUI pointer will be non-null. // This will mean we'll have to check if we're using // names already used. if (pChild->GetGUI()) { try { // Atomic function, if it fails it won't // have changed anything //UpdateObjects(); pChild->GetGUI()->UpdateObjects(); } catch (PSERROR_GUI&) { // If anything went wrong, reverse what we did and throw // an exception telling it never added a child m_Children.erase(m_Children.end()-1); throw; } } // else do nothing } void IGUIObject::AddToPointersMap(map_pObjects& ObjectMap) { // Just don't do anything about the top node if (m_pParent == NULL) return; // Now actually add this one // notice we won't add it if it's doesn't have any parent // (i.e. being the base object) if (m_Name.empty()) { throw PSERROR_GUI_ObjectNeedsName(); } if (ObjectMap.count(m_Name) > 0) { throw PSERROR_GUI_NameAmbiguity(m_Name.c_str()); } else { ObjectMap[m_Name] = this; } } void IGUIObject::Destroy() { // Is there anything besides the children to destroy? } template void IGUIObject::AddSetting(const CStr& Name) { // This can happen due to inheritance if (SettingExists(Name)) return; m_Settings[Name] = new CGUISetting(*this, Name); } bool IGUIObject::MouseOver() { if (!GetGUI()) throw PSERROR_GUI_OperationNeedsGUIObject(); return m_CachedActualSize.PointInside(GetMousePos()); } bool IGUIObject::MouseOverIcon() { return false; } CPos IGUIObject::GetMousePos() const { if (GetGUI()) return GetGUI()->m_MousePos; return CPos(); } void IGUIObject::UpdateMouseOver(IGUIObject* const& pMouseOver) { if (pMouseOver == this) { if (!m_MouseHovering) SendEvent(GUIM_MOUSE_ENTER, "mouseenter"); m_MouseHovering = true; SendEvent(GUIM_MOUSE_OVER, "mousemove"); } else { if (m_MouseHovering) { m_MouseHovering = false; SendEvent(GUIM_MOUSE_LEAVE, "mouseleave"); } } } bool IGUIObject::SettingExists(const CStr& Setting) const { return m_Settings.count(Setting) == 1; } PSRETURN IGUIObject::SetSetting(const CStr& Setting, const CStrW& Value, const bool& SkipMessage) { if (!SettingExists(Setting)) return PSRETURN_GUI_InvalidSetting; if (!m_Settings[Setting]->FromString(Value, SkipMessage)) return PSRETURN_GUI_UnableToParse; return PSRETURN_OK; } void IGUIObject::ChooseMouseOverAndClosest(IGUIObject*& pObject) { if (!MouseOver()) return; // Check if we've got competition at all if (pObject == NULL) { pObject = this; return; } // Or if it's closer if (GetBufferedZ() >= pObject->GetBufferedZ()) { pObject = this; return; } } IGUIObject* IGUIObject::GetParent() const { // Important, we're not using GetParent() for these // checks, that could screw it up if (m_pParent) { if (m_pParent->m_pParent == NULL) return NULL; } return m_pParent; } void IGUIObject::ResetStates() { // Notify the gui that we aren't hovered anymore UpdateMouseOver(nullptr); } void IGUIObject::UpdateCachedSize() { bool absolute; GUI::GetSetting(this, "absolute", absolute); float aspectratio = 0.f; GUI::GetSetting(this, "aspectratio", aspectratio); CClientArea ca; GUI::GetSetting(this, "size", ca); // If absolute="false" and the object has got a parent, // use its cached size instead of the screen. Notice // it must have just been cached for it to work. if (absolute == false && m_pParent && !IsRootObject()) m_CachedActualSize = ca.GetClientArea(m_pParent->m_CachedActualSize); else m_CachedActualSize = ca.GetClientArea(CRect(0.f, 0.f, g_xres / g_GuiScale, g_yres / g_GuiScale)); // In a few cases, GUI objects have to resize to fill the screen // but maintain a constant aspect ratio. // Adjust the size to be the max possible, centered in the original size: if (aspectratio) { if (m_CachedActualSize.GetWidth() > m_CachedActualSize.GetHeight()*aspectratio) { float delta = m_CachedActualSize.GetWidth() - m_CachedActualSize.GetHeight()*aspectratio; m_CachedActualSize.left += delta/2.f; m_CachedActualSize.right -= delta/2.f; } else { float delta = m_CachedActualSize.GetHeight() - m_CachedActualSize.GetWidth()/aspectratio; m_CachedActualSize.bottom -= delta/2.f; m_CachedActualSize.top += delta/2.f; } } } void IGUIObject::LoadStyle(CGUI& GUIinstance, const CStr& StyleName) { // Fetch style if (GUIinstance.m_Styles.count(StyleName) == 1) { LoadStyle(GUIinstance.m_Styles[StyleName]); } else { debug_warn(L"IGUIObject::LoadStyle failed"); } } void IGUIObject::LoadStyle(const SGUIStyle& Style) { // Iterate settings, it won't be able to set them all probably, but that doesn't matter for (const std::pair& p : Style.m_SettingsDefaults) { // Try set setting in object SetSetting(p.first, p.second); // It doesn't matter if it fail, it's not suppose to be able to set every setting. // since it's generic. // The beauty with styles is that it can contain more settings // than exists for the objects using it. So if the SetSetting // fails, don't care. } } float IGUIObject::GetBufferedZ() const { bool absolute; GUI::GetSetting(this, "absolute", absolute); float Z; GUI::GetSetting(this, "z", Z); if (absolute) return Z; else { if (GetParent()) return GetParent()->GetBufferedZ() + Z; else { // In philosophy, a parentless object shouldn't be able to have a relative sizing, // but we'll accept it so that absolute can be used as default without a complaint. // Also, you could consider those objects children to the screen resolution. return Z; } } } void IGUIObject::RegisterScriptHandler(const CStr& Action, const CStr& Code, CGUI* pGUI) { if(!GetGUI()) throw PSERROR_GUI_OperationNeedsGUIObject(); JSContext* cx = pGUI->GetScriptInterface()->GetContext(); JSAutoRequest rq(cx); JS::RootedValue globalVal(cx, pGUI->GetGlobalObject()); JS::RootedObject globalObj(cx, &globalVal.toObject()); const int paramCount = 1; const char* paramNames[paramCount] = { "mouse" }; // Location to report errors from CStr CodeName = GetName()+" "+Action; // Generate a unique name static int x = 0; char buf[64]; sprintf_s(buf, ARRAY_SIZE(buf), "__eventhandler%d (%s)", x++, Action.c_str()); JS::CompileOptions options(cx); options.setFileAndLine(CodeName.c_str(), 0); - options.setCompileAndGo(true); + options.setIsRunOnce(true); JS::RootedFunction func(cx); JS::AutoObjectVector emptyScopeChain(cx); if (!JS::CompileFunction(cx, emptyScopeChain, options, buf, paramCount, paramNames, Code.c_str(), Code.length(), &func)) { LOGERROR("RegisterScriptHandler: Failed to compile the script for %s", Action.c_str()); return; } JS::RootedObject funcObj(cx, JS_GetFunctionObject(func)); SetScriptHandler(Action, funcObj); } void IGUIObject::SetScriptHandler(const CStr& Action, JS::HandleObject Function) { ENSURE(m_pGUI && "A GUI must be associated with the GUIObject before adding ScriptHandlers!"); if (m_ScriptHandlers.empty()) JS_AddExtraGCRootsTracer(m_pGUI->GetScriptInterface()->GetJSRuntime(), Trace, this); m_ScriptHandlers[Action] = JS::Heap(Function); } InReaction IGUIObject::SendEvent(EGUIMessageType type, const CStr& EventName) { PROFILE2_EVENT("gui event"); PROFILE2_ATTR("type: %s", EventName.c_str()); PROFILE2_ATTR("object: %s", m_Name.c_str()); SGUIMessage msg(type); HandleMessage(msg); ScriptEvent(EventName); return (msg.skipped ? IN_PASS : IN_HANDLED); } void IGUIObject::ScriptEvent(const CStr& Action) { std::map >::iterator it = m_ScriptHandlers.find(Action); if (it == m_ScriptHandlers.end()) return; JSContext* cx = m_pGUI->GetScriptInterface()->GetContext(); JSAutoRequest rq(cx); // Set up the 'mouse' parameter JS::RootedValue mouse(cx); m_pGUI->GetScriptInterface()->CreateObject( &mouse, "x", m_pGUI->m_MousePos.x, "y", m_pGUI->m_MousePos.y, "buttons", m_pGUI->m_MouseButtons); JS::AutoValueVector paramData(cx); paramData.append(mouse); JS::RootedObject obj(cx, GetJSObject()); JS::RootedValue handlerVal(cx, JS::ObjectValue(*it->second)); JS::RootedValue result(cx); bool ok = JS_CallFunctionValue(cx, obj, handlerVal, paramData, &result); if (!ok) { // We have no way to propagate the script exception, so just ignore it // and hope the caller checks JS_IsExceptionPending } } void IGUIObject::ScriptEvent(const CStr& Action, JS::HandleValueArray paramData) { std::map >::iterator it = m_ScriptHandlers.find(Action); if (it == m_ScriptHandlers.end()) return; JSContext* cx = m_pGUI->GetScriptInterface()->GetContext(); JSAutoRequest rq(cx); JS::RootedObject obj(cx, GetJSObject()); JS::RootedValue handlerVal(cx, JS::ObjectValue(*it->second)); JS::RootedValue result(cx); if (!JS_CallFunctionValue(cx, obj, handlerVal, paramData, &result)) JS_ReportError(cx, "Errors executing script action \"%s\"", Action.c_str()); } void IGUIObject::CreateJSObject() { JSContext* cx = m_pGUI->GetScriptInterface()->GetContext(); JSAutoRequest rq(cx); m_JSObject.init(cx, m_pGUI->GetScriptInterface()->CreateCustomObject("GUIObject")); JS_SetPrivate(m_JSObject.get(), this); } JSObject* IGUIObject::GetJSObject() { // Cache the object when somebody first asks for it, because otherwise // we end up doing far too much object allocation. if (!m_JSObject.initialized()) CreateJSObject(); return m_JSObject.get(); } CStr IGUIObject::GetPresentableName() const { // __internal(), must be at least 13 letters to be able to be // an internal name if (m_Name.length() <= 12) return m_Name; if (m_Name.substr(0, 10) == "__internal") return CStr("[unnamed object]"); else return m_Name; } void IGUIObject::SetFocus() { GetGUI()->m_FocusedObject = this; } bool IGUIObject::IsFocused() const { return GetGUI()->m_FocusedObject == this; } bool IGUIObject::IsRootObject() const { return GetGUI() != 0 && m_pParent == GetGUI()->m_BaseObject; } void IGUIObject::TraceMember(JSTracer* trc) { // Please ensure to adapt the Tracer enabling and disabling in accordance with the GC things traced! for (std::pair>& handler : m_ScriptHandlers) JS_CallObjectTracer(trc, &handler.second, "IGUIObject::m_ScriptHandlers"); } // Instantiate templated functions: #define TYPE(T) template void IGUIObject::AddSetting(const CStr& Name); #include "GUItypes.h" #undef TYPE Index: ps/trunk/source/gui/IGUIObject.h =================================================================== --- ps/trunk/source/gui/IGUIObject.h (revision 22626) +++ ps/trunk/source/gui/IGUIObject.h (revision 22627) @@ -1,489 +1,489 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ /* * The base class of an object. * All objects are derived from this class. * It's an abstract data type, so it can't be used per se. * Also contains a Dummy object which is used for completely blank objects. */ #ifndef INCLUDED_IGUIOBJECT #define INCLUDED_IGUIOBJECT #include "GUIbase.h" #include "GUItext.h" #include "gui/scripting/JSInterface_IGUIObject.h" #include "lib/input.h" // just for IN_PASS #include "ps/XML/Xeromyces.h" #include #include #include struct SGUIStyle; class CGUI; class JSObject; class IGUISetting; ERROR_TYPE(GUI, UnableToParse); /** * GUI object such as a button or an input-box. * Abstract data type ! */ class IGUIObject { friend class CGUI; friend class IGUIScrollBar; friend class GUITooltip; // Allow getProperty to access things like GetParent() friend bool JSI_IGUIObject::getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp); - friend bool JSI_IGUIObject::setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool UNUSED(strict), JS::MutableHandleValue vp); + friend bool JSI_IGUIObject::setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp, JS::ObjectOpResult& result); friend bool JSI_IGUIObject::getComputedSize(JSContext* cx, uint argc, JS::Value* vp); public: IGUIObject(CGUI* pGUI); virtual ~IGUIObject(); /** * Checks if mouse is hovering this object. * The mouse position is cached in CGUI. * * This function checks if the mouse is hovering the * rectangle that the base setting "size" makes. * Although it is virtual, so one could derive * an object from CButton, which changes only this * to checking the circle that "size" makes. * * @return true if mouse is hovering */ virtual bool MouseOver(); /** * Test if mouse position is over an icon */ virtual bool MouseOverIcon(); //-------------------------------------------------------- /** @name Leaf Functions */ //-------------------------------------------------------- //@{ /// Get object name, name is unique const CStr& GetName() const { return m_Name; } /// Get object name void SetName(const CStr& Name) { m_Name = Name; } // Get Presentable name. // Will change all internally set names to something like "" CStr GetPresentableName() const; /** * Adds object and its children to the map, it's name being the * first part, and the second being itself. * * @param ObjectMap Adds this to the map_pObjects. * * @throws PSERROR_GUI_ObjectNeedsName Name is missing * @throws PSERROR_GUI_NameAmbiguity Name is already taken */ void AddToPointersMap(map_pObjects& ObjectMap); /** * Notice nothing will be returned or thrown if the child hasn't * been inputted into the GUI yet. This is because that's were * all is checked. Now we're just linking two objects, but * it's when we're inputting them into the GUI we'll check * validity! Notice also when adding it to the GUI this function * will inevitably have been called by CGUI::AddObject which * will catch the throw and return the error code. * i.e. The user will never put in the situation wherein a throw * must be caught, the GUI's internal error handling will be * completely transparent to the interfacially sequential model. * * @param pChild Child to add * * @throws PSERROR_GUI from CGUI::UpdateObjects(). */ void AddChild(IGUIObject* pChild); //@} //-------------------------------------------------------- /** @name Iterate * Used to iterate over all children of this object. */ //-------------------------------------------------------- //@{ vector_pObjects::iterator begin() { return m_Children.begin(); } vector_pObjects::iterator end() { return m_Children.end(); } //@} //-------------------------------------------------------- /** @name Settings Management */ //-------------------------------------------------------- //@{ /** * Returns whether there is a setting with the given name registered. * * @param Setting setting name * @return True if settings exist. */ bool SettingExists(const CStr& Setting) const; /** * All sizes are relative to resolution, and the calculation * is not wanted in real time, therefore it is cached, update * the cached size with this function. */ virtual void UpdateCachedSize(); /** * Reset internal state of this object. */ virtual void ResetStates(); /** * Set a setting by string, regardless of what type it is. * * example a CRect(10,10,20,20) would be "10 10 20 20" * * @param Setting Setting by name * @param Value Value to set to * @param SkipMessage Does not send a GUIM_SETTINGS_UPDATED if true * * @return PSRETURN (PSRETURN_OK if successful) */ PSRETURN SetSetting(const CStr& Setting, const CStrW& Value, const bool& SkipMessage = false); /** * Set the script handler for a particular object-specific action * * @param Action Name of action * @param Code Javascript code to execute when the action occurs * @param pGUI GUI instance to associate the script with */ void RegisterScriptHandler(const CStr& Action, const CStr& Code, CGUI* pGUI); /** * Creates the JS Object representing this page upon first use. * Can be overridden by derived classes to extend it. */ virtual void CreateJSObject(); /** * Retrieves the JSObject representing this GUI object. */ JSObject* GetJSObject(); //@} protected: //-------------------------------------------------------- /** @name Called by CGUI and friends * * Methods that the CGUI will call using * its friendship, these should not * be called by user. * These functions' security are a lot * what constitutes the GUI's */ //-------------------------------------------------------- //@{ /** * Add a setting to m_Settings * * @param Type Setting type * @param Name Setting reference name */ template void AddSetting(const CStr& Name); /** * Calls Destroy on all children, and deallocates all memory. * MEGA TODO Should it destroy it's children? */ virtual void Destroy(); public: /** * This function is called with different messages * for instance when the mouse enters the object. * * @param Message GUI Message */ virtual void HandleMessage(SGUIMessage& UNUSED(Message)) {} protected: /** * Draws the object. * * @throws PSERROR if any. But this will mostlikely be * very rare since if an object is drawn unsuccessfully * it'll probably only output in the Error log, and not * disrupt the whole GUI drawing. */ virtual void Draw() = 0; /** * Some objects need to handle the SDL_Event_ manually. * For instance the input box. * * Only the object with focus will have this function called. * * Returns either IN_PASS or IN_HANDLED. If IN_HANDLED, then * the key won't be passed on and processed by other handlers. * This is used for keys that the GUI uses. */ virtual InReaction ManuallyHandleEvent(const SDL_Event_* UNUSED(ev)) { return IN_PASS; } /** * Loads a style. * * @param GUIinstance Reference to the GUI * @param StyleName Style by name */ void LoadStyle(CGUI& GUIinstance, const CStr& StyleName); /** * Loads a style. * * @param Style The style object. */ void LoadStyle(const SGUIStyle& Style); /** * Returns not the Z value, but the actual buffered Z value, i.e. if it's * defined relative, then it will check its parent's Z value and add * the relativity. * * @return Actual Z value on the screen. */ virtual float GetBufferedZ() const; /** * Set parent of this object */ void SetParent(IGUIObject* pParent) { m_pParent = pParent; } public: CGUI* GetGUI() { return m_pGUI; } const CGUI* GetGUI() const { return m_pGUI; } /** * Take focus! */ void SetFocus(); /** * Workaround to avoid a dynamic_cast which can be 80 times slower than this. */ virtual void* GetTextOwner() { return nullptr; } protected: /** * Check if object is focused. */ bool IsFocused() const; /** * NOTE! This will not just return m_pParent, when that is * need use it! There is one exception to it, when the parent is * the top-node (the object that isn't a real object), this * will return NULL, so that the top-node's children are * seemingly parentless. * * @return Pointer to parent */ IGUIObject* GetParent() const; /** * Get Mouse from CGUI. */ CPos GetMousePos() const; /** * Handle additional children to the \-tag. In IGUIObject, this function does * nothing. In CList and CDropDown, it handles the \, used to build the data. * * Returning false means the object doesn't recognize the child. Should be reported. * Notice 'false' is default, because an object not using this function, should not * have any additional children (and this function should never be called). */ virtual bool HandleAdditionalChildren(const XMBElement& UNUSED(child), CXeromyces* UNUSED(pFile)) { return false; } /** * Cached size, real size m_Size is actually dependent on resolution * and can have different *real* outcomes, this is the real outcome * cached to avoid slow calculations in real time. */ CRect m_CachedActualSize; /** * Send event to this GUI object (HandleMessage and ScriptEvent) * * @param type Type of GUI message to be handled * @param EventName String representation of event name * @return IN_HANDLED if event was handled, or IN_PASS if skipped */ InReaction SendEvent(EGUIMessageType type, const CStr& EventName); /** * Execute the script for a particular action. * Does nothing if no script has been registered for that action. * The mouse coordinates will be passed as the first argument. * * @param Action Name of action */ void ScriptEvent(const CStr& Action); /** * Execute the script for a particular action. * Does nothing if no script has been registered for that action. * * @param Action Name of action * @param paramData JS::HandleValueArray arguments to pass to the event. */ void ScriptEvent(const CStr& Action, JS::HandleValueArray paramData); void SetScriptHandler(const CStr& Action, JS::HandleObject Function); /** * Inputes the object that is currently hovered, this function * updates this object accordingly (i.e. if it's the object * being inputted one thing happens, and not, another). * * @param pMouseOver Object that is currently hovered, * can OF COURSE be NULL too! */ void UpdateMouseOver(IGUIObject* const& pMouseOver); //@} private: //-------------------------------------------------------- /** @name Internal functions */ //-------------------------------------------------------- //@{ /** * Inputs a reference pointer, checks if the new inputted object * if hovered, if so, then check if this's Z value is greater * than the inputted object... If so then the object is closer * and we'll replace the pointer with this. * Also Notice input can be NULL, which means the Z value demand * is out. NOTICE you can't input NULL as const so you'll have * to set an object to NULL. * * @param pObject Object pointer, can be either the old one, or * the new one. */ void ChooseMouseOverAndClosest(IGUIObject*& pObject); // Is the object a Root object, in philosophy, this means it // has got no parent, and technically, it's got the m_BaseObject // as parent. bool IsRootObject() const; static void Trace(JSTracer* trc, void* data) { reinterpret_cast(data)->TraceMember(trc); } void TraceMember(JSTracer* trc); // Variables protected: // Name of object CStr m_Name; // Constructed on the heap, will be destroyed along with the the object // TODO Gee: really the above? vector_pObjects m_Children; // Pointer to parent IGUIObject *m_pParent; //This represents the last click time for each mouse button double m_LastClickTime[6]; /** * This is an array of true or false, each element is associated with * a string representing a setting. Number of elements is equal to * number of settings. * * A true means the setting has been manually set in the file when * read. This is important to know because I don't want to force * the user to include its \-XML-files first, so somehow * the GUI needs to know which settings were set, and which is meant * to. */ // More variables // Is mouse hovering the object? used with the function MouseOver() bool m_MouseHovering; /** * Settings pool, all an object's settings are located here * If a derived object has got more settings that the base * settings, it's because they have a new version of the * function SetupSettings(). * * @see SetupSettings() */ public: std::map m_Settings; protected: // An object can't function stand alone CGUI* const m_pGUI; // Internal storage for registered script handlers. std::map > m_ScriptHandlers; // Cached JSObject representing this GUI object JS::PersistentRootedObject m_JSObject; }; /** * Dummy object used primarily for the root object * or objects of type 'empty' */ class CGUIDummyObject : public IGUIObject { GUI_OBJECT(CGUIDummyObject) public: CGUIDummyObject(CGUI* pGUI) : IGUIObject(pGUI) {} virtual void Draw() {} // Empty can never be hovered. It is only a category. virtual bool MouseOver() { return false; } }; #endif // INCLUDED_IGUIOBJECT Index: ps/trunk/source/gui/scripting/JSInterface_IGUIObject.cpp =================================================================== --- ps/trunk/source/gui/scripting/JSInterface_IGUIObject.cpp (revision 22626) +++ ps/trunk/source/gui/scripting/JSInterface_IGUIObject.cpp (revision 22627) @@ -1,263 +1,263 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "JSInterface_IGUIObject.h" #include "gui/CGUI.h" #include "gui/CGUIColor.h" #include "gui/CList.h" #include "gui/GUIManager.h" #include "gui/IGUIObject.h" #include "gui/IGUIScrollBar.h" #include "gui/scripting/JSInterface_GUITypes.h" #include "ps/CLogger.h" #include "scriptinterface/ScriptExtraHeaders.h" #include "scriptinterface/ScriptInterface.h" JSClass JSI_IGUIObject::JSI_class = { "GUIObject", JSCLASS_HAS_PRIVATE, nullptr, nullptr, JSI_IGUIObject::getProperty, JSI_IGUIObject::setProperty, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }; JSFunctionSpec JSI_IGUIObject::JSI_methods[] = { JS_FN("toString", JSI_IGUIObject::toString, 0, 0), JS_FN("focus", JSI_IGUIObject::focus, 0, 0), JS_FN("blur", JSI_IGUIObject::blur, 0, 0), JS_FN("getComputedSize", JSI_IGUIObject::getComputedSize, 0, 0), JS_FS_END }; bool JSI_IGUIObject::getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp) { JSAutoRequest rq(cx); ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface; IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, obj, &JSI_IGUIObject::JSI_class, NULL); if (!e) return false; JS::RootedValue idval(cx); if (!JS_IdToValue(cx, id, &idval)) return false; std::string propName; if (!ScriptInterface::FromJSVal(cx, idval, propName)) return false; // Skip registered functions and inherited properties // including JSInterfaces of derived classes if (propName == "constructor" || propName == "prototype" || propName == "toString" || propName == "toJSON" || propName == "focus" || propName == "blur" || propName == "getTextSize" || propName == "getComputedSize" ) return true; // Use onWhatever to access event handlers if (propName.substr(0, 2) == "on") { CStr eventName(CStr(propName.substr(2)).LowerCase()); std::map>::iterator it = e->m_ScriptHandlers.find(eventName); if (it == e->m_ScriptHandlers.end()) vp.setNull(); else vp.setObject(*it->second.get()); return true; } if (propName == "parent") { IGUIObject* parent = e->GetParent(); if (parent) vp.set(JS::ObjectValue(*parent->GetJSObject())); else vp.set(JS::NullValue()); return true; } else if (propName == "children") { pScriptInterface->CreateArray(vp); for (size_t i = 0; i < e->m_Children.size(); ++i) pScriptInterface->SetPropertyInt(vp, i, e->m_Children[i]); return true; } else if (propName == "name") { ScriptInterface::ToJSVal(cx, vp, e->GetName()); return true; } else if (e->SettingExists(propName)) { e->m_Settings[propName]->ToJSVal(cx, vp); return true; } JS_ReportError(cx, "Property '%s' does not exist!", propName.c_str()); return false; } -bool JSI_IGUIObject::setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool UNUSED(strict), JS::MutableHandleValue vp) +bool JSI_IGUIObject::setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp, JS::ObjectOpResult& result) { IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, obj, &JSI_IGUIObject::JSI_class, NULL); if (!e) - return false; + return result.fail(JSMSG_NOT_NONNULL_OBJECT); JSAutoRequest rq(cx); JS::RootedValue idval(cx); if (!JS_IdToValue(cx, id, &idval)) - return false; + return result.fail(JSMSG_NOT_NONNULL_OBJECT); std::string propName; if (!ScriptInterface::FromJSVal(cx, idval, propName)) - return false; + return result.fail(JSMSG_UNDEFINED_PROP); if (propName == "name") { std::string value; if (!ScriptInterface::FromJSVal(cx, vp, value)) - return false; + return result.fail(JSMSG_UNDEFINED_PROP); e->SetName(value); - return true; + return result.succeed(); } JS::RootedObject vpObj(cx); if (vp.isObject()) vpObj = &vp.toObject(); // Use onWhatever to set event handlers if (propName.substr(0, 2) == "on") { if (vp.isPrimitive() || vp.isNull() || !JS_ObjectIsFunction(cx, &vp.toObject())) { JS_ReportError(cx, "on- event-handlers must be functions"); - return false; + return result.fail(JSMSG_NOT_FUNCTION); } CStr eventName(CStr(propName.substr(2)).LowerCase()); e->SetScriptHandler(eventName, vpObj); - return true; + return result.succeed(); } if (e->SettingExists(propName)) - return e->m_Settings[propName]->FromJSVal(cx, vp); + return e->m_Settings[propName]->FromJSVal(cx, vp) ? result.succeed() : result.fail(JSMSG_TYPE_ERR_BAD_ARGS); JS_ReportError(cx, "Property '%s' does not exist!", propName.c_str()); - return false; + return result.fail(JSMSG_UNDEFINED_PROP); } void JSI_IGUIObject::init(ScriptInterface& scriptInterface) { scriptInterface.DefineCustomObjectType(&JSI_class, nullptr, 1, nullptr, JSI_methods, nullptr, nullptr); } bool JSI_IGUIObject::toString(JSContext* cx, uint UNUSED(argc), JS::Value* vp) { JSAutoRequest rq(cx); JS::CallReceiver rec = JS::CallReceiverFromVp(vp); JS::RootedObject thisObj(cx, JS_THIS_OBJECT(cx, vp)); IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, thisObj, &JSI_IGUIObject::JSI_class, NULL); if (!e) return false; ScriptInterface::ToJSVal(cx, rec.rval(), "[GUIObject: " + e->GetName() + "]"); return true; } bool JSI_IGUIObject::focus(JSContext* cx, uint UNUSED(argc), JS::Value* vp) { JSAutoRequest rq(cx); JS::CallReceiver rec = JS::CallReceiverFromVp(vp); JS::RootedObject thisObj(cx, JS_THIS_OBJECT(cx, vp)); IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, thisObj, &JSI_IGUIObject::JSI_class, NULL); if (!e) return false; e->GetGUI()->SetFocusedObject(e); rec.rval().setUndefined(); return true; } bool JSI_IGUIObject::blur(JSContext* cx, uint UNUSED(argc), JS::Value* vp) { JSAutoRequest rq(cx); JS::CallReceiver rec = JS::CallReceiverFromVp(vp); JS::RootedObject thisObj(cx, JS_THIS_OBJECT(cx, vp)); IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, thisObj, &JSI_IGUIObject::JSI_class, NULL); if (!e) return false; e->GetGUI()->SetFocusedObject(NULL); rec.rval().setUndefined(); return true; } bool JSI_IGUIObject::getComputedSize(JSContext* cx, uint UNUSED(argc), JS::Value* vp) { JSAutoRequest rq(cx); JS::CallReceiver rec = JS::CallReceiverFromVp(vp); JS::RootedObject thisObj(cx, JS_THIS_OBJECT(cx, vp)); IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, thisObj, &JSI_IGUIObject::JSI_class, NULL); if (!e) return false; e->UpdateCachedSize(); CRect size = e->m_CachedActualSize; JS::RootedValue objVal(cx); try { ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface->CreateObject( &objVal, "left", size.left, "right", size.right, "top", size.top, "bottom", size.bottom); } catch (PSERROR_Scripting_ConversionFailed&) { debug_warn(L"Error creating size object!"); return false; } rec.rval().set(objVal); return true; } Index: ps/trunk/source/gui/scripting/JSInterface_IGUIObject.h =================================================================== --- ps/trunk/source/gui/scripting/JSInterface_IGUIObject.h (revision 22626) +++ ps/trunk/source/gui/scripting/JSInterface_IGUIObject.h (revision 22627) @@ -1,37 +1,37 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #ifndef INCLUDED_JSI_IGUIOBJECT #define INCLUDED_JSI_IGUIOBJECT #include "scriptinterface/ScriptInterface.h" namespace JSI_IGUIObject { extern JSClass JSI_class; extern JSFunctionSpec JSI_methods[]; bool getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp); - bool setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool UNUSED(strict), JS::MutableHandleValue vp); + bool setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp, JS::ObjectOpResult& result); bool toString(JSContext* cx, uint argc, JS::Value* vp); bool focus(JSContext* cx, uint argc, JS::Value* vp); bool blur(JSContext* cx, uint argc, JS::Value* vp); bool getComputedSize(JSContext* cx, uint argc, JS::Value* vp); bool getTextSize(JSContext* cx, uint argc, JS::Value* vp); void init(ScriptInterface& scriptInterface); } #endif // INCLUDED_JSI_IGUIOBJECT Index: ps/trunk/source/ps/ModIo.cpp =================================================================== --- ps/trunk/source/ps/ModIo.cpp (revision 22626) +++ ps/trunk/source/ps/ModIo.cpp (revision 22627) @@ -1,839 +1,841 @@ /* Copyright (C) 2019 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "precompiled.h" #include "ModIo.h" #include "i18n/L10n.h" #include "lib/file/file_system.h" #include "lib/sysdep/filesystem.h" #include "lib/sysdep/sysdep.h" #include "maths/MD5.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/GameSetup/Paths.h" #include "ps/Mod.h" #include "ps/ModInstaller.h" #include "ps/Util.h" #include "scriptinterface/ScriptConversions.h" #include "scriptinterface/ScriptInterface.h" #include #include ModIo* g_ModIo = nullptr; struct DownloadCallbackData { DownloadCallbackData() : fp(nullptr), md5(), hash_state(nullptr) { } DownloadCallbackData(FILE* _fp) : fp(_fp), md5() { hash_state = static_cast( sodium_malloc(crypto_generichash_statebytes())); ENSURE(hash_state); crypto_generichash_init(hash_state, nullptr, 0U, crypto_generichash_BYTES_MAX); } ~DownloadCallbackData() { if (hash_state) sodium_free(hash_state); } FILE* fp; MD5 md5; crypto_generichash_state* hash_state; }; ModIo::ModIo() : m_GamesRequest("/games"), m_CallbackData(nullptr) { // Get config values from the sytem namespace, or below (default). // This can be overridden on the command line. // // We do this so a malicious mod cannot change the base url and // get the user to make connections to someone else's endpoint. // If another user of the engine wants to provide different values // here, while still using the same engine version, they can just // provide some shortcut/script that sets these using command line // parameters. std::string pk_str; g_ConfigDB.GetValue(CFG_SYSTEM, "modio.public_key", pk_str); g_ConfigDB.GetValue(CFG_SYSTEM, "modio.v1.baseurl", m_BaseUrl); { std::string api_key; g_ConfigDB.GetValue(CFG_SYSTEM, "modio.v1.api_key", api_key); m_ApiKey = "api_key=" + api_key; } { std::string nameid; g_ConfigDB.GetValue(CFG_SYSTEM, "modio.v1.name_id", nameid); m_IdQuery = "name_id="+nameid; } m_CurlMulti = curl_multi_init(); ENSURE(m_CurlMulti); m_Curl = curl_easy_init(); ENSURE(m_Curl); // Capture error messages curl_easy_setopt(m_Curl, CURLOPT_ERRORBUFFER, m_ErrorBuffer); // Fail if the server did curl_easy_setopt(m_Curl, CURLOPT_FAILONERROR, 1L); // Disable signal handlers (required for multithreaded applications) curl_easy_setopt(m_Curl, CURLOPT_NOSIGNAL, 1L); // To minimise security risks, don't support redirects (except for file // downloads, for which this setting will be enabled). curl_easy_setopt(m_Curl, CURLOPT_FOLLOWLOCATION, 0L); // For file downloads, one redirect seems plenty for a CDN serving the files. curl_easy_setopt(m_Curl, CURLOPT_MAXREDIRS, 1L); m_Headers = NULL; std::string ua = "User-Agent: pyrogenesis "; ua += curl_version(); ua += " (https://play0ad.com/)"; m_Headers = curl_slist_append(m_Headers, ua.c_str()); curl_easy_setopt(m_Curl, CURLOPT_HTTPHEADER, m_Headers); if (sodium_init() < 0) ENSURE(0 && "Failed to initialize libsodium."); size_t bin_len = 0; if (sodium_base642bin((unsigned char*)&m_pk, sizeof m_pk, pk_str.c_str(), pk_str.size(), NULL, &bin_len, NULL, sodium_base64_VARIANT_ORIGINAL) != 0 || bin_len != sizeof m_pk) ENSURE(0 && "Failed to decode base64 public key. Please fix your configuration or mod.io will be unusable."); } ModIo::~ModIo() { // Clean things up to avoid unpleasant surprises, // and delete the temporary file if any. TearDownRequest(); if (m_DownloadProgressData.status == DownloadProgressStatus::DOWNLOADING) DeleteDownloadedFile(); curl_slist_free_all(m_Headers); curl_easy_cleanup(m_Curl); curl_multi_cleanup(m_CurlMulti); delete m_CallbackData; } size_t ModIo::ReceiveCallback(void* buffer, size_t size, size_t nmemb, void* userp) { ModIo* self = static_cast(userp); self->m_ResponseData += std::string((char*)buffer, (char*)buffer+size*nmemb); return size*nmemb; } size_t ModIo::DownloadCallback(void* buffer, size_t size, size_t nmemb, void* userp) { DownloadCallbackData* data = static_cast(userp); if (!data->fp) return 0; size_t len = fwrite(buffer, size, nmemb, data->fp); // Only update the hash with data we actually managed to write. // In case we did not write all of it we will fail the download, // but we do not want to have a possibly valid hash in that case. size_t written = len*size; data->md5.Update(static_cast(buffer), written); ENSURE(data->hash_state); crypto_generichash_update(data->hash_state, static_cast(buffer), written); return written; } int ModIo::DownloadProgressCallback(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t UNUSED(ultotal), curl_off_t UNUSED(ulnow)) { DownloadProgressData* data = static_cast(clientp); // If we got more data than curl expected, something is very wrong, abort. if (dltotal != 0 && dlnow > dltotal) return 1; data->progress = dltotal == 0 ? 0 : static_cast(dlnow) / static_cast(dltotal); return 0; } CURLMcode ModIo::SetupRequest(const std::string& url, bool fileDownload) { if (fileDownload) { // The download link will most likely redirect elsewhere, so allow that. // We verify the validity of the file later. curl_easy_setopt(m_Curl, CURLOPT_FOLLOWLOCATION, 1L); // Enable the progress meter curl_easy_setopt(m_Curl, CURLOPT_NOPROGRESS, 0L); // Set IO callbacks curl_easy_setopt(m_Curl, CURLOPT_WRITEFUNCTION, DownloadCallback); curl_easy_setopt(m_Curl, CURLOPT_WRITEDATA, static_cast(m_CallbackData)); curl_easy_setopt(m_Curl, CURLOPT_XFERINFOFUNCTION, DownloadProgressCallback); curl_easy_setopt(m_Curl, CURLOPT_XFERINFODATA, static_cast(&m_DownloadProgressData)); // Initialize the progress counter m_DownloadProgressData.progress = 0; } else { // To minimise security risks, don't support redirects curl_easy_setopt(m_Curl, CURLOPT_FOLLOWLOCATION, 0L); // Disable the progress meter curl_easy_setopt(m_Curl, CURLOPT_NOPROGRESS, 1L); // Set IO callbacks curl_easy_setopt(m_Curl, CURLOPT_WRITEFUNCTION, ReceiveCallback); curl_easy_setopt(m_Curl, CURLOPT_WRITEDATA, this); } m_ErrorBuffer[0] = '\0'; curl_easy_setopt(m_Curl, CURLOPT_URL, url.c_str()); return curl_multi_add_handle(m_CurlMulti, m_Curl); } void ModIo::TearDownRequest() { ENSURE(curl_multi_remove_handle(m_CurlMulti, m_Curl) == CURLM_OK); if (m_CallbackData) { if (m_CallbackData->fp) fclose(m_CallbackData->fp); m_CallbackData->fp = nullptr; } } void ModIo::StartGetGameId() { // Don't start such a request during active downloads. if (m_DownloadProgressData.status == DownloadProgressStatus::GAMEID || m_DownloadProgressData.status == DownloadProgressStatus::LISTING || m_DownloadProgressData.status == DownloadProgressStatus::DOWNLOADING) return; m_GameId.clear(); CURLMcode err = SetupRequest(m_BaseUrl+m_GamesRequest+"?"+m_ApiKey+"&"+m_IdQuery, false); if (err != CURLM_OK) { TearDownRequest(); m_DownloadProgressData.status = DownloadProgressStatus::FAILED_GAMEID; m_DownloadProgressData.error = fmt::sprintf( g_L10n.Translate("Failure while starting querying for game id. Error: %s; %s."), curl_multi_strerror(err), m_ErrorBuffer); return; } m_DownloadProgressData.status = DownloadProgressStatus::GAMEID; } void ModIo::StartListMods() { // Don't start such a request during active downloads. if (m_DownloadProgressData.status == DownloadProgressStatus::GAMEID || m_DownloadProgressData.status == DownloadProgressStatus::LISTING || m_DownloadProgressData.status == DownloadProgressStatus::DOWNLOADING) return; m_ModData.clear(); if (m_GameId.empty()) { LOGERROR("Game ID not fetched from mod.io. Call StartGetGameId first and wait for it to finish."); return; } CURLMcode err = SetupRequest(m_BaseUrl+m_GamesRequest+m_GameId+"/mods?"+m_ApiKey, false); if (err != CURLM_OK) { TearDownRequest(); m_DownloadProgressData.status = DownloadProgressStatus::FAILED_LISTING; m_DownloadProgressData.error = fmt::sprintf( g_L10n.Translate("Failure while starting querying for mods. Error: %s; %s."), curl_multi_strerror(err), m_ErrorBuffer); return; } m_DownloadProgressData.status = DownloadProgressStatus::LISTING; } void ModIo::StartDownloadMod(size_t idx) { // Don't start such a request during active downloads. if (m_DownloadProgressData.status == DownloadProgressStatus::GAMEID || m_DownloadProgressData.status == DownloadProgressStatus::LISTING || m_DownloadProgressData.status == DownloadProgressStatus::DOWNLOADING) return; if (idx >= m_ModData.size()) return; const Paths paths(g_args); const OsPath modUserPath = paths.UserData()/"mods"; const OsPath modPath = modUserPath/m_ModData[idx].properties["name_id"]; if (!DirectoryExists(modPath) && INFO::OK != CreateDirectories(modPath, 0700, false)) { m_DownloadProgressData.status = DownloadProgressStatus::FAILED_DOWNLOADING; m_DownloadProgressData.error = fmt::sprintf( g_L10n.Translate("Could not create mod directory: %s."), modPath.string8()); return; } // Name the file after the name_id, since using the filename would mean that // we could end up with multiple zip files in the folder that might not work // as expected for a user (since a later version might remove some files // that aren't compatible anymore with the engine version). // So we ignore the filename provided by the API and assume that we do not // care about handling update.zip files. If that is the case we would need // a way to find out what files are required by the current one and which // should be removed for everything to work. This seems to be too complicated // so we just do not support that usage. // NOTE: We do save the file under a slightly different name from the final // one, to ensure that in case a download aborts and the file stays // around, the game will not attempt to open the file which has not // been verified. m_DownloadFilePath = modPath/(m_ModData[idx].properties["name_id"]+".zip.temp"); delete m_CallbackData; m_CallbackData = new DownloadCallbackData(sys_OpenFile(m_DownloadFilePath, "wb")); if (!m_CallbackData->fp) { m_DownloadProgressData.status = DownloadProgressStatus::FAILED_DOWNLOADING; m_DownloadProgressData.error = fmt::sprintf( g_L10n.Translate("Could not open temporary file for mod download: %s."), m_DownloadFilePath.string8()); return; } CURLMcode err = SetupRequest(m_ModData[idx].properties["binary_url"], true); if (err != CURLM_OK) { TearDownRequest(); m_DownloadProgressData.status = DownloadProgressStatus::FAILED_DOWNLOADING; m_DownloadProgressData.error = fmt::sprintf( g_L10n.Translate("Failed to start the download. Error: %s; %s."), curl_multi_strerror(err), m_ErrorBuffer); return; } m_DownloadModID = idx; m_DownloadProgressData.status = DownloadProgressStatus::DOWNLOADING; } void ModIo::CancelRequest() { TearDownRequest(); switch (m_DownloadProgressData.status) { case DownloadProgressStatus::GAMEID: case DownloadProgressStatus::FAILED_GAMEID: m_DownloadProgressData.status = DownloadProgressStatus::NONE; break; case DownloadProgressStatus::LISTING: case DownloadProgressStatus::FAILED_LISTING: m_DownloadProgressData.status = DownloadProgressStatus::READY; break; case DownloadProgressStatus::DOWNLOADING: case DownloadProgressStatus::FAILED_DOWNLOADING: m_DownloadProgressData.status = DownloadProgressStatus::LISTED; DeleteDownloadedFile(); break; default: break; } } bool ModIo::AdvanceRequest(const ScriptInterface& scriptInterface) { // If the request was cancelled, stop trying to advance it if (m_DownloadProgressData.status != DownloadProgressStatus::GAMEID && m_DownloadProgressData.status != DownloadProgressStatus::LISTING && m_DownloadProgressData.status != DownloadProgressStatus::DOWNLOADING) return true; int stillRunning; CURLMcode err = curl_multi_perform(m_CurlMulti, &stillRunning); if (err != CURLM_OK) { std::string error = fmt::sprintf( g_L10n.Translate("Asynchronous download failure: %s, %s."), curl_multi_strerror(err), m_ErrorBuffer); TearDownRequest(); if (m_DownloadProgressData.status == DownloadProgressStatus::GAMEID) m_DownloadProgressData.status = DownloadProgressStatus::FAILED_GAMEID; else if (m_DownloadProgressData.status == DownloadProgressStatus::LISTING) m_DownloadProgressData.status = DownloadProgressStatus::FAILED_LISTING; else if (m_DownloadProgressData.status == DownloadProgressStatus::DOWNLOADING) { m_DownloadProgressData.status = DownloadProgressStatus::FAILED_DOWNLOADING; DeleteDownloadedFile(); } m_DownloadProgressData.error = error; return true; } CURLMsg* message; do { int in_queue; message = curl_multi_info_read(m_CurlMulti, &in_queue); if (!message) continue; CURLcode err = message->data.result; if (err == CURLE_OK) continue; std::string error = fmt::sprintf( g_L10n.Translate("Download failure. Server response: %s; %s."), curl_easy_strerror(err), m_ErrorBuffer); TearDownRequest(); if (m_DownloadProgressData.status == DownloadProgressStatus::GAMEID) m_DownloadProgressData.status = DownloadProgressStatus::FAILED_GAMEID; else if (m_DownloadProgressData.status == DownloadProgressStatus::LISTING) m_DownloadProgressData.status = DownloadProgressStatus::FAILED_LISTING; else if (m_DownloadProgressData.status == DownloadProgressStatus::DOWNLOADING) { m_DownloadProgressData.status = DownloadProgressStatus::FAILED_DOWNLOADING; DeleteDownloadedFile(); } m_DownloadProgressData.error = error; return true; } while (message); if (stillRunning) return false; // Download finished. TearDownRequest(); // Perform parsing and/or checks std::string error; switch (m_DownloadProgressData.status) { case DownloadProgressStatus::GAMEID: if (!ParseGameId(scriptInterface, error)) { m_DownloadProgressData.status = DownloadProgressStatus::FAILED_GAMEID; m_DownloadProgressData.error = error; break; } m_DownloadProgressData.status = DownloadProgressStatus::READY; break; case DownloadProgressStatus::LISTING: if (!ParseMods(scriptInterface, error)) { m_ModData.clear(); // Failed during parsing, make sure we don't provide partial data m_DownloadProgressData.status = DownloadProgressStatus::FAILED_LISTING; m_DownloadProgressData.error = error; break; } m_DownloadProgressData.status = DownloadProgressStatus::LISTED; break; case DownloadProgressStatus::DOWNLOADING: if (!VerifyDownloadedFile(error)) { m_DownloadProgressData.status = DownloadProgressStatus::FAILED_FILECHECK; m_DownloadProgressData.error = error; DeleteDownloadedFile(); break; } m_DownloadProgressData.status = DownloadProgressStatus::SUCCESS; { Paths paths(g_args); CModInstaller installer(paths.UserData() / "mods", paths.Cache()); installer.Install(m_DownloadFilePath, g_ScriptRuntime, false); } break; default: break; } return true; } bool ModIo::ParseGameId(const ScriptInterface& scriptInterface, std::string& err) { int id = -1; bool ret = ParseGameIdResponse(scriptInterface, m_ResponseData, id, err); m_ResponseData.clear(); if (!ret) return false; m_GameId = "/" + std::to_string(id); return true; } bool ModIo::ParseMods(const ScriptInterface& scriptInterface, std::string& err) { bool ret = ParseModsResponse(scriptInterface, m_ResponseData, m_ModData, m_pk, err); m_ResponseData.clear(); return ret; } void ModIo::DeleteDownloadedFile() { if (wunlink(m_DownloadFilePath) != 0) LOGERROR("Failed to delete temporary file."); m_DownloadFilePath = OsPath(); } bool ModIo::VerifyDownloadedFile(std::string& err) { // Verify filesize, as a first basic download check. { u64 filesize = std::stoull(m_ModData[m_DownloadModID].properties.at("filesize")); if (filesize != FileSize(m_DownloadFilePath)) { err = g_L10n.Translate("Mismatched filesize."); return false; } } ENSURE(m_CallbackData); // MD5 (because upstream provides it) // Just used to make sure there was no obvious corruption during transfer. { u8 digest[MD5::DIGESTSIZE]; m_CallbackData->md5.Final(digest); std::string md5digest = Hexify(digest, MD5::DIGESTSIZE); if (m_ModData[m_DownloadModID].properties.at("filehash_md5") != md5digest) { err = fmt::sprintf( g_L10n.Translate("Invalid file. Expected md5 %s, got %s."), m_ModData[m_DownloadModID].properties.at("filehash_md5").c_str(), md5digest); return false; } } // Verify file signature. // Used to make sure that the downloaded file was actually checked and signed // by Wildfire Games. And has not been tampered with by the API provider, or the CDN. unsigned char hash_fin[crypto_generichash_BYTES_MAX] = {}; ENSURE(m_CallbackData->hash_state); if (crypto_generichash_final(m_CallbackData->hash_state, hash_fin, sizeof hash_fin) != 0) { err = g_L10n.Translate("Failed to compute final hash."); return false; } if (crypto_sign_verify_detached(m_ModData[m_DownloadModID].sig.sig, hash_fin, sizeof hash_fin, m_pk.pk) != 0) { err = g_L10n.Translate("Failed to verify signature."); return false; } return true; } #define FAIL(...) STMT(err = fmt::sprintf(__VA_ARGS__); CLEANUP(); return false;) /** * Parses the current content of m_ResponseData to extract m_GameId. * * The JSON data is expected to look like * { "data": [{"id": 42, ...}, ...], ... } * where we are only interested in the value of the id property. * * @returns true iff it successfully parsed the id. */ bool ModIo::ParseGameIdResponse(const ScriptInterface& scriptInterface, const std::string& responseData, int& id, std::string& err) { #define CLEANUP() id = -1; JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); JS::RootedValue gameResponse(cx); if (!scriptInterface.ParseJSON(responseData, &gameResponse)) FAIL("Failed to parse response as JSON."); if (!gameResponse.isObject()) FAIL("response not an object."); JS::RootedObject gameResponseObj(cx, gameResponse.toObjectOrNull()); JS::RootedValue dataVal(cx); if (!JS_GetProperty(cx, gameResponseObj, "data", &dataVal)) FAIL("data property not in response."); // [{"id": 42, ...}, ...] if (!dataVal.isObject()) FAIL("data property not an object."); JS::RootedObject data(cx, dataVal.toObjectOrNull()); u32 length; - if (!JS_IsArrayObject(cx, data) || !JS_GetArrayLength(cx, data, &length) || !length) + bool isArray; + if (!JS_IsArrayObject(cx, data, &isArray) || !isArray || !JS_GetArrayLength(cx, data, &length) || !length) FAIL("data property not an array with at least one element."); // {"id": 42, ...} JS::RootedValue first(cx); if (!JS_GetElement(cx, data, 0, &first)) FAIL("Couldn't get first element."); if (!first.isObject()) FAIL("First element not an object."); JS::RootedObject firstObj(cx, &first.toObject()); bool hasIdProperty; if (!JS_HasProperty(cx, firstObj, "id", &hasIdProperty) || !hasIdProperty) FAIL("No id property in first element."); JS::RootedValue idProperty(cx); ENSURE(JS_GetProperty(cx, firstObj, "id", &idProperty)); // Make sure the property is not set to something that could be converted to a bogus value // TODO: We should be able to convert JS::Values to C++ variables in a way that actually // fails when types do not match (see https://trac.wildfiregames.com/ticket/5128). if (!idProperty.isNumber()) FAIL("id property not a number."); id = -1; if (!ScriptInterface::FromJSVal(cx, idProperty, id) || id <= 0) FAIL("Invalid id."); return true; #undef CLEANUP } /** * Parses the current content of m_ResponseData into m_ModData. * * The JSON data is expected to look like * { data: [modobj1, modobj2, ...], ... (including result_count) } * where modobjN has the following structure * { homepage_url: "url", name: "displayname", nameid: "short-non-whitespace-name", * summary: "short desc.", modfile: { version: "1.2.4", filename: "asdf.zip", * filehash: { md5: "deadbeef" }, filesize: 1234, download: { binary_url: "someurl", ... } }, ... }. * Only the listed properties are of interest to consumers, and we flatten * the modfile structure as that simplifies handling and there are no conflicts. */ bool ModIo::ParseModsResponse(const ScriptInterface& scriptInterface, const std::string& responseData, std::vector& modData, const PKStruct& pk, std::string& err) { // Make sure we don't end up passing partial results back #define CLEANUP() modData.clear(); JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); JS::RootedValue modResponse(cx); if (!scriptInterface.ParseJSON(responseData, &modResponse)) FAIL("Failed to parse response as JSON."); if (!modResponse.isObject()) FAIL("response not an object."); JS::RootedObject modResponseObj(cx, modResponse.toObjectOrNull()); JS::RootedValue dataVal(cx); if (!JS_GetProperty(cx, modResponseObj, "data", &dataVal)) FAIL("data property not in response."); // [modobj1, modobj2, ... ] if (!dataVal.isObject()) FAIL("data property not an object."); JS::RootedObject data(cx, dataVal.toObjectOrNull()); u32 length; - if (!JS_IsArrayObject(cx, data) || !JS_GetArrayLength(cx, data, &length) || !length) + bool isArray; + if (!JS_IsArrayObject(cx, data, &isArray) || !isArray || !JS_GetArrayLength(cx, data, &length) || !length) FAIL("data property not an array with at least one element."); modData.clear(); modData.reserve(length); for (u32 i = 0; i < length; ++i) { JS::RootedValue el(cx); if (!JS_GetElement(cx, data, i, &el) || !el.isObject()) FAIL("Failed to get array element object."); modData.emplace_back(); #define COPY_STRINGS(prefix, obj, ...) \ for (const std::string& prop : { __VA_ARGS__ }) \ { \ std::string val; \ if (!ScriptInterface::FromJSProperty(cx, obj, prop.c_str(), val)) \ FAIL("Failed to get %s from %s.", prop, #obj);\ modData.back().properties.emplace(prefix+prop, val); \ } // TODO: Currently the homepage_url field does not contain a non-null value for any entry. COPY_STRINGS("", el, "name", "name_id", "summary"); // Now copy over the modfile part, but without the pointless substructure JS::RootedObject elObj(cx, el.toObjectOrNull()); JS::RootedValue modFile(cx); if (!JS_GetProperty(cx, elObj, "modfile", &modFile)) FAIL("Failed to get modfile data."); if (!modFile.isObject()) FAIL("modfile not an object."); COPY_STRINGS("", modFile, "version", "filesize"); JS::RootedObject modFileObj(cx, modFile.toObjectOrNull()); JS::RootedValue filehash(cx); if (!JS_GetProperty(cx, modFileObj, "filehash", &filehash)) FAIL("Failed to get filehash data."); COPY_STRINGS("filehash_", filehash, "md5"); JS::RootedValue download(cx); if (!JS_GetProperty(cx, modFileObj, "download", &download)) FAIL("Failed to get download data."); COPY_STRINGS("", download, "binary_url"); // Parse metadata_blob (sig+deps) std::string metadata_blob; if (!ScriptInterface::FromJSProperty(cx, modFile, "metadata_blob", metadata_blob)) FAIL("Failed to get metadata_blob from modFile."); JS::RootedValue metadata(cx); if (!scriptInterface.ParseJSON(metadata_blob, &metadata)) FAIL("Failed to parse metadata_blob as JSON."); if (!metadata.isObject()) FAIL("metadata_blob not decoded as an object."); if (!ScriptInterface::FromJSProperty(cx, metadata, "dependencies", modData.back().dependencies)) FAIL("Failed to get dependencies from metadata_blob."); std::vector minisigs; if (!ScriptInterface::FromJSProperty(cx, metadata, "minisigs", minisigs)) FAIL("Failed to get minisigs from metadata_blob."); // Remove this entry if we did not find a valid matching signature. std::string signatureParsingErr; if (!ParseSignature(minisigs, modData.back().sig, pk, signatureParsingErr)) modData.pop_back(); #undef COPY_STRINGS } return true; #undef CLEANUP } /** * Parse signatures to find one that matches the public key, and has a valid global signature. * Returns true and sets @param sig to the valid matching signature. */ bool ModIo::ParseSignature(const std::vector& minisigs, SigStruct& sig, const PKStruct& pk, std::string& err) { #define CLEANUP() sig = {}; for (const std::string& file_sig : minisigs) { // Format of a .minisig file (created using minisign(1) with -SHm file.zip) // untrusted comment: .*\nb64sign_of_file\ntrusted comment: .*\nb64sign_of_sign_of_file_and_trusted_comment std::vector sig_lines; boost::split(sig_lines, file_sig, boost::is_any_of("\n")); if (sig_lines.size() < 4) FAIL("Invalid (too short) sig."); // Verify that both the untrusted comment and the trusted comment start with the correct prefix // because that is easy. const std::string untrusted_comment_prefix = "untrusted comment: "; const std::string trusted_comment_prefix = "trusted comment: "; if (!boost::algorithm::starts_with(sig_lines[0], untrusted_comment_prefix)) FAIL("Malformed untrusted comment."); if (!boost::algorithm::starts_with(sig_lines[2], trusted_comment_prefix)) FAIL("Malformed trusted comment."); // We only _really_ care about the second line which is the signature of the file (b64-encoded) // Also handling the other signature is nice, but not really required. const std::string& msg_sig = sig_lines[1]; size_t bin_len = 0; if (sodium_base642bin((unsigned char*)&sig, sizeof sig, msg_sig.c_str(), msg_sig.size(), NULL, &bin_len, NULL, sodium_base64_VARIANT_ORIGINAL) != 0 || bin_len != sizeof sig) FAIL("Failed to decode base64 sig."); cassert(sizeof pk.keynum == sizeof sig.keynum); if (memcmp(&pk.keynum, &sig.keynum, sizeof sig.keynum) != 0) continue; // mismatched key, try another one if (memcmp(&sig.sig_alg, "ED", 2) != 0) FAIL("Only hashed minisign signatures are supported."); // Signature matches our public key // Now verify the global signature (sig || trusted_comment) unsigned char global_sig[crypto_sign_BYTES]; if (sodium_base642bin(global_sig, sizeof global_sig, sig_lines[3].c_str(), sig_lines[3].size(), NULL, &bin_len, NULL, sodium_base64_VARIANT_ORIGINAL) != 0 || bin_len != sizeof global_sig) FAIL("Failed to decode base64 global_sig."); const std::string trusted_comment = sig_lines[2].substr(trusted_comment_prefix.size()); unsigned char* sig_and_trusted_comment = (unsigned char*)sodium_malloc((sizeof sig.sig) + trusted_comment.size()); if (!sig_and_trusted_comment) FAIL("sodium_malloc failed."); memcpy(sig_and_trusted_comment, sig.sig, sizeof sig.sig); memcpy(sig_and_trusted_comment + sizeof sig.sig, trusted_comment.data(), trusted_comment.size()); if (crypto_sign_verify_detached(global_sig, sig_and_trusted_comment, (sizeof sig.sig) + trusted_comment.size(), pk.pk) != 0) { err = "Failed to verify global signature."; sodium_free(sig_and_trusted_comment); return false; } sodium_free(sig_and_trusted_comment); // Valid global sig, and the keynum matches the real one return true; } return false; #undef CLEANUP } #undef FAIL Index: ps/trunk/source/ps/VisualReplay.cpp =================================================================== --- ps/trunk/source/ps/VisualReplay.cpp (revision 22626) +++ ps/trunk/source/ps/VisualReplay.cpp (revision 22627) @@ -1,529 +1,530 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "VisualReplay.h" #include "graphics/GameView.h" #include "gui/GUIManager.h" #include "lib/allocators/shared_ptr.h" #include "lib/external_libraries/libsdl.h" #include "lib/utf8.h" #include "network/NetClient.h" #include "network/NetServer.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/GameSetup/Paths.h" #include "ps/Mod.h" #include "ps/Pyrogenesis.h" #include "ps/Replay.h" #include "ps/Util.h" #include "scriptinterface/ScriptInterface.h" /** * Filter too short replays (value in seconds). */ const u8 minimumReplayDuration = 3; OsPath VisualReplay::GetDirectoryPath() { return Paths(g_args).UserData() / "replays" / engine_version; } OsPath VisualReplay::GetCacheFilePath() { return GetDirectoryPath() / L"replayCache.json"; } OsPath VisualReplay::GetTempCacheFilePath() { return GetDirectoryPath() / L"replayCache_temp.json"; } bool VisualReplay::StartVisualReplay(const OsPath& directory) { ENSURE(!g_NetServer); ENSURE(!g_NetClient); ENSURE(!g_Game); const OsPath replayFile = VisualReplay::GetDirectoryPath() / directory / L"commands.txt"; if (!FileExists(replayFile)) return false; g_Game = new CGame(false, false); return g_Game->StartVisualReplay(replayFile); } bool VisualReplay::ReadCacheFile(const ScriptInterface& scriptInterface, JS::MutableHandleObject cachedReplaysObject) { JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); if (!FileExists(GetCacheFilePath())) return false; std::ifstream cacheStream(OsString(GetCacheFilePath()).c_str()); CStr cacheStr((std::istreambuf_iterator(cacheStream)), std::istreambuf_iterator()); cacheStream.close(); JS::RootedValue cachedReplays(cx); if (scriptInterface.ParseJSON(cacheStr, &cachedReplays)) { cachedReplaysObject.set(&cachedReplays.toObject()); - if (JS_IsArrayObject(cx, cachedReplaysObject)) + bool isArray; + if (JS_IsArrayObject(cx, cachedReplaysObject, &isArray) && isArray) return true; } LOGWARNING("The replay cache file is corrupted, it will be deleted"); wunlink(GetCacheFilePath()); return false; } void VisualReplay::StoreCacheFile(const ScriptInterface& scriptInterface, JS::HandleObject replays) { JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); JS::RootedValue replaysRooted(cx, JS::ObjectValue(*replays)); std::ofstream cacheStream(OsString(GetTempCacheFilePath()).c_str(), std::ofstream::out | std::ofstream::trunc); cacheStream << scriptInterface.StringifyJSON(&replaysRooted); cacheStream.close(); wunlink(GetCacheFilePath()); if (wrename(GetTempCacheFilePath(), GetCacheFilePath())) LOGERROR("Could not store the replay cache"); } JS::HandleObject VisualReplay::ReloadReplayCache(const ScriptInterface& scriptInterface, bool compareFiles) { TIMER(L"ReloadReplayCache"); JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); // Maps the filename onto the index and size typedef std::map> replayCacheMap; replayCacheMap fileList; JS::RootedObject cachedReplaysObject(cx); if (ReadCacheFile(scriptInterface, &cachedReplaysObject)) { // Create list of files included in the cache u32 cacheLength = 0; JS_GetArrayLength(cx, cachedReplaysObject, &cacheLength); for (u32 j = 0; j < cacheLength; ++j) { JS::RootedValue replay(cx); JS_GetElement(cx, cachedReplaysObject, j, &replay); JS::RootedValue file(cx); OsPath fileName; double fileSize; scriptInterface.GetProperty(replay, "directory", fileName); scriptInterface.GetProperty(replay, "fileSize", fileSize); fileList[fileName] = std::make_pair(j, fileSize); } } JS::RootedObject replays(cx, JS_NewArrayObject(cx, 0)); DirectoryNames directories; if (GetDirectoryEntries(GetDirectoryPath(), nullptr, &directories) != INFO::OK) return replays; bool newReplays = false; std::vector copyFromOldCache; // Specifies where the next replay should be kept u32 i = 0; for (const OsPath& directory : directories) { // This cannot use IsQuitRequested(), because the current loop and that function both run in the main thread. // So SDL events are not processed unless called explicitly here. if (SDL_QuitRequested()) // Don't return, because we want to save our progress break; const OsPath replayFile = GetDirectoryPath() / directory / L"commands.txt"; bool isNew = true; replayCacheMap::iterator it = fileList.find(directory); if (it != fileList.end()) { if (compareFiles) { if (!FileExists(replayFile)) continue; CFileInfo fileInfo; GetFileInfo(replayFile, &fileInfo); if (fileInfo.Size() == it->second.second) isNew = false; } else isNew = false; } if (isNew) { JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, directory)); if (replayData.isNull()) { if (!FileExists(replayFile)) continue; CFileInfo fileInfo; GetFileInfo(replayFile, &fileInfo); scriptInterface.CreateObject( &replayData, "directory", directory.string(), "fileSize", static_cast(fileInfo.Size())); } JS_SetElement(cx, replays, i++, replayData); newReplays = true; } else copyFromOldCache.push_back(it->second.first); } debug_printf( "Loading %lu cached replays, removed %lu outdated entries, loaded %i new entries\n", (unsigned long)fileList.size(), (unsigned long)(fileList.size() - copyFromOldCache.size()), i); if (!newReplays && fileList.empty()) return replays; // No replay was changed, so just return the cache if (!newReplays && fileList.size() == copyFromOldCache.size()) return cachedReplaysObject; { // Copy the replays from the old cache that are not deleted if (!copyFromOldCache.empty()) for (u32 j : copyFromOldCache) { JS::RootedValue replay(cx); JS_GetElement(cx, cachedReplaysObject, j, &replay); JS_SetElement(cx, replays, i++, replay); } } StoreCacheFile(scriptInterface, replays); return replays; } JS::Value VisualReplay::GetReplays(const ScriptInterface& scriptInterface, bool compareFiles) { TIMER(L"GetReplays"); JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); JS::RootedObject replays(cx, ReloadReplayCache(scriptInterface, compareFiles)); // Only take entries with data JS::RootedValue replaysWithoutNullEntries(cx); scriptInterface.CreateArray(&replaysWithoutNullEntries); u32 replaysLength = 0; JS_GetArrayLength(cx, replays, &replaysLength); for (u32 j = 0, i = 0; j < replaysLength; ++j) { JS::RootedValue replay(cx); JS_GetElement(cx, replays, j, &replay); if (scriptInterface.HasProperty(replay, "attribs")) scriptInterface.SetPropertyInt(replaysWithoutNullEntries, i++, replay); } return replaysWithoutNullEntries; } /** * Move the cursor backwards until a newline was read or the beginning of the file was found. * Either way the cursor points to the beginning of a newline. * * @return The current cursor position or -1 on error. */ inline off_t goBackToLineBeginning(std::istream* replayStream, const OsPath& fileName, off_t fileSize) { int currentPos; char character; for (int characters = 0; characters < 10000; ++characters) { currentPos = (int) replayStream->tellg(); // Stop when reached the beginning of the file if (currentPos == 0) return currentPos; if (!replayStream->good()) { LOGERROR("Unknown error when returning to the last line (%i of %lu) of %s", currentPos, fileSize, fileName.string8().c_str()); return -1; } // Stop when reached newline replayStream->get(character); if (character == '\n') return currentPos; // Otherwise go back one character. // Notice: -1 will set the cursor back to the most recently read character. replayStream->seekg(-2, std::ios_base::cur); } LOGERROR("Infinite loop when going back to a line beginning in %s", fileName.string8().c_str()); return -1; } /** * Compute game duration in seconds. Assume constant turn length. * Find the last line that starts with "turn" by reading the file backwards. * * @return seconds or -1 on error */ inline int getReplayDuration(std::istream* replayStream, const OsPath& fileName, off_t fileSize) { CStr type; // Move one character before the file-end replayStream->seekg(-2, std::ios_base::end); // Infinite loop protection, should never occur. // There should be about 5 lines to read until a turn is found. for (int linesRead = 1; linesRead < 1000; ++linesRead) { off_t currentPosition = goBackToLineBeginning(replayStream, fileName, fileSize); // Read error or reached file beginning. No turns exist. if (currentPosition < 1) return -1; if (!replayStream->good()) { LOGERROR("Read error when determining replay duration at %i of %llu in %s", currentPosition - 2, fileSize, fileName.string8().c_str()); return -1; } // Found last turn, compute duration. if (currentPosition + 4 < fileSize && (*replayStream >> type).good() && type == "turn") { u32 turn = 0, turnLength = 0; *replayStream >> turn >> turnLength; return (turn+1) * turnLength / 1000; // add +1 as turn numbers starts with 0 } // Otherwise move cursor back to the character before the last newline replayStream->seekg(currentPosition - 2, std::ios_base::beg); } LOGERROR("Infinite loop when determining replay duration for %s", fileName.string8().c_str()); return -1; } JS::Value VisualReplay::LoadReplayData(const ScriptInterface& scriptInterface, const OsPath& directory) { // The directory argument must not be constant, otherwise concatenating will fail const OsPath replayFile = GetDirectoryPath() / directory / L"commands.txt"; if (!FileExists(replayFile)) return JS::NullValue(); // Get file size and modification date CFileInfo fileInfo; GetFileInfo(replayFile, &fileInfo); const off_t fileSize = fileInfo.Size(); if (fileSize == 0) return JS::NullValue(); std::ifstream* replayStream = new std::ifstream(OsString(replayFile).c_str()); CStr type; if (!(*replayStream >> type).good()) { LOGERROR("Couldn't open %s.", replayFile.string8().c_str()); SAFE_DELETE(replayStream); return JS::NullValue(); } if (type != "start") { LOGWARNING("The replay %s doesn't begin with 'start'!", replayFile.string8().c_str()); SAFE_DELETE(replayStream); return JS::NullValue(); } // Parse header / first line CStr header; std::getline(*replayStream, header); JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); JS::RootedValue attribs(cx); if (!scriptInterface.ParseJSON(header, &attribs)) { LOGERROR("Couldn't parse replay header of %s", replayFile.string8().c_str()); SAFE_DELETE(replayStream); return JS::NullValue(); } // Ensure "turn" after header if (!(*replayStream >> type).good() || type != "turn") { SAFE_DELETE(replayStream); return JS::NullValue(); // there are no turns at all } // Don't process files of rejoined clients u32 turn = 1; *replayStream >> turn; if (turn != 0) { SAFE_DELETE(replayStream); return JS::NullValue(); } int duration = getReplayDuration(replayStream, replayFile, fileSize); SAFE_DELETE(replayStream); // Ensure minimum duration if (duration < minimumReplayDuration) return JS::NullValue(); // Return the actual data JS::RootedValue replayData(cx); scriptInterface.CreateObject( &replayData, "directory", directory.string(), "fileSize", static_cast(fileSize), "duration", static_cast(duration)); scriptInterface.SetProperty(replayData, "attribs", attribs); return replayData; } bool VisualReplay::DeleteReplay(const OsPath& replayDirectory) { if (replayDirectory.empty()) return false; const OsPath directory = GetDirectoryPath() / replayDirectory; return DirectoryExists(directory) && DeleteDirectory(directory) == INFO::OK; } JS::Value VisualReplay::GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const OsPath& directoryName) { // Create empty JS object JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); JSAutoRequest rq(cx); JS::RootedValue attribs(cx); pCxPrivate->pScriptInterface->CreateObject(&attribs); // Return empty object if file doesn't exist const OsPath replayFile = GetDirectoryPath() / directoryName / L"commands.txt"; if (!FileExists(replayFile)) return attribs; // Open file std::istream* replayStream = new std::ifstream(OsString(replayFile).c_str()); CStr type, line; ENSURE((*replayStream >> type).good() && type == "start"); // Read and return first line std::getline(*replayStream, line); pCxPrivate->pScriptInterface->ParseJSON(line, &attribs); SAFE_DELETE(replayStream);; return attribs; } void VisualReplay::AddReplayToCache(const ScriptInterface& scriptInterface, const CStrW& directoryName) { TIMER(L"AddReplayToCache"); JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, OsPath(directoryName))); if (replayData.isNull()) return; JS::RootedObject cachedReplaysObject(cx); if (!ReadCacheFile(scriptInterface, &cachedReplaysObject)) cachedReplaysObject = JS_NewArrayObject(cx, 0); u32 cacheLength = 0; JS_GetArrayLength(cx, cachedReplaysObject, &cacheLength); JS_SetElement(cx, cachedReplaysObject, cacheLength, replayData); StoreCacheFile(scriptInterface, cachedReplaysObject); } void VisualReplay::SaveReplayMetadata(ScriptInterface* scriptInterface) { JSContext* cx = scriptInterface->GetContext(); JSAutoRequest rq(cx); JS::RootedValue metadata(cx); JS::RootedValue global(cx, scriptInterface->GetGlobalObject()); if (!scriptInterface->CallFunction(global, "getReplayMetadata", &metadata)) { LOGERROR("Could not save replay metadata!"); return; } // Get the directory of the currently active replay const OsPath fileName = g_Game->GetReplayLogger().GetDirectory() / L"metadata.json"; CreateDirectories(fileName.Parent(), 0700); std::ofstream stream (OsString(fileName).c_str(), std::ofstream::out | std::ofstream::trunc); stream << scriptInterface->StringifyJSON(&metadata, false); stream.close(); debug_printf("Saved replay metadata to %s\n", fileName.string8().c_str()); } bool VisualReplay::HasReplayMetadata(const OsPath& directoryName) { const OsPath filePath(GetDirectoryPath() / directoryName / L"metadata.json"); if (!FileExists(filePath)) return false; CFileInfo fileInfo; GetFileInfo(filePath, &fileInfo); return fileInfo.Size() > 0; } JS::Value VisualReplay::GetReplayMetadata(ScriptInterface::CxPrivate* pCxPrivate, const OsPath& directoryName) { if (!HasReplayMetadata(directoryName)) return JS::NullValue(); JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); JSAutoRequest rq(cx); JS::RootedValue metadata(cx); std::ifstream* stream = new std::ifstream(OsString(GetDirectoryPath() / directoryName / L"metadata.json").c_str()); ENSURE(stream->good()); CStr line; std::getline(*stream, line); stream->close(); SAFE_DELETE(stream); pCxPrivate->pScriptInterface->ParseJSON(line, &metadata); return metadata; } Index: ps/trunk/source/scriptinterface/ScriptConversions.h =================================================================== --- ps/trunk/source/scriptinterface/ScriptConversions.h (revision 22626) +++ ps/trunk/source/scriptinterface/ScriptConversions.h (revision 22627) @@ -1,108 +1,109 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #ifndef INCLUDED_SCRIPTCONVERSIONS #define INCLUDED_SCRIPTCONVERSIONS #include "ScriptInterface.h" #include "scriptinterface/ScriptExtraHeaders.h" // for typed arrays #include template static void ToJSVal_vector(JSContext* cx, JS::MutableHandleValue ret, const std::vector& val) { JSAutoRequest rq(cx); JS::RootedObject obj(cx, JS_NewArrayObject(cx, 0)); if (!obj) { ret.setUndefined(); return; } ENSURE(val.size() <= std::numeric_limits::max()); for (u32 i = 0; i < val.size(); ++i) { JS::RootedValue el(cx); ScriptInterface::ToJSVal(cx, &el, val[i]); JS_SetElement(cx, obj, i, el); } ret.setObject(*obj); } #define FAIL(msg) STMT(JS_ReportError(cx, msg); return false) template static bool FromJSVal_vector(JSContext* cx, JS::HandleValue v, std::vector& out) { JSAutoRequest rq(cx); JS::RootedObject obj(cx); if (!v.isObject()) FAIL("Argument must be an array"); + bool isArray; obj = &v.toObject(); - if (!(JS_IsArrayObject(cx, obj) || JS_IsTypedArrayObject(obj))) + if ((!JS_IsArrayObject(cx, obj, &isArray) || !isArray) && !JS_IsTypedArrayObject(obj)) FAIL("Argument must be an array"); u32 length; if (!JS_GetArrayLength(cx, obj, &length)) FAIL("Failed to get array length"); out.reserve(length); for (u32 i = 0; i < length; ++i) { JS::RootedValue el(cx); if (!JS_GetElement(cx, obj, i, &el)) FAIL("Failed to read array element"); T el2; if (!ScriptInterface::FromJSVal(cx, el, el2)) return false; out.push_back(el2); } return true; } #undef FAIL #define JSVAL_VECTOR(T) \ template<> void ScriptInterface::ToJSVal >(JSContext* cx, JS::MutableHandleValue ret, const std::vector& val) \ { \ ToJSVal_vector(cx, ret, val); \ } \ template<> bool ScriptInterface::FromJSVal >(JSContext* cx, JS::HandleValue v, std::vector& out) \ { \ return FromJSVal_vector(cx, v, out); \ } template bool ScriptInterface::FromJSProperty(JSContext* cx, const JS::HandleValue val, const char* name, T& ret) { if (!val.isObject()) return false; JSAutoRequest rq(cx); JS::RootedObject obj(cx, &val.toObject()); bool hasProperty; if (!JS_HasProperty(cx, obj, name, &hasProperty) || !hasProperty) return false; JS::RootedValue value(cx); if (!JS_GetProperty(cx, obj, name, &value)) return false; return FromJSVal(cx, value, ret); } #endif //INCLUDED_SCRIPTCONVERSIONS Index: ps/trunk/source/scriptinterface/ScriptEngine.h =================================================================== --- ps/trunk/source/scriptinterface/ScriptEngine.h (revision 22626) +++ ps/trunk/source/scriptinterface/ScriptEngine.h (revision 22627) @@ -1,54 +1,56 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #ifndef INCLUDED_SCRIPTENGINE #define INCLUDED_SCRIPTENGINE #include "ScriptTypes.h" #include "ps/Singleton.h" +#include "js/Initialization.h" + /** * A class using the RAII (Resource Acquisition Is Initialization) idiom to manage initialization * and shutdown of the SpiderMonkey script engine. It also keeps a count of active script runtimes * in order to validate the following constraints: * 1. JS_Init must be called before any ScriptRuntimes are initialized * 2. JS_Shutdown must be called after all ScriptRuntimes have been destroyed */ class ScriptEngine : public Singleton { public: ScriptEngine() { ENSURE(m_Runtimes.empty() && "JS_Init must be called before any runtimes are created!"); JS_Init(); } ~ScriptEngine() { ENSURE(m_Runtimes.empty() && "All runtimes must be destroyed before calling JS_ShutDown!"); JS_ShutDown(); } void RegisterRuntime(const JSRuntime* rt) { m_Runtimes.push_back(rt); } void UnRegisterRuntime(const JSRuntime* rt) { m_Runtimes.remove(rt); } private: std::list m_Runtimes; }; #endif // INCLUDED_SCRIPTENGINE Index: ps/trunk/source/scriptinterface/ScriptInterface.cpp =================================================================== --- ps/trunk/source/scriptinterface/ScriptInterface.cpp (revision 22626) +++ ps/trunk/source/scriptinterface/ScriptInterface.cpp (revision 22627) @@ -1,1165 +1,1160 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "ScriptInterface.h" #include "ScriptRuntime.h" #include "ScriptStats.h" #include "lib/debug.h" #include "lib/utf8.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/Profile.h" #include "ps/utf16string.h" #include #include #define BOOST_MULTI_INDEX_DISABLE_SERIALIZATION #include #include #include #include #include #include #include #include "valgrind.h" #include "scriptinterface/ScriptExtraHeaders.h" /** * @file * Abstractions of various SpiderMonkey features. * Engine code should be using functions of these interfaces rather than * directly accessing the underlying JS api. */ struct ScriptInterface_impl { ScriptInterface_impl(const char* nativeScopeName, const shared_ptr& runtime); ~ScriptInterface_impl(); void Register(const char* name, JSNative fptr, uint nargs) const; // Take care to keep this declaration before heap rooted members. Destructors of heap rooted // members have to be called before the runtime destructor. shared_ptr m_runtime; JSContext* m_cx; JS::PersistentRootedObject m_glob; // global scope object JSCompartment* m_comp; boost::rand48* m_rng; JS::PersistentRootedObject m_nativeScope; // native function scope object }; namespace { JSClass global_class = { "global", JSCLASS_GLOBAL_FLAGS, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook }; void ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report) { JSAutoRequest rq(cx); std::stringstream msg; bool isWarning = JSREPORT_IS_WARNING(report->flags); msg << (isWarning ? "JavaScript warning: " : "JavaScript error: "); if (report->filename) { msg << report->filename; msg << " line " << report->lineno << "\n"; } msg << message; // If there is an exception, then print its stack trace JS::RootedValue excn(cx); if (JS_GetPendingException(cx, &excn) && excn.isObject()) { JS::RootedValue stackVal(cx); JS::RootedObject excnObj(cx, &excn.toObject()); JS_GetProperty(cx, excnObj, "stack", &stackVal); std::string stackText; ScriptInterface::FromJSVal(cx, stackVal, stackText); std::istringstream stream(stackText); for (std::string line; std::getline(stream, line);) msg << "\n " << line; } if (isWarning) LOGWARNING("%s", msg.str().c_str()); else LOGERROR("%s", msg.str().c_str()); // When running under Valgrind, print more information in the error message // VALGRIND_PRINTF_BACKTRACE("->"); } // Functions in the global namespace: bool print(JSContext* cx, uint argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); for (uint i = 0; i < args.length(); ++i) { std::wstring str; if (!ScriptInterface::FromJSVal(cx, args[i], str)) return false; debug_printf("%s", utf8_from_wstring(str).c_str()); } fflush(stdout); args.rval().setUndefined(); return true; } bool logmsg(JSContext* cx, uint argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (args.length() < 1) { args.rval().setUndefined(); return true; } std::wstring str; if (!ScriptInterface::FromJSVal(cx, args[0], str)) return false; LOGMESSAGE("%s", utf8_from_wstring(str)); args.rval().setUndefined(); return true; } bool warn(JSContext* cx, uint argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (args.length() < 1) { args.rval().setUndefined(); return true; } std::wstring str; if (!ScriptInterface::FromJSVal(cx, args[0], str)) return false; LOGWARNING("%s", utf8_from_wstring(str)); args.rval().setUndefined(); return true; } bool error(JSContext* cx, uint argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (args.length() < 1) { args.rval().setUndefined(); return true; } std::wstring str; if (!ScriptInterface::FromJSVal(cx, args[0], str)) return false; LOGERROR("%s", utf8_from_wstring(str)); args.rval().setUndefined(); return true; } bool deepcopy(JSContext* cx, uint argc, JS::Value* vp) { JSAutoRequest rq(cx); JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (args.length() < 1) { args.rval().setUndefined(); return true; } JS::RootedValue ret(cx); if (!JS_StructuredClone(cx, args[0], &ret, NULL, NULL)) return false; args.rval().set(ret); return true; } bool deepfreeze(JSContext* cx, uint argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (args.length() != 1 || !args.get(0).isObject()) { JS_ReportError(cx, "deepfreeze requires exactly one object as an argument."); return false; } ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface->FreezeObject(args.get(0), true); args.rval().set(args.get(0)); return true; } bool ProfileStart(JSContext* cx, uint argc, JS::Value* vp) { const char* name = "(ProfileStart)"; JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (args.length() >= 1) { std::string str; if (!ScriptInterface::FromJSVal(cx, args[0], str)) return false; typedef boost::flyweight< std::string, boost::flyweights::no_tracking, boost::flyweights::no_locking > StringFlyweight; name = StringFlyweight(str).get().c_str(); } if (CProfileManager::IsInitialised() && ThreadUtil::IsMainThread()) g_Profiler.StartScript(name); g_Profiler2.RecordRegionEnter(name); args.rval().setUndefined(); return true; } bool ProfileStop(JSContext* UNUSED(cx), uint UNUSED(argc), JS::Value* vp) { JS::CallReceiver rec = JS::CallReceiverFromVp(vp); if (CProfileManager::IsInitialised() && ThreadUtil::IsMainThread()) g_Profiler.Stop(); g_Profiler2.RecordRegionLeave(); rec.rval().setUndefined(); return true; } bool ProfileAttribute(JSContext* cx, uint argc, JS::Value* vp) { const char* name = "(ProfileAttribute)"; JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (args.length() >= 1) { std::string str; if (!ScriptInterface::FromJSVal(cx, args[0], str)) return false; typedef boost::flyweight< std::string, boost::flyweights::no_tracking, boost::flyweights::no_locking > StringFlyweight; name = StringFlyweight(str).get().c_str(); } g_Profiler2.RecordAttribute("%s", name); args.rval().setUndefined(); return true; } // Math override functions: // boost::uniform_real is apparently buggy in Boost pre-1.47 - for integer generators // it returns [min,max], not [min,max). The bug was fixed in 1.47. // We need consistent behaviour, so manually implement the correct version: static double generate_uniform_real(boost::rand48& rng, double min, double max) { while (true) { double n = (double)(rng() - rng.min()); double d = (double)(rng.max() - rng.min()) + 1.0; ENSURE(d > 0 && n >= 0 && n <= d); double r = n / d * (max - min) + min; if (r < max) return r; } } bool Math_random(JSContext* cx, uint UNUSED(argc), JS::Value* vp) { JS::CallReceiver rec = JS::CallReceiverFromVp(vp); double r; if (!ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface->MathRandom(r)) return false; rec.rval().setNumber(r); return true; } } // anonymous namespace bool ScriptInterface::MathRandom(double& nbr) { if (m->m_rng == NULL) return false; nbr = generate_uniform_real(*(m->m_rng), 0.0, 1.0); return true; } ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, const shared_ptr& runtime) : m_runtime(runtime), m_glob(runtime->m_rt), m_nativeScope(runtime->m_rt) { bool ok; m_cx = JS_NewContext(m_runtime->m_rt, STACK_CHUNK_SIZE); ENSURE(m_cx); JS_SetOffthreadIonCompilationEnabled(m_runtime->m_rt, true); // For GC debugging: // JS_SetGCZeal(m_cx, 2, JS_DEFAULT_ZEAL_FREQ); JS_SetContextPrivate(m_cx, NULL); JS_SetErrorReporter(m_runtime->m_rt, ErrorReporter); JS_SetGlobalJitCompilerOption(m_runtime->m_rt, JSJITCOMPILER_ION_ENABLE, 1); JS_SetGlobalJitCompilerOption(m_runtime->m_rt, JSJITCOMPILER_BASELINE_ENABLE, 1); JS::RuntimeOptionsRef(m_cx) .setExtraWarnings(true) .setWerror(false) - .setVarObjFix(true) .setStrictMode(true); JS::CompartmentOptions opt; opt.setVersion(JSVERSION_LATEST); // Keep JIT code during non-shrinking GCs. This brings a quite big performance improvement. opt.setPreserveJitCode(true); JSAutoRequest rq(m_cx); JS::RootedObject globalRootedVal(m_cx, JS_NewGlobalObject(m_cx, &global_class, NULL, JS::OnNewGlobalHookOption::FireOnNewGlobalHook, opt)); m_comp = JS_EnterCompartment(m_cx, globalRootedVal); ok = JS_InitStandardClasses(m_cx, globalRootedVal); ENSURE(ok); m_glob = globalRootedVal.get(); JS_DefineProperty(m_cx, m_glob, "global", globalRootedVal, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); m_nativeScope = JS_DefineObject(m_cx, m_glob, nativeScopeName, nullptr, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); JS_DefineFunction(m_cx, globalRootedVal, "print", ::print, 0, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); JS_DefineFunction(m_cx, globalRootedVal, "log", ::logmsg, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); JS_DefineFunction(m_cx, globalRootedVal, "warn", ::warn, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); JS_DefineFunction(m_cx, globalRootedVal, "error", ::error, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); JS_DefineFunction(m_cx, globalRootedVal, "clone", ::deepcopy, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); JS_DefineFunction(m_cx, globalRootedVal, "deepfreeze", ::deepfreeze, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); Register("ProfileStart", ::ProfileStart, 1); Register("ProfileStop", ::ProfileStop, 0); Register("ProfileAttribute", ::ProfileAttribute, 1); runtime->RegisterContext(m_cx); } ScriptInterface_impl::~ScriptInterface_impl() { m_runtime->UnRegisterContext(m_cx); { JSAutoRequest rq(m_cx); JS_LeaveCompartment(m_cx, m_comp); } JS_DestroyContext(m_cx); } void ScriptInterface_impl::Register(const char* name, JSNative fptr, uint nargs) const { JSAutoRequest rq(m_cx); JS::RootedObject nativeScope(m_cx, m_nativeScope); JS::RootedFunction func(m_cx, JS_DefineFunction(m_cx, nativeScope, name, fptr, nargs, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)); } ScriptInterface::ScriptInterface(const char* nativeScopeName, const char* debugName, const shared_ptr& runtime) : m(new ScriptInterface_impl(nativeScopeName, runtime)) { // Profiler stats table isn't thread-safe, so only enable this on the main thread if (ThreadUtil::IsMainThread()) { if (g_ScriptStatsTable) g_ScriptStatsTable->Add(this, debugName); } m_CxPrivate.pScriptInterface = this; JS_SetContextPrivate(m->m_cx, (void*)&m_CxPrivate); } ScriptInterface::~ScriptInterface() { if (ThreadUtil::IsMainThread()) { if (g_ScriptStatsTable) g_ScriptStatsTable->Remove(this); } } void ScriptInterface::SetCallbackData(void* pCBData) { m_CxPrivate.pCBData = pCBData; } ScriptInterface::CxPrivate* ScriptInterface::GetScriptInterfaceAndCBData(JSContext* cx) { CxPrivate* pCxPrivate = (CxPrivate*)JS_GetContextPrivate(cx); return pCxPrivate; } bool ScriptInterface::LoadGlobalScripts() { // Ignore this failure in tests if (!g_VFS) return false; // Load and execute *.js in the global scripts directory VfsPaths pathnames; vfs::GetPathnames(g_VFS, L"globalscripts/", L"*.js", pathnames); for (const VfsPath& path : pathnames) if (!LoadGlobalScriptFile(path)) { LOGERROR("LoadGlobalScripts: Failed to load script %s", path.string8()); return false; } return true; } bool ScriptInterface::ReplaceNondeterministicRNG(boost::rand48& rng) { JSAutoRequest rq(m->m_cx); JS::RootedValue math(m->m_cx); JS::RootedObject global(m->m_cx, m->m_glob); if (JS_GetProperty(m->m_cx, global, "Math", &math) && math.isObject()) { JS::RootedObject mathObj(m->m_cx, &math.toObject()); JS::RootedFunction random(m->m_cx, JS_DefineFunction(m->m_cx, mathObj, "random", Math_random, 0, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)); if (random) { m->m_rng = &rng; return true; } } LOGERROR("ReplaceNondeterministicRNG: failed to replace Math.random"); return false; } void ScriptInterface::Register(const char* name, JSNative fptr, size_t nargs) const { m->Register(name, fptr, (uint)nargs); } JSContext* ScriptInterface::GetContext() const { return m->m_cx; } JSRuntime* ScriptInterface::GetJSRuntime() const { return m->m_runtime->m_rt; } shared_ptr ScriptInterface::GetRuntime() const { return m->m_runtime; } void ScriptInterface::CallConstructor(JS::HandleValue ctor, JS::HandleValueArray argv, JS::MutableHandleValue out) const { JSAutoRequest rq(m->m_cx); if (!ctor.isObject()) { LOGERROR("CallConstructor: ctor is not an object"); out.setNull(); return; } JS::RootedObject ctorObj(m->m_cx, &ctor.toObject()); out.setObjectOrNull(JS_New(m->m_cx, ctorObj, argv)); } void ScriptInterface::DefineCustomObjectType(JSClass *clasp, JSNative constructor, uint minArgs, JSPropertySpec *ps, JSFunctionSpec *fs, JSPropertySpec *static_ps, JSFunctionSpec *static_fs) { JSAutoRequest rq(m->m_cx); std::string typeName = clasp->name; if (m_CustomObjectTypes.find(typeName) != m_CustomObjectTypes.end()) { // This type already exists throw PSERROR_Scripting_DefineType_AlreadyExists(); } JS::RootedObject global(m->m_cx, m->m_glob); - JS::RootedObject obj(m->m_cx, JS_InitClass(m->m_cx, global, JS::NullPtr(), - clasp, - constructor, minArgs, // Constructor, min args - ps, fs, // Properties, methods - static_ps, static_fs)); // Constructor properties, methods + JS::RootedObject obj(m->m_cx, JS_InitClass(m->m_cx, global, nullptr, + clasp, + constructor, minArgs, // Constructor, min args + ps, fs, // Properties, methods + static_ps, static_fs)); // Constructor properties, methods if (obj == NULL) throw PSERROR_Scripting_DefineType_CreationFailed(); CustomType& type = m_CustomObjectTypes[typeName]; type.m_Prototype.init(m->m_cx, obj); type.m_Class = clasp; type.m_Constructor = constructor; } JSObject* ScriptInterface::CreateCustomObject(const std::string& typeName) const { std::map::const_iterator it = m_CustomObjectTypes.find(typeName); if (it == m_CustomObjectTypes.end()) throw PSERROR_Scripting_TypeDoesNotExist(); JS::RootedObject prototype(m->m_cx, it->second.m_Prototype.get()); return JS_NewObjectWithGivenProto(m->m_cx, it->second.m_Class, prototype); } bool ScriptInterface::CallFunction_(JS::HandleValue val, const char* name, JS::HandleValueArray argv, JS::MutableHandleValue ret) const { JSAutoRequest rq(m->m_cx); JS::RootedObject obj(m->m_cx); if (!JS_ValueToObject(m->m_cx, val, &obj) || !obj) return false; // Check that the named function actually exists, to avoid ugly JS error reports // when calling an undefined value bool found; if (!JS_HasProperty(m->m_cx, obj, name, &found) || !found) return false; bool ok = JS_CallFunctionName(m->m_cx, obj, name, argv, ret); return ok; } bool ScriptInterface::CreateObject(JS::MutableHandleValue objectValue) const { JSContext* cx = GetContext(); JSAutoRequest rq(cx); objectValue.setObjectOrNull(JS_NewPlainObject(cx)); if (!objectValue.isObject()) throw PSERROR_Scripting_CreateObjectFailed(); return true; } void ScriptInterface::CreateArray(JS::MutableHandleValue objectValue, size_t length) const { JSContext* cx = GetContext(); JSAutoRequest rq(cx); objectValue.setObjectOrNull(JS_NewArrayObject(cx, length)); if (!objectValue.isObject()) throw PSERROR_Scripting_CreateObjectFailed(); } JS::Value ScriptInterface::GetGlobalObject() const { JSAutoRequest rq(m->m_cx); return JS::ObjectValue(*JS::CurrentGlobalOrNull(m->m_cx)); } bool ScriptInterface::SetGlobal_(const char* name, JS::HandleValue value, bool replace, bool constant, bool enumerate) { JSAutoRequest rq(m->m_cx); JS::RootedObject global(m->m_cx, m->m_glob); bool found; if (!JS_HasProperty(m->m_cx, global, name, &found)) return false; if (found) { JS::Rooted desc(m->m_cx); if (!JS_GetOwnPropertyDescriptor(m->m_cx, global, name, &desc)) return false; - if (desc.isReadonly()) + if (!desc.writable()) { if (!replace) { JS_ReportError(m->m_cx, "SetGlobal \"%s\" called multiple times", name); return false; } // This is not supposed to happen, unless the user has called SetProperty with constant = true on the global object // instead of using SetGlobal. - if (desc.isPermanent()) + if (!desc.configurable()) { JS_ReportError(m->m_cx, "The global \"%s\" is permanent and cannot be hotloaded", name); return false; } LOGMESSAGE("Hotloading new value for global \"%s\".", name); ENSURE(JS_DeleteProperty(m->m_cx, global, name)); } } uint attrs = 0; if (constant) attrs |= JSPROP_READONLY; if (enumerate) attrs |= JSPROP_ENUMERATE; return JS_DefineProperty(m->m_cx, global, name, value, attrs); } bool ScriptInterface::SetProperty_(JS::HandleValue obj, const char* name, JS::HandleValue value, bool constant, bool enumerate) const { JSAutoRequest rq(m->m_cx); uint attrs = 0; if (constant) attrs |= JSPROP_READONLY | JSPROP_PERMANENT; if (enumerate) attrs |= JSPROP_ENUMERATE; if (!obj.isObject()) return false; JS::RootedObject object(m->m_cx, &obj.toObject()); if (!JS_DefineProperty(m->m_cx, object, name, value, attrs)) return false; return true; } bool ScriptInterface::SetProperty_(JS::HandleValue obj, const wchar_t* name, JS::HandleValue value, bool constant, bool enumerate) const { JSAutoRequest rq(m->m_cx); uint attrs = 0; if (constant) attrs |= JSPROP_READONLY | JSPROP_PERMANENT; if (enumerate) attrs |= JSPROP_ENUMERATE; if (!obj.isObject()) return false; JS::RootedObject object(m->m_cx, &obj.toObject()); utf16string name16(name, name + wcslen(name)); if (!JS_DefineUCProperty(m->m_cx, object, reinterpret_cast(name16.c_str()), name16.length(), value, attrs)) return false; return true; } bool ScriptInterface::SetPropertyInt_(JS::HandleValue obj, int name, JS::HandleValue value, bool constant, bool enumerate) const { JSAutoRequest rq(m->m_cx); uint attrs = 0; if (constant) attrs |= JSPROP_READONLY | JSPROP_PERMANENT; if (enumerate) attrs |= JSPROP_ENUMERATE; if (!obj.isObject()) return false; JS::RootedObject object(m->m_cx, &obj.toObject()); JS::RootedId id(m->m_cx, INT_TO_JSID(name)); if (!JS_DefinePropertyById(m->m_cx, object, id, value, attrs)) return false; return true; } bool ScriptInterface::GetProperty(JS::HandleValue obj, const char* name, JS::MutableHandleValue out) const { return GetProperty_(obj, name, out); } bool ScriptInterface::GetProperty(JS::HandleValue obj, const char* name, JS::MutableHandleObject out) const { JSContext* cx = GetContext(); JSAutoRequest rq(cx); JS::RootedValue val(cx); if (!GetProperty_(obj, name, &val)) return false; if (!val.isObject()) { LOGERROR("GetProperty failed: trying to get an object, but the property is not an object!"); return false; } out.set(&val.toObject()); return true; } bool ScriptInterface::GetPropertyInt(JS::HandleValue obj, int name, JS::MutableHandleValue out) const { return GetPropertyInt_(obj, name, out); } bool ScriptInterface::GetProperty_(JS::HandleValue obj, const char* name, JS::MutableHandleValue out) const { JSAutoRequest rq(m->m_cx); if (!obj.isObject()) return false; JS::RootedObject object(m->m_cx, &obj.toObject()); if (!JS_GetProperty(m->m_cx, object, name, out)) return false; return true; } bool ScriptInterface::GetPropertyInt_(JS::HandleValue obj, int name, JS::MutableHandleValue out) const { JSAutoRequest rq(m->m_cx); JS::RootedId nameId(m->m_cx, INT_TO_JSID(name)); if (!obj.isObject()) return false; JS::RootedObject object(m->m_cx, &obj.toObject()); if (!JS_GetPropertyById(m->m_cx, object, nameId, out)) return false; return true; } bool ScriptInterface::HasProperty(JS::HandleValue obj, const char* name) const { // TODO: proper errorhandling JSAutoRequest rq(m->m_cx); if (!obj.isObject()) return false; JS::RootedObject object(m->m_cx, &obj.toObject()); bool found; if (!JS_HasProperty(m->m_cx, object, name, &found)) return false; return found; } bool ScriptInterface::EnumeratePropertyNamesWithPrefix(JS::HandleValue objVal, const char* prefix, std::vector& out) const { JSAutoRequest rq(m->m_cx); if (!objVal.isObjectOrNull()) { LOGERROR("EnumeratePropertyNamesWithPrefix expected object type!"); return false; } if (objVal.isNull()) return true; // reached the end of the prototype chain JS::RootedObject obj(m->m_cx, &objVal.toObject()); - JS::AutoIdArray props(m->m_cx, JS_Enumerate(m->m_cx, obj)); - if (!props) + JS::Rooted props(m->m_cx, JS::IdVector(m->m_cx)); + if (!JS_Enumerate(m->m_cx, obj, &props)) return false; for (size_t i = 0; i < props.length(); ++i) { JS::RootedId id(m->m_cx, props[i]); JS::RootedValue val(m->m_cx); if (!JS_IdToValue(m->m_cx, id, &val)) return false; if (!val.isString()) continue; // ignore integer properties JS::RootedString name(m->m_cx, val.toString()); size_t len = strlen(prefix)+1; std::vector buf(len); size_t prefixLen = strlen(prefix) * sizeof(char); JS_EncodeStringToBuffer(m->m_cx, name, &buf[0], prefixLen); buf[len-1]= '\0'; if (0 == strcmp(&buf[0], prefix)) { if (JS_StringHasLatin1Chars(name)) { size_t length; JS::AutoCheckCannotGC nogc; const JS::Latin1Char* chars = JS_GetLatin1StringCharsAndLength(m->m_cx, nogc, name, &length); if (chars) out.push_back(std::string(chars, chars+length)); } else { size_t length; JS::AutoCheckCannotGC nogc; const char16_t* chars = JS_GetTwoByteStringCharsAndLength(m->m_cx, nogc, name, &length); if (chars) out.push_back(std::string(chars, chars+length)); } } } // Recurse up the prototype chain JS::RootedObject prototype(m->m_cx); if (JS_GetPrototype(m->m_cx, obj, &prototype)) { JS::RootedValue prototypeVal(m->m_cx, JS::ObjectOrNullValue(prototype)); if (!EnumeratePropertyNamesWithPrefix(prototypeVal, prefix, out)) return false; } return true; } bool ScriptInterface::SetPrototype(JS::HandleValue objVal, JS::HandleValue protoVal) { JSAutoRequest rq(m->m_cx); if (!objVal.isObject() || !protoVal.isObject()) return false; JS::RootedObject obj(m->m_cx, &objVal.toObject()); JS::RootedObject proto(m->m_cx, &protoVal.toObject()); return JS_SetPrototype(m->m_cx, obj, proto); } bool ScriptInterface::FreezeObject(JS::HandleValue objVal, bool deep) const { JSAutoRequest rq(m->m_cx); if (!objVal.isObject()) return false; JS::RootedObject obj(m->m_cx, &objVal.toObject()); if (deep) return JS_DeepFreezeObject(m->m_cx, obj); else return JS_FreezeObject(m->m_cx, obj); } bool ScriptInterface::LoadScript(const VfsPath& filename, const std::string& code) const { JSAutoRequest rq(m->m_cx); JS::RootedObject global(m->m_cx, m->m_glob); utf16string codeUtf16(code.begin(), code.end()); uint lineNo = 1; // CompileOptions does not copy the contents of the filename string pointer. // Passing a temporary string there will cause undefined behaviour, so we create a separate string to avoid the temporary. std::string filenameStr = filename.string8(); JS::CompileOptions options(m->m_cx); options.setFileAndLine(filenameStr.c_str(), lineNo); - options.setCompileAndGo(true); + options.setIsRunOnce(true); JS::RootedFunction func(m->m_cx); JS::AutoObjectVector emptyScopeChain(m->m_cx); if (!JS::CompileFunction(m->m_cx, emptyScopeChain, options, NULL, 0, NULL, reinterpret_cast(codeUtf16.c_str()), (uint)(codeUtf16.length()), &func)) return false; JS::RootedValue rval(m->m_cx); - return JS_CallFunction(m->m_cx, JS::NullPtr(), func, JS::HandleValueArray::empty(), &rval); + return JS_CallFunction(m->m_cx, nullptr, func, JS::HandleValueArray::empty(), &rval); } shared_ptr ScriptInterface::CreateRuntime(shared_ptr parentRuntime, int runtimeSize, int heapGrowthBytesGCTrigger) { return shared_ptr(new ScriptRuntime(parentRuntime, runtimeSize, heapGrowthBytesGCTrigger)); } bool ScriptInterface::LoadGlobalScript(const VfsPath& filename, const std::wstring& code) const { JSAutoRequest rq(m->m_cx); - JS::RootedObject global(m->m_cx, m->m_glob); utf16string codeUtf16(code.begin(), code.end()); uint lineNo = 1; // CompileOptions does not copy the contents of the filename string pointer. // Passing a temporary string there will cause undefined behaviour, so we create a separate string to avoid the temporary. std::string filenameStr = filename.string8(); JS::RootedValue rval(m->m_cx); JS::CompileOptions opts(m->m_cx); opts.setFileAndLine(filenameStr.c_str(), lineNo); - return JS::Evaluate(m->m_cx, global, opts, + return JS::Evaluate(m->m_cx, opts, reinterpret_cast(codeUtf16.c_str()), (uint)(codeUtf16.length()), &rval); } bool ScriptInterface::LoadGlobalScriptFile(const VfsPath& path) const { JSAutoRequest rq(m->m_cx); - JS::RootedObject global(m->m_cx, m->m_glob); if (!VfsFileExists(path)) { LOGERROR("File '%s' does not exist", path.string8()); return false; } CVFSFile file; PSRETURN ret = file.Load(g_VFS, path); if (ret != PSRETURN_OK) { LOGERROR("Failed to load file '%s': %s", path.string8(), GetErrorString(ret)); return false; } std::wstring code = wstring_from_utf8(file.DecodeUTF8()); // assume it's UTF-8 utf16string codeUtf16(code.begin(), code.end()); uint lineNo = 1; // CompileOptions does not copy the contents of the filename string pointer. // Passing a temporary string there will cause undefined behaviour, so we create a separate string to avoid the temporary. std::string filenameStr = path.string8(); JS::RootedValue rval(m->m_cx); JS::CompileOptions opts(m->m_cx); opts.setFileAndLine(filenameStr.c_str(), lineNo); - return JS::Evaluate(m->m_cx, global, opts, + return JS::Evaluate(m->m_cx, opts, reinterpret_cast(codeUtf16.c_str()), (uint)(codeUtf16.length()), &rval); } bool ScriptInterface::Eval(const char* code) const { JSAutoRequest rq(m->m_cx); JS::RootedValue rval(m->m_cx); return Eval_(code, &rval); } bool ScriptInterface::Eval_(const char* code, JS::MutableHandleValue rval) const { JSAutoRequest rq(m->m_cx); - JS::RootedObject global(m->m_cx, m->m_glob); utf16string codeUtf16(code, code+strlen(code)); JS::CompileOptions opts(m->m_cx); opts.setFileAndLine("(eval)", 1); - return JS::Evaluate(m->m_cx, global, opts, reinterpret_cast(codeUtf16.c_str()), (uint)codeUtf16.length(), rval); + return JS::Evaluate(m->m_cx, opts, reinterpret_cast(codeUtf16.c_str()), (uint)codeUtf16.length(), rval); } bool ScriptInterface::Eval_(const wchar_t* code, JS::MutableHandleValue rval) const { JSAutoRequest rq(m->m_cx); - JS::RootedObject global(m->m_cx, m->m_glob); utf16string codeUtf16(code, code+wcslen(code)); JS::CompileOptions opts(m->m_cx); opts.setFileAndLine("(eval)", 1); - return JS::Evaluate(m->m_cx, global, opts, reinterpret_cast(codeUtf16.c_str()), (uint)codeUtf16.length(), rval); + return JS::Evaluate(m->m_cx, opts, reinterpret_cast(codeUtf16.c_str()), (uint)codeUtf16.length(), rval); } bool ScriptInterface::ParseJSON(const std::string& string_utf8, JS::MutableHandleValue out) const { JSAutoRequest rq(m->m_cx); std::wstring attrsW = wstring_from_utf8(string_utf8); utf16string string(attrsW.begin(), attrsW.end()); if (JS_ParseJSON(m->m_cx, reinterpret_cast(string.c_str()), (u32)string.size(), out)) return true; LOGERROR("JS_ParseJSON failed!"); if (!JS_IsExceptionPending(m->m_cx)) return false; JS::RootedValue exc(m->m_cx); if (!JS_GetPendingException(m->m_cx, &exc)) return false; JS_ClearPendingException(m->m_cx); // We expect an object of type SyntaxError if (!exc.isObject()) return false; JS::RootedValue rval(m->m_cx); JS::RootedObject excObj(m->m_cx, &exc.toObject()); if (!JS_CallFunctionName(m->m_cx, excObj, "toString", JS::HandleValueArray::empty(), &rval)) return false; std::wstring error; ScriptInterface::FromJSVal(m->m_cx, rval, error); LOGERROR("%s", utf8_from_wstring(error)); return false; } void ScriptInterface::ReadJSONFile(const VfsPath& path, JS::MutableHandleValue out) const { if (!VfsFileExists(path)) { LOGERROR("File '%s' does not exist", path.string8()); return; } CVFSFile file; PSRETURN ret = file.Load(g_VFS, path); if (ret != PSRETURN_OK) { LOGERROR("Failed to load file '%s': %s", path.string8(), GetErrorString(ret)); return; } std::string content(file.DecodeUTF8()); // assume it's UTF-8 if (!ParseJSON(content, out)) LOGERROR("Failed to parse '%s'", path.string8()); } struct Stringifier { static bool callback(const char16_t* buf, u32 len, void* data) { utf16string str(buf, buf+len); std::wstring strw(str.begin(), str.end()); Status err; // ignore Unicode errors static_cast(data)->stream << utf8_from_wstring(strw, &err); return true; } std::stringstream stream; }; // TODO: It's not quite clear why JS_Stringify needs JS::MutableHandleValue. |obj| should not get modified. // It probably has historical reasons and could be changed by SpiderMonkey in the future. std::string ScriptInterface::StringifyJSON(JS::MutableHandleValue obj, bool indent) const { JSAutoRequest rq(m->m_cx); Stringifier str; JS::RootedValue indentVal(m->m_cx, indent ? JS::Int32Value(2) : JS::UndefinedValue()); - if (!JS_Stringify(m->m_cx, obj, JS::NullPtr(), indentVal, &Stringifier::callback, &str)) + if (!JS_Stringify(m->m_cx, obj, nullptr, indentVal, &Stringifier::callback, &str)) { JS_ClearPendingException(m->m_cx); LOGERROR("StringifyJSON failed"); return std::string(); } return str.stream.str(); } std::string ScriptInterface::ToString(JS::MutableHandleValue obj, bool pretty) const { JSAutoRequest rq(m->m_cx); if (obj.isUndefined()) return "(void 0)"; // Try to stringify as JSON if possible // (TODO: this is maybe a bad idea since it'll drop 'undefined' values silently) if (pretty) { Stringifier str; JS::RootedValue indentVal(m->m_cx, JS::Int32Value(2)); // Temporary disable the error reporter, so we don't print complaints about cyclic values JSErrorReporter er = JS_SetErrorReporter(m->m_runtime->m_rt, NULL); - bool ok = JS_Stringify(m->m_cx, obj, JS::NullPtr(), indentVal, &Stringifier::callback, &str); + bool ok = JS_Stringify(m->m_cx, obj, nullptr, indentVal, &Stringifier::callback, &str); // Restore error reporter JS_SetErrorReporter(m->m_runtime->m_rt, er); if (ok) return str.stream.str(); // Clear the exception set when Stringify failed JS_ClearPendingException(m->m_cx); } // Caller didn't want pretty output, or JSON conversion failed (e.g. due to cycles), // so fall back to obj.toSource() std::wstring source = L"(error)"; CallFunction(obj, "toSource", source); return utf8_from_wstring(source); } void ScriptInterface::ReportError(const char* msg) const { JSAutoRequest rq(m->m_cx); // JS_ReportError by itself doesn't seem to set a JS-style exception, and so // script callers will be unable to catch anything. So use JS_SetPendingException // to make sure there really is a script-level exception. But just set it to undefined // because there's not much value yet in throwing a real exception object. JS_SetPendingException(m->m_cx, JS::UndefinedHandleValue); // And report the actual error JS_ReportError(m->m_cx, "%s", msg); // TODO: Why doesn't JS_ReportPendingException(m->m_cx); work? } bool ScriptInterface::IsExceptionPending(JSContext* cx) { JSAutoRequest rq(cx); return JS_IsExceptionPending(cx) ? true : false; } const JSClass* ScriptInterface::GetClass(JS::HandleObject obj) { return JS_GetClass(obj); } void* ScriptInterface::GetPrivate(JS::HandleObject obj) { // TODO: use JS_GetInstancePrivate return JS_GetPrivate(obj); } JS::Value ScriptInterface::CloneValueFromOtherContext(const ScriptInterface& otherContext, JS::HandleValue val) const { PROFILE("CloneValueFromOtherContext"); JSAutoRequest rq(m->m_cx); JS::RootedValue out(m->m_cx); shared_ptr structuredClone = otherContext.WriteStructuredClone(val); ReadStructuredClone(structuredClone, &out); return out.get(); } ScriptInterface::StructuredClone::StructuredClone() : m_Data(NULL), m_Size(0) { } ScriptInterface::StructuredClone::~StructuredClone() { if (m_Data) JS_ClearStructuredClone(m_Data, m_Size, NULL, NULL); } shared_ptr ScriptInterface::WriteStructuredClone(JS::HandleValue v) const { JSAutoRequest rq(m->m_cx); u64* data = NULL; size_t nbytes = 0; if (!JS_WriteStructuredClone(m->m_cx, v, &data, &nbytes, NULL, NULL, JS::UndefinedHandleValue)) { debug_warn(L"Writing a structured clone with JS_WriteStructuredClone failed!"); return shared_ptr(); } shared_ptr ret(new StructuredClone); ret->m_Data = data; ret->m_Size = nbytes; return ret; } void ScriptInterface::ReadStructuredClone(const shared_ptr& ptr, JS::MutableHandleValue ret) const { JSAutoRequest rq(m->m_cx); JS_ReadStructuredClone(m->m_cx, ptr->m_Data, ptr->m_Size, JS_STRUCTURED_CLONE_VERSION, ret, NULL, NULL); } Index: ps/trunk/source/scriptinterface/ScriptTypes.h =================================================================== --- ps/trunk/source/scriptinterface/ScriptTypes.h (revision 22626) +++ ps/trunk/source/scriptinterface/ScriptTypes.h (revision 22627) @@ -1,97 +1,97 @@ /* Copyright (C) 2016 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #ifndef INCLUDED_SCRIPTTYPES #define INCLUDED_SCRIPTTYPES #define JSGC_GENERATIONAL 1 #define JSGC_USE_EXACT_ROOTING 1 #ifdef _WIN32 # define XP_WIN # ifndef WIN32 # define WIN32 // SpiderMonkey expects this # endif #endif // Ignore some harmless warnings #if GCC_VERSION # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wunused-parameter" # pragma GCC diagnostic ignored "-Wredundant-decls" # pragma GCC diagnostic ignored "-Wundef" // Some versions of GCC will still print warnings (see http://gcc.gnu.org/bugzilla/show_bug.cgi?id=53431). # pragma GCC diagnostic ignored "-Wnon-virtual-dtor" # pragma GCC diagnostic ignored "-Wignored-qualifiers" # pragma GCC diagnostic ignored "-Wextra" #endif #if CLANG_VERSION # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wuninitialized" # pragma clang diagnostic ignored "-Wc++11-extensions" # pragma clang diagnostic ignored "-Wignored-qualifiers" # pragma clang diagnostic ignored "-Wmismatched-tags" // Ugly hack to deal with macro redefinitions from libc++ # ifdef nullptr # undef nullptr # endif # ifdef decltype # undef decltype # endif #endif #if MSC_VERSION // reduce the warning level for the SpiderMonkey headers # pragma warning(push, 1) #endif #include "jspubtd.h" #include "jsapi.h" // restore user flags and re-enable the warnings disabled a few lines above #if MSC_VERSION # pragma warning(pop) #endif #if CLANG_VERSION # pragma clang diagnostic pop #endif #if GCC_VERSION # pragma GCC diagnostic pop #endif -#if MOZJS_MAJOR_VERSION != 38 +#if MOZJS_MAJOR_VERSION != 45 #error Your compiler is trying to use an incorrect major version of the \ SpiderMonkey library. The only version that works is the one in the \ libraries/spidermonkey/ directory, and it will not work with a typical \ system-installed version. Make sure you have got all the right files and \ include paths. #endif -#if MOZJS_MINOR_VERSION != 3 +#if MOZJS_MINOR_VERSION != 0 #error Your compiler is trying to use an untested minor version of the \ SpiderMonkey library. If you are a package maintainer, please make sure \ to check very carefully that this version does not change the behaviour \ of the code executed by SpiderMonkey. Different parts of the game (e.g. \ the multiplayer mode) rely on deterministic behaviour of the JavaScript \ engine. A simple way for testing this would be playing a network game \ with one player using the old version and one player using the new \ version. Another way for testing is running replays and comparing the \ final hash (check trac.wildfiregames.com/wiki/Debugging#Replaymode). \ For more information check this link: trac.wildfiregames.com/wiki/Debugging#Outofsync #endif class ScriptInterface; #endif // INCLUDED_SCRIPTTYPES Index: ps/trunk/source/simulation2/components/CCmpAIManager.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpAIManager.cpp (revision 22626) +++ ps/trunk/source/simulation2/components/CCmpAIManager.cpp (revision 22627) @@ -1,1199 +1,1201 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "simulation2/system/Component.h" #include "ICmpAIManager.h" #include "simulation2/MessageTypes.h" #include "graphics/Terrain.h" #include "lib/timer.h" #include "lib/tex/tex.h" #include "lib/allocators/shared_ptr.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/Profile.h" #include "ps/scripting/JSInterface_VFS.h" #include "ps/TemplateLoader.h" #include "ps/Util.h" #include "simulation2/components/ICmpAIInterface.h" #include "simulation2/components/ICmpCommandQueue.h" #include "simulation2/components/ICmpObstructionManager.h" #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/components/ICmpTemplateManager.h" #include "simulation2/components/ICmpTerritoryManager.h" #include "simulation2/helpers/HierarchicalPathfinder.h" #include "simulation2/helpers/LongPathfinder.h" #include "simulation2/serialization/DebugSerializer.h" #include "simulation2/serialization/StdDeserializer.h" #include "simulation2/serialization/StdSerializer.h" #include "simulation2/serialization/SerializeTemplates.h" extern void QuitEngine(); /** * @file * Player AI interface. * AI is primarily scripted, and the CCmpAIManager component defined here * takes care of managing all the scripts. * * To avoid slow AI scripts causing jerky rendering, they are run in a background * thread (maintained by CAIWorker) so that it's okay if they take a whole simulation * turn before returning their results (though preferably they shouldn't use nearly * that much CPU). * * CCmpAIManager grabs the world state after each turn (making use of AIInterface.js * and AIProxy.js to decide what data to include) then passes it to CAIWorker. * The AI scripts will then run asynchronously and return a list of commands to execute. * Any attempts to read the command list (including indirectly via serialization) * will block until it's actually completed, so the rest of the engine should avoid * reading it for as long as possible. * * JS::Values are passed between the game and AI threads using ScriptInterface::StructuredClone. * * TODO: actually the thread isn't implemented yet, because performance hasn't been * sufficiently problematic to justify the complexity yet, but the CAIWorker interface * is designed to hopefully support threading when we want it. */ /** * Implements worker thread for CCmpAIManager. */ class CAIWorker { private: class CAIPlayer { NONCOPYABLE(CAIPlayer); public: CAIPlayer(CAIWorker& worker, const std::wstring& aiName, player_id_t player, u8 difficulty, const std::wstring& behavior, shared_ptr scriptInterface) : m_Worker(worker), m_AIName(aiName), m_Player(player), m_Difficulty(difficulty), m_Behavior(behavior), m_ScriptInterface(scriptInterface), m_Obj(scriptInterface->GetJSRuntime()) { } bool Initialise() { // LoadScripts will only load each script once even though we call it for each player if (!m_Worker.LoadScripts(m_AIName)) return false; JSContext* cx = m_ScriptInterface->GetContext(); JSAutoRequest rq(cx); OsPath path = L"simulation/ai/" + m_AIName + L"/data.json"; JS::RootedValue metadata(cx); m_Worker.LoadMetadata(path, &metadata); if (metadata.isUndefined()) { LOGERROR("Failed to create AI player: can't find %s", path.string8()); return false; } // Get the constructor name from the metadata std::string moduleName; std::string constructor; JS::RootedValue objectWithConstructor(cx); // object that should contain the constructor function JS::RootedValue global(cx, m_ScriptInterface->GetGlobalObject()); JS::RootedValue ctor(cx); if (!m_ScriptInterface->HasProperty(metadata, "moduleName")) { LOGERROR("Failed to create AI player: %s: missing 'moduleName'", path.string8()); return false; } m_ScriptInterface->GetProperty(metadata, "moduleName", moduleName); if (!m_ScriptInterface->GetProperty(global, moduleName.c_str(), &objectWithConstructor) || objectWithConstructor.isUndefined()) { LOGERROR("Failed to create AI player: %s: can't find the module that should contain the constructor: '%s'", path.string8(), moduleName); return false; } if (!m_ScriptInterface->GetProperty(metadata, "constructor", constructor)) { LOGERROR("Failed to create AI player: %s: missing 'constructor'", path.string8()); return false; } // Get the constructor function from the loaded scripts if (!m_ScriptInterface->GetProperty(objectWithConstructor, constructor.c_str(), &ctor) || ctor.isNull()) { LOGERROR("Failed to create AI player: %s: can't find constructor '%s'", path.string8(), constructor); return false; } m_ScriptInterface->GetProperty(metadata, "useShared", m_UseSharedComponent); // Set up the data to pass as the constructor argument JS::RootedValue settings(cx); m_ScriptInterface->Eval(L"({})", &settings); m_ScriptInterface->SetProperty(settings, "player", m_Player, false); m_ScriptInterface->SetProperty(settings, "difficulty", m_Difficulty, false); m_ScriptInterface->SetProperty(settings, "behavior", m_Behavior, false); if (!m_UseSharedComponent) { ENSURE(m_Worker.m_HasLoadedEntityTemplates); m_ScriptInterface->SetProperty(settings, "templates", m_Worker.m_EntityTemplates, false); } JS::AutoValueVector argv(cx); argv.append(settings.get()); m_ScriptInterface->CallConstructor(ctor, argv, &m_Obj); if (m_Obj.get().isNull()) { LOGERROR("Failed to create AI player: %s: error calling constructor '%s'", path.string8(), constructor); return false; } return true; } void Run(JS::HandleValue state, int playerID) { m_Commands.clear(); m_ScriptInterface->CallFunctionVoid(m_Obj, "HandleMessage", state, playerID); } // overloaded with a sharedAI part. // javascript can handle both natively on the same function. void Run(JS::HandleValue state, int playerID, JS::HandleValue SharedAI) { m_Commands.clear(); m_ScriptInterface->CallFunctionVoid(m_Obj, "HandleMessage", state, playerID, SharedAI); } void InitAI(JS::HandleValue state, JS::HandleValue SharedAI) { m_Commands.clear(); m_ScriptInterface->CallFunctionVoid(m_Obj, "Init", state, m_Player, SharedAI); } CAIWorker& m_Worker; std::wstring m_AIName; player_id_t m_Player; u8 m_Difficulty; std::wstring m_Behavior; bool m_UseSharedComponent; // Take care to keep this declaration before heap rooted members. Destructors of heap rooted // members have to be called before the runtime destructor. shared_ptr m_ScriptInterface; JS::PersistentRootedValue m_Obj; std::vector > m_Commands; }; public: struct SCommandSets { player_id_t player; std::vector > commands; }; CAIWorker() : m_ScriptInterface(new ScriptInterface("Engine", "AI", g_ScriptRuntime)), m_TurnNum(0), m_CommandsComputed(true), m_HasLoadedEntityTemplates(false), m_HasSharedComponent(false), m_SerializablePrototypes(new ObjectIdCache(g_ScriptRuntime)), m_EntityTemplates(g_ScriptRuntime->m_rt), m_SharedAIObj(g_ScriptRuntime->m_rt), m_PassabilityMapVal(g_ScriptRuntime->m_rt), m_TerritoryMapVal(g_ScriptRuntime->m_rt) { m_ScriptInterface->ReplaceNondeterministicRNG(m_RNG); m_ScriptInterface->SetCallbackData(static_cast (this)); m_SerializablePrototypes->init(); JS_AddExtraGCRootsTracer(m_ScriptInterface->GetJSRuntime(), Trace, this); m_ScriptInterface->RegisterFunction("PostCommand"); m_ScriptInterface->RegisterFunction("IncludeModule"); m_ScriptInterface->RegisterFunction("Exit"); m_ScriptInterface->RegisterFunction("ComputePath"); m_ScriptInterface->RegisterFunction, u32, u32, u32, CAIWorker::DumpImage>("DumpImage"); m_ScriptInterface->RegisterFunction("GetTemplate"); JSI_VFS::RegisterScriptFunctions_Simulation(*(m_ScriptInterface.get())); // Globalscripts may use VFS script functions m_ScriptInterface->LoadGlobalScripts(); } ~CAIWorker() { JS_RemoveExtraGCRootsTracer(m_ScriptInterface->GetJSRuntime(), Trace, this); } bool HasLoadedEntityTemplates() const { return m_HasLoadedEntityTemplates; } bool LoadScripts(const std::wstring& moduleName) { // Ignore modules that are already loaded if (m_LoadedModules.find(moduleName) != m_LoadedModules.end()) return true; // Mark this as loaded, to prevent it recursively loading itself m_LoadedModules.insert(moduleName); // Load and execute *.js VfsPaths pathnames; if (vfs::GetPathnames(g_VFS, L"simulation/ai/" + moduleName + L"/", L"*.js", pathnames) < 0) { LOGERROR("Failed to load AI scripts for module %s", utf8_from_wstring(moduleName)); return false; } for (const VfsPath& path : pathnames) { if (!m_ScriptInterface->LoadGlobalScriptFile(path)) { LOGERROR("Failed to load script %s", path.string8()); return false; } } return true; } static void IncludeModule(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name) { ENSURE(pCxPrivate->pCBData); CAIWorker* self = static_cast (pCxPrivate->pCBData); self->LoadScripts(name); } static void PostCommand(ScriptInterface::CxPrivate* pCxPrivate, int playerid, JS::HandleValue cmd) { ENSURE(pCxPrivate->pCBData); CAIWorker* self = static_cast (pCxPrivate->pCBData); self->PostCommand(playerid, cmd); } void PostCommand(int playerid, JS::HandleValue cmd) { for (size_t i=0; im_Player == playerid) { m_Players[i]->m_Commands.push_back(m_ScriptInterface->WriteStructuredClone(cmd)); return; } } LOGERROR("Invalid playerid in PostCommand!"); } static JS::Value ComputePath(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue position, JS::HandleValue goal, pass_class_t passClass) { ENSURE(pCxPrivate->pCBData); CAIWorker* self = static_cast (pCxPrivate->pCBData); JSContext* cx(self->m_ScriptInterface->GetContext()); JSAutoRequest rq(cx); CFixedVector2D pos, goalPos; std::vector waypoints; JS::RootedValue retVal(cx); self->m_ScriptInterface->FromJSVal(cx, position, pos); self->m_ScriptInterface->FromJSVal(cx, goal, goalPos); self->ComputePath(pos, goalPos, passClass, waypoints); self->m_ScriptInterface->ToJSVal >(cx, &retVal, waypoints); return retVal; } void ComputePath(const CFixedVector2D& pos, const CFixedVector2D& goal, pass_class_t passClass, std::vector& waypoints) { WaypointPath ret; PathGoal pathGoal = { PathGoal::POINT, goal.X, goal.Y }; m_LongPathfinder.ComputePath(m_HierarchicalPathfinder, pos.X, pos.Y, pathGoal, passClass, ret); for (Waypoint& wp : ret.m_Waypoints) waypoints.emplace_back(wp.x, wp.z); } static CParamNode GetTemplate(ScriptInterface::CxPrivate* pCxPrivate, const std::string& name) { ENSURE(pCxPrivate->pCBData); CAIWorker* self = static_cast (pCxPrivate->pCBData); return self->GetTemplate(name); } CParamNode GetTemplate(const std::string& name) { if (!m_TemplateLoader.TemplateExists(name)) return CParamNode(false); return m_TemplateLoader.GetTemplateFileData(name).GetChild("Entity"); } static void ExitProgram(ScriptInterface::CxPrivate* UNUSED(pCxPrivate)) { QuitEngine(); } /** * Debug function for AI scripts to dump 2D array data (e.g. terrain tile weights). */ static void DumpImage(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& name, const std::vector& data, u32 w, u32 h, u32 max) { // TODO: this is totally not threadsafe. VfsPath filename = L"screenshots/aidump/" + name; if (data.size() != w*h) { debug_warn(L"DumpImage: data size doesn't match w*h"); return; } if (max == 0) { debug_warn(L"DumpImage: max must not be 0"); return; } const size_t bpp = 8; int flags = TEX_BOTTOM_UP|TEX_GREY; const size_t img_size = w * h * bpp/8; const size_t hdr_size = tex_hdr_size(filename); shared_ptr buf; AllocateAligned(buf, hdr_size+img_size, maxSectorSize); Tex t; if (t.wrap(w, h, bpp, flags, buf, hdr_size) < 0) return; u8* img = buf.get() + hdr_size; for (size_t i = 0; i < data.size(); ++i) img[i] = (u8)((data[i] * 255) / max); tex_write(&t, filename); } void SetRNGSeed(u32 seed) { m_RNG.seed(seed); } bool TryLoadSharedComponent() { JSContext* cx = m_ScriptInterface->GetContext(); JSAutoRequest rq(cx); // we don't need to load it. if (!m_HasSharedComponent) return false; // reset the value so it can be used to determine if we actually initialized it. m_HasSharedComponent = false; if (LoadScripts(L"common-api")) m_HasSharedComponent = true; else return false; // mainly here for the error messages OsPath path = L"simulation/ai/common-api/"; // Constructor name is SharedScript, it's in the module API3 // TODO: Hardcoding this is bad, we need a smarter way. JS::RootedValue AIModule(cx); JS::RootedValue global(cx, m_ScriptInterface->GetGlobalObject()); JS::RootedValue ctor(cx); if (!m_ScriptInterface->GetProperty(global, "API3", &AIModule) || AIModule.isUndefined()) { LOGERROR("Failed to create shared AI component: %s: can't find module '%s'", path.string8(), "API3"); return false; } if (!m_ScriptInterface->GetProperty(AIModule, "SharedScript", &ctor) || ctor.isUndefined()) { LOGERROR("Failed to create shared AI component: %s: can't find constructor '%s'", path.string8(), "SharedScript"); return false; } // Set up the data to pass as the constructor argument JS::RootedValue settings(cx); m_ScriptInterface->Eval(L"({})", &settings); JS::RootedValue playersID(cx); m_ScriptInterface->Eval(L"({})", &playersID); for (size_t i = 0; i < m_Players.size(); ++i) { JS::RootedValue val(cx); m_ScriptInterface->ToJSVal(cx, &val, m_Players[i]->m_Player); m_ScriptInterface->SetPropertyInt(playersID, i, val, true); } m_ScriptInterface->SetProperty(settings, "players", playersID); ENSURE(m_HasLoadedEntityTemplates); m_ScriptInterface->SetProperty(settings, "templates", m_EntityTemplates, false); JS::AutoValueVector argv(cx); argv.append(settings); m_ScriptInterface->CallConstructor(ctor, argv, &m_SharedAIObj); if (m_SharedAIObj.get().isNull()) { LOGERROR("Failed to create shared AI component: %s: error calling constructor '%s'", path.string8(), "SharedScript"); return false; } return true; } bool AddPlayer(const std::wstring& aiName, player_id_t player, u8 difficulty, const std::wstring& behavior) { shared_ptr ai(new CAIPlayer(*this, aiName, player, difficulty, behavior, m_ScriptInterface)); if (!ai->Initialise()) return false; // this will be set to true if we need to load the shared Component. if (!m_HasSharedComponent) m_HasSharedComponent = ai->m_UseSharedComponent; m_Players.push_back(ai); return true; } bool RunGamestateInit(const shared_ptr& gameState, const Grid& passabilityMap, const Grid& territoryMap, const std::map& nonPathfindingPassClassMasks, const std::map& pathfindingPassClassMasks) { // this will be run last by InitGame.js, passing the full game representation. // For now it will run for the shared Component. // This is NOT run during deserialization. JSContext* cx = m_ScriptInterface->GetContext(); JSAutoRequest rq(cx); JS::RootedValue state(cx); m_ScriptInterface->ReadStructuredClone(gameState, &state); ScriptInterface::ToJSVal(cx, &m_PassabilityMapVal, passabilityMap); ScriptInterface::ToJSVal(cx, &m_TerritoryMapVal, territoryMap); m_PassabilityMap = passabilityMap; m_NonPathfindingPassClasses = nonPathfindingPassClassMasks; m_PathfindingPassClasses = pathfindingPassClassMasks; m_LongPathfinder.Reload(&m_PassabilityMap); m_HierarchicalPathfinder.Recompute(&m_PassabilityMap, nonPathfindingPassClassMasks, pathfindingPassClassMasks); if (m_HasSharedComponent) { m_ScriptInterface->SetProperty(state, "passabilityMap", m_PassabilityMapVal, true); m_ScriptInterface->SetProperty(state, "territoryMap", m_TerritoryMapVal, true); m_ScriptInterface->CallFunctionVoid(m_SharedAIObj, "init", state); for (size_t i = 0; i < m_Players.size(); ++i) { if (m_HasSharedComponent && m_Players[i]->m_UseSharedComponent) m_Players[i]->InitAI(state, m_SharedAIObj); } } return true; } void UpdateGameState(const shared_ptr& gameState) { ENSURE(m_CommandsComputed); m_GameState = gameState; } void UpdatePathfinder(const Grid& passabilityMap, bool globallyDirty, const Grid& dirtinessGrid, bool justDeserialized, const std::map& nonPathfindingPassClassMasks, const std::map& pathfindingPassClassMasks) { ENSURE(m_CommandsComputed); bool dimensionChange = m_PassabilityMap.m_W != passabilityMap.m_W || m_PassabilityMap.m_H != passabilityMap.m_H; m_PassabilityMap = passabilityMap; if (globallyDirty) { m_LongPathfinder.Reload(&m_PassabilityMap); m_HierarchicalPathfinder.Recompute(&m_PassabilityMap, nonPathfindingPassClassMasks, pathfindingPassClassMasks); } else { m_LongPathfinder.Update(&m_PassabilityMap); m_HierarchicalPathfinder.Update(&m_PassabilityMap, dirtinessGrid); } JSContext* cx = m_ScriptInterface->GetContext(); if (dimensionChange || justDeserialized) ScriptInterface::ToJSVal(cx, &m_PassabilityMapVal, m_PassabilityMap); else { // Avoid a useless memory reallocation followed by a garbage collection. JSAutoRequest rq(cx); JS::RootedObject mapObj(cx, &m_PassabilityMapVal.toObject()); JS::RootedValue mapData(cx); ENSURE(JS_GetProperty(cx, mapObj, "data", &mapData)); JS::RootedObject dataObj(cx, &mapData.toObject()); u32 length = 0; ENSURE(JS_GetArrayLength(cx, dataObj, &length)); u32 nbytes = (u32)(length * sizeof(NavcellData)); + bool sharedMemory; JS::AutoCheckCannotGC nogc; - memcpy((void*)JS_GetUint16ArrayData(dataObj, nogc), m_PassabilityMap.m_Data, nbytes); + memcpy((void*)JS_GetUint16ArrayData(dataObj, &sharedMemory, nogc), m_PassabilityMap.m_Data, nbytes); } } void UpdateTerritoryMap(const Grid& territoryMap) { ENSURE(m_CommandsComputed); bool dimensionChange = m_TerritoryMap.m_W != territoryMap.m_W || m_TerritoryMap.m_H != territoryMap.m_H; m_TerritoryMap = territoryMap; JSContext* cx = m_ScriptInterface->GetContext(); if (dimensionChange) ScriptInterface::ToJSVal(cx, &m_TerritoryMapVal, m_TerritoryMap); else { // Avoid a useless memory reallocation followed by a garbage collection. JSAutoRequest rq(cx); JS::RootedObject mapObj(cx, &m_TerritoryMapVal.toObject()); JS::RootedValue mapData(cx); ENSURE(JS_GetProperty(cx, mapObj, "data", &mapData)); JS::RootedObject dataObj(cx, &mapData.toObject()); u32 length = 0; ENSURE(JS_GetArrayLength(cx, dataObj, &length)); u32 nbytes = (u32)(length * sizeof(u8)); + bool sharedMemory; JS::AutoCheckCannotGC nogc; - memcpy((void*)JS_GetUint8ArrayData(dataObj, nogc), m_TerritoryMap.m_Data, nbytes); + memcpy((void*)JS_GetUint8ArrayData(dataObj, &sharedMemory, nogc), m_TerritoryMap.m_Data, nbytes); } } void StartComputation() { m_CommandsComputed = false; } void WaitToFinishComputation() { if (!m_CommandsComputed) { PerformComputation(); m_CommandsComputed = true; } } void GetCommands(std::vector& commands) { WaitToFinishComputation(); commands.clear(); commands.resize(m_Players.size()); for (size_t i = 0; i < m_Players.size(); ++i) { commands[i].player = m_Players[i]->m_Player; commands[i].commands = m_Players[i]->m_Commands; } } void LoadEntityTemplates(const std::vector >& templates) { JSContext* cx = m_ScriptInterface->GetContext(); JSAutoRequest rq(cx); m_HasLoadedEntityTemplates = true; m_ScriptInterface->CreateObject(&m_EntityTemplates); JS::RootedValue val(cx); for (size_t i = 0; i < templates.size(); ++i) { templates[i].second->ToJSVal(cx, false, &val); m_ScriptInterface->SetProperty(m_EntityTemplates, templates[i].first.c_str(), val, true); } } void Serialize(std::ostream& stream, bool isDebug) { WaitToFinishComputation(); if (isDebug) { CDebugSerializer serializer(*m_ScriptInterface, stream); serializer.Indent(4); SerializeState(serializer); } else { CStdSerializer serializer(*m_ScriptInterface, stream); // TODO: see comment in Deserialize() serializer.SetSerializablePrototypes(m_SerializablePrototypes); SerializeState(serializer); } } void SerializeState(ISerializer& serializer) { if (m_Players.empty()) return; JSContext* cx = m_ScriptInterface->GetContext(); JSAutoRequest rq(cx); std::stringstream rngStream; rngStream << m_RNG; serializer.StringASCII("rng", rngStream.str(), 0, 32); serializer.NumberU32_Unbounded("turn", m_TurnNum); serializer.Bool("useSharedScript", m_HasSharedComponent); if (m_HasSharedComponent) { JS::RootedValue sharedData(cx); if (!m_ScriptInterface->CallFunction(m_SharedAIObj, "Serialize", &sharedData)) LOGERROR("AI shared script Serialize call failed"); serializer.ScriptVal("sharedData", &sharedData); } for (size_t i = 0; i < m_Players.size(); ++i) { serializer.String("name", m_Players[i]->m_AIName, 1, 256); serializer.NumberI32_Unbounded("player", m_Players[i]->m_Player); serializer.NumberU8_Unbounded("difficulty", m_Players[i]->m_Difficulty); serializer.String("behavior", m_Players[i]->m_Behavior, 1, 256); serializer.NumberU32_Unbounded("num commands", (u32)m_Players[i]->m_Commands.size()); for (size_t j = 0; j < m_Players[i]->m_Commands.size(); ++j) { JS::RootedValue val(cx); m_ScriptInterface->ReadStructuredClone(m_Players[i]->m_Commands[j], &val); serializer.ScriptVal("command", &val); } bool hasCustomSerialize = m_ScriptInterface->HasProperty(m_Players[i]->m_Obj, "Serialize"); if (hasCustomSerialize) { JS::RootedValue scriptData(cx); if (!m_ScriptInterface->CallFunction(m_Players[i]->m_Obj, "Serialize", &scriptData)) LOGERROR("AI script Serialize call failed"); serializer.ScriptVal("data", &scriptData); } else { serializer.ScriptVal("data", &m_Players[i]->m_Obj); } } // AI pathfinder SerializeMap()(serializer, "non pathfinding pass classes", m_NonPathfindingPassClasses); SerializeMap()(serializer, "pathfinding pass classes", m_PathfindingPassClasses); serializer.NumberU16_Unbounded("pathfinder grid w", m_PassabilityMap.m_W); serializer.NumberU16_Unbounded("pathfinder grid h", m_PassabilityMap.m_H); serializer.RawBytes("pathfinder grid data", (const u8*)m_PassabilityMap.m_Data, m_PassabilityMap.m_W*m_PassabilityMap.m_H*sizeof(NavcellData)); } void Deserialize(std::istream& stream, u32 numAis) { m_PlayerMetadata.clear(); m_Players.clear(); if (numAis == 0) return; JSContext* cx = m_ScriptInterface->GetContext(); JSAutoRequest rq(cx); ENSURE(m_CommandsComputed); // deserializing while we're still actively computing would be bad CStdDeserializer deserializer(*m_ScriptInterface, stream); std::string rngString; std::stringstream rngStream; deserializer.StringASCII("rng", rngString, 0, 32); rngStream << rngString; rngStream >> m_RNG; deserializer.NumberU32_Unbounded("turn", m_TurnNum); deserializer.Bool("useSharedScript", m_HasSharedComponent); if (m_HasSharedComponent) { TryLoadSharedComponent(); JS::RootedValue sharedData(cx); deserializer.ScriptVal("sharedData", &sharedData); if (!m_ScriptInterface->CallFunctionVoid(m_SharedAIObj, "Deserialize", sharedData)) LOGERROR("AI shared script Deserialize call failed"); } for (size_t i = 0; i < numAis; ++i) { std::wstring name; player_id_t player; u8 difficulty; std::wstring behavior; deserializer.String("name", name, 1, 256); deserializer.NumberI32_Unbounded("player", player); deserializer.NumberU8_Unbounded("difficulty",difficulty); deserializer.String("behavior", behavior, 1, 256); if (!AddPlayer(name, player, difficulty, behavior)) throw PSERROR_Deserialize_ScriptError(); u32 numCommands; deserializer.NumberU32_Unbounded("num commands", numCommands); m_Players.back()->m_Commands.reserve(numCommands); for (size_t j = 0; j < numCommands; ++j) { JS::RootedValue val(cx); deserializer.ScriptVal("command", &val); m_Players.back()->m_Commands.push_back(m_ScriptInterface->WriteStructuredClone(val)); } // TODO: this is yucky but necessary while the AIs are sharing data between contexts; // ideally a new (de)serializer instance would be created for each player // so they would have a single, consistent script context to use and serializable // prototypes could be stored in their ScriptInterface deserializer.SetSerializablePrototypes(m_DeserializablePrototypes); bool hasCustomDeserialize = m_ScriptInterface->HasProperty(m_Players.back()->m_Obj, "Deserialize"); if (hasCustomDeserialize) { JS::RootedValue scriptData(cx); deserializer.ScriptVal("data", &scriptData); if (m_Players[i]->m_UseSharedComponent) { if (!m_ScriptInterface->CallFunctionVoid(m_Players.back()->m_Obj, "Deserialize", scriptData, m_SharedAIObj)) LOGERROR("AI script Deserialize call failed"); } else if (!m_ScriptInterface->CallFunctionVoid(m_Players.back()->m_Obj, "Deserialize", scriptData)) { LOGERROR("AI script deserialize() call failed"); } } else { deserializer.ScriptVal("data", &m_Players.back()->m_Obj); } } // AI pathfinder SerializeMap()(deserializer, "non pathfinding pass classes", m_NonPathfindingPassClasses); SerializeMap()(deserializer, "pathfinding pass classes", m_PathfindingPassClasses); u16 mapW, mapH; deserializer.NumberU16_Unbounded("pathfinder grid w", mapW); deserializer.NumberU16_Unbounded("pathfinder grid h", mapH); m_PassabilityMap = Grid(mapW, mapH); deserializer.RawBytes("pathfinder grid data", (u8*)m_PassabilityMap.m_Data, mapW*mapH*sizeof(NavcellData)); m_LongPathfinder.Reload(&m_PassabilityMap); m_HierarchicalPathfinder.Recompute(&m_PassabilityMap, m_NonPathfindingPassClasses, m_PathfindingPassClasses); } int getPlayerSize() { return m_Players.size(); } void RegisterSerializablePrototype(std::wstring name, JS::HandleValue proto) { // Require unique prototype and name (for reverse lookup) // TODO: this is yucky - see comment in Deserialize() ENSURE(proto.isObject() && "A serializable prototype has to be an object!"); JSContext* cx = m_ScriptInterface->GetContext(); JSAutoRequest rq(cx); JS::RootedObject obj(cx, &proto.toObject()); if (m_SerializablePrototypes->has(obj) || m_DeserializablePrototypes.find(name) != m_DeserializablePrototypes.end()) { LOGERROR("RegisterSerializablePrototype called with same prototype multiple times: p=%p n='%s'", (void *)obj.get(), utf8_from_wstring(name)); return; } m_SerializablePrototypes->add(cx, obj, name); m_DeserializablePrototypes[name] = JS::Heap(obj); } private: static void Trace(JSTracer *trc, void *data) { reinterpret_cast(data)->TraceMember(trc); } void TraceMember(JSTracer *trc) { for (std::pair>& prototype : m_DeserializablePrototypes) JS_CallObjectTracer(trc, &prototype.second, "CAIWorker::m_DeserializablePrototypes"); for (std::pair>& metadata : m_PlayerMetadata) JS_CallValueTracer(trc, &metadata.second, "CAIWorker::m_PlayerMetadata"); } void LoadMetadata(const VfsPath& path, JS::MutableHandleValue out) { if (m_PlayerMetadata.find(path) == m_PlayerMetadata.end()) { // Load and cache the AI player metadata m_ScriptInterface->ReadJSONFile(path, out); m_PlayerMetadata[path] = JS::Heap(out); return; } out.set(m_PlayerMetadata[path].get()); } void PerformComputation() { // Deserialize the game state, to pass to the AI's HandleMessage JSContext* cx = m_ScriptInterface->GetContext(); JSAutoRequest rq(cx); JS::RootedValue state(cx); { PROFILE3("AI compute read state"); m_ScriptInterface->ReadStructuredClone(m_GameState, &state); m_ScriptInterface->SetProperty(state, "passabilityMap", m_PassabilityMapVal, true); m_ScriptInterface->SetProperty(state, "territoryMap", m_TerritoryMapVal, true); } // It would be nice to do // m_ScriptInterface->FreezeObject(state.get(), true); // to prevent AI scripts accidentally modifying the state and // affecting other AI scripts they share it with. But the performance // cost is far too high, so we won't do that. // If there is a shared component, run it if (m_HasSharedComponent) { PROFILE3("AI run shared component"); m_ScriptInterface->CallFunctionVoid(m_SharedAIObj, "onUpdate", state); } for (size_t i = 0; i < m_Players.size(); ++i) { PROFILE3("AI script"); PROFILE2_ATTR("player: %d", m_Players[i]->m_Player); PROFILE2_ATTR("script: %ls", m_Players[i]->m_AIName.c_str()); if (m_HasSharedComponent && m_Players[i]->m_UseSharedComponent) m_Players[i]->Run(state, m_Players[i]->m_Player, m_SharedAIObj); else m_Players[i]->Run(state, m_Players[i]->m_Player); } } // Take care to keep this declaration before heap rooted members. Destructors of heap rooted // members have to be called before the runtime destructor. shared_ptr m_ScriptRuntime; shared_ptr m_ScriptInterface; boost::rand48 m_RNG; u32 m_TurnNum; JS::PersistentRootedValue m_EntityTemplates; bool m_HasLoadedEntityTemplates; std::map > m_PlayerMetadata; std::vector > m_Players; // use shared_ptr just to avoid copying bool m_HasSharedComponent; JS::PersistentRootedValue m_SharedAIObj; std::vector m_Commands; std::set m_LoadedModules; shared_ptr m_GameState; Grid m_PassabilityMap; JS::PersistentRootedValue m_PassabilityMapVal; Grid m_TerritoryMap; JS::PersistentRootedValue m_TerritoryMapVal; std::map m_NonPathfindingPassClasses; std::map m_PathfindingPassClasses; HierarchicalPathfinder m_HierarchicalPathfinder; LongPathfinder m_LongPathfinder; bool m_CommandsComputed; shared_ptr > m_SerializablePrototypes; std::map > m_DeserializablePrototypes; CTemplateLoader m_TemplateLoader; }; /** * Implementation of ICmpAIManager. */ class CCmpAIManager : public ICmpAIManager { public: static void ClassInit(CComponentManager& UNUSED(componentManager)) { } DEFAULT_COMPONENT_ALLOCATOR(AIManager) static std::string GetSchema() { return ""; } virtual void Init(const CParamNode& UNUSED(paramNode)) { m_TerritoriesDirtyID = 0; m_TerritoriesDirtyBlinkingID = 0; m_JustDeserialized = false; } virtual void Deinit() { } virtual void Serialize(ISerializer& serialize) { serialize.NumberU32_Unbounded("num ais", m_Worker.getPlayerSize()); // Because the AI worker uses its own ScriptInterface, we can't use the // ISerializer (which was initialised with the simulation ScriptInterface) // directly. So we'll just grab the ISerializer's stream and write to it // with an independent serializer. m_Worker.Serialize(serialize.GetStream(), serialize.IsDebug()); } virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) { Init(paramNode); u32 numAis; deserialize.NumberU32_Unbounded("num ais", numAis); if (numAis > 0) LoadUsedEntityTemplates(); m_Worker.Deserialize(deserialize.GetStream(), numAis); m_JustDeserialized = true; } virtual void AddPlayer(const std::wstring& id, player_id_t player, u8 difficulty, const std::wstring& behavior) { LoadUsedEntityTemplates(); m_Worker.AddPlayer(id, player, difficulty, behavior); // AI players can cheat and see through FoW/SoD, since that greatly simplifies // their implementation. // (TODO: maybe cleverer AIs should be able to optionally retain FoW/SoD) CmpPtr cmpRangeManager(GetSystemEntity()); if (cmpRangeManager) cmpRangeManager->SetLosRevealAll(player, true); } virtual void SetRNGSeed(u32 seed) { m_Worker.SetRNGSeed(seed); } virtual void TryLoadSharedComponent() { m_Worker.TryLoadSharedComponent(); } virtual void RunGamestateInit() { const ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface(); JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); CmpPtr cmpAIInterface(GetSystemEntity()); ENSURE(cmpAIInterface); // Get the game state from AIInterface // We flush events from the initialization so we get a clean state now. JS::RootedValue state(cx); cmpAIInterface->GetFullRepresentation(&state, true); // Get the passability data Grid dummyGrid; const Grid* passabilityMap = &dummyGrid; CmpPtr cmpPathfinder(GetSystemEntity()); if (cmpPathfinder) passabilityMap = &cmpPathfinder->GetPassabilityGrid(); // Get the territory data // Since getting the territory grid can trigger a recalculation, we check NeedUpdateAI first Grid dummyGrid2; const Grid* territoryMap = &dummyGrid2; CmpPtr cmpTerritoryManager(GetSystemEntity()); if (cmpTerritoryManager && cmpTerritoryManager->NeedUpdateAI(&m_TerritoriesDirtyID, &m_TerritoriesDirtyBlinkingID)) territoryMap = &cmpTerritoryManager->GetTerritoryGrid(); LoadPathfinderClasses(state); std::map nonPathfindingPassClassMasks, pathfindingPassClassMasks; if (cmpPathfinder) cmpPathfinder->GetPassabilityClasses(nonPathfindingPassClassMasks, pathfindingPassClassMasks); m_Worker.RunGamestateInit(scriptInterface.WriteStructuredClone(state), *passabilityMap, *territoryMap, nonPathfindingPassClassMasks, pathfindingPassClassMasks); } virtual void StartComputation() { PROFILE("AI setup"); const ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface(); JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); if (m_Worker.getPlayerSize() == 0) return; CmpPtr cmpAIInterface(GetSystemEntity()); ENSURE(cmpAIInterface); // Get the game state from AIInterface JS::RootedValue state(cx); if (m_JustDeserialized) cmpAIInterface->GetFullRepresentation(&state, false); else cmpAIInterface->GetRepresentation(&state); LoadPathfinderClasses(state); // add the pathfinding classes to it // Update the game state m_Worker.UpdateGameState(scriptInterface.WriteStructuredClone(state)); // Update the pathfinding data CmpPtr cmpPathfinder(GetSystemEntity()); if (cmpPathfinder) { const GridUpdateInformation& dirtinessInformations = cmpPathfinder->GetAIPathfinderDirtinessInformation(); if (dirtinessInformations.dirty || m_JustDeserialized) { const Grid& passabilityMap = cmpPathfinder->GetPassabilityGrid(); std::map nonPathfindingPassClassMasks, pathfindingPassClassMasks; cmpPathfinder->GetPassabilityClasses(nonPathfindingPassClassMasks, pathfindingPassClassMasks); m_Worker.UpdatePathfinder(passabilityMap, dirtinessInformations.globallyDirty, dirtinessInformations.dirtinessGrid, m_JustDeserialized, nonPathfindingPassClassMasks, pathfindingPassClassMasks); } cmpPathfinder->FlushAIPathfinderDirtinessInformation(); } // Update the territory data // Since getting the territory grid can trigger a recalculation, we check NeedUpdateAI first CmpPtr cmpTerritoryManager(GetSystemEntity()); if (cmpTerritoryManager && (cmpTerritoryManager->NeedUpdateAI(&m_TerritoriesDirtyID, &m_TerritoriesDirtyBlinkingID) || m_JustDeserialized)) { const Grid& territoryMap = cmpTerritoryManager->GetTerritoryGrid(); m_Worker.UpdateTerritoryMap(territoryMap); } m_Worker.StartComputation(); m_JustDeserialized = false; } virtual void PushCommands() { std::vector commands; m_Worker.GetCommands(commands); CmpPtr cmpCommandQueue(GetSystemEntity()); if (!cmpCommandQueue) return; const ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface(); JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); JS::RootedValue clonedCommandVal(cx); for (size_t i = 0; i < commands.size(); ++i) { for (size_t j = 0; j < commands[i].commands.size(); ++j) { scriptInterface.ReadStructuredClone(commands[i].commands[j], &clonedCommandVal); cmpCommandQueue->PushLocalCommand(commands[i].player, clonedCommandVal); } } } private: size_t m_TerritoriesDirtyID; size_t m_TerritoriesDirtyBlinkingID; bool m_JustDeserialized; /** * Load the templates of all entities on the map (called when adding a new AI player for a new game * or when deserializing) */ void LoadUsedEntityTemplates() { if (m_Worker.HasLoadedEntityTemplates()) return; CmpPtr cmpTemplateManager(GetSystemEntity()); ENSURE(cmpTemplateManager); std::vector templateNames = cmpTemplateManager->FindUsedTemplates(); std::vector > usedTemplates; usedTemplates.reserve(templateNames.size()); for (const std::string& name : templateNames) { const CParamNode* node = cmpTemplateManager->GetTemplateWithoutValidation(name); if (node) usedTemplates.emplace_back(name, node); } // Send the data to the worker m_Worker.LoadEntityTemplates(usedTemplates); } void LoadPathfinderClasses(JS::HandleValue state) { CmpPtr cmpPathfinder(GetSystemEntity()); if (!cmpPathfinder) return; const ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface(); JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); JS::RootedValue classesVal(cx); scriptInterface.CreateObject(&classesVal); std::map classes; cmpPathfinder->GetPassabilityClasses(classes); for (std::map::iterator it = classes.begin(); it != classes.end(); ++it) scriptInterface.SetProperty(classesVal, it->first.c_str(), it->second, true); scriptInterface.SetProperty(state, "passabilityClasses", classesVal, true); } CAIWorker m_Worker; }; REGISTER_COMPONENT_TYPE(AIManager) Index: ps/trunk/source/simulation2/serialization/BinarySerializer.cpp =================================================================== --- ps/trunk/source/simulation2/serialization/BinarySerializer.cpp (revision 22626) +++ ps/trunk/source/simulation2/serialization/BinarySerializer.cpp (revision 22627) @@ -1,494 +1,494 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "BinarySerializer.h" #include "lib/alignment.h" #include "ps/CLogger.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptExtraHeaders.h" #include "SerializedScriptTypes.h" static u8 GetArrayType(js::Scalar::Type arrayType) { switch(arrayType) { case js::Scalar::Int8: return SCRIPT_TYPED_ARRAY_INT8; case js::Scalar::Uint8: return SCRIPT_TYPED_ARRAY_UINT8; case js::Scalar::Int16: return SCRIPT_TYPED_ARRAY_INT16; case js::Scalar::Uint16: return SCRIPT_TYPED_ARRAY_UINT16; case js::Scalar::Int32: return SCRIPT_TYPED_ARRAY_INT32; case js::Scalar::Uint32: return SCRIPT_TYPED_ARRAY_UINT32; case js::Scalar::Float32: return SCRIPT_TYPED_ARRAY_FLOAT32; case js::Scalar::Float64: return SCRIPT_TYPED_ARRAY_FLOAT64; case js::Scalar::Uint8Clamped: return SCRIPT_TYPED_ARRAY_UINT8_CLAMPED; default: LOGERROR("Cannot serialize unrecognized typed array view: %d", arrayType); throw PSERROR_Serialize_InvalidScriptValue(); } } CBinarySerializerScriptImpl::CBinarySerializerScriptImpl(const ScriptInterface& scriptInterface, ISerializer& serializer) : m_ScriptInterface(scriptInterface), m_Serializer(serializer), m_ScriptBackrefs(scriptInterface.GetRuntime()), m_SerializablePrototypes(new ObjectIdCache(scriptInterface.GetRuntime())), m_ScriptBackrefsNext(1) { m_ScriptBackrefs.init(); m_SerializablePrototypes->init(); } void CBinarySerializerScriptImpl::HandleScriptVal(JS::HandleValue val) { JSContext* cx = m_ScriptInterface.GetContext(); JSAutoRequest rq(cx); switch (JS_TypeOfValue(cx, val)) { case JSTYPE_VOID: { m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_VOID); break; } case JSTYPE_NULL: // This type is never actually returned (it's a JS2 feature) { m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_NULL); break; } case JSTYPE_OBJECT: { if (val.isNull()) { m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_NULL); break; } JS::RootedObject obj(cx, &val.toObject()); // If we've already serialized this object, just output a reference to it u32 tag = GetScriptBackrefTag(obj); if (tag) { m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_BACKREF); m_Serializer.NumberU32_Unbounded("tag", tag); break; } // Arrays are special cases of Object - if (JS_IsArrayObject(cx, obj)) + bool isArray; + if (JS_IsArrayObject(cx, obj, &isArray) && isArray) { m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_ARRAY); // TODO: probably should have a more efficient storage format // Arrays like [1, 2, ] have an 'undefined' at the end which is part of the // length but seemingly isn't enumerated, so store the length explicitly uint length = 0; if (!JS_GetArrayLength(cx, obj, &length)) throw PSERROR_Serialize_ScriptError("JS_GetArrayLength failed"); m_Serializer.NumberU32_Unbounded("array length", length); } else if (JS_IsTypedArrayObject(obj)) { m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_TYPED_ARRAY); m_Serializer.NumberU8_Unbounded("array type", GetArrayType(JS_GetArrayBufferViewType(obj))); m_Serializer.NumberU32_Unbounded("byte offset", JS_GetTypedArrayByteOffset(obj)); m_Serializer.NumberU32_Unbounded("length", JS_GetTypedArrayLength(obj)); + bool sharedMemory; // Now handle its array buffer // this may be a backref, since ArrayBuffers can be shared by multiple views - JS::RootedValue bufferVal(cx, JS::ObjectValue(*JS_GetArrayBufferViewBuffer(cx, obj))); + JS::RootedValue bufferVal(cx, JS::ObjectValue(*JS_GetArrayBufferViewBuffer(cx, obj, &sharedMemory))); HandleScriptVal(bufferVal); break; } else if (JS_IsArrayBufferObject(obj)) { m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_ARRAY_BUFFER); #if BYTE_ORDER != LITTLE_ENDIAN #error TODO: need to convert JS ArrayBuffer data to little-endian #endif u32 length = JS_GetArrayBufferByteLength(obj); m_Serializer.NumberU32_Unbounded("buffer length", length); JS::AutoCheckCannotGC nogc; - m_Serializer.RawBytes("buffer data", (const u8*)JS_GetArrayBufferData(obj, nogc), length); + bool sharedMemory; + m_Serializer.RawBytes("buffer data", (const u8*)JS_GetArrayBufferData(obj, &sharedMemory, nogc), length); break; } else { // Find type of object const JSClass* jsclass = JS_GetClass(obj); if (!jsclass) throw PSERROR_Serialize_ScriptError("JS_GetClass failed"); -// TODO: Remove this workaround for upstream API breakage when updating SpiderMonkey -// See https://bugzilla.mozilla.org/show_bug.cgi?id=1236373 -#define JSCLASS_CACHED_PROTO_WIDTH js::JSCLASS_CACHED_PROTO_WIDTH + JSProtoKey protokey = JSCLASS_CACHED_PROTO_KEY(jsclass); -#undef JSCLASS_CACHED_PROTO_WIDTH if (protokey == JSProto_Object) { // Object class - check for user-defined prototype JS::RootedObject proto(cx); JS_GetPrototype(cx, obj, &proto); if (!proto) throw PSERROR_Serialize_ScriptError("JS_GetPrototype failed"); if (m_SerializablePrototypes->empty() || !IsSerializablePrototype(proto)) { // Standard Object prototype m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT); // TODO: maybe we should throw an error for unrecognized non-Object prototypes? // (requires fixing AI serialization first and excluding component scripts) } else { // User-defined custom prototype m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_PROTOTYPE); const std::wstring prototypeName = GetPrototypeName(proto); m_Serializer.String("proto name", prototypeName, 0, 256); // Does it have custom Serialize function? // if so, we serialize the data it returns, rather than the object's properties directly bool hasCustomSerialize; if (!JS_HasProperty(cx, obj, "Serialize", &hasCustomSerialize)) throw PSERROR_Serialize_ScriptError("JS_HasProperty failed"); if (hasCustomSerialize) { JS::RootedValue serialize(cx); if (!JS_GetProperty(cx, obj, "Serialize", &serialize)) throw PSERROR_Serialize_ScriptError("JS_GetProperty failed"); // If serialize is null, so don't serialize anything more if (!serialize.isNull()) { JS::RootedValue data(cx); if (!m_ScriptInterface.CallFunction(val, "Serialize", &data)) throw PSERROR_Serialize_ScriptError("Prototype Serialize function failed"); HandleScriptVal(data); } break; } } } else if (protokey == JSProto_Number) { // Standard Number object m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_NUMBER); // Get primitive value double d; if (!JS::ToNumber(cx, val, &d)) throw PSERROR_Serialize_ScriptError("JS::ToNumber failed"); m_Serializer.NumberDouble_Unbounded("value", d); break; } else if (protokey == JSProto_String) { // Standard String object m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_STRING); // Get primitive value JS::RootedString str(cx, JS::ToString(cx, val)); if (!str) throw PSERROR_Serialize_ScriptError("JS_ValueToString failed"); ScriptString("value", str); break; } else if (protokey == JSProto_Boolean) { // Standard Boolean object m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_BOOLEAN); // Get primitive value bool b = JS::ToBoolean(val); m_Serializer.Bool("value", b); break; } // TODO: Follow upstream progresses about a JS::IsMapObject // https://bugzilla.mozilla.org/show_bug.cgi?id=1285909 else if (protokey == JSProto_Map) { m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_MAP); m_Serializer.NumberU32_Unbounded("map size", JS::MapSize(cx, obj)); JS::RootedValue keyValueIterator(cx); if (!JS::MapEntries(cx, obj, &keyValueIterator)) throw PSERROR_Serialize_ScriptError("JS::MapEntries failed"); JS::ForOfIterator it(cx); if (!it.init(keyValueIterator)) throw PSERROR_Serialize_ScriptError("JS::ForOfIterator::init failed"); JS::RootedValue keyValuePair(cx); bool done; while (true) { if (!it.next(&keyValuePair, &done)) throw PSERROR_Serialize_ScriptError("JS::ForOfIterator::next failed"); if (done) break; JS::RootedObject keyValuePairObj(cx, &keyValuePair.toObject()); JS::RootedValue key(cx); JS::RootedValue value(cx); ENSURE(JS_GetElement(cx, keyValuePairObj, 0, &key)); ENSURE(JS_GetElement(cx, keyValuePairObj, 1, &value)); HandleScriptVal(key); HandleScriptVal(value); } break; } // TODO: Follow upstream progresses about a JS::IsSetObject // https://bugzilla.mozilla.org/show_bug.cgi?id=1285909 else if (protokey == JSProto_Set) { // TODO: When updating SpiderMonkey to a release after 38 use the C++ API for Sets. // https://bugzilla.mozilla.org/show_bug.cgi?id=1159469 u32 setSize; m_ScriptInterface.GetProperty(val, "size", setSize); m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_SET); m_Serializer.NumberU32_Unbounded("set size", setSize); JS::RootedValue valueIterator(cx); m_ScriptInterface.CallFunction(val, "values", &valueIterator); for (u32 i=0; iname); throw PSERROR_Serialize_InvalidScriptValue(); } } // Find all properties (ordered by insertion time) - JS::AutoIdArray ida (cx, JS_Enumerate(cx, obj)); - if (!ida) + JS::Rooted ida(cx, JS::IdVector(cx)); + if (!JS_Enumerate(cx, obj, &ida)) throw PSERROR_Serialize_ScriptError("JS_Enumerate failed"); m_Serializer.NumberU32_Unbounded("num props", (u32)ida.length()); for (size_t i = 0; i < ida.length(); ++i) { JS::RootedId id(cx, ida[i]); JS::RootedValue idval(cx); JS::RootedValue propval(cx); // Forbid getters, which might delete values and mess things up. JS::Rooted desc(cx); if (!JS_GetPropertyDescriptorById(cx, obj, id, &desc)) throw PSERROR_Serialize_ScriptError("JS_GetPropertyDescriptorById failed"); if (desc.hasGetterObject()) throw PSERROR_Serialize_ScriptError("Cannot serialize property getters"); // Get the property name as a string if (!JS_IdToValue(cx, id, &idval)) throw PSERROR_Serialize_ScriptError("JS_IdToValue failed"); JS::RootedString idstr(cx, JS::ToString(cx, idval)); if (!idstr) throw PSERROR_Serialize_ScriptError("JS_ValueToString failed"); ScriptString("prop name", idstr); if (!JS_GetPropertyById(cx, obj, id, &propval)) throw PSERROR_Serialize_ScriptError("JS_GetPropertyById failed"); HandleScriptVal(propval); } break; } case JSTYPE_FUNCTION: { // We can't serialise functions, but we can at least name the offender (hopefully) std::wstring funcname(L"(unnamed)"); JS::RootedFunction func(cx, JS_ValueToFunction(cx, val)); if (func) { JS::RootedString string(cx, JS_GetFunctionId(func)); if (string) { if (JS_StringHasLatin1Chars(string)) { size_t length; JS::AutoCheckCannotGC nogc; const JS::Latin1Char* ch = JS_GetLatin1StringCharsAndLength(cx, nogc, string, &length); if (ch && length > 0) funcname.assign(ch, ch + length); } else { size_t length; JS::AutoCheckCannotGC nogc; const char16_t* ch = JS_GetTwoByteStringCharsAndLength(cx, nogc, string, &length); if (ch && length > 0) funcname.assign(ch, ch + length); } } } LOGERROR("Cannot serialise JS objects of type 'function': %s", utf8_from_wstring(funcname)); throw PSERROR_Serialize_InvalidScriptValue(); } case JSTYPE_STRING: { m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_STRING); JS::RootedString stringVal(cx, val.toString()); ScriptString("string", stringVal); break; } case JSTYPE_NUMBER: { // To reduce the size of the serialized data, we handle integers and doubles separately. // We can't check for val.isInt32 and val.isDouble directly, because integer numbers are not guaranteed // to be represented as integers. A number like 33 could be stored as integer on the computer of one player // and as double on the other player's computer. That would cause out of sync errors in multiplayer games because // their binary representation and thus the hash would be different. double d; d = val.toNumber(); i32 integer; if (JS_DoubleIsInt32(d, &integer)) { m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_INT); m_Serializer.NumberI32_Unbounded("value", integer); } else { m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_DOUBLE); m_Serializer.NumberDouble_Unbounded("value", d); } break; } case JSTYPE_BOOLEAN: { m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_BOOLEAN); bool b = val.toBoolean(); m_Serializer.NumberU8_Unbounded("value", b ? 1 : 0); break; } default: { debug_warn(L"Invalid TypeOfValue"); throw PSERROR_Serialize_InvalidScriptValue(); } } } void CBinarySerializerScriptImpl::ScriptString(const char* name, JS::HandleString string) { JSContext* cx = m_ScriptInterface.GetContext(); JSAutoRequest rq(cx); #if BYTE_ORDER != LITTLE_ENDIAN #error TODO: probably need to convert JS strings to little-endian #endif size_t length; JS::AutoCheckCannotGC nogc; // Serialize strings directly as UTF-16 or Latin1, to avoid expensive encoding conversions bool isLatin1 = JS_StringHasLatin1Chars(string); m_Serializer.Bool("isLatin1", isLatin1); if (isLatin1) { const JS::Latin1Char* chars = JS_GetLatin1StringCharsAndLength(cx, nogc, string, &length); if (!chars) throw PSERROR_Serialize_ScriptError("JS_GetLatin1StringCharsAndLength failed"); m_Serializer.NumberU32_Unbounded("string length", (u32)length); m_Serializer.RawBytes(name, (const u8*)chars, length); } else { const char16_t* chars = JS_GetTwoByteStringCharsAndLength(cx, nogc, string, &length); if (!chars) throw PSERROR_Serialize_ScriptError("JS_GetTwoByteStringCharsAndLength failed"); m_Serializer.NumberU32_Unbounded("string length", (u32)length); m_Serializer.RawBytes(name, (const u8*)chars, length*2); } } u32 CBinarySerializerScriptImpl::GetScriptBackrefTag(JS::HandleObject obj) { // To support non-tree structures (e.g. "var x = []; var y = [x, x];"), we need a way // to indicate multiple references to one object(/array). So every time we serialize a // new object, we give it a new non-zero tag; when we serialize it a second time we just // refer to that tag. // // The tags are stored in a map. Maybe it'd be more efficient to store it inline in the object // somehow? but this works okay for now // If it was already there, return the tag u32 tag; if (m_ScriptBackrefs.find(obj, tag)) return tag; JSContext* cx = m_ScriptInterface.GetContext(); JSAutoRequest rq(cx); m_ScriptBackrefs.add(cx, obj, m_ScriptBackrefsNext); m_ScriptBackrefsNext++; // Return a non-tag number so callers know they need to serialize the object return 0; } bool CBinarySerializerScriptImpl::IsSerializablePrototype(JS::HandleObject prototype) { return m_SerializablePrototypes->has(prototype); } std::wstring CBinarySerializerScriptImpl::GetPrototypeName(JS::HandleObject prototype) { std::wstring ret; bool found = m_SerializablePrototypes->find(prototype, ret); ENSURE(found); return ret; } void CBinarySerializerScriptImpl::SetSerializablePrototypes(shared_ptr > prototypes) { m_SerializablePrototypes = prototypes; } Index: ps/trunk/source/simulation2/system/ParamNode.cpp =================================================================== --- ps/trunk/source/simulation2/system/ParamNode.cpp (revision 22626) +++ ps/trunk/source/simulation2/system/ParamNode.cpp (revision 22627) @@ -1,445 +1,445 @@ /* Copyright (C) 2017 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "ParamNode.h" #include "lib/utf8.h" #include "ps/CLogger.h" #include "ps/CStr.h" #include "ps/Filesystem.h" #include "ps/XML/Xeromyces.h" #include #include #include // this isn't in string.hpp in old Boosts static CParamNode g_NullNode(false); CParamNode::CParamNode(bool isOk) : m_IsOk(isOk) { } void CParamNode::LoadXML(CParamNode& ret, const XMBFile& xmb, const wchar_t* sourceIdentifier /*= NULL*/) { ret.ApplyLayer(xmb, xmb.GetRoot(), sourceIdentifier); } void CParamNode::LoadXML(CParamNode& ret, const VfsPath& path, const std::string& validatorName) { CXeromyces xero; PSRETURN ok = xero.Load(g_VFS, path, validatorName); if (ok != PSRETURN_OK) return; // (Xeromyces already logged an error) LoadXML(ret, xero, path.string().c_str()); } PSRETURN CParamNode::LoadXMLString(CParamNode& ret, const char* xml, const wchar_t* sourceIdentifier /*=NULL*/) { CXeromyces xero; PSRETURN ok = xero.LoadString(xml); if (ok != PSRETURN_OK) return ok; ret.ApplyLayer(xero, xero.GetRoot(), sourceIdentifier); return PSRETURN_OK; } void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element, const wchar_t* sourceIdentifier /*= NULL*/) { ResetScriptVal(); std::string name = xmb.GetElementString(element.GetNodeName()); // TODO: is GetElementString inefficient? CStrW value = element.GetText().FromUTF8(); bool hasSetValue = false; // Look for special attributes int at_disable = xmb.GetAttributeID("disable"); int at_replace = xmb.GetAttributeID("replace"); int at_filtered = xmb.GetAttributeID("filtered"); int at_merge = xmb.GetAttributeID("merge"); int at_op = xmb.GetAttributeID("op"); int at_datatype = xmb.GetAttributeID("datatype"); enum op { INVALID, ADD, MUL, MUL_ROUND } op = INVALID; bool replacing = false; bool filtering = false; bool merging = false; { XERO_ITER_ATTR(element, attr) { if (attr.Name == at_disable) { m_Childs.erase(name); return; } else if (attr.Name == at_replace) { m_Childs.erase(name); replacing = true; } else if (attr.Name == at_filtered) { filtering = true; } else if (attr.Name == at_merge) { if (m_Childs.find(name) == m_Childs.end()) return; merging = true; } else if (attr.Name == at_op) { if (std::wstring(attr.Value.begin(), attr.Value.end()) == L"add") op = ADD; else if (std::wstring(attr.Value.begin(), attr.Value.end()) == L"mul") op = MUL; else if (std::wstring(attr.Value.begin(), attr.Value.end()) == L"mul_round") op = MUL_ROUND; else LOGWARNING("Invalid op '%ls'", attr.Value); } } } { XERO_ITER_ATTR(element, attr) { if (attr.Name == at_datatype && std::wstring(attr.Value.begin(), attr.Value.end()) == L"tokens") { CParamNode& node = m_Childs[name]; // Split into tokens std::vector oldTokens; std::vector newTokens; if (!replacing && !node.m_Value.empty()) // ignore the old tokens if replace="" was given boost::algorithm::split(oldTokens, node.m_Value, boost::algorithm::is_space(), boost::algorithm::token_compress_on); if (!value.empty()) boost::algorithm::split(newTokens, value, boost::algorithm::is_space(), boost::algorithm::token_compress_on); // Merge the two lists std::vector tokens = oldTokens; for (size_t i = 0; i < newTokens.size(); ++i) { if (newTokens[i][0] == L'-') { std::vector::iterator tokenIt = std::find(tokens.begin(), tokens.end(), newTokens[i].substr(1)); if (tokenIt != tokens.end()) tokens.erase(tokenIt); else LOGWARNING("[ParamNode] Could not remove token '%s' from node '%s'%s; not present in list nor inherited (possible typo?)", utf8_from_wstring(newTokens[i].substr(1)), name, sourceIdentifier ? (" in '" + utf8_from_wstring(sourceIdentifier) + "'").c_str() : ""); } else { if (std::find(oldTokens.begin(), oldTokens.end(), newTokens[i]) == oldTokens.end()) tokens.push_back(newTokens[i]); } } node.m_Value = boost::algorithm::join(tokens, L" "); hasSetValue = true; break; } } } // Add this element as a child node CParamNode& node = m_Childs[name]; if (op != INVALID) { // TODO: Support parsing of data types other than fixed; log warnings in other cases fixed oldval = node.ToFixed(); fixed mod = fixed::FromString(CStrW(value)); switch (op) { case ADD: node.m_Value = (oldval + mod).ToString().FromUTF8(); break; case MUL: node.m_Value = oldval.Multiply(mod).ToString().FromUTF8(); break; case MUL_ROUND: node.m_Value = fixed::FromInt(oldval.Multiply(mod).ToInt_RoundToNearest()).ToString().FromUTF8(); break; } hasSetValue = true; } if (!hasSetValue && !merging) node.m_Value = value; // We also need to reset node's script val, even if it has no children // or if the attributes change. node.ResetScriptVal(); // For the filtered case ChildrenMap childs; // Recurse through the element's children XERO_ITER_EL(element, child) { node.ApplyLayer(xmb, child, sourceIdentifier); if (filtering) { std::string childname = xmb.GetElementString(child.GetNodeName()); if (node.m_Childs.find(childname) != node.m_Childs.end()) childs[childname] = std::move(node.m_Childs[childname]); } } if (filtering) node.m_Childs.swap(childs); // Add the element's attributes, prefixing names with "@" XERO_ITER_ATTR(element, attr) { // Skip special attributes if (attr.Name == at_replace || attr.Name == at_op || attr.Name == at_merge || attr.Name == at_filtered) continue; // Add any others std::string attrName = xmb.GetAttributeString(attr.Name); node.m_Childs["@" + attrName].m_Value = attr.Value.FromUTF8(); } } const CParamNode& CParamNode::GetChild(const char* name) const { ChildrenMap::const_iterator it = m_Childs.find(name); if (it == m_Childs.end()) return g_NullNode; return it->second; } bool CParamNode::IsOk() const { return m_IsOk; } const std::wstring& CParamNode::ToString() const { return m_Value; } const std::string CParamNode::ToUTF8() const { return utf8_from_wstring(m_Value); } const CStrIntern CParamNode::ToUTF8Intern() const { return CStrIntern(utf8_from_wstring(m_Value)); } int CParamNode::ToInt() const { int ret = 0; std::wstringstream strm; strm << m_Value; strm >> ret; return ret; } fixed CParamNode::ToFixed() const { return fixed::FromString(CStrW(m_Value)); } float CParamNode::ToFloat() const { float ret = 0; std::wstringstream strm; strm << m_Value; strm >> ret; return ret; } bool CParamNode::ToBool() const { if (m_Value == L"true") return true; else return false; } const CParamNode::ChildrenMap& CParamNode::GetChildren() const { return m_Childs; } std::wstring CParamNode::EscapeXMLString(const std::wstring& str) { std::wstring ret; ret.reserve(str.size()); for (size_t i = 0; i < str.size(); ++i) { wchar_t c = str[i]; switch (c) { case '<': ret += L"<"; break; case '>': ret += L">"; break; case '&': ret += L"&"; break; case '"': ret += L"""; break; case '\t': ret += L" "; break; case '\n': ret += L" "; break; case '\r': ret += L" "; break; default: if ((0x20 <= c && c <= 0xD7FF) || (0xE000 <= c && c <= 0xFFFD)) ret += c; else ret += 0xFFFD; } } return ret; } std::wstring CParamNode::ToXML() const { std::wstringstream strm; ToXML(strm); return strm.str(); } void CParamNode::ToXML(std::wostream& strm) const { strm << m_Value; ChildrenMap::const_iterator it = m_Childs.begin(); for (; it != m_Childs.end(); ++it) { // Skip attributes here (they were handled when the caller output the tag) if (it->first.length() && it->first[0] == '@') continue; std::wstring name (it->first.begin(), it->first.end()); strm << L"<" << name; // Output the child's attributes first ChildrenMap::const_iterator cit = it->second.m_Childs.begin(); for (; cit != it->second.m_Childs.end(); ++cit) { if (cit->first.length() && cit->first[0] == '@') { std::wstring attrname (cit->first.begin()+1, cit->first.end()); strm << L" " << attrname << L"=\"" << EscapeXMLString(cit->second.m_Value) << L"\""; } } strm << L">"; it->second.ToXML(strm); strm << L""; } } void CParamNode::ToJSVal(JSContext* cx, bool cacheValue, JS::MutableHandleValue ret) const { if (cacheValue && m_ScriptVal != NULL) { ret.set(*m_ScriptVal); return; } ConstructJSVal(cx, ret); if (cacheValue) m_ScriptVal.reset(new JS::PersistentRootedValue(cx, ret)); } void CParamNode::ConstructJSVal(JSContext* cx, JS::MutableHandleValue ret) const { JSAutoRequest rq(cx); if (m_Childs.empty()) { // Empty node - map to undefined if (m_Value.empty()) { ret.setUndefined(); return; } // Just a string utf16string text(m_Value.begin(), m_Value.end()); - JS::RootedString str(cx, JS_InternUCStringN(cx, reinterpret_cast(text.data()), text.length())); + JS::RootedString str(cx, JS_AtomizeAndPinUCStringN(cx, reinterpret_cast(text.data()), text.length())); if (str) { ret.setString(str); return; } // TODO: report error ret.setUndefined(); return; } // Got child nodes - convert this node into a hash-table-style object: JS::RootedObject obj(cx, JS_NewPlainObject(cx)); if (!obj) { ret.setUndefined(); return; // TODO: report error } JS::RootedValue childVal(cx); for (std::map::const_iterator it = m_Childs.begin(); it != m_Childs.end(); ++it) { it->second.ConstructJSVal(cx, &childVal); if (!JS_SetProperty(cx, obj, it->first.c_str(), childVal)) { ret.setUndefined(); return; // TODO: report error } } // If the node has a string too, add that as an extra property if (!m_Value.empty()) { utf16string text(m_Value.begin(), m_Value.end()); - JS::RootedString str(cx, JS_InternUCStringN(cx, reinterpret_cast(text.data()), text.length())); + JS::RootedString str(cx, JS_AtomizeAndPinUCStringN(cx, reinterpret_cast(text.data()), text.length())); if (!str) { ret.setUndefined(); return; // TODO: report error } JS::RootedValue childVal(cx, JS::StringValue(str)); if (!JS_SetProperty(cx, obj, "_string", childVal)) { ret.setUndefined(); return; // TODO: report error } } ret.setObject(*obj); } void CParamNode::ResetScriptVal() { m_ScriptVal = NULL; }