[SM68 2/2] Update to Spidermonkey 68 APIs

No noteworthy API changes.

Details:
- Remove UTF16 script execution since UTF8 is supported in SM68 and
going forward
- Several new headers includes are required
- Realms replace Compartments as "global holders" (see meta-Bug 1357862)
- JSRequests are removed entirely (Bug 722345), see also aae417bd29
- Trivial API updates in ProxyHandlers, ArrayBuffer, Warnings, GC
reasons, Context options, ObjectIsFunction, ValueVectors and
JSCompartment

See also the migration guide:
https://github.com/mozilla-spidermonkey/spidermonkey-embedding-examples/blob/esr78/docs/Migration%20Guide.md

Tested by: Freagarach, Stan, Subitaneo
Fixes #5860

Differential Revision: https://code.wildfiregames.com/D3144
This was SVN commit r24297.
This commit is contained in:
wraitii
2020-11-30 09:03:20 +00:00
parent d8332a2938
commit 02578e46bf
25 changed files with 158 additions and 125 deletions
+10 -10
View File
@@ -161,14 +161,14 @@ BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~)
#undef OVERLOADS
template<int i, typename T, typename... Ts>
static void AssignOrToJSValHelper(const ScriptRequest& rq, JS::AutoValueVector& argv, const T& a, const Ts&... params)
static void AssignOrToJSValHelper(const ScriptRequest& rq, JS::MutableHandleValueVector argv, const T& a, const Ts&... params)
{
ScriptInterface::AssignOrToJSVal(rq, argv[i], a);
AssignOrToJSValHelper<i+1>(rq, argv, params...);
}
template<int i, typename... Ts>
static void AssignOrToJSValHelper(const ScriptRequest& UNUSED(rq), JS::AutoValueVector& UNUSED(argv))
static void AssignOrToJSValHelper(const ScriptRequest& UNUSED(rq), JS::MutableHandleValueVector UNUSED(argv))
{
cassert(sizeof...(Ts) == 0);
// Nop, for terminating the template recursion.
@@ -179,9 +179,9 @@ bool ScriptInterface::CallFunction(JS::HandleValue val, const char* name, R& ret
{
ScriptRequest rq(this);
JS::RootedValue jsRet(rq.cx);
JS::AutoValueVector argv(rq.cx);
JS::RootedValueVector argv(rq.cx);
DISCARD argv.resize(sizeof...(Ts));
AssignOrToJSValHelper<0>(rq, argv, params...);
AssignOrToJSValHelper<0>(rq, &argv, params...);
if (!CallFunction_(val, name, argv, &jsRet))
return false;
return FromJSVal(rq, jsRet, ret);
@@ -192,9 +192,9 @@ bool ScriptInterface::CallFunction(JS::HandleValue val, const char* name, JS::Ro
{
ScriptRequest rq(this);
JS::MutableHandle<R> jsRet(ret);
JS::AutoValueVector argv(rq.cx);
JS::RootedValueVector argv(rq.cx);
DISCARD argv.resize(sizeof...(Ts));
AssignOrToJSValHelper<0>(rq, argv, params...);
AssignOrToJSValHelper<0>(rq, &argv, params...);
return CallFunction_(val, name, argv, jsRet);
}
@@ -202,9 +202,9 @@ template<typename R, typename... Ts>
bool ScriptInterface::CallFunction(JS::HandleValue val, const char* name, JS::MutableHandle<R> ret, const Ts&... params) const
{
ScriptRequest rq(this);
JS::AutoValueVector argv(rq.cx);
JS::RootedValueVector argv(rq.cx);
DISCARD argv.resize(sizeof...(Ts));
AssignOrToJSValHelper<0>(rq, argv, params...);
AssignOrToJSValHelper<0>(rq, &argv, params...);
return CallFunction_(val, name, argv, ret);
}
@@ -214,9 +214,9 @@ bool ScriptInterface::CallFunctionVoid(JS::HandleValue val, const char* name, co
{
ScriptRequest rq(this);
JS::RootedValue jsRet(rq.cx);
JS::AutoValueVector argv(rq.cx);
JS::RootedValueVector argv(rq.cx);
DISCARD argv.resize(sizeof...(Ts));
AssignOrToJSValHelper<0>(rq, argv, params...);
AssignOrToJSValHelper<0>(rq, &argv, params...);
return CallFunction_(val, name, argv, &jsRet);
}
+15 -15
View File
@@ -21,10 +21,10 @@
#include "ps/GameSetup/Config.h"
#include "ps/Profile.h"
#include "scriptinterface/ScriptExtraHeaders.h"
#include "scriptinterface/ScriptEngine.h"
#include "scriptinterface/ScriptInterface.h"
void GCSliceCallbackHook(JSContext* UNUSED(cx), JS::GCProgress progress, const JS::GCDescription& UNUSED(desc))
{
/**
@@ -130,15 +130,15 @@ ScriptContext::~ScriptContext()
ScriptEngine::GetSingleton().UnRegisterContext(m_cx);
}
void ScriptContext::RegisterCompartment(JSCompartment* cmpt)
void ScriptContext::RegisterRealm(JS::Realm* realm)
{
ENSURE(cmpt);
m_Compartments.push_back(cmpt);
ENSURE(realm);
m_Realms.push_back(realm);
}
void ScriptContext::UnRegisterCompartment(JSCompartment* cmpt)
void ScriptContext::UnRegisterRealm(JS::Realm* realm)
{
m_Compartments.remove(cmpt);
m_Realms.remove(realm);
}
#define GC_DEBUG_PRINT 0
@@ -204,8 +204,8 @@ void ScriptContext::MaybeIncrementalGC(double delay)
#if GC_DEBUG_PRINT
printf("Finishing incremental GC because gcBytes > m_ContextSize / 2. \n");
#endif
PrepareCompartmentsForIncrementalGC();
JS::FinishIncrementalGC(m_cx, JS::gcreason::API);
PrepareZonesForIncrementalGC();
JS::FinishIncrementalGC(m_cx, JS::GCReason::API);
}
else
{
@@ -233,11 +233,11 @@ void ScriptContext::MaybeIncrementalGC(double delay)
else
printf("Running incremental GC slice \n");
#endif
PrepareCompartmentsForIncrementalGC();
PrepareZonesForIncrementalGC();
if (!JS::IsIncrementalGCInProgress(m_cx))
JS::StartIncrementalGC(m_cx, GC_NORMAL, JS::gcreason::API, GCSliceTimeBudget);
JS::StartIncrementalGC(m_cx, GC_NORMAL, JS::GCReason::API, GCSliceTimeBudget);
else
JS::IncrementalGCSlice(m_cx, JS::gcreason::API, GCSliceTimeBudget);
JS::IncrementalGCSlice(m_cx, JS::GCReason::API, GCSliceTimeBudget);
}
m_LastGCBytes = gcBytes;
}
@@ -248,12 +248,12 @@ void ScriptContext::ShrinkingGC()
{
JS_SetGCParameter(m_cx, JSGC_MODE, JSGC_MODE_ZONE);
JS::PrepareForFullGC(m_cx);
JS::GCForReason(m_cx, GC_SHRINK, JS::gcreason::API);
JS::NonIncrementalGC(m_cx, GC_SHRINK, JS::GCReason::API);
JS_SetGCParameter(m_cx, JSGC_MODE, JSGC_MODE_INCREMENTAL);
}
void ScriptContext::PrepareCompartmentsForIncrementalGC() const
void ScriptContext::PrepareZonesForIncrementalGC() const
{
for (JSCompartment* const& cmpt : m_Compartments)
JS::PrepareZoneForGC(js::GetCompartmentZone(cmpt));
for (JS::Realm* const& realm : m_Realms)
JS::PrepareZoneForGC(js::GetRealmZone(realm));
}
+5 -5
View File
@@ -69,10 +69,10 @@ public:
void ShrinkingGC();
/**
* This is used to keep track of compartments which should be prepared for a GC.
* This is used to keep track of realms which should be prepared for a GC.
*/
void RegisterCompartment(JSCompartment* cmpt);
void UnRegisterCompartment(JSCompartment* cmpt);
void RegisterRealm(JS::Realm* realm);
void UnRegisterRealm(JS::Realm* realm);
/**
* GetGeneralJSContext returns the context without starting a GC request and without
@@ -87,8 +87,8 @@ private:
JSContext* m_cx;
void PrepareCompartmentsForIncrementalGC() const;
std::list<JSCompartment*> m_Compartments;
void PrepareZonesForIncrementalGC() const;
std::list<JS::Realm*> m_Realms;
int m_ContextSize;
int m_HeapGrowthBytesGCTrigger;
+2 -1
View File
@@ -18,6 +18,7 @@
#include "precompiled.h"
#include "ScriptConversions.h"
#include "ScriptExtraHeaders.h"
#include "graphics/Entity.h"
#include "maths/Vector2D.h"
@@ -28,7 +29,7 @@
#define FAIL(msg) STMT(LOGERROR(msg); return false)
// Implicit type conversions often hide bugs, so warn about them
#define WARN_IF_NOT(c, v) STMT(if (!(c)) { JS_ReportWarningUTF8(rq.cx, "Script value conversion check failed: %s (got type %s)", #c, JS::InformalValueTypeName(v)); })
#define WARN_IF_NOT(c, v) STMT(if (!(c)) { JS::WarnUTF8(rq.cx, "Script value conversion check failed: %s (got type %s)", #c, JS::InformalValueTypeName(v)); })
template<> bool ScriptInterface::FromJSVal<bool>(const ScriptRequest& rq, JS::HandleValue v, bool& out)
{
@@ -46,10 +46,20 @@
#endif
#include "jsfriendapi.h"
#include "js/ArrayBuffer.h"
#include "js/Conversions.h"
#include "js/ContextOptions.h"
#include "js/ForOfIterator.h"
#include "js/GCAPI.h"
#include "js/JSON.h"
#include "js/StructuredClone.h"
#include "js/Proxy.h"
#include "js/Warnings.h"
// CompileFunction & Evaluate
#include "js/CompilationAndEvaluation.h"
#include "js/SourceText.h"
#undef signbit
+27
View File
@@ -18,9 +18,36 @@
#ifndef INCLUDED_SCRIPTFORWARD
#define INCLUDED_SCRIPTFORWARD
// Ignore some harmless warnings
#if GCC_VERSION
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wunused-parameter"
#endif
#if CLANG_VERSION
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wunused-parameter"
#endif
#if MSC_VERSION
# pragma warning(push, 1)
# pragma warning(disable: 4100)
#endif
#include "js/TypeDecls.h"
class ScriptInterface;
class ScriptRequest;
#if GCC_VERSION
# pragma GCC diagnostic pop
#endif
#if CLANG_VERSION
# pragma clang diagnostic pop
#endif
#if MSC_VERSION
# pragma warning(pop)
#endif
#endif // INCLUDED_SCRIPTFORWARD
+35 -42
View File
@@ -18,6 +18,7 @@
#include "precompiled.h"
#include "ScriptContext.h"
#include "ScriptExtraHeaders.h"
#include "ScriptInterface.h"
#include "ScriptStats.h"
@@ -42,8 +43,6 @@
#include "valgrind.h"
#include "scriptinterface/ScriptExtraHeaders.h"
/**
* @file
* Abstractions of various SpiderMonkey features.
@@ -75,8 +74,7 @@ struct ScriptInterface_impl
ScriptRequest::ScriptRequest(const ScriptInterface& scriptInterface) :
cx(scriptInterface.m->m_cx)
{
JS_BeginRequest(cx);
m_formerCompartment = JS_EnterCompartment(cx, scriptInterface.m->m_glob);
m_formerRealm = JS::EnterRealm(cx, scriptInterface.m->m_glob);
glob = JS::CurrentGlobalOrNull(cx);
}
@@ -87,8 +85,7 @@ JS::Value ScriptRequest::globalValue() const
ScriptRequest::~ScriptRequest()
{
JS_LeaveCompartment(cx, m_formerCompartment);
JS_EndRequest(cx);
JS::LeaveRealm(cx, m_formerRealm);
}
namespace
@@ -324,17 +321,16 @@ bool ScriptInterface::MathRandom(double& nbr)
ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, const shared_ptr<ScriptContext>& context) :
m_context(context), m_cx(context->GetGeneralJSContext()), m_glob(context->GetGeneralJSContext()), m_nativeScope(context->GetGeneralJSContext())
{
JS::CompartmentCreationOptions creationOpt;
JS::RealmCreationOptions creationOpt;
// Keep JIT code during non-shrinking GCs. This brings a quite big performance improvement.
creationOpt.setPreserveJitCode(true);
JS::CompartmentOptions opt(creationOpt, JS::CompartmentBehaviors{});
JS::RealmOptions opt(creationOpt, JS::RealmBehaviors{});
JSAutoRequest rq(m_cx);
m_glob = JS_NewGlobalObject(m_cx, &global_class, nullptr, JS::OnNewGlobalHookOption::FireOnNewGlobalHook, opt);
JSAutoCompartment autoCmpt(m_cx, m_glob);
JSAutoRealm autoRealm(m_cx, m_glob);
ENSURE(JS_InitStandardClasses(m_cx, m_glob));
ENSURE(JS::InitRealmStandardClasses(m_cx));
JS_DefineProperty(m_cx, m_glob, "global", m_glob, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
@@ -351,18 +347,17 @@ ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, const sh
Register("ProfileStop", ::ProfileStop, 0);
Register("ProfileAttribute", ::ProfileAttribute, 1);
m_context->RegisterCompartment(js::GetObjectCompartment(m_glob));
m_context->RegisterRealm(JS::GetObjectRealmOrNull(m_glob));
}
ScriptInterface_impl::~ScriptInterface_impl()
{
m_context->UnRegisterCompartment(js::GetObjectCompartment(m_glob));
m_context->UnRegisterRealm(JS::GetObjectRealmOrNull(m_glob));
}
void ScriptInterface_impl::Register(const char* name, JSNative fptr, uint nargs) const
{
JSAutoRequest rq(m_cx);
JSAutoCompartment autoCmpt(m_cx, m_glob);
JSAutoRealm autoRealm(m_cx, m_glob);
JS::RootedObject nativeScope(m_cx, m_nativeScope);
JS::RootedFunction func(m_cx, JS_DefineFunction(m_cx, nativeScope, name, fptr, nargs, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT));
}
@@ -718,7 +713,7 @@ bool ScriptInterface::EnumeratePropertyNames(JS::HandleValue objVal, bool enumer
}
JS::RootedObject obj(rq.cx, &objVal.toObject());
JS::AutoIdVector props(rq.cx);
JS::RootedIdVector props(rq.cx);
// This recurses up the prototype chain on its own.
if (!js::GetPropertyKeys(rq.cx, obj, enumerableOnly? 0 : JSITER_HIDDEN, &props))
return false;
@@ -774,20 +769,20 @@ bool ScriptInterface::LoadScript(const VfsPath& filename, const std::string& cod
{
ScriptRequest rq(this);
JS::RootedObject global(rq.cx, rq.glob);
utf16string codeUtf16(code.begin(), code.end());
uint lineNo = 1;
// CompileOptions does not copy the contents of the filename string pointer.
// Passing a temporary string there will cause undefined behaviour, so we create a separate string to avoid the temporary.
std::string filenameStr = filename.string8();
JS::CompileOptions options(rq.cx);
options.setFileAndLine(filenameStr.c_str(), lineNo);
options.setFileAndLine(filenameStr.c_str(), 1);
options.setIsRunOnce(false);
JS::RootedFunction func(rq.cx);
JS::AutoObjectVector emptyScopeChain(rq.cx);
if (!JS::CompileFunction(rq.cx, emptyScopeChain, options, NULL, 0, NULL,
reinterpret_cast<const char16_t*>(codeUtf16.c_str()), (uint)(codeUtf16.length()), &func))
JS::SourceText<mozilla::Utf8Unit> src;
ENSURE(src.init(rq.cx, code.c_str(), code.length(), JS::SourceOwnership::Borrowed));
JS::RootedObjectVector emptyScopeChain(rq.cx);
JS::RootedFunction func(rq.cx, JS::CompileFunction(rq.cx, emptyScopeChain, options, NULL, 0, NULL, src));
if (func == nullptr)
{
ScriptException::CatchPending(rq);
return false;
@@ -801,19 +796,20 @@ bool ScriptInterface::LoadScript(const VfsPath& filename, const std::string& cod
return false;
}
bool ScriptInterface::LoadGlobalScript(const VfsPath& filename, const std::wstring& code) const
bool ScriptInterface::LoadGlobalScript(const VfsPath& filename, const std::string& code) const
{
ScriptRequest rq(this);
utf16string codeUtf16(code.begin(), code.end());
uint lineNo = 1;
// CompileOptions does not copy the contents of the filename string pointer.
// Passing a temporary string there will cause undefined behaviour, so we create a separate string to avoid the temporary.
std::string filenameStr = filename.string8();
JS::RootedValue rval(rq.cx);
JS::CompileOptions opts(rq.cx);
opts.setFileAndLine(filenameStr.c_str(), lineNo);
if (JS::Evaluate(rq.cx, opts, reinterpret_cast<const char16_t*>(codeUtf16.c_str()), (uint)(codeUtf16.length()), &rval))
opts.setFileAndLine(filenameStr.c_str(), 1);
JS::SourceText<mozilla::Utf8Unit> src;
ENSURE(src.init(rq.cx, code.c_str(), code.length(), JS::SourceOwnership::Borrowed));
if (JS::Evaluate(rq.cx, opts, src, &rval))
return true;
ScriptException::CatchPending(rq);
@@ -839,9 +835,8 @@ bool ScriptInterface::LoadGlobalScriptFile(const VfsPath& path) const
return false;
}
std::wstring code = wstring_from_utf8(file.DecodeUTF8()); // assume it's UTF-8
CStr code = file.DecodeUTF8(); // assume it's UTF-8
utf16string codeUtf16(code.begin(), code.end());
uint lineNo = 1;
// CompileOptions does not copy the contents of the filename string pointer.
// Passing a temporary string there will cause undefined behaviour, so we create a separate string to avoid the temporary.
@@ -850,7 +845,9 @@ bool ScriptInterface::LoadGlobalScriptFile(const VfsPath& path) const
JS::RootedValue rval(rq.cx);
JS::CompileOptions opts(rq.cx);
opts.setFileAndLine(filenameStr.c_str(), lineNo);
if (JS::Evaluate(rq.cx, opts, reinterpret_cast<const char16_t*>(codeUtf16.c_str()), (uint)(codeUtf16.length()), &rval))
JS::SourceText<mozilla::Utf8Unit> src;
ENSURE(src.init(rq.cx, code.c_str(), code.length(), JS::SourceOwnership::Borrowed));
if (JS::Evaluate(rq.cx, opts, src, &rval))
return true;
ScriptException::CatchPending(rq);
@@ -861,31 +858,27 @@ bool ScriptInterface::Eval(const char* code) const
{
ScriptRequest rq(this);
JS::RootedValue rval(rq.cx);
return Eval_(code, &rval);
}
bool ScriptInterface::Eval_(const char* code, JS::MutableHandleValue rval) const
{
ScriptRequest rq(this);
utf16string codeUtf16(code, code+strlen(code));
JS::CompileOptions opts(rq.cx);
opts.setFileAndLine("(eval)", 1);
if (JS::Evaluate(rq.cx, opts, reinterpret_cast<const char16_t*>(codeUtf16.c_str()), (uint)codeUtf16.length(), rval))
JS::SourceText<mozilla::Utf8Unit> src;
ENSURE(src.init(rq.cx, code, strlen(code), JS::SourceOwnership::Borrowed));
if (JS::Evaluate(rq.cx, opts, src, &rval))
return true;
ScriptException::CatchPending(rq);
return false;
}
bool ScriptInterface::Eval_(const wchar_t* code, JS::MutableHandleValue rval) const
bool ScriptInterface::Eval(const char* code, JS::MutableHandleValue rval) const
{
ScriptRequest rq(this);
utf16string codeUtf16(code, code+wcslen(code));
JS::CompileOptions opts(rq.cx);
opts.setFileAndLine("(eval)", 1);
if (JS::Evaluate(rq.cx, opts, reinterpret_cast<const char16_t*>(codeUtf16.c_str()), (uint)codeUtf16.length(), rval))
JS::SourceText<mozilla::Utf8Unit> src;
ENSURE(src.init(rq.cx, code, strlen(code), JS::SourceOwnership::Borrowed));
if (JS::Evaluate(rq.cx, opts, src, rval))
return true;
ScriptException::CatchPending(rq);
+17 -21
View File
@@ -49,6 +49,8 @@ ERROR_TYPE(Scripting_DefineType, CreationFailed);
// but as large as necessary for all wrapped functions)
#define SCRIPT_INTERFACE_MAX_ARGS 8
class JSStructuredCloneData;
class ScriptInterface;
struct ScriptInterface_impl;
@@ -61,7 +63,7 @@ extern thread_local shared_ptr<ScriptContext> g_ScriptContext;
* RAII structure which encapsulates an access to the context and compartment of a ScriptInterface.
* This struct provides:
* - a pointer to the context, while acting like JSAutoRequest
* - a pointer to the global object of the compartment, while acting like JSAutoCompartment
* - a pointer to the global object of the compartment, while acting like JSAutoRealm
*
* This way, getting and using those pointers is safe with respect to the GC
* and to the separation of compartments.
@@ -81,11 +83,11 @@ public:
JSContext* cx;
JSObject* glob;
private:
JSCompartment* m_formerCompartment;
JS::Realm* m_formerRealm;
};
/**
* Abstraction around a SpiderMonkey JSCompartment.
* Abstraction around a SpiderMonkey JS::Realm.
*
* Thread-safety:
* - May be used in non-main threads.
@@ -244,11 +246,6 @@ public:
bool FreezeObject(JS::HandleValue objVal, bool deep) const;
bool Eval(const char* code) const;
template<typename CHAR> bool Eval(const CHAR* code, JS::MutableHandleValue out) const;
template<typename T, typename CHAR> bool Eval(const CHAR* code, T& out) const;
/**
* Convert an object to a UTF-8 encoded string, either with JSON
* (if pretty == true and there is no JSON error) or with toSource().
@@ -288,7 +285,7 @@ public:
* @param code JS code to execute
* @return true on successful compilation and execution; false otherwise
*/
bool LoadGlobalScript(const VfsPath& filename, const std::wstring& code) const;
bool LoadGlobalScript(const VfsPath& filename, const std::string& code) const;
/**
* Load and execute the given script in the global scope.
@@ -296,6 +293,14 @@ public:
*/
bool LoadGlobalScriptFile(const VfsPath& path) const;
/**
* Evaluate some JS code in the global scope.
* @return true on successful compilation and execution; false otherwise
*/
bool Eval(const char* code) const;
bool Eval(const char* code, JS::MutableHandleValue out) const;
template<typename T> bool Eval(const char* code, T& out) const;
/**
* Convert a JS::Value to a C++ type. (This might trigger GC.)
*/
@@ -425,8 +430,6 @@ private:
}
bool CallFunction_(JS::HandleValue val, const char* name, JS::HandleValueArray argv, JS::MutableHandleValue ret) const;
bool Eval_(const char* code, JS::MutableHandleValue ret) const;
bool Eval_(const wchar_t* code, JS::MutableHandleValue ret) const;
bool SetGlobal_(const char* name, JS::HandleValue value, bool replace, bool constant, bool enumerate);
bool SetProperty_(JS::HandleValue obj, const char* name, JS::HandleValue value, bool constant, bool enumerate) const;
bool SetProperty_(JS::HandleValue obj, const wchar_t* name, JS::HandleValue value, bool constant, bool enumerate) const;
@@ -592,20 +595,13 @@ bool ScriptInterface::GetPropertyInt(JS::HandleValue obj, int name, T& out) cons
return FromJSVal(rq, val, out);
}
template<typename CHAR>
bool ScriptInterface::Eval(const CHAR* code, JS::MutableHandleValue ret) const
{
if (!Eval_(code, ret))
return false;
return true;
}
template<typename T, typename CHAR>
bool ScriptInterface::Eval(const CHAR* code, T& ret) const
template<typename T>
bool ScriptInterface::Eval(const char* code, T& ret) const
{
ScriptRequest rq(this);
JS::RootedValue rval(rq.cx);
if (!Eval_(code, &rval))
if (!Eval(code, &rval))
return false;
return FromJSVal(rq, rval, ret);
}
@@ -243,10 +243,10 @@ public:
TS_ASSERT(script.Eval(input.c_str(), &val));
std::string stringified = script.StringifyJSON(&val);
TS_ASSERT_STR_EQUALS(stringified, "{\n \"x\": 1,\n \"z\": [\n 2,\n \"3\xE2\x98\xBA\xEF\xBF\xBD\"\n ],\n \"y\": true\n}");
TS_ASSERT_STR_EQUALS(stringified, "{\n \"x\": 1,\n \"z\": [\n 2,\n \"3\u263A\\ud800\"\n ],\n \"y\": true\n}");
TS_ASSERT(script.ParseJSON(stringified, &val));
TS_ASSERT_STR_EQUALS(script.ToString(&val), "({x:1, z:[2, \"3\\u263A\\uFFFD\"], y:true})");
TS_ASSERT_STR_EQUALS(script.ToString(&val), "({x:1, z:[2, \"3\\u263A\\uD800\"], y:true})");
}
// This function tests a common way to mod functions, by crating a wrapper that