/*
 * Copyright 2008 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.dev.jjs.test;

import com.google.gwt.core.client.JavaScriptException;
import com.google.gwt.dev.jjs.test.compilertests.MethodNamedSameAsClass;
import com.google.gwt.junit.client.GWTTestCase;

import junit.framework.Assert;

import java.util.ArrayList;
import java.util.EventListener;
import java.util.Locale;

/**
 * Miscellaneous tests of the Java to JavaScript compiler.
 */
@SuppressWarnings("unused")
public class CompilerTest extends GWTTestCase {
  interface MyMap {
    Object get(String key);
  }

  interface Silly { }

  @SuppressWarnings("ComparableType")
  interface SillyComparable<T extends Silly> extends Comparable<T> {
    @Override
    int compareTo(T obj);
  }

  private abstract static class AbstractSuper {
    public static String foo() {
      if (FALSE) {
        // prevent inlining
        return foo();
      }
      return "AbstractSuper";
    }
  }

  private abstract static class Apple implements Fruit {
  }

  private abstract static class Bm2BaseEvent {
  }

  private abstract static class Bm2ComponentEvent extends Bm2BaseEvent {
  }

  private static class Bm2KeyNav<E extends Bm2ComponentEvent> implements
      Bm2Listener<E> {
    @Override
    public int handleEvent(Bm2ComponentEvent ce) {
      return 5;
    }
  }

  private interface Bm2Listener<E extends Bm2BaseEvent> extends EventListener {
    int handleEvent(E be);
  }

  /**
   * Used in {@link #testSwitchOnEnumTypedThis()}.
   */
  private static  enum ChangeDirection {
    NEGATIVE, POSITIVE;

    public String getText() {
      switch (this) {
        case POSITIVE:
          return "POSITIVE";
        case NEGATIVE:
          return "NEGATIVE";
        default:
          throw new IllegalArgumentException("Unhandled change direction: "
              + this);
      }
    }
  }

  private static class ConcreteSub extends AbstractSuper {
    public static String foo() {
      if (FALSE) {
        // prevent inlining
        return foo();
      }
      return "ConcreteSub";
    }
  }

  private static interface Fruit {
  }

  private static class Fuji extends Apple {
  }

  private static class Granny extends Apple {
  }

  private static class NonSideEffectCauser {
    public static final String NOT_A_COMPILE_TIME_CONSTANT = null;
  }

  private static class SideEffectCauser {
    private static Object instance = new Object();

    static {
      CompilerTest.sideEffectChecker++;
    }

    public static Object causeClinitSideEffect() {
      return instance;
    }
  }

  private static class SideEffectCauser2 {
    static {
      CompilerTest.sideEffectChecker++;
    }

    public static Object causeClinitSideEffect() {
      return null;
    }
  }

  private static class SideEffectCauser3 {
    static {
      CompilerTest.sideEffectChecker++;
    }

    public static void causeClinitSideEffect() {
    }
  }

  private static class SideEffectCauser4 {
    public static String causeClinitSideEffectOnRead = "foo";

    static {
      CompilerTest.sideEffectChecker++;
    }
  }

  private static class SideEffectCauser5 {
    public static String causeClinitSideEffectOnRead = "bar";

    static {
      CompilerTest.sideEffectChecker++;
    }
  }

  private static class SideEffectCauser6 extends SideEffectCauser6Super {
    public static String causeClinitSideEffectOnRead = "bar";
  }

  private static class SideEffectCauser6Super {
    static {
      CompilerTest.sideEffectChecker++;
    }
  }

  /**
   * Ensures that a superclass's clinit is run before supercall arguments are
   * evaluated.
   */
  private static class SideEffectCauser7 extends SideEffectCauser7Super {
    public SideEffectCauser7() {
      super(SideEffectCauser7Super.SHOULD_BE_TRUE);
    }
  }

  private static class SideEffectCauser7Super {
    public static boolean SHOULD_BE_TRUE = false;

    static {
      SHOULD_BE_TRUE = true;
    }

    public SideEffectCauser7Super(boolean should_be_true) {
      if (should_be_true) {
        CompilerTest.sideEffectChecker++;
      }
    }
  }

  /**
   * Used in test {@link #testPrivateOverride()}.
   */
  private static class TpoChild extends TpoParent {
    @Override
    public int foo() {
      return callSuper();
    }

    private int callSuper() {
      return super.foo();
    }
  }

  /**
   * Used in test {@link #testPrivateOverride()}.
   */
  private static class TpoGrandparent {
    public int foo() {
      return 0;
    }
  }

  /**
   * Used in test {@link #testPrivateOverride()}.
   */
  private static class TpoJsniChild extends TpoJsniParent {
    @Override
    public native int foo() /*-{
      return this.@com.google.gwt.dev.jjs.test.CompilerTest$TpoJsniChild::callSuper()();
    }-*/;

