/******************************************************************************* * Copyright (c) 2000, 2004 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Common Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/cpl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; import java.util.ArrayList; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.ASTVisitor; import org.eclipse.jdt.internal.compiler.impl.*; import org.eclipse.jdt.internal.compiler.codegen.*; import org.eclipse.jdt.internal.compiler.flow.*; import org.eclipse.jdt.internal.compiler.lookup.*; import org.eclipse.jdt.internal.compiler.problem.*; import org.eclipse.jdt.internal.compiler.util.Util; public abstract class Expression extends Statement { public static final boolean isConstantValueRepresentable( Constant constant, int constantTypeID, int targetTypeID) { //true if there is no loss of precision while casting. // constantTypeID == constant.typeID if (targetTypeID == constantTypeID) return true; switch (targetTypeID) { case T_char : switch (constantTypeID) { case T_char : return true; case T_double : return constant.doubleValue() == constant.charValue(); case T_float : return constant.floatValue() == constant.charValue(); case T_int : return constant.intValue() == constant.charValue(); case T_short : return constant.shortValue() == constant.charValue(); case T_byte : return constant.byteValue() == constant.charValue(); case T_long : return constant.longValue() == constant.charValue(); default : return false;//boolean } case T_float : switch (constantTypeID) { case T_char : return constant.charValue() == constant.floatValue(); case T_double : return constant.doubleValue() == constant.floatValue(); case T_float : return true; case T_int : return constant.intValue() == constant.floatValue(); case T_short : return constant.shortValue() == constant.floatValue(); case T_byte : return constant.byteValue() == constant.floatValue(); case T_long : return constant.longValue() == constant.floatValue(); default : return false;//boolean } case T_double : switch (constantTypeID) { case T_char : return constant.charValue() == constant.doubleValue(); case T_double : return true; case T_float : return constant.floatValue() == constant.doubleValue(); case T_int : return constant.intValue() == constant.doubleValue(); case T_short : return constant.shortValue() == constant.doubleValue(); case T_byte : return constant.byteValue() == constant.doubleValue(); case T_long : return constant.longValue() == constant.doubleValue(); default : return false; //boolean } case T_byte : switch (constantTypeID) { case T_char : return constant.charValue() == constant.byteValue(); case T_double : return constant.doubleValue() == constant.byteValue(); case T_float : return constant.floatValue() == constant.byteValue(); case T_int : return constant.intValue() == constant.byteValue(); case T_short : return constant.shortValue() == constant.byteValue(); case T_byte : return true; case T_long : return constant.longValue() == constant.byteValue(); default : return false; //boolean } case T_short : switch (constantTypeID) { case T_char : return constant.charValue() == constant.shortValue(); case T_double : return constant.doubleValue() == constant.shortValue(); case T_float : return constant.floatValue() == constant.shortValue(); case T_int : return constant.intValue() == constant.shortValue(); case T_short : return true; case T_byte : return constant.byteValue() == constant.shortValue(); case T_long : return constant.longValue() == constant.shortValue(); default : return false; //boolean } case T_int : switch (constantTypeID) { case T_char : return constant.charValue() == constant.intValue(); case T_double : return constant.doubleValue() == constant.intValue(); case T_float : return constant.floatValue() == constant.intValue(); case T_int : return true; case T_short : return constant.shortValue() == constant.intValue(); case T_byte : return constant.byteValue() == constant.intValue(); case T_long : return constant.longValue() == constant.intValue(); default : return false; //boolean } case T_long : switch (constantTypeID) { case T_char : return constant.charValue() == constant.longValue(); case T_double : return constant.doubleValue() == constant.longValue(); case T_float : return constant.floatValue() == constant.longValue(); case T_int : return constant.intValue() == constant.longValue(); case T_short : return constant.shortValue() == constant.longValue(); case T_byte : return constant.byteValue() == constant.longValue(); case T_long : return true; default : return false; //boolean } default : return false; //boolean } } public Constant constant; //Some expression may not be used - from a java semantic point //of view only - as statements. Other may. In order to avoid the creation //of wrappers around expression in order to tune them as expression //Expression is a subclass of Statement. See the message isValidJavaStatement() public int implicitConversion; public TypeBinding resolvedType; public Expression() { super(); } public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { return flowInfo; } public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo, boolean valueRequired) { return analyseCode(currentScope, flowContext, flowInfo); } /** * Returns false if cast is not legal. */ public final boolean checkCastTypesCompatibility( Scope scope, TypeBinding castType, TypeBinding expressionType, Expression expression) { // see specifications 5.5 // handle errors and process constant when needed // if either one of the type is null ==> // some error has been already reported some where ==> // we then do not report an obvious-cascade-error. if (castType == null || expressionType == null) return true; // identity conversion cannot be performed upfront, due to side-effects // like constant propagation LookupEnvironment env = scope.environment(); boolean use15specifics = env.options.sourceLevel >= JDK1_5; if (castType.isBaseType()) { if (expressionType.isBaseType()) { if (expressionType == castType) { if (expression != null) { this.constant = expression.constant; //use the same constant } tagAsUnnecessaryCast(scope, castType); return true; } boolean necessary = false; if (expressionType.isCompatibleWith(castType) || (necessary = BaseTypeBinding.isNarrowing(castType.id, expressionType.id))) { if (expression != null) { expression.implicitConversion = (castType.id << 4) + expressionType.id; if (expression.constant != Constant.NotAConstant) { constant = expression.constant.castTo(expression.implicitConversion); } } if (!necessary) tagAsUnnecessaryCast(scope, castType); return true; } } else if (use15specifics) { // unboxing - only exact match is allowed if (env.computeBoxingType(expressionType) == castType) { // TODO (philippe) could tagAsUnnecessaryCast(scope, castType); return true; } } reportIllegalCast(scope, castType, expressionType); return false; } else if (use15specifics && expressionType.isBaseType()) { // boxing - only exact match is allowed if (env.computeBoxingType(castType) == expressionType) { // TODO (philippe) could tagAsUnnecessaryCast(scope, castType); return true; } } //-----------cast to something which is NOT a base type-------------------------- if (expressionType == NullBinding) { tagAsUnnecessaryCast(scope, castType); return true; //null is compatible with every thing } if (expressionType.isBaseType()) { reportIllegalCast(scope, castType, expressionType); return false; } if (expressionType.isArrayType()) { if (castType == expressionType) { tagAsUnnecessaryCast(scope, castType); return true; // identity conversion } if (castType.isArrayType()) { //------- (castType.isArray) expressionType.isArray ----------- TypeBinding exprElementType = ((ArrayBinding) expressionType).elementsType(); if (exprElementType.isBaseType()) { // <---stop the recursion------- if (((ArrayBinding) castType).elementsType() == exprElementType) { tagAsNeedCheckCast(); return true; } else { reportIllegalCast(scope, castType, expressionType); return false; } } // recursively on the elements... return checkCastTypesCompatibility( scope, ((ArrayBinding) castType).elementsType(), exprElementType, expression); } else if ( castType.isClass()) { //------(castType.isClass) expressionType.isArray --------------- if (castType.id == T_JavaLangObject) { tagAsUnnecessaryCast(scope, castType); return true; } } else { //------- (castType.isInterface) expressionType.isArray ----------- if (castType.id == T_JavaLangCloneable || castType.id == T_JavaIoSerializable) { tagAsNeedCheckCast(); return true; } } reportIllegalCast(scope, castType, expressionType); return false; } if (expressionType.isClass()) { if (castType.isArrayType()) { // ---- (castType.isArray) expressionType.isClass ------- if (expressionType.id == T_JavaLangObject) { // potential runtime error tagAsNeedCheckCast(); return true; } } else if (castType.isClass()) { // ----- (castType.isClass) expressionType.isClass ------ ReferenceBinding match = ((ReferenceBinding)expressionType).findSuperTypeErasingTo((ReferenceBinding)castType.erasure()); if (match != null) { if (expression != null && castType.id == T_JavaLangString) this.constant = expression.constant; // (String) cst is still a constant return checkUnsafeCast(scope, castType, expressionType, match, false); } match = ((ReferenceBinding)castType).findSuperTypeErasingTo((ReferenceBinding)expressionType.erasure()); if (match != null) { tagAsNeedCheckCast(); return checkUnsafeCast(scope, castType, expressionType, match, true); } } else { // ----- (castType.isInterface) expressionType.isClass ------- ReferenceBinding match = ((ReferenceBinding)expressionType).findSuperTypeErasingTo((ReferenceBinding)castType.erasure()); if (match != null) { return checkUnsafeCast(scope, castType, expressionType, match, false); } // a subclass may implement the interface ==> no check at compile time if (!((ReferenceBinding) expressionType).isFinal()) { tagAsNeedCheckCast(); match = ((ReferenceBinding)castType).findSuperTypeErasingTo((ReferenceBinding)expressionType.erasure()); if (match != null) { return checkUnsafeCast(scope, castType, expressionType, match, true); } return true; } // no subclass for expressionType, thus compile-time check is valid } reportIllegalCast(scope, castType, expressionType); return false; } // if (expressionType.isInterface()) { cannot be anything else if (castType.isArrayType()) { // ----- (castType.isArray) expressionType.isInterface ------ if (expressionType.id == T_JavaLangCloneable || expressionType.id == T_JavaIoSerializable) {// potential runtime error tagAsNeedCheckCast(); return true; } else { reportIllegalCast(scope, castType, expressionType); return false; } } else if (castType.isClass()) { // ----- (castType.isClass) expressionType.isInterface -------- if (castType.id == T_JavaLangObject) { // no runtime error tagAsUnnecessaryCast(scope, castType); return true; } if (((ReferenceBinding) castType).isFinal()) { // no subclass for castType, thus compile-time check is valid ReferenceBinding match = ((ReferenceBinding)castType).findSuperTypeErasingTo((ReferenceBinding)expressionType.erasure()); if (match == null) { // potential runtime error reportIllegalCast(scope, castType, expressionType); return false; } } } else { // ----- (castType.isInterface) expressionType.isInterface ------- ReferenceBinding match = ((ReferenceBinding)expressionType).findSuperTypeErasingTo((ReferenceBinding)castType.erasure()); if (match != null) { return checkUnsafeCast(scope, castType, expressionType, match, false); } match = ((ReferenceBinding)castType).findSuperTypeErasingTo((ReferenceBinding)expressionType.erasure()); if (match != null) { tagAsNeedCheckCast(); return checkUnsafeCast(scope, castType, expressionType, match, true); } else { MethodBinding[] castTypeMethods = getAllInheritedMethods((ReferenceBinding) castType); MethodBinding[] expressionTypeMethods = getAllInheritedMethods((ReferenceBinding) expressionType); int exprMethodsLength = expressionTypeMethods.length; for (int i = 0, castMethodsLength = castTypeMethods.length; i < castMethodsLength; i++) { for (int j = 0; j < exprMethodsLength; j++) { if ((castTypeMethods[i].returnType != expressionTypeMethods[j].returnType) && (CharOperation.equals(castTypeMethods[i].selector, expressionTypeMethods[j].selector)) && castTypeMethods[i].areParametersEqual(expressionTypeMethods[j])) { reportIllegalCast(scope, castType, expressionType); return false; } } } } } tagAsNeedCheckCast(); return true; } public FlowInfo checkNullStatus(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, int nullStatus) { LocalVariableBinding local = this.localVariableBinding(); if (local != null) { switch(nullStatus) { case FlowInfo.NULL : flowContext.recordUsingNullReference(scope, local, this, FlowInfo.NULL, flowInfo); flowInfo.markAsDefinitelyNull(local); // from thereon it is set break; case FlowInfo.NON_NULL : flowContext.recordUsingNullReference(scope, local, this, FlowInfo.NON_NULL, flowInfo); flowInfo.markAsDefinitelyNonNull(local); // from thereon it is set break; case FlowInfo.UNKNOWN : break; } } return flowInfo; } private MethodBinding[] getAllInheritedMethods(ReferenceBinding binding) { ArrayList collector = new ArrayList(); getAllInheritedMethods0(binding, collector); return (MethodBinding[]) collector.toArray(new MethodBinding[collector.size()]); } private void getAllInheritedMethods0(ReferenceBinding binding, ArrayList collector) { if (!binding.isInterface()) return; MethodBinding[] methodBindings = binding.methods(); for (int i = 0, max = methodBindings.length; i < max; i++) { collector.add(methodBindings[i]); } ReferenceBinding[] superInterfaces = binding.superInterfaces(); for (int i = 0, max = superInterfaces.length; i < max; i++) { getAllInheritedMethods0(superInterfaces[i], collector); } } public void checkNullComparison(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, FlowInfo initsWhenTrue, FlowInfo initsWhenFalse) { // do nothing by default - see EqualExpression } public boolean checkUnsafeCast(Scope scope, TypeBinding castType, TypeBinding expressionType, TypeBinding match, boolean isNarrowing) { if (match == castType) { if (!isNarrowing) tagAsUnnecessaryCast(scope, castType); return true; } if (castType.isBoundParameterizedType() || castType.isGenericType()) { if (match.isProvablyDistinctFrom(isNarrowing ? expressionType : castType, 0)) { reportIllegalCast(scope, castType, expressionType); return false; } } if (!isNarrowing) tagAsUnnecessaryCast(scope, castType); return true; } /** * Base types need that the widening is explicitly done by the compiler using some bytecode like i2f. * Also check unsafe type operations. */ public void computeConversion(Scope scope, TypeBinding runtimeTimeType, TypeBinding compileTimeType) { if (runtimeTimeType == null || compileTimeType == null) return; if (this.implicitConversion != 0) return; // already set independantly // it is possible for a Byte to be unboxed to a byte & then converted to an int // but it is not possible for a byte to become Byte & then assigned to an Integer, // or to become an int before boxed into an Integer if (runtimeTimeType != NullBinding && runtimeTimeType.isBaseType()) { if (!compileTimeType.isBaseType()) { compileTimeType = scope.environment().computeBoxingType(compileTimeType); this.implicitConversion = UNBOXING; } } else { if (compileTimeType != NullBinding && compileTimeType.isBaseType()) { TypeBinding boxedType = scope.environment().computeBoxingType(runtimeTimeType); if (boxedType == runtimeTimeType) // Object o = 12; boxedType = compileTimeType; this.implicitConversion = BOXING | (boxedType.id << 4) + compileTimeType.id; return; } } switch (runtimeTimeType.id) { case T_byte : case T_short : case T_char : this.implicitConversion |= (T_int << 4) + compileTimeType.id; break; case T_JavaLangString : case T_float : case T_boolean : case T_double : case T_int : //implicitConversion may result in i2i which will result in NO code gen case T_long : this.implicitConversion |= (runtimeTimeType.id << 4) + compileTimeType.id; break; default : // regular object ref // if (compileTimeType.isRawType() && runtimeTimeType.isBoundParameterizedType()) { // scope.problemReporter().unsafeRawExpression(this, compileTimeType, runtimeTimeType); // } } } /** * Expression statements are plain expressions, however they generate like * normal expressions with no value required. * * @param currentScope org.eclipse.jdt.internal.compiler.lookup.BlockScope * @param codeStream org.eclipse.jdt.internal.compiler.codegen.CodeStream */ public void generateCode(BlockScope currentScope, CodeStream codeStream) { if ((bits & IsReachableMASK) == 0) { return; } generateCode(currentScope, codeStream, false); } /** * Every expression is responsible for generating its implicit conversion when necessary. * * @param currentScope org.eclipse.jdt.internal.compiler.lookup.BlockScope * @param codeStream org.eclipse.jdt.internal.compiler.codegen.CodeStream * @param valueRequired boolean */ public void generateCode( BlockScope currentScope, CodeStream codeStream, boolean valueRequired) { if (constant != NotAConstant) { // generate a constant expression int pc = codeStream.position; codeStream.generateConstant(constant, implicitConversion); codeStream.recordPositionsFrom(pc, this.sourceStart); } else { // actual non-constant code generation throw new ShouldNotImplement(Util.bind("ast.missingCode")); //$NON-NLS-1$ } } /** * Default generation of a boolean value * @param currentScope * @param codeStream * @param trueLabel * @param falseLabel * @param valueRequired */ public void generateOptimizedBoolean( BlockScope currentScope, CodeStream codeStream, Label trueLabel, Label falseLabel, boolean valueRequired) { // a label valued to nil means: by default we fall through the case... // both nil means we leave the value on the stack if ((constant != Constant.NotAConstant) && (constant.typeID() == T_boolean)) { int pc = codeStream.position; if (constant.booleanValue() == true) { // constant == true if (valueRequired) { if (falseLabel == null) { // implicit falling through the FALSE case if (trueLabel != null) { codeStream.goto_(trueLabel); } } } } else { if (valueRequired) { if (falseLabel != null) { // implicit falling through the TRUE case if (trueLabel == null) { codeStream.goto_(falseLabel); } } } } codeStream.recordPositionsFrom(pc, this.sourceStart); return; } generateCode(currentScope, codeStream, valueRequired); // branching int position = codeStream.position; if (valueRequired) { if (falseLabel == null) { if (trueLabel != null) { // Implicit falling through the FALSE case codeStream.ifne(trueLabel); } } else { if (trueLabel == null) { // Implicit falling through the TRUE case codeStream.ifeq(falseLabel); } else { // No implicit fall through TRUE/FALSE --> should never occur } } } // reposition the endPC codeStream.updateLastRecordedEndPC(position); } /* Optimized (java) code generation for string concatenations that involve StringBuffer * creation: going through this path means that there is no need for a new StringBuffer * creation, further operands should rather be only appended to the current one. * By default: no optimization. */ public void generateOptimizedStringConcatenation( BlockScope blockScope, CodeStream codeStream, int typeID) { if (typeID == T_JavaLangString && this.constant != NotAConstant && this.constant.stringValue().length() == 0) { return; // optimize str + "" } generateCode(blockScope, codeStream, true); codeStream.invokeStringConcatenationAppendForType(typeID); } /* Optimized (java) code generation for string concatenations that involve StringBuffer * creation: going through this path means that there is no need for a new StringBuffer * creation, further operands should rather be only appended to the current one. */ public void generateOptimizedStringConcatenationCreation( BlockScope blockScope, CodeStream codeStream, int typeID) { codeStream.newStringContatenation(); codeStream.dup(); switch (typeID) { case T_JavaLangObject : case T_undefined : // in the case the runtime value of valueOf(Object) returns null, we have to use append(Object) instead of directly valueOf(Object) // append(Object) returns append(valueOf(Object)), which means that the null case is handled by the next case. codeStream.invokeStringConcatenationDefaultConstructor(); generateCode(blockScope, codeStream, true); codeStream.invokeStringConcatenationAppendForType(T_JavaLangObject); return; case T_JavaLangString : case T_null : if (constant != NotAConstant) { String stringValue = constant.stringValue(); if (stringValue.length() == 0) { // optimize ""+ codeStream.invokeStringConcatenationDefaultConstructor(); return; } codeStream.ldc(stringValue); } else { // null case is not a constant generateCode(blockScope, codeStream, true); codeStream.invokeStringValueOf(T_JavaLangObject); } break; default : generateCode(blockScope, codeStream, true); codeStream.invokeStringValueOf(typeID); } codeStream.invokeStringConcatenationStringConstructor(); } public boolean isCompactableOperation() { return false; } //Return true if the conversion is done AUTOMATICALLY by the vm //while the javaVM is an int based-machine, thus for example pushing //a byte onto the stack , will automatically create an int on the stack //(this request some work d be done by the VM on signed numbers) public boolean isConstantValueOfTypeAssignableToType(TypeBinding constantType, TypeBinding targetType) { if (constant == Constant.NotAConstant) return false; if (constantType == targetType) return true; if (constantType.isBaseType() && targetType.isBaseType()) { //No free assignment conversion from anything but to integral ones. if ((constantType == IntBinding || BaseTypeBinding.isWidening(T_int, constantType.id)) && (BaseTypeBinding.isNarrowing(targetType.id, T_int))) { //use current explicit conversion in order to get some new value to compare with current one return isConstantValueRepresentable(constant, constantType.id, targetType.id); } } return false; } public boolean isTypeReference() { return false; } public int nullStatus(FlowInfo flowInfo) { if (this.constant != null && this.constant != NotAConstant) return FlowInfo.NON_NULL; // constant expression cannot be null LocalVariableBinding local = localVariableBinding(); if (local != null) { if (flowInfo.isDefinitelyNull(local)) return FlowInfo.NULL; if (flowInfo.isDefinitelyNonNull(local)) return FlowInfo.NON_NULL; return FlowInfo.UNKNOWN; } return FlowInfo.NON_NULL; } /** * Constant usable for bytecode pattern optimizations, but cannot be inlined * since it is not strictly equivalent to the definition of constant expressions. * In particular, some side-effects may be required to occur (only the end value * is known). * @return Constant known to be of boolean type */ public Constant optimizedBooleanConstant() { return this.constant; } public StringBuffer print(int indent, StringBuffer output) { printIndent(indent, output); return printExpression(indent, output); } public abstract StringBuffer printExpression(int indent, StringBuffer output); public StringBuffer printStatement(int indent, StringBuffer output) { return print(indent, output).append(";"); //$NON-NLS-1$ } public void reportIllegalCast(Scope scope, TypeBinding castType, TypeBinding expressionType) { // do nothing by default } public void resolve(BlockScope scope) { // drops the returning expression's type whatever the type is. this.resolveType(scope); return; } public TypeBinding resolveType(BlockScope scope) { // by default... subclasses should implement a better TC if required. return null; } public TypeBinding resolveType(ClassScope classScope) { // by default... subclasses should implement a better TB if required. return null; } public TypeBinding resolveTypeExpecting( BlockScope scope, TypeBinding expectedType) { this.setExpectedType(expectedType); // needed in case of generic method invocation TypeBinding expressionType = this.resolveType(scope); if (expressionType == null) return null; if (expressionType == expectedType) return expressionType; if (!expressionType.isCompatibleWith(expectedType)) { if (scope.isBoxingCompatibleWith(expressionType, expectedType)) { this.computeConversion(scope, expectedType, expressionType); } else { scope.problemReporter().typeMismatchError(expressionType, expectedType, this); return null; } } return expressionType; } /** * Record the type expectation before this expression is typechecked. * e.g. String s = foo();, foo() will be tagged as being expected of type String * Used to trigger proper inference of generic method invocations. */ public void setExpectedType(TypeBinding expectedType) { // do nothing by default } public void tagAsUnnecessaryCast(Scope scope, TypeBinding castType) { // do nothing by default } public void tagAsNeedCheckCast() { // do nothing by default } public Expression toTypeReference() { //by default undefined //this method is meanly used by the parser in order to transform //an expression that is used as a type reference in a cast .... //--appreciate the fact that castExpression and ExpressionWithParenthesis //--starts with the same pattern..... return this; } public void traverse(ASTVisitor visitor, BlockScope scope) { // do nothing by default } public void traverse(ASTVisitor visitor, ClassScope scope) { // do nothing by default } public void traverse(ASTVisitor visitor, CompilationUnitScope scope) { // do nothing by default } /** * Returns the local variable referenced by this node. Can be a direct reference (SingleNameReference) * or thru a cast expression etc... */ public LocalVariableBinding localVariableBinding() { return null; } }