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 2, // declaration + initialization 236 EXPECT_RESULT, Undefined()); 237 } 238 239 { DeclarationContext context; 240 context.Check("const x = 0; x", 241 1, // access 242 2, // declaration + initialization 243 2, // declaration + initialization 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 0, 289 0, 290 1, // (re-)declaration 291 EXPECT_EXCEPTION); // x has already been declared! 292 } 293 294 { PresentPropertyContext context; 295 context.Check("const x = 0; x", 296 0, 297 0, 298 1, // (re-)declaration 299 EXPECT_EXCEPTION); // x has already been declared! 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 2, // declaration + initializetion 345 EXPECT_RESULT, Undefined()); 346 } 347 348 { AbsentPropertyContext context; 349 context.Check("const x = 0; x", 350 1, // access 351 2, // declaration + initialization 352 2, // declaration + initialization 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 0, 433 1, // declaration 434 2, // declaration + initialization 435 EXPECT_EXCEPTION); // x has already been declared! 436 } 437 438 { AppearingPropertyContext context; 439 context.Check("const x = 0; x", 440 0, 441 1, // declaration 442 2, // declaration + initialization 443 EXPECT_EXCEPTION); // x has already been declared! 444 } 445 } 446 447 448 449 class ReappearingPropertyContext: public DeclarationContext { 450 public: 451 enum State { 452 DECLARE, 453 DONT_DECLARE, 454 INITIALIZE, 455 UNKNOWN 456 }; 457 458 ReappearingPropertyContext() : state_(DECLARE) { } 459 460 protected: 461 virtual v8::Handle<Integer> Query(Local<String> key) { 462 switch (state_) { 463 case DECLARE: 464 // Force the first declaration by returning that 465 // the property is absent. 466 state_ = DONT_DECLARE; 467 return Handle<Integer>(); 468 case DONT_DECLARE: 469 // Ignore the second declaration by returning 470 // that the property is already there. 471 state_ = INITIALIZE; 472 return Integer::New(v8::None); 473 case INITIALIZE: 474 // Force an initialization by returning that 475 // the property is absent. This will make sure 476 // that the setter is called and it will not 477 // lead to redeclaration conflicts (yet). 478 state_ = UNKNOWN; 479 return Handle<Integer>(); 480 default: 481 CHECK(state_ == UNKNOWN); 482 break; 483 } 484 // Do the lookup in the object. 485 return Handle<Integer>(); 486 } 487 488 private: 489 State state_; 490 }; 491 492 493 TEST(Reappearing) { 494 HandleScope scope; 495 496 { ReappearingPropertyContext context; 497 context.Check("const x; var x = 0", 498 0, 499 2, // var declaration + const initialization 500 4, // 2 x declaration + 2 x initialization 501 EXPECT_EXCEPTION); // x has already been declared! 502 } 503 } 504 505 506 507 class ExistsInPrototypeContext: public DeclarationContext { 508 protected: 509 virtual v8::Handle<Integer> Query(Local<String> key) { 510 // Let it seem that the property exists in the prototype object. 511 return Integer::New(v8::None); 512 } 513 514 // Use the prototype as the holder for the interceptors. 515 virtual Local<ObjectTemplate> GetHolder(Local<FunctionTemplate> function) { 516 return function->PrototypeTemplate(); 517 } 518 }; 519 520 521 TEST(ExistsInPrototype) { 522 HandleScope scope; 523 524 // Sanity check to make sure that the holder of the interceptor 525 // really is the prototype object. 526 { ExistsInPrototypeContext context; 527 context.Check("this.x = 87; this.x", 528 0, 529 0, 530 0, 531 EXPECT_RESULT, Number::New(87)); 532 } 533 534 { ExistsInPrototypeContext context; 535 context.Check("var x; x", 536 1, // get 537 0, 538 1, // declaration 539 EXPECT_EXCEPTION); 540 } 541 542 { ExistsInPrototypeContext context; 543 context.Check("var x = 0; x", 544 0, 545 0, 546 1, // declaration 547 EXPECT_RESULT, Number::New(0)); 548 } 549 550 { ExistsInPrototypeContext context; 551 context.Check("const x; x", 552 0, 553 0, 554 1, // declaration 555 EXPECT_RESULT, Undefined()); 556 } 557 558 { ExistsInPrototypeContext context; 559 context.Check("const x = 0; x", 560 0, 561 0, 562 1, // declaration 563 EXPECT_RESULT, Number::New(0)); 564 } 565 } 566 567 568 569 class AbsentInPrototypeContext: public DeclarationContext { 570 protected: 571 virtual v8::Handle<Integer> Query(Local<String> key) { 572 // Let it seem that the property is absent in the prototype object. 573 return Handle<Integer>(); 574 } 575 576 // Use the prototype as the holder for the interceptors. 577 virtual Local<ObjectTemplate> GetHolder(Local<FunctionTemplate> function) { 578 return function->PrototypeTemplate(); 579 } 580 }; 581 582 583 TEST(AbsentInPrototype) { 584 HandleScope scope; 585 586 { AbsentInPrototypeContext context; 587 context.Check("if (false) { var x = 0; }; x", 588 0, 589 0, 590 1, // declaration 591 EXPECT_RESULT, Undefined()); 592 } 593 } 594