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
35using namespace v8;
36
37
38enum 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.
48class 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
104DeclarationContext::DeclarationContext()
105    : is_initialized_(false), get_count_(0), set_count_(0), query_count_(0) {
106  // Do nothing.
107}
108
109
110void 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
126void 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
156v8::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
164v8::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
173v8::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
181DeclarationContext* DeclarationContext::GetInstance(const AccessorInfo& info) {
182  return static_cast<DeclarationContext*>(External::Unwrap(info.Data()));
183}
184
185
186v8::Handle<Value> DeclarationContext::Get(Local<String> key) {
187  return v8::Handle<Value>();
188}
189
190
191v8::Handle<Value> DeclarationContext::Set(Local<String> key,
192                                          Local<Value> value) {
193  return v8::Handle<Value>();
194}
195
196
197v8::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.
204TEST(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
250class 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
259TEST(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
305class AbsentPropertyContext: public DeclarationContext {
306 protected:
307  virtual v8::Handle<Integer> Query(Local<String> key) {
308    return v8::Handle<Integer>();
309  }
310};
311
312
313TEST(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
367class 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
403TEST(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
451class 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
495TEST(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
509class 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
523TEST(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
571class 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
585TEST(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