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 "src/v8.h"
31
32#include "src/heap/heap.h"
33#include "test/cctest/cctest.h"
34
35using namespace v8;
36
37
38enum Expectations {
39  EXPECT_RESULT,
40  EXPECT_EXCEPTION,
41  EXPECT_ERROR
42};
43
44
45// A DeclarationContext holds a reference to a v8::Context and keeps
46// track of various declaration related counters to make it easier to
47// track if global declarations in the presence of interceptors behave
48// the right way.
49class DeclarationContext {
50 public:
51  DeclarationContext();
52
53  virtual ~DeclarationContext() {
54    if (is_initialized_) {
55      Isolate* isolate = CcTest::isolate();
56      HandleScope scope(isolate);
57      Local<Context> context = Local<Context>::New(isolate, context_);
58      context->Exit();
59      context_.Reset();
60    }
61  }
62
63  void Check(const char* source,
64             int get, int set, int has,
65             Expectations expectations,
66             v8::Handle<Value> value = Local<Value>());
67
68  int get_count() const { return get_count_; }
69  int set_count() const { return set_count_; }
70  int query_count() const { return query_count_; }
71
72 protected:
73  virtual v8::Handle<Value> Get(Local<String> key);
74  virtual v8::Handle<Value> Set(Local<String> key, Local<Value> value);
75  virtual v8::Handle<Integer> Query(Local<String> key);
76
77  void InitializeIfNeeded();
78
79  // Perform optional initialization steps on the context after it has
80  // been created. Defaults to none but may be overwritten.
81  virtual void PostInitializeContext(Handle<Context> context) {}
82
83  // Get the holder for the interceptor. Default to the instance template
84  // but may be overwritten.
85  virtual Local<ObjectTemplate> GetHolder(Local<FunctionTemplate> function) {
86    return function->InstanceTemplate();
87  }
88
89  // The handlers are called as static functions that forward
90  // to the instance specific virtual methods.
91  static void HandleGet(Local<String> key,
92                        const v8::PropertyCallbackInfo<v8::Value>& info);
93  static void HandleSet(Local<String> key,
94                        Local<Value> value,
95                        const v8::PropertyCallbackInfo<v8::Value>& info);
96  static void HandleQuery(Local<String> key,
97                          const v8::PropertyCallbackInfo<v8::Integer>& info);
98
99  v8::Isolate* isolate() const { return CcTest::isolate(); }
100
101 private:
102  bool is_initialized_;
103  Persistent<Context> context_;
104
105  int get_count_;
106  int set_count_;
107  int query_count_;
108
109  static DeclarationContext* GetInstance(Local<Value> data);
110};
111
112
113DeclarationContext::DeclarationContext()
114    : is_initialized_(false), get_count_(0), set_count_(0), query_count_(0) {
115  // Do nothing.
116}
117
118
119void DeclarationContext::InitializeIfNeeded() {
120  if (is_initialized_) return;
121  Isolate* isolate = CcTest::isolate();
122  HandleScope scope(isolate);
123  Local<FunctionTemplate> function = FunctionTemplate::New(isolate);
124  Local<Value> data = External::New(CcTest::isolate(), this);
125  GetHolder(function)->SetNamedPropertyHandler(&HandleGet,
126                                               &HandleSet,
127                                               &HandleQuery,
128                                               0, 0,
129                                               data);
130  Local<Context> context = Context::New(isolate,
131                                        0,
132                                        function->InstanceTemplate(),
133                                        Local<Value>());
134  context_.Reset(isolate, context);
135  context->Enter();
136  is_initialized_ = true;
137  PostInitializeContext(context);
138}
139
140
141void DeclarationContext::Check(const char* source,
142                               int get, int set, int query,
143                               Expectations expectations,
144                               v8::Handle<Value> value) {
145  InitializeIfNeeded();
146  // A retry after a GC may pollute the counts, so perform gc now
147  // to avoid that.
148  CcTest::heap()->CollectGarbage(v8::internal::NEW_SPACE);
149  HandleScope scope(CcTest::isolate());
150  TryCatch catcher;
151  catcher.SetVerbose(true);
152  Local<Script> script =
153      Script::Compile(String::NewFromUtf8(CcTest::isolate(), source));
154  if (expectations == EXPECT_ERROR) {
155    CHECK(script.IsEmpty());
156    return;
157  }
158  CHECK(!script.IsEmpty());
159  Local<Value> result = script->Run();
160  CHECK_EQ(get, get_count());
161  CHECK_EQ(set, set_count());
162  CHECK_EQ(query, query_count());
163  if (expectations == EXPECT_RESULT) {
164    CHECK(!catcher.HasCaught());
165    if (!value.IsEmpty()) {
166      CHECK_EQ(value, result);
167    }
168  } else {
169    CHECK(expectations == EXPECT_EXCEPTION);
170    CHECK(catcher.HasCaught());
171    if (!value.IsEmpty()) {
172      CHECK_EQ(value, catcher.Exception());
173    }
174  }
175  // Clean slate for the next test.
176  CcTest::heap()->CollectAllAvailableGarbage();
177}
178
179
180void DeclarationContext::HandleGet(
181    Local<String> key,
182    const v8::PropertyCallbackInfo<v8::Value>& info) {
183  DeclarationContext* context = GetInstance(info.Data());
184  context->get_count_++;
185  info.GetReturnValue().Set(context->Get(key));
186}
187
188
189void DeclarationContext::HandleSet(
190    Local<String> key,
191    Local<Value> value,
192    const v8::PropertyCallbackInfo<v8::Value>& info) {
193  DeclarationContext* context = GetInstance(info.Data());
194  context->set_count_++;
195  info.GetReturnValue().Set(context->Set(key, value));
196}
197
198
199void DeclarationContext::HandleQuery(
200    Local<String> key,
201    const v8::PropertyCallbackInfo<v8::Integer>& info) {
202  DeclarationContext* context = GetInstance(info.Data());
203  context->query_count_++;
204  info.GetReturnValue().Set(context->Query(key));
205}
206
207
208DeclarationContext* DeclarationContext::GetInstance(Local<Value> data) {
209  void* value = Local<External>::Cast(data)->Value();
210  return static_cast<DeclarationContext*>(value);
211}
212
213
214v8::Handle<Value> DeclarationContext::Get(Local<String> key) {
215  return v8::Handle<Value>();
216}
217
218
219v8::Handle<Value> DeclarationContext::Set(Local<String> key,
220                                          Local<Value> value) {
221  return v8::Handle<Value>();
222}
223
224
225v8::Handle<Integer> DeclarationContext::Query(Local<String> key) {
226  return v8::Handle<Integer>();
227}
228
229
230// Test global declaration of a property the interceptor doesn't know
231// about and doesn't handle.
232TEST(Unknown) {
233  HandleScope scope(CcTest::isolate());
234  v8::V8::Initialize();
235
236  { DeclarationContext context;
237    context.Check("var x; x",
238                  1,  // access
239                  0, 0, EXPECT_RESULT, Undefined(CcTest::isolate()));
240  }
241
242  { DeclarationContext context;
243    context.Check("var x = 0; x",
244                  1,  // access
245                  1,  // initialization
246                  0, EXPECT_RESULT, Number::New(CcTest::isolate(), 0));
247  }
248
249  { DeclarationContext context;
250    context.Check("function x() { }; x",
251                  1,  // access
252                  0,
253                  0,
254                  EXPECT_RESULT);
255  }
256
257  { DeclarationContext context;
258    context.Check("const x; x",
259                  1,  // access
260                  0, 0, EXPECT_RESULT, Undefined(CcTest::isolate()));
261  }
262
263  { DeclarationContext context;
264    context.Check("const x = 0; x",
265                  1,  // access
266                  0,
267                  0,
268                  EXPECT_RESULT, Number::New(CcTest::isolate(), 0));
269  }
270}
271
272
273class AbsentPropertyContext: public DeclarationContext {
274 protected:
275  virtual v8::Handle<Integer> Query(Local<String> key) {
276    return v8::Handle<Integer>();
277  }
278};
279
280
281TEST(Absent) {
282  v8::Isolate* isolate = CcTest::isolate();
283  v8::V8::Initialize();
284  HandleScope scope(isolate);
285
286  { AbsentPropertyContext context;
287    context.Check("var x; x",
288                  1,  // access
289                  0, 0, EXPECT_RESULT, Undefined(isolate));
290  }
291
292  { AbsentPropertyContext context;
293    context.Check("var x = 0; x",
294                  1,  // access
295                  1,  // initialization
296                  0, EXPECT_RESULT, Number::New(isolate, 0));
297  }
298
299  { AbsentPropertyContext context;
300    context.Check("function x() { }; x",
301                  1,  // access
302                  0,
303                  0,
304                  EXPECT_RESULT);
305  }
306
307  { AbsentPropertyContext context;
308    context.Check("const x; x",
309                  1,  // access
310                  0, 0, EXPECT_RESULT, Undefined(isolate));
311  }
312
313  { AbsentPropertyContext context;
314    context.Check("const x = 0; x",
315                  1,  // access
316                  0, 0, EXPECT_RESULT, Number::New(isolate, 0));
317  }
318
319  { AbsentPropertyContext context;
320    context.Check("if (false) { var x = 0 }; x",
321                  1,  // access
322                  0, 0, EXPECT_RESULT, Undefined(isolate));
323  }
324}
325
326
327
328class AppearingPropertyContext: public DeclarationContext {
329 public:
330  enum State {
331    DECLARE,
332    INITIALIZE_IF_ASSIGN,
333    UNKNOWN
334  };
335
336  AppearingPropertyContext() : state_(DECLARE) { }
337
338 protected:
339  virtual v8::Handle<Integer> Query(Local<String> key) {
340    switch (state_) {
341      case DECLARE:
342        // Force declaration by returning that the
343        // property is absent.
344        state_ = INITIALIZE_IF_ASSIGN;
345        return Handle<Integer>();
346      case INITIALIZE_IF_ASSIGN:
347        // Return that the property is present so we only get the
348        // setter called when initializing with a value.
349        state_ = UNKNOWN;
350        return Integer::New(isolate(), v8::None);
351      default:
352        CHECK(state_ == UNKNOWN);
353        break;
354    }
355    // Do the lookup in the object.
356    return v8::Handle<Integer>();
357  }
358
359 private:
360  State state_;
361};
362
363
364TEST(Appearing) {
365  v8::V8::Initialize();
366  HandleScope scope(CcTest::isolate());
367
368  { AppearingPropertyContext context;
369    context.Check("var x; x",
370                  1,  // access
371                  0, 0, EXPECT_RESULT, Undefined(CcTest::isolate()));
372  }
373
374  { AppearingPropertyContext context;
375    context.Check("var x = 0; x",
376                  1,  // access
377                  1,  // initialization
378                  0, EXPECT_RESULT, Number::New(CcTest::isolate(), 0));
379  }
380
381  { AppearingPropertyContext context;
382    context.Check("function x() { }; x",
383                  1,  // access
384                  0,
385                  0,
386                  EXPECT_RESULT);
387  }
388
389  { AppearingPropertyContext context;
390    context.Check("const x; x",
391                  1,  // access
392                  0, 0, EXPECT_RESULT, Undefined(CcTest::isolate()));
393  }
394
395  { AppearingPropertyContext context;
396    context.Check("const x = 0; x",
397                  1,  // access
398                  0, 0, EXPECT_RESULT, Number::New(CcTest::isolate(), 0));
399  }
400}
401
402
403
404class ExistsInPrototypeContext: public DeclarationContext {
405 public:
406  ExistsInPrototypeContext() { InitializeIfNeeded(); }
407 protected:
408  virtual v8::Handle<Integer> Query(Local<String> key) {
409    // Let it seem that the property exists in the prototype object.
410    return Integer::New(isolate(), v8::None);
411  }
412
413  // Use the prototype as the holder for the interceptors.
414  virtual Local<ObjectTemplate> GetHolder(Local<FunctionTemplate> function) {
415    return function->PrototypeTemplate();
416  }
417};
418
419
420TEST(ExistsInPrototype) {
421  HandleScope scope(CcTest::isolate());
422
423  // Sanity check to make sure that the holder of the interceptor
424  // really is the prototype object.
425  { ExistsInPrototypeContext context;
426    context.Check("this.x = 87; this.x", 0, 0, 1, EXPECT_RESULT,
427                  Number::New(CcTest::isolate(), 87));
428  }
429
430  { ExistsInPrototypeContext context;
431    context.Check("var x; x",
432                  0,
433                  0,
434                  0,
435                  EXPECT_RESULT, Undefined(CcTest::isolate()));
436  }
437
438  { ExistsInPrototypeContext context;
439    context.Check("var x = 0; x",
440                  0,
441                  0,
442                  0,
443                  EXPECT_RESULT, Number::New(CcTest::isolate(), 0));
444  }
445
446  { ExistsInPrototypeContext context;
447    context.Check("const x; x",
448                  0,
449                  0,
450                  0,
451                  EXPECT_RESULT, Undefined(CcTest::isolate()));
452  }
453
454  { ExistsInPrototypeContext context;
455    context.Check("const x = 0; x",
456                  0,
457                  0,
458                  0,
459                  EXPECT_RESULT, Number::New(CcTest::isolate(), 0));
460  }
461}
462
463
464
465class AbsentInPrototypeContext: public DeclarationContext {
466 protected:
467  virtual v8::Handle<Integer> Query(Local<String> key) {
468    // Let it seem that the property is absent in the prototype object.
469    return Handle<Integer>();
470  }
471
472  // Use the prototype as the holder for the interceptors.
473  virtual Local<ObjectTemplate> GetHolder(Local<FunctionTemplate> function) {
474    return function->PrototypeTemplate();
475  }
476};
477
478
479TEST(AbsentInPrototype) {
480  v8::V8::Initialize();
481  HandleScope scope(CcTest::isolate());
482
483  { AbsentInPrototypeContext context;
484    context.Check("if (false) { var x = 0; }; x",
485                  0,
486                  0,
487                  0,
488                  EXPECT_RESULT, Undefined(CcTest::isolate()));
489  }
490}
491
492
493
494class ExistsInHiddenPrototypeContext: public DeclarationContext {
495 public:
496  ExistsInHiddenPrototypeContext() {
497    hidden_proto_ = FunctionTemplate::New(CcTest::isolate());
498    hidden_proto_->SetHiddenPrototype(true);
499  }
500
501 protected:
502  virtual v8::Handle<Integer> Query(Local<String> key) {
503    // Let it seem that the property exists in the hidden prototype object.
504    return Integer::New(isolate(), v8::None);
505  }
506
507  // Install the hidden prototype after the global object has been created.
508  virtual void PostInitializeContext(Handle<Context> context) {
509    Local<Object> global_object = context->Global();
510    Local<Object> hidden_proto = hidden_proto_->GetFunction()->NewInstance();
511    Local<Object> inner_global =
512        Local<Object>::Cast(global_object->GetPrototype());
513    inner_global->SetPrototype(hidden_proto);
514  }
515
516  // Use the hidden prototype as the holder for the interceptors.
517  virtual Local<ObjectTemplate> GetHolder(Local<FunctionTemplate> function) {
518    return hidden_proto_->InstanceTemplate();
519  }
520
521 private:
522  Local<FunctionTemplate> hidden_proto_;
523};
524
525
526TEST(ExistsInHiddenPrototype) {
527  HandleScope scope(CcTest::isolate());
528
529  { ExistsInHiddenPrototypeContext context;
530    context.Check("var x; x", 0, 0, 0, EXPECT_RESULT,
531                  Undefined(CcTest::isolate()));
532  }
533
534  { ExistsInHiddenPrototypeContext context;
535    context.Check("var x = 0; x", 0, 0, 0, EXPECT_RESULT,
536                  Number::New(CcTest::isolate(), 0));
537  }
538
539  { ExistsInHiddenPrototypeContext context;
540    context.Check("function x() { }; x",
541                  0,
542                  0,
543                  0,
544                  EXPECT_RESULT);
545  }
546
547  // TODO(mstarzinger): The semantics of global const is vague.
548  { ExistsInHiddenPrototypeContext context;
549    context.Check("const x; x", 0, 0, 0, EXPECT_RESULT,
550                  Undefined(CcTest::isolate()));
551  }
552
553  // TODO(mstarzinger): The semantics of global const is vague.
554  { ExistsInHiddenPrototypeContext context;
555    context.Check("const x = 0; x", 0, 0, 0, EXPECT_RESULT,
556                  Number::New(CcTest::isolate(), 0));
557  }
558}
559
560
561
562class SimpleContext {
563 public:
564  SimpleContext()
565      : handle_scope_(CcTest::isolate()),
566        context_(Context::New(CcTest::isolate())) {
567    context_->Enter();
568  }
569
570  ~SimpleContext() {
571    context_->Exit();
572  }
573
574  void Check(const char* source,
575             Expectations expectations,
576             v8::Handle<Value> value = Local<Value>()) {
577    HandleScope scope(context_->GetIsolate());
578    TryCatch catcher;
579    catcher.SetVerbose(true);
580    Local<Script> script =
581        Script::Compile(String::NewFromUtf8(context_->GetIsolate(), source));
582    if (expectations == EXPECT_ERROR) {
583      CHECK(script.IsEmpty());
584      return;
585    }
586    CHECK(!script.IsEmpty());
587    Local<Value> result = script->Run();
588    if (expectations == EXPECT_RESULT) {
589      CHECK(!catcher.HasCaught());
590      if (!value.IsEmpty()) {
591        CHECK_EQ(value, result);
592      }
593    } else {
594      CHECK(expectations == EXPECT_EXCEPTION);
595      CHECK(catcher.HasCaught());
596      if (!value.IsEmpty()) {
597        CHECK_EQ(value, catcher.Exception());
598      }
599    }
600  }
601
602 private:
603  HandleScope handle_scope_;
604  Local<Context> context_;
605};
606
607
608TEST(CrossScriptReferences) {
609  v8::Isolate* isolate = CcTest::isolate();
610  HandleScope scope(isolate);
611
612  { SimpleContext context;
613    context.Check("var x = 1; x",
614                  EXPECT_RESULT, Number::New(isolate, 1));
615    context.Check("var x = 2; x",
616                  EXPECT_RESULT, Number::New(isolate, 2));
617    context.Check("const x = 3; x", EXPECT_EXCEPTION);
618    context.Check("const x = 4; x", EXPECT_EXCEPTION);
619    context.Check("x = 5; x",
620                  EXPECT_RESULT, Number::New(isolate, 5));
621    context.Check("var x = 6; x",
622                  EXPECT_RESULT, Number::New(isolate, 6));
623    context.Check("this.x",
624                  EXPECT_RESULT, Number::New(isolate, 6));
625    context.Check("function x() { return 7 }; x()",
626                  EXPECT_RESULT, Number::New(isolate, 7));
627  }
628
629  { SimpleContext context;
630    context.Check("const x = 1; x",
631                  EXPECT_RESULT, Number::New(isolate, 1));
632    context.Check("var x = 2; x",  // assignment ignored
633                  EXPECT_RESULT, Number::New(isolate, 1));
634    context.Check("const x = 3; x", EXPECT_EXCEPTION);
635    context.Check("x = 4; x",  // assignment ignored
636                  EXPECT_RESULT, Number::New(isolate, 1));
637    context.Check("var x = 5; x",  // assignment ignored
638                  EXPECT_RESULT, Number::New(isolate, 1));
639    context.Check("this.x",
640                  EXPECT_RESULT, Number::New(isolate, 1));
641    context.Check("function x() { return 7 }; x",
642                  EXPECT_EXCEPTION);
643  }
644}
645
646
647TEST(CrossScriptReferencesHarmony) {
648  i::FLAG_use_strict = true;
649  i::FLAG_harmony_scoping = true;
650  i::FLAG_harmony_modules = true;
651
652  v8::Isolate* isolate = CcTest::isolate();
653  HandleScope scope(isolate);
654
655  const char* decs[] = {
656    "var x = 1; x", "x", "this.x",
657    "function x() { return 1 }; x()", "x()", "this.x()",
658    "let x = 1; x", "x", "this.x",
659    "const x = 1; x", "x", "this.x",
660    "module x { export let a = 1 }; x.a", "x.a", "this.x.a",
661    NULL
662  };
663
664  for (int i = 0; decs[i] != NULL; i += 3) {
665    SimpleContext context;
666    context.Check(decs[i], EXPECT_RESULT, Number::New(isolate, 1));
667    context.Check(decs[i+1], EXPECT_RESULT, Number::New(isolate, 1));
668    // TODO(rossberg): The current ES6 draft spec does not reflect lexical
669    // bindings on the global object. However, this will probably change, in
670    // which case we reactivate the following test.
671    if (i/3 < 2) {
672      context.Check(decs[i+2], EXPECT_RESULT, Number::New(isolate, 1));
673    }
674  }
675}
676
677
678TEST(CrossScriptConflicts) {
679  i::FLAG_use_strict = true;
680  i::FLAG_harmony_scoping = true;
681  i::FLAG_harmony_modules = true;
682
683  HandleScope scope(CcTest::isolate());
684
685  const char* firsts[] = {
686    "var x = 1; x",
687    "function x() { return 1 }; x()",
688    "let x = 1; x",
689    "const x = 1; x",
690    "module x { export let a = 1 }; x.a",
691    NULL
692  };
693  const char* seconds[] = {
694    "var x = 2; x",
695    "function x() { return 2 }; x()",
696    "let x = 2; x",
697    "const x = 2; x",
698    "module x { export let a = 2 }; x.a",
699    NULL
700  };
701
702  for (int i = 0; firsts[i] != NULL; ++i) {
703    for (int j = 0; seconds[j] != NULL; ++j) {
704      SimpleContext context;
705      context.Check(firsts[i], EXPECT_RESULT,
706                    Number::New(CcTest::isolate(), 1));
707      // TODO(rossberg): All tests should actually be errors in Harmony,
708      // but we currently do not detect the cases where the first declaration
709      // is not lexical.
710      context.Check(seconds[j],
711                    i < 2 ? EXPECT_RESULT : EXPECT_ERROR,
712                    Number::New(CcTest::isolate(), 2));
713    }
714  }
715}
716