1 // Copyright 2007-2008 the V8 project authors. All rights reserved. 2 // Redistribution and use in source and binary forms, with or without 3 // modification, are permitted provided that the following conditions are 4 // met: 5 // 6 // * Redistributions of source code must retain the above copyright 7 // notice, this list of conditions and the following disclaimer. 8 // * Redistributions in binary form must reproduce the above 9 // copyright notice, this list of conditions and the following 10 // disclaimer in the documentation and/or other materials provided 11 // with the distribution. 12 // * Neither the name of Google Inc. nor the names of its 13 // contributors may be used to endorse or promote products derived 14 // from this software without specific prior written permission. 15 // 16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 28 #include <stdlib.h> 29 30 #include "v8.h" 31 32 #include "heap.h" 33 #include "cctest.h" 34 35 using namespace v8; 36 37 38 enum Expectations { 39 EXPECT_RESULT, 40 EXPECT_EXCEPTION 41 }; 42 43 44 // A DeclarationContext holds a reference to a v8::Context and keeps 45 // track of various declaration related counters to make it easier to 46 // track if global declarations in the presence of interceptors behave 47 // the right way. 48 class DeclarationContext { 49 public: 50 DeclarationContext(); 51 52 virtual ~DeclarationContext() { 53 if (is_initialized_) { 54 context_->Exit(); 55 context_.Dispose(); 56 } 57 } 58 59 void Check(const char* source, 60 int get, int set, int has, 61 Expectations expectations, 62 v8::Handle<Value> value = Local<Value>()); 63 64 int get_count() const { return get_count_; } 65 int set_count() const { return set_count_; } 66 int query_count() const { return query_count_; } 67 68 protected: 69 virtual v8::Handle<Value> Get(Local<String> key); 70 virtual v8::Handle<Value> Set(Local<String> key, Local<Value> value); 71 virtual v8::Handle<Integer> Query(Local<String> key); 72 73 void InitializeIfNeeded(); 74 75 // Get the holder for the interceptor. Default to the instance template 76 // but may be overwritten. 77 virtual Local<ObjectTemplate> GetHolder(Local<FunctionTemplate> function) { 78 return function->InstanceTemplate(); 79 } 80 81 // The handlers are called as static functions that forward 82 // to the instance specific virtual methods. 83 static v8::Handle<Value> HandleGet(Local<String> key, 84 const AccessorInfo& info); 85 static v8::Handle<Value> HandleSet(Local<String> key, 86 Local<Value> value, 87 const AccessorInfo& info); 88 static v8::Handle<Integer> HandleQuery(Local<String> key, 89 const AccessorInfo& info); 90 91 private: 92 bool is_initialized_; 93 Persistent<Context> context_; 94 Local<String> property_; 95 96 int get_count_; 97 int set_count_; 98 int query_count_; 99 100 static DeclarationContext* GetInstance(const AccessorInfo& info); 101 }; 102 103 104 DeclarationContext::DeclarationContext() 105 : is_initialized_(false), get_count_(0), set_count_(0), query_count_(0) { 106 // Do nothing. 107 } 108 109 110 void DeclarationContext::InitializeIfNeeded() { 111 if (is_initialized_) return; 112 HandleScope scope; 113 Local<FunctionTemplate> function = FunctionTemplate::New(); 114 Local<Value> data = External::New(this); 115 GetHolder(function)->SetNamedPropertyHandler(&HandleGet, 116 &HandleSet, 117 &HandleQuery, 118 0, 0, 119 data); 120 context_ = Context::New(0, function->InstanceTemplate(), Local<Value>()); 121 context_->Enter(); 122 is_initialized_ = true; 123 } 124 125 126 void DeclarationContext::Check(const char* source, 127 int get, int set, int query, 128 Expectations expectations, 129 v8::Handle<Value> value) { 130 InitializeIfNeeded(); 131 // A retry after a GC may pollute the counts, so perform gc now 132 // to avoid that. 133 HEAP->CollectGarbage(v8::internal::NEW_SPACE); 134 HandleScope scope; 135 TryCatch catcher; 136 catcher.SetVerbose(true); 137 Local<Value> result = Script::Compile(String::New(source))->Run(); 138 CHECK_EQ(get, get_count()); 139 CHECK_EQ(set, set_count()); 140 CHECK_EQ(query, query_count()); 141 if (expectations == EXPECT_RESULT) { 142 CHECK(!catcher.HasCaught()); 143 if (!value.IsEmpty()) { 144 CHECK_EQ(value, result); 145 } 146 } else { 147 CHECK(expectations == EXPECT_EXCEPTION); 148 CHECK(catcher.HasCaught()); 149 if (!value.IsEmpty()) { 150 CHECK_EQ(value, catcher.Exception()); 151 } 152 } 153 } 154 155 156 v8::Handle<Value> DeclarationContext::HandleGet(Local<String> key, 157 const AccessorInfo& info) { 158 DeclarationContext* context = GetInstance(info); 159 context->get_count_++; 160 return context->Get(key); 161 } 162 163 164 v8::Handle<Value> DeclarationContext::HandleSet(Local<String> key, 165 Local<Value> value, 166 const AccessorInfo& info) { 167 DeclarationContext* context = GetInstance(info); 168 context->set_count_++; 169 return context->Set(key, value); 170 } 171 172 173 v8::Handle<Integer> DeclarationContext::HandleQuery(Local<String> key, 174 const AccessorInfo& info) { 175 DeclarationContext* context = GetInstance(info); 176 context->query_count_++; 177 return context->Query(key); 178 } 179 180 181 DeclarationContext* DeclarationContext::GetInstance(const AccessorInfo& info) { 182 return static_cast<DeclarationContext*>(External::Unwrap(info.Data())); 183 } 184 185 186 v8::Handle<Value> DeclarationContext::Get(Local<String> key) { 187 return v8::Handle<Value>(); 188 } 189 190 191 v8::Handle<Value> DeclarationContext::Set(Local<String> key, 192 Local<Value> value) { 193 return v8::Handle<Value>(); 194 } 195 196 197 v8::Handle<Integer> DeclarationContext::Query(Local<String> key) { 198 return v8::Handle<Integer>(); 199 } 200 201 202 // Test global declaration of a property the interceptor doesn't know 203 // about and doesn't handle. 204 TEST(Unknown) { 205 HandleScope scope; 206 207 { DeclarationContext context; 208 context.Check("var x; x", 209 1, // access 210 1, // declaration 211 2, // declaration + initialization 212 EXPECT_RESULT, Undefined()); 213 } 214 215 { DeclarationContext context; 216 context.Check("var x = 0; x", 217 1, // access 218 2, // declaration + initialization 219 2, // declaration + initialization 220 EXPECT_RESULT, Number::New(0)); 221 } 222 223 { DeclarationContext context; 224 context.Check("function x() { }; x", 225 1, // access 226 0, 227 0, 228 EXPECT_RESULT); 229 } 230 231 { DeclarationContext context; 232 context.Check("const x; x", 233 1, // access 234 2, // declaration + initialization 235 1, // declaration 236 EXPECT_RESULT, Undefined()); 237 } 238 239 { DeclarationContext context; 240 context.Check("const x = 0; x", 241 1, // access 242 2, // declaration + initialization 243 1, // declaration 244 EXPECT_RESULT, Undefined()); // SB 0 - BUG 1213579 245 } 246 } 247 248 249 250 class PresentPropertyContext: public DeclarationContext { 251 protected: 252 virtual v8::Handle<Integer> Query(Local<String> key) { 253 return Integer::New(v8::None); 254 } 255 }; 256 257 258 259 TEST(Present) { 260 HandleScope scope; 261 262 { PresentPropertyContext context; 263 context.Check("var x; x", 264 1, // access 265 0, 266 2, // declaration + initialization 267 EXPECT_EXCEPTION); // x is not defined! 268 } 269 270 { PresentPropertyContext context; 271 context.Check("var x = 0; x", 272 1, // access 273 1, // initialization 274 2, // declaration + initialization 275 EXPECT_RESULT, Number::New(0)); 276 } 277 278 { PresentPropertyContext context; 279 context.Check("function x() { }; x", 280 1, // access 281 0, 282 0, 283 EXPECT_RESULT); 284 } 285 286 { PresentPropertyContext context; 287 context.Check("const x; x", 288 1, // access 289 1, // initialization 290 1, // (re-)declaration 291 EXPECT_RESULT, Undefined()); 292 } 293 294 { PresentPropertyContext context; 295 context.Check("const x = 0; x", 296 1, // access 297 1, // initialization 298 1, // (re-)declaration 299 EXPECT_RESULT, Number::New(0)); 300 } 301 } 302 303 304 305 class AbsentPropertyContext: public DeclarationContext { 306 protected: 307 virtual v8::Handle<Integer> Query(Local<String> key) { 308 return v8::Handle<Integer>(); 309 } 310 }; 311 312 313 TEST(Absent) { 314 HandleScope scope; 315 316 { AbsentPropertyContext context; 317 context.Check("var x; x", 318 1, // access 319 1, // declaration 320 2, // declaration + initialization 321 EXPECT_RESULT, Undefined()); 322 } 323 324 { AbsentPropertyContext context; 325 context.Check("var x = 0; x", 326 1, // access 327 2, // declaration + initialization 328 2, // declaration + initialization 329 EXPECT_RESULT, Number::New(0)); 330 } 331 332 { AbsentPropertyContext context; 333 context.Check("function x() { }; x", 334 1, // access 335 0, 336 0, 337 EXPECT_RESULT); 338 } 339 340 { AbsentPropertyContext context; 341 context.Check("const x; x", 342 1, // access 343 2, // declaration + initialization 344 1, // declaration 345 EXPECT_RESULT, Undefined()); 346 } 347 348 { AbsentPropertyContext context; 349 context.Check("const x = 0; x", 350 1, // access 351 2, // declaration + initialization 352 1, // declaration 353 EXPECT_RESULT, Undefined()); // SB 0 - BUG 1213579 354 } 355 356 { AbsentPropertyContext context; 357 context.Check("if (false) { var x = 0 }; x", 358 1, // access 359 1, // declaration 360 1, // declaration + initialization 361 EXPECT_RESULT, Undefined()); 362 } 363 } 364 365 366 367 class AppearingPropertyContext: public DeclarationContext { 368 public: 369 enum State { 370 DECLARE, 371 INITIALIZE_IF_ASSIGN, 372 UNKNOWN 373 }; 374 375 AppearingPropertyContext() : state_(DECLARE) { } 376 377 protected: 378 virtual v8::Handle<Integer> Query(Local<String> key) { 379 switch (state_) { 380 case DECLARE: 381 // Force declaration by returning that the 382 // property is absent. 383 state_ = INITIALIZE_IF_ASSIGN; 384 return Handle<Integer>(); 385 case INITIALIZE_IF_ASSIGN: 386 // Return that the property is present so we only get the 387 // setter called when initializing with a value. 388 state_ = UNKNOWN; 389 return Integer::New(v8::None); 390 default: 391 CHECK(state_ == UNKNOWN); 392 break; 393 } 394 // Do the lookup in the object. 395 return v8::Handle<Integer>(); 396 } 397 398 private: 399 State state_; 400 }; 401 402 403 TEST(Appearing) { 404 HandleScope scope; 405 406 { AppearingPropertyContext context; 407 context.Check("var x; x", 408 1, // access 409 1, // declaration 410 2, // declaration + initialization 411 EXPECT_RESULT, Undefined()); 412 } 413 414 { AppearingPropertyContext context; 415 context.Check("var x = 0; x", 416 1, // access 417 2, // declaration + initialization 418 2, // declaration + initialization 419 EXPECT_RESULT, Number::New(0)); 420 } 421 422 { AppearingPropertyContext context; 423 context.Check("function x() { }; x", 424 1, // access 425 0, 426 0, 427 EXPECT_RESULT); 428 } 429 430 { AppearingPropertyContext context; 431 context.Check("const x; x", 432 1, // access 433 2, // declaration + initialization 434 1, // declaration 435 EXPECT_RESULT, Undefined()); 436 } 437 438 { AppearingPropertyContext context; 439 context.Check("const x = 0; x", 440 1, // access 441 2, // declaration + initialization 442 1, // declaration 443 EXPECT_RESULT, Undefined()); 444 // Result is undefined because declaration succeeded but 445 // initialization to 0 failed (due to context behavior). 446 } 447 } 448 449 450 451 class ReappearingPropertyContext: public DeclarationContext { 452 public: 453 enum State { 454 DECLARE, 455 DONT_DECLARE, 456 INITIALIZE, 457 UNKNOWN 458 }; 459 460 ReappearingPropertyContext() : state_(DECLARE) { } 461 462 protected: 463 virtual v8::Handle<Integer> Query(Local<String> key) { 464 switch (state_) { 465 case DECLARE: 466 // Force the first declaration by returning that 467 // the property is absent. 468 state_ = DONT_DECLARE; 469 return Handle<Integer>(); 470 case DONT_DECLARE: 471 // Ignore the second declaration by returning 472 // that the property is already there. 473 state_ = INITIALIZE; 474 return Integer::New(v8::None); 475 case INITIALIZE: 476 // Force an initialization by returning that 477 // the property is absent. This will make sure 478 // that the setter is called and it will not 479 // lead to redeclaration conflicts (yet). 480 state_ = UNKNOWN; 481 return Handle<Integer>(); 482 default: 483 CHECK(state_ == UNKNOWN); 484 break; 485 } 486 // Do the lookup in the object. 487 return Handle<Integer>(); 488 } 489 490 private: 491 State state_; 492 }; 493 494 495 TEST(Reappearing) { 496 HandleScope scope; 497 498 { ReappearingPropertyContext context; 499 context.Check("const x; var x = 0", 500 0, 501 3, // const declaration+initialization, var initialization 502 3, // 2 x declaration + var initialization 503 EXPECT_RESULT, Undefined()); 504 } 505 } 506 507 508 509 class ExistsInPrototypeContext: public DeclarationContext { 510 protected: 511 virtual v8::Handle<Integer> Query(Local<String> key) { 512 // Let it seem that the property exists in the prototype object. 513 return Integer::New(v8::None); 514 } 515 516 // Use the prototype as the holder for the interceptors. 517 virtual Local<ObjectTemplate> GetHolder(Local<FunctionTemplate> function) { 518 return function->PrototypeTemplate(); 519 } 520 }; 521 522 523 TEST(ExistsInPrototype) { 524 HandleScope scope; 525 526 // Sanity check to make sure that the holder of the interceptor 527 // really is the prototype object. 528 { ExistsInPrototypeContext context; 529 context.Check("this.x = 87; this.x", 530 0, 531 0, 532 0, 533 EXPECT_RESULT, Number::New(87)); 534 } 535 536 { ExistsInPrototypeContext context; 537 context.Check("var x; x", 538 1, // get 539 0, 540 1, // declaration 541 EXPECT_EXCEPTION); 542 } 543 544 { ExistsInPrototypeContext context; 545 context.Check("var x = 0; x", 546 0, 547 0, 548 1, // declaration 549 EXPECT_RESULT, Number::New(0)); 550 } 551 552 { ExistsInPrototypeContext context; 553 context.Check("const x; x", 554 0, 555 0, 556 1, // declaration 557 EXPECT_RESULT, Undefined()); 558 } 559 560 { ExistsInPrototypeContext context; 561 context.Check("const x = 0; x", 562 0, 563 0, 564 1, // declaration 565 EXPECT_RESULT, Number::New(0)); 566 } 567 } 568 569 570 571 class AbsentInPrototypeContext: public DeclarationContext { 572 protected: 573 virtual v8::Handle<Integer> Query(Local<String> key) { 574 // Let it seem that the property is absent in the prototype object. 575 return Handle<Integer>(); 576 } 577 578 // Use the prototype as the holder for the interceptors. 579 virtual Local<ObjectTemplate> GetHolder(Local<FunctionTemplate> function) { 580 return function->PrototypeTemplate(); 581 } 582 }; 583 584 585 TEST(AbsentInPrototype) { 586 HandleScope scope; 587 588 { AbsentInPrototypeContext context; 589 context.Check("if (false) { var x = 0; }; x", 590 0, 591 0, 592 1, // declaration 593 EXPECT_RESULT, Undefined()); 594 } 595 } 596