Line data Source code
1 : /* Copyright (C) 2022 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 "StdDeserializer.h"
21 :
22 : #include "lib/byte_order.h"
23 : #include "lib/utf8.h"
24 : #include "ps/CLogger.h"
25 : #include "ps/CStr.h"
26 : #include "scriptinterface/FunctionWrapper.h"
27 : #include "scriptinterface/Object.h"
28 : #include "scriptinterface/ScriptConversions.h"
29 : #include "scriptinterface/ScriptExtraHeaders.h" // For typed arrays and ArrayBuffer
30 : #include "scriptinterface/ScriptInterface.h"
31 : #include "simulation2/serialization/ISerializer.h"
32 : #include "simulation2/serialization/SerializedScriptTypes.h"
33 : #include "simulation2/serialization/StdSerializer.h" // for DEBUG_SERIALIZER_ANNOTATE
34 :
35 96 : CStdDeserializer::CStdDeserializer(const ScriptInterface& scriptInterface, std::istream& stream) :
36 96 : m_ScriptInterface(scriptInterface), m_Stream(stream)
37 : {
38 96 : JS_AddExtraGCRootsTracer(ScriptRequest(scriptInterface).cx, CStdDeserializer::Trace, this);
39 : // Insert a dummy object in front, as valid tags start at 1.
40 96 : m_ScriptBackrefs.emplace_back(nullptr);
41 96 : }
42 :
43 192 : CStdDeserializer::~CStdDeserializer()
44 : {
45 96 : JS_RemoveExtraGCRootsTracer(ScriptRequest(m_ScriptInterface).cx, CStdDeserializer::Trace, this);
46 96 : }
47 :
48 0 : void CStdDeserializer::Trace(JSTracer *trc, void *data)
49 : {
50 0 : reinterpret_cast<CStdDeserializer*>(data)->TraceMember(trc);
51 0 : }
52 :
53 0 : void CStdDeserializer::TraceMember(JSTracer *trc)
54 : {
55 0 : for (JS::Heap<JSObject*>& backref : m_ScriptBackrefs)
56 0 : JS::TraceEdge(trc, &backref, "StdDeserializer::m_ScriptBackrefs");
57 0 : }
58 :
59 1446 : void CStdDeserializer::Get(const char* name, u8* data, size_t len)
60 : {
61 : #if DEBUG_SERIALIZER_ANNOTATE
62 : std::string strName;
63 : char c = m_Stream.get();
64 : ENSURE(c == '<');
65 : while (1)
66 : {
67 : c = m_Stream.get();
68 : if (c == '>')
69 : break;
70 : else
71 : strName += c;
72 : }
73 : ENSURE(strName == name);
74 : #else
75 : UNUSED2(name);
76 : #endif
77 1446 : m_Stream.read((char*)data, (std::streamsize)len);
78 1446 : if (!m_Stream.good())
79 : {
80 : // hit eof before len, or other errors
81 : // NOTE: older libc++ versions incorrectly set eofbit on the last char; test gcount as a workaround
82 : // see https://llvm.org/bugs/show_bug.cgi?id=9335
83 0 : if (m_Stream.bad() || m_Stream.fail() || (m_Stream.eof() && m_Stream.gcount() != (std::streamsize)len))
84 0 : throw PSERROR_Deserialize_ReadFailed();
85 : }
86 1446 : }
87 :
88 0 : std::istream& CStdDeserializer::GetStream()
89 : {
90 0 : return m_Stream;
91 : }
92 :
93 223 : void CStdDeserializer::RequireBytesInStream(size_t numBytes)
94 : {
95 : // It would be nice to do:
96 : // if (numBytes > (size_t)m_Stream.rdbuf()->in_avail())
97 : // throw PSERROR_Deserialize_OutOfBounds("RequireBytesInStream");
98 : // but that doesn't work (at least on MSVC) since in_avail isn't
99 : // guaranteed to return the actual number of bytes available; see e.g.
100 : // http://social.msdn.microsoft.com/Forums/en/vclanguage/thread/13009a88-933f-4be7-bf3d-150e425e66a6#70ea562d-8605-4742-8851-1bae431ce6ce
101 :
102 : // Instead we'll just verify that it's not an extremely large number:
103 223 : if (numBytes > 64*MiB)
104 0 : throw PSERROR_Deserialize_OutOfBounds("RequireBytesInStream");
105 223 : }
106 :
107 163 : void CStdDeserializer::AddScriptBackref(JS::HandleObject obj)
108 : {
109 163 : m_ScriptBackrefs.push_back(JS::Heap<JSObject*>(obj));
110 163 : }
111 :
112 17 : void CStdDeserializer::GetScriptBackref(size_t tag, JS::MutableHandleObject ret)
113 : {
114 17 : ENSURE(m_ScriptBackrefs.size() > tag);
115 17 : ret.set(m_ScriptBackrefs[tag]);
116 17 : }
117 :
118 : ////////////////////////////////////////////////////////////////
119 :
120 324 : JS::Value CStdDeserializer::ReadScriptVal(const char* UNUSED(name), JS::HandleObject preexistingObject)
121 : {
122 648 : ScriptRequest rq(m_ScriptInterface);
123 :
124 : uint8_t type;
125 324 : NumberU8_Unbounded("type", type);
126 324 : switch (type)
127 : {
128 2 : case SCRIPT_TYPE_VOID:
129 2 : return JS::UndefinedValue();
130 :
131 2 : case SCRIPT_TYPE_NULL:
132 2 : return JS::NullValue();
133 :
134 75 : case SCRIPT_TYPE_ARRAY:
135 : case SCRIPT_TYPE_OBJECT:
136 : case SCRIPT_TYPE_OBJECT_PROTOTYPE:
137 : {
138 150 : JS::RootedObject obj(rq.cx);
139 75 : if (type == SCRIPT_TYPE_ARRAY)
140 : {
141 : u32 length;
142 31 : NumberU32_Unbounded("array length", length);
143 31 : obj.set(JS::NewArrayObject(rq.cx, length));
144 : }
145 44 : else if (type == SCRIPT_TYPE_OBJECT)
146 : {
147 28 : obj.set(JS_NewPlainObject(rq.cx));
148 : }
149 : else // SCRIPT_TYPE_OBJECT_PROTOTYPE
150 : {
151 24 : CStrW prototypeName;
152 16 : String("proto", prototypeName, 0, 256);
153 :
154 : // If an object was passed, no need to construct a new one.
155 16 : if (preexistingObject != nullptr)
156 5 : obj.set(preexistingObject);
157 : else
158 : {
159 22 : JS::RootedValue constructor(rq.cx);
160 11 : if (!ScriptInterface::GetGlobalProperty(rq, prototypeName.ToUTF8(), &constructor))
161 0 : throw PSERROR_Deserialize_ScriptError("Deserializer failed to get constructor object");
162 :
163 22 : JS::RootedObject newObj(rq.cx);
164 11 : if (!JS::Construct(rq.cx, constructor, JS::HandleValueArray::empty(), &newObj))
165 0 : throw PSERROR_Deserialize_ScriptError("Deserializer failed to construct object");
166 11 : obj.set(newObj);
167 : }
168 :
169 24 : JS::RootedObject prototype(rq.cx);
170 16 : JS_GetPrototype(rq.cx, obj, &prototype);
171 24 : SPrototypeSerialization info = GetPrototypeInfo(rq, prototype);
172 :
173 16 : if (preexistingObject != nullptr && prototypeName != wstring_from_utf8(info.name))
174 0 : throw PSERROR_Deserialize_ScriptError("Deserializer failed: incorrect pre-existing object");
175 :
176 :
177 16 : if (info.hasCustomDeserialize)
178 : {
179 8 : AddScriptBackref(obj);
180 :
181 : // If Serialize is null, we'll still call Deserialize but with undefined argument
182 16 : JS::RootedValue data(rq.cx);
183 8 : if (!info.hasNullSerialize)
184 5 : ScriptVal("data", &data);
185 :
186 16 : JS::RootedValue objVal(rq.cx, JS::ObjectValue(*obj));
187 8 : ScriptFunction::CallVoid(rq, objVal, "Deserialize", data);
188 :
189 8 : return JS::ObjectValue(*obj);
190 : }
191 8 : else if (info.hasNullSerialize)
192 : {
193 : // If we serialized null, this means we're pretty much a default-constructed object.
194 : // Nothing to do.
195 0 : AddScriptBackref(obj);
196 0 : return JS::ObjectValue(*obj);
197 : }
198 : }
199 :
200 67 : if (!obj)
201 0 : throw PSERROR_Deserialize_ScriptError("Deserializer failed to create new object");
202 :
203 67 : AddScriptBackref(obj);
204 :
205 : uint32_t numProps;
206 67 : NumberU32_Unbounded("num props", numProps);
207 : bool isLatin1;
208 217 : for (uint32_t i = 0; i < numProps; ++i)
209 : {
210 150 : Bool("isLatin1", isLatin1);
211 150 : if (isLatin1)
212 : {
213 300 : std::vector<JS::Latin1Char> propname;
214 150 : ReadStringLatin1("prop name", propname);
215 300 : JS::RootedValue propval(rq.cx, ReadScriptVal("prop value", nullptr));
216 :
217 300 : std::u16string prp(propname.begin(), propname.end());;
218 : // TODO: Should ask upstream about getting a variant of JS_SetProperty with a length param.
219 150 : if (!JS_SetUCProperty(rq.cx, obj, (const char16_t*)prp.data(), prp.length(), propval))
220 0 : throw PSERROR_Deserialize_ScriptError();
221 : }
222 : else
223 : {
224 0 : std::u16string propname;
225 0 : ReadStringUTF16("prop name", propname);
226 0 : JS::RootedValue propval(rq.cx, ReadScriptVal("prop value", nullptr));
227 :
228 0 : if (!JS_SetUCProperty(rq.cx, obj, (const char16_t*)propname.data(), propname.length(), propval))
229 0 : throw PSERROR_Deserialize_ScriptError();
230 : }
231 : }
232 :
233 67 : return JS::ObjectValue(*obj);
234 : }
235 37 : case SCRIPT_TYPE_STRING:
236 : {
237 74 : JS::RootedString str(rq.cx);
238 37 : ScriptString("string", &str);
239 37 : return JS::StringValue(str);
240 : }
241 83 : case SCRIPT_TYPE_INT:
242 : {
243 : int32_t value;
244 83 : NumberI32("value", value, JSVAL_INT_MIN, JSVAL_INT_MAX);
245 83 : return JS::NumberValue(value);
246 : }
247 14 : case SCRIPT_TYPE_DOUBLE:
248 : {
249 : double value;
250 14 : NumberDouble_Unbounded("value", value);
251 28 : JS::RootedValue rval(rq.cx, JS::NumberValue(value));
252 14 : if (rval.isNull())
253 0 : throw PSERROR_Deserialize_ScriptError("JS_NewNumberValue failed");
254 14 : return rval;
255 : }
256 6 : case SCRIPT_TYPE_BOOLEAN:
257 : {
258 : uint8_t value;
259 6 : NumberU8("value", value, 0, 1);
260 6 : return JS::BooleanValue(value ? true : false);
261 : }
262 17 : case SCRIPT_TYPE_BACKREF:
263 : {
264 : i32 tag;
265 17 : NumberI32("tag", tag, 0, JSVAL_INT_MAX);
266 34 : JS::RootedObject obj(rq.cx);
267 17 : GetScriptBackref(tag, &obj);
268 17 : if (!obj)
269 0 : throw PSERROR_Deserialize_ScriptError("Invalid backref tag");
270 17 : return JS::ObjectValue(*obj);
271 : }
272 4 : case SCRIPT_TYPE_OBJECT_NUMBER:
273 : {
274 : double value;
275 4 : NumberDouble_Unbounded("value", value);
276 8 : JS::RootedValue val(rq.cx, JS::NumberValue(value));
277 :
278 8 : JS::RootedObject ctorobj(rq.cx);
279 4 : if (!JS_GetClassObject(rq.cx, JSProto_Number, &ctorobj))
280 0 : throw PSERROR_Deserialize_ScriptError("JS_GetClassObject failed");
281 8 : JS::RootedValue protoval(rq.cx, JS::ObjectOrNullValue(ctorobj));
282 :
283 8 : JS::RootedObject obj(rq.cx);
284 4 : if (!JS::Construct(rq.cx, protoval, JS::HandleValueArray(val), &obj))
285 0 : throw PSERROR_Deserialize_ScriptError("JS_New failed");
286 4 : AddScriptBackref(obj);
287 4 : return JS::ObjectValue(*obj);
288 : }
289 4 : case SCRIPT_TYPE_OBJECT_STRING:
290 : {
291 8 : JS::RootedString str(rq.cx);
292 4 : ScriptString("value", &str);
293 4 : if (!str)
294 0 : throw PSERROR_Deserialize_ScriptError();
295 8 : JS::RootedValue val(rq.cx, JS::StringValue(str));
296 :
297 8 : JS::RootedObject ctorobj(rq.cx);
298 4 : if (!JS_GetClassObject(rq.cx, JSProto_String, &ctorobj))
299 0 : throw PSERROR_Deserialize_ScriptError("JS_GetClassObject failed");
300 8 : JS::RootedValue protoval(rq.cx, JS::ObjectOrNullValue(ctorobj));
301 :
302 8 : JS::RootedObject obj(rq.cx);
303 4 : if (!JS::Construct(rq.cx, protoval, JS::HandleValueArray(val), &obj))
304 0 : throw PSERROR_Deserialize_ScriptError("JS_New failed");
305 4 : AddScriptBackref(obj);
306 4 : return JS::ObjectValue(*obj);
307 : }
308 4 : case SCRIPT_TYPE_OBJECT_BOOLEAN:
309 : {
310 : bool value;
311 4 : Bool("value", value);
312 8 : JS::RootedValue val(rq.cx, JS::BooleanValue(value));
313 :
314 8 : JS::RootedObject ctorobj(rq.cx);
315 4 : if (!JS_GetClassObject(rq.cx, JSProto_Boolean, &ctorobj))
316 0 : throw PSERROR_Deserialize_ScriptError("JS_GetClassObject failed");
317 8 : JS::RootedValue protoval(rq.cx, JS::ObjectOrNullValue(ctorobj));
318 :
319 8 : JS::RootedObject obj(rq.cx);
320 4 : if (!JS::Construct(rq.cx, protoval, JS::HandleValueArray(val), &obj))
321 0 : throw PSERROR_Deserialize_ScriptError("JS_New failed");
322 4 : AddScriptBackref(obj);
323 4 : return JS::ObjectValue(*obj);
324 : }
325 32 : case SCRIPT_TYPE_TYPED_ARRAY:
326 : {
327 : u8 arrayType;
328 : u32 byteOffset, length;
329 32 : NumberU8_Unbounded("array type", arrayType);
330 32 : NumberU32_Unbounded("byte offset", byteOffset);
331 32 : NumberU32_Unbounded("length", length);
332 :
333 : // To match the serializer order, we reserve the typed array's backref tag here
334 64 : JS::RootedObject arrayObj(rq.cx);
335 32 : AddScriptBackref(arrayObj);
336 :
337 : // Get buffer object
338 64 : JS::RootedValue bufferVal(rq.cx, ReadScriptVal("buffer", nullptr));
339 :
340 32 : if (!bufferVal.isObject())
341 0 : throw PSERROR_Deserialize_ScriptError();
342 :
343 64 : JS::RootedObject bufferObj(rq.cx, &bufferVal.toObject());
344 32 : if (!JS::IsArrayBufferObject(bufferObj))
345 0 : throw PSERROR_Deserialize_ScriptError("js_IsArrayBuffer failed");
346 :
347 32 : switch(arrayType)
348 : {
349 4 : case SCRIPT_TYPED_ARRAY_INT8:
350 4 : arrayObj = JS_NewInt8ArrayWithBuffer(rq.cx, bufferObj, byteOffset, length);
351 4 : break;
352 4 : case SCRIPT_TYPED_ARRAY_UINT8:
353 4 : arrayObj = JS_NewUint8ArrayWithBuffer(rq.cx, bufferObj, byteOffset, length);
354 4 : break;
355 6 : case SCRIPT_TYPED_ARRAY_INT16:
356 6 : arrayObj = JS_NewInt16ArrayWithBuffer(rq.cx, bufferObj, byteOffset, length);
357 6 : break;
358 4 : case SCRIPT_TYPED_ARRAY_UINT16:
359 4 : arrayObj = JS_NewUint16ArrayWithBuffer(rq.cx, bufferObj, byteOffset, length);
360 4 : break;
361 4 : case SCRIPT_TYPED_ARRAY_INT32:
362 4 : arrayObj = JS_NewInt32ArrayWithBuffer(rq.cx, bufferObj, byteOffset, length);
363 4 : break;
364 4 : case SCRIPT_TYPED_ARRAY_UINT32:
365 4 : arrayObj = JS_NewUint32ArrayWithBuffer(rq.cx, bufferObj, byteOffset, length);
366 4 : break;
367 2 : case SCRIPT_TYPED_ARRAY_FLOAT32:
368 2 : arrayObj = JS_NewFloat32ArrayWithBuffer(rq.cx, bufferObj, byteOffset, length);
369 2 : break;
370 2 : case SCRIPT_TYPED_ARRAY_FLOAT64:
371 2 : arrayObj = JS_NewFloat64ArrayWithBuffer(rq.cx, bufferObj, byteOffset, length);
372 2 : break;
373 2 : case SCRIPT_TYPED_ARRAY_UINT8_CLAMPED:
374 2 : arrayObj = JS_NewUint8ClampedArrayWithBuffer(rq.cx, bufferObj, byteOffset, length);
375 2 : break;
376 0 : default:
377 0 : throw PSERROR_Deserialize_ScriptError("Failed to deserialize unrecognized typed array view");
378 : }
379 32 : if (!arrayObj)
380 0 : throw PSERROR_Deserialize_ScriptError("js_CreateTypedArrayWithBuffer failed");
381 :
382 32 : return JS::ObjectValue(*arrayObj);
383 : }
384 24 : case SCRIPT_TYPE_ARRAY_BUFFER:
385 : {
386 : u32 length;
387 24 : NumberU32_Unbounded("buffer length", length);
388 :
389 : #if BYTE_ORDER != LITTLE_ENDIAN
390 : #error TODO: need to convert JS ArrayBuffer data from little-endian
391 : #endif
392 24 : void* contents = malloc(length);
393 24 : ENSURE(contents);
394 24 : RawBytes("buffer data", (u8*)contents, length);
395 48 : JS::RootedObject bufferObj(rq.cx, JS::NewArrayBufferWithContents(rq.cx, length, contents));
396 24 : AddScriptBackref(bufferObj);
397 :
398 24 : return JS::ObjectValue(*bufferObj);
399 : }
400 12 : case SCRIPT_TYPE_OBJECT_MAP:
401 : {
402 24 : JS::RootedObject obj(rq.cx, JS::NewMapObject(rq.cx));
403 12 : AddScriptBackref(obj);
404 :
405 : u32 mapSize;
406 12 : NumberU32_Unbounded("map size", mapSize);
407 :
408 30 : for (u32 i=0; i<mapSize; ++i)
409 : {
410 36 : JS::RootedValue key(rq.cx, ReadScriptVal("map key", nullptr));
411 36 : JS::RootedValue value(rq.cx, ReadScriptVal("map value", nullptr));
412 18 : JS::MapSet(rq.cx, obj, key, value);
413 : }
414 :
415 12 : return JS::ObjectValue(*obj);
416 : }
417 8 : case SCRIPT_TYPE_OBJECT_SET:
418 : {
419 16 : JS::RootedObject obj(rq.cx, JS::NewSetObject(rq.cx));
420 8 : AddScriptBackref(obj);
421 :
422 : u32 setSize;
423 8 : NumberU32_Unbounded("set size", setSize);
424 :
425 16 : for (u32 i=0; i<setSize; ++i)
426 : {
427 16 : JS::RootedValue value(rq.cx, ReadScriptVal("set value", nullptr));
428 8 : JS::SetAdd(rq.cx, obj, value);
429 : }
430 :
431 8 : return JS::ObjectValue(*obj);
432 : }
433 0 : default:
434 0 : throw PSERROR_Deserialize_OutOfBounds();
435 : }
436 : }
437 :
438 185 : void CStdDeserializer::ReadStringLatin1(const char* name, std::vector<JS::Latin1Char>& str)
439 : {
440 : uint32_t len;
441 185 : NumberU32_Unbounded("string length", len);
442 185 : RequireBytesInStream(len);
443 185 : str.resize(len);
444 185 : Get(name, (u8*)str.data(), len);
445 185 : }
446 :
447 6 : void CStdDeserializer::ReadStringUTF16(const char* name, std::u16string& str)
448 : {
449 : uint32_t len;
450 6 : NumberU32_Unbounded("string length", len);
451 6 : RequireBytesInStream(len*2);
452 6 : str.resize(len);
453 6 : Get(name, (u8*)str.data(), len*2);
454 6 : }
455 :
456 41 : void CStdDeserializer::ScriptString(const char* name, JS::MutableHandleString out)
457 : {
458 : #if BYTE_ORDER != LITTLE_ENDIAN
459 : #error TODO: probably need to convert JS strings from little-endian
460 : #endif
461 :
462 82 : ScriptRequest rq(m_ScriptInterface);
463 :
464 : bool isLatin1;
465 41 : Bool("isLatin1", isLatin1);
466 41 : if (isLatin1)
467 : {
468 70 : std::vector<JS::Latin1Char> str;
469 35 : ReadStringLatin1(name, str);
470 :
471 35 : out.set(JS_NewStringCopyN(rq.cx, (const char*)str.data(), str.size()));
472 35 : if (!out)
473 0 : throw PSERROR_Deserialize_ScriptError("JS_NewStringCopyN failed");
474 : }
475 : else
476 : {
477 12 : std::u16string str;
478 6 : ReadStringUTF16(name, str);
479 :
480 6 : out.set(JS_NewUCStringCopyN(rq.cx, (const char16_t*)str.data(), str.length()));
481 6 : if (!out)
482 0 : throw PSERROR_Deserialize_ScriptError("JS_NewUCStringCopyN failed");
483 : }
484 41 : }
485 :
486 93 : void CStdDeserializer::ScriptVal(const char* name, JS::MutableHandleValue out)
487 : {
488 93 : out.set(ReadScriptVal(name, nullptr));
489 93 : }
490 :
491 5 : void CStdDeserializer::ScriptObjectAssign(const char* name, JS::HandleValue objVal)
492 : {
493 10 : ScriptRequest rq(m_ScriptInterface);
494 :
495 5 : if (!objVal.isObject())
496 0 : throw PSERROR_Deserialize_ScriptError();
497 :
498 10 : JS::RootedObject obj(rq.cx, &objVal.toObject());
499 5 : ReadScriptVal(name, obj);
500 5 : }
|