// Copyright 2016 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: --no-analyze-environment-liveness

// The functions used for testing backtraces. They are at the top to make the
// testing of source line/column easier.

var Debug = debug.Debug;

var test_name;
var exception;
var begin_test_count = 0;
var end_test_count = 0;

// Initialize for a new test.
function BeginTest(name) {
  test_name = name;
  exception = null;
  begin_test_count++;
}

// Check result of a test.
function EndTest() {
  assertNull(exception, test_name + " / " + exception);
  end_test_count++;
}

// Check that two scope are the same.
function assertScopeMirrorEquals(scope1, scope2) {
  assertEquals(scope1.scopeType(), scope2.scopeType());
  assertEquals(scope1.scopeIndex(), scope2.scopeIndex());
  assertPropertiesEqual(scope1.scopeObject().value(),
                        scope2.scopeObject().value());
}

// Check that the scope chain contains the expected types of scopes.
function CheckScopeChain(scopes, gen) {
  var all_scopes = Debug.generatorScopes(gen);
  assertEquals(scopes.length, Debug.generatorScopeCount(gen));
  assertEquals(scopes.length, all_scopes.length,
               "FrameMirror.allScopes length");
  for (var i = 0; i < scopes.length; i++) {
    var scope = all_scopes[i];
    assertEquals(scopes[i], scope.scopeType(),
                 `Scope ${i} has unexpected type`);

    // Check the global object when hitting the global scope.
    if (scopes[i] == debug.ScopeType.Global) {
      // Objects don't have same class (one is "global", other is "Object",
      // so just check the properties directly.
      assertPropertiesEqual(this, scope.scopeObject().value());
    }
  }
}

// Check that the content of the scope is as expected. For functions just check
// that there is a function.
function CheckScopeContent(content, number, gen) {
  var scope = Debug.generatorScope(gen, number);
  var count = 0;
  for (var p in content) {
    var property_mirror = scope.scopeObject().property(p);
    if (content[p] === undefined) {
      assertTrue(property_mirror === undefined);
    } else {
      assertFalse(property_mirror === undefined,
                  'property ' + p + ' not found in scope');
    }
    if (typeof(content[p]) === 'function') {
      assertTrue(typeof property_mirror == "function");
    } else {
      assertEquals(content[p], property_mirror,
                   'property ' + p + ' has unexpected value');
    }
    count++;
  }

  // 'arguments' and might be exposed in the local and closure scope. Just
  // ignore this.
  var scope_size = scope.scopeObject().properties().length;
  if (scope.scopeObject().property('arguments') !== undefined) {
    scope_size--;
  }
  // Ditto for 'this'.
  if (scope.scopeObject().property('this') !== undefined) {
    scope_size--;
  }
  // Temporary variables introduced by the parser have not been materialized.
  assertTrue(scope.scopeObject().property('') === undefined);

  if (count != scope_size) {
    print('Names found in scope:');
    var names = scope.scopeObject().propertyNames();
    for (var i = 0; i < names.length; i++) {
      print(names[i]);
    }
  }
  assertEquals(count, scope_size);
}

// Simple empty closure scope.

function *gen1() {
  yield 1;
  return 2;
}

var g = gen1();
CheckScopeChain([debug.ScopeType.Local,
                 debug.ScopeType.Script,
                 debug.ScopeType.Global], g);
CheckScopeContent({}, 0, g);

// Closure scope with a parameter.

function *gen2(a) {
  yield a;
  return 2;
}

g = gen2(42);
CheckScopeChain([debug.ScopeType.Local,
                 debug.ScopeType.Script,
                 debug.ScopeType.Global], g);
CheckScopeContent({a: 42}, 0, g);

// Closure scope with a parameter.

function *gen3(a) {
  var b = 1
  yield a;
  return b;
}

g = gen3(0);
CheckScopeChain([debug.ScopeType.Local,
                 debug.ScopeType.Script,
                 debug.ScopeType.Global], g);
CheckScopeContent({a: 0, b: undefined}, 0, g);

g.next();  // Create b.
CheckScopeContent({a: 0, b: 1}, 0, g);

// Closure scope with a parameter.

