// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Flags: --allow-natives-syntax


assertEquals(this.__proto__, Object.prototype);

function TestAddingPropertyToGlobalPrototype() {
  let foo_func_called = 0;
  let bar_func_called = 0;

  function Foo() {}
  Foo.prototype.func = function() { ++foo_func_called; }

  delete this.func;
  this.__proto__ = Foo.prototype;

  function Bar() {}
  Bar.prototype = this;

  let o = new Bar();

  for (let i = 0; i < 11; ++i) {
    // First, the property is looked up from Foo.
    o.func();

    // Add the property to Bar which is a Global-mode prototype between o
    // and Foo. In the next iteration, it's looked up from Bar.
    if (i == 9) {
      Bar.prototype.func = function() { ++bar_func_called; }
    }
  }

  assertEquals(10, foo_func_called);
  assertEquals(1, bar_func_called);
}

TestAddingPropertyToGlobalPrototype();


// Same as TestAddingPropertyToGlobalPrototype, but using o["foo"] access
// instead of o.foo.
function TestAddingPropertyToGlobalPrototype2() {
  let foo_func_called = 0;
  let bar_func_called = 0;
  let name = "func";

  function Foo() {}
  Foo.prototype[name] = function() { ++foo_func_called; }

  delete this[name];
  this.__proto__ = Foo.prototype;

  function Bar() {}
  Bar.prototype = this;

  let o = new Bar();

  for (let i = 0; i < 11; ++i) {
    // First, the property is looked up from Foo.
    o[name]();

    // Add the property to Bar which is a Global-mode prototype between o
    // and Foo. In the next iteration, it's looked up from Bar.
    if (i == 9) {
      Bar.prototype[name] = function() { ++bar_func_called; }
    }
  }

  assertEquals(10, foo_func_called);
  assertEquals(1, bar_func_called);
}

TestAddingPropertyToGlobalPrototype2();


function TestAddingPropertyToGlobalPrototype_DefineProperty() {
  let foo_func_called = 0;
  let bar_func_called = 0;

  function Foo() {}
  Foo.prototype.func = function() { ++foo_func_called; }

  delete this.func;
  this.__proto__ = Foo.prototype;

  function Bar() {}
  Bar.prototype = this;

  let o = new Bar();

  for (let i = 0; i < 11; ++i) {
    // First, the property is looked up from Foo.
    o.func();

    // Add the property to Bar which is a Global-mode prototype between o
    // and Foo. In the next iteration, it's looked up from Bar.
    if (i == 9) {
      Object.defineProperty(Bar.prototype, "func",
                            {
                              value: function() { ++bar_func_called; },
                              configurable:true
                            });
    }
  }

  assertEquals(10, foo_func_called);
  assertEquals(1, bar_func_called);
}

TestAddingPropertyToGlobalPrototype_DefineProperty();


function TestAddingAccessorPropertyToGlobalPrototype() {
  let foo_func_called = 0;
  let bar_func_called = 0;

  function Foo() {}
  Foo.prototype.func = function() { ++foo_func_called; }

  delete this.func;
  this.__proto__ = Foo.prototype;

  function Bar() {}
  Bar.prototype = this;

  let o = new Bar();

  for (let i = 0; i < 11; ++i) {
    // First, the property is looked up from Foo.
    o.func();

    // Add the property to Bar which is a Global-mode prototype between o
    // and Foo. In the next iteration, it's looked up from Bar.
    if (i == 9) {
      Object.defineProperty(Bar.prototype, "func",
                            {
                              get: function() { return function() { ++bar_func_called; }},
                              configurable: true
                            });
    }
  }

  assertEquals(10, foo_func_called);
  assertEquals(1, bar_func_called);
}

TestAddingAccessorPropertyToGlobalPrototype();


function TestRemovingPropertyFromGlobalPrototype() {
  let foo_func_called = 0;
  let bar_func_called = 0;

  function Foo() {}
  Foo.prototype.func = function() { ++foo_func_called; }

  delete this.func;
  this.__proto__ = Foo.prototype;

  function Bar() {}
  Bar.prototype = this;
  Bar.prototype.func = function() { ++bar_func_called; }

  let o = new Bar();

  for (let i = 0; i < 11; ++i) {
    // First, the property is looked up from Bar.
    o.func();

    // Remove the property from Bar which is a Global-mode prototype between
    // o and Foo. In the next iteration, it's looked up from Foo.
    if (i == 9) {
      delete Bar.prototype.func;
    }
  }

  assertEquals(1, foo_func_called);
  assertEquals(10, bar_func_called);
}

TestRemovingPropertyFromGlobalPrototype();


// Same as TestRemovingPropertyFromGlobalPrototype, but using o["foo"] access
// instead of o.foo.
function TestRemovingPropertyFromGlobalPrototype2() {
  let foo_func_called = 0;
  let bar_func_called = 0;
  let name = "func";

  function Foo() {}
  Foo.prototype[name] = function() { ++foo_func_called; }

  this.__proto__ = Foo.prototype;

  function Bar() {}
  Bar.prototype = this;
  Bar.prototype[name] = function() { ++bar_func_called; }

  let o = new Bar();

  for (let i = 0; i < 11; ++i) {
    // First, the property is looked up from Bar.
    o[name]();

    // Remove the property from Bar which is a Global-mode prototype between
    // o and Foo. In the next iteration, it's looked up from Foo.
    if (i == 9) {
      delete Bar.prototype[name];
    }
  }

  assertEquals(1, foo_func_called);
  assertEquals(10, bar_func_called);
}

TestRemovingPropertyFromGlobalPrototype2();


function TestAddingPropertyToGlobalPrototype_MonomorphicDot() {
  function DoMonomorphicStoreToPrototypeDot(p, f, do_delete=true) {
    p.func = f;
    if (do_delete) {
      delete p.func;
    }
  }
  let foo_func_called = 0;
  let bar_func_called = 0;

  function Foo() {}
  Foo.prototype.func = function() { ++foo_func_called; }

  delete this.func;
  this.__proto__ = Foo.prototype;

  function Bar() {}
  Bar.prototype = this;

  function bar_func() {
    ++bar_func_called;
  }
  DoMonomorphicStoreToPrototypeDot(Bar.prototype, bar_func);
  DoMonomorphicStoreToPrototypeDot(Bar.prototype, bar_func);
  DoMonomorphicStoreToPrototypeDot(Bar.prototype, bar_func);

  let o = new Bar();

  for (let i = 0; i < 11; ++i) {
    // First, the property is looked up from Foo.
    o.func();

    // Add the property to Bar which is a Global-mode prototype between o
    // and Foo. In the next iteration, it's looked up from Bar.
    if (i == 9) {
      DoMonomorphicStoreToPrototypeDot(Bar.prototype, bar_func, false);
    }
  }

  assertEquals(10, foo_func_called);
  assertEquals(1, bar_func_called);
}

TestAddingPropertyToGlobalPrototype_MonomorphicDot();


function TestAddingPropertyToGlobalPrototype_MonomorphicBrackets() {
  function DoMonomorphicStoreToPrototypeBrackets(p, name, f, do_delete=true) {
    p[name] = f;
    if (do_delete) {
      delete p[name];
    }
  }
  let foo_func_called = 0;
  let bar_func_called = 0;
  let name = "func";

  function Foo() {}
  Foo.prototype[name] = function() { ++foo_func_called; }

  delete this[name];
  this.__proto__ = Foo.prototype;

  function Bar() {}
  Bar.prototype = this;

  function bar_func() {
    ++bar_func_called;
  }
  DoMonomorphicStoreToPrototypeBrackets(Bar.prototype, name, bar_func);
  DoMonomorphicStoreToPrototypeBrackets(Bar.prototype, name, bar_func);
  DoMonomorphicStoreToPrototypeBrackets(Bar.prototype, name, bar_func);

  let o = new Bar();

  for (let i = 0; i < 11; ++i) {
    // First, the property is looked up from Foo.
    o.func();

    // Add the property to Bar which is a Global-mode prototype between o
    // and Foo. In the next iteration, it's looked up from Bar.
    if (i == 9) {
      DoMonomorphicStoreToPrototypeBrackets(Bar.prototype, name, bar_func, false);
    }
  }

  assertEquals(10, foo_func_called);
  assertEquals(1, bar_func_called);
}

TestAddingPropertyToGlobalPrototype_MonomorphicBrackets();


function TestReconfiguringDataToAccessor() {
  let setter_called = 0;
  let name = "prop";

  delete this[name];
  this.__proto__ = Object.prototype;

  function Bar() {}
  Bar.prototype = this;

  Object.defineProperty(Bar.prototype, name, {value: 1000, writable: true, configurable: true});

  for (let i = 0; i < 11; ++i) {
    let obj1 = new Bar();
    if (i < 10) {
      assertEquals(1000, obj1.prop);
    } else {
      assertEquals(3000, obj1.prop);
    }

    // Add the property into the object.
    obj1.prop = 2000;
    if (i < 10) {
      assertEquals(2000, obj1.prop);
    } else {
      assertEquals(3000, obj1.prop);
    }

    // Make "prop" an accessor property in the prototype.
    if (i == 9) {
      Object.defineProperty(Bar.prototype, name,
                            {get: () => 3000,
                             set: function(val) { ++setter_called; }});
    }
  }
  assertEquals(1, setter_called);
}

TestReconfiguringDataToAccessor();