/*
 * 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.impl.gflow.constants;

import com.google.gwt.dev.jjs.impl.gflow.Analysis;
import com.google.gwt.dev.jjs.impl.gflow.CfgAnalysisTestBase;
import com.google.gwt.dev.jjs.impl.gflow.cfg.Cfg;
import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgEdge;
import com.google.gwt.dev.jjs.impl.gflow.cfg.CfgNode;

/**
 * Tests for {@link ConstantAnalysis} dataflow analysis.
 */
public class ConstantsAnalysisTest extends CfgAnalysisTestBase<ConstantsAssumption> {
  @Override
  protected void setUp() throws Exception {
    super.setUp();
    addSnippetClassDecl("static int i;");
    addSnippetClassDecl("static int j;");
    addSnippetClassDecl("static int k;");
    addSnippetClassDecl("static int l;");
    addSnippetClassDecl("static int m;");

    addSnippetClassDecl("static int foo() { return 0; };");
    addSnippetClassDecl("static void bar(Object o) { };");
    addSnippetClassDecl("static int baz(Object o) { return 0; };");
  }

  public void testDeclWithConstInit() throws Exception {
    analyze("void", "int i = 1;").into(
        "BLOCK -> [* T]",
        "STMT -> [* T]",
        "WRITE(i, 1) -> [* {i = 1}]",
        "END");
  }

  public void testDeclWithConstOps() throws Exception {
    analyze("void", "int i = 1 + 1;").into(
        "BLOCK -> [* T]",
        "STMT -> [* T]",
        "WRITE(i, 1 + 1) -> [* {i = 2}]",
        "END");
  }

  public void testDeclWithNonconstInit() throws Exception {
    analyze("void", "int i = foo();").into(
        "BLOCK -> [* T]",
        "STMT -> [* T]",
        "OPTTHROW(foo()) -> [NOTHROW=* T, RE=1 T, E=1 T]",
        "CALL(foo) -> [* T]",
        "WRITE(i, EntryPoint.foo()) -> [* T]",
        "1: END");
  }

  public void testReassign() throws Exception {
    analyze("void", "int i = 1; i = 2;").into(
        "BLOCK -> [* T]",
        "STMT -> [* T]",
        "WRITE(i, 1) -> [* {i = 1}]",
        "STMT -> [* {i = 1}]",
        "WRITE(i, 2) -> [* {i = 2}]",
        "END");
    analyze("void", "int i; i = 3;").into(
        "BLOCK -> [* T]",
        "STMT -> [* T]",
        "STMT -> [* T]",
        "WRITE(i, 3) -> [* {i = 3}]",
        "END");
  }

  public void test2Vars() throws Exception {
    analyze("void", "int i = 1; int j = 2;").into(
        "BLOCK -> [* T]",
        "STMT -> [* T]",
        "WRITE(i, 1) -> [* {i = 1}]",
        "STMT -> [* {i = 1}]",
        "WRITE(j, 2) -> [* {i = 1, j = 2}]",
        "END");
  }

  public void testSequence() throws Exception {
    analyze("void", "int i = 1; int j = i; int k = j; int l = k;").into(
        "BLOCK -> [* T]",
        "STMT -> [* T]",
        "WRITE(i, 1) -> [* {i = 1}]",
        "STMT -> [* {i = 1}]",
        "READ(i) -> [* {i = 1}]",
        "WRITE(j, i) -> [* {i = 1, j = 1}]",
        "STMT -> [* {i = 1, j = 1}]",
        "READ(j) -> [* {i = 1, j = 1}]",
        "WRITE(k, j) -> [* {i = 1, j = 1, k = 1}]",
        "STMT -> [* {i = 1, j = 1, k = 1}]",
        "READ(k) -> [* {i = 1, j = 1, k = 1}]",
        "WRITE(l, k) -> [* {i = 1, j = 1, k = 1, l = 1}]",
        "END");
  }

  public void testIfStatement() throws Exception {
    analyze("void", "int i = k; if (i == 1) { int j = i; } else { int j = i; } ").into(
        "BLOCK -> [* T]",
        "STMT -> [* T]",
        "READ(k) -> [* T]",
        "WRITE(i, EntryPoint.k) -> [* T]",
        "STMT -> [* T]",
        "READ(i) -> [* T]",
        "COND (i == 1) -> [THEN=* {i = 1}, ELSE=1 T]",
        "BLOCK -> [* {i = 1}]",
        "STMT -> [* {i = 1}]",
        "READ(i) -> [* {i = 1}]",
        "WRITE(j, i) -> [2 {i = 1, j = 1}]",
        "1: BLOCK -> [* T]",
        "STMT -> [* T]",
        "READ(i) -> [* T]",
        "WRITE(j, i) -> [* T]",
        "2: END");

    analyze("int", "int j = 0; if (foo() == 1) j = 1; return j;").into(
        "BLOCK -> [* T]",
        "STMT -> [* T]",
        "WRITE(j, 0) -> [* {j = 0}]",
        "STMT -> [* {j = 0}]",
        "OPTTHROW(foo()) -> [NOTHROW=* {j = 0}, RE=2 {j = 0}, E=2 {j = 0}]",
        "CALL(foo) -> [* {j = 0}]",
        "COND (EntryPoint.foo() == 1) -> [THEN=* {j = 0}, ELSE=1 {j = 0}]",
        "STMT -> [* {j = 0}]",
        "WRITE(j, 1) -> [* {j = 1}]",
        "1: STMT -> [* T]",
        "READ(j) -> [* T]",
        "GOTO -> [* T]",
        "2: END");

    analyze("int", "int j = 0; if (foo() == 1) j = foo(); return j;").into(
        "BLOCK -> [* T]",
        "STMT -> [* T]",
        "WRITE(j, 0) -> [* {j = 0}]",
        "STMT -> [* {j = 0}]",
        "OPTTHROW(foo()) -> [NOTHROW=* {j = 0}, RE=2 {j = 0}, E=2 {j = 0}]",
        "CALL(foo) -> [* {j = 0}]",
        "COND (EntryPoint.foo() == 1) -> [THEN=* {j = 0}, ELSE=1 {j = 0}]",
        "STMT -> [* {j = 0}]",
        "OPTTHROW(foo()) -> [NOTHROW=* {j = 0}, RE=2 {j = 0}, E=2 {j = 0}]",
        "CALL(foo) -> [* {j = 0}]",
        "WRITE(j, EntryPoint.foo()) -> [* T]",
        "1: STMT -> [* T]",
        "READ(j) -> [* T]",
        "GOTO -> [* T]",
        "2: END");
  }

  public void testWhileLoop1() throws Exception {
    analyze("void", "int j = 1; while (j > 0) ++j;").into(
        "BLOCK -> [* T]",
        "STMT -> [* T]",
        "WRITE(j, 1) -> [* {j = 1}]",
        "STMT -> [* {j = 1}]",
        "1: READ(j) -> [* T]",
        "COND (j > 0) -> [THEN=* T, ELSE=2 T]",
        "STMT -> [* T]",
        "READWRITE(j, null) -> [1 T]",
        "2: END");
  }

  public void testWhileLoop2() throws Exception {
    analyze("void", "int j = 0; while (j > 0) {};").into(
        "BLOCK -> [* T]",
        "STMT -> [* T]",
        "WRITE(j, 0) -> [* {j = 0}]",
        "STMT -> [* {j = 0}]",
        "1: READ(j) -> [* {j = 0}]",
        "COND (j > 0) -> [THEN=* {j = 0}, ELSE=2 {j = 0}]",
        "BLOCK -> [1 {j = 0}]",
        "2: END");
  }

  public void testConditionalExpressions() throws Exception {
    analyze("void", "boolean b1 = false; boolean b2 = false; if (b1 && (b2 = true)) b1 = true;").into(
        "BLOCK -> [* T]",
        "STMT -> [* T]",
        "WRITE(b1, false) -> [* {b1 = false}]",
        "STMT -> [* {b1 = false}]",
        "WRITE(b2, false) -> [* {b1 = false, b2 = false}]",
        "STMT -> [* {b1 = false, b2 = false}]",
        "READ(b1) -> [* {b1 = false, b2 = false}]",
        "COND (b1) -> [THEN=* {b1 = false, b2 = false}, ELSE=1 {b1 = false, b2 = false}]",
        "WRITE(b2, true) -> [* {b1 = false, b2 = true}]",
        "1: COND (b1 && (b2 = true)) -> [THEN=* {b1 = false}, ELSE=2 {b1 = false}]",
        "STMT -> [* {b1 = false}]",
        "WRITE(b1, true) -> [* {b1 = true}]",
        "2: END");
  }

  // Various real-world stuff
  public void testVariousStuff() throws Exception {
    addSnippetClassDecl("static Object f = null;");

    analyze("void",
        "Object e = null;" +
        "if (f != null) if (e == null)" +
        "  return;" +
        "boolean b = e == null;").into(
        "BLOCK -> [* T]",
        "STMT -> [* T]",
        "WRITE(e, null) -> [* {e = null}]",
        "STMT -> [* {e = null}]",
        "READ(f) -> [* {e = null}]",
        "COND (EntryPoint.f != null) -> [THEN=* {e = null}, ELSE=1 {e = null}]",
        "STMT -> [* {e = null}]",
        "READ(e) -> [* {e = null}]",
        "COND (e == null) -> [THEN=* {e = null}, ELSE=1 {e = null}]",
        "STMT -> [* {e = null}]",
        "GOTO -> [2 {e = null}]",
        "1: STMT -> [* {e = null}]",
        "READ(e) -> [* {e = null}]",
        "WRITE(b, e == null) -> [* {b = true, e = null}]",
        "2: END"
        );
  }

  /**
   * Parameters should have an initial assumption of non-constant.
   */
  public void testParamNonConstant() throws Exception {
    analyzeWithParams("void", "int i, int j", "if (j == 0) { i = 0; } j=i; j=0;").into(
        "BLOCK -> [* T]",
        "STMT -> [* T]",
        "READ(j) -> [* T]",
        "COND (j == 0) -> [THEN=* {j = 0}, ELSE=1 T]",
        "BLOCK -> [* {j = 0}]",
        "STMT -> [* {j = 0}]",
        "WRITE(i, 0) -> [* {i = 0, j = 0}]",
        "1: STMT -> [* T]",
        "READ(i) -> [* T]",
        "WRITE(j, i) -> [* T]",
        "STMT -> [* T]",
        "WRITE(j, 0) -> [* {j = 0}]",
        "END"
      );
  }

  @Override
  protected Analysis<CfgNode<?>, CfgEdge, Cfg, ConstantsAssumption> createAnalysis() {
    return new ConstantsAnalysis();
  }
}
