LCOV - code coverage report
Current view: top level - source/simulation2/serialization - BinarySerializer.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 203 251 80.9 %
Date: 2023-01-19 00:18:29 Functions: 6 7 85.7 %

          Line data    Source code
       1             : /* Copyright (C) 2023 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "BinarySerializer.h"
      21             : 
      22             : #include "lib/alignment.h"
      23             : #include "lib/utf8.h"
      24             : #include "ps/CLogger.h"
      25             : #include "scriptinterface/FunctionWrapper.h"
      26             : #include "scriptinterface/ScriptExtraHeaders.h"
      27             : #include "scriptinterface/ScriptRequest.h"
      28             : #include "scriptinterface/JSON.h"
      29             : 
      30             : #include "SerializedScriptTypes.h"
      31             : 
      32          32 : static u8 GetArrayType(js::Scalar::Type arrayType)
      33             : {
      34          32 :     switch(arrayType)
      35             :     {
      36           4 :     case js::Scalar::Int8:
      37           4 :         return SCRIPT_TYPED_ARRAY_INT8;
      38           4 :     case js::Scalar::Uint8:
      39           4 :         return SCRIPT_TYPED_ARRAY_UINT8;
      40           6 :     case js::Scalar::Int16:
      41           6 :         return SCRIPT_TYPED_ARRAY_INT16;
      42           4 :     case js::Scalar::Uint16:
      43           4 :         return SCRIPT_TYPED_ARRAY_UINT16;
      44           4 :     case js::Scalar::Int32:
      45           4 :         return SCRIPT_TYPED_ARRAY_INT32;
      46           4 :     case js::Scalar::Uint32:
      47           4 :         return SCRIPT_TYPED_ARRAY_UINT32;
      48           2 :     case js::Scalar::Float32:
      49           2 :         return SCRIPT_TYPED_ARRAY_FLOAT32;
      50           2 :     case js::Scalar::Float64:
      51           2 :         return SCRIPT_TYPED_ARRAY_FLOAT64;
      52           2 :     case js::Scalar::Uint8Clamped:
      53           2 :         return SCRIPT_TYPED_ARRAY_UINT8_CLAMPED;
      54           0 :     default:
      55           0 :         LOGERROR("Cannot serialize unrecognized typed array view: %d", arrayType);
      56           0 :         throw PSERROR_Serialize_InvalidScriptValue();
      57             :     }
      58             : }
      59             : 
      60         120 : CBinarySerializerScriptImpl::CBinarySerializerScriptImpl(const ScriptInterface& scriptInterface, ISerializer& serializer) :
      61         120 :     m_ScriptInterface(scriptInterface), m_Serializer(serializer), m_ScriptBackrefsNext(0)
      62             : {
      63         240 :     ScriptRequest rq(m_ScriptInterface);
      64         120 :     JS_AddExtraGCRootsTracer(rq.cx, Trace, this);
      65         120 : }
      66             : 
      67         240 : CBinarySerializerScriptImpl::~CBinarySerializerScriptImpl()
      68             : {
      69         240 :     ScriptRequest rq(m_ScriptInterface);
      70         120 :     JS_RemoveExtraGCRootsTracer(rq.cx, Trace, this);
      71         120 : }
      72             : 
      73         353 : void CBinarySerializerScriptImpl::HandleScriptVal(JS::HandleValue val)
      74             : {
      75         706 :     ScriptRequest rq(m_ScriptInterface);
      76             : 
      77         353 :     switch (JS_TypeOfValue(rq.cx, val))
      78             :     {
      79           2 :     case JSTYPE_UNDEFINED:
      80             :     {
      81           2 :         m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_VOID);
      82           2 :         break;
      83             :     }
      84           0 :     case JSTYPE_NULL: // This type is never actually returned (it's a JS2 feature)
      85             :     {
      86           0 :         m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_NULL);
      87           0 :         break;
      88             :     }
      89         193 :     case JSTYPE_OBJECT:
      90             :     {
      91         193 :         if (val.isNull())
      92             :         {
      93           2 :             m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_NULL);
      94           2 :             break;
      95             :         }
      96             : 
      97         382 :         JS::RootedObject obj(rq.cx, &val.toObject());
      98             : 
      99             :         // If we've already serialized this object, just output a reference to it
     100         191 :         u32 tag = GetScriptBackrefTag(obj);
     101         191 :         if (tag != 0)
     102             :         {
     103          17 :             m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_BACKREF);
     104          17 :             m_Serializer.NumberU32("tag", tag, 0, JSVAL_INT_MAX);
     105          17 :             break;
     106             :         }
     107             : 
     108             :         // Arrays, Maps and Sets are special cases of Objects
     109             :         bool isArray;
     110             :         bool isMap;
     111             :         bool isSet;
     112             : 
     113         174 :         if (JS::IsArrayObject(rq.cx, obj, &isArray) && isArray)
     114             :         {
     115          38 :             m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_ARRAY);
     116             :             // TODO: probably should have a more efficient storage format
     117             : 
     118             :             // Arrays like [1, 2, ] have an 'undefined' at the end which is part of the
     119             :             // length but seemingly isn't enumerated, so store the length explicitly
     120          38 :             uint length = 0;
     121          38 :             if (!JS::GetArrayLength(rq.cx, obj, &length))
     122           0 :                 throw PSERROR_Serialize_ScriptError("JS::GetArrayLength failed");
     123          38 :             m_Serializer.NumberU32_Unbounded("array length", length);
     124             :         }
     125         136 :         else if (JS_IsTypedArrayObject(obj))
     126             :         {
     127          32 :             m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_TYPED_ARRAY);
     128             : 
     129          32 :             m_Serializer.NumberU8_Unbounded("array type", GetArrayType(JS_GetArrayBufferViewType(obj)));
     130          32 :             m_Serializer.NumberU32_Unbounded("byte offset", JS_GetTypedArrayByteOffset(obj));
     131          32 :             m_Serializer.NumberU32_Unbounded("length", JS_GetTypedArrayLength(obj));
     132             : 
     133             :             bool sharedMemory;
     134             :             // Now handle its array buffer
     135             :             // this may be a backref, since ArrayBuffers can be shared by multiple views
     136          64 :             JS::RootedValue bufferVal(rq.cx, JS::ObjectValue(*JS_GetArrayBufferViewBuffer(rq.cx, obj, &sharedMemory)));
     137          32 :             HandleScriptVal(bufferVal);
     138          32 :             break;
     139             :         }
     140         104 :         else if (JS::IsArrayBufferObject(obj))
     141             :         {
     142          24 :             m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_ARRAY_BUFFER);
     143             : 
     144             : #if BYTE_ORDER != LITTLE_ENDIAN
     145             : #error TODO: need to convert JS ArrayBuffer data to little-endian
     146             : #endif
     147             : 
     148          24 :             u32 length = JS::GetArrayBufferByteLength(obj);
     149          24 :             m_Serializer.NumberU32_Unbounded("buffer length", length);
     150          48 :             JS::AutoCheckCannotGC nogc;
     151             :             bool sharedMemory;
     152          24 :             m_Serializer.RawBytes("buffer data", (const u8*)JS::GetArrayBufferData(obj, &sharedMemory, nogc), length);
     153          24 :             break;
     154             :         }
     155             : 
     156          80 :         else if (JS::IsMapObject(rq.cx, obj, &isMap) && isMap)
     157             :         {
     158          12 :             m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_MAP);
     159          12 :             m_Serializer.NumberU32_Unbounded("map size", JS::MapSize(rq.cx, obj));
     160             : 
     161          24 :             JS::RootedValue keyValueIterator(rq.cx);
     162          12 :             if (!JS::MapEntries(rq.cx, obj, &keyValueIterator))
     163           0 :                 throw PSERROR_Serialize_ScriptError("JS::MapEntries failed");
     164             : 
     165          24 :             JS::ForOfIterator it(rq.cx);
     166          12 :             if (!it.init(keyValueIterator))
     167           0 :                 throw PSERROR_Serialize_ScriptError("JS::ForOfIterator::init failed");
     168             : 
     169          24 :             JS::RootedValue keyValuePair(rq.cx);
     170             :             bool done;
     171             :             while (true)
     172             :             {
     173          30 :                 if (!it.next(&keyValuePair, &done))
     174           0 :                     throw PSERROR_Serialize_ScriptError("JS::ForOfIterator::next failed");
     175             : 
     176          30 :                 if (done)
     177          12 :                     break;
     178             : 
     179          36 :                 JS::RootedObject keyValuePairObj(rq.cx, &keyValuePair.toObject());
     180          36 :                 JS::RootedValue key(rq.cx);
     181          36 :                 JS::RootedValue value(rq.cx);
     182          18 :                 ENSURE(JS_GetElement(rq.cx, keyValuePairObj, 0, &key));
     183          18 :                 ENSURE(JS_GetElement(rq.cx, keyValuePairObj, 1, &value));
     184             : 
     185          18 :                 HandleScriptVal(key);
     186          18 :                 HandleScriptVal(value);
     187          18 :             }
     188          12 :             break;
     189             :         }
     190             : 
     191          68 :         else if (JS::IsSetObject(rq.cx, obj, &isSet) && isSet)
     192             :         {
     193           8 :             m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_SET);
     194           8 :             m_Serializer.NumberU32_Unbounded("set size", JS::SetSize(rq.cx, obj));
     195             : 
     196          16 :             JS::RootedValue valueIterator(rq.cx);
     197           8 :             if (!JS::SetValues(rq.cx, obj, &valueIterator))
     198           0 :                 throw PSERROR_Serialize_ScriptError("JS::SetValues failed");
     199             : 
     200          16 :             JS::ForOfIterator it(rq.cx);
     201           8 :             if (!it.init(valueIterator))
     202           0 :                 throw PSERROR_Serialize_ScriptError("JS::ForOfIterator::init failed");
     203             : 
     204          16 :             JS::RootedValue value(rq.cx);
     205             :             bool done;
     206             :             while (true)
     207             :             {
     208          24 :                 if (!it.next(&value, &done))
     209           0 :                     throw PSERROR_Serialize_ScriptError("JS::ForOfIterator::next failed");
     210             : 
     211          16 :                 if (done)
     212           8 :                     break;
     213             : 
     214           8 :                 HandleScriptVal(value);
     215             :             }
     216           8 :             break;
     217             :         }
     218             : 
     219             :         else
     220             :         {
     221             :             // Find type of object
     222          60 :             const JSClass* jsclass = JS::GetClass(obj);
     223          60 :             if (!jsclass)
     224           0 :                 throw PSERROR_Serialize_ScriptError("JS::GetClass failed");
     225             : 
     226          60 :             JSProtoKey protokey = JSCLASS_CACHED_PROTO_KEY(jsclass);
     227             : 
     228          60 :             if (protokey == JSProto_Object)
     229             :             {
     230             :                 // Object class - check for user-defined prototype
     231          88 :                 JS::RootedObject proto(rq.cx);
     232          48 :                 if (!JS_GetPrototype(rq.cx, obj, &proto))
     233           0 :                     throw PSERROR_Serialize_ScriptError("JS_GetPrototype failed");
     234             : 
     235          88 :                 SPrototypeSerialization protoInfo = GetPrototypeInfo(rq, proto);
     236             : 
     237          48 :                 if (protoInfo.name == "Object")
     238          31 :                     m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT);
     239             :                 else
     240             :                 {
     241          17 :                     m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_PROTOTYPE);
     242          17 :                     m_Serializer.String("proto", wstring_from_utf8(protoInfo.name), 0, 256);
     243             : 
     244             :                     // Does it have custom Serialize function?
     245             :                     // if so, we serialize the data it returns, rather than the object's properties directly
     246          17 :                     if (protoInfo.hasCustomSerialize)
     247             :                     {
     248             :                         // If serialize is null, don't serialize anything more
     249           8 :                         if (!protoInfo.hasNullSerialize)
     250             :                         {
     251          10 :                             JS::RootedValue data(rq.cx);
     252           5 :                             if (!ScriptFunction::Call(rq, val, "Serialize", &data))
     253           0 :                                 throw PSERROR_Serialize_ScriptError("Prototype Serialize function failed");
     254           5 :                             m_Serializer.ScriptVal("data", &data);
     255             :                         }
     256             :                         // Break here to skip the custom object property serialization logic below.
     257           8 :                         break;
     258             :                     }
     259             :                 }
     260             :             }
     261          12 :             else if (protokey == JSProto_Number)
     262             :             {
     263             :                 // Get primitive value
     264             :                 double d;
     265           4 :                 if (!JS::ToNumber(rq.cx, val, &d))
     266           0 :                     throw PSERROR_Serialize_ScriptError("JS::ToNumber failed");
     267             : 
     268             :                 // Refuse to serialize NaN values: their representation can differ, leading to OOS
     269             :                 // and in general this is indicative of an underlying bug rather than desirable behaviour.
     270           4 :                 if (std::isnan(d))
     271             :                 {
     272           0 :                     LOGERROR("Cannot serialize NaN values.");
     273           0 :                     throw PSERROR_Serialize_InvalidScriptValue();
     274             :                 }
     275             : 
     276             :                 // Standard Number object
     277           4 :                 m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_NUMBER);
     278           4 :                 m_Serializer.NumberDouble_Unbounded("value", d);
     279           4 :                 break;
     280             :             }
     281           8 :             else if (protokey == JSProto_String)
     282             :             {
     283             :                 // Standard String object
     284           4 :                 m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_STRING);
     285             :                 // Get primitive value
     286           8 :                 JS::RootedString str(rq.cx, JS::ToString(rq.cx, val));
     287           4 :                 if (!str)
     288           0 :                     throw PSERROR_Serialize_ScriptError("JS_ValueToString failed");
     289           4 :                 ScriptString("value", str);
     290           4 :                 break;
     291             :             }
     292           4 :             else if (protokey == JSProto_Boolean)
     293             :             {
     294             :                 // Standard Boolean object
     295           4 :                 m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_BOOLEAN);
     296             :                 // Get primitive value
     297           4 :                 bool b = JS::ToBoolean(val);
     298           4 :                 m_Serializer.Bool("value", b);
     299           4 :                 break;
     300             :             }
     301             :             else
     302             :             {
     303             :                 // Unrecognized class
     304           0 :                 LOGERROR("Cannot serialise JS objects with unrecognized class '%s'", jsclass->name);
     305           0 :                 throw PSERROR_Serialize_InvalidScriptValue();
     306             :             }
     307             :         }
     308             : 
     309             :         // Find all properties (ordered by insertion time)
     310         156 :         JS::Rooted<JS::IdVector> ida(rq.cx, JS::IdVector(rq.cx));
     311          78 :         if (!JS_Enumerate(rq.cx, obj, &ida))
     312           0 :             throw PSERROR_Serialize_ScriptError("JS_Enumerate failed");
     313             : 
     314          78 :         m_Serializer.NumberU32_Unbounded("num props", (u32)ida.length());
     315             : 
     316         244 :         for (size_t i = 0; i < ida.length(); ++i)
     317             :         {
     318         338 :             JS::RootedId id(rq.cx, ida[i]);
     319             : 
     320         338 :             JS::RootedValue idval(rq.cx);
     321         338 :             JS::RootedValue propval(rq.cx);
     322             : 
     323             :             // Forbid getters, which might delete values and mess things up.
     324         338 :             JS::Rooted<mozilla::Maybe<JS::PropertyDescriptor>> desc(rq.cx);
     325         338 :             JS::RootedObject holder(rq.cx);
     326         169 :             if (!JS_GetPropertyDescriptorById(rq.cx, obj, id, &desc, &holder))
     327           0 :                 throw PSERROR_Serialize_ScriptError("JS_GetPropertyDescriptorById failed");
     328         169 :             if (desc.isSome() && desc->hasGetter())
     329           1 :                 throw PSERROR_Serialize_ScriptError("Cannot serialize property getters");
     330             : 
     331             :             // Get the property name as a string
     332         168 :             if (!JS_IdToValue(rq.cx, id, &idval))
     333           0 :                 throw PSERROR_Serialize_ScriptError("JS_IdToValue failed");
     334         336 :             JS::RootedString idstr(rq.cx, JS::ToString(rq.cx, idval));
     335         168 :             if (!idstr)
     336           0 :                 throw PSERROR_Serialize_ScriptError("JS_ValueToString failed");
     337             : 
     338         168 :             ScriptString("prop name", idstr);
     339             : 
     340         168 :             if (!JS_GetPropertyById(rq.cx, obj, id, &propval))
     341           0 :                 throw PSERROR_Serialize_ScriptError("JS_GetPropertyById failed");
     342             : 
     343         170 :             HandleScriptVal(propval);
     344             :         }
     345             : 
     346          75 :         break;
     347             :     }
     348           1 :     case JSTYPE_FUNCTION:
     349             :     {
     350             :         // We can't serialise functions, but we can at least name the offender (hopefully)
     351           2 :         std::wstring funcname(L"(unnamed)");
     352           2 :         JS::RootedFunction func(rq.cx, JS_ValueToFunction(rq.cx, val));
     353           1 :         if (func)
     354             :         {
     355           2 :             JS::RootedString string(rq.cx, JS_GetFunctionId(func));
     356           1 :             if (string)
     357             :             {
     358           0 :                 if (JS::StringHasLatin1Chars(string))
     359             :                 {
     360             :                     size_t length;
     361           0 :                     JS::AutoCheckCannotGC nogc;
     362           0 :                     const JS::Latin1Char* ch = JS_GetLatin1StringCharsAndLength(rq.cx, nogc, string, &length);
     363           0 :                     if (ch && length > 0)
     364           0 :                         funcname.assign(ch, ch + length);
     365             :                 }
     366             :                 else
     367             :                 {
     368             :                     size_t length;
     369           0 :                     JS::AutoCheckCannotGC nogc;
     370           0 :                     const char16_t* ch = JS_GetTwoByteStringCharsAndLength(rq.cx, nogc, string, &length);
     371           0 :                     if (ch && length > 0)
     372           0 :                         funcname.assign(ch, ch + length);
     373             :                 }
     374             :             }
     375             :         }
     376             : 
     377           1 :         LOGERROR("Cannot serialise JS objects of type 'function': %s", utf8_from_wstring(funcname));
     378           1 :         throw PSERROR_Serialize_InvalidScriptValue();
     379             :     }
     380          37 :     case JSTYPE_STRING:
     381             :     {
     382          37 :         m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_STRING);
     383          74 :         JS::RootedString stringVal(rq.cx, val.toString());
     384          37 :         ScriptString("string", stringVal);
     385          37 :         break;
     386             :     }
     387         114 :     case JSTYPE_NUMBER:
     388             :     {
     389             :         // Refuse to serialize NaN values: their representation can differ, leading to OOS
     390             :         // and in general this is indicative of an underlying bug rather than desirable behaviour.
     391         114 :         if (val == JS::NaNValue())
     392             :         {
     393           1 :             LOGERROR("Cannot serialize NaN values.");
     394           1 :             throw PSERROR_Serialize_InvalidScriptValue();
     395             :         }
     396             : 
     397             :         // To reduce the size of the serialized data, we handle integers and doubles separately.
     398             :         // We can't check for val.isInt32 and val.isDouble directly, because integer numbers are not guaranteed
     399             :         // to be represented as integers. A number like 33 could be stored as integer on the computer of one player
     400             :         // and as double on the other player's computer. That would cause out of sync errors in multiplayer games because
     401             :         // their binary representation and thus the hash would be different.
     402             :         double d;
     403         113 :         d = val.toNumber();
     404             :         i32 integer;
     405             : 
     406         113 :         if (JS_DoubleIsInt32(d, &integer))
     407             :         {
     408          99 :             m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_INT);
     409          99 :             m_Serializer.NumberI32_Unbounded("value", integer);
     410             :         }
     411             :         else
     412             :         {
     413          14 :             m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_DOUBLE);
     414          14 :             m_Serializer.NumberDouble_Unbounded("value", d);
     415             :         }
     416         113 :         break;
     417             :     }
     418           6 :     case JSTYPE_BOOLEAN:
     419             :     {
     420           6 :         m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_BOOLEAN);
     421           6 :         bool b = val.toBoolean();
     422           6 :         m_Serializer.NumberU8_Unbounded("value", b ? 1 : 0);
     423           6 :         break;
     424             :     }
     425           0 :     default:
     426             :     {
     427           0 :         debug_warn(L"Invalid TypeOfValue");
     428           0 :         throw PSERROR_Serialize_InvalidScriptValue();
     429             :     }
     430             :     }
     431         348 : }
     432             : 
     433         209 : void CBinarySerializerScriptImpl::ScriptString(const char* name, JS::HandleString string)
     434             : {
     435         418 :     ScriptRequest rq(m_ScriptInterface);
     436             : 
     437             : #if BYTE_ORDER != LITTLE_ENDIAN
     438             : #error TODO: probably need to convert JS strings to little-endian
     439             : #endif
     440             : 
     441             :     size_t length;
     442         418 :     JS::AutoCheckCannotGC nogc;
     443             :     // Serialize strings directly as UTF-16 or Latin1, to avoid expensive encoding conversions
     444         209 :     bool isLatin1 = JS::StringHasLatin1Chars(string);
     445         209 :     m_Serializer.Bool("isLatin1", isLatin1);
     446         209 :     if (isLatin1)
     447             :     {
     448         203 :         const JS::Latin1Char* chars = JS_GetLatin1StringCharsAndLength(rq.cx, nogc, string, &length);
     449         203 :         if (!chars)
     450           0 :             throw PSERROR_Serialize_ScriptError("JS_GetLatin1StringCharsAndLength failed");
     451         203 :         m_Serializer.NumberU32_Unbounded("string length", (u32)length);
     452         203 :         m_Serializer.RawBytes(name, (const u8*)chars, length);
     453             :     }
     454             :     else
     455             :     {
     456           6 :         const char16_t* chars = JS_GetTwoByteStringCharsAndLength(rq.cx, nogc, string, &length);
     457             : 
     458           6 :         if (!chars)
     459           0 :             throw PSERROR_Serialize_ScriptError("JS_GetTwoByteStringCharsAndLength failed");
     460           6 :         m_Serializer.NumberU32_Unbounded("string length", (u32)length);
     461           6 :         m_Serializer.RawBytes(name, (const u8*)chars, length*2);
     462             :     }
     463         209 : }
     464             : 
     465           0 : void CBinarySerializerScriptImpl::Trace(JSTracer *trc, void *data)
     466             : {
     467           0 :     CBinarySerializerScriptImpl* serializer = static_cast<CBinarySerializerScriptImpl*>(data);
     468           0 :     serializer->m_ScriptBackrefTags.trace(trc);
     469           0 : }
     470             : 
     471         191 : u32 CBinarySerializerScriptImpl::GetScriptBackrefTag(JS::HandleObject obj)
     472             : {
     473             :     // To support non-tree structures (e.g. "var x = []; var y = [x, x];"), we need a way
     474             :     // to indicate multiple references to one object(/array). So every time we serialize a
     475             :     // new object, we give it a new tag; when we serialize it a second time we just refer
     476             :     // to that tag.
     477             : 
     478         382 :     ScriptRequest rq(m_ScriptInterface);
     479             : 
     480         191 :     ObjectTagMap::Ptr ptr = m_ScriptBackrefTags.lookup(JS::Heap<JSObject*>(obj.get()));
     481         191 :     if (!ptr.found())
     482             :     {
     483         174 :         if (!m_ScriptBackrefTags.put(JS::Heap<JSObject*>(obj.get()), ++m_ScriptBackrefsNext))
     484             :         {
     485           0 :             JS::RootedValue objval(rq.cx, JS::ObjectValue(*obj.get()));
     486           0 :             LOGERROR("BinarySerializer: error at insertion. Object was %s", Script::ToString(rq, &objval));
     487           0 :             return 0;
     488             :         }
     489             :         // Return 0 to mean "you have to serialize this object";
     490         174 :         return 0;
     491             :     }
     492             :     else
     493          17 :         return ptr->value();
     494             : }

Generated by: LCOV version 1.13