    private int callSuper() {
      return super.foo();
    }
  }

  /**
   * Used in test {@link #testPrivateOverride()}.
   */
  private static class TpoJsniGrandparent {
    public int foo() {
      return 0;
    }
  }

  /**
   * Used in test {@link #testPrivateOverride()}.
   */
  private static class TpoJsniParent extends TpoJsniGrandparent {
    @Override
    public native int foo() /*-{
      // This should call callSuper in TpoJsniParent, not the one
      // in TpoJsniChild      
      return this.@com.google.gwt.dev.jjs.test.CompilerTest$TpoJsniParent::callSuper()();
    }-*/;

    private int callSuper() {
      return super.foo();
    }
  }

  /**
   * Used in test {@link #testPrivateOverride()}.
   */
  private static class TpoParent extends TpoGrandparent {
    @Override
    public int foo() {
      // This should call the callSuper in TpoJsniParent, not the one
      // in TpoJsniChild
      return callSuper();
    }

    private int callSuper() {
      return super.foo();
    }
  }

  private static final class UninstantiableType {
    public Object field;
    public int intField;

    private UninstantiableType() {
    }

    public int returnInt() {
      return intField;
    }

    public Object returnNull() {
      return null;
    }
  }

  private static volatile boolean FALSE = false;

  private static int sideEffectChecker;

  private static volatile int THREE = 3;

  private static volatile boolean TRUE = true;

  private static volatile boolean volatileBoolean;
  private static volatile int volatileInt;
  private static volatile long volatileLong;
  private static volatile UninstantiableType volatileUninstantiableType;

  private static native void accessUninstantiableField(UninstantiableType u) /*-{
    u.@com.google.gwt.dev.jjs.test.CompilerTest$UninstantiableType::field.toString();
  }-*/;

  private static native void accessUninstantiableMethod(UninstantiableType u) /*-{
    u.@com.google.gwt.dev.jjs.test.CompilerTest$UninstantiableType::returnNull()();
  }-*/;

  private static String barShouldInline() {
    return "bar";
  }

  private static void foo(String string) {
  }

  private static void foo(Throwable throwable) {
  }

  private static native String jsniReadSideEffectCauser5() /*-{
    return @com.google.gwt.dev.jjs.test.CompilerTest$SideEffectCauser5::causeClinitSideEffectOnRead;
  }-*/;

  private Integer boxedInteger = 0;

  @Override
  public String getModuleName() {
    return "com.google.gwt.dev.jjs.CompilerSuite";
  }

  public void testArrayAccessSideEffect() {
    if (System.getProperty("user.agent", "safari").equals("gecko1_8")) {
      // Firefox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1259605
      return;
    }

    int index = 1;
    int[] array = null;
    try {
      // index should be set before exception is thrown
      array[index = 2]++;
      fail("null reference expected");
    } catch (Exception e) {
      // expected
    }
    assertEquals(2, index);
  }

  public void testArrayStore() {
    Object[][] oaa;
    oaa = new Object[4][4];
    oaa[0][0] = "foo";
    assertEquals("foo", oaa[0][0]);

    oaa = new Object[4][];
    oaa[0] = new Object[4];
    oaa[0][0] = "bar";
    assertEquals("bar", oaa[0][0]);

    Apple[] apple = TRUE ? new Granny[3] : new Apple[3];
    Apple g = TRUE ? (Apple) new Granny() : (Apple) new Fuji();
    Apple a = apple[0] = g;
    assertEquals(g, a);

    byte[] bytes = new byte[10];
    bytes[0] = (byte) '1';
    assertEquals(49, bytes[0]);

    Object[] disguisedApple = apple;
    expectArrayStoreException(disguisedApple, "Hello");
    expectArrayStoreException(disguisedApple, true);
    expectArrayStoreException(disguisedApple, 42.0);
  }

  private void expectArrayStoreException(Object[] array, Object o) {
    try {
      array[0] = o;
      fail("Expected ArrayStoreException");
    } catch (ArrayStoreException e) {
    }
  }

  public void testUnboxedArrays() {
    Double[] doubleArray = new Double[1];
    doubleArray[0] = 42.0;

    expectArrayStoreException(doubleArray, true);
    expectArrayStoreException(doubleArray, "Hello");
    expectArrayStoreException(doubleArray, new Integer(23));

    Boolean[] booleanArray = new Boolean[1];
    booleanArray[0] = true;

    expectArrayStoreException(booleanArray, 42.0);
    expectArrayStoreException(booleanArray, "Hello");
    expectArrayStoreException(booleanArray, new Integer(23));

    String[] stringArray = new String[1];
    stringArray[0] = "Hello";

    expectArrayStoreException(stringArray, 42.0);
    expectArrayStoreException(stringArray, true);
    expectArrayStoreException(stringArray, new Integer(23));
  }