function *gen4(a, b) {
  var x = 2;
  yield a;
  var y = 3;
  yield a;
  return b;
}

g = gen4(0, 1);
CheckScopeChain([debug.ScopeType.Local,
                 debug.ScopeType.Script,
                 debug.ScopeType.Global], g);
CheckScopeContent({a: 0, b: 1, x: undefined, y: undefined}, 0, g);

g.next();  // Create x.
CheckScopeContent({a: 0, b: 1, x: 2, y: undefined}, 0, g);

g.next();  // Create y.
CheckScopeContent({a: 0, b: 1, x: 2, y: 3}, 0, g);

// Closure introducing local variable using eval.

function *gen5(a) {
  eval('var b = 2');
  yield a;
  return b;
}

g = gen5(1);
g.next();
CheckScopeChain([debug.ScopeType.Local,
                 debug.ScopeType.Script,
                 debug.ScopeType.Global], g);
CheckScopeContent({a: 1, b: 2}, 0, g);

// Single empty with block.

function *gen6() {
  with({}) {
    yield 1;
  }
  yield 2;
  return 3;
}

g = gen6();
g.next();
CheckScopeChain([debug.ScopeType.With,
                 debug.ScopeType.Local,
                 debug.ScopeType.Script,
                 debug.ScopeType.Global], g);
CheckScopeContent({}, 0, g);

g.next();
CheckScopeChain([debug.ScopeType.Local,
                 debug.ScopeType.Script,
                 debug.ScopeType.Global], g);

// Nested empty with blocks.

function *gen7() {
  with({}) {
    with({}) {
      yield 1;
    }
    yield 2;
  }
  return 3;
}

g = gen7();
g.next();
CheckScopeChain([debug.ScopeType.With,
                 debug.ScopeType.With,
                 debug.ScopeType.Local,
                 debug.ScopeType.Script,
                 debug.ScopeType.Global], g);
CheckScopeContent({}, 0, g);

// Nested with blocks using in-place object literals.

function *gen8() {
  with({a: 1,b: 2}) {
    with({a: 2,b: 1}) {
      yield a;
    }
    yield a;
  }
  return 3;
}

g = gen8();
g.next();
CheckScopeChain([debug.ScopeType.With,
                 debug.ScopeType.With,
                 debug.ScopeType.Local,
                 debug.ScopeType.Script,
                 debug.ScopeType.Global], g);
CheckScopeContent({a: 2, b: 1}, 0, g);

g.next();
CheckScopeContent({a: 1, b: 2}, 0, g);

// Catch block.

function *gen9() {
  try {
    throw 42;
  } catch (e) {
    yield e;
  }
  return 3;
}

g = gen9();
g.next();
CheckScopeChain([debug.ScopeType.Catch,
                 debug.ScopeType.Local,
                 debug.ScopeType.Script,
                 debug.ScopeType.Global], g);
CheckScopeContent({e: 42}, 0, g);

// For statement with block scope.

function *gen10() {
  for (let i = 0; i < 42; i++) yield i;
  return 3;
}

g = gen10();
g.next();
CheckScopeChain([debug.ScopeType.Block,
                 debug.ScopeType.Local,
                 debug.ScopeType.Script,
                 debug.ScopeType.Global], g);
CheckScopeContent({i: 0}, 0, g);

g.next();
CheckScopeContent({i: 1}, 0, g);

// Nested generators.

var gen12;
function *gen11() {
  var b = 2;
  gen12 = function*() {
    var a = 1;
    yield 1;
    return b;
  }();

  var a = 0;
  yield* gen12;
}

gen11().next();
g = gen12;

CheckScopeChain([debug.ScopeType.Local,
                 debug.ScopeType.Closure,
                 debug.ScopeType.Script,
                 debug.ScopeType.Global], g);
CheckScopeContent({a: 1}, 0, g);
CheckScopeContent({b: 2}, 1, g);

// Set a variable in an empty scope.

function *gen13() {
  yield 1;
  return 2;
}

var g = gen13();
assertThrows(() => Debug.generatorScope(g, 0).setVariableValue("a", 42));
CheckScopeContent({}, 0, g);

// Set a variable in a simple scope.

