Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
StdDeserializer.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2013 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 "SerializedScriptTypes.h"
23 #include "StdSerializer.h" // for DEBUG_SERIALIZER_ANNOTATE
24 
26 #include "scriptinterface/ScriptExtraHeaders.h" // for typed arrays
27 
28 #include "js/jsapi.h"
29 
30 #include "lib/byte_order.h"
31 
32 static uint32 GetJSArrayType(u8 arrayType)
33 {
34  switch(arrayType)
35  {
37  return js::TypedArray::TYPE_INT8;
39  return js::TypedArray::TYPE_UINT8;
41  return js::TypedArray::TYPE_INT16;
43  return js::TypedArray::TYPE_UINT16;
45  return js::TypedArray::TYPE_INT32;
47  return js::TypedArray::TYPE_UINT32;
49  return js::TypedArray::TYPE_FLOAT32;
51  return js::TypedArray::TYPE_FLOAT64;
53  return js::TypedArray::TYPE_UINT8_CLAMPED;
54  default:
55  throw PSERROR_Deserialize_ScriptError("Failed to deserialize unrecognized typed array view");
56  }
57 }
58 
59 CStdDeserializer::CStdDeserializer(ScriptInterface& scriptInterface, std::istream& stream) :
60  m_ScriptInterface(scriptInterface), m_Stream(stream)
61 {
62 }
63 
65 {
67 }
68 
69 void CStdDeserializer::Get(const char* name, u8* data, size_t len)
70 {
71 #if DEBUG_SERIALIZER_ANNOTATE
72  std::string strName;
73  char c = m_Stream.get();
74  ENSURE(c == '<');
75  while (1)
76  {
77  c = m_Stream.get();
78  if (c == '>')
79  break;
80  else
81  strName += c;
82  }
83  ENSURE(strName == name);
84 #else
85  UNUSED2(name);
86 #endif
87  m_Stream.read((char*)data, (std::streamsize)len);
88  if (!m_Stream.good()) // hit eof before len, or other errors
90 }
91 
93 {
94  return m_Stream;
95 }
96 
98 {
99  // It would be nice to do:
100 // if (numBytes > (size_t)m_Stream.rdbuf()->in_avail())
101 // throw PSERROR_Deserialize_OutOfBounds("RequireBytesInStream");
102  // but that doesn't work (at least on MSVC) since in_avail isn't
103  // guaranteed to return the actual number of bytes available; see e.g.
104  // http://social.msdn.microsoft.com/Forums/en/vclanguage/thread/13009a88-933f-4be7-bf3d-150e425e66a6#70ea562d-8605-4742-8851-1bae431ce6ce
105 
106  // Instead we'll just verify that it's not an extremely large number:
107  if (numBytes > 64*MiB)
108  throw PSERROR_Deserialize_OutOfBounds("RequireBytesInStream");
109 }
110 
112 {
113  std::pair<std::map<u32, JSObject*>::iterator, bool> it = m_ScriptBackrefs.insert(std::make_pair((u32)m_ScriptBackrefs.size()+1, obj));
114  ENSURE(it.second);
115  if (!JS_AddObjectRoot(m_ScriptInterface.GetContext(), &it.first->second))
116  throw PSERROR_Deserialize_ScriptError("JS_AddRoot failed");
117 }
118 
120 {
121  std::map<u32, JSObject*>::const_iterator it = m_ScriptBackrefs.find(tag);
122  if (it == m_ScriptBackrefs.end())
123  return NULL;
124  return it->second;
125 }
126 
128 {
129  AddScriptBackref(NULL);
130  return m_ScriptBackrefs.size();
131 }
132 
134 {
135  std::pair<std::map<u32, JSObject*>::iterator, bool> it = m_ScriptBackrefs.insert(std::make_pair(tag, obj));
136  ENSURE(!it.second);
137 }
138 
140 {
141  std::map<u32, JSObject*>::iterator it = m_ScriptBackrefs.begin();
142  for (; it != m_ScriptBackrefs.end(); ++it)
143  {
144  if (!JS_RemoveObjectRoot(m_ScriptInterface.GetContext(), &it->second))
145  throw PSERROR_Deserialize_ScriptError("JS_RemoveRoot failed");
146  }
147  m_ScriptBackrefs.clear();
148 }
149 
150 ////////////////////////////////////////////////////////////////
151 
152 jsval CStdDeserializer::ReadScriptVal(const char* UNUSED(name), JSObject* appendParent)
153 {
154  JSContext* cx = m_ScriptInterface.GetContext();
155 
156  uint8_t type;
157  NumberU8_Unbounded("type", type);
158  switch (type)
159  {
160  case SCRIPT_TYPE_VOID:
161  return JSVAL_VOID;
162 
163  case SCRIPT_TYPE_NULL:
164  return JSVAL_NULL;
165 
166  case SCRIPT_TYPE_ARRAY:
167  case SCRIPT_TYPE_OBJECT:
169  {
170  JSObject* obj;
171  if (appendParent)
172  {
173  obj = appendParent;
174  }
175  else if (type == SCRIPT_TYPE_ARRAY)
176  {
177  u32 length;
178  NumberU32_Unbounded("array length", length);
179  obj = JS_NewArrayObject(cx, length, NULL);
180  }
181  else if (type == SCRIPT_TYPE_OBJECT)
182  {
183  obj = JS_NewObject(cx, NULL, NULL, NULL);
184  }
185  else // SCRIPT_TYPE_OBJECT_PROTOTYPE
186  {
187  std::wstring prototypeName;
188  String("proto name", prototypeName, 0, 256);
189 
190  // Get constructor object
191  JSObject* proto = GetSerializablePrototype(prototypeName);
192  if (!proto)
193  throw PSERROR_Deserialize_ScriptError("Failed to find serializable prototype for object");
194 
195  JSObject* parent = JS_GetParent(cx, proto);
196  if (!proto || !parent)
198 
199  obj = JS_NewObject(cx, NULL, proto, parent);
200  if (!obj)
201  throw PSERROR_Deserialize_ScriptError("JS_NewObject failed");
202  CScriptValRooted objRoot(cx, OBJECT_TO_JSVAL(obj));
203 
204  // Does it have custom Deserialize function?
205  // if so, we let it handle the deserialized data, rather than adding properties directly
206  JSBool hasCustomDeserialize, hasCustomSerialize;
207  if (!JS_HasProperty(cx, obj, "Serialize", &hasCustomSerialize) || !JS_HasProperty(cx, obj, "Deserialize", &hasCustomDeserialize))
208  throw PSERROR_Serialize_ScriptError("JS_HasProperty failed");
209 
210  if (hasCustomDeserialize)
211  {
212  jsval serialize;
213  if (!JS_LookupProperty(cx, obj, "Serialize", &serialize))
214  throw PSERROR_Serialize_ScriptError("JS_LookupProperty failed");
215  bool hasNullSerialize = hasCustomSerialize && JSVAL_IS_NULL(serialize);
216 
217  // If Serialize is null, we'll still call Deserialize but with undefined argument
218  CScriptValRooted data;
219  if (!hasNullSerialize)
220  ScriptVal("data", data);
221 
222  m_ScriptInterface.CallFunctionVoid(OBJECT_TO_JSVAL(obj), "Deserialize", data);
223 
224  AddScriptBackref(obj);
225 
226  return OBJECT_TO_JSVAL(obj);
227  }
228  }
229 
230  if (!obj)
231  throw PSERROR_Deserialize_ScriptError("Deserializer failed to create new object");
232  CScriptValRooted objRoot(cx, OBJECT_TO_JSVAL(obj));
233 
234  AddScriptBackref(obj);
235 
236  uint32_t numProps;
237  NumberU32_Unbounded("num props", numProps);
238 
239  for (uint32_t i = 0; i < numProps; ++i)
240  {
241  utf16string propname;
242  ReadStringUTF16("prop name", propname);
243 
244  jsval propval = ReadScriptVal("prop value", NULL);
245  CScriptValRooted propvalRoot(cx, propval);
246 
247  if (!JS_SetUCProperty(cx, obj, (const jschar*)propname.data(), propname.length(), &propval))
249  }
250 
251  return OBJECT_TO_JSVAL(obj);
252  }
253  case SCRIPT_TYPE_STRING:
254  {
255  JSString* str;
256  ScriptString("string", str);
257  return STRING_TO_JSVAL(str);
258  }
259  case SCRIPT_TYPE_INT:
260  {
261  int32_t value;
262  NumberI32("value", value, JSVAL_INT_MIN, JSVAL_INT_MAX);
263  return INT_TO_JSVAL(value);
264  }
265  case SCRIPT_TYPE_DOUBLE:
266  {
267  double value;
268  NumberDouble_Unbounded("value", value);
269  jsval rval;
270  if (!JS_NewNumberValue(cx, value, &rval))
271  throw PSERROR_Deserialize_ScriptError("JS_NewNumberValue failed");
272  return rval;
273  }
274  case SCRIPT_TYPE_BOOLEAN:
275  {
276  uint8_t value;
277  NumberU8("value", value, 0, 1);
278  return BOOLEAN_TO_JSVAL(value ? JS_TRUE : JS_FALSE);
279  }
280  case SCRIPT_TYPE_BACKREF:
281  {
282  u32 tag;
283  NumberU32_Unbounded("tag", tag);
284  JSObject* obj = GetScriptBackref(tag);
285  if (!obj)
286  throw PSERROR_Deserialize_ScriptError("Invalid backref tag");
287  return OBJECT_TO_JSVAL(obj);
288  }
290  {
291  double value;
292  NumberDouble_Unbounded("value", value);
293  jsval val;
294  if (!JS_NewNumberValue(cx, value, &val))
296  CScriptValRooted objRoot(cx, val);
297 
298  JSObject* ctorobj;
299  if (!JS_GetClassObject(cx, JS_GetGlobalObject(cx), JSProto_Number, &ctorobj))
300  throw PSERROR_Deserialize_ScriptError("JS_GetClassObject failed");
301 
302  JSObject* obj = JS_New(cx, ctorobj, 1, &val);
303  if (!obj)
304  throw PSERROR_Deserialize_ScriptError("JS_New failed");
305  AddScriptBackref(obj);
306  return OBJECT_TO_JSVAL(obj);
307  }
309  {
310  JSString* str;
311  ScriptString("value", str);
312  if (!str)
314  jsval val = STRING_TO_JSVAL(str);
315  CScriptValRooted valRoot(cx, val);
316 
317  JSObject* ctorobj;
318  if (!JS_GetClassObject(cx, JS_GetGlobalObject(cx), JSProto_String, &ctorobj))
319  throw PSERROR_Deserialize_ScriptError("JS_GetClassObject failed");
320 
321  JSObject* obj = JS_New(cx, ctorobj, 1, &val);
322  if (!obj)
323  throw PSERROR_Deserialize_ScriptError("JS_New failed");
324  AddScriptBackref(obj);
325  return OBJECT_TO_JSVAL(obj);
326  }
328  {
329  bool value;
330  Bool("value", value);
331  jsval val = BOOLEAN_TO_JSVAL(value ? JS_TRUE : JS_FALSE);
332  CScriptValRooted objRoot(cx, val);
333 
334  JSObject* ctorobj;
335  if (!JS_GetClassObject(cx, JS_GetGlobalObject(cx), JSProto_Boolean, &ctorobj))
336  throw PSERROR_Deserialize_ScriptError("JS_GetClassObject failed");
337 
338  JSObject* obj = JS_New(cx, ctorobj, 1, &val);
339  if (!obj)
340  throw PSERROR_Deserialize_ScriptError("JS_New failed");
341  AddScriptBackref(obj);
342  return OBJECT_TO_JSVAL(obj);
343  }
345  {
346  u8 arrayType;
347  u32 byteOffset, length;
348  NumberU8_Unbounded("array type", arrayType);
349  NumberU32_Unbounded("byte offset", byteOffset);
350  NumberU32_Unbounded("length", length);
351 
352  // To match the serializer order, we reserve the typed array's backref tag here
353  u32 arrayTag = ReserveScriptBackref();
354 
355  // Get buffer object
356  jsval bufferVal = ReadScriptVal("buffer", NULL);
357  CScriptValRooted bufferValRoot(cx, bufferVal);
358 
359  if (!JSVAL_IS_OBJECT(bufferVal))
361 
362  JSObject* bufferObj = JSVAL_TO_OBJECT(bufferVal);
363  if (!js_IsArrayBuffer(bufferObj))
364  throw PSERROR_Deserialize_ScriptError("js_IsArrayBuffer failed");
365 
366  JSObject* arrayObj = js_CreateTypedArrayWithBuffer(cx, GetJSArrayType(arrayType), bufferObj, byteOffset, length);
367  if (!arrayObj)
368  throw PSERROR_Deserialize_ScriptError("js_CreateTypedArrayWithBuffer failed");
369 
370  SetReservedScriptBackref(arrayTag, arrayObj);
371 
372  return OBJECT_TO_JSVAL(arrayObj);
373  }
375  {
376  u32 length;
377  NumberU32_Unbounded("buffer length", length);
378 
379  u8* bufferData = new u8[length];
380  RawBytes("buffer data", bufferData, length);
381 
382 #if BYTE_ORDER != LITTLE_ENDIAN
383 #error TODO: need to convert JS ArrayBuffer data from little-endian
384 #endif
385 
386  JSObject* bufferObj = js_CreateArrayBuffer(cx, length);
387  if (!bufferObj)
388  throw PSERROR_Deserialize_ScriptError("js_CreateArrayBuffer failed");
389 
390  AddScriptBackref(bufferObj);
391 
392  js::ArrayBuffer* buffer = js::ArrayBuffer::fromJSObject(bufferObj);
393  memcpy(buffer->data, bufferData, length);
394  delete[] bufferData;
395 
396  return OBJECT_TO_JSVAL(bufferObj);
397  }
398  default:
400  }
401 }
402 
403 void CStdDeserializer::ReadStringUTF16(const char* name, utf16string& str)
404 {
405  uint32_t len;
406  NumberU32_Unbounded("string length", len);
407  RequireBytesInStream(len*2);
408  str.resize(len);
409  Get(name, (u8*)str.data(), len*2);
410 }
411 
412 void CStdDeserializer::ScriptString(const char* name, JSString*& out)
413 {
414  utf16string str;
415  ReadStringUTF16(name, str);
416 
417 #if BYTE_ORDER != LITTLE_ENDIAN
418 #error TODO: probably need to convert JS strings from little-endian
419 #endif
420 
421  out = JS_NewUCStringCopyN(m_ScriptInterface.GetContext(), (const jschar*)str.data(), str.length());
422  if (!out)
423  throw PSERROR_Deserialize_ScriptError("JS_NewUCStringCopyN failed");
424 }
425 
426 void CStdDeserializer::ScriptVal(const char* name, jsval& out)
427 {
428  out = ReadScriptVal(name, NULL);
429 }
430 
432 {
433  out = ReadScriptVal(name, NULL);
434 }
435 
437 {
439 }
440 
441 void CStdDeserializer::ScriptObjectAppend(const char* name, jsval& obj)
442 {
443  if (!JSVAL_IS_OBJECT(obj))
445 
446  ReadScriptVal(name, JSVAL_TO_OBJECT(obj));
447 }
448 
449 void CStdDeserializer::SetSerializablePrototypes(std::map<std::wstring, JSObject*>& prototypes)
450 {
451  m_SerializablePrototypes = prototypes;
452 }
453 
454 bool CStdDeserializer::IsSerializablePrototype(const std::wstring& name)
455 {
456  return m_SerializablePrototypes.find(name) != m_SerializablePrototypes.end();
457 }
458 
459 JSObject* CStdDeserializer::GetSerializablePrototype(const std::wstring& name)
460 {
461  std::map<std::wstring, JSObject*>::iterator it = m_SerializablePrototypes.find(name);
462  if (it != m_SerializablePrototypes.end())
463  return it->second;
464  else
465  return NULL;
466 }
#define u8
Definition: types.h:39
CStdDeserializer(ScriptInterface &scriptInterface, std::istream &stream)
virtual void Bool(const char *name, bool &out)
#define UNUSED(param)
mark a function parameter as unused and avoid the corresponding compiler warning. ...
virtual void NumberDouble_Unbounded(const char *name, double &out)
ScriptInterface & m_ScriptInterface
static void out(const wchar_t *fmt,...)
Definition: wdbg_sym.cpp:419
bool CallFunctionVoid(jsval val, const char *name)
Call the named property on the given object, with void return type and 0 arguments.
virtual JSObject * GetScriptBackref(u32 tag)
virtual void NumberU32_Unbounded(const char *name, uint32_t &out)
A trivial wrapper around a jsval.
Definition: ScriptVal.h:29
virtual std::istream & GetStream()
Returns a stream which can be used to deserialize data directly.
JSObject * GetSerializablePrototype(const std::wstring &name)
virtual void NumberU8_Unbounded(const char *name, uint8_t &out)
virtual void ScriptVal(const char *name, jsval &out)
Deserialize a jsval, replacing &#39;out&#39;.
virtual u32 ReserveScriptBackref()
virtual void RequireBytesInStream(size_t numBytes)
Throws an exception if the stream definitely cannot provide the required number of bytes...
static uint32 GetJSArrayType(u8 arrayType)
virtual void Get(const char *name, u8 *data, size_t len)
#define ENSURE(expr)
ensure the expression &lt;expr&gt; evaluates to non-zero.
Definition: debug.h:282
#define UNUSED2(param)
mark a function local variable or parameter as unused and avoid the corresponding compiler warning...
virtual void RawBytes(const char *name, u8 *data, size_t len)
bool IsSerializablePrototype(const std::wstring &name)
unsigned char uint8_t
Definition: wposix_types.h:51
static const size_t MiB
Definition: alignment.h:72
std::map< u32, JSObject * > m_ScriptBackrefs
virtual void SetReservedScriptBackref(u32 tag, JSObject *obj)
std::basic_string< utf16_t, utf16_traits > utf16string
Definition: utf16string.h:109
std::istream & m_Stream
virtual void NumberU8(const char *name, uint8_t &out, uint8_t lower, uint8_t upper)
#define u32
Definition: types.h:41
virtual void ScriptObjectAppend(const char *name, jsval &obj)
Deserialize an object jsval, appending properties to object &#39;obj&#39;.
std::map< std::wstring, JSObject * > m_SerializablePrototypes
virtual void String(const char *name, std::wstring &out, uint32_t minlength, uint32_t maxlength)
void ReadStringUTF16(const char *name, utf16string &str)
unsigned int uint32_t
Definition: wposix_types.h:53
Abstraction around a SpiderMonkey JSContext.
virtual ~CStdDeserializer()
jsval ReadScriptVal(const char *name, JSObject *appendParent)
virtual void SetSerializablePrototypes(std::map< std::wstring, JSObject * > &prototypes)
virtual void ScriptString(const char *name, JSString *&out)
Deserialize a JSString.
JSContext * GetContext() const
virtual void NumberI32(const char *name, int32_t &out, int32_t lower, int32_t upper)
virtual void AddScriptBackref(JSObject *obj)