  /**
   * Issue 3064: when the implementation of an interface comes from a
   * superclass, it can be necessary to add a bridge method that overrides the
   * interface method and calls the inherited method.
   */
  public void testBridgeMethods1() {
    abstract class AbstractFoo {
      public int compareTo(AbstractFoo o) {
        return 0;
      }
    }

    @SuppressWarnings("ComparableType")
    class MyFoo extends AbstractFoo implements Comparable<AbstractFoo> {
    }

    /*
     * This subclass adds an extra curve ball: only one bridge method should be
     * created, in class MyFoo. MyFooSub should not get its own but instead use
     * the inherited one. Otherwise, two final methods with identical signatures
     * would override each other.
     */
    class MyFooSub extends MyFoo {
    }

    Comparable<AbstractFoo> comparable1 = new MyFooSub();
    assertEquals(0, comparable1.compareTo(new MyFoo()));

    Comparable<AbstractFoo> comparable2 = new MyFoo();
    assertEquals(0, comparable2.compareTo(new MyFooSub()));
  }

  /**
   * Issue 3304. Doing superclasses first is tricky when the superclass is a raw
   * type.
   */
  @SuppressWarnings("unchecked")
  public void testBridgeMethods2() {
    Bm2KeyNav<?> obs = new Bm2KeyNav() {
    };
    assertEquals(5, obs.handleEvent(null));
  }

  /**
   * When adding a bridge method, be sure to handle transitive overrides
   * relationships.
   */
  public void testBridgeMethods3() {
    class AbstractFoo implements Silly {
      public int compareTo(AbstractFoo obj) {
        if (FALSE) {
          return compareTo(obj);
        }
        return 0;
      }
    }
    class MyFoo extends AbstractFoo implements SillyComparable<AbstractFoo> {
    }
    
    assertEquals(0, new MyFoo().compareTo(new MyFoo()));
  }

  /**
   * Issue 3517. Sometimes JDT adds a bridge method when a subclass's method
   * differs only by return type. In some versions of GWT, this has resulted in
   * a bridge method overriding its own target, and eventually TypeTightener
   * producing an infinite recursion.
   */
  public void testBridgeMethods4() {
    abstract class MyMapAbstract<V> implements MyMap {
      @Override
      public String get(String key) {
        return null;
      }
    }

    final class MyMapImpl<V> extends MyMapAbstract<V> {
    }

    MyMapImpl<String> mmap = new MyMapImpl<String>();
    assertNull(mmap.get("foo"));
  }

  public void testCastOptimizer() {
    Granny g = new Granny();
    Apple a = g;
    Fruit f = g;
    a = (Apple) f;
    g = (Granny) a;
    g = (Granny) f;
  }

  public void testClassLiterals() {
    if (Object.class.getName().startsWith("Class$")) {
      // If class metadata is disabled
      return;
    }

    assertEquals("void", void.class.toString());
    assertEquals("int", int.class.toString());
    assertEquals("class java.lang.String", String.class.toString());
    assertEquals("class com.google.gwt.dev.jjs.test.CompilerTest",
        CompilerTest.class.toString());
    assertEquals(
        "class com.google.gwt.dev.jjs.test.CompilerTest$UninstantiableType",
        UninstantiableType.class.toString());
    assertEquals("interface com.google.gwt.dev.jjs.test.CompilerTest$Fruit",
        Fruit.class.toString());
    assertEquals("class [I", int[].class.toString());
    assertEquals("class [Ljava.lang.String;", String[].class.toString());
    assertEquals("class [Lcom.google.gwt.dev.jjs.test.CompilerTest;",
        CompilerTest[].class.toString());
    assertEquals(
        "class [Lcom.google.gwt.dev.jjs.test.CompilerTest$UninstantiableType;",
        UninstantiableType[].class.toString());
    assertEquals("class [Lcom.google.gwt.dev.jjs.test.CompilerTest$Fruit;",
        Fruit[].class.toString());
  }

  public void testClinitSideEffectInlining() {
    sideEffectChecker = 0;
    SideEffectCauser.causeClinitSideEffect();
    assertEquals(1, sideEffectChecker);
    SideEffectCauser2.causeClinitSideEffect();
    assertEquals(2, sideEffectChecker);
    SideEffectCauser3.causeClinitSideEffect();
    assertEquals(3, sideEffectChecker);
    String foo = SideEffectCauser4.causeClinitSideEffectOnRead;
    assertEquals(4, sideEffectChecker);
    jsniReadSideEffectCauser5();
    assertEquals(5, sideEffectChecker);
    foo = SideEffectCauser6.causeClinitSideEffectOnRead;
    assertEquals(6, sideEffectChecker);
    new SideEffectCauser7();
    assertEquals(7, sideEffectChecker);
    String checkRescued = NonSideEffectCauser.NOT_A_COMPILE_TIME_CONSTANT;
    assertEquals(null, checkRescued);
  }