function *gen14() {
  var a = 0;
  yield 1;
  yield a;
  return 2;
}

var g = gen14();
assertEquals(1, g.next().value);

CheckScopeContent({a: 0}, 0, g);

Debug.generatorScope(g, 0).setVariableValue("a", 1);
CheckScopeContent({a: 1}, 0, g);

assertEquals(1, g.next().value);

// Set a variable in nested with blocks using in-place object literals.

function *gen15() {
  var c = 3;
  with({a: 1,b: 2}) {
    var d = 4;
    yield a;
    var e = 5;
  }
  yield e;
  return e;
}

var g = gen15();
assertEquals(1, g.next().value);

CheckScopeChain([debug.ScopeType.With,
                 debug.ScopeType.Local,
                 debug.ScopeType.Script,
                 debug.ScopeType.Global], g);
CheckScopeContent({a: 1, b: 2}, 0, g);
CheckScopeContent({c: 3, d: 4, e: undefined}, 1, g);

// Variables don't exist in given scope.
assertThrows(() => Debug.generatorScope(g, 0).setVariableValue("c", 42));
assertThrows(() => Debug.generatorScope(g, 1).setVariableValue("a", 42));

// Variables in with scope are immutable.
assertThrows(() => Debug.generatorScope(g, 0).setVariableValue("a", 3));
assertThrows(() => Debug.generatorScope(g, 0).setVariableValue("b", 3));

Debug.generatorScope(g, 1).setVariableValue("c", 1);
Debug.generatorScope(g, 1).setVariableValue("e", 42);

CheckScopeContent({a: 1, b: 2}, 0, g);
CheckScopeContent({c: 1, d: 4, e: 42}, 1, g);
assertEquals(5, g.next().value);  // Initialized after set.

CheckScopeChain([debug.ScopeType.Local,
                 debug.ScopeType.Script,
                 debug.ScopeType.Global], g);

Debug.generatorScope(g, 0).setVariableValue("e", 42);

CheckScopeContent({c: 1, d: 4, e: 42}, 0, g);
assertEquals(42, g.next().value);

// Set a variable in nested with blocks using in-place object literals plus a
// nested block scope.

function *gen16() {
  var c = 3;
  with({a: 1,b: 2}) {
    let d = 4;
    yield a;
    let e = 5;
    yield d;
  }
  return 3;
}

var g = gen16();
g.next();

CheckScopeChain([debug.ScopeType.Block,
                 debug.ScopeType.With,
                 debug.ScopeType.Local,
                 debug.ScopeType.Script,
                 debug.ScopeType.Global], g);
CheckScopeContent({d: 4, e: undefined}, 0, g);
CheckScopeContent({a: 1, b: 2}, 1, g);
CheckScopeContent({c: 3}, 2, g);

Debug.generatorScope(g, 0).setVariableValue("d", 1);
CheckScopeContent({d: 1, e: undefined}, 0, g);

assertEquals(1, g.next().value);

// Set variable in catch block.

var yyzyzzyz = 4829;
let xxxyyxxyx = 42284;
function *gen17() {
  try {
    throw 42;
  } catch (e) {
    yield e;
    yield e;
  }
  return 3;
}

g = gen17();
g.next();

CheckScopeChain([debug.ScopeType.Catch,
                 debug.ScopeType.Local,
                 debug.ScopeType.Script,
                 debug.ScopeType.Global], g);
CheckScopeContent({e: 42}, 0, g);
CheckScopeContent({xxxyyxxyx: 42284,
                   printProtocolMessages : printProtocolMessages,
                   activeWrapper : activeWrapper,
                   DebugWrapper : DebugWrapper
                  }, 2, g);

Debug.generatorScope(g, 0).setVariableValue("e", 1);
CheckScopeContent({e: 1}, 0, g);

assertEquals(1, g.next().value);

// Script scope.
Debug.generatorScope(g, 2).setVariableValue("xxxyyxxyx", 42);
assertEquals(42, xxxyyxxyx);

// Global scope.
assertThrows(() => Debug.generatorScope(g, 3).setVariableValue("yyzyzzyz", 42));
assertEquals(4829, yyzyzzyz);