/*
 * Copyright (c) 1994, 2003, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.tools.tree;

import sun.tools.java.*;
import sun.tools.asm.Assembler;
import sun.tools.asm.Label;
import sun.tools.asm.TryData;
import sun.tools.asm.CatchData;
import java.io.PrintStream;
import java.util.Enumeration;
import java.util.Hashtable;

/**
 * WARNING: The contents of this source file are not part of any
 * supported API.  Code that depends on them does so at its own risk:
 * they are subject to change or removal without notice.
 */
public
class TryStatement extends Statement {
    Statement body;
    Statement args[];
    long arrayCloneWhere;       // private note posted from MethodExpression

    /**
     * Constructor
     */
    public TryStatement(long where, Statement body, Statement args[]) {
        super(TRY, where);
        this.body = body;
        this.args = args;
    }

    /**
     * Check statement
     */
    Vset check(Environment env, Context ctx, Vset vset, Hashtable<Object, Object> exp) {
        checkLabel(env, ctx);
        try {
            vset = reach(env, vset);
            Hashtable<Object, Object> newexp = new Hashtable<>();
            CheckContext newctx =  new CheckContext(ctx, this);

            // Check 'try' block.  A variable is DA (DU) before the try
            // block if it is DA (DU) before the try statement.
            Vset vs = body.check(env, newctx, vset.copy(), newexp);

            // A variable is DA before a catch block if it is DA before the
            // try statement.  A variable is DU before a catch block if it
            // is DU after the try block and before any 'break', 'continue',
            // 'throw', or 'return' contained therein. That is, the variable
            // is DU upon entry to the try-statement and is not assigned to
            // anywhere within the try block.
            Vset cvs = Vset.firstDAandSecondDU(vset, vs.copy().join(newctx.vsTryExit));

            for (int i = 0 ; i < args.length ; i++) {
                // A variable is DA (DU) after a try statement if
                // it is DA (DU) after every catch block.
                vs = vs.join(args[i].check(env, newctx, cvs.copy(), exp));
            }

            // Check that catch statements are actually reached
            for (int i = 1 ; i < args.length ; i++) {
                CatchStatement cs = (CatchStatement)args[i];
                if (cs.field == null) {
                    continue;
                }
                Type type = cs.field.getType();
                ClassDefinition def = env.getClassDefinition(type);

                for (int j = 0 ; j < i ; j++) {
                    CatchStatement cs2 = (CatchStatement)args[j];
                    if (cs2.field == null) {
                        continue;
                    }
                    Type t = cs2.field.getType();
                    ClassDeclaration c = env.getClassDeclaration(t);
                    if (def.subClassOf(env, c)) {
                        env.error(args[i].where, "catch.not.reached");
                        break;
                    }
                }
            }

            ClassDeclaration ignore1 = env.getClassDeclaration(idJavaLangError);
            ClassDeclaration ignore2 = env.getClassDeclaration(idJavaLangRuntimeException);

            // Make sure the exception is actually throw in that part of the code
            for (int i = 0 ; i < args.length ; i++) {
                CatchStatement cs = (CatchStatement)args[i];
                if (cs.field == null) {
                    continue;
                }
                Type type = cs.field.getType();
                if (!type.isType(TC_CLASS)) {
                    // CatchStatement.checkValue() will have already printed
                    // an error message
                    continue;
                }

                ClassDefinition def = env.getClassDefinition(type);

                // Anyone can throw these!
                if (def.subClassOf(env, ignore1) || def.superClassOf(env, ignore1) ||
                    def.subClassOf(env, ignore2) || def.superClassOf(env, ignore2)) {
                    continue;
                }

                // Make sure the exception is actually throw in that part of the code
                boolean ok = false;
                for (Enumeration<?> e = newexp.keys() ; e.hasMoreElements() ; ) {
                    ClassDeclaration c = (ClassDeclaration)e.nextElement();
                    if (def.superClassOf(env, c) || def.subClassOf(env, c)) {
                        ok = true;
                        break;
                    }
                }
                if (!ok && arrayCloneWhere != 0
                    && def.getName().toString().equals("java.lang.CloneNotSupportedException")) {
                    env.error(arrayCloneWhere, "warn.array.clone.supported", def.getName());
                }

                if (!ok) {
                    env.error(cs.where, "catch.not.thrown", def.getName());
                }
            }

            // Only carry over exceptions that are not caught
            for (Enumeration<?> e = newexp.keys() ; e.hasMoreElements() ; ) {
                ClassDeclaration c = (ClassDeclaration)e.nextElement();
                ClassDefinition def = c.getClassDefinition(env);
                boolean add = true;
                for (int i = 0 ; i < args.length ; i++) {
                    CatchStatement cs = (CatchStatement)args[i];
                    if (cs.field == null) {
                        continue;
                    }
                    Type type = cs.field.getType();
                    if (type.isType(TC_ERROR))
                        continue;
                    if (def.subClassOf(env, env.getClassDeclaration(type))) {
                        add = false;
                        break;
                    }
                }
                if (add) {
                    exp.put(c, newexp.get(c));
                }
            }
            // A variable is DA (DU) after a try statement if it is DA (DU)
            // after the try block and after every catch block. These variables
            // are represented by 'vs'.  If the try statement is labelled, we
            // may also exit from it (including from within a catch block) via
            // a break statement.
            // If there is a finally block, the Vset returned here is further
            // adjusted. Note that this 'TryStatement' node will be a child of
            // a 'FinallyStatement' node in that case.
            return ctx.removeAdditionalVars(vs.join(newctx.vsBreak));
        } catch (ClassNotFound e) {
            env.error(where, "class.not.found", e.name, opNames[op]);
            return vset;
        }
    }

    /**
     * Inline
     */
    public Statement inline(Environment env, Context ctx) {
        if (body != null) {
            body = body.inline(env, new Context(ctx, this));
        }
        if (body == null) {
            return null;
        }
        for (int i = 0 ; i < args.length ; i++) {
            if (args[i] != null) {
                args[i] = args[i].inline(env, new Context(ctx, this));
            }
        }
        return (args.length == 0) ? eliminate(env, body) : this;
    }

    /**
     * Create a copy of the statement for method inlining
     */
    public Statement copyInline(Context ctx, boolean valNeeded) {
        TryStatement s = (TryStatement)clone();
        if (body != null) {
            s.body = body.copyInline(ctx, valNeeded);
        }
        s.args = new Statement[args.length];
        for (int i = 0 ; i < args.length ; i++) {
            if (args[i] != null) {
                s.args[i] = args[i].copyInline(ctx, valNeeded);
            }
        }
        return s;
    }

    /**
     * Compute cost of inlining this statement
     */
    public int costInline(int thresh, Environment env, Context ctx){

        // Don't inline methods containing try statements.
        // If the try statement is being inlined in order to
        // inline a method that returns a value which is
        // a subexpression of an expression involving the
        // operand stack, then the early operands may get lost.
        // This shows up as a verifier error.  For example,
        // in the following:
        //
        //    public static int test() {
        //       try { return 2; } catch (Exception e)  { return 0; }
        //    }
        //
        //    System.out.println(test());
        //
        // an inlined call to test() might look like this:
        //
        //     0 getstatic <Field java.io.PrintStream out>
        //     3 iconst_2
        //     4 goto 9
        //     7 pop
        //     8 iconst_0
        //     9 invokevirtual <Method void println(int)>
        //    12 return
        //  Exception table:
        //     from   to  target type
        //       3     7     7   <Class java.lang.Exception>
        //
        // This fails to verify because the operand stored
        // for System.out gets axed at an exception, leading to
        // an inconsistent stack depth at pc=7.
        //
        // Note that although all code must be able to be inlined
        // to implement initializers, this problem doesn't come up,
        // as try statements themselves can never be expressions.
        // It suffices here to make sure they are never inlined as part
        // of optimization.

        return thresh;
    }

    /**
     * Code
     */
    public void code(Environment env, Context ctx, Assembler asm) {
        CodeContext newctx = new CodeContext(ctx, this);

        TryData td = new TryData();
        for (int i = 0 ; i < args.length ; i++) {
            Type t = ((CatchStatement)args[i]).field.getType();
            if (t.isType(TC_CLASS)) {
                td.add(env.getClassDeclaration(t));
            } else {
                td.add(t);
            }
        }
        asm.add(where, opc_try, td);
        if (body != null) {
            body.code(env, newctx, asm);
        }

        asm.add(td.getEndLabel());
        asm.add(where, opc_goto, newctx.breakLabel);

        for (int i = 0 ; i < args.length ; i++) {
            CatchData cd = td.getCatch(i);
            asm.add(cd.getLabel());
            args[i].code(env, newctx, asm);
            asm.add(where, opc_goto, newctx.breakLabel);
        }

        asm.add(newctx.breakLabel);
    }

    /**
     * Print
     */
    public void print(PrintStream out, int indent) {
        super.print(out, indent);
        out.print("try ");
        if (body != null) {
            body.print(out, indent);
        } else {
            out.print("<empty>");
        }
        for (int i = 0 ; i < args.length ; i++) {
            out.print(" ");
            args[i].print(out, indent);
        }
    }
}
