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
~DeclarationContext()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
get_count() const64 int get_count() const { return get_count_; }
set_count() const65 int set_count() const { return set_count_; }
has_count() const66 int has_count() const { return has_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<Boolean> Has(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.
GetHolder(Local<FunctionTemplate> function)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<Boolean> HandleHas(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 has_count_;
99
100 static DeclarationContext* GetInstance(const AccessorInfo& info);
101 };
102
103
DeclarationContext()104 DeclarationContext::DeclarationContext()
105 : is_initialized_(false), get_count_(0), set_count_(0), has_count_(0) {
106 // Do nothing.
107 }
108
109
InitializeIfNeeded()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 &HandleHas,
118 0, 0,
119 data);
120 context_ = Context::New(0, function->InstanceTemplate(), Local<Value>());
121 context_->Enter();
122 is_initialized_ = true;
123 }
124
125
Check(const char * source,int get,int set,int has,Expectations expectations,v8::Handle<Value> value)126 void DeclarationContext::Check(const char* source,
127 int get, int set, int has,
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 v8::internal::Heap::CollectGarbage(0, 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(has, has_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
HandleGet(Local<String> key,const AccessorInfo & info)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
HandleSet(Local<String> key,Local<Value> value,const AccessorInfo & info)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
HandleHas(Local<String> key,const AccessorInfo & info)173 v8::Handle<Boolean> DeclarationContext::HandleHas(Local<String> key,
174 const AccessorInfo& info) {
175 DeclarationContext* context = GetInstance(info);
176 context->has_count_++;
177 return context->Has(key);
178 }
179
180
GetInstance(const AccessorInfo & info)181 DeclarationContext* DeclarationContext::GetInstance(const AccessorInfo& info) {
182 return static_cast<DeclarationContext*>(External::Unwrap(info.Data()));
183 }
184
185
Get(Local<String> key)186 v8::Handle<Value> DeclarationContext::Get(Local<String> key) {
187 return v8::Handle<Value>();
188 }
189
190
Set(Local<String> key,Local<Value> value)191 v8::Handle<Value> DeclarationContext::Set(Local<String> key,
192 Local<Value> value) {
193 return v8::Handle<Value>();
194 }
195
196
Has(Local<String> key)197 v8::Handle<Boolean> DeclarationContext::Has(Local<String> key) {
198 return v8::Handle<Boolean>();
199 }
200
201
202 // Test global declaration of a property the interceptor doesn't know
203 // about and doesn't handle.
TEST(Unknown)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 1, // declaration
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:
Has(Local<String> key)252 virtual v8::Handle<Boolean> Has(Local<String> key) {
253 return True();
254 }
255 };
256
257
258
TEST(Present)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 1, // declaration
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:
Has(Local<String> key)307 virtual v8::Handle<Boolean> Has(Local<String> key) {
308 return False();
309 }
310 };
311
312
TEST(Absent)313 TEST(Absent) {
314 HandleScope scope;
315
316 { AbsentPropertyContext context;
317 context.Check("var x; x",
318 1, // access
319 2, // declaration + initialization
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 1, // declaration
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
AppearingPropertyContext()375 AppearingPropertyContext() : state_(DECLARE) { }
376
377 protected:
Has(Local<String> key)378 virtual v8::Handle<Boolean> Has(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 False();
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 True();
390 default:
391 CHECK(state_ == UNKNOWN);
392 break;
393 }
394 // Do the lookup in the object.
395 return v8::Local<Boolean>();
396 }
397
398 private:
399 State state_;
400 };
401
402
TEST(Appearing)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 1, // declaration
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
ReappearingPropertyContext()458 ReappearingPropertyContext() : state_(DECLARE) { }
459
460 protected:
Has(Local<String> key)461 virtual v8::Handle<Boolean> Has(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 False();
468 case DONT_DECLARE:
469 // Ignore the second declaration by returning
470 // that the property is already there.
471 state_ = INITIALIZE;
472 return True();
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 False();
480 default:
481 CHECK(state_ == UNKNOWN);
482 break;
483 }
484 // Do the lookup in the object.
485 return v8::Local<Boolean>();
486 }
487
488 private:
489 State state_;
490 };
491
492
TEST(Reappearing)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:
Has(Local<String> key)509 virtual v8::Handle<Boolean> Has(Local<String> key) {
510 // Let it seem that the property exists in the prototype object.
511 return True();
512 }
513
514 // Use the prototype as the holder for the interceptors.
GetHolder(Local<FunctionTemplate> function)515 virtual Local<ObjectTemplate> GetHolder(Local<FunctionTemplate> function) {
516 return function->PrototypeTemplate();
517 }
518 };
519
520
TEST(ExistsInPrototype)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:
Has(Local<String> key)571 virtual v8::Handle<Boolean> Has(Local<String> key) {
572 // Let it seem that the property is absent in the prototype object.
573 return False();
574 }
575
576 // Use the prototype as the holder for the interceptors.
GetHolder(Local<FunctionTemplate> function)577 virtual Local<ObjectTemplate> GetHolder(Local<FunctionTemplate> function) {
578 return function->PrototypeTemplate();
579 }
580 };
581
582
TEST(AbsentInPrototype)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