  private static boolean testClassInitialized = false;

  private static class TestClass {
    static {
      testClassInitialized = true;
    }
    public static int staticField = 32;
  }

  public void testClinitOverInstance() {
    assertEquals(32, getTest().staticField);
    assertTrue(testClassInitialized);
  }

  private TestClass getTest() {
    assertFalse(testClassInitialized);
    return null;
  }

  public void testConditionals() {
    assertTrue(TRUE ? TRUE : FALSE);
    assertFalse(FALSE ? TRUE : FALSE);
    assertFalse(TRUE ? FALSE : TRUE);
    assertTrue(FALSE ? FALSE : TRUE);

    assertTrue(true ? TRUE : FALSE);
    assertFalse(false ? TRUE : FALSE);
    assertFalse(true ? FALSE : TRUE);
    assertTrue(false ? FALSE : TRUE);

    assertTrue(TRUE ? true : FALSE);
    assertFalse(FALSE ? true : FALSE);
    assertFalse(TRUE ? false : TRUE);
    assertTrue(FALSE ? false : TRUE);

    assertTrue(TRUE ? TRUE : false);
    assertFalse(FALSE ? TRUE : false);
    assertFalse(TRUE ? FALSE : true);
    assertTrue(FALSE ? FALSE : true);

    assertEquals("abc", "ab" + (TRUE ? "c" : 'z'));
    assertEquals("abz", "ab" + (FALSE ? "c" : 'z'));
  }

  /**
   * Test for issue 7045.
   *
   * Background. GWT does not explicitly model block scopes but allows names across different blocks
   * to be repeated and are represented as different local variables with the same name.
   *
   * The GenerateJavaScriptAST assumes that locals with the same name can be coalesced. This
   * assumption only holds if no optimization introduces a local variable reference in
   * a blockscope that is using a different local of the same name.
   *
   * The dataflow optimizer does not respect this assumption when doing copy propagation.
   */
  public void testDataflowDuplicateNamesError() {
    StringBuilder topScope;
    {
      StringBuilder localScopeDuplicatedVar = new StringBuilder();
      localScopeDuplicatedVar.append("initial text");
      topScope = localScopeDuplicatedVar;
    }

    {
      // It's important that this StringBuilder have the same name as the one above.
      StringBuilder localScopeDuplicatedVar = new StringBuilder();
      localScopeDuplicatedVar.append("different text");
    }

    {
      // The value of main should be "initial text" at this point, but if this code
      // is run as compiled JavaScript then it will be "different text". (before the issue was
      // fixed).

      // The copy propagation optimization in the DataflowOptimizer replaces the reference to
      // topScope by localScopeDuplicatedVar (from the previous block).
      // When the JavaScript output is produces these two variables are coalesced.

      // Although unlikely an error like this can occur due to the way temporary variables are
      // generated via the TempLocalVisitor.
      assertEquals("initial text", topScope.toString());
    }
  }

  private int functionWithClashingParameters(int i0) {
    int c = 0;
    for (int i = 0; i < 5; i++) {
      c++;
    }
    for (int i = 0; i < 6; i++) {
      c++;
    }
    // Assumes that GenerateJavaScriptAST will rename one of the "i" variables to i0.
    return i0;
  }

  /**
   * Test for issue 8870.
   *
   */
  public void testParameterVariableClash() {
    assertEquals(1, functionWithClashingParameters(1));
  }

  /**
   * Test for issue 8877.
   */
  public void testOptimizeInstanceOf() {
    class A {
      int counter = 0;
      Object get() {
        if (counter >= 0) {
          counter++;
        }
        return "aString";
      }
    }

    A anA = new A();
    assertFalse(anA.get() instanceof Integer);
    assertEquals(1, anA.counter);
  }

  public void testDeadCode() {
    while (returnFalse()) {
      break;
    }

    do {
      break;
    } while (false);

    do {
      break;
    } while (returnFalse());

    for (; returnFalse();) {
    }

    boolean check = false;
    for (check = true; returnFalse(); fail()) {
      fail();
    }
    assertTrue(check);

    if (returnFalse()) {
      fail();
    } else {
    }

    if (!returnFalse()) {
    } else {
      fail();
    }

    // For these following tests, make sure that side effects in conditions
    // get propagated, even if they cause introduction of dead code.
    // 
    boolean b = false;
    if ((b = true) ? true : true) {
    }
    assertTrue(b);

    boolean c = true;
    int val = 0;
    for (val = 1; c = false; ++val) {
    }
    assertFalse(c);

    boolean d = true;
    while (d = false) {
    }
    assertFalse(d);

    boolean e = true;
    if (true | (e = false)) {
    }
    assertFalse(e);
  }

  /**
   * Test that code considered unreachable by the JDT does not crash the GWT
   * compiler.
   */
  public void testDeadCode2() {
    class SillyList {
    }

    final SillyList outerLocalVariable = new SillyList();

    new SillyList() {
      private void pretendToUse(SillyList x) {
      }

      void blah() {
        if (true) {
          throw new RuntimeException();
        }
        /*
         * This code is unreachable, and so outerLocalVariable is never actually
         * read by reachable code.
         */
        pretendToUse(outerLocalVariable);
      }
    };
  }

  @SuppressWarnings({"ReturnValueIgnored", "IdentityBinaryExpression"})
  public void testDeadTypes() {
    if (false) {
      new Object() {
      }.toString();

      class Foo {
        void a() {
        }
      }
      new Foo().a();
    }
  }

  /**
   * Make sure that the compiler does not crash itself on user code that divides
   * by zero. The actual behavior varies by the numeric type and whether it is
   * Development Mode or Production Mode, but the important thing is that the
   * compiler does not crash.
   */
  @SuppressWarnings({"divzero", "ConstantOverflow", "IdentityBinaryExpression"})
  public void testDivByZero() {
    assertTrue(Double.isNaN(0.0 / 0.0));

    try {
      // 0 / 0 is currently 0 in Production Mode.
      assertEquals(0, 0 / 0);
    } catch (ArithmeticException expected) {
      // expected in Development Mode
    }

    try {
      volatileLong = 0L / 0;
      fail("expected an ArithmeticException");
    } catch (ArithmeticException expected) {
      // expected
    }

    assertTrue(Double.isNaN(0.0 % 0.0));

    try {
      // 0 % 0 is currently NaN in Production Mode.
      assertTrue(Double.isNaN(0 % 0));
    } catch (ArithmeticException expected) {
      // expected in Development Mode
    }

    try {
      volatileLong = 0L % 0;
      fail("expected an ArithmeticException");
    } catch (ArithmeticException expected) {
      // expected
    }
  }

  @SuppressWarnings({"IdentityBinaryExpression", "LoopConditionChecker"})
  public void testEmptyBlockStatements() {
    boolean b = false;
    while (b) {
    }

    do {
    } while (b);

    for (; b;) {
    }

    for (;;) {
      break;
    }

    if (b) {
    }

    if (b) {
    } else {
      b = false;
    }

    if (b) {
    } else {
    }
  }

  public native void testEmptyBlockStatementsNative() /*-{
    var b = false;
    while (b) {
    }

    do {
    } while (b);

    for (; b; ) {
    }

    for (;;) {
      break;
    }

    if (b) {
    }

    if (b) {
    } else {
      b = false;
    }

    if (b) {
    } else {
    }
  }-*/;

  // CHECKSTYLE_OFF

  @SuppressWarnings({"empty", "LoopConditionChecker"})
  public void testEmptyStatements() {
    boolean b = false;

    while (b);

    do; while (b);

    for (; b;);

    for (int i = 0; i < 10; ++i);

    for (int i : new int[10]);

    for (Integer i : new ArrayList<Integer>());

    for (;;)
      break;

    if (b)
      ;

    if (b)
      ;
    else
      b = false;

    if (b)
      ;
    else
      ;
  }

  // CHECKSTYLE_ON

  public native void testEmptyStatementsNative() /*-{
    var b = false;

    while (b);

    do; while (b);

    for (; b;);

    for (;;)
      break;

    if (b)
      ;

    if (b)
      ;
    else
      b = false;

    if (b)
      ;
    else
      ;
  }-*/;

  public void testEmptyTryBlock() {
    int x = 0;
    try {
    } finally {
      x = 1;
    }
    assertEquals(1, x);
  }

  public void testCatchBlockWithKeyword() {
    try {
      throw new RuntimeException();
    } catch (Exception var) {
      assertFalse(var.toString().isEmpty());
    }
  }

  /** Ensure that only final fields are initializers when cstrs run, see issue 380. */
  public void testFieldInitializationOrder() {
    ArrayList<String> seenValues = new ArrayList<String>();
    new FieldInitOrderChild(seenValues);
    assertEquals("i1=1,i2=0,i3=null,i4=null,i5=1,i6=1,i7=1", seenValues.get(0));
    assertEquals("i1=1,i2=1,i3=1,i4=2,i5=1,i6=2,i7=2", seenValues.get(1));
  }

  public void testForStatement() {
    {
      int i;
      for (i = 0; i < 10; ++i) {
      }
      assertEquals(10, i);
    }
    {
      int i, c;
      for (i = 0, c = 10; i < c; ++i) {
      }
      assertEquals(10, i);
      assertEquals(10, c);
    }
    {
      int j = 0;
      for (int i = 0; i < 10; ++i) {
        ++j;
      }
      assertEquals(10, j);
    }
    {
      int j = 0;
      for (int i = 0, c = 10; i < c; ++i) {
        ++j;
      }
      assertEquals(10, j);
    }
  }

  /**
   * Issue #615: Internal Compiler Error.
   */
  public void testImplicitNull() {
    boolean b;
    String test = ((((b = true) ? null : null) + " ") + b);
    assertTrue(b);
    assertEquals("null true", test);
  }

  /**
   * Issue 2886: inlining should cope with local variables that do not have an
   * explicit declaration node.
   */
  public void testInliningBoxedIncrement() {
    // should not actually inline, because it has a temp variable
    incrementBoxedInteger();
    assertEquals((Integer) 1, boxedInteger);
  }

  public void testJavaScriptReservedWords() {
    boolean delete = TRUE;
    for (int in = 0; in < 10; ++in) {
      assertTrue(in < 10);
      assertTrue(delete);
    }
  }

  public void testLabels() {
    int i = 0, j = 0;
    outer : for (i = 0; i < 1; ++i) {
      inner : for (j = 0; j < 1; ++j) {
        break outer;
      }
      fail();
    }
    assertEquals(0, i);
    assertEquals(0, j);

    outer : for (i = 0; i < 1; ++i) {
      inner : for (j = 0; j < 1; ++j) {
        continue outer;
      }
      fail();
    }
    assertEquals(1, i);
    assertEquals(0, j);

    outer : for (i = 0; i < 1; ++i) {
      inner : for (j = 0; j < 1; ++j) {
        break inner;
      }
    }
    assertEquals(1, i);
    assertEquals(0, j);

    outer : for (i = 0; i < 1; ++i) {
      inner : for (j = 0; j < 1; ++j) {
        continue inner;
      }
    }
    assertEquals(1, i);
    assertEquals(1, j);

    /*
     * Issue 2069: a default with a break should not be stripped unless the
     * break has no label.
     */
    outer : while (true) {
      switch (THREE) {
        case 0:
        case 1:
          break;
        default:
          break outer;
      }
      fail("should not be reached");
    }

    /*
     * Issue 2770: labeled breaks in a switch statement with no default case
     * should not be pruned.
     */
    outer : while (true) {
      switch (THREE) {
        case 0:
        case 1:
        case 2:
        case 3:
          break outer;
        case 4:
        case 5:
          break;
      }
    }
  }

  private final class RefObject<T> {
    T argvalue;

    RefObject(T refarg) {
        argvalue = refarg;
    }
  }

  /**
   * Test for issue 6471.
   *
   * Cast normally can not appear on the left hand side of an assignment (they are not allowed in
   * Java source). However they might be present in the Java AST as a result of generics and
   * erasure; hence they need to be handled in the proper places.
   */
  public void testLhsCastString() {

    RefObject<String> prefix = new RefObject<String>("Hello");

    // Due to erasure, prefix.argvalue is of type Object hence
    // the compiler "inserts" a cast transforming it into
    // ((String) prefix.argvalue) += ", goodbye!"
    // This way a lhs can be a cast (which is illegal in Java source code).
    prefix.argvalue += ", goodbye!";
    assertEquals("Hello, goodbye!", prefix.argvalue);
  }

  public void testLhsCastInt() {

    RefObject<Integer> prefix = new RefObject<Integer>(41);

    // Due to erasure, prefix.argvalue is of type Object hence
    // the compiler "inserts" a cast transforming it into
    // ((Integer) prefix.argvalue) ++
    // This way a lhs can be a cast (which is illegal in Java source code).
    prefix.argvalue++;
    assertEquals(new Integer(42), prefix.argvalue);
  }

  public void testLhsCastNested() {

    RefObject<Integer> prefix = new RefObject<Integer>(41);

    // Due to erasure, prefix.argvalue is of type Object hence
    // the compiler "inserts" a cast transforming it into
    // ((Integer) prefix.argvalue) ++
    // This way a lhs can be a cast (which is illegal in Java source code).
    Math.max(prefix.argvalue++,40);
    assertEquals(new Integer(42), prefix.argvalue);
  }

  public void testLocalClasses() {
    class Foo {
      public Foo(int j) {
        assertEquals(1, j);
      };
    }
    final int i;
    new Foo(i = 1) {
      {
        assertEquals(1, i);
      }
    };
    assertEquals(1, i);
  }

  public void testLocalRefs() {
    final String foo = TRUE ? "foo" : "bar";
    final String bar = TRUE ? "bar" : "foo";
    String result = new Object() {

      private String a = foo;

      {
        a = foo;
      }

      @Override
      public String toString() {
        return new Object() {

          private static final String constantString = "wallawalla";

          private String ai = foo;

          {
            ai = foo;
          }

          @SuppressWarnings("ReturnValueIgnored")
          @Override
          public String toString() {
            // this line used to cause ICE due to no synthetic path to bar
            bar.valueOf(false);

            assertEquals("wallawalla", constantString);
            return foo + a + ai;
          }

        }.toString() + a;
      }

    }.toString();
    assertEquals("foofoofoofoo", result);
  }

  /**
   * test for issue 7824.
   */
  public void testMethodNamedSameAsClass() {
    MethodNamedSameAsClass obj = new MethodNamedSameAsClass();
    obj.MethodNamedSameAsClass();
  }

  public void testNotOptimizations() {
    assertFalse(!true);
    assertTrue(!false);

    assertTrue(!(TRUE == FALSE));
    assertFalse(!(TRUE != FALSE));

    assertFalse(!(3 < 4));
    assertFalse(!(3 <= 4));
    assertTrue(!(3 > 4));
    assertTrue(!(3 >= 4));

    assertTrue(!(4 < 3));
    assertTrue(!(4 <= 3));
    assertFalse(!(4 > 3));
    assertFalse(!(4 >= 3));

    assertTrue(!!TRUE);
    assertFalse(!!FALSE);

    double nan = Math.random() == 0 ? Double.NaN : Double.NaN;
    assertFalse(nan == (0.0 / 0.0));
    assertTrue(!(nan > 0));
    assertTrue(!(nan < 0));
    assertFalse(!!(nan > 0));
  }

  public void testNullFlow() {
    UninstantiableType f = null;

    try {
      f.returnNull().toString();
      fail();
    } catch (NullPointerException e) {
      // Development Mode
    } catch (JavaScriptException e) {
      // Production Mode
    }

    try {
      UninstantiableType[] fa = null;
      fa[4] = null;
      fail();
    } catch (NullPointerException e) {
      // Development Mode
    } catch (JavaScriptException e) {
      // Production Mode
    }
  }

  public void testNullFlowArray() {
    UninstantiableType[] uta = new UninstantiableType[10];
    assertEquals(10, uta.length);
    assertEquals(uta[0], null);
    uta[1] = null;
    assertEquals(uta[1], null);
  }

  public void testNullFlowOverloads() {
    foo((Throwable) null);
    foo((String) null);
  }

  public void testNullFlowVsClassCastPrecedence() {
    try {
      ((UninstantiableType) new Object()).returnNull();
      fail();
    } catch (ClassCastException e) {
      // success
    }
  }

  public void testOuterSuperThisRefs() {
    new B();
  }

  /**
   * Test that calling a private instance method does not accidentally call
   * another private method that appears to override it. Private methods don't
   * truly override each other.
   */
  public void testPrivateOverride() {
    assertEquals(0, new TpoChild().foo());
    assertEquals(0, new TpoJsniChild().foo());
  }

  public void testReturnStatementInCtor() {
    class Foo {
      int i;

      Foo(int i) {
        this.i = i;
        if (i == 0) {
          return;
        } else if (i == 1) {
          return;
        }
        return;
      }
    }
    assertEquals(0, new Foo(0).i);
    assertEquals(1, new Foo(1).i);
    assertEquals(2, new Foo(2).i);
  }

  public void testStaticMethodResolution() {
    // Issue 2922
    assertEquals("AbstractSuper", AbstractSuper.foo());
  }

  public void testStringOptimizations() {
    assertEquals("Herro, AJAX", "Hello, AJAX".replace('l', 'r'));
    assertEquals('J', "Hello, AJAX".charAt(8));
    assertEquals(11, "Hello, AJAX".length());
    assertFalse("Hello, AJAX".equals("me"));
    assertTrue("Hello, AJAX".equals("Hello, AJAX"));
    assertTrue("Hello, AJAX".equalsIgnoreCase("HELLO, ajax"));
    assertEquals("hello, ajax", "Hello, AJAX".toLowerCase(Locale.ROOT));

    assertEquals("foobar", "foo" + barShouldInline());
    assertEquals("1bar", 1 + barShouldInline());
    assertEquals("fbar", 'f' + barShouldInline());
    assertEquals("truebar", true + barShouldInline());
    assertEquals("3.5bar", 3.5 + barShouldInline());
    assertEquals("3.5bar", 3.5f + barShouldInline());
    assertEquals("27bar", 27L + barShouldInline());
    assertEquals("nullbar", null + barShouldInline());
  }

  public void testSubclassStaticInnerAndClinitOrdering() {
    new CheckSubclassStaticInnerAndClinitOrdering();
  }
  
  public void testSwitchOnEnumTypedThis() {
    assertEquals("POSITIVE", ChangeDirection.POSITIVE.getText());
    assertEquals("NEGATIVE", ChangeDirection.NEGATIVE.getText());
  }
  
  public void testSwitchStatement() {
    switch (0) {
      case 0:
        // Once caused an ICE.
        int test;
        break;
    }
  }

  /**
   * Tests cases where the compiler will convert to a simple if or block.
   */
  public void testSwitchStatementConversions() {
    int i = 1;

    switch (i) {
      case 1:
        i = 2;
    }
    assertEquals(2, i);

    switch (i) {
      case 1:
        i = 3;
    }
    assertEquals(2, i);

    switch (i) {
      default:
        i = 3;
    }
    assertEquals(3, i);
  }

  public void testSwitchStatementEmpty() {
    switch (0) {
    }
  }

  public void testSwitchStatementFallthroughs() {
    int i = 1;
    switch (i) {
      case 1:
        i = 2;
        // fallthrough
      case 2:
        break;
      case 3:
        fail("Shouldn't get here");
    }
    assertEquals(2, i);

    switch (i) {
      case 1:
        break;
      case 2:
        i = 3;
        break;
      case 3:
        break;
      case 4:
        fail("Shouldn't get here");
        break;
    }
    assertEquals(3, i);
  }

  public void testSwitchStatementWithUsefulDefault() {
    switch (1) {
      case 1:
      case 2:
      case 3: {
        break;
      }
      case 4:
      case 5:
      case 6:
      default:
        fail("Shouldn't get here");
        break;
    }
  }

  public void testSwitchStatementWithUselessCases() {
    switch (1) {
      case 1:
      case 2:
      case 3: {
        break;
      }
      case 4:
      case 5:
      case 6:
      default:
        break;
    }
  }

  public void testSwitchStatementWontRemoveExpression() {
    class Foo {
      boolean setFlag;

      int setFlag() {
        setFlag = true;
        return 3;
      }
    }

    Foo foo = new Foo();
    switch (foo.setFlag()) {
      case 3:
        break;
    }

    // Make sure that compiler didn't optimize away the switch statement's
    // expression
    assertTrue(foo.setFlag);
  }

  public void testUnaryPlus() {
    int x, y = -7;
    x = +y;
    assertEquals(-7, x);
  }

  public void testUninstantiableAccess() {
    UninstantiableType u = null;

    try {
      accessUninstantiableField(u);
      fail("Expected JavaScriptException");
    } catch (JavaScriptException expected) {
    }

    try {
      accessUninstantiableMethod(u);
      fail("Expected JavaScriptException");
    } catch (JavaScriptException expected) {
    }

    try {
      volatileUninstantiableType = (UninstantiableType) (new Object());
      fail("Expected ClassCastException");
    } catch (ClassCastException expected) {
    }

    try {
      volatileInt = u.intField++;
      fail("Expected NullPointerException (1)");
    } catch (Exception expected) {
    }

    try {
      volatileInt = u.intField--;
      fail("Expected NullPointerException (2)");
    } catch (Exception expected) {
    }

    try {
      volatileInt = ++u.intField;
      fail("Expected NullPointerException (3)");
    } catch (Exception expected) {
    }

    try {
      volatileInt = --u.intField;
      fail("Expected NullPointerException (4)");
    } catch (Exception expected) {
    }

    try {
      u.intField = 0;
      /*
       * Currently, null.nullField gets pruned rather than being allowed to
       * execute its side effect of tripping an NPE.
       */
      // fail("Expected NullPointerException (5)");
    } catch (Exception expected) {
    }

    try {
      volatileBoolean = u.intField == 0;
      fail("Expected NullPointerException (6)");
    } catch (Exception expected) {
    }

    try {
      volatileBoolean = u.returnInt() == 0;
      fail("Expected NullPointerException (7)");
    } catch (Exception expected) {
    }
  }

  private void incrementBoxedInteger() {
    // the following will need a temporary variable created
    boxedInteger++;
  }

  private boolean returnFalse() {
    return false;
  }

}

class A {
  public abstract class AA {
  }
}

class B extends A {
  {
    new AA() {
    };
  }
}

// This construct used to cause an ICE
class CheckSubclassStaticInnerAndClinitOrdering extends Outer.StaticInner {
  private static class Foo {
  }

  private static final Foo FOO = new Foo();

  public CheckSubclassStaticInnerAndClinitOrdering() {
    this(FOO);
  }

  public CheckSubclassStaticInnerAndClinitOrdering(Foo foo) {
    // This used to be null due to clinit ordering issues
    Assert.assertNotNull(foo);
  }
}

class Outer {
  public static class StaticInner {
  }
}
