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_; }
query_count() const66 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.
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<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
DeclarationContext()104 DeclarationContext::DeclarationContext()
105 : is_initialized_(false), get_count_(0), set_count_(0), query_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 &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
Check(const char * source,int get,int set,int query,Expectations expectations,v8::Handle<Value> value)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
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
HandleQuery(Local<String> key,const AccessorInfo & info)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
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
Query(Local<String> key)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.
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 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:
Query(Local<String> key)252 virtual v8::Handle<Integer> Query(Local<String> key) {
253 return Integer::New(v8::None);
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 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:
Query(Local<String> key)307 virtual v8::Handle<Integer> Query(Local<String> key) {
308 return v8::Handle<Integer>();
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 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
AppearingPropertyContext()375 AppearingPropertyContext() : state_(DECLARE) { }
376
377 protected:
Query(Local<String> key)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
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 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
ReappearingPropertyContext()460 ReappearingPropertyContext() : state_(DECLARE) { }
461
462 protected:
Query(Local<String> key)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
TEST(Reappearing)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:
Query(Local<String> key)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.
GetHolder(Local<FunctionTemplate> function)517 virtual Local<ObjectTemplate> GetHolder(Local<FunctionTemplate> function) {
518 return function->PrototypeTemplate();
519 }
520 };
521
522
TEST(ExistsInPrototype)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:
Query(Local<String> key)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.
GetHolder(Local<FunctionTemplate> function)579 virtual Local<ObjectTemplate> GetHolder(Local<FunctionTemplate> function) {
580 return function->PrototypeTemplate();
581 }
582 };
583
584
TEST(AbsentInPrototype)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