// Copyright 2015 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: --strong-mode --allow-natives-syntax

function getSloppyArguments() {
  return arguments;
}

function getObjects() {
  "use strict";
  return [
    {},
    Object(""),
    [],
    (function(){}),
    (class Foo {}),
    getSloppyArguments(),
    arguments,
    new Date()
  ];
}

//TODO(conradw): add tests for non-inheritance once semantics are implemented.
function getNonInheritingObjects() {
  "use strong";
  return [
    Object(""),
    [],
    new Uint32Array(0)
  ];
}

function readFromObjectElementSloppy(o) {
  return o[0];
}

function readFromObjectElementSparseSloppy(o) {
  return o[100000];
}

function readFromObjectElementNonSmiSloppy(o) {
  return o[3000000000];
}

function readFromObjectNonIndexSloppy(o) {
  return o[5000000000];
}

function readFromObjectElementVarSloppy(o) {
  var a = 0;
  return o[a];
}

function readFromObjectElementSparseVarSloppy(o) {
  var a = 100000;
  return o[a];
}

function readFromObjectElementNonSmiVarSloppy(o) {
  var a = 3000000000;
  return o[a];
}

function readFromObjectNonIndexVarSloppy(o) {
  var a = 5000000000;
  return o[a];
}

function readFromObjectElementStrong(o) {
  "use strong";
  return o[0];
}

function readFromObjectElementSparseStrong(o) {
  "use strong";
  return o[100000];
}

function readFromObjectElementNonSmiStrong(o) {
  "use strong";
  return o[3000000000];
}

function readFromObjectNonIndexStrong(o) {
  "use strong";
  return o[5000000000];
}

function readFromObjectElementLetStrong(o) {
  "use strong";
  let a = 0;
  return o[a];
}

function readFromObjectElementSparseLetStrong(o) {
  "use strong";
  let a = 100000;
  return o[a];
}

function readFromObjectElementNonSmiLetStrong(o) {
  "use strong";
  let a = 3000000000;
  return o[a];
}

function readFromObjectNonIndexLetStrong(o) {
  "use strong";
  let a = 5000000000;
  return o[a];
}

function getDescs(x) {
  return [
    {value: x},
    {configurable: true, enumerable: true, writable: true, value: x},
    {configurable: true, enumerable: true, get: (function() {return x}) },
  ];
}

function assertStrongSemantics(func, object) {
  %DeoptimizeFunction(func);
  %ClearFunctionTypeFeedback(func);
  assertThrows(function(){func(object)}, TypeError);
  assertThrows(function(){func(object)}, TypeError);
  assertThrows(function(){func(object)}, TypeError);
  %OptimizeFunctionOnNextCall(func);
  assertThrows(function(){func(object)}, TypeError);
  %DeoptimizeFunction(func);
  assertThrows(function(){func(object)}, TypeError);
}

function assertSloppySemantics(func, object) {
  %DeoptimizeFunction(func);
  %ClearFunctionTypeFeedback(func);
  assertDoesNotThrow(function(){func(object)});
  assertDoesNotThrow(function(){func(object)});
  assertDoesNotThrow(function(){func(object)});
  %OptimizeFunctionOnNextCall(func);
  assertDoesNotThrow(function(){func(object)});
  %DeoptimizeFunction(func);
  assertDoesNotThrow(function(){func(object)});
}

(function () {
  "use strict";

  let goodKeys = [
    "0",
    "100000",
    "3000000000",
    "5000000000"
  ]

  let badKeys = [
    "bar",
    "1",
    "100001",
    "3000000001",
    "5000000001"
  ];

  let values = [
    "string",
    1,
    100001,
    30000000001,
    50000000001,
    NaN,
    {},
    undefined
  ];

  let literals = [0, NaN, true, ""];

  let badAccessorDescs = [
    { set: (function(){}) },
    { configurable: true, enumerable: true, set: (function(){}) }
  ];

  let readSloppy = [
    readFromObjectElementSloppy,
    readFromObjectElementSparseSloppy,
    readFromObjectElementNonSmiSloppy,
    readFromObjectNonIndexSloppy,
    readFromObjectElementVarSloppy,
    readFromObjectElementSparseVarSloppy,
    readFromObjectElementNonSmiVarSloppy,
    readFromObjectNonIndexVarSloppy
  ];

  let readStrong = [
    readFromObjectElementStrong,
    readFromObjectElementSparseStrong,
    readFromObjectElementNonSmiStrong,
    readFromObjectNonIndexStrong,
    readFromObjectElementLetStrong,
    readFromObjectElementSparseLetStrong,
    readFromObjectElementNonSmiLetStrong,
    readFromObjectNonIndexLetStrong
  ];

  let dummyProto = {};
  for (let key of goodKeys) {
    Object.defineProperty(dummyProto, key, { value: undefined });
  }

  let dummyAccessorProto = {};
  for (let key of goodKeys) {
    Object.defineProperty(dummyAccessorProto, key, { set: (function(){}) })
  }

  // String literals/objects should not throw on character index access
  assertDoesNotThrow(function() {"use strong"; return "string"[0]; });
  assertDoesNotThrow(function() {"use strong"; return Object("string")[0]; });

  // Attempting to access a property on an object with no defined properties
  // should throw.
  for (let object of getObjects().concat(getNonInheritingObjects(), literals)) {
    for (let func of readStrong) {
      assertStrongSemantics(func, object);
    }
    for (let func of readSloppy) {
      assertSloppySemantics(func, object);
    }
  }
  for (let object of getObjects()) {
    // Accessing a property which is on the prototype chain of the object should
    // not throw.
    object.__proto__ = dummyProto;
    for (let key of goodKeys) {
      for (let func of readStrong.concat(readSloppy)) {
        assertSloppySemantics(func, object);
      }
    }
  }
  // Properties with accessor descriptors missing 'get' should throw on access.
  for (let desc of badAccessorDescs) {
    for (let key of goodKeys) {
      for (let object of getObjects()) {
        Object.defineProperty(object, key, desc);
        for (let func of readStrong) {
          assertStrongSemantics(func, object);
        }
        for (let func of readSloppy) {
          assertSloppySemantics(func, object);
        }
      }
    }
  }
  // The same behaviour should be expected for bad accessor properties on the
  // prototype chain.
  for (let object of getObjects()) {
    object.__proto__ = dummyAccessorProto;
    for (let func of readStrong) {
      assertStrongSemantics(func, object);
    }
    for (let func of readSloppy) {
      assertSloppySemantics(func, object);
    }
  }
  assertThrows(function(){"use strong"; typeof ({})[1];}, TypeError);
  assertThrows(
    function(){"use strong"; typeof ({})[1] === "undefined"}, TypeError);
})();
