1// Copyright 2014 PDFium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com 6 7#include "../../include/javascript/IJavaScript.h" 8#include "../../include/javascript/JS_Context.h" 9#include "../../include/javascript/JS_Define.h" 10#include "../../include/javascript/JS_EventHandler.h" 11#include "../../include/javascript/JS_GlobalData.h" 12#include "../../include/javascript/JS_Object.h" 13#include "../../include/javascript/JS_Value.h" 14#include "../../include/javascript/JavaScript.h" 15#include "../../include/javascript/global.h" 16#include "../../include/javascript/resource.h" 17 18/* ---------------------------- global ---------------------------- */ 19 20// Helper class for compile-time calculation of hash values in order to 21// avoid having global object initializers. 22template <unsigned ACC, wchar_t... Ns> 23struct CHash; 24 25// Only needed to hash single-character strings. 26template <wchar_t N> 27struct CHash<N> { 28 static const unsigned value = N; 29}; 30 31template <unsigned ACC, wchar_t N> 32struct CHash<ACC, N> { 33 static const unsigned value = (ACC * 1313LLU + N) & 0xFFFFFFFF; 34}; 35 36template <unsigned ACC, wchar_t N, wchar_t... Ns> 37struct CHash<ACC, N, Ns...> { 38 static const unsigned value = CHash<CHash<ACC, N>::value, Ns...>::value; 39}; 40 41extern const unsigned int JSCONST_nStringHash = 42 CHash<'s','t','r','i','n','g'>::value; 43extern const unsigned int JSCONST_nNumberHash = 44 CHash<'n','u','m','b','e','r'>::value; 45extern const unsigned int JSCONST_nBoolHash = 46 CHash<'b','o','o','l','e','a','n'>::value; 47extern const unsigned int JSCONST_nDateHash = 48 CHash<'d','a','t','e'>::value; 49extern const unsigned int JSCONST_nObjectHash = 50 CHash<'o','b','j','e','c','t'>::value; 51extern const unsigned int JSCONST_nFXobjHash = 52 CHash<'f','x','o','b','j'>::value; 53extern const unsigned int JSCONST_nNullHash = 54 CHash<'n','u','l','l'>::value; 55extern const unsigned int JSCONST_nUndefHash = 56 CHash<'u','n','d','e','f','i','n','e','d'>::value; 57 58#ifdef _DEBUG 59class HashVerify 60{ 61public: 62 HashVerify(); 63} g_hashVerify; 64 65HashVerify::HashVerify() 66{ 67 ASSERT(JSCONST_nStringHash == 68 JS_CalcHash(VALUE_NAME_STRING,wcslen(VALUE_NAME_STRING))); 69 ASSERT(JSCONST_nNumberHash == 70 JS_CalcHash(VALUE_NAME_NUMBER,wcslen(VALUE_NAME_NUMBER))); 71 ASSERT(JSCONST_nBoolHash == 72 JS_CalcHash(VALUE_NAME_BOOLEAN,wcslen(VALUE_NAME_BOOLEAN))); 73 ASSERT(JSCONST_nDateHash == 74 JS_CalcHash(VALUE_NAME_DATE,wcslen(VALUE_NAME_DATE))); 75 ASSERT(JSCONST_nObjectHash == 76 JS_CalcHash(VALUE_NAME_OBJECT,wcslen(VALUE_NAME_OBJECT))); 77 ASSERT(JSCONST_nFXobjHash == 78 JS_CalcHash(VALUE_NAME_FXOBJ,wcslen(VALUE_NAME_FXOBJ))); 79 ASSERT(JSCONST_nNullHash == 80 JS_CalcHash(VALUE_NAME_NULL,wcslen(VALUE_NAME_NULL))); 81 ASSERT(JSCONST_nUndefHash == 82 JS_CalcHash(VALUE_NAME_UNDEFINED,wcslen(VALUE_NAME_UNDEFINED))); 83} 84#endif 85 86 87BEGIN_JS_STATIC_CONST(CJS_Global) 88END_JS_STATIC_CONST() 89 90BEGIN_JS_STATIC_PROP(CJS_Global) 91END_JS_STATIC_PROP() 92 93BEGIN_JS_STATIC_METHOD(CJS_Global) 94 JS_STATIC_METHOD_ENTRY(setPersistent) 95END_JS_STATIC_METHOD() 96 97IMPLEMENT_SPECIAL_JS_CLASS(CJS_Global, global_alternate, global); 98 99FX_BOOL CJS_Global::InitInstance(IFXJS_Context* cc) 100{ 101 CJS_Context* pContext = (CJS_Context*)cc; 102 ASSERT(pContext != NULL); 103 104 global_alternate* pGlobal = (global_alternate*)GetEmbedObject(); 105 ASSERT(pGlobal != NULL); 106 107 pGlobal->Initial(pContext->GetReaderApp()); 108 109 return TRUE; 110}; 111 112global_alternate::global_alternate(CJS_Object* pJSObject) 113 : CJS_EmbedObj(pJSObject), 114 m_pApp(NULL) 115{ 116} 117 118global_alternate::~global_alternate(void) 119{ 120 ASSERT(m_pApp != NULL); 121 122// CommitGlobalPersisitentVariables(); 123 DestroyGlobalPersisitentVariables(); 124 125 CJS_RuntimeFactory* pFactory = m_pApp->m_pJSRuntimeFactory; 126 ASSERT(pFactory); 127 128 pFactory->ReleaseGlobalData(); 129} 130 131void global_alternate::Initial(CPDFDoc_Environment* pApp) 132{ 133 m_pApp = pApp; 134 135 CJS_RuntimeFactory* pFactory = pApp->m_pJSRuntimeFactory; 136 ASSERT(pFactory); 137 m_pGlobalData = pFactory->NewGlobalData(pApp); 138 UpdateGlobalPersistentVariables(); 139} 140 141FX_BOOL global_alternate::QueryProperty(FX_LPCWSTR propname) 142{ 143 return CFX_WideString(propname) != L"setPersistent"; 144} 145 146FX_BOOL global_alternate::DelProperty(IFXJS_Context* cc, FX_LPCWSTR propname, CFX_WideString& sError) 147{ 148 js_global_data* pData = NULL; 149 CFX_ByteString sPropName = CFX_ByteString::FromUnicode(propname); 150 151 if (m_mapGlobal.Lookup(sPropName, (FX_LPVOID&)pData)) 152 { 153 pData->bDeleted = TRUE; 154 return TRUE; 155 } 156 157 return FALSE; 158} 159 160FX_BOOL global_alternate::DoProperty(IFXJS_Context* cc, FX_LPCWSTR propname, CJS_PropValue& vp, CFX_WideString& sError) 161{ 162 if (vp.IsSetting()) 163 { 164 CFX_ByteString sPropName = CFX_ByteString::FromUnicode(propname); 165 switch (vp.GetType()) 166 { 167 case VT_number: 168 { 169 double dData; 170 vp >> dData; 171 return SetGlobalVariables(sPropName, JS_GLOBALDATA_TYPE_NUMBER, dData, false, "", v8::Local<v8::Object>(), FALSE); 172 } 173 case VT_boolean: 174 { 175 bool bData; 176 vp >> bData; 177 return SetGlobalVariables(sPropName, JS_GLOBALDATA_TYPE_BOOLEAN, 0, bData, "", v8::Local<v8::Object>(), FALSE); 178 } 179 case VT_string: 180 { 181 CFX_ByteString sData; 182 vp >> sData; 183 return SetGlobalVariables(sPropName, JS_GLOBALDATA_TYPE_STRING, 0, false, sData, v8::Local<v8::Object>(), FALSE); 184 } 185 case VT_object: 186 { 187 JSObject pData; 188 vp >> pData; 189 return SetGlobalVariables(sPropName, JS_GLOBALDATA_TYPE_OBJECT, 0, false, "", pData, FALSE); 190 } 191 case VT_null: 192 { 193 return SetGlobalVariables(sPropName, JS_GLOBALDATA_TYPE_NULL, 0, false, "", v8::Local<v8::Object>(), FALSE); 194 } 195 case VT_undefined: 196 { 197 DelProperty(cc, propname, sError); 198 return TRUE; 199 } 200 default: 201 return FALSE; 202 } 203 } 204 else 205 { 206 js_global_data* pData = NULL; 207 CFX_ByteString sPropName = CFX_ByteString::FromUnicode(propname); 208 209 if (m_mapGlobal.Lookup(sPropName, (FX_LPVOID&)pData)) 210 { 211 if (pData) 212 { 213 if (!pData->bDeleted) 214 { 215 switch (pData->nType) 216 { 217 case JS_GLOBALDATA_TYPE_NUMBER: 218 vp << pData->dData; 219 break; 220 case JS_GLOBALDATA_TYPE_BOOLEAN: 221 vp << pData->bData; 222 break; 223 case JS_GLOBALDATA_TYPE_STRING: 224 vp << pData->sData; 225 break; 226 case JS_GLOBALDATA_TYPE_OBJECT: 227 { 228 v8::Local<v8::Object> obj = v8::Local<v8::Object>::New(vp.GetIsolate(),pData->pData); 229 vp << obj; 230 break; 231 } 232 case JS_GLOBALDATA_TYPE_NULL: 233 vp.SetNull(); 234 break; 235 default: 236 return FALSE; 237 } 238 return TRUE; 239 } 240 else 241 { 242 return TRUE; 243 } 244 } 245 else 246 { 247 vp.SetNull(); 248 return TRUE; 249 } 250 } 251 else 252 { 253 vp.SetNull(); 254 return TRUE; 255 } 256 } 257 258 return FALSE; 259} 260 261FX_BOOL global_alternate::setPersistent(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError) 262{ 263 CJS_Context* pContext = static_cast<CJS_Context*>(cc); 264 if (params.size() != 2) 265 { 266 sError = JSGetStringFromID(pContext, IDS_STRING_JSPARAMERROR); 267 return FALSE; 268 } 269 270 CFX_ByteString sName = params[0].ToCFXByteString(); 271 272 js_global_data* pData = NULL; 273 if (m_mapGlobal.Lookup(sName, (FX_LPVOID&)pData)) 274 { 275 if (pData && !pData->bDeleted) 276 { 277 pData->bPersistent = params[1].ToBool(); 278 return TRUE; 279 } 280 } 281 282 sError = JSGetStringFromID(pContext, IDS_STRING_JSNOGLOBAL); 283 return FALSE; 284} 285 286void global_alternate::UpdateGlobalPersistentVariables() 287{ 288 ASSERT(m_pGlobalData != NULL); 289 290 for (int i=0,sz=m_pGlobalData->GetSize(); i<sz; i++) 291 { 292 CJS_GlobalData_Element* pData = m_pGlobalData->GetAt(i); 293 ASSERT(pData != NULL); 294 295 switch (pData->data.nType) 296 { 297 case JS_GLOBALDATA_TYPE_NUMBER: 298 this->SetGlobalVariables(pData->data.sKey, JS_GLOBALDATA_TYPE_NUMBER, pData->data.dData, false, "", v8::Local<v8::Object>(), pData->bPersistent == 1); 299 JS_PutObjectNumber(NULL,(JSFXObject)(*m_pJSObject), 300 pData->data.sKey.UTF8Decode().c_str(), pData->data.dData); 301 break; 302 case JS_GLOBALDATA_TYPE_BOOLEAN: 303 this->SetGlobalVariables(pData->data.sKey, JS_GLOBALDATA_TYPE_BOOLEAN, 0, (bool)(pData->data.bData == 1), "", v8::Local<v8::Object>(), pData->bPersistent == 1); 304 JS_PutObjectBoolean(NULL,(JSFXObject)(*m_pJSObject), 305 pData->data.sKey.UTF8Decode().c_str(), (bool)(pData->data.bData == 1)); 306 break; 307 case JS_GLOBALDATA_TYPE_STRING: 308 this->SetGlobalVariables(pData->data.sKey, JS_GLOBALDATA_TYPE_STRING, 0, false, pData->data.sData, v8::Local<v8::Object>(), pData->bPersistent == 1); 309 JS_PutObjectString(NULL, (JSFXObject)(*m_pJSObject), 310 pData->data.sKey.UTF8Decode().c_str(), 311 pData->data.sData.UTF8Decode().c_str()); 312 break; 313 case JS_GLOBALDATA_TYPE_OBJECT: 314 { 315 IJS_Runtime* pRuntime = JS_GetRuntime((JSFXObject)(*m_pJSObject)); 316 v8::Local<v8::Object> pObj = JS_NewFxDynamicObj(pRuntime, NULL, -1); 317 318 PutObjectProperty(pObj, &pData->data); 319 320 this->SetGlobalVariables(pData->data.sKey, JS_GLOBALDATA_TYPE_OBJECT, 0, false, "", 321 (JSObject)pObj, pData->bPersistent == 1); 322 JS_PutObjectObject(NULL,(JSFXObject)(*m_pJSObject), 323 pData->data.sKey.UTF8Decode().c_str(), (JSObject)pObj); 324 } 325 break; 326 case JS_GLOBALDATA_TYPE_NULL: 327 this->SetGlobalVariables(pData->data.sKey, JS_GLOBALDATA_TYPE_NULL, 0, false, "", v8::Local<v8::Object>(), pData->bPersistent == 1); 328 JS_PutObjectNull(NULL,(JSFXObject)(*m_pJSObject), 329 pData->data.sKey.UTF8Decode().c_str()); 330 break; 331 } 332 } 333} 334 335void global_alternate::CommitGlobalPersisitentVariables() 336{ 337 ASSERT(m_pGlobalData != NULL); 338 339 FX_POSITION pos = m_mapGlobal.GetStartPosition(); 340 while (pos) 341 { 342 CFX_ByteString name; 343 js_global_data* pData = NULL; 344 m_mapGlobal.GetNextAssoc(pos, name, (FX_LPVOID&)pData); 345 346 if (pData) 347 { 348 if (pData->bDeleted) 349 { 350 m_pGlobalData->DeleteGlobalVariable(name); 351 } 352 else 353 { 354 switch (pData->nType) 355 { 356 case JS_GLOBALDATA_TYPE_NUMBER: 357 m_pGlobalData->SetGlobalVariableNumber(name, pData->dData); 358 m_pGlobalData->SetGlobalVariablePersistent(name, pData->bPersistent); 359 break; 360 case JS_GLOBALDATA_TYPE_BOOLEAN: 361 m_pGlobalData->SetGlobalVariableBoolean(name, pData->bData); 362 m_pGlobalData->SetGlobalVariablePersistent(name, pData->bPersistent); 363 break; 364 case JS_GLOBALDATA_TYPE_STRING: 365 m_pGlobalData->SetGlobalVariableString(name, pData->sData); 366 m_pGlobalData->SetGlobalVariablePersistent(name, pData->bPersistent); 367 break; 368 case JS_GLOBALDATA_TYPE_OBJECT: 369 //if (pData->pData) 370 { 371 CJS_GlobalVariableArray array; 372 v8::Local<v8::Object> obj = v8::Local<v8::Object>::New(GetJSObject()->GetIsolate(),pData->pData); 373 ObjectToArray(obj, array); 374 m_pGlobalData->SetGlobalVariableObject(name, array); 375 m_pGlobalData->SetGlobalVariablePersistent(name, pData->bPersistent); 376 } 377 break; 378 case JS_GLOBALDATA_TYPE_NULL: 379 m_pGlobalData->SetGlobalVariableNull(name); 380 m_pGlobalData->SetGlobalVariablePersistent(name, pData->bPersistent); 381 break; 382 } 383 } 384 } 385 } 386} 387 388void global_alternate::ObjectToArray(v8::Local<v8::Object> pObj, CJS_GlobalVariableArray& array) 389{ 390 v8::Local<v8::Context> context = pObj->CreationContext(); 391 v8::Isolate* isolate = context->GetIsolate(); 392 v8::Local<v8::Array> pKeyList = JS_GetObjectElementNames(isolate, pObj); 393 int nObjElements = pKeyList->Length(); 394 395 for (int i=0; i<nObjElements; i++) 396 { 397 398 CFX_WideString ws = JS_ToString(isolate, JS_GetArrayElement(isolate, pKeyList, i)); 399 CFX_ByteString sKey = ws.UTF8Encode(); 400 401 v8::Local<v8::Value> v = JS_GetObjectElement(isolate, pObj, ws.c_str()); 402 FXJSVALUETYPE vt = GET_VALUE_TYPE(v); 403 switch (vt) 404 { 405 case VT_number: 406 { 407 CJS_KeyValue* pObjElement = new CJS_KeyValue; 408 pObjElement->nType = JS_GLOBALDATA_TYPE_NUMBER; 409 pObjElement->sKey = sKey; 410 pObjElement->dData = JS_ToNumber(isolate, v); 411 array.Add(pObjElement); 412 } 413 break; 414 case VT_boolean: 415 { 416 CJS_KeyValue* pObjElement = new CJS_KeyValue; 417 pObjElement->nType = JS_GLOBALDATA_TYPE_BOOLEAN; 418 pObjElement->sKey = sKey; 419 pObjElement->dData = JS_ToBoolean(isolate, v); 420 array.Add(pObjElement); 421 } 422 break; 423 case VT_string: 424 { 425 CFX_ByteString sValue = CJS_Value(isolate, v, VT_string).ToCFXByteString(); 426 CJS_KeyValue* pObjElement = new CJS_KeyValue; 427 pObjElement->nType = JS_GLOBALDATA_TYPE_STRING; 428 pObjElement->sKey = sKey; 429 pObjElement->sData = sValue; 430 array.Add(pObjElement); 431 } 432 break; 433 case VT_object: 434 { 435 CJS_KeyValue* pObjElement = new CJS_KeyValue; 436 pObjElement->nType = JS_GLOBALDATA_TYPE_OBJECT; 437 pObjElement->sKey = sKey; 438 ObjectToArray(JS_ToObject(isolate, v), pObjElement->objData); 439 array.Add(pObjElement); 440 } 441 break; 442 case VT_null: 443 { 444 CJS_KeyValue* pObjElement = new CJS_KeyValue; 445 pObjElement->nType = JS_GLOBALDATA_TYPE_NULL; 446 pObjElement->sKey = sKey; 447 array.Add(pObjElement); 448 } 449 break; 450 default: 451 break; 452 } 453 } 454} 455 456void global_alternate::PutObjectProperty(v8::Local<v8::Object> pObj, CJS_KeyValue* pData) 457{ 458 ASSERT(pData != NULL); 459 460 for (int i=0,sz=pData->objData.Count(); i<sz; i++) 461 { 462 CJS_KeyValue* pObjData = pData->objData.GetAt(i); 463 ASSERT(pObjData != NULL); 464 465 switch (pObjData->nType) 466 { 467 case JS_GLOBALDATA_TYPE_NUMBER: 468 JS_PutObjectNumber(NULL,(JSObject)pObj, pObjData->sKey.UTF8Decode().c_str(), pObjData->dData); 469 break; 470 case JS_GLOBALDATA_TYPE_BOOLEAN: 471 JS_PutObjectBoolean(NULL,(JSObject)pObj, pObjData->sKey.UTF8Decode().c_str(), (bool)(pObjData->bData == 1)); 472 break; 473 case JS_GLOBALDATA_TYPE_STRING: 474 JS_PutObjectString(NULL,(JSObject)pObj, pObjData->sKey.UTF8Decode().c_str(), pObjData->sData.UTF8Decode().c_str()); 475 break; 476 case JS_GLOBALDATA_TYPE_OBJECT: 477 { 478 IJS_Runtime* pRuntime = JS_GetRuntime((JSFXObject)(*m_pJSObject)); 479 v8::Local<v8::Object> pNewObj = JS_NewFxDynamicObj(pRuntime, NULL, -1); 480 PutObjectProperty(pNewObj, pObjData); 481 JS_PutObjectObject(NULL, (JSObject)pObj, pObjData->sKey.UTF8Decode().c_str(), (JSObject)pNewObj); 482 } 483 break; 484 case JS_GLOBALDATA_TYPE_NULL: 485 JS_PutObjectNull(NULL,(JSObject)pObj, pObjData->sKey.UTF8Decode().c_str()); 486 break; 487 } 488 } 489} 490 491void global_alternate::DestroyGlobalPersisitentVariables() 492{ 493 FX_POSITION pos = m_mapGlobal.GetStartPosition(); 494 while (pos) 495 { 496 CFX_ByteString name; 497 js_global_data* pData = NULL; 498 m_mapGlobal.GetNextAssoc(pos, name, (FX_LPVOID&)pData); 499 delete pData; 500 } 501 502 m_mapGlobal.RemoveAll(); 503} 504 505 506FX_BOOL global_alternate::SetGlobalVariables(FX_LPCSTR propname, int nType, 507 double dData, bool bData, const CFX_ByteString& sData, JSObject pData, bool bDefaultPersistent) 508{ 509 if (propname == NULL) return FALSE; 510 511 js_global_data* pTemp = NULL; 512 m_mapGlobal.Lookup(propname, (FX_LPVOID&)pTemp); 513 514 if (pTemp) 515 { 516 if (pTemp->bDeleted || pTemp->nType != nType) 517 { 518 pTemp->dData = 0; 519 pTemp->bData = 0; 520 pTemp->sData = ""; 521 pTemp->nType = nType; 522 } 523 524 pTemp->bDeleted = FALSE; 525 526 switch (nType) 527 { 528 case JS_GLOBALDATA_TYPE_NUMBER: 529 { 530 pTemp->dData = dData; 531 } 532 break; 533 case JS_GLOBALDATA_TYPE_BOOLEAN: 534 { 535 pTemp->bData = bData; 536 } 537 break; 538 case JS_GLOBALDATA_TYPE_STRING: 539 { 540 pTemp->sData = sData; 541 } 542 break; 543 case JS_GLOBALDATA_TYPE_OBJECT: 544 { 545 pTemp->pData.Reset(JS_GetRuntime(pData), pData); 546 } 547 break; 548 case JS_GLOBALDATA_TYPE_NULL: 549 break; 550 default: 551 return FALSE; 552 } 553 554 return TRUE; 555 } 556 557 js_global_data* pNewData = NULL; 558 559 switch (nType) 560 { 561 case JS_GLOBALDATA_TYPE_NUMBER: 562 { 563 pNewData = new js_global_data; 564 pNewData->nType = JS_GLOBALDATA_TYPE_NUMBER; 565 pNewData->dData = dData; 566 pNewData->bPersistent = bDefaultPersistent; 567 } 568 break; 569 case JS_GLOBALDATA_TYPE_BOOLEAN: 570 { 571 pNewData = new js_global_data; 572 pNewData->nType = JS_GLOBALDATA_TYPE_BOOLEAN; 573 pNewData->bData = bData; 574 pNewData->bPersistent = bDefaultPersistent; 575 } 576 break; 577 case JS_GLOBALDATA_TYPE_STRING: 578 { 579 pNewData = new js_global_data; 580 pNewData->nType = JS_GLOBALDATA_TYPE_STRING; 581 pNewData->sData = sData; 582 pNewData->bPersistent = bDefaultPersistent; 583 } 584 break; 585 case JS_GLOBALDATA_TYPE_OBJECT: 586 { 587 pNewData = new js_global_data; 588 pNewData->nType = JS_GLOBALDATA_TYPE_OBJECT; 589 pNewData->pData.Reset(JS_GetRuntime(pData), pData); 590 pNewData->bPersistent = bDefaultPersistent; 591 } 592 break; 593 case JS_GLOBALDATA_TYPE_NULL: 594 { 595 pNewData = new js_global_data; 596 pNewData->nType = JS_GLOBALDATA_TYPE_NULL; 597 pNewData->bPersistent = bDefaultPersistent; 598 } 599 break; 600 default: 601 return FALSE; 602 } 603 604 m_mapGlobal.SetAt(propname, (FX_LPVOID)pNewData); 605 606 return TRUE; 607} 608 609FXJSVALUETYPE GET_VALUE_TYPE(v8::Local<v8::Value> p) 610{ 611 const unsigned int nHash = JS_CalcHash(JS_GetTypeof(p)); 612 613 if (nHash == JSCONST_nUndefHash) 614 return VT_undefined; 615 if (nHash == JSCONST_nNullHash) 616 return VT_null; 617 if (nHash == JSCONST_nStringHash) 618 return VT_string; 619 if (nHash == JSCONST_nNumberHash) 620 return VT_number; 621 if (nHash == JSCONST_nBoolHash) 622 return VT_boolean; 623 if (nHash == JSCONST_nDateHash) 624 return VT_date; 625 if (nHash == JSCONST_nObjectHash) 626 return VT_object; 627 if (nHash == JSCONST_nFXobjHash) 628 return VT_fxobject; 629 630 return VT_unknown; 631} 632 633