| /* |
| * Copyright (c) 1994, 2004, 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.Label; |
| import sun.tools.asm.Assembler; |
| import java.io.PrintStream; |
| 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 Expression extends Node { |
| Type type; |
| |
| /** |
| * Constructor |
| */ |
| Expression(int op, long where, Type type) { |
| super(op, where); |
| this.type = type; |
| } |
| |
| /** |
| * Type checking may assign a more complex implementation |
| * to an innocuous-looking expression (like an identifier). |
| * Return that implementation, or the original expression itself |
| * if there is no special implementation. |
| * <p> |
| * This appears at present to be dead code, and is not called |
| * from within javac. Access to the implementation generally |
| * occurs within the same class, and thus uses the underlying |
| * field directly. |
| */ |
| public Expression getImplementation() { |
| return this; |
| } |
| |
| public Type getType() { |
| return type; |
| } |
| |
| /** |
| * Return the precedence of the operator |
| */ |
| int precedence() { |
| return (op < opPrecedence.length) ? opPrecedence[op] : 100; |
| } |
| |
| /** |
| * Order the expression based on precedence |
| */ |
| public Expression order() { |
| return this; |
| } |
| |
| /** |
| * Return true if constant, according to JLS 15.27. |
| * A constant expression must inline away to a literal constant. |
| */ |
| public boolean isConstant() { |
| return false; |
| } |
| |
| /** |
| * Return the constant value. |
| */ |
| public Object getValue() { |
| return null; |
| } |
| |
| /** |
| * Check if the expression is known to be equal to a given value. |
| * Returns false for any expression other than a literal constant, |
| * thus should be called only after simplification (inlining) has |
| * been performed. |
| */ |
| public boolean equals(int i) { |
| return false; |
| } |
| public boolean equals(boolean b) { |
| return false; |
| } |
| public boolean equals(Identifier id) { |
| return false; |
| } |
| public boolean equals(String s) { |
| return false; |
| } |
| |
| /** |
| * Check if the expression must be a null reference. |
| */ |
| public boolean isNull() { |
| return false; |
| } |
| |
| /** |
| * Check if the expression cannot be a null reference. |
| */ |
| public boolean isNonNull() { |
| return false; |
| } |
| |
| /** |
| * Check if the expression is equal to its default static value |
| */ |
| public boolean equalsDefault() { |
| return false; |
| } |
| |
| |
| /** |
| * Convert an expresion to a type |
| */ |
| Type toType(Environment env, Context ctx) { |
| env.error(where, "invalid.type.expr"); |
| return Type.tError; |
| } |
| |
| /** |
| * Convert an expresion to a type in a context where a qualified |
| * type name is expected, e.g., in the prefix of a qualified type |
| * name. |
| */ |
| /*-----------------------------------------------------* |
| Type toQualifiedType(Environment env, Context ctx) { |
| env.error(where, "invalid.type.expr"); |
| return Type.tError; |
| } |
| *-----------------------------------------------------*/ |
| |
| /** |
| * See if this expression fits in the given type. |
| * This is useful because some larger numbers fit into |
| * smaller types. |
| * <p> |
| * If it is an "int" constant expression, inline it, if necessary, |
| * to examine its numerical value. See JLS 5.2 and 15.24. |
| */ |
| public boolean fitsType(Environment env, Context ctx, Type t) { |
| try { |
| if (env.isMoreSpecific(this.type, t)) { |
| return true; |
| } |
| if (this.type.isType(TC_INT) && this.isConstant() && ctx != null) { |
| // Tentative inlining is harmless for constant expressions. |
| Expression n = this.inlineValue(env, ctx); |
| if (n != this && n instanceof ConstantExpression) { |
| return n.fitsType(env, ctx, t); |
| } |
| } |
| return false; |
| } catch (ClassNotFound e) { |
| return false; |
| } |
| } |
| |
| /** @deprecated (for backward compatibility) */ |
| @Deprecated |
| public boolean fitsType(Environment env, Type t) { |
| return fitsType(env, (Context) null, t); |
| } |
| |
| /** |
| * Check an expression |
| */ |
| public Vset checkValue(Environment env, Context ctx, Vset vset, Hashtable<Object, Object> exp) { |
| return vset; |
| } |
| public Vset checkInitializer(Environment env, Context ctx, Vset vset, Type t, Hashtable<Object, Object> exp) { |
| return checkValue(env, ctx, vset, exp); |
| } |
| public Vset check(Environment env, Context ctx, Vset vset, Hashtable<Object, Object> exp) { |
| throw new CompilerError("check failed"); |
| } |
| |
| public Vset checkLHS(Environment env, Context ctx, |
| Vset vset, Hashtable<Object, Object> exp) { |
| env.error(where, "invalid.lhs.assignment"); |
| type = Type.tError; |
| return vset; |
| } |
| |
| /** |
| * Return a {@code FieldUpdater} object to be used in updating the |
| * value of the location denoted by {@code this}, which must be an |
| * expression suitable for the left-hand side of an assignment. |
| * This is used for implementing assignments to private fields for which |
| * an access method is required. Returns null if no access method is |
| * needed, in which case the assignment is handled in the usual way, by |
| * direct access. Only simple assignment expressions are handled here |
| * Assignment operators and pre/post increment/decrement operators are |
| * are handled by 'getUpdater' below. |
| * <p> |
| * Called during the checking phase. |
| */ |
| |
| public FieldUpdater getAssigner(Environment env, Context ctx) { |
| throw new CompilerError("getAssigner lhs"); |
| } |
| |
| /** |
| * Return a {@code FieldUpdater} object to be used in updating the value of the |
| * location denoted by {@code this}, which must be an expression suitable for the |
| * left-hand side of an assignment. This is used for implementing the assignment |
| * operators and the increment/decrement operators on private fields that require an |
| * access method, e.g., uplevel from an inner class. Returns null if no access method |
| * is needed. |
| * <p> |
| * Called during the checking phase. |
| */ |
| |
| public FieldUpdater getUpdater(Environment env, Context ctx) { |
| throw new CompilerError("getUpdater lhs"); |
| } |
| |
| public Vset checkAssignOp(Environment env, Context ctx, |
| Vset vset, Hashtable<Object, Object> exp, Expression outside) { |
| if (outside instanceof IncDecExpression) |
| env.error(where, "invalid.arg", opNames[outside.op]); |
| else |
| env.error(where, "invalid.lhs.assignment"); |
| type = Type.tError; |
| return vset; |
| } |
| |
| /** |
| * Check something that might be an AmbiguousName (refman 6.5.2). |
| * A string of dot-separated identifiers might be, in order of preference: |
| * <nl> |
| * <li> a variable name followed by fields or types |
| * <li> a type name followed by fields or types |
| * <li> a package name followed a type and then fields or types |
| * </nl> |
| * If a type name is found, it rewrites itself as a {@code TypeExpression}. |
| * If a node decides it can only be a package prefix, it sets its |
| * type to {@code Type.tPackage}. The caller must detect this |
| * and act appropriately to verify the full package name. |
| * @arg loc the expression containing the ambiguous expression |
| */ |
| public Vset checkAmbigName(Environment env, Context ctx, Vset vset, Hashtable<Object, Object> exp, |
| UnaryExpression loc) { |
| return checkValue(env, ctx, vset, exp); |
| } |
| |
| /** |
| * Check a condition. Return a ConditionVars(), which indicates when |
| * which variables are set if the condition is true, and which are set if |
| * the condition is false. |
| */ |
| public ConditionVars checkCondition(Environment env, Context ctx, |
| Vset vset, Hashtable<Object, Object> exp) { |
| ConditionVars cvars = new ConditionVars(); |
| checkCondition(env, ctx, vset, exp, cvars); |
| return cvars; |
| } |
| |
| /* |
| * Check a condition. |
| * |
| * cvars is modified so that |
| * cvar.vsTrue indicates variables with a known value if result = true |
| * cvars.vsFalse indicates variables with a known value if !result |
| * |
| * The default action is to simply call checkValue on the expression, and |
| * to see both vsTrue and vsFalse to the result. |
| */ |
| |
| public void checkCondition(Environment env, Context ctx, |
| Vset vset, Hashtable<Object, Object> exp, ConditionVars cvars) { |
| cvars.vsTrue = cvars.vsFalse = checkValue(env, ctx, vset, exp); |
| // unshare side effects: |
| cvars.vsFalse = cvars.vsFalse.copy(); |
| } |
| |
| /** |
| * Evaluate. |
| * |
| * Attempt to compute the value of an expression node. If all operands are |
| * literal constants of the same kind (e.g., IntegerExpression nodes), a |
| * new constant node of the proper type is returned representing the value |
| * as computed at compile-time. Otherwise, the original node 'this' is |
| * returned. |
| */ |
| Expression eval() { |
| return this; |
| } |
| |
| /** |
| * Simplify. |
| * |
| * Attempt to simplify an expression node by returning a semantically- |
| * equivalent expression that is presumably less costly to execute. There |
| * is some overlap with the intent of 'eval', as compile-time evaluation of |
| * conditional expressions and the short-circuit boolean operators is |
| * performed here. Other simplifications include logical identities |
| * involving logical negation and comparisons. If no simplification is |
| * possible, the original node 'this' is returned. It is assumed that the |
| * children of the node have previously been recursively simplified and |
| * evaluated. A result of 'null' indicates that the expression may be |
| * elided entirely. |
| */ |
| Expression simplify() { |
| return this; |
| } |
| |
| /** |
| * Inline. |
| * |
| * Recursively simplify each child of an expression node, destructively |
| * replacing the child with the simplified result. Also attempts to |
| * simplify the current node 'this', and returns the simplified result. |
| * |
| * The name 'inline' is somthing of a misnomer, as these methods are |
| * responsible for compile-time expression simplification in general. |
| * The 'eval' and 'simplify' methods apply to a single expression node |
| * only -- it is 'inline' and 'inlineValue' that drive the simplification |
| * of entire expressions. |
| */ |
| public Expression inline(Environment env, Context ctx) { |
| return null; |
| } |
| public Expression inlineValue(Environment env, Context ctx) { |
| return this; |
| } |
| |
| /** |
| * Attempt to evaluate this expression. If this expression |
| * yields a value, append it to the StringBuffer `buffer'. |
| * If this expression cannot be evaluated at this time (for |
| * example if it contains a division by zero, a non-constant |
| * subexpression, or a subexpression which "refuses" to evaluate) |
| * then return `null' to indicate failure. |
| * |
| * It is anticipated that this method will be called to evaluate |
| * concatenations of compile-time constant strings. The call |
| * originates from AddExpression#inlineValue(). |
| * |
| * See AddExpression#inlineValueSB() for detailed comments. |
| */ |
| protected StringBuffer inlineValueSB(Environment env, |
| Context ctx, |
| StringBuffer buffer) { |
| Expression inlined = inlineValue(env, ctx); |
| Object val = inlined.getValue(); |
| |
| if (val == null && !inlined.isNull()){ |
| // This (supposedly constant) expression refuses to yield |
| // a value. This can happen, in particular, when we are |
| // trying to evaluate a division by zero. It can also |
| // happen in cases where isConstant() is able to classify |
| // expressions as constant that the compiler's inlining |
| // mechanisms aren't able to evaluate; this is rare, |
| // and all such cases that we have found so far |
| // (e.g. 4082814, 4106244) have been plugged up. |
| // |
| // We return a null to indicate that we have failed to |
| // evaluate the concatenation. |
| return null; |
| } |
| |
| // For boolean and character expressions, getValue() returns |
| // an Integer. We need to take care, when appending the result |
| // of getValue(), that we preserve the type. |
| // Fix for 4103959, 4102672. |
| if (type == Type.tChar) { |
| buffer.append((char)((Integer)val).intValue()); |
| } else if (type == Type.tBoolean) { |
| buffer.append(((Integer)val).intValue() != 0); |
| } else { |
| buffer.append(val); |
| } |
| |
| return buffer; |
| } |
| |
| public Expression inlineLHS(Environment env, Context ctx) { |
| return null; |
| } |
| |
| /** |
| * The cost of inlining this expression. |
| * This cost controls the inlining of methods, and does not determine |
| * the compile-time simplifications performed by 'inline' and friends. |
| */ |
| public int costInline(int thresh, Environment env, Context ctx) { |
| return 1; |
| } |
| |
| /** |
| * Code |
| */ |
| void codeBranch(Environment env, Context ctx, Assembler asm, Label lbl, boolean whenTrue) { |
| if (type.isType(TC_BOOLEAN)) { |
| codeValue(env, ctx, asm); |
| asm.add(where, whenTrue ? opc_ifne : opc_ifeq, lbl, whenTrue); |
| } else { |
| throw new CompilerError("codeBranch " + opNames[op]); |
| } |
| } |
| public void codeValue(Environment env, Context ctx, Assembler asm) { |
| if (type.isType(TC_BOOLEAN)) { |
| Label l1 = new Label(); |
| Label l2 = new Label(); |
| |
| codeBranch(env, ctx, asm, l1, true); |
| asm.add(true, where, opc_ldc, 0); |
| asm.add(true, where, opc_goto, l2); |
| asm.add(l1); |
| asm.add(true, where, opc_ldc, 1); |
| asm.add(l2); |
| } else { |
| throw new CompilerError("codeValue"); |
| } |
| } |
| public void code(Environment env, Context ctx, Assembler asm) { |
| codeValue(env, ctx, asm); |
| |
| switch (type.getTypeCode()) { |
| case TC_VOID: |
| break; |
| |
| case TC_DOUBLE: |
| case TC_LONG: |
| asm.add(where, opc_pop2); |
| break; |
| |
| default: |
| asm.add(where, opc_pop); |
| break; |
| } |
| } |
| int codeLValue(Environment env, Context ctx, Assembler asm) { |
| print(System.out); |
| throw new CompilerError("invalid lhs"); |
| } |
| void codeLoad(Environment env, Context ctx, Assembler asm) { |
| print(System.out); |
| throw new CompilerError("invalid load"); |
| } |
| void codeStore(Environment env, Context ctx, Assembler asm) { |
| print(System.out); |
| throw new CompilerError("invalid store"); |
| } |
| |
| /** |
| * Convert this expression to a string. |
| */ |
| void ensureString(Environment env, Context ctx, Assembler asm) |
| throws ClassNotFound, AmbiguousMember |
| { |
| if (type == Type.tString && isNonNull()) { |
| return; |
| } |
| // Make sure it's a non-null string. |
| ClassDefinition sourceClass = ctx.field.getClassDefinition(); |
| ClassDeclaration stClass = env.getClassDeclaration(Type.tString); |
| ClassDefinition stClsDef = stClass.getClassDefinition(env); |
| // FIX FOR 4071548 |
| // We use 'String.valueOf' to do the conversion, in order to |
| // correctly handle null references and efficiently handle |
| // primitive types. For reference types, we force the argument |
| // to be interpreted as of 'Object' type, thus avoiding the |
| // the special-case overloading of 'valueOf' for character arrays. |
| // This special treatment would conflict with JLS 15.17.1.1. |
| if (type.inMask(TM_REFERENCE)) { |
| // Reference type |
| if (type != Type.tString) { |
| // Convert non-string object to string. If object is |
| // a string, we don't need to convert it, except in the |
| // case that it is null, which is handled below. |
| Type argType1[] = {Type.tObject}; |
| MemberDefinition f1 = |
| stClsDef.matchMethod(env, sourceClass, idValueOf, argType1); |
| asm.add(where, opc_invokestatic, f1); |
| } |
| // FIX FOR 4030173 |
| // If the argument was null, then value is "null", but if the |
| // argument was not null, 'toString' was called and could have |
| // returned null. We call 'valueOf' again to make sure that |
| // the result is a non-null string. See JLS 15.17.1.1. The |
| // approach taken here minimizes code size -- open code would |
| // be faster. The 'toString' method for an array class cannot |
| // be overridden, thus we know that it will never return null. |
| if (!type.inMask(TM_ARRAY|TM_NULL)) { |
| Type argType2[] = {Type.tString}; |
| MemberDefinition f2 = |
| stClsDef.matchMethod(env, sourceClass, idValueOf, argType2); |
| asm.add(where, opc_invokestatic, f2); |
| } |
| } else { |
| // Primitive type |
| Type argType[] = {type}; |
| MemberDefinition f = |
| stClsDef.matchMethod(env, sourceClass, idValueOf, argType); |
| asm.add(where, opc_invokestatic, f); |
| } |
| } |
| |
| /** |
| * Convert this expression to a string and append it to the string |
| * buffer on the top of the stack. |
| * If the needBuffer argument is true, the string buffer needs to be |
| * created, initialized, and pushed on the stack, first. |
| */ |
| void codeAppend(Environment env, Context ctx, Assembler asm, |
| ClassDeclaration sbClass, boolean needBuffer) |
| throws ClassNotFound, AmbiguousMember |
| { |
| ClassDefinition sourceClass = ctx.field.getClassDefinition(); |
| ClassDefinition sbClsDef = sbClass.getClassDefinition(env); |
| MemberDefinition f; |
| if (needBuffer) { |
| // need to create the string buffer |
| asm.add(where, opc_new, sbClass); // create the class |
| asm.add(where, opc_dup); |
| if (equals("")) { |
| // make an empty string buffer |
| f = sbClsDef.matchMethod(env, sourceClass, idInit); |
| } else { |
| // optimize by initializing the buffer with the string |
| codeValue(env, ctx, asm); |
| ensureString(env, ctx, asm); |
| Type argType[] = {Type.tString}; |
| f = sbClsDef.matchMethod(env, sourceClass, idInit, argType); |
| } |
| asm.add(where, opc_invokespecial, f); |
| } else { |
| // append this item to the string buffer |
| codeValue(env, ctx, asm); |
| // FIX FOR 4071548 |
| // 'StringBuffer.append' converts its argument as if by |
| // 'valueOf', treating character arrays specially. This |
| // violates JLS 15.17.1.1, which requires that concatenation |
| // convert non-primitive arguments using 'toString'. We force |
| // the treatment of all reference types as type 'Object', thus |
| // invoking an overloading of 'append' that has the required |
| // semantics. |
| Type argType[] = |
| { (type.inMask(TM_REFERENCE) && type != Type.tString) |
| ? Type.tObject |
| : type }; |
| f = sbClsDef.matchMethod(env, sourceClass, idAppend, argType); |
| asm.add(where, opc_invokevirtual, f); |
| } |
| } |
| |
| /** |
| * Code |
| */ |
| void codeDup(Environment env, Context ctx, Assembler asm, int items, int depth) { |
| switch (items) { |
| case 0: |
| return; |
| |
| case 1: |
| switch (depth) { |
| case 0: |
| asm.add(where, opc_dup); |
| return; |
| case 1: |
| asm.add(where, opc_dup_x1); |
| return; |
| case 2: |
| asm.add(where, opc_dup_x2); |
| return; |
| |
| } |
| break; |
| case 2: |
| switch (depth) { |
| case 0: |
| asm.add(where, opc_dup2); |
| return; |
| case 1: |
| asm.add(where, opc_dup2_x1); |
| return; |
| case 2: |
| asm.add(where, opc_dup2_x2); |
| return; |
| |
| } |
| break; |
| } |
| throw new CompilerError("can't dup: " + items + ", " + depth); |
| } |
| |
| void codeConversion(Environment env, Context ctx, Assembler asm, Type f, Type t) { |
| int from = f.getTypeCode(); |
| int to = t.getTypeCode(); |
| |
| switch (to) { |
| case TC_BOOLEAN: |
| if (from != TC_BOOLEAN) { |
| break; |
| } |
| return; |
| case TC_BYTE: |
| if (from != TC_BYTE) { |
| codeConversion(env, ctx, asm, f, Type.tInt); |
| asm.add(where, opc_i2b); |
| } |
| return; |
| case TC_CHAR: |
| if (from != TC_CHAR) { |
| codeConversion(env, ctx, asm, f, Type.tInt); |
| asm.add(where, opc_i2c); |
| } |
| return; |
| case TC_SHORT: |
| if (from != TC_SHORT) { |
| codeConversion(env, ctx, asm, f, Type.tInt); |
| asm.add(where, opc_i2s); |
| } |
| return; |
| case TC_INT: |
| switch (from) { |
| case TC_BYTE: |
| case TC_CHAR: |
| case TC_SHORT: |
| case TC_INT: |
| return; |
| case TC_LONG: |
| asm.add(where, opc_l2i); |
| return; |
| case TC_FLOAT: |
| asm.add(where, opc_f2i); |
| return; |
| case TC_DOUBLE: |
| asm.add(where, opc_d2i); |
| return; |
| } |
| break; |
| case TC_LONG: |
| switch (from) { |
| case TC_BYTE: |
| case TC_CHAR: |
| case TC_SHORT: |
| case TC_INT: |
| asm.add(where, opc_i2l); |
| return; |
| case TC_LONG: |
| return; |
| case TC_FLOAT: |
| asm.add(where, opc_f2l); |
| return; |
| case TC_DOUBLE: |
| asm.add(where, opc_d2l); |
| return; |
| } |
| break; |
| case TC_FLOAT: |
| switch (from) { |
| case TC_BYTE: |
| case TC_CHAR: |
| case TC_SHORT: |
| case TC_INT: |
| asm.add(where, opc_i2f); |
| return; |
| case TC_LONG: |
| asm.add(where, opc_l2f); |
| return; |
| case TC_FLOAT: |
| return; |
| case TC_DOUBLE: |
| asm.add(where, opc_d2f); |
| return; |
| } |
| break; |
| case TC_DOUBLE: |
| switch (from) { |
| case TC_BYTE: |
| case TC_CHAR: |
| case TC_SHORT: |
| case TC_INT: |
| asm.add(where, opc_i2d); |
| return; |
| case TC_LONG: |
| asm.add(where, opc_l2d); |
| return; |
| case TC_FLOAT: |
| asm.add(where, opc_f2d); |
| return; |
| case TC_DOUBLE: |
| return; |
| } |
| break; |
| |
| case TC_CLASS: |
| switch (from) { |
| case TC_NULL: |
| return; |
| case TC_CLASS: |
| case TC_ARRAY: |
| try { |
| if (!env.implicitCast(f, t)) { |
| asm.add(where, opc_checkcast, env.getClassDeclaration(t)); |
| } |
| } catch (ClassNotFound e) { |
| throw new CompilerError(e); |
| } |
| return; |
| } |
| |
| break; |
| |
| case TC_ARRAY: |
| switch (from) { |
| case TC_NULL: |
| return; |
| case TC_CLASS: |
| case TC_ARRAY: |
| try { |
| if (!env.implicitCast(f, t)) { |
| asm.add(where, opc_checkcast, t); |
| } |
| return; |
| } catch (ClassNotFound e) { |
| throw new CompilerError(e); |
| } |
| } |
| break; |
| } |
| throw new CompilerError("codeConversion: " + from + ", " + to); |
| } |
| |
| /** |
| * Check if the first thing is a constructor invocation |
| */ |
| public Expression firstConstructor() { |
| return null; |
| } |
| |
| /** |
| * Create a copy of the expression for method inlining |
| */ |
| public Expression copyInline(Context ctx) { |
| return (Expression)clone(); |
| } |
| |
| /** |
| * Print |
| */ |
| public void print(PrintStream out) { |
| out.print(opNames[op]); |
| } |
| } |