Index: ps/trunk/source/scriptinterface/third_party/ObjectToIDMap.h =================================================================== --- ps/trunk/source/scriptinterface/third_party/ObjectToIDMap.h (revision 24166) +++ ps/trunk/source/scriptinterface/third_party/ObjectToIDMap.h (nonexistent) @@ -1,132 +0,0 @@ -/* 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 - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - - -/** - * Providing a map-like structure with JSObject pointers (actually their hash) as keys - * with correct garbage collection handling (JSObjects can move in memory). - * - * The code in this class was copied from here and modified to work in our environment. - * * https://mxr.mozilla.org/mozilla-esr38/source/js/ipc/JavaScriptShared.h - * * https://mxr.mozilla.org/mozilla-esr38/source/js/ipc/JavaScriptShared.cpp - * - * When updating SpiderMonkey, you most likely have to reintegrate an updated version - * of the class(es) in this file. The best way is probably to get a diff between the - * original files and integrate that because this file is heavily modified from the - * original version. - */ - -#ifndef INCLUDED_OBJECTTOIDMAP -#define INCLUDED_OBJECTTOIDMAP - -#include "scriptinterface/ScriptRuntime.h" -#include "scriptinterface/ScriptTypes.h" -#include - -// Map JSObjects -> ids -template -class ObjectIdCache -{ - typedef js::PointerHasher Hasher; - typedef js::HashMap Table; - - NONCOPYABLE(ObjectIdCache); - -public: - ObjectIdCache(shared_ptr rt) - : table_(nullptr), m_rt(rt) - { - } - - ~ObjectIdCache() - { - if (table_) - { - m_rt->AddDeferredFinalizationObject(std::shared_ptr((void*)table_, DeleteTable)); - table_ = nullptr; - JS_RemoveExtraGCRootsTracer(m_rt->m_rt, ObjectIdCache::Trace, this); - } - } - - bool init() - { - if (table_) - return true; - - table_ = new Table(js::SystemAllocPolicy()); - JS_AddExtraGCRootsTracer(m_rt->m_rt, ObjectIdCache::Trace, this); - return table_ && table_->init(32); - } - - void trace(JSTracer* trc) - { - for (typename Table::Enum e(*table_); !e.empty(); e.popFront()) - { - JSObject* obj = e.front().key(); - JS_CallUnbarrieredObjectTracer(trc, &obj, "ipc-object"); - if (obj != e.front().key()) - e.rekeyFront(obj); - } - } - -// TODO sweep? - - bool find(JSObject* obj, T& ret) - { - typename Table::Ptr p = table_->lookup(obj); - if (!p) - return false; - ret = p->value(); - return true; - } - - bool add(JSContext* cx, JSObject* obj, T id) - { - if (!table_->put(obj, id)) - return false; - JS_StoreObjectPostBarrierCallback(cx, keyMarkCallback, obj, table_); - return true; - } - - void remove(JSObject* obj) - { - table_->remove(obj); - } - -// TODO clear? - - bool empty() - { - return table_->empty(); - } - - bool has(JSObject* obj) - { - return table_->has(obj); - } - -private: - static void keyMarkCallback(JSTracer* trc, JSObject* key, void* data) - { - Table* table = static_cast(data); - JSObject* prior = key; - JS_CallUnbarrieredObjectTracer(trc, &key, "ObjectIdCache::table_ key"); - table->rekeyIfMoved(prior, key); - } - - static void Trace(JSTracer* trc, void* data) - { - reinterpret_cast(data)->trace(trc); - } - - static void DeleteTable(void* table) - { - delete (Table*)table; - } - - shared_ptr m_rt; - Table* table_; -}; - -#endif // INCLUDED_OBJECTTOIDMAP Property changes on: ps/trunk/source/scriptinterface/third_party/ObjectToIDMap.h ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Index: ps/trunk/source/scriptinterface/tests/test_ObjectToIDMap.h =================================================================== --- ps/trunk/source/scriptinterface/tests/test_ObjectToIDMap.h (revision 24166) +++ ps/trunk/source/scriptinterface/tests/test_ObjectToIDMap.h (nonexistent) @@ -1,64 +0,0 @@ -/* 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 . - */ - -#include "lib/self_test.h" - -#include "scriptinterface/ScriptInterface.h" -#include "scriptinterface/ScriptRuntime.h" -#include "scriptinterface/third_party/ObjectToIDMap.h" - -class TestObjectToIDMap : public CxxTest::TestSuite -{ -public: - void test_movinggc() - { - ScriptInterface script("Test", "Test", g_ScriptRuntime); - JSContext* cx = script.GetContext(); - JSAutoRequest rq(cx); - - JS::RootedObject obj(cx, JS_NewPlainObject(cx)); - ObjectIdCache map(g_ScriptRuntime); - map.init(); - - TS_ASSERT(map.add(cx, obj, 1)); - JSObject* plainObj = obj; - // The map should contain the object we've just added - TS_ASSERT(map.has(plainObj)); - - JS_GC(g_ScriptRuntime->m_rt); - - // After a GC, the object should have been moved and plainObj should - // not be valid anymore and not be found in the map anymore. - // Obj should have an updated reference too, so it should still be found - // in the map. - // - // NOTE: It's observed behaviour that a full GC always moves an object. - // This might change in future SpiderMonkey versions. We only rely on - // that behaviour for this test. - // - // TODO: It might be a good idea to test the behaviour when only a minor - // GC runs, but there's no API for calling a minor GC yet. - TS_ASSERT(plainObj != obj); - TS_ASSERT(!map.has(plainObj)); - TS_ASSERT(map.has(obj)); - - // Finding the ID associated with the object - u32 ret(0); - TS_ASSERT(map.find(obj, ret)); - TS_ASSERT_EQUALS(ret, 1); - } -}; Property changes on: ps/trunk/source/scriptinterface/tests/test_ObjectToIDMap.h ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Index: ps/trunk/libraries/osx/build-osx-libs.sh =================================================================== --- ps/trunk/libraries/osx/build-osx-libs.sh (revision 24166) +++ ps/trunk/libraries/osx/build-osx-libs.sh (revision 24167) @@ -1,1069 +1,1069 @@ #!/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" # NOTE: remember to also update LIB_URL below when changing version 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.13" GLOOX_VERSION="gloox-1.0.22" # 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_2" ENET_VERSION="enet-1.3.13" MINIUPNPC_VERSION="miniupnpc-2.0.20180222" SODIUM_VERSION="libsodium-1.0.18" # -------------------------------------------------------------- # Bundled with the game: # * SpiderMonkey 45 # * NVTT # * FCollada # -------------------------------------------------------------- # We use suffixes here in order to force rebuilding when patching these libs -SPIDERMONKEY_VERSION="mozjs-45.0.2+wildfiregames.1" +SPIDERMONKEY_VERSION="mozjs-45.0.2+wildfiregames.2" NVTT_VERSION="nvtt-2.1.1+wildfiregames.1" FCOLLADA_VERSION="fcollada-3.05+wildfiregames.1" # -------------------------------------------------------------- # 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)" != "$LIB_VERSION" ]] 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 echo "$LIB_VERSION" > .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)" != "$LIB_VERSION" ]] 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 echo "$LIB_VERSION" > .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)" != "$LIB_VERSION" ]] 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 echo "$LIB_VERSION" > .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)" != "$LIB_VERSION" ]] 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 echo "$LIB_VERSION" > .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)" != "$LIB_VERSION" ]] 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-video-cocoa \ --enable-shared=no \ && make $JOBS && make install) || die "SDL2 build failed" popd echo "$LIB_VERSION" > .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="https://dl.bintray.com/boostorg/release/1.64.0/source/" mkdir -p boost pushd boost > /dev/null if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ "$(<.already-built)" != "$LIB_VERSION" ]] 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 echo "$LIB_VERSION" > .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)" != "$LIB_VERSION" ]] 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 echo "$LIB_VERSION" > .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)" != "$LIB_VERSION" ]] 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 echo "$LIB_VERSION" > .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)" != "$LIB_VERSION" ]] 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 echo "$LIB_VERSION" > .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)" != "$LIB_VERSION" ]] 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 echo "$LIB_VERSION" > .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)" != "$LIB_VERSION" ]] 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 echo "$LIB_VERSION" > .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)" != "$LIB_VERSION" ]] 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 echo "$LIB_VERSION" > .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)" != "$LIB_VERSION" ]] 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-guile \ --disable-nls \ && make ${JOBS} LDFLAGS= install) || die "GnuTLS build failed" popd echo "$LIB_VERSION" > .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)" != "$LIB_VERSION" ]] 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 echo "$LIB_VERSION" > .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="https://github.com/unicode-org/icu/releases/download/release-59-2/" mkdir -p $LIB_DIRECTORY pushd icu > /dev/null if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ "$(<.already-built)" != "$LIB_VERSION" ]] 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 echo "$LIB_VERSION" > .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)" != "$LIB_VERSION" ]] 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 echo "$LIB_VERSION" > .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)" != "$LIB_VERSION" ]] 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 echo "$LIB_VERSION" > .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)" != "$LIB_VERSION" ]] 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 echo "$LIB_VERSION" > .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 # -------------------------------------------------------------------- # SpiderMonkey - bundled, no download echo -e "Building SpiderMonkey..." LIB_VERSION="${SPIDERMONKEY_VERSION}" LIB_DIRECTORY="mozjs-45.0.2" LIB_ARCHIVE="$LIB_DIRECTORY.tar.bz2" pushd ../source/spidermonkey/ > /dev/null if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ "$(<.already-built)" != "$LIB_VERSION" ]] 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 CONF_OPTS="--target=$ARCH-apple-darwin --prefix=${INSTALL_DIR} --enable-posix-nspr-emulation --with-system-zlib=${ZLIB_DIR} --disable-tests --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/\""' 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/sdk/lib/*.a $INSTALL_DIR/lib cp js/src/*.a $INSTALL_DIR/lib popd mv moz.build.bak 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/sdk/lib/*.a $INSTALL_DIR/lib cp js/src/*.a $INSTALL_DIR/lib popd mv moz.build.bak moz.build popd echo "$LIB_VERSION" > .already-built else already_built fi popd > /dev/null # -------------------------------------------------------------- # NVTT - bundled, no download echo -e "Building NVTT..." LIB_VERSION="${NVTT_VERSION}" pushd ../source/nvtt > /dev/null if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ "$(<.already-built)" != "$LIB_VERSION" ]] 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 png support (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 \ -DPNG=0 \ -G "Unix Makefiles" \ && make clean && make nvtt ${JOBS}) || die "NVTT build failed" popd mkdir -p ../lib cp build/src/bc*/libbc*.a ../lib/ cp build/src/nv*/libnv*.a ../lib/ cp build/src/nvtt/squish/libsquish.a ../lib/ popd echo "$LIB_VERSION" > .already-built else already_built fi popd > /dev/null # -------------------------------------------------------------- # FCollada - bundled, no download echo -e "Building FCollada..." LIB_VERSION="${FCOLLADA_VERSION}" pushd ../source/fcollada > /dev/null if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ "$(<.already-built)" != "$LIB_VERSION" ]] 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 echo "$LIB_VERSION" > .already-built else already_built fi popd > /dev/null Index: ps/trunk/libraries/source/spidermonkey/FixPersistentRootedSymbol.diff =================================================================== --- ps/trunk/libraries/source/spidermonkey/FixPersistentRootedSymbol.diff (nonexistent) +++ ps/trunk/libraries/source/spidermonkey/FixPersistentRootedSymbol.diff (revision 24167) @@ -0,0 +1,13 @@ +diff --git a/js/public/RootingAPI.h b/js/public/RootingAPI.h +index 4e925cba04..06d2a95440 100644 +--- a/js/public/RootingAPI.h ++++ b/js/public/RootingAPI.h +@@ -1019,6 +1019,7 @@ class PersistentRooted : public js::PersistentRootedBase, + MOZ_ASSERT(kind == js::THING_ROOT_OBJECT || + kind == js::THING_ROOT_SCRIPT || + kind == js::THING_ROOT_STRING || ++ kind == js::THING_ROOT_SYMBOL || + kind == js::THING_ROOT_ID || + kind == js::THING_ROOT_VALUE || + kind == js::THING_ROOT_TRACEABLE); + Property changes on: ps/trunk/libraries/source/spidermonkey/FixPersistentRootedSymbol.diff ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/libraries/source/spidermonkey/include-win32-debug/js/RootingAPI.h =================================================================== --- ps/trunk/libraries/source/spidermonkey/include-win32-debug/js/RootingAPI.h (revision 24166) +++ ps/trunk/libraries/source/spidermonkey/include-win32-debug/js/RootingAPI.h (revision 24167) @@ -1,1201 +1,1202 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * 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 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef js_RootingAPI_h #define js_RootingAPI_h #include "mozilla/Attributes.h" #include "mozilla/DebugOnly.h" #include "mozilla/GuardObjects.h" #include "mozilla/LinkedList.h" #include "mozilla/Move.h" #include "mozilla/TypeTraits.h" #include "jspubtd.h" #include "js/GCAPI.h" #include "js/HeapAPI.h" #include "js/TypeDecls.h" #include "js/Utility.h" /* * Moving GC Stack Rooting * * A moving GC may change the physical location of GC allocated things, even * when they are rooted, updating all pointers to the thing to refer to its new * location. The GC must therefore know about all live pointers to a thing, * not just one of them, in order to behave correctly. * * The |Rooted| and |Handle| classes below are used to root stack locations * whose value may be held live across a call that can trigger GC. For a * code fragment such as: * * JSObject* obj = NewObject(cx); * DoSomething(cx); * ... = obj->lastProperty(); * * If |DoSomething()| can trigger a GC, the stack location of |obj| must be * rooted to ensure that the GC does not move the JSObject referred to by * |obj| without updating |obj|'s location itself. This rooting must happen * regardless of whether there are other roots which ensure that the object * itself will not be collected. * * If |DoSomething()| cannot trigger a GC, and the same holds for all other * calls made between |obj|'s definitions and its last uses, then no rooting * is required. * * SpiderMonkey can trigger a GC at almost any time and in ways that are not * always clear. For example, the following innocuous-looking actions can * cause a GC: allocation of any new GC thing; JSObject::hasProperty; * JS_ReportError and friends; and ToNumber, among many others. The following * dangerous-looking actions cannot trigger a GC: js_malloc, cx->malloc_, * rt->malloc_, and friends and JS_ReportOutOfMemory. * * The following family of three classes will exactly root a stack location. * Incorrect usage of these classes will result in a compile error in almost * all cases. Therefore, it is very hard to be incorrectly rooted if you use * these classes exclusively. These classes are all templated on the type T of * the value being rooted. * * - Rooted declares a variable of type T, whose value is always rooted. * Rooted may be automatically coerced to a Handle, below. Rooted * should be used whenever a local variable's value may be held live across a * call which can trigger a GC. * * - Handle is a const reference to a Rooted. Functions which take GC * things or values as arguments and need to root those arguments should * generally use handles for those arguments and avoid any explicit rooting. * This has two benefits. First, when several such functions call each other * then redundant rooting of multiple copies of the GC thing can be avoided. * Second, if the caller does not pass a rooted value a compile error will be * generated, which is quicker and easier to fix than when relying on a * separate rooting analysis. * * - MutableHandle is a non-const reference to Rooted. It is used in the * same way as Handle and includes a |set(const T& v)| method to allow * updating the value of the referenced Rooted. A MutableHandle can be * created with an implicit cast from a Rooted*. * * In some cases the small performance overhead of exact rooting (measured to * be a few nanoseconds on desktop) is too much. In these cases, try the * following: * * - Move all Rooted above inner loops: this allows you to re-use the root * on each iteration of the loop. * * - Pass Handle through your hot call stack to avoid re-rooting costs at * every invocation. * * The following diagram explains the list of supported, implicit type * conversions between classes of this family: * * Rooted ----> Handle * | ^ * | | * | | * +---> MutableHandle * (via &) * * All of these types have an implicit conversion to raw pointers. */ namespace js { template struct GCMethods { static T initial() { return T(); } }; template class RootedBase {}; template class HandleBase {}; template class MutableHandleBase {}; template class HeapBase {}; template class PersistentRootedBase {}; static void* const ConstNullValue = nullptr; namespace gc { struct Cell; template struct PersistentRootedMarker; } /* namespace gc */ #define DECLARE_POINTER_COMPARISON_OPS(T) \ bool operator==(const T& other) const { return get() == other; } \ bool operator!=(const T& other) const { return get() != other; } // Important: Return a reference so passing a Rooted, etc. to // something that takes a |const T&| is not a GC hazard. #define DECLARE_POINTER_CONSTREF_OPS(T) \ operator const T&() const { return get(); } \ const T& operator->() const { return get(); } // Assignment operators on a base class are hidden by the implicitly defined // operator= on the derived class. Thus, define the operator= directly on the // class as we would need to manually pass it through anyway. #define DECLARE_POINTER_ASSIGN_OPS(Wrapper, T) \ Wrapper& operator=(const T& p) { \ set(p); \ return *this; \ } \ Wrapper& operator=(const Wrapper& other) { \ set(other.get()); \ return *this; \ } \ #define DELETE_ASSIGNMENT_OPS(Wrapper, T) \ template Wrapper& operator=(S) = delete; \ Wrapper& operator=(const Wrapper&) = delete; #define DECLARE_NONPOINTER_ACCESSOR_METHODS(ptr) \ const T* address() const { return &(ptr); } \ const T& get() const { return (ptr); } \ #define DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS(ptr) \ T* address() { return &(ptr); } \ T& get() { return (ptr); } \ } /* namespace js */ namespace JS { template class Rooted; template class PersistentRooted; /* This is exposing internal state of the GC for inlining purposes. */ JS_FRIEND_API(bool) isGCEnabled(); JS_FRIEND_API(void) HeapObjectPostBarrier(JSObject** objp, JSObject* prev, JSObject* next); #ifdef JS_DEBUG /** * For generational GC, assert that an object is in the tenured generation as * opposed to being in the nursery. */ extern JS_FRIEND_API(void) AssertGCThingMustBeTenured(JSObject* obj); extern JS_FRIEND_API(void) AssertGCThingIsNotAnObjectSubclass(js::gc::Cell* cell); #else inline void AssertGCThingMustBeTenured(JSObject* obj) {} inline void AssertGCThingIsNotAnObjectSubclass(js::gc::Cell* cell) {} #endif /** * The Heap class is a heap-stored reference to a JS GC thing. All members of * heap classes that refer to GC things should use Heap (or possibly * TenuredHeap, described below). * * Heap is an abstraction that hides some of the complexity required to * maintain GC invariants for the contained reference. It uses operator * overloading to provide a normal pointer interface, but notifies the GC every * time the value it contains is updated. This is necessary for generational GC, * which keeps track of all pointers into the nursery. * * Heap instances must be traced when their containing object is traced to * keep the pointed-to GC thing alive. * * Heap objects should only be used on the heap. GC references stored on the * C/C++ stack must use Rooted/Handle/MutableHandle instead. * * Type T must be one of: JS::Value, jsid, JSObject*, JSString*, JSScript* */ template class Heap : public js::HeapBase { public: Heap() { static_assert(sizeof(T) == sizeof(Heap), "Heap must be binary compatible with T."); init(js::GCMethods::initial()); } explicit Heap(T p) { init(p); } /* * For Heap, move semantics are equivalent to copy semantics. In C++, a * copy constructor taking const-ref is the way to get a single function * that will be used for both lvalue and rvalue copies, so we can simply * omit the rvalue variant. */ explicit Heap(const Heap& p) { init(p.ptr); } ~Heap() { post(ptr, js::GCMethods::initial()); } DECLARE_POINTER_CONSTREF_OPS(T); DECLARE_POINTER_ASSIGN_OPS(Heap, T); DECLARE_NONPOINTER_ACCESSOR_METHODS(ptr); T* unsafeGet() { return &ptr; } /* * Set the pointer to a value which will cause a crash if it is * dereferenced. */ void setToCrashOnTouch() { ptr = reinterpret_cast(crashOnTouchPointer); } bool isSetToCrashOnTouch() { return ptr == crashOnTouchPointer; } private: void init(T newPtr) { ptr = newPtr; post(js::GCMethods::initial(), ptr); } void set(T newPtr) { T tmp = ptr; ptr = newPtr; post(tmp, ptr); } void post(const T& prev, const T& next) { js::GCMethods::postBarrier(&ptr, prev, next); } enum { crashOnTouchPointer = 1 }; T ptr; }; /** * The TenuredHeap class is similar to the Heap class above in that it * encapsulates the GC concerns of an on-heap reference to a JS object. However, * it has two important differences: * * 1) Pointers which are statically known to only reference "tenured" objects * can avoid the extra overhead of SpiderMonkey's write barriers. * * 2) Objects in the "tenured" heap have stronger alignment restrictions than * those in the "nursery", so it is possible to store flags in the lower * bits of pointers known to be tenured. TenuredHeap wraps a normal tagged * pointer with a nice API for accessing the flag bits and adds various * assertions to ensure that it is not mis-used. * * GC things are said to be "tenured" when they are located in the long-lived * heap: e.g. they have gained tenure as an object by surviving past at least * one GC. For performance, SpiderMonkey allocates some things which are known * to normally be long lived directly into the tenured generation; for example, * global objects. Additionally, SpiderMonkey does not visit individual objects * when deleting non-tenured objects, so object with finalizers are also always * tenured; for instance, this includes most DOM objects. * * The considerations to keep in mind when using a TenuredHeap vs a normal * Heap are: * * - It is invalid for a TenuredHeap to refer to a non-tenured thing. * - It is however valid for a Heap to refer to a tenured thing. * - It is not possible to store flag bits in a Heap. */ template class TenuredHeap : public js::HeapBase { public: TenuredHeap() : bits(0) { static_assert(sizeof(T) == sizeof(TenuredHeap), "TenuredHeap must be binary compatible with T."); } explicit TenuredHeap(T p) : bits(0) { setPtr(p); } explicit TenuredHeap(const TenuredHeap& p) : bits(0) { setPtr(p.getPtr()); } bool operator==(const TenuredHeap& other) { return bits == other.bits; } bool operator!=(const TenuredHeap& other) { return bits != other.bits; } void setPtr(T newPtr) { MOZ_ASSERT((reinterpret_cast(newPtr) & flagsMask) == 0); if (newPtr) AssertGCThingMustBeTenured(newPtr); bits = (bits & flagsMask) | reinterpret_cast(newPtr); } void setFlags(uintptr_t flagsToSet) { MOZ_ASSERT((flagsToSet & ~flagsMask) == 0); bits |= flagsToSet; } void unsetFlags(uintptr_t flagsToUnset) { MOZ_ASSERT((flagsToUnset & ~flagsMask) == 0); bits &= ~flagsToUnset; } bool hasFlag(uintptr_t flag) const { MOZ_ASSERT((flag & ~flagsMask) == 0); return (bits & flag) != 0; } T getPtr() const { return reinterpret_cast(bits & ~flagsMask); } uintptr_t getFlags() const { return bits & flagsMask; } operator T() const { return getPtr(); } T operator->() const { return getPtr(); } TenuredHeap& operator=(T p) { setPtr(p); return *this; } TenuredHeap& operator=(const TenuredHeap& other) { bits = other.bits; return *this; } private: enum { maskBits = 3, flagsMask = (1 << maskBits) - 1, }; uintptr_t bits; }; /** * Reference to a T that has been rooted elsewhere. This is most useful * as a parameter type, which guarantees that the T lvalue is properly * rooted. See "Move GC Stack Rooting" above. * * If you want to add additional methods to Handle for a specific * specialization, define a HandleBase specialization containing them. */ template class MOZ_NONHEAP_CLASS Handle : public js::HandleBase { friend class JS::MutableHandle; public: /* Creates a handle from a handle of a type convertible to T. */ template MOZ_IMPLICIT Handle(Handle handle, typename mozilla::EnableIf::value, int>::Type dummy = 0) { static_assert(sizeof(Handle) == sizeof(T*), "Handle must be binary compatible with T*."); ptr = reinterpret_cast(handle.address()); } MOZ_IMPLICIT Handle(decltype(nullptr)) { static_assert(mozilla::IsPointer::value, "nullptr_t overload not valid for non-pointer types"); ptr = reinterpret_cast(&js::ConstNullValue); } MOZ_IMPLICIT Handle(MutableHandle handle) { ptr = handle.address(); } /* * Take care when calling this method! * * This creates a Handle from the raw location of a T. * * It should be called only if the following conditions hold: * * 1) the location of the T is guaranteed to be marked (for some reason * other than being a Rooted), e.g., if it is guaranteed to be reachable * from an implicit root. * * 2) the contents of the location are immutable, or at least cannot change * for the lifetime of the handle, as its users may not expect its value * to change underneath them. */ static MOZ_CONSTEXPR Handle fromMarkedLocation(const T* p) { return Handle(p, DeliberatelyChoosingThisOverload, ImUsingThisOnlyInFromFromMarkedLocation); } /* * Construct a handle from an explicitly rooted location. This is the * normal way to create a handle, and normally happens implicitly. */ template inline MOZ_IMPLICIT Handle(const Rooted& root, typename mozilla::EnableIf::value, int>::Type dummy = 0); template inline MOZ_IMPLICIT Handle(const PersistentRooted& root, typename mozilla::EnableIf::value, int>::Type dummy = 0); /* Construct a read only handle from a mutable handle. */ template inline MOZ_IMPLICIT Handle(MutableHandle& root, typename mozilla::EnableIf::value, int>::Type dummy = 0); DECLARE_POINTER_COMPARISON_OPS(T); DECLARE_POINTER_CONSTREF_OPS(T); DECLARE_NONPOINTER_ACCESSOR_METHODS(*ptr); private: Handle() {} DELETE_ASSIGNMENT_OPS(Handle, T); enum Disambiguator { DeliberatelyChoosingThisOverload = 42 }; enum CallerIdentity { ImUsingThisOnlyInFromFromMarkedLocation = 17 }; MOZ_CONSTEXPR Handle(const T* p, Disambiguator, CallerIdentity) : ptr(p) {} const T* ptr; }; /** * Similar to a handle, but the underlying storage can be changed. This is * useful for outparams. * * If you want to add additional methods to MutableHandle for a specific * specialization, define a MutableHandleBase specialization containing * them. */ template class MOZ_STACK_CLASS MutableHandle : public js::MutableHandleBase { public: inline MOZ_IMPLICIT MutableHandle(Rooted* root); inline MOZ_IMPLICIT MutableHandle(PersistentRooted* root); private: // Disallow nullptr for overloading purposes. MutableHandle(decltype(nullptr)) = delete; public: void set(T v) { *ptr = v; } /* * This may be called only if the location of the T is guaranteed * to be marked (for some reason other than being a Rooted), * e.g., if it is guaranteed to be reachable from an implicit root. * * Create a MutableHandle from a raw location of a T. */ static MutableHandle fromMarkedLocation(T* p) { MutableHandle h; h.ptr = p; return h; } DECLARE_POINTER_CONSTREF_OPS(T); DECLARE_NONPOINTER_ACCESSOR_METHODS(*ptr); DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS(*ptr); private: MutableHandle() {} DELETE_ASSIGNMENT_OPS(MutableHandle, T); T* ptr; }; } /* namespace JS */ namespace js { /** * By default, things should use the inheritance hierarchy to find their * ThingRootKind. Some pointer types are explicitly set in jspubtd.h so that * Rooted may be used without the class definition being available. */ template struct RootKind { static ThingRootKind rootKind() { return T::rootKind(); } }; template struct RootKind { static ThingRootKind rootKind() { return T::rootKind(); } }; template struct GCMethods { static T* initial() { return nullptr; } static void postBarrier(T** vp, T* prev, T* next) { if (next) JS::AssertGCThingIsNotAnObjectSubclass(reinterpret_cast(next)); } static void relocate(T** vp) {} }; template <> struct GCMethods { static JSObject* initial() { return nullptr; } static gc::Cell* asGCThingOrNull(JSObject* v) { if (!v) return nullptr; MOZ_ASSERT(uintptr_t(v) > 32); return reinterpret_cast(v); } static void postBarrier(JSObject** vp, JSObject* prev, JSObject* next) { JS::HeapObjectPostBarrier(vp, prev, next); } }; template <> struct GCMethods { static JSFunction* initial() { return nullptr; } static void postBarrier(JSFunction** vp, JSFunction* prev, JSFunction* next) { JS::HeapObjectPostBarrier(reinterpret_cast(vp), reinterpret_cast(prev), reinterpret_cast(next)); } }; // Provide hash codes for Cell kinds that may be relocated and, thus, not have // a stable address to use as the base for a hash code. Instead of the address, // this hasher uses Cell::getUniqueId to provide exact matches and as a base // for generating hash codes. // // Note: this hasher, like PointerHasher can "hash" a nullptr. While a nullptr // would not likely be a useful key, there are some cases where being able to // hash a nullptr is useful, either on purpose or because of bugs: // (1) existence checks where the key may happen to be null and (2) some // aggregate Lookup kinds embed a JSObject* that is frequently null and do not // null test before dispatching to the hasher. template struct JS_PUBLIC_API(MovableCellHasher) { using Key = T; using Lookup = T; static HashNumber hash(const Lookup& l); static bool match(const Key& k, const Lookup& l); static void rekey(Key& k, const Key& newKey) { k = newKey; } }; template struct JS_PUBLIC_API(MovableCellHasher>) { using Key = JS::Heap; using Lookup = T; static HashNumber hash(const Lookup& l) { return MovableCellHasher::hash(l); } static bool match(const Key& k, const Lookup& l) { return MovableCellHasher::match(k, l); } static void rekey(Key& k, const Key& newKey) { k.unsafeSet(newKey); } }; } /* namespace js */ namespace JS { // Non pointer types -- structs or classes that contain GC pointers, either as // a member or in a more complex container layout -- can also be stored in a // [Persistent]Rooted if it derives from JS::Traceable. A JS::Traceable stored // in a [Persistent]Rooted must implement the method: // |static void trace(T*, JSTracer*)| class Traceable { public: static js::ThingRootKind rootKind() { return js::THING_ROOT_TRACEABLE; } }; } /* namespace JS */ namespace js { template class DispatchWrapper { static_assert(mozilla::IsBaseOf::value, "DispatchWrapper is intended only for usage with a Traceable"); using TraceFn = void (*)(T*, JSTracer*); TraceFn tracer; #if JS_BITS_PER_WORD == 32 uint32_t padding; // Ensure the storage fields have CellSize alignment. #endif T storage; public: template MOZ_IMPLICIT DispatchWrapper(U&& initial) : tracer(&T::trace), storage(mozilla::Forward(initial)) { } // Mimic a pointer type, so that we can drop into Rooted. T* operator &() { return &storage; } const T* operator &() const { return &storage; } operator T&() { return storage; } operator const T&() const { return storage; } // Trace the contained storage (of unknown type) using the trace function // we set aside when we did know the type. static void TraceWrapped(JSTracer* trc, JS::Traceable* thingp, const char* name) { auto wrapper = reinterpret_cast( uintptr_t(thingp) - offsetof(DispatchWrapper, storage)); wrapper->tracer(&wrapper->storage, trc); } }; inline RootLists& RootListsForRootingContext(JSContext* cx) { return ContextFriendFields::get(cx)->roots; } inline RootLists& RootListsForRootingContext(js::ContextFriendFields* cx) { return cx->roots; } inline RootLists& RootListsForRootingContext(JSRuntime* rt) { return PerThreadDataFriendFields::getMainThread(rt)->roots; } inline RootLists& RootListsForRootingContext(js::PerThreadDataFriendFields* pt) { return pt->roots; } } /* namespace js */ namespace JS { /** * Local variable of type T whose value is always rooted. This is typically * used for local variables, or for non-rooted values being passed to a * function that requires a handle, e.g. Foo(Root(cx, x)). * * If you want to add additional methods to Rooted for a specific * specialization, define a RootedBase specialization containing them. */ template class MOZ_RAII Rooted : public js::RootedBase { static_assert(!mozilla::IsConvertible::value, "Rooted takes pointer or Traceable types but not Traceable* type"); /* Note: CX is a subclass of either ContextFriendFields or PerThreadDataFriendFields. */ void registerWithRootLists(js::RootLists& roots) { js::ThingRootKind kind = js::RootKind::rootKind(); this->stack = &roots.stackRoots_[kind]; this->prev = *stack; *stack = reinterpret_cast*>(this); } public: template explicit Rooted(const RootingContext& cx) : ptr(js::GCMethods::initial()) { registerWithRootLists(js::RootListsForRootingContext(cx)); } template Rooted(const RootingContext& cx, S&& initial) : ptr(mozilla::Forward(initial)) { registerWithRootLists(js::RootListsForRootingContext(cx)); } ~Rooted() { MOZ_ASSERT(*stack == reinterpret_cast*>(this)); *stack = prev; } Rooted* previous() { return reinterpret_cast*>(prev); } /* * This method is public for Rooted so that Codegen.py can use a Rooted * interchangeably with a MutableHandleValue. */ void set(T value) { ptr = value; } DECLARE_POINTER_COMPARISON_OPS(T); DECLARE_POINTER_CONSTREF_OPS(T); DECLARE_POINTER_ASSIGN_OPS(Rooted, T); DECLARE_NONPOINTER_ACCESSOR_METHODS(ptr); DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS(ptr); private: /* * These need to be templated on void* to avoid aliasing issues between, for * example, Rooted and Rooted, which use the same * stack head pointer for different classes. */ Rooted** stack; Rooted* prev; /* * For pointer types, the TraceKind for tracing is based on the list it is * in (selected via rootKind), so no additional storage is required here. * All Traceable, however, share the same list, so the function to * call for tracing is stored adjacent to the struct. Since C++ cannot * templatize on storage class, this is implemented via the wrapper class * DispatchWrapper. */ using MaybeWrapped = typename mozilla::Conditional< mozilla::IsBaseOf::value, js::DispatchWrapper, T>::Type; MaybeWrapped ptr; Rooted(const Rooted&) = delete; }; } /* namespace JS */ namespace js { /** * Augment the generic Rooted interface when T = JSObject* with * class-querying and downcasting operations. * * Given a Rooted obj, one can view * Handle h = obj.as(); * as an optimization of * Rooted rooted(cx, &obj->as()); * Handle h = rooted; */ template <> class RootedBase { public: template JS::Handle as() const; }; /** * Augment the generic Handle interface when T = JSObject* with * downcasting operations. * * Given a Handle obj, one can view * Handle h = obj.as(); * as an optimization of * Rooted rooted(cx, &obj->as()); * Handle h = rooted; */ template <> class HandleBase { public: template JS::Handle as() const; }; /** Interface substitute for Rooted which does not root the variable's memory. */ template class MOZ_RAII FakeRooted : public RootedBase { public: template explicit FakeRooted(CX* cx) : ptr(GCMethods::initial()) {} template FakeRooted(CX* cx, T initial) : ptr(initial) {} DECLARE_POINTER_COMPARISON_OPS(T); DECLARE_POINTER_CONSTREF_OPS(T); DECLARE_POINTER_ASSIGN_OPS(FakeRooted, T); DECLARE_NONPOINTER_ACCESSOR_METHODS(ptr); DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS(ptr); private: T ptr; void set(const T& value) { ptr = value; } FakeRooted(const FakeRooted&) = delete; }; /** Interface substitute for MutableHandle which is not required to point to rooted memory. */ template class FakeMutableHandle : public js::MutableHandleBase { public: MOZ_IMPLICIT FakeMutableHandle(T* t) { ptr = t; } MOZ_IMPLICIT FakeMutableHandle(FakeRooted* root) { ptr = root->address(); } void set(T v) { *ptr = v; } DECLARE_POINTER_CONSTREF_OPS(T); DECLARE_NONPOINTER_ACCESSOR_METHODS(*ptr); DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS(*ptr); private: FakeMutableHandle() {} DELETE_ASSIGNMENT_OPS(FakeMutableHandle, T); T* ptr; }; /** * Types for a variable that either should or shouldn't be rooted, depending on * the template parameter allowGC. Used for implementing functions that can * operate on either rooted or unrooted data. * * The toHandle() and toMutableHandle() functions are for calling functions * which require handle types and are only called in the CanGC case. These * allow the calling code to type check. */ enum AllowGC { NoGC = 0, CanGC = 1 }; template class MaybeRooted { }; template class MaybeRooted { public: typedef JS::Handle HandleType; typedef JS::Rooted RootType; typedef JS::MutableHandle MutableHandleType; static inline JS::Handle toHandle(HandleType v) { return v; } static inline JS::MutableHandle toMutableHandle(MutableHandleType v) { return v; } template static inline JS::Handle downcastHandle(HandleType v) { return v.template as(); } }; template class MaybeRooted { public: typedef T HandleType; typedef FakeRooted RootType; typedef FakeMutableHandle MutableHandleType; static JS::Handle toHandle(HandleType v) { MOZ_CRASH("Bad conversion"); } static JS::MutableHandle toMutableHandle(MutableHandleType v) { MOZ_CRASH("Bad conversion"); } template static inline T2* downcastHandle(HandleType v) { return &v->template as(); } }; } /* namespace js */ namespace JS { template template inline Handle::Handle(const Rooted& root, typename mozilla::EnableIf::value, int>::Type dummy) { ptr = reinterpret_cast(root.address()); } template template inline Handle::Handle(const PersistentRooted& root, typename mozilla::EnableIf::value, int>::Type dummy) { ptr = reinterpret_cast(root.address()); } template template inline Handle::Handle(MutableHandle& root, typename mozilla::EnableIf::value, int>::Type dummy) { ptr = reinterpret_cast(root.address()); } template inline MutableHandle::MutableHandle(Rooted* root) { static_assert(sizeof(MutableHandle) == sizeof(T*), "MutableHandle must be binary compatible with T*."); ptr = root->address(); } template inline MutableHandle::MutableHandle(PersistentRooted* root) { static_assert(sizeof(MutableHandle) == sizeof(T*), "MutableHandle must be binary compatible with T*."); ptr = root->address(); } /** * A copyable, assignable global GC root type with arbitrary lifetime, an * infallible constructor, and automatic unrooting on destruction. * * These roots can be used in heap-allocated data structures, so they are not * associated with any particular JSContext or stack. They are registered with * the JSRuntime itself, without locking, so they require a full JSContext to be * initialized, not one of its more restricted superclasses. Initialization may * take place on construction, or in two phases if the no-argument constructor * is called followed by init(). * * Note that you must not use an PersistentRooted in an object owned by a JS * object: * * Whenever one object whose lifetime is decided by the GC refers to another * such object, that edge must be traced only if the owning JS object is traced. * This applies not only to JS objects (which obviously are managed by the GC) * but also to C++ objects owned by JS objects. * * If you put a PersistentRooted in such a C++ object, that is almost certainly * a leak. When a GC begins, the referent of the PersistentRooted is treated as * live, unconditionally (because a PersistentRooted is a *root*), even if the * JS object that owns it is unreachable. If there is any path from that * referent back to the JS object, then the C++ object containing the * PersistentRooted will not be destructed, and the whole blob of objects will * not be freed, even if there are no references to them from the outside. * * In the context of Firefox, this is a severe restriction: almost everything in * Firefox is owned by some JS object or another, so using PersistentRooted in * such objects would introduce leaks. For these kinds of edges, Heap or * TenuredHeap would be better types. It's up to the implementor of the type * containing Heap or TenuredHeap members to make sure their referents get * marked when the object itself is marked. */ template class PersistentRooted : public js::PersistentRootedBase, private mozilla::LinkedListElement> { typedef mozilla::LinkedListElement> ListBase; friend class mozilla::LinkedList; friend class mozilla::LinkedListElement; friend struct js::gc::PersistentRootedMarker; friend void js::gc::FinishPersistentRootedChains(js::RootLists&); void registerWithRootLists(js::RootLists& roots) { MOZ_ASSERT(!initialized()); js::ThingRootKind kind = js::RootKind::rootKind(); roots.heapRoots_[kind].insertBack(reinterpret_cast*>(this)); // Until marking and destruction support the full set, we assert that // we don't try to add any unsupported types. MOZ_ASSERT(kind == js::THING_ROOT_OBJECT || kind == js::THING_ROOT_SCRIPT || kind == js::THING_ROOT_STRING || + kind == js::THING_ROOT_SYMBOL || kind == js::THING_ROOT_ID || kind == js::THING_ROOT_VALUE || kind == js::THING_ROOT_TRACEABLE); } public: PersistentRooted() : ptr(js::GCMethods::initial()) {} template explicit PersistentRooted(const RootingContext& cx) : ptr(js::GCMethods::initial()) { registerWithRootLists(js::RootListsForRootingContext(cx)); } template PersistentRooted(const RootingContext& cx, U&& initial) : ptr(mozilla::Forward(initial)) { registerWithRootLists(js::RootListsForRootingContext(cx)); } PersistentRooted(const PersistentRooted& rhs) : mozilla::LinkedListElement>(), ptr(rhs.ptr) { /* * Copy construction takes advantage of the fact that the original * is already inserted, and simply adds itself to whatever list the * original was on - no JSRuntime pointer needed. * * This requires mutating rhs's links, but those should be 'mutable' * anyway. C++ doesn't let us declare mutable base classes. */ const_cast(rhs).setNext(this); } bool initialized() { return ListBase::isInList(); } template void init(const RootingContext& cx) { init(cx, js::GCMethods::initial()); } template void init(const RootingContext& cx, U&& initial) { ptr = mozilla::Forward(initial); registerWithRootLists(js::RootListsForRootingContext(cx)); } void reset() { if (initialized()) { set(js::GCMethods::initial()); ListBase::remove(); } } DECLARE_POINTER_COMPARISON_OPS(T); DECLARE_POINTER_CONSTREF_OPS(T); DECLARE_POINTER_ASSIGN_OPS(PersistentRooted, T); DECLARE_NONPOINTER_ACCESSOR_METHODS(ptr); // These are the same as DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS, except // they check that |this| is initialized in case the caller later stores // something in |ptr|. T* address() { MOZ_ASSERT(initialized()); return &ptr; } T& get() { MOZ_ASSERT(initialized()); return ptr; } private: void set(T value) { MOZ_ASSERT(initialized()); ptr = value; } // See the comment above Rooted::ptr. using MaybeWrapped = typename mozilla::Conditional< mozilla::IsBaseOf::value, js::DispatchWrapper, T>::Type; MaybeWrapped ptr; }; class JS_PUBLIC_API(ObjectPtr) { Heap value; public: ObjectPtr() : value(nullptr) {} explicit ObjectPtr(JSObject* obj) : value(obj) {} /* Always call finalize before the destructor. */ ~ObjectPtr() { MOZ_ASSERT(!value); } void finalize(JSRuntime* rt) { if (IsIncrementalBarrierNeeded(rt)) IncrementalObjectBarrier(value); value = nullptr; } void init(JSObject* obj) { value = obj; } JSObject* get() const { return value; } void writeBarrierPre(JSRuntime* rt) { IncrementalObjectBarrier(value); } void updateWeakPointerAfterGC(); ObjectPtr& operator=(JSObject* obj) { IncrementalObjectBarrier(value); value = obj; return *this; } void trace(JSTracer* trc, const char* name); JSObject& operator*() const { return *value; } JSObject* operator->() const { return value; } operator JSObject*() const { return value; } }; } /* namespace JS */ namespace js { namespace gc { template void CallTraceCallbackOnNonHeap(T* v, const TraceCallbacks& aCallbacks, const char* aName, void* aClosure) { static_assert(sizeof(T) == sizeof(JS::Heap), "T and Heap must be compatible."); MOZ_ASSERT(v); mozilla::DebugOnly cell = GCMethods::asGCThingOrNull(*v); MOZ_ASSERT(cell); MOZ_ASSERT(!IsInsideNursery(cell)); JS::Heap* asHeapT = reinterpret_cast*>(v); aCallbacks.Trace(asHeapT, aName, aClosure); } } /* namespace gc */ } /* namespace js */ // mozilla::Swap uses a stack temporary, which prevents classes like Heap // from being declared MOZ_HEAP_CLASS. namespace mozilla { template inline void Swap(JS::Heap& aX, JS::Heap& aY) { T tmp = aX; aX = aY; aY = tmp; } template inline void Swap(JS::TenuredHeap& aX, JS::TenuredHeap& aY) { T tmp = aX; aX = aY; aY = tmp; } } /* namespace mozilla */ #undef DELETE_ASSIGNMENT_OPS #endif /* js_RootingAPI_h */ Index: ps/trunk/libraries/source/spidermonkey/include-win32-release/js/RootingAPI.h =================================================================== --- ps/trunk/libraries/source/spidermonkey/include-win32-release/js/RootingAPI.h (revision 24166) +++ ps/trunk/libraries/source/spidermonkey/include-win32-release/js/RootingAPI.h (revision 24167) @@ -1,1201 +1,1202 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * 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 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef js_RootingAPI_h #define js_RootingAPI_h #include "mozilla/Attributes.h" #include "mozilla/DebugOnly.h" #include "mozilla/GuardObjects.h" #include "mozilla/LinkedList.h" #include "mozilla/Move.h" #include "mozilla/TypeTraits.h" #include "jspubtd.h" #include "js/GCAPI.h" #include "js/HeapAPI.h" #include "js/TypeDecls.h" #include "js/Utility.h" /* * Moving GC Stack Rooting * * A moving GC may change the physical location of GC allocated things, even * when they are rooted, updating all pointers to the thing to refer to its new * location. The GC must therefore know about all live pointers to a thing, * not just one of them, in order to behave correctly. * * The |Rooted| and |Handle| classes below are used to root stack locations * whose value may be held live across a call that can trigger GC. For a * code fragment such as: * * JSObject* obj = NewObject(cx); * DoSomething(cx); * ... = obj->lastProperty(); * * If |DoSomething()| can trigger a GC, the stack location of |obj| must be * rooted to ensure that the GC does not move the JSObject referred to by * |obj| without updating |obj|'s location itself. This rooting must happen * regardless of whether there are other roots which ensure that the object * itself will not be collected. * * If |DoSomething()| cannot trigger a GC, and the same holds for all other * calls made between |obj|'s definitions and its last uses, then no rooting * is required. * * SpiderMonkey can trigger a GC at almost any time and in ways that are not * always clear. For example, the following innocuous-looking actions can * cause a GC: allocation of any new GC thing; JSObject::hasProperty; * JS_ReportError and friends; and ToNumber, among many others. The following * dangerous-looking actions cannot trigger a GC: js_malloc, cx->malloc_, * rt->malloc_, and friends and JS_ReportOutOfMemory. * * The following family of three classes will exactly root a stack location. * Incorrect usage of these classes will result in a compile error in almost * all cases. Therefore, it is very hard to be incorrectly rooted if you use * these classes exclusively. These classes are all templated on the type T of * the value being rooted. * * - Rooted declares a variable of type T, whose value is always rooted. * Rooted may be automatically coerced to a Handle, below. Rooted * should be used whenever a local variable's value may be held live across a * call which can trigger a GC. * * - Handle is a const reference to a Rooted. Functions which take GC * things or values as arguments and need to root those arguments should * generally use handles for those arguments and avoid any explicit rooting. * This has two benefits. First, when several such functions call each other * then redundant rooting of multiple copies of the GC thing can be avoided. * Second, if the caller does not pass a rooted value a compile error will be * generated, which is quicker and easier to fix than when relying on a * separate rooting analysis. * * - MutableHandle is a non-const reference to Rooted. It is used in the * same way as Handle and includes a |set(const T& v)| method to allow * updating the value of the referenced Rooted. A MutableHandle can be * created with an implicit cast from a Rooted*. * * In some cases the small performance overhead of exact rooting (measured to * be a few nanoseconds on desktop) is too much. In these cases, try the * following: * * - Move all Rooted above inner loops: this allows you to re-use the root * on each iteration of the loop. * * - Pass Handle through your hot call stack to avoid re-rooting costs at * every invocation. * * The following diagram explains the list of supported, implicit type * conversions between classes of this family: * * Rooted ----> Handle * | ^ * | | * | | * +---> MutableHandle * (via &) * * All of these types have an implicit conversion to raw pointers. */ namespace js { template struct GCMethods { static T initial() { return T(); } }; template class RootedBase {}; template class HandleBase {}; template class MutableHandleBase {}; template class HeapBase {}; template class PersistentRootedBase {}; static void* const ConstNullValue = nullptr; namespace gc { struct Cell; template struct PersistentRootedMarker; } /* namespace gc */ #define DECLARE_POINTER_COMPARISON_OPS(T) \ bool operator==(const T& other) const { return get() == other; } \ bool operator!=(const T& other) const { return get() != other; } // Important: Return a reference so passing a Rooted, etc. to // something that takes a |const T&| is not a GC hazard. #define DECLARE_POINTER_CONSTREF_OPS(T) \ operator const T&() const { return get(); } \ const T& operator->() const { return get(); } // Assignment operators on a base class are hidden by the implicitly defined // operator= on the derived class. Thus, define the operator= directly on the // class as we would need to manually pass it through anyway. #define DECLARE_POINTER_ASSIGN_OPS(Wrapper, T) \ Wrapper& operator=(const T& p) { \ set(p); \ return *this; \ } \ Wrapper& operator=(const Wrapper& other) { \ set(other.get()); \ return *this; \ } \ #define DELETE_ASSIGNMENT_OPS(Wrapper, T) \ template Wrapper& operator=(S) = delete; \ Wrapper& operator=(const Wrapper&) = delete; #define DECLARE_NONPOINTER_ACCESSOR_METHODS(ptr) \ const T* address() const { return &(ptr); } \ const T& get() const { return (ptr); } \ #define DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS(ptr) \ T* address() { return &(ptr); } \ T& get() { return (ptr); } \ } /* namespace js */ namespace JS { template class Rooted; template class PersistentRooted; /* This is exposing internal state of the GC for inlining purposes. */ JS_FRIEND_API(bool) isGCEnabled(); JS_FRIEND_API(void) HeapObjectPostBarrier(JSObject** objp, JSObject* prev, JSObject* next); #ifdef JS_DEBUG /** * For generational GC, assert that an object is in the tenured generation as * opposed to being in the nursery. */ extern JS_FRIEND_API(void) AssertGCThingMustBeTenured(JSObject* obj); extern JS_FRIEND_API(void) AssertGCThingIsNotAnObjectSubclass(js::gc::Cell* cell); #else inline void AssertGCThingMustBeTenured(JSObject* obj) {} inline void AssertGCThingIsNotAnObjectSubclass(js::gc::Cell* cell) {} #endif /** * The Heap class is a heap-stored reference to a JS GC thing. All members of * heap classes that refer to GC things should use Heap (or possibly * TenuredHeap, described below). * * Heap is an abstraction that hides some of the complexity required to * maintain GC invariants for the contained reference. It uses operator * overloading to provide a normal pointer interface, but notifies the GC every * time the value it contains is updated. This is necessary for generational GC, * which keeps track of all pointers into the nursery. * * Heap instances must be traced when their containing object is traced to * keep the pointed-to GC thing alive. * * Heap objects should only be used on the heap. GC references stored on the * C/C++ stack must use Rooted/Handle/MutableHandle instead. * * Type T must be one of: JS::Value, jsid, JSObject*, JSString*, JSScript* */ template class Heap : public js::HeapBase { public: Heap() { static_assert(sizeof(T) == sizeof(Heap), "Heap must be binary compatible with T."); init(js::GCMethods::initial()); } explicit Heap(T p) { init(p); } /* * For Heap, move semantics are equivalent to copy semantics. In C++, a * copy constructor taking const-ref is the way to get a single function * that will be used for both lvalue and rvalue copies, so we can simply * omit the rvalue variant. */ explicit Heap(const Heap& p) { init(p.ptr); } ~Heap() { post(ptr, js::GCMethods::initial()); } DECLARE_POINTER_CONSTREF_OPS(T); DECLARE_POINTER_ASSIGN_OPS(Heap, T); DECLARE_NONPOINTER_ACCESSOR_METHODS(ptr); T* unsafeGet() { return &ptr; } /* * Set the pointer to a value which will cause a crash if it is * dereferenced. */ void setToCrashOnTouch() { ptr = reinterpret_cast(crashOnTouchPointer); } bool isSetToCrashOnTouch() { return ptr == crashOnTouchPointer; } private: void init(T newPtr) { ptr = newPtr; post(js::GCMethods::initial(), ptr); } void set(T newPtr) { T tmp = ptr; ptr = newPtr; post(tmp, ptr); } void post(const T& prev, const T& next) { js::GCMethods::postBarrier(&ptr, prev, next); } enum { crashOnTouchPointer = 1 }; T ptr; }; /** * The TenuredHeap class is similar to the Heap class above in that it * encapsulates the GC concerns of an on-heap reference to a JS object. However, * it has two important differences: * * 1) Pointers which are statically known to only reference "tenured" objects * can avoid the extra overhead of SpiderMonkey's write barriers. * * 2) Objects in the "tenured" heap have stronger alignment restrictions than * those in the "nursery", so it is possible to store flags in the lower * bits of pointers known to be tenured. TenuredHeap wraps a normal tagged * pointer with a nice API for accessing the flag bits and adds various * assertions to ensure that it is not mis-used. * * GC things are said to be "tenured" when they are located in the long-lived * heap: e.g. they have gained tenure as an object by surviving past at least * one GC. For performance, SpiderMonkey allocates some things which are known * to normally be long lived directly into the tenured generation; for example, * global objects. Additionally, SpiderMonkey does not visit individual objects * when deleting non-tenured objects, so object with finalizers are also always * tenured; for instance, this includes most DOM objects. * * The considerations to keep in mind when using a TenuredHeap vs a normal * Heap are: * * - It is invalid for a TenuredHeap to refer to a non-tenured thing. * - It is however valid for a Heap to refer to a tenured thing. * - It is not possible to store flag bits in a Heap. */ template class TenuredHeap : public js::HeapBase { public: TenuredHeap() : bits(0) { static_assert(sizeof(T) == sizeof(TenuredHeap), "TenuredHeap must be binary compatible with T."); } explicit TenuredHeap(T p) : bits(0) { setPtr(p); } explicit TenuredHeap(const TenuredHeap& p) : bits(0) { setPtr(p.getPtr()); } bool operator==(const TenuredHeap& other) { return bits == other.bits; } bool operator!=(const TenuredHeap& other) { return bits != other.bits; } void setPtr(T newPtr) { MOZ_ASSERT((reinterpret_cast(newPtr) & flagsMask) == 0); if (newPtr) AssertGCThingMustBeTenured(newPtr); bits = (bits & flagsMask) | reinterpret_cast(newPtr); } void setFlags(uintptr_t flagsToSet) { MOZ_ASSERT((flagsToSet & ~flagsMask) == 0); bits |= flagsToSet; } void unsetFlags(uintptr_t flagsToUnset) { MOZ_ASSERT((flagsToUnset & ~flagsMask) == 0); bits &= ~flagsToUnset; } bool hasFlag(uintptr_t flag) const { MOZ_ASSERT((flag & ~flagsMask) == 0); return (bits & flag) != 0; } T getPtr() const { return reinterpret_cast(bits & ~flagsMask); } uintptr_t getFlags() const { return bits & flagsMask; } operator T() const { return getPtr(); } T operator->() const { return getPtr(); } TenuredHeap& operator=(T p) { setPtr(p); return *this; } TenuredHeap& operator=(const TenuredHeap& other) { bits = other.bits; return *this; } private: enum { maskBits = 3, flagsMask = (1 << maskBits) - 1, }; uintptr_t bits; }; /** * Reference to a T that has been rooted elsewhere. This is most useful * as a parameter type, which guarantees that the T lvalue is properly * rooted. See "Move GC Stack Rooting" above. * * If you want to add additional methods to Handle for a specific * specialization, define a HandleBase specialization containing them. */ template class MOZ_NONHEAP_CLASS Handle : public js::HandleBase { friend class JS::MutableHandle; public: /* Creates a handle from a handle of a type convertible to T. */ template MOZ_IMPLICIT Handle(Handle handle, typename mozilla::EnableIf::value, int>::Type dummy = 0) { static_assert(sizeof(Handle) == sizeof(T*), "Handle must be binary compatible with T*."); ptr = reinterpret_cast(handle.address()); } MOZ_IMPLICIT Handle(decltype(nullptr)) { static_assert(mozilla::IsPointer::value, "nullptr_t overload not valid for non-pointer types"); ptr = reinterpret_cast(&js::ConstNullValue); } MOZ_IMPLICIT Handle(MutableHandle handle) { ptr = handle.address(); } /* * Take care when calling this method! * * This creates a Handle from the raw location of a T. * * It should be called only if the following conditions hold: * * 1) the location of the T is guaranteed to be marked (for some reason * other than being a Rooted), e.g., if it is guaranteed to be reachable * from an implicit root. * * 2) the contents of the location are immutable, or at least cannot change * for the lifetime of the handle, as its users may not expect its value * to change underneath them. */ static MOZ_CONSTEXPR Handle fromMarkedLocation(const T* p) { return Handle(p, DeliberatelyChoosingThisOverload, ImUsingThisOnlyInFromFromMarkedLocation); } /* * Construct a handle from an explicitly rooted location. This is the * normal way to create a handle, and normally happens implicitly. */ template inline MOZ_IMPLICIT Handle(const Rooted& root, typename mozilla::EnableIf::value, int>::Type dummy = 0); template inline MOZ_IMPLICIT Handle(const PersistentRooted& root, typename mozilla::EnableIf::value, int>::Type dummy = 0); /* Construct a read only handle from a mutable handle. */ template inline MOZ_IMPLICIT Handle(MutableHandle& root, typename mozilla::EnableIf::value, int>::Type dummy = 0); DECLARE_POINTER_COMPARISON_OPS(T); DECLARE_POINTER_CONSTREF_OPS(T); DECLARE_NONPOINTER_ACCESSOR_METHODS(*ptr); private: Handle() {} DELETE_ASSIGNMENT_OPS(Handle, T); enum Disambiguator { DeliberatelyChoosingThisOverload = 42 }; enum CallerIdentity { ImUsingThisOnlyInFromFromMarkedLocation = 17 }; MOZ_CONSTEXPR Handle(const T* p, Disambiguator, CallerIdentity) : ptr(p) {} const T* ptr; }; /** * Similar to a handle, but the underlying storage can be changed. This is * useful for outparams. * * If you want to add additional methods to MutableHandle for a specific * specialization, define a MutableHandleBase specialization containing * them. */ template class MOZ_STACK_CLASS MutableHandle : public js::MutableHandleBase { public: inline MOZ_IMPLICIT MutableHandle(Rooted* root); inline MOZ_IMPLICIT MutableHandle(PersistentRooted* root); private: // Disallow nullptr for overloading purposes. MutableHandle(decltype(nullptr)) = delete; public: void set(T v) { *ptr = v; } /* * This may be called only if the location of the T is guaranteed * to be marked (for some reason other than being a Rooted), * e.g., if it is guaranteed to be reachable from an implicit root. * * Create a MutableHandle from a raw location of a T. */ static MutableHandle fromMarkedLocation(T* p) { MutableHandle h; h.ptr = p; return h; } DECLARE_POINTER_CONSTREF_OPS(T); DECLARE_NONPOINTER_ACCESSOR_METHODS(*ptr); DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS(*ptr); private: MutableHandle() {} DELETE_ASSIGNMENT_OPS(MutableHandle, T); T* ptr; }; } /* namespace JS */ namespace js { /** * By default, things should use the inheritance hierarchy to find their * ThingRootKind. Some pointer types are explicitly set in jspubtd.h so that * Rooted may be used without the class definition being available. */ template struct RootKind { static ThingRootKind rootKind() { return T::rootKind(); } }; template struct RootKind { static ThingRootKind rootKind() { return T::rootKind(); } }; template struct GCMethods { static T* initial() { return nullptr; } static void postBarrier(T** vp, T* prev, T* next) { if (next) JS::AssertGCThingIsNotAnObjectSubclass(reinterpret_cast(next)); } static void relocate(T** vp) {} }; template <> struct GCMethods { static JSObject* initial() { return nullptr; } static gc::Cell* asGCThingOrNull(JSObject* v) { if (!v) return nullptr; MOZ_ASSERT(uintptr_t(v) > 32); return reinterpret_cast(v); } static void postBarrier(JSObject** vp, JSObject* prev, JSObject* next) { JS::HeapObjectPostBarrier(vp, prev, next); } }; template <> struct GCMethods { static JSFunction* initial() { return nullptr; } static void postBarrier(JSFunction** vp, JSFunction* prev, JSFunction* next) { JS::HeapObjectPostBarrier(reinterpret_cast(vp), reinterpret_cast(prev), reinterpret_cast(next)); } }; // Provide hash codes for Cell kinds that may be relocated and, thus, not have // a stable address to use as the base for a hash code. Instead of the address, // this hasher uses Cell::getUniqueId to provide exact matches and as a base // for generating hash codes. // // Note: this hasher, like PointerHasher can "hash" a nullptr. While a nullptr // would not likely be a useful key, there are some cases where being able to // hash a nullptr is useful, either on purpose or because of bugs: // (1) existence checks where the key may happen to be null and (2) some // aggregate Lookup kinds embed a JSObject* that is frequently null and do not // null test before dispatching to the hasher. template struct JS_PUBLIC_API(MovableCellHasher) { using Key = T; using Lookup = T; static HashNumber hash(const Lookup& l); static bool match(const Key& k, const Lookup& l); static void rekey(Key& k, const Key& newKey) { k = newKey; } }; template struct JS_PUBLIC_API(MovableCellHasher>) { using Key = JS::Heap; using Lookup = T; static HashNumber hash(const Lookup& l) { return MovableCellHasher::hash(l); } static bool match(const Key& k, const Lookup& l) { return MovableCellHasher::match(k, l); } static void rekey(Key& k, const Key& newKey) { k.unsafeSet(newKey); } }; } /* namespace js */ namespace JS { // Non pointer types -- structs or classes that contain GC pointers, either as // a member or in a more complex container layout -- can also be stored in a // [Persistent]Rooted if it derives from JS::Traceable. A JS::Traceable stored // in a [Persistent]Rooted must implement the method: // |static void trace(T*, JSTracer*)| class Traceable { public: static js::ThingRootKind rootKind() { return js::THING_ROOT_TRACEABLE; } }; } /* namespace JS */ namespace js { template class DispatchWrapper { static_assert(mozilla::IsBaseOf::value, "DispatchWrapper is intended only for usage with a Traceable"); using TraceFn = void (*)(T*, JSTracer*); TraceFn tracer; #if JS_BITS_PER_WORD == 32 uint32_t padding; // Ensure the storage fields have CellSize alignment. #endif T storage; public: template MOZ_IMPLICIT DispatchWrapper(U&& initial) : tracer(&T::trace), storage(mozilla::Forward(initial)) { } // Mimic a pointer type, so that we can drop into Rooted. T* operator &() { return &storage; } const T* operator &() const { return &storage; } operator T&() { return storage; } operator const T&() const { return storage; } // Trace the contained storage (of unknown type) using the trace function // we set aside when we did know the type. static void TraceWrapped(JSTracer* trc, JS::Traceable* thingp, const char* name) { auto wrapper = reinterpret_cast( uintptr_t(thingp) - offsetof(DispatchWrapper, storage)); wrapper->tracer(&wrapper->storage, trc); } }; inline RootLists& RootListsForRootingContext(JSContext* cx) { return ContextFriendFields::get(cx)->roots; } inline RootLists& RootListsForRootingContext(js::ContextFriendFields* cx) { return cx->roots; } inline RootLists& RootListsForRootingContext(JSRuntime* rt) { return PerThreadDataFriendFields::getMainThread(rt)->roots; } inline RootLists& RootListsForRootingContext(js::PerThreadDataFriendFields* pt) { return pt->roots; } } /* namespace js */ namespace JS { /** * Local variable of type T whose value is always rooted. This is typically * used for local variables, or for non-rooted values being passed to a * function that requires a handle, e.g. Foo(Root(cx, x)). * * If you want to add additional methods to Rooted for a specific * specialization, define a RootedBase specialization containing them. */ template class MOZ_RAII Rooted : public js::RootedBase { static_assert(!mozilla::IsConvertible::value, "Rooted takes pointer or Traceable types but not Traceable* type"); /* Note: CX is a subclass of either ContextFriendFields or PerThreadDataFriendFields. */ void registerWithRootLists(js::RootLists& roots) { js::ThingRootKind kind = js::RootKind::rootKind(); this->stack = &roots.stackRoots_[kind]; this->prev = *stack; *stack = reinterpret_cast*>(this); } public: template explicit Rooted(const RootingContext& cx) : ptr(js::GCMethods::initial()) { registerWithRootLists(js::RootListsForRootingContext(cx)); } template Rooted(const RootingContext& cx, S&& initial) : ptr(mozilla::Forward(initial)) { registerWithRootLists(js::RootListsForRootingContext(cx)); } ~Rooted() { MOZ_ASSERT(*stack == reinterpret_cast*>(this)); *stack = prev; } Rooted* previous() { return reinterpret_cast*>(prev); } /* * This method is public for Rooted so that Codegen.py can use a Rooted * interchangeably with a MutableHandleValue. */ void set(T value) { ptr = value; } DECLARE_POINTER_COMPARISON_OPS(T); DECLARE_POINTER_CONSTREF_OPS(T); DECLARE_POINTER_ASSIGN_OPS(Rooted, T); DECLARE_NONPOINTER_ACCESSOR_METHODS(ptr); DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS(ptr); private: /* * These need to be templated on void* to avoid aliasing issues between, for * example, Rooted and Rooted, which use the same * stack head pointer for different classes. */ Rooted** stack; Rooted* prev; /* * For pointer types, the TraceKind for tracing is based on the list it is * in (selected via rootKind), so no additional storage is required here. * All Traceable, however, share the same list, so the function to * call for tracing is stored adjacent to the struct. Since C++ cannot * templatize on storage class, this is implemented via the wrapper class * DispatchWrapper. */ using MaybeWrapped = typename mozilla::Conditional< mozilla::IsBaseOf::value, js::DispatchWrapper, T>::Type; MaybeWrapped ptr; Rooted(const Rooted&) = delete; }; } /* namespace JS */ namespace js { /** * Augment the generic Rooted interface when T = JSObject* with * class-querying and downcasting operations. * * Given a Rooted obj, one can view * Handle h = obj.as(); * as an optimization of * Rooted rooted(cx, &obj->as()); * Handle h = rooted; */ template <> class RootedBase { public: template JS::Handle as() const; }; /** * Augment the generic Handle interface when T = JSObject* with * downcasting operations. * * Given a Handle obj, one can view * Handle h = obj.as(); * as an optimization of * Rooted rooted(cx, &obj->as()); * Handle h = rooted; */ template <> class HandleBase { public: template JS::Handle as() const; }; /** Interface substitute for Rooted which does not root the variable's memory. */ template class MOZ_RAII FakeRooted : public RootedBase { public: template explicit FakeRooted(CX* cx) : ptr(GCMethods::initial()) {} template FakeRooted(CX* cx, T initial) : ptr(initial) {} DECLARE_POINTER_COMPARISON_OPS(T); DECLARE_POINTER_CONSTREF_OPS(T); DECLARE_POINTER_ASSIGN_OPS(FakeRooted, T); DECLARE_NONPOINTER_ACCESSOR_METHODS(ptr); DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS(ptr); private: T ptr; void set(const T& value) { ptr = value; } FakeRooted(const FakeRooted&) = delete; }; /** Interface substitute for MutableHandle which is not required to point to rooted memory. */ template class FakeMutableHandle : public js::MutableHandleBase { public: MOZ_IMPLICIT FakeMutableHandle(T* t) { ptr = t; } MOZ_IMPLICIT FakeMutableHandle(FakeRooted* root) { ptr = root->address(); } void set(T v) { *ptr = v; } DECLARE_POINTER_CONSTREF_OPS(T); DECLARE_NONPOINTER_ACCESSOR_METHODS(*ptr); DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS(*ptr); private: FakeMutableHandle() {} DELETE_ASSIGNMENT_OPS(FakeMutableHandle, T); T* ptr; }; /** * Types for a variable that either should or shouldn't be rooted, depending on * the template parameter allowGC. Used for implementing functions that can * operate on either rooted or unrooted data. * * The toHandle() and toMutableHandle() functions are for calling functions * which require handle types and are only called in the CanGC case. These * allow the calling code to type check. */ enum AllowGC { NoGC = 0, CanGC = 1 }; template class MaybeRooted { }; template class MaybeRooted { public: typedef JS::Handle HandleType; typedef JS::Rooted RootType; typedef JS::MutableHandle MutableHandleType; static inline JS::Handle toHandle(HandleType v) { return v; } static inline JS::MutableHandle toMutableHandle(MutableHandleType v) { return v; } template static inline JS::Handle downcastHandle(HandleType v) { return v.template as(); } }; template class MaybeRooted { public: typedef T HandleType; typedef FakeRooted RootType; typedef FakeMutableHandle MutableHandleType; static JS::Handle toHandle(HandleType v) { MOZ_CRASH("Bad conversion"); } static JS::MutableHandle toMutableHandle(MutableHandleType v) { MOZ_CRASH("Bad conversion"); } template static inline T2* downcastHandle(HandleType v) { return &v->template as(); } }; } /* namespace js */ namespace JS { template template inline Handle::Handle(const Rooted& root, typename mozilla::EnableIf::value, int>::Type dummy) { ptr = reinterpret_cast(root.address()); } template template inline Handle::Handle(const PersistentRooted& root, typename mozilla::EnableIf::value, int>::Type dummy) { ptr = reinterpret_cast(root.address()); } template template inline Handle::Handle(MutableHandle& root, typename mozilla::EnableIf::value, int>::Type dummy) { ptr = reinterpret_cast(root.address()); } template inline MutableHandle::MutableHandle(Rooted* root) { static_assert(sizeof(MutableHandle) == sizeof(T*), "MutableHandle must be binary compatible with T*."); ptr = root->address(); } template inline MutableHandle::MutableHandle(PersistentRooted* root) { static_assert(sizeof(MutableHandle) == sizeof(T*), "MutableHandle must be binary compatible with T*."); ptr = root->address(); } /** * A copyable, assignable global GC root type with arbitrary lifetime, an * infallible constructor, and automatic unrooting on destruction. * * These roots can be used in heap-allocated data structures, so they are not * associated with any particular JSContext or stack. They are registered with * the JSRuntime itself, without locking, so they require a full JSContext to be * initialized, not one of its more restricted superclasses. Initialization may * take place on construction, or in two phases if the no-argument constructor * is called followed by init(). * * Note that you must not use an PersistentRooted in an object owned by a JS * object: * * Whenever one object whose lifetime is decided by the GC refers to another * such object, that edge must be traced only if the owning JS object is traced. * This applies not only to JS objects (which obviously are managed by the GC) * but also to C++ objects owned by JS objects. * * If you put a PersistentRooted in such a C++ object, that is almost certainly * a leak. When a GC begins, the referent of the PersistentRooted is treated as * live, unconditionally (because a PersistentRooted is a *root*), even if the * JS object that owns it is unreachable. If there is any path from that * referent back to the JS object, then the C++ object containing the * PersistentRooted will not be destructed, and the whole blob of objects will * not be freed, even if there are no references to them from the outside. * * In the context of Firefox, this is a severe restriction: almost everything in * Firefox is owned by some JS object or another, so using PersistentRooted in * such objects would introduce leaks. For these kinds of edges, Heap or * TenuredHeap would be better types. It's up to the implementor of the type * containing Heap or TenuredHeap members to make sure their referents get * marked when the object itself is marked. */ template class PersistentRooted : public js::PersistentRootedBase, private mozilla::LinkedListElement> { typedef mozilla::LinkedListElement> ListBase; friend class mozilla::LinkedList; friend class mozilla::LinkedListElement; friend struct js::gc::PersistentRootedMarker; friend void js::gc::FinishPersistentRootedChains(js::RootLists&); void registerWithRootLists(js::RootLists& roots) { MOZ_ASSERT(!initialized()); js::ThingRootKind kind = js::RootKind::rootKind(); roots.heapRoots_[kind].insertBack(reinterpret_cast*>(this)); // Until marking and destruction support the full set, we assert that // we don't try to add any unsupported types. MOZ_ASSERT(kind == js::THING_ROOT_OBJECT || kind == js::THING_ROOT_SCRIPT || kind == js::THING_ROOT_STRING || + kind == js::THING_ROOT_SYMBOL || kind == js::THING_ROOT_ID || kind == js::THING_ROOT_VALUE || kind == js::THING_ROOT_TRACEABLE); } public: PersistentRooted() : ptr(js::GCMethods::initial()) {} template explicit PersistentRooted(const RootingContext& cx) : ptr(js::GCMethods::initial()) { registerWithRootLists(js::RootListsForRootingContext(cx)); } template PersistentRooted(const RootingContext& cx, U&& initial) : ptr(mozilla::Forward(initial)) { registerWithRootLists(js::RootListsForRootingContext(cx)); } PersistentRooted(const PersistentRooted& rhs) : mozilla::LinkedListElement>(), ptr(rhs.ptr) { /* * Copy construction takes advantage of the fact that the original * is already inserted, and simply adds itself to whatever list the * original was on - no JSRuntime pointer needed. * * This requires mutating rhs's links, but those should be 'mutable' * anyway. C++ doesn't let us declare mutable base classes. */ const_cast(rhs).setNext(this); } bool initialized() { return ListBase::isInList(); } template void init(const RootingContext& cx) { init(cx, js::GCMethods::initial()); } template void init(const RootingContext& cx, U&& initial) { ptr = mozilla::Forward(initial); registerWithRootLists(js::RootListsForRootingContext(cx)); } void reset() { if (initialized()) { set(js::GCMethods::initial()); ListBase::remove(); } } DECLARE_POINTER_COMPARISON_OPS(T); DECLARE_POINTER_CONSTREF_OPS(T); DECLARE_POINTER_ASSIGN_OPS(PersistentRooted, T); DECLARE_NONPOINTER_ACCESSOR_METHODS(ptr); // These are the same as DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS, except // they check that |this| is initialized in case the caller later stores // something in |ptr|. T* address() { MOZ_ASSERT(initialized()); return &ptr; } T& get() { MOZ_ASSERT(initialized()); return ptr; } private: void set(T value) { MOZ_ASSERT(initialized()); ptr = value; } // See the comment above Rooted::ptr. using MaybeWrapped = typename mozilla::Conditional< mozilla::IsBaseOf::value, js::DispatchWrapper, T>::Type; MaybeWrapped ptr; }; class JS_PUBLIC_API(ObjectPtr) { Heap value; public: ObjectPtr() : value(nullptr) {} explicit ObjectPtr(JSObject* obj) : value(obj) {} /* Always call finalize before the destructor. */ ~ObjectPtr() { MOZ_ASSERT(!value); } void finalize(JSRuntime* rt) { if (IsIncrementalBarrierNeeded(rt)) IncrementalObjectBarrier(value); value = nullptr; } void init(JSObject* obj) { value = obj; } JSObject* get() const { return value; } void writeBarrierPre(JSRuntime* rt) { IncrementalObjectBarrier(value); } void updateWeakPointerAfterGC(); ObjectPtr& operator=(JSObject* obj) { IncrementalObjectBarrier(value); value = obj; return *this; } void trace(JSTracer* trc, const char* name); JSObject& operator*() const { return *value; } JSObject* operator->() const { return value; } operator JSObject*() const { return value; } }; } /* namespace JS */ namespace js { namespace gc { template void CallTraceCallbackOnNonHeap(T* v, const TraceCallbacks& aCallbacks, const char* aName, void* aClosure) { static_assert(sizeof(T) == sizeof(JS::Heap), "T and Heap must be compatible."); MOZ_ASSERT(v); mozilla::DebugOnly cell = GCMethods::asGCThingOrNull(*v); MOZ_ASSERT(cell); MOZ_ASSERT(!IsInsideNursery(cell)); JS::Heap* asHeapT = reinterpret_cast*>(v); aCallbacks.Trace(asHeapT, aName, aClosure); } } /* namespace gc */ } /* namespace js */ // mozilla::Swap uses a stack temporary, which prevents classes like Heap // from being declared MOZ_HEAP_CLASS. namespace mozilla { template inline void Swap(JS::Heap& aX, JS::Heap& aY) { T tmp = aX; aX = aY; aY = tmp; } template inline void Swap(JS::TenuredHeap& aX, JS::TenuredHeap& aY) { T tmp = aX; aX = aY; aY = tmp; } } /* namespace mozilla */ #undef DELETE_ASSIGNMENT_OPS #endif /* js_RootingAPI_h */ Index: ps/trunk/libraries/source/spidermonkey/patch.sh =================================================================== --- ps/trunk/libraries/source/spidermonkey/patch.sh (revision 24166) +++ ps/trunk/libraries/source/spidermonkey/patch.sh (revision 24167) @@ -1,56 +1,60 @@ #!/bin/sh # Apply patches if needed # This script gets called from build-osx-libs.sh and build.sh. # 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 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=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=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 # 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 (for arm64) # * https://bugzilla.mozilla.org/show_bug.cgi?id=1277742 (for aarch64) # * https://bugzilla.mozilla.org/show_bug.cgi?id=1266366 (for ppc64) 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 + +# Allow the use of JS::PersistentRootedSymbol in debug builds. +# This check will be removed in SM52. +patch -p1 < ../FixPersistentRootedSymbol.diff Index: ps/trunk/source/scriptinterface/ScriptRuntime.cpp =================================================================== --- ps/trunk/source/scriptinterface/ScriptRuntime.cpp (revision 24166) +++ ps/trunk/source/scriptinterface/ScriptRuntime.cpp (revision 24167) @@ -1,269 +1,250 @@ -/* Copyright (C) 2016 Wildfire Games. +/* Copyright (C) 2020 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 "ScriptRuntime.h" #include "ps/GameSetup/Config.h" #include "ps/Profile.h" #include "scriptinterface/ScriptEngine.h" void GCSliceCallbackHook(JSRuntime* UNUSED(rt), JS::GCProgress progress, const JS::GCDescription& UNUSED(desc)) { /* * During non-incremental GC, the GC is bracketed by JSGC_CYCLE_BEGIN/END * callbacks. During an incremental GC, the sequence of callbacks is as * follows: * JSGC_CYCLE_BEGIN, JSGC_SLICE_END (first slice) * JSGC_SLICE_BEGIN, JSGC_SLICE_END (second slice) * ... * JSGC_SLICE_BEGIN, JSGC_CYCLE_END (last slice) */ if (progress == JS::GC_SLICE_BEGIN) { if (CProfileManager::IsInitialised() && ThreadUtil::IsMainThread()) g_Profiler.Start("GCSlice"); g_Profiler2.RecordRegionEnter("GCSlice"); } else if (progress == JS::GC_SLICE_END) { if (CProfileManager::IsInitialised() && ThreadUtil::IsMainThread()) g_Profiler.Stop(); g_Profiler2.RecordRegionLeave(); } else if (progress == JS::GC_CYCLE_BEGIN) { if (CProfileManager::IsInitialised() && ThreadUtil::IsMainThread()) g_Profiler.Start("GCSlice"); g_Profiler2.RecordRegionEnter("GCSlice"); } else if (progress == JS::GC_CYCLE_END) { if (CProfileManager::IsInitialised() && ThreadUtil::IsMainThread()) g_Profiler.Stop(); g_Profiler2.RecordRegionLeave(); } // The following code can be used to print some information aobut garbage collection // Search for "Nonincremental reason" if there are problems running GC incrementally. #if 0 if (progress == JS::GCProgress::GC_CYCLE_BEGIN) printf("starting cycle ===========================================\n"); const char16_t* str = desc.formatMessage(rt); int len = 0; for(int i = 0; i < 10000; i++) { len++; if(!str[i]) break; } wchar_t outstring[len]; for(int i = 0; i < len; i++) { outstring[i] = (wchar_t)str[i]; } printf("---------------------------------------\n: %ls \n---------------------------------------\n", outstring); #endif } -void ScriptRuntime::GCCallback(JSRuntime* UNUSED(rt), JSGCStatus status, void *data) -{ - if (status == JSGC_END) - reinterpret_cast(data)->GCCallbackMember(); -} - -void ScriptRuntime::GCCallbackMember() -{ - m_FinalizationListObjectIdCache.clear(); -} - -void ScriptRuntime::AddDeferredFinalizationObject(const std::shared_ptr& obj) -{ - m_FinalizationListObjectIdCache.push_back(obj); -} - ScriptRuntime::ScriptRuntime(shared_ptr parentRuntime, int runtimeSize, int heapGrowthBytesGCTrigger): m_LastGCBytes(0), m_LastGCCheck(0.0f), m_HeapGrowthBytesGCTrigger(heapGrowthBytesGCTrigger), m_RuntimeSize(runtimeSize) { ENSURE(ScriptEngine::IsInitialised() && "The ScriptEngine must be initialized before constructing any ScriptRuntimes!"); JSRuntime* parentJSRuntime = parentRuntime ? parentRuntime->m_rt : nullptr; m_rt = JS_NewRuntime(runtimeSize, JS::DefaultNurseryBytes, parentJSRuntime); ENSURE(m_rt); // TODO: error handling JS::SetGCSliceCallback(m_rt, GCSliceCallbackHook); - JS_SetGCCallback(m_rt, ScriptRuntime::GCCallback, this); JS_SetGCParameter(m_rt, JSGC_MAX_MALLOC_BYTES, m_RuntimeSize); JS_SetGCParameter(m_rt, JSGC_MAX_BYTES, m_RuntimeSize); JS_SetGCParameter(m_rt, JSGC_MODE, JSGC_MODE_INCREMENTAL); // The whole heap-growth mechanism seems to work only for non-incremental GCs. // We disable it to make it more clear if full GCs happen triggered by this JSAPI internal mechanism. JS_SetGCParameter(m_rt, JSGC_DYNAMIC_HEAP_GROWTH, false); ScriptEngine::GetSingleton().RegisterRuntime(m_rt); } ScriptRuntime::~ScriptRuntime() { - JS_SetGCCallback(m_rt, nullptr, nullptr); JS_DestroyRuntime(m_rt); - ENSURE(m_FinalizationListObjectIdCache.empty() && "Leak: Removing callback while some objects still aren't finalized!"); ENSURE(ScriptEngine::IsInitialised() && "The ScriptEngine must be active (initialized and not yet shut down) when destroying a ScriptRuntime!"); ScriptEngine::GetSingleton().UnRegisterRuntime(m_rt); } void ScriptRuntime::RegisterContext(JSContext* cx) { m_Contexts.push_back(cx); } void ScriptRuntime::UnRegisterContext(JSContext* cx) { m_Contexts.remove(cx); } #define GC_DEBUG_PRINT 0 void ScriptRuntime::MaybeIncrementalGC(double delay) { PROFILE2("MaybeIncrementalGC"); if (JS::IsIncrementalGCEnabled(m_rt)) { // The idea is to get the heap size after a completed GC and trigger the next GC when the heap size has // reached m_LastGCBytes + X. // In practice it doesn't quite work like that. When the incremental marking is completed, the sweeping kicks in. // The sweeping actually frees memory and it does this in a background thread (if JS_USE_HELPER_THREADS is set). // While the sweeping is happening we already run scripts again and produce new garbage. const int GCSliceTimeBudget = 30; // Milliseconds an incremental slice is allowed to run // Have a minimum time in seconds to wait between GC slices and before starting a new GC to distribute the GC // load and to hopefully make it unnoticeable for the player. This value should be high enough to distribute // the load well enough and low enough to make sure we don't run out of memory before we can start with the // sweeping. if (timer_Time() - m_LastGCCheck < delay) return; m_LastGCCheck = timer_Time(); int gcBytes = JS_GetGCParameter(m_rt, JSGC_BYTES); #if GC_DEBUG_PRINT std::cout << "gcBytes: " << gcBytes / 1024 << " KB" << std::endl; #endif if (m_LastGCBytes > gcBytes || m_LastGCBytes == 0) { #if GC_DEBUG_PRINT printf("Setting m_LastGCBytes: %d KB \n", gcBytes / 1024); #endif m_LastGCBytes = gcBytes; } // Run an additional incremental GC slice if the currently running incremental GC isn't over yet // ... or // start a new incremental GC if the JS heap size has grown enough for a GC to make sense if (JS::IsIncrementalGCInProgress(m_rt) || (gcBytes - m_LastGCBytes > m_HeapGrowthBytesGCTrigger)) { #if GC_DEBUG_PRINT if (JS::IsIncrementalGCInProgress(m_rt)) printf("An incremental GC cycle is in progress. \n"); else printf("GC needed because JSGC_BYTES - m_LastGCBytes > m_HeapGrowthBytesGCTrigger \n" " JSGC_BYTES: %d KB \n m_LastGCBytes: %d KB \n m_HeapGrowthBytesGCTrigger: %d KB \n", gcBytes / 1024, m_LastGCBytes / 1024, m_HeapGrowthBytesGCTrigger / 1024); #endif // A hack to make sure we never exceed the runtime size because we can't collect the memory // fast enough. if (gcBytes > m_RuntimeSize / 2) { if (JS::IsIncrementalGCInProgress(m_rt)) { #if GC_DEBUG_PRINT printf("Finishing incremental GC because gcBytes > m_RuntimeSize / 2. \n"); #endif PrepareContextsForIncrementalGC(); JS::FinishIncrementalGC(m_rt, JS::gcreason::REFRESH_FRAME); } else { if (gcBytes > m_RuntimeSize * 0.75) { ShrinkingGC(); #if GC_DEBUG_PRINT printf("Running shrinking GC because gcBytes > m_RuntimeSize * 0.75. \n"); #endif } else { #if GC_DEBUG_PRINT printf("Running full GC because gcBytes > m_RuntimeSize / 2. \n"); #endif JS_GC(m_rt); } } } else { #if GC_DEBUG_PRINT if (!JS::IsIncrementalGCInProgress(m_rt)) printf("Starting incremental GC \n"); else printf("Running incremental GC slice \n"); #endif PrepareContextsForIncrementalGC(); if (!JS::IsIncrementalGCInProgress(m_rt)) JS::StartIncrementalGC(m_rt, GC_NORMAL, JS::gcreason::REFRESH_FRAME, GCSliceTimeBudget); else JS::IncrementalGCSlice(m_rt, JS::gcreason::REFRESH_FRAME, GCSliceTimeBudget); } m_LastGCBytes = gcBytes; } } } void ScriptRuntime::ShrinkingGC() { JS_SetGCParameter(m_rt, JSGC_MODE, JSGC_MODE_COMPARTMENT); JS::PrepareForFullGC(m_rt); JS::GCForReason(m_rt, GC_SHRINK, JS::gcreason::REFRESH_FRAME); JS_SetGCParameter(m_rt, JSGC_MODE, JSGC_MODE_INCREMENTAL); } void ScriptRuntime::PrepareContextsForIncrementalGC() { for (JSContext* const& ctx : m_Contexts) JS::PrepareZoneForGC(js::GetCompartmentZone(js::GetContextCompartment(ctx))); } Index: ps/trunk/source/scriptinterface/ScriptRuntime.h =================================================================== --- ps/trunk/source/scriptinterface/ScriptRuntime.h (revision 24166) +++ ps/trunk/source/scriptinterface/ScriptRuntime.h (revision 24167) @@ -1,88 +1,74 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2020 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_SCRIPTRUNTIME #define INCLUDED_SCRIPTRUNTIME #include "ScriptTypes.h" #include "ScriptExtraHeaders.h" #include #define STACK_CHUNK_SIZE 8192 /** * Abstraction around a SpiderMonkey JSRuntime. * Each ScriptRuntime can be used to initialize several ScriptInterface * contexts which can then share data, but a single ScriptRuntime should * only be used on a single thread. * * (One means to share data between threads and runtimes is to create * a ScriptInterface::StructuredClone.) */ class ScriptRuntime { public: ScriptRuntime(shared_ptr parentRuntime, int runtimeSize, int heapGrowthBytesGCTrigger); ~ScriptRuntime(); /** * MaybeIncrementalRuntimeGC tries to determine whether a runtime-wide garbage collection would free up enough memory to * be worth the amount of time it would take. It does this with our own logic and NOT some predefined JSAPI logic because * such functionality currently isn't available out of the box. * It does incremental GC which means it will collect one slice each time it's called until the garbage collection is done. * This can and should be called quite regularly. The delay parameter allows you to specify a minimum time since the last GC * in seconds (the delay should be a fraction of a second in most cases though). * It will only start a new incremental GC or another GC slice if this time is exceeded. The user of this function is * responsible for ensuring that GC can run with a small enough delay to get done with the work. */ void MaybeIncrementalGC(double delay); void ShrinkingGC(); void RegisterContext(JSContext* cx); void UnRegisterContext(JSContext* cx); - /** - * Registers an object to be freed/finalized by the ScriptRuntime. Freeing is - * guaranteed to happen after the next minor GC has completed, but might also - * happen a bit later. This is only needed in very special situations - * and you should only use it if you know exactly why you need it! - * Specify a deleter for the shared_ptr to free the void pointer correctly - * (by casting to the right type before calling delete for example). - */ - void AddDeferredFinalizationObject(const std::shared_ptr& obj); - JSRuntime* m_rt; private: void PrepareContextsForIncrementalGC(); - void GCCallbackMember(); std::list m_Contexts; - std::vector > m_FinalizationListObjectIdCache; int m_RuntimeSize; int m_HeapGrowthBytesGCTrigger; int m_LastGCBytes; double m_LastGCCheck; - - static void GCCallback(JSRuntime *rt, JSGCStatus status, void *data); }; #endif // INCLUDED_SCRIPTRUNTIME Index: ps/trunk/source/simulation2/components/CCmpAIManager.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpAIManager.cpp (revision 24166) +++ ps/trunk/source/simulation2/components/CCmpAIManager.cpp (revision 24167) @@ -1,1173 +1,1174 @@ /* Copyright (C) 2020 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 "scriptinterface/ScriptRuntime.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); ScriptInterface::CreateObject( cx, &settings, "player", m_Player, "difficulty", m_Difficulty, "behavior", m_Behavior); 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_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)); 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 playersID(cx); ScriptInterface::CreateObject(cx, &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); } ENSURE(m_HasLoadedEntityTemplates); JS::RootedValue settings(cx); ScriptInterface::CreateObject( cx, &settings, "players", playersID, "templates", m_EntityTemplates); 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, &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, &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; ScriptInterface::CreateObject(cx, &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); 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)); } 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(); } private: static void Trace(JSTracer *trc, void *data) { reinterpret_cast(data)->TraceMember(trc); } void TraceMember(JSTracer *trc) { 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; 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(cx, &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/components/tests/test_CommandQueue.h =================================================================== --- ps/trunk/source/simulation2/components/tests/test_CommandQueue.h (revision 24166) +++ ps/trunk/source/simulation2/components/tests/test_CommandQueue.h (revision 24167) @@ -1,75 +1,75 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2020 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 "simulation2/system/ComponentTest.h" #include "simulation2/components/ICmpCommandQueue.h" class TestCmpCommandQueue : public CxxTest::TestSuite { public: void setUp() { CXeromyces::Startup(); } void tearDown() { CXeromyces::Terminate(); } void test_basic() { ComponentTestHelper test(g_ScriptRuntime); JSContext* cx = test.GetScriptInterface().GetContext(); JSAutoRequest rq(cx); std::vector empty; ICmpCommandQueue* cmp = test.Add(CID_CommandQueue, "", SYSTEM_ENTITY); TS_ASSERT(test.GetScriptInterface().Eval("var cmds = []; function ProcessCommand(player, cmd) { cmds.push([player, cmd]); }")); JS::RootedValue cmd(cx); TS_ASSERT(test.GetScriptInterface().Eval("([1,2,3])", &cmd)); cmp->PushLocalCommand(1, cmd); - TS_ASSERT(test.GetScriptInterface().Eval("({x:4})", &cmd)); + TS_ASSERT(test.GetScriptInterface().Eval("({\"x\":4})", &cmd)); cmp->PushLocalCommand(-1, cmd); test.Roundtrip(); // Process the first two commands cmp->FlushTurn(empty); - TS_ASSERT(test.GetScriptInterface().Eval("({y:5})", &cmd)); + TS_ASSERT(test.GetScriptInterface().Eval("({\"y\":5})", &cmd)); cmp->PushLocalCommand(10, cmd); // Process the next command cmp->FlushTurn(empty); // Process no commands cmp->FlushTurn(empty); test.Roundtrip(); std::string output; - TS_ASSERT(test.GetScriptInterface().Eval("uneval(cmds)", output)); - TS_ASSERT_STR_EQUALS(output, "[[1, [1, 2, 3]], [-1, {x:4}], [10, {y:5}]]"); + TS_ASSERT(test.GetScriptInterface().Eval("JSON.stringify(cmds)", output)); + TS_ASSERT_STR_EQUALS(output, "[[1,[1,2,3]],[-1,{\"x\":4}],[10,{\"y\":5}]]"); } }; Index: ps/trunk/source/simulation2/serialization/BinarySerializer.cpp =================================================================== --- ps/trunk/source/simulation2/serialization/BinarySerializer.cpp (revision 24166) +++ ps/trunk/source/simulation2/serialization/BinarySerializer.cpp (revision 24167) @@ -1,432 +1,446 @@ /* Copyright (C) 2020 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_ScriptBackrefsNext(1) + m_ScriptInterface(scriptInterface), m_Serializer(serializer), m_ScriptBackrefsNext(0) { - m_ScriptBackrefs.init(); + JSContext* cx = m_ScriptInterface.GetContext(); + JSAutoRequest rq(cx); + + m_ScriptBackrefSymbol.init(cx, JS::NewSymbol(cx, nullptr)); } 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) + i32 tag = GetScriptBackrefTag(obj); + if (tag != -1) { m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_BACKREF); - m_Serializer.NumberU32_Unbounded("tag", tag); + m_Serializer.NumberI32("tag", tag, 0, JSVAL_INT_MAX); break; } // Arrays are special cases of Object 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, &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; 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"); JSProtoKey protokey = JSCLASS_CACHED_PROTO_KEY(jsclass); if (protokey == JSProto_Object) { // Standard Object prototype m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT); } 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::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) +i32 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. + // new object, we give it a new 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; + // Tags are stored on the object. To avoid overwriting any existing property, + // they are saved as a uniquely-named, non-enumerable property (the serializer's unique symbol). JSContext* cx = m_ScriptInterface.GetContext(); JSAutoRequest rq(cx); - m_ScriptBackrefs.add(cx, obj, m_ScriptBackrefsNext); + JS::RootedValue symbolValue(cx, JS::SymbolValue(m_ScriptBackrefSymbol)); + JS::RootedId symbolId(cx); + ENSURE(JS_ValueToId(cx, symbolValue, &symbolId)); + + JS::RootedValue tagValue(cx); + + // If it was already there, return the tag + bool tagFound; + ENSURE(JS_HasPropertyById(cx, obj, symbolId, &tagFound)); + if (tagFound) + { + ENSURE(JS_GetPropertyById(cx, obj, symbolId, &tagValue)); + ENSURE(tagValue.isInt32()); + return tagValue.toInt32(); + } + + tagValue = JS::Int32Value(m_ScriptBackrefsNext); + JS_SetPropertyById(cx, obj, symbolId, tagValue); - m_ScriptBackrefsNext++; + ++m_ScriptBackrefsNext; // Return a non-tag number so callers know they need to serialize the object - return 0; + return -1; } Index: ps/trunk/source/simulation2/serialization/BinarySerializer.h =================================================================== --- ps/trunk/source/simulation2/serialization/BinarySerializer.h (revision 24166) +++ ps/trunk/source/simulation2/serialization/BinarySerializer.h (revision 24167) @@ -1,223 +1,221 @@ /* Copyright (C) 2020 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_BINARYSERIALIZER #define INCLUDED_BINARYSERIALIZER #include "ISerializer.h" -#include "scriptinterface/third_party/ObjectToIDMap.h" - #include "lib/byte_order.h" #include "lib/allocators/arena.h" #include /** * Wrapper for redirecting ostream writes to CBinarySerializer's impl */ template class CSerializerStreamBuf : public std::streambuf { NONCOPYABLE(CSerializerStreamBuf); T& m_SerializerImpl; char m_Buffer[2048]; public: CSerializerStreamBuf(T& impl) : m_SerializerImpl(impl) { setp(m_Buffer, m_Buffer + ARRAY_SIZE(m_Buffer) - 1); } protected: // Override overflow and sync, because older versions of libc++ streams // write strings as individual characters, then xsputn is never called int overflow(int ch) { if (ch == traits_type::eof()) return traits_type::not_eof(ch); ENSURE(pptr() <= epptr()); *pptr() = ch; pbump(1); sync(); return ch; } int sync() { std::ptrdiff_t n = pptr() - pbase(); if (n != 0) { pbump(-n); m_SerializerImpl.Put("stream", reinterpret_cast (pbase()), n); } return 0; } std::streamsize xsputn(const char* s, std::streamsize n) { m_SerializerImpl.Put("stream", reinterpret_cast (s), n); return n; } }; /** * PutScriptVal implementation details. * (Split out from the main class because it's too big to be inlined.) */ class CBinarySerializerScriptImpl { public: CBinarySerializerScriptImpl(const ScriptInterface& scriptInterface, ISerializer& serializer); void ScriptString(const char* name, JS::HandleString string); void HandleScriptVal(JS::HandleValue val); private: const ScriptInterface& m_ScriptInterface; ISerializer& m_Serializer; - ObjectIdCache m_ScriptBackrefs; - u32 m_ScriptBackrefsNext; - u32 GetScriptBackrefTag(JS::HandleObject obj); + JS::PersistentRootedSymbol m_ScriptBackrefSymbol; + i32 m_ScriptBackrefsNext; + i32 GetScriptBackrefTag(JS::HandleObject obj); }; /** * Serialize to a binary stream. T must just implement the Put() method. * (We use this templated approach to allow compiler inlining.) */ template class CBinarySerializer : public ISerializer { NONCOPYABLE(CBinarySerializer); public: CBinarySerializer(const ScriptInterface& scriptInterface) : m_ScriptImpl(new CBinarySerializerScriptImpl(scriptInterface, *this)), m_RawStreamBuf(m_Impl), m_RawStream(&m_RawStreamBuf) { } template CBinarySerializer(const ScriptInterface& scriptInterface, A& a) : m_ScriptImpl(new CBinarySerializerScriptImpl(scriptInterface, *this)), m_Impl(a), m_RawStreamBuf(m_Impl), m_RawStream(&m_RawStreamBuf) { } protected: /* The Put* implementations here are designed for subclasses that want an efficient, portable, deserializable representation. (Subclasses with different requirements should override these methods.) Numbers are converted to little-endian byte strings, for portability and efficiency. Data is not aligned, for storage efficiency. */ virtual void PutNumber(const char* name, uint8_t value) { m_Impl.Put(name, (const u8*)&value, sizeof(uint8_t)); } virtual void PutNumber(const char* name, int8_t value) { m_Impl.Put(name, (const u8*)&value, sizeof(int8_t)); } virtual void PutNumber(const char* name, uint16_t value) { uint16_t v = to_le16(value); m_Impl.Put(name, (const u8*)&v, sizeof(uint16_t)); } virtual void PutNumber(const char* name, int16_t value) { int16_t v = (i16)to_le16((u16)value); m_Impl.Put(name, (const u8*)&v, sizeof(int16_t)); } virtual void PutNumber(const char* name, uint32_t value) { uint32_t v = to_le32(value); m_Impl.Put(name, (const u8*)&v, sizeof(uint32_t)); } virtual void PutNumber(const char* name, int32_t value) { int32_t v = (i32)to_le32((u32)value); m_Impl.Put(name, (const u8*)&v, sizeof(int32_t)); } virtual void PutNumber(const char* name, float value) { m_Impl.Put(name, (const u8*)&value, sizeof(float)); } virtual void PutNumber(const char* name, double value) { m_Impl.Put(name, (const u8*)&value, sizeof(double)); } virtual void PutNumber(const char* name, fixed value) { int32_t v = (i32)to_le32((u32)value.GetInternalValue()); m_Impl.Put(name, (const u8*)&v, sizeof(int32_t)); } virtual void PutBool(const char* name, bool value) { NumberU8(name, value ? 1 : 0, 0, 1); } virtual void PutString(const char* name, const std::string& value) { // TODO: maybe should intern strings, particularly to save space with script property names PutNumber("string length", (uint32_t)value.length()); m_Impl.Put(name, (u8*)value.data(), value.length()); } virtual void PutScriptVal(const char* UNUSED(name), JS::MutableHandleValue value) { m_ScriptImpl->HandleScriptVal(value); } virtual void PutRaw(const char* name, const u8* data, size_t len) { m_Impl.Put(name, data, len); } virtual std::ostream& GetStream() { return m_RawStream; } protected: T m_Impl; private: std::unique_ptr m_ScriptImpl; CSerializerStreamBuf m_RawStreamBuf; std::ostream m_RawStream; }; #endif // INCLUDED_BINARYSERIALIZER Index: ps/trunk/source/simulation2/serialization/StdDeserializer.cpp =================================================================== --- ps/trunk/source/simulation2/serialization/StdDeserializer.cpp (revision 24166) +++ ps/trunk/source/simulation2/serialization/StdDeserializer.cpp (revision 24167) @@ -1,458 +1,449 @@ /* Copyright (C) 2020 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()) + m_ScriptInterface(scriptInterface), m_Stream(stream) { - 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') 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) +void CStdDeserializer::GetScriptBackref(size_t 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: { 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 // SCRIPT_TYPE_OBJECT { obj.set(JS_NewPlainObject(cx)); } 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", 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", 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); + i32 tag; + NumberI32("tag", tag, 0, JSVAL_INT_MAX); 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", 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, 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); } Index: ps/trunk/source/simulation2/serialization/StdDeserializer.h =================================================================== --- ps/trunk/source/simulation2/serialization/StdDeserializer.h (revision 24166) +++ ps/trunk/source/simulation2/serialization/StdDeserializer.h (revision 24167) @@ -1,63 +1,62 @@ /* Copyright (C) 2020 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_STDDESERIALIZER #define INCLUDED_STDDESERIALIZER #include "IDeserializer.h" #include "ps/utf16string.h" #include class CStdDeserializer : public IDeserializer { NONCOPYABLE(CStdDeserializer); public: CStdDeserializer(const ScriptInterface& scriptInterface, std::istream& stream); virtual ~CStdDeserializer(); virtual void ScriptVal(const char* name, JS::MutableHandleValue out); virtual void ScriptObjectAppend(const char* name, JS::HandleValue objVal); virtual void ScriptString(const char* name, JS::MutableHandleString out); virtual std::istream& GetStream(); virtual void RequireBytesInStream(size_t numBytes); static void Trace(JSTracer *trc, void *data); void TraceMember(JSTracer *trc); protected: virtual void Get(const char* name, u8* data, size_t len); private: JS::Value ReadScriptVal(const char* name, JS::HandleObject appendParent); void ReadStringLatin1(const char* name, std::vector& str); void ReadStringUTF16(const char* name, utf16string& str); virtual void AddScriptBackref(JS::HandleObject obj); - virtual void GetScriptBackref(u32 tag, JS::MutableHandleObject ret); + virtual void GetScriptBackref(size_t tag, JS::MutableHandleObject ret); std::vector > m_ScriptBackrefs; - JS::PersistentRooted m_dummyObject; const ScriptInterface& m_ScriptInterface; std::istream& m_Stream; }; #endif // INCLUDED_STDDESERIALIZER Index: ps/trunk/source/simulation2/tests/test_Serializer.h =================================================================== --- ps/trunk/source/simulation2/tests/test_Serializer.h (revision 24166) +++ ps/trunk/source/simulation2/tests/test_Serializer.h (revision 24167) @@ -1,886 +1,886 @@ /* 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 "lib/self_test.h" #include "simulation2/serialization/DebugSerializer.h" #include "simulation2/serialization/HashSerializer.h" #include "simulation2/serialization/StdSerializer.h" #include "simulation2/serialization/StdDeserializer.h" #include "scriptinterface/ScriptInterface.h" #include "graphics/MapReader.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureManager.h" #include "lib/timer.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/Loader.h" #include "ps/XML/Xeromyces.h" #include "simulation2/Simulation2.h" #include "callgrind.h" #include #define TS_ASSERT_STREAM(stream, len, buffer) \ TS_ASSERT_EQUALS(stream.str().length(), (size_t)len); \ TS_ASSERT_SAME_DATA(stream.str().data(), buffer, len) #define TSM_ASSERT_STREAM(m, stream, len, buffer) \ TSM_ASSERT_EQUALS(m, stream.str().length(), (size_t)len); \ TSM_ASSERT_SAME_DATA(m, stream.str().data(), buffer, len) class TestSerializer : public CxxTest::TestSuite { public: void serialize_types(ISerializer& serialize) { serialize.NumberI8_Unbounded("i8", (signed char)-123); serialize.NumberU8_Unbounded("u8", (unsigned char)255); serialize.NumberI16_Unbounded("i16", -12345); serialize.NumberU16_Unbounded("u16", 56789); serialize.NumberI32_Unbounded("i32", -123); serialize.NumberU32_Unbounded("u32", (unsigned)-123); serialize.NumberFloat_Unbounded("float", 1e+30f); serialize.NumberDouble_Unbounded("double", 1e+300); serialize.NumberFixed_Unbounded("fixed", fixed::FromFloat(1234.5f)); serialize.Bool("t", true); serialize.Bool("f", false); serialize.StringASCII("string", "example", 0, 255); serialize.StringASCII("string 2", "example\"\\\"", 0, 255); serialize.StringASCII("string 3", "example\n\ntest", 0, 255); wchar_t testw[] = { 't', 0xEA, 's', 't', 0 }; serialize.String("string 4", testw, 0, 255); serialize.RawBytes("raw bytes", (const u8*)"\0\1\2\3\x0f\x10", 6); } void test_Debug_basic() { ScriptInterface script("Test", "Test", g_ScriptRuntime); std::stringstream stream; CDebugSerializer serialize(script, stream); serialize.NumberI32_Unbounded("x", -123); serialize.NumberU32_Unbounded("y", 1234); serialize.NumberI32("z", 12345, 0, 65535); TS_ASSERT_STR_EQUALS(stream.str(), "x: -123\ny: 1234\nz: 12345\n"); } void test_Debug_floats() { ScriptInterface script("Test", "Test", g_ScriptRuntime); std::stringstream stream; CDebugSerializer serialize(script, stream); serialize.NumberFloat_Unbounded("x", 1e4f); serialize.NumberFloat_Unbounded("x", 1e-4f); serialize.NumberFloat_Unbounded("x", 1e5f); serialize.NumberFloat_Unbounded("x", 1e-5f); serialize.NumberFloat_Unbounded("x", 1e6f); serialize.NumberFloat_Unbounded("x", 1e-6f); serialize.NumberFloat_Unbounded("x", 1e10f); serialize.NumberFloat_Unbounded("x", 1e-10f); serialize.NumberDouble_Unbounded("x", 1e4); serialize.NumberDouble_Unbounded("x", 1e-4); serialize.NumberDouble_Unbounded("x", 1e5); serialize.NumberDouble_Unbounded("x", 1e-5); serialize.NumberDouble_Unbounded("x", 1e6); serialize.NumberDouble_Unbounded("x", 1e-6); serialize.NumberDouble_Unbounded("x", 1e10); serialize.NumberDouble_Unbounded("x", 1e-10); serialize.NumberDouble_Unbounded("x", 1e100); serialize.NumberDouble_Unbounded("x", 1e-100); serialize.NumberFixed_Unbounded("x", fixed::FromDouble(1e4)); TS_ASSERT_STR_EQUALS(stream.str(), "x: 10000\nx: 9.9999997e-05\nx: 100000\nx: 9.9999997e-06\nx: 1000000\nx: 1e-06\nx: 1e+10\nx: 1e-10\n" "x: 10000\nx: 0.0001\nx: 100000\nx: 1.0000000000000001e-05\nx: 1000000\nx: 9.9999999999999995e-07\nx: 10000000000\nx: 1e-10\nx: 1e+100\nx: 1e-100\n" "x: 10000\n" ); } void test_Debug_types() { ScriptInterface script("Test", "Test", g_ScriptRuntime); std::stringstream stream; CDebugSerializer serialize(script, stream); serialize.Comment("comment"); serialize_types(serialize); TS_ASSERT_STR_EQUALS(stream.str(), "# comment\n" "i8: -123\n" "u8: 255\n" "i16: -12345\n" "u16: 56789\n" "i32: -123\n" "u32: 4294967173\n" "float: 1e+30\n" "double: 1.0000000000000001e+300\n" "fixed: 1234.5\n" "t: true\n" "f: false\n" "string: \"example\"\n" "string 2: \"example\\\"\\\\\\\"\"\n" // C-escaped form of: "example\"\\\"" "string 3: \"example\\n\\ntest\"\n" "string 4: \"t\xC3\xAAst\"\n" "raw bytes: (6 bytes) 00 01 02 03 0f 10\n" ); } void test_Std_basic() { ScriptInterface script("Test", "Test", g_ScriptRuntime); std::stringstream stream; CStdSerializer serialize(script, stream); serialize.NumberI32_Unbounded("x", -123); serialize.NumberU32_Unbounded("y", 1234); serialize.NumberI32("z", 12345, 0, 65535); TS_ASSERT_STREAM(stream, 12, "\x85\xff\xff\xff" "\xd2\x04\x00\x00" "\x39\x30\x00\x00"); CStdDeserializer deserialize(script, stream); int32_t n; deserialize.NumberI32_Unbounded("x", n); TS_ASSERT_EQUALS(n, -123); deserialize.NumberI32_Unbounded("y", n); TS_ASSERT_EQUALS(n, 1234); deserialize.NumberI32("z", n, 0, 65535); TS_ASSERT_EQUALS(n, 12345); // NOTE: Don't use good() here - it fails due to a bug in older libc++ versions TS_ASSERT(!stream.bad() && !stream.fail()); TS_ASSERT_EQUALS(stream.peek(), EOF); } void test_Std_types() { ScriptInterface script("Test", "Test", g_ScriptRuntime); std::stringstream stream; CStdSerializer serialize(script, stream); serialize_types(serialize); CStdDeserializer deserialize(script, stream); int8_t i8v; uint8_t u8v; int16_t i16v; uint16_t u16v; int32_t i32v; uint32_t u32v; float flt; double dbl; fixed fxd; bool bl; std::string str; std::wstring wstr; u8 cbuf[256]; deserialize.NumberI8_Unbounded("i8", i8v); TS_ASSERT_EQUALS(i8v, -123); deserialize.NumberU8_Unbounded("u8", u8v); TS_ASSERT_EQUALS(u8v, 255); deserialize.NumberI16_Unbounded("i16", i16v); TS_ASSERT_EQUALS(i16v, -12345); deserialize.NumberU16_Unbounded("u16", u16v); TS_ASSERT_EQUALS(u16v, 56789); deserialize.NumberI32_Unbounded("i32", i32v); TS_ASSERT_EQUALS(i32v, -123); deserialize.NumberU32_Unbounded("u32", u32v); TS_ASSERT_EQUALS(u32v, 4294967173u); deserialize.NumberFloat_Unbounded("float", flt); TS_ASSERT_EQUALS(flt, 1e+30f); deserialize.NumberDouble_Unbounded("double", dbl); TS_ASSERT_EQUALS(dbl, 1e+300); deserialize.NumberFixed_Unbounded("fixed", fxd); TS_ASSERT_EQUALS(fxd.ToDouble(), 1234.5); deserialize.Bool("t", bl); TS_ASSERT_EQUALS(bl, true); deserialize.Bool("f", bl); TS_ASSERT_EQUALS(bl, false); deserialize.StringASCII("string", str, 0, 255); TS_ASSERT_STR_EQUALS(str, "example"); deserialize.StringASCII("string 2", str, 0, 255); TS_ASSERT_STR_EQUALS(str, "example\"\\\""); deserialize.StringASCII("string 3", str, 0, 255); TS_ASSERT_STR_EQUALS(str, "example\n\ntest"); wchar_t testw[] = { 't', 0xEA, 's', 't', 0 }; deserialize.String("string 4", wstr, 0, 255); TS_ASSERT_WSTR_EQUALS(wstr, testw); cbuf[6] = 0x42; // sentinel deserialize.RawBytes("raw bytes", cbuf, 6); TS_ASSERT_SAME_DATA(cbuf, (const u8*)"\0\1\2\3\x0f\x10\x42", 7); // NOTE: Don't use good() here - it fails due to a bug in older libc++ versions TS_ASSERT(!stream.bad() && !stream.fail()); TS_ASSERT_EQUALS(stream.peek(), EOF); } void test_Hash_basic() { ScriptInterface script("Test", "Test", g_ScriptRuntime); CHashSerializer serialize(script); serialize.NumberI32_Unbounded("x", -123); serialize.NumberU32_Unbounded("y", 1234); serialize.NumberI32("z", 12345, 0, 65535); TS_ASSERT_EQUALS(serialize.GetHashLength(), (size_t)16); TS_ASSERT_SAME_DATA(serialize.ComputeHash(), "\xa0\x3a\xe5\x3e\x9b\xd7\xfb\x11\x88\x35\xc6\xfb\xb9\x94\xa9\x72", 16); // echo -en "\x85\xff\xff\xff\xd2\x04\x00\x00\x39\x30\x00\x00" | openssl md5 -binary | xxd -p | perl -pe 's/(..)/\\x$1/g' } void test_Hash_stream() { ScriptInterface script("Test", "Test", g_ScriptRuntime); CHashSerializer hashSerialize(script); hashSerialize.NumberI32_Unbounded("x", -123); hashSerialize.NumberU32_Unbounded("y", 1234); hashSerialize.NumberI32("z", 12345, 0, 65535); ISerializer& serialize = hashSerialize; { CStdSerializer streamSerialize(script, serialize.GetStream()); streamSerialize.NumberI32_Unbounded("x2", -456); streamSerialize.NumberU32_Unbounded("y2", 5678); streamSerialize.NumberI32("z2", 45678, 0, 65535); } TS_ASSERT_EQUALS(hashSerialize.GetHashLength(), (size_t)16); TS_ASSERT_SAME_DATA(hashSerialize.ComputeHash(), "\x5c\xff\x33\xd1\x72\xdd\x6d\x77\xa8\xd4\xa1\xf6\x84\xcc\xaa\x10", 16); // echo -en "\x85\xff\xff\xff\xd2\x04\x00\x00\x39\x30\x00\x00\x38\xfe\xff\xff\x2e\x16\x00\x00\x6e\xb2\x00\x00" | openssl md5 -binary | xxd -p | perl -pe 's/(..)/\\x$1/g' } void test_bounds() { ScriptInterface script("Test", "Test", g_ScriptRuntime); std::stringstream stream; CDebugSerializer serialize(script, stream); serialize.NumberI32("x", 16, -16, 16); serialize.NumberI32("x", -16, -16, 16); TS_ASSERT_THROWS(serialize.NumberI32("x", 99, -16, 16), const PSERROR_Serialize_OutOfBounds&); TS_ASSERT_THROWS(serialize.NumberI32("x", -17, -16, 16), const PSERROR_Serialize_OutOfBounds&); } // TODO: test exceptions more thoroughly void helper_script_roundtrip(const char* msg, const char* input, const char* expected, size_t expstreamlen = 0, const char* expstream = NULL, const char* debug = NULL) { ScriptInterface script("Test", "Test", g_ScriptRuntime); JSContext* cx = script.GetContext(); JSAutoRequest rq(cx); JS::RootedValue obj(cx); TSM_ASSERT(msg, script.Eval(input, &obj)); if (debug) { std::stringstream dbgstream; CDebugSerializer serialize(script, dbgstream); serialize.ScriptVal("script", &obj); TS_ASSERT_STR_EQUALS(dbgstream.str(), debug); } std::stringstream stream; CStdSerializer serialize(script, stream); serialize.ScriptVal("script", &obj); if (expstream) { TSM_ASSERT_STREAM(msg, stream, expstreamlen, expstream); } CStdDeserializer deserialize(script, stream); JS::RootedValue newobj(cx); deserialize.ScriptVal("script", &newobj); // NOTE: Don't use good() here - it fails due to a bug in older libc++ versions TSM_ASSERT(msg, !stream.bad() && !stream.fail()); TSM_ASSERT_EQUALS(msg, stream.peek(), EOF); std::string source; TSM_ASSERT(msg, script.CallFunction(newobj, "toSource", source)); TS_ASSERT_STR_EQUALS(source, expected); } void test_script_basic() { helper_script_roundtrip("Object", "({'x': 123, 'y': [1, 1.5, '2', 'test', undefined, null, true, false]})", /* expected: */ "({x:123, y:[1, 1.5, \"2\", \"test\", (void 0), null, true, false]})", /* expected stream: */ 116, "\x03" // SCRIPT_TYPE_OBJECT "\x02\0\0\0" // num props "\x01\x01\0\0\0" "x" // "x" "\x05" // SCRIPT_TYPE_INT "\x7b\0\0\0" // 123 "\x01\x01\0\0\0" "y" // "y" "\x02" // SCRIPT_TYPE_ARRAY "\x08\0\0\0" // array length "\x08\0\0\0" // num props "\x01\x01\0\0\0" "0" // "0" "\x05" "\x01\0\0\0" // SCRIPT_TYPE_INT 1 "\x01\x01\0\0\0" "1" // "1" "\x06" "\0\0\0\0\0\0\xf8\x3f" // SCRIPT_TYPE_DOUBLE 1.5 "\x01\x01\0\0\0" "2" // "2" "\x04" "\x01\x01\0\0\0" "2" // SCRIPT_TYPE_STRING "2" "\x01\x01\0\0\0" "3" // "3" "\x04" "\x01\x04\0\0\0" "test" // SCRIPT_TYPE_STRING "test" "\x01\x01\0\0\0" "4" // "4" "\x00" // SCRIPT_TYPE_VOID "\x01\x01\0\0\0" "5" // "5" "\x01" // SCRIPT_TYPE_NULL "\x01\x01\0\0\0" "6" // "6" "\x07" "\x01" // SCRIPT_TYPE_BOOLEAN true "\x01\x01\0\0\0" "7" // "7" "\x07" "\x00", // SCRIPT_TYPE_BOOLEAN false /* expected debug: */ "script: {\n" " \"x\": 123,\n" " \"y\": [\n" " 1,\n" " 1.5,\n" " \"2\",\n" " \"test\",\n" " null,\n" " null,\n" " true,\n" " false\n" " ]\n" "}\n" ); } void test_script_unicode() { helper_script_roundtrip("unicode", "({" "'x': \"\\x01\\x80\\xff\\u0100\\ud7ff\", " "'y': \"\\ue000\\ufffd\"" "})", /* expected: */ "({" "x:\"\\x01\\x80\\xFF\\u0100\\uD7FF\", " "y:\"\\uE000\\uFFFD\"" "})"); // Disabled since we no longer do the UTF-8 conversion that rejects invalid characters // TS_ASSERT_THROWS(helper_script_roundtrip("invalid chars 1", "(\"\\ud7ff\\ud800\")", "..."), PSERROR_Serialize_InvalidCharInString); // TS_ASSERT_THROWS(helper_script_roundtrip("invalid chars 2", "(\"\\udfff\")", "..."), PSERROR_Serialize_InvalidCharInString); // TS_ASSERT_THROWS(helper_script_roundtrip("invalid chars 3", "(\"\\uffff\")", "..."), PSERROR_Serialize_InvalidCharInString); // TS_ASSERT_THROWS(helper_script_roundtrip("invalid chars 4", "(\"\\ud800\\udc00\")" /* U+10000 */, "..."), PSERROR_Serialize_InvalidCharInString); helper_script_roundtrip("unicode", "\"\\ud800\\uffff\"", "(new String(\"\\uD800\\uFFFF\"))"); } void test_script_objects() { helper_script_roundtrip("Number", "[1, new Number('2.0'), 3]", "[1, (new Number(2)), 3]"); helper_script_roundtrip("Number with props", "var n=new Number('2.0'); n.foo='bar'; n", "(new Number(2))"); helper_script_roundtrip("String", "['test1', new String('test2'), 'test3']", "[\"test1\", (new String(\"test2\")), \"test3\"]"); helper_script_roundtrip("String with props", "var s=new String('test'); s.foo='bar'; s", "(new String(\"test\"))"); helper_script_roundtrip("Boolean", "[new Boolean('true'), false]", "[(new Boolean(true)), false]"); helper_script_roundtrip("Boolean with props", "var b=new Boolean('true'); b.foo='bar'; b", "(new Boolean(true))"); } void test_script_objects_properties() { helper_script_roundtrip("Object with null in prop name", "({\"foo\\0bar\":1})", "({\'foo\\x00bar\':1})"); } void test_script_typed_arrays_simple() { helper_script_roundtrip("Int8Array", "var arr=new Int8Array(8);" "for(var i=0; iMount(L"", DataDir()/"mods"/"public", VFS_MOUNT_MUST_EXIST)); TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache")); // Need some stuff for terrain movement costs: // (TODO: this ought to be independent of any graphics code) new CTerrainTextureManager; g_TexMan.LoadTerrainTextures(); CTerrain terrain; CSimulation2 sim2(NULL, g_ScriptRuntime, &terrain); sim2.LoadDefaultScripts(); sim2.ResetState(); std::unique_ptr mapReader(new CMapReader()); LDR_BeginRegistering(); mapReader->LoadMap(L"maps/skirmishes/Greek Acropolis (2).pmp", sim2.GetScriptInterface().GetJSRuntime(), JS::UndefinedHandleValue, &terrain, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &sim2, &sim2.GetSimContext(), -1, false); LDR_EndRegistering(); TS_ASSERT_OK(LDR_NonprogressiveLoad()); sim2.Update(0); { std::stringstream str; std::string hash; sim2.SerializeState(str); sim2.ComputeStateHash(hash, false); debug_printf("\n"); debug_printf("# size = %d\n", (int)str.str().length()); debug_printf("# hash = "); for (size_t i = 0; i < hash.size(); ++i) debug_printf("%02x", (unsigned int)(u8)hash[i]); debug_printf("\n"); } double t = timer_Time(); CALLGRIND_START_INSTRUMENTATION; size_t reps = 128; for (size_t i = 0; i < reps; ++i) { std::string hash; sim2.ComputeStateHash(hash, false); } CALLGRIND_STOP_INSTRUMENTATION; t = timer_Time() - t; debug_printf("# time = %f (%f/%d)\n", t/reps, t, (int)reps); // Shut down the world delete &g_TexMan; g_VFS.reset(); DeleteDirectory(DataDir()/"_testcache"); CXeromyces::Terminate(); } };