| /******************************************************************************* |
| * Copyright (c) 2000, 2021 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Fraunhofer FIRST - extended API and implementation |
| * Technical University Berlin - extended API and implementation |
| * Stephan Herrmann - Contributions for |
| * bug 292478 - Report potentially null across variable assignment |
| * bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null" |
| * bug 392862 - [1.8][compiler][null] Evaluate null annotations on array types |
| * bug 331649 - [compiler][null] consider null annotations for fields |
| * bug 383368 - [compiler][null] syntactic null analysis for field references |
| * bug 400761 - [compiler][null] null may be return as boolean without a diagnostic |
| * bug 402993 - [null] Follow up of bug 401088: Missing warning about redundant null check |
| * bug 403147 - [compiler][null] FUP of bug 400761: consolidate interaction between unboxing, NPE, and deferred checking |
| * Bug 392099 - [1.8][compiler][null] Apply null annotation on types for null analysis |
| * Bug 417295 - [1.8[[null] Massage type annotated null analysis to gel well with deep encoded type bindings. |
| * Bug 400874 - [1.8][compiler] Inference infrastructure should evolve to meet JLS8 18.x (Part G of JSR335 spec) |
| * Bug 426792 - [1.8][inference][impl] generify new type inference engine |
| * Bug 423505 - [1.8] Implement "18.5.4 More Specific Method Inference" |
| * Bug 427438 - [1.8][compiler] NPE at org.eclipse.jdt.internal.compiler.ast.ConditionalExpression.generateCode(ConditionalExpression.java:280) |
| * Bug 426996 - [1.8][inference] try to avoid method Expression.unresolve()? |
| * Bug 428274 - [1.8] [compiler] Cannot cast from Number to double |
| * Bug 428352 - [1.8][compiler] Resolution errors don't always surface |
| * Bug 452788 - [1.8][compiler] Type not correctly inferred in lambda expression |
| * Lars Vogel <Lars.Vogel@vogella.com> - Contributions for |
| * Bug 473178 |
| *******************************************************************************/ |
| 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.classfmt.ClassFileConstants; |
| import org.eclipse.jdt.internal.compiler.codegen.BranchLabel; |
| import org.eclipse.jdt.internal.compiler.codegen.CodeStream; |
| import org.eclipse.jdt.internal.compiler.flow.FlowContext; |
| import org.eclipse.jdt.internal.compiler.flow.FlowInfo; |
| import org.eclipse.jdt.internal.compiler.impl.Constant; |
| import org.eclipse.jdt.internal.compiler.impl.ReferenceContext; |
| import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.Binding; |
| import org.eclipse.jdt.internal.compiler.lookup.BlockScope; |
| import org.eclipse.jdt.internal.compiler.lookup.ClassScope; |
| import org.eclipse.jdt.internal.compiler.lookup.FieldBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.InferenceContext18; |
| import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.ProblemReasons; |
| import org.eclipse.jdt.internal.compiler.lookup.ProblemReferenceBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.Scope; |
| import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.TagBits; |
| import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.TypeIds; |
| import org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.VariableBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.WildcardBinding; |
| import org.eclipse.jdt.internal.compiler.problem.ShouldNotImplement; |
| import org.eclipse.jdt.internal.compiler.util.Messages; |
| import org.eclipse.objectteams.otdt.core.compiler.IOTConstants; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.control.Config; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.util.TypeAnalyzer; |
| |
| /** |
| * OTDT changes: |
| * What: some support for CastExpressions |
| * Why: implemented here, since some operator expressions and instanceof also need this infrastructure. |
| * How: methods |
| * + adapt: checkCastTypesCompatibility() special treatment of anchors, cast methods etc. |
| * + new: requireRoleCastMethod(), createRoleCheck(): helper for the above |
| * |
| * What: Basic support for baseclass decapsulation. |
| * How: |
| * Strategy A: mark AST as allowing baseclass decapsulation using DecapsulationState |
| * Applies to: |
| * + Parser.consumeClassHeaderPlayedBy() sets type reference to DecapsulationState.ALLOWED |
| * All others to REPORTED: |
| * + field _OT$base (StandardElementGenerator.generatePlayedByElements) |
| * + return type of and cast in _OT$getBase() (StandardElementGenerator.createGetBaseMethod) |
| * + base argument of creation method (CopyInheritance.createCreationMethod) |
| * + base argument in liftTo method (Lifting.createLiftToMethod) |
| * + base argument of lifting ctors (Lifting.createLiftToConstructorDeclaration) * |
| * + _OT$base in Lifting.genRoleRegistrationStatements |
| * + cast inf "new MyRole((MyBase)b))" in Lifting.genCaseStatement |
| * + base argument of callin wrapper (CallinImplementor.createWrapperMethod) |
| * + base arg to lift-call in callin wrapper (CallinImplementor.generateCallinStatements) |
| * + receiver of message send within callout wrapper: |
| * (a) TypeReference of _OT$base |
| * (b) NameReference: MyBaseClass (static case) |
| * + local var "base" (for parameter mappings) in callout wrapper (CalloutImplementor.transformCalloutMethodBody) |
| * + base arg as 'receiver' for callout-to-field (CalloutImplementor.makeArguments) |
| * + cache type reference (AstGenerator.getCacheTypeReference) |
| * Not storing state: |
| * + message send "_OT$getBase()" (determined from the selector). |
| * Strategy B: (not here): during resolving temporarily mark the baseclass as public. |
| * Applies to: |
| * + base method specs (from AbstractMethodMappingDeclaration.resolveMethodSpecs(..)) |
| * + the message send within a callout wrapper (from MessageSend.resolveType(..)) |
| */ |
| public abstract class Expression extends Statement { |
| |
| //{ObjectTeams: baseclass decapsulation support: |
| /** |
| * This enum implements several predicates regarding base references: |
| * - isAllowed() whether or not decapsulation may happen |
| * - CONFINED: if decapsulation of a confined type was attempted (very bad) |
| * - REPORTED: whether decapsulation has already been reported (don't report again) |
| * As another consequence if "isAllowed()" a type reference should preferrably be resolved |
| * using the base import scope. |
| */ |
| public enum DecapsulationState { |
| /** Decapsulation is not allowed. */ |
| NONE { @Override |
| public boolean isAllowed() { return false; }}, |
| /** This node refers to a confined role, decapsulation is not allowed. */ |
| CONFINED { @Override |
| public boolean isAllowed() { return false; }}, |
| /** This node is a playedBy reference, decapsulation is allowed, and not yet reported. */ |
| ALLOWED { @Override |
| public boolean isAllowed() { return true; }}, |
| /** This node is either a playedBy reference for which decapsulation has been reported, |
| * or some other (generated) base reference for which decapsulation shall not be reported. */ |
| REPORTED { @Override |
| public boolean isAllowed() { return true; }}, |
| /** This mode is for team method return types: prefer local resolution, but tolerate base class as a fallback. */ |
| TOLERATED { @Override |
| public boolean isAllowed() { return false; }}; |
| abstract public boolean isAllowed(); |
| } |
| public DecapsulationState getBaseclassDecapsulation() {return DecapsulationState.NONE;} |
| public void tagReportedBaseclassDecapsulation() { /* subclasses may implement */ } |
| |
| /** Is baseclass decapsulation allowed for this node referring to `type'? */ |
| public DecapsulationState getBaseclassDecapsulation(ReferenceBinding type) { |
| if (type == null) |
| return DecapsulationState.NONE; |
| DecapsulationState state = getBaseclassDecapsulation(); |
| if (state == DecapsulationState.NONE) |
| return state; |
| // search superclass Object to detect confined roles |
| // FIXME(SH): should ifc part of a confined class have Confined as its superclass? |
| if (type.isSynthInterface()) |
| type = type.getRealClass(); |
| ReferenceBinding supertype = type.superclass(); |
| while (supertype != null) { |
| if (supertype.id == TypeIds.T_JavaLangObject) |
| return state; |
| supertype = supertype.superclass(); |
| } |
| |
| return DecapsulationState.CONFINED; // confined cannot be decapsulated |
| } |
| /** Is baseclass decapsulation allowed for this node? |
| * PRE: this node is resolved, perhaps to a ProblemReferenceBinding. |
| * POST: If decapsulation takes place, it has been reported. |
| * @param scope enclosing scope of this node. |
| * @return whether decapsulation actually takes place. |
| */ |
| protected boolean checkBaseclassDecapsulation(Scope scope) { |
| if ( this.resolvedType instanceof ProblemReferenceBinding |
| && this.resolvedType.problemId() == ProblemReasons.NotVisible |
| && this.getBaseclassDecapsulation().isAllowed()) |
| { |
| ProblemReferenceBinding problemType = ((ProblemReferenceBinding)this.resolvedType); |
| if ( problemType.closestMatch() != null |
| && !problemType.closestMatch().isCompatibleWith(scope.getJavaLangObject())) |
| return false; // the case of confined roles |
| this.resolvedType = problemType.closestMatch(); |
| scope.problemReporter().decapsulation(this); |
| SourceTypeBinding sourceType = scope.enclosingSourceType(); |
| if (sourceType.isRole()) |
| sourceType.roleModel.markBaseClassDecapsulation((ReferenceBinding)this.resolvedType); |
| return true; |
| } |
| return false; |
| } |
| // SH} |
| |
| public Constant constant; |
| |
| public int statementEnd = -1; |
| |
| //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 static Expression [] NO_EXPRESSIONS = new Expression[0]; |
| |
| |
| 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 Expression() { |
| super(); |
| } |
| |
| @Override |
| public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { |
| return flowInfo; |
| } |
| |
| /** |
| * More sophisticated for of the flow analysis used for analyzing expressions, and be able to optimize out |
| * portions of expressions where no actual value is required. |
| * |
| * @param currentScope |
| * @param flowContext |
| * @param flowInfo |
| * @param valueRequired |
| * @return The state of initialization after the analysis of the current expression |
| */ |
| public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo, boolean valueRequired) { |
| return analyseCode(currentScope, flowContext, flowInfo); |
| } |
| |
| /** |
| * Back-propagation of flow info: before analysing a branch where a given condition is known to hold true/false respectively, |
| * ask the condition to contribute its information to the given flowInfo. |
| * @param flowInfo the info to be used for analysing the branch |
| * @param result condition result that would cause entering the branch |
| */ |
| protected void updateFlowOnBooleanResult(FlowInfo flowInfo, boolean result) { |
| // nop |
| } |
| |
| /** |
| * Returns false if cast is not legal. |
| */ |
| public final boolean checkCastTypesCompatibility(Scope scope, TypeBinding castType, TypeBinding expressionType, Expression expression, boolean useAutoBoxing) { |
| //{ObjectTeams: implement as wrapper delegating to version with new signature: |
| return checkCastTypesCompatibility(scope, castType, expressionType, expression, useAutoBoxing, false); |
| } |
| |
| public final boolean checkCastTypesCompatibility( |
| Scope scope, |
| TypeBinding castType, |
| TypeBinding expressionType, |
| Expression expression, |
| boolean useAutoBoxing, |
| boolean inArrayRecursion) // new parameter |
| { |
| // SH} |
| // 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; |
| |
| //{ObjectTeams: roles need special checks concerning type anchors |
| if ( !inArrayRecursion |
| && handledByGeneratedMethod(scope, castType, expressionType)) |
| return true; |
| //SH} |
| |
| // identity conversion cannot be performed upfront, due to side-effects |
| // like constant propagation |
| boolean use15specifics = scope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_5; |
| boolean use17specifics = scope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_7; |
| useAutoBoxing &= use15specifics; |
| if (castType.isBaseType()) { |
| if (expressionType.isBaseType()) { |
| if (TypeBinding.equalsEquals(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) { |
| this.constant = expression.constant.castTo(expression.implicitConversion); |
| } |
| } |
| if (!necessary) tagAsUnnecessaryCast(scope, castType); |
| return true; |
| |
| } |
| } else if (useAutoBoxing && use17specifics && castType.isPrimitiveType() && expressionType instanceof ReferenceBinding && |
| !expressionType.isBoxedPrimitiveType() && checkCastTypesCompatibility(scope, scope.boxing(castType), expressionType, expression, useAutoBoxing)) { |
| // cast from any reference type (other than boxing types) to base type allowed from 1.7, see JLS $5.5 |
| // by our own interpretation (in accordance with javac) we reject arays, though. |
| return true; |
| } else if (useAutoBoxing |
| && scope.environment().computeBoxingType(expressionType).isCompatibleWith(castType)) { // unboxing - only widening match is allowed |
| tagAsUnnecessaryCast(scope, castType); |
| return true; |
| } |
| return false; |
| } else if (useAutoBoxing |
| && expressionType.isBaseType() |
| && scope.environment().computeBoxingType(expressionType).isCompatibleWith(castType)) { // boxing - only widening match is allowed |
| tagAsUnnecessaryCast(scope, castType); |
| return true; |
| } |
| |
| if (castType.isIntersectionType18()) { |
| ReferenceBinding [] intersectingTypes = castType.getIntersectingTypes(); |
| for (int i = 0, length = intersectingTypes.length; i < length; i++) { |
| if (!checkCastTypesCompatibility(scope, intersectingTypes[i], expressionType, expression, useAutoBoxing)) |
| return false; |
| } |
| return true; |
| } |
| |
| switch(expressionType.kind()) { |
| case Binding.BASE_TYPE : |
| //-----------cast to something which is NOT a base type-------------------------- |
| if (expressionType == TypeBinding.NULL) { |
| tagAsUnnecessaryCast(scope, castType); |
| return true; //null is compatible with every thing |
| } |
| return false; |
| |
| case Binding.ARRAY_TYPE : |
| if (TypeBinding.equalsEquals(castType, expressionType)) { |
| tagAsUnnecessaryCast(scope, castType); |
| return true; // identity conversion |
| } |
| switch (castType.kind()) { |
| case Binding.ARRAY_TYPE : |
| // ( ARRAY ) ARRAY |
| TypeBinding castElementType = ((ArrayBinding) castType).elementsType(); |
| TypeBinding exprElementType = ((ArrayBinding) expressionType).elementsType(); |
| if (exprElementType.isBaseType() || castElementType.isBaseType()) { |
| if (TypeBinding.equalsEquals(castElementType, exprElementType)) { |
| tagAsNeedCheckCast(); |
| return true; |
| } |
| return false; |
| } |
| // recurse on array type elements |
| return checkCastTypesCompatibility(scope, castElementType, exprElementType, expression, useAutoBoxing |
| //{ObjectTeams: new parameter |
| , true/*inArrayRecursion*/); |
| // SH} |
| |
| case Binding.TYPE_PARAMETER : |
| // ( TYPE_PARAMETER ) ARRAY |
| TypeBinding match = expressionType.findSuperTypeOriginatingFrom(castType); |
| if (match == null) { |
| checkUnsafeCast(scope, castType, expressionType, null /*no match*/, true); |
| } |
| for (TypeBinding bound : ((TypeVariableBinding) castType).allUpperBounds()) { |
| if (!checkCastTypesCompatibility(scope, bound, expressionType, expression, useAutoBoxing)) |
| return false; |
| } |
| return true; |
| |
| default: |
| // ( CLASS/INTERFACE ) ARRAY |
| switch (castType.id) { |
| case T_JavaLangCloneable : |
| case T_JavaIoSerializable : |
| tagAsNeedCheckCast(); |
| return true; |
| case T_JavaLangObject : |
| tagAsUnnecessaryCast(scope, castType); |
| return true; |
| default : |
| return false; |
| } |
| } |
| |
| case Binding.TYPE_PARAMETER : |
| TypeBinding match = expressionType.findSuperTypeOriginatingFrom(castType); |
| if (match == null) { |
| // recursively on the type variable upper bounds |
| if (castType instanceof TypeVariableBinding) { |
| // prefer iterating over required types, not provides |
| for (TypeBinding bound : ((TypeVariableBinding)castType).allUpperBounds()) { |
| if (!checkCastTypesCompatibility(scope, bound, expressionType, expression, useAutoBoxing)) |
| return false; |
| } |
| } else { |
| for (TypeBinding bound : ((TypeVariableBinding)expressionType).allUpperBounds()) { |
| if (!checkCastTypesCompatibility(scope, castType, bound, expression, useAutoBoxing)) |
| return false; |
| } |
| } |
| } |
| // if no incompatibility found: |
| return checkUnsafeCast(scope, castType, expressionType, match, match == null); |
| |
| case Binding.WILDCARD_TYPE : |
| case Binding.INTERSECTION_TYPE : |
| match = expressionType.findSuperTypeOriginatingFrom(castType); |
| if (match != null) { |
| return checkUnsafeCast(scope, castType, expressionType, match, false); |
| } |
| TypeBinding bound = ((WildcardBinding)expressionType).bound; |
| if (bound == null) bound = scope.getJavaLangObject(); |
| // recursively on the type variable upper bound |
| return checkCastTypesCompatibility(scope, castType, bound, expression, useAutoBoxing); |
| case Binding.INTERSECTION_TYPE18: |
| ReferenceBinding [] intersectingTypes = expressionType.getIntersectingTypes(); |
| for (int i = 0, length = intersectingTypes.length; i < length; i++) { |
| if (checkCastTypesCompatibility(scope, castType, intersectingTypes[i], expression, useAutoBoxing)) |
| return true; |
| } |
| return false; |
| default: |
| if (expressionType.isInterface()) { |
| switch (castType.kind()) { |
| case Binding.ARRAY_TYPE : |
| // ( ARRAY ) INTERFACE |
| switch (expressionType.id) { |
| case T_JavaLangCloneable : |
| case T_JavaIoSerializable : |
| tagAsNeedCheckCast(); |
| return true; |
| default : |
| return false; |
| } |
| |
| case Binding.TYPE_PARAMETER : |
| // ( INTERFACE ) TYPE_PARAMETER |
| match = expressionType.findSuperTypeOriginatingFrom(castType); |
| if (match == null) { |
| checkUnsafeCast(scope, castType, expressionType, null /*no match*/, true); |
| } |
| // recursively on the type variable upper bounds |
| for (TypeBinding upperBound : ((TypeVariableBinding)castType).allUpperBounds()) { |
| if (!checkCastTypesCompatibility(scope, upperBound, expressionType, expression, useAutoBoxing)) |
| return false; |
| } |
| return true; |
| |
| default : |
| if (castType.isInterface()) { |
| // ( INTERFACE ) INTERFACE |
| ReferenceBinding interfaceType = (ReferenceBinding) expressionType; |
| match = interfaceType.findSuperTypeOriginatingFrom(castType); |
| if (match != null) { |
| return checkUnsafeCast(scope, castType, interfaceType, match, false); |
| } |
| tagAsNeedCheckCast(); |
| match = castType.findSuperTypeOriginatingFrom(interfaceType); |
| if (match != null) { |
| return checkUnsafeCast(scope, castType, interfaceType, match, true); |
| } |
| if (use15specifics) { |
| checkUnsafeCast(scope, castType, expressionType, null /*no match*/, true); |
| // ensure there is no collision between both interfaces: i.e. I1 extends List<String>, I2 extends List<Object> |
| if (scope.compilerOptions().complianceLevel < ClassFileConstants.JDK1_7) { |
| if (interfaceType.hasIncompatibleSuperType((ReferenceBinding) castType)) { |
| return false; |
| } |
| } else if (!castType.isRawType() && interfaceType.hasIncompatibleSuperType((ReferenceBinding) castType)) { |
| return false; |
| } |
| } else { |
| // pre1.5 semantics - no covariance allowed (even if 1.5 compliant, but 1.4 source) |
| // look at original methods rather than the parameterized variants at 1.4 to detect |
| // covariance. Otherwise when confronted with one raw type and one parameterized type, |
| // we could mistakenly detect covariance and scream foul. See https://bugs.eclipse.org/bugs/show_bug.cgi?id=332744 |
| MethodBinding[] castTypeMethods = getAllOriginalInheritedMethods((ReferenceBinding) castType); |
| MethodBinding[] expressionTypeMethods = getAllOriginalInheritedMethods((ReferenceBinding) expressionType); |
| int exprMethodsLength = expressionTypeMethods.length; |
| for (int i = 0, castMethodsLength = castTypeMethods.length; i < castMethodsLength; i++) { |
| for (int j = 0; j < exprMethodsLength; j++) { |
| if ((TypeBinding.notEquals(castTypeMethods[i].returnType, expressionTypeMethods[j].returnType)) |
| && (CharOperation.equals(castTypeMethods[i].selector, expressionTypeMethods[j].selector)) |
| && castTypeMethods[i].areParametersEqual(expressionTypeMethods[j])) { |
| return false; |
| |
| } |
| } |
| } |
| } |
| return true; |
| } else { |
| // ( CLASS ) INTERFACE |
| if (castType.id == TypeIds.T_JavaLangObject) { // no runtime error |
| //{ObjectTeams: exception for confined type (except from generated like _OT$addRole(Object)): |
| if (TypeAnalyzer.isConfined(expressionType) && !scope.isGeneratedScope()) { |
| tagAsNeedCheckCast(); |
| return false; |
| } |
| // SH} |
| tagAsUnnecessaryCast(scope, castType); |
| return true; |
| } |
| // can only be a downcast |
| tagAsNeedCheckCast(); |
| match = castType.findSuperTypeOriginatingFrom(expressionType); |
| if (match != null) { |
| return checkUnsafeCast(scope, castType, expressionType, match, true); |
| } |
| //{ObjectTeams: casting IBoundBase to final bound base class? |
| if ((expressionType.id == IOTConstants.T_OrgObjectTeamsIBoundBase |
| || expressionType.id == IOTConstants.T_OrgObjectTeamsIBoundBase2) |
| && castType instanceof ReferenceBinding |
| && ((ReferenceBinding) castType).isBoundBase()) |
| { |
| return true; |
| } |
| // SH} |
| if (((ReferenceBinding) castType).isFinal()) { |
| // no subclass for castType, thus compile-time check is invalid |
| return false; |
| } |
| if (use15specifics) { |
| checkUnsafeCast(scope, castType, expressionType, null /*no match*/, true); |
| // ensure there is no collision between both interfaces: i.e. I1 extends List<String>, I2 extends List<Object> |
| if (scope.compilerOptions().complianceLevel < ClassFileConstants.JDK1_7) { |
| if (((ReferenceBinding)castType).hasIncompatibleSuperType((ReferenceBinding) expressionType)) { |
| return false; |
| } |
| } else if (!castType.isRawType() && ((ReferenceBinding)castType).hasIncompatibleSuperType((ReferenceBinding) expressionType)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| } |
| } else { |
| switch (castType.kind()) { |
| case Binding.ARRAY_TYPE : |
| // ( ARRAY ) CLASS |
| if (expressionType.id == TypeIds.T_JavaLangObject) { // potential runtime error |
| if (use15specifics) checkUnsafeCast(scope, castType, expressionType, expressionType, true); |
| tagAsNeedCheckCast(); |
| return true; |
| } |
| return false; |
| |
| case Binding.TYPE_PARAMETER : |
| // ( TYPE_PARAMETER ) CLASS |
| match = expressionType.findSuperTypeOriginatingFrom(castType); |
| if (match == null) { |
| checkUnsafeCast(scope, castType, expressionType, null, true); |
| } |
| // recursively on the type variable upper bounds |
| for (TypeBinding upperBound : ((TypeVariableBinding)castType).allUpperBounds()) { |
| if (!checkCastTypesCompatibility(scope, upperBound, expressionType, expression, useAutoBoxing)) |
| return false; |
| } |
| return true; |
| |
| default : |
| if (castType.isInterface()) { |
| // ( INTERFACE ) CLASS |
| ReferenceBinding refExprType = (ReferenceBinding) expressionType; |
| match = refExprType.findSuperTypeOriginatingFrom(castType); |
| //{ObjectTeams: conformance of final bound base class to IBoundBase? |
| if ( match == null |
| && refExprType.isBoundBase() |
| && (castType.id == IOTConstants.T_OrgObjectTeamsIBoundBase |
| || castType.id == IOTConstants.T_OrgObjectTeamsIBoundBase2)) |
| match = castType; |
| // SH} |
| if (match != null) { |
| return checkUnsafeCast(scope, castType, expressionType, match, false); |
| } |
| // unless final a subclass may implement the interface ==> no check at compile time |
| if (refExprType.isFinal()) { |
| return false; |
| } |
| tagAsNeedCheckCast(); |
| match = castType.findSuperTypeOriginatingFrom(expressionType); |
| if (match != null) { |
| return checkUnsafeCast(scope, castType, expressionType, match, true); |
| } |
| if (use15specifics) { |
| checkUnsafeCast(scope, castType, expressionType, null /*no match*/, true); |
| // ensure there is no collision between both interfaces: i.e. I1 extends List<String>, I2 extends List<Object> |
| if (scope.compilerOptions().complianceLevel < ClassFileConstants.JDK1_7) { |
| if (refExprType.hasIncompatibleSuperType((ReferenceBinding) castType)) { |
| return false; |
| } |
| } else if (!castType.isRawType() && refExprType.hasIncompatibleSuperType((ReferenceBinding) castType)) { |
| return false; |
| } |
| } |
| return true; |
| } else { |
| // ( CLASS ) CLASS |
| //{ObjectTeams: check whether compatibility includes cast from role-ifc to role-class |
| // mark this as requiring runtime check |
| Config oldConfig = Config.createOrResetConfig(this); |
| try { |
| // orig: |
| match = expressionType.findSuperTypeOriginatingFrom(castType); |
| // :giro |
| ReferenceBinding requiredClass = Config.getCastRequired(); |
| if (match != null && requiredClass != null) { |
| Config.setCastRequired(null); |
| // will this expression fulfill the requirement? |
| if ( this.resolvedType != null |
| && this.resolvedType.isCompatibleWith(requiredClass) |
| && Config.getCastRequired() == null) // check side effect of isCompatiblyWith |
| { |
| tagAsNeedCheckCast(); |
| } else { |
| match = null; // reset false match |
| } |
| } |
| } finally { |
| Config.removeOrRestore(oldConfig, this); |
| } |
| // SH} |
| if (match != null) { |
| if (expression != null && castType.id == TypeIds.T_JavaLangString) this.constant = expression.constant; // (String) cst is still a constant |
| return checkUnsafeCast(scope, castType, expressionType, match, false); |
| } |
| match = castType.findSuperTypeOriginatingFrom(expressionType); |
| if (match != null) { |
| tagAsNeedCheckCast(); |
| return checkUnsafeCast(scope, castType, expressionType, match, true); |
| } |
| return false; |
| } |
| } |
| } |
| } |
| } |
| |
| //{ObjectTeams: hook for CastExpression to short-cut checkCastTypesCompatibility(..) |
| /** |
| * Is this expression actually translated into a call to a generated method? |
| * Hook for CastExpression. |
| */ |
| boolean handledByGeneratedMethod(Scope scope, TypeBinding castType, TypeBinding expressionType) |
| { return false; } |
| // SH} |
| /** |
| * Check this expression against potential NPEs, which may occur: |
| * <ul> |
| * <li>if the expression is the receiver in a field access, qualified allocation, array reference or message send |
| * incl. implicit message sends like it happens for the collection in a foreach statement.</li> |
| * <li>if the expression is subject to unboxing</li> |
| * <li>if the expression is the exception in a throw statement</li> |
| * </ul> |
| * If a risk of NPE is detected report it to the context. |
| * If the expression denotes a local variable, mark it as checked, which affects the flow info. |
| * @param scope the scope of the analysis |
| * @param flowContext the current flow context |
| * @param flowInfo the upstream flow info; caveat: may get modified |
| * @param ttlForFieldCheck if this is a reference to a field we will mark that field as nonnull for the specified timeToLive |
| * @return could this expression be checked by the current implementation? |
| */ |
| public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, int ttlForFieldCheck) { |
| boolean isNullable = false; |
| if (this.resolvedType != null) { |
| // 1. priority: @NonNull |
| if ((this.resolvedType.tagBits & TagBits.AnnotationNonNull) != 0) { |
| return true; // no danger |
| } else if ((this.resolvedType.tagBits & TagBits.AnnotationNullable) != 0) { |
| isNullable = true; |
| } |
| } |
| LocalVariableBinding local = localVariableBinding(); |
| if (local != null && |
| (local.type.tagBits & TagBits.IsBaseType) == 0) { |
| // 2. priority: local with flow analysis (via the FlowContext) |
| if ((this.bits & ASTNode.IsNonNull) == 0) { |
| flowContext.recordUsingNullReference(scope, local, this, |
| FlowContext.MAY_NULL, flowInfo); |
| // account for possible NPE: |
| if (!flowInfo.isDefinitelyNonNull(local)) { |
| flowContext.recordAbruptExit(); |
| } |
| } |
| flowInfo.markAsComparedEqualToNonNull(local); |
| // from thereon it is set |
| flowContext.markFinallyNullStatus(local, FlowInfo.NON_NULL); |
| return true; |
| } else if (isNullable) { |
| // 3. priority: @Nullable without a local |
| scope.problemReporter().dereferencingNullableExpression(this); |
| return true; |
| } |
| return false; // not checked |
| } |
| public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) { |
| return checkNPE(scope, flowContext, flowInfo, 0); // default: don't mark field references as checked for null |
| } |
| |
| /** If this expression requires unboxing check if that operation can throw NPE. */ |
| protected void checkNPEbyUnboxing(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) { |
| int status; |
| if ((this.implicitConversion & UNBOXING) != 0 |
| && (this.bits & ASTNode.IsNonNull) == 0 |
| && (status = nullStatus(flowInfo, flowContext)) != FlowInfo.NON_NULL) |
| { |
| flowContext.recordUnboxing(scope, this, status, flowInfo); |
| } |
| } |
| |
| public boolean checkUnsafeCast(Scope scope, TypeBinding castType, TypeBinding expressionType, TypeBinding match, boolean isNarrowing) { |
| if (TypeBinding.equalsEquals(match, castType)) { |
| if (!isNarrowing) tagAsUnnecessaryCast(scope, castType); |
| return true; |
| } |
| if (match != null && (!castType.isReifiable() || !expressionType.isReifiable())) { |
| if(isNarrowing |
| ? match.isProvablyDistinct(expressionType) |
| : castType.isProvablyDistinct(match)) { |
| 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 runtimeType, TypeBinding compileTimeType) { |
| if (runtimeType == null || compileTimeType == null) |
| return; |
| if (this.implicitConversion != 0) return; // already set independently |
| |
| // 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 (runtimeType != TypeBinding.NULL && runtimeType.isBaseType()) { |
| if (!compileTimeType.isBaseType()) { |
| TypeBinding unboxedType = scope.environment().computeBoxingType(compileTimeType); |
| this.implicitConversion = TypeIds.UNBOXING; |
| scope.problemReporter().autoboxing(this, compileTimeType, runtimeType); |
| compileTimeType = unboxedType; |
| } |
| } else if (compileTimeType != TypeBinding.NULL && compileTimeType.isBaseType()) { |
| TypeBinding boxedType = scope.environment().computeBoxingType(runtimeType); |
| if (TypeBinding.equalsEquals(boxedType, runtimeType)) // Object o = 12; |
| boxedType = compileTimeType; |
| if (boxedType.id > TypeIds.T_JavaLangBoolean) { // (Comparable & Serializable) 0 |
| boxedType = compileTimeType; |
| } |
| this.implicitConversion = TypeIds.BOXING | (boxedType.id << 4) + compileTimeType.id; |
| scope.problemReporter().autoboxing(this, compileTimeType, scope.environment().computeBoxingType(boxedType)); |
| return; |
| } else if (this.constant != Constant.NotAConstant && this.constant.typeID() != TypeIds.T_JavaLangString) { |
| this.implicitConversion = TypeIds.BOXING; |
| return; |
| } |
| int compileTimeTypeID, runtimeTypeID; |
| if ((compileTimeTypeID = compileTimeType.id) >= TypeIds.T_LastWellKnownTypeId) { // e.g. ? extends String ==> String (103227); >= TypeIds.T_LastWellKnownTypeId implies TypeIds.NoId |
| compileTimeTypeID = compileTimeType.erasure().id == TypeIds.T_JavaLangString ? TypeIds.T_JavaLangString : TypeIds.T_JavaLangObject; |
| } else if (runtimeType.isPrimitiveType() && compileTimeType instanceof ReferenceBinding && !compileTimeType.isBoxedPrimitiveType()) { |
| compileTimeTypeID = TypeIds.T_JavaLangObject; // treatment is the same as for jlO. |
| } |
| |
| switch (runtimeTypeID = runtimeType.id) { |
| case T_byte : |
| case T_short : |
| case T_char : |
| if (compileTimeTypeID == TypeIds.T_JavaLangObject) { |
| this.implicitConversion |= (runtimeTypeID << 4) + compileTimeTypeID; |
| } else { |
| this.implicitConversion |= (TypeIds.T_int << 4) + compileTimeTypeID; |
| } |
| 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 |= (runtimeTypeID << 4) + compileTimeTypeID; |
| break; |
| default : // regular object ref |
| // if (compileTimeType.isRawType() && runtimeTimeType.isBoundParameterizedType()) { |
| // scope.problemReporter().unsafeRawExpression(this, compileTimeType, runtimeTimeType); |
| // } |
| } |
| } |
| |
| public static int computeNullStatus(int status, int combinedStatus) { |
| if ((combinedStatus & (FlowInfo.NULL|FlowInfo.POTENTIALLY_NULL)) != 0) |
| status |= FlowInfo.POTENTIALLY_NULL; |
| if ((combinedStatus & (FlowInfo.NON_NULL|FlowInfo.POTENTIALLY_NON_NULL)) != 0) |
| status |= FlowInfo.POTENTIALLY_NON_NULL; |
| if ((combinedStatus & (FlowInfo.UNKNOWN|FlowInfo.POTENTIALLY_UNKNOWN)) != 0) |
| status |= FlowInfo.POTENTIALLY_UNKNOWN; |
| return status; |
| } |
| /** |
| * 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 |
| */ |
| @Override |
| public void generateCode(BlockScope currentScope, CodeStream codeStream) { |
| if ((this.bits & ASTNode.IsReachable) == 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 (this.constant != Constant.NotAConstant) { |
| // generate a constant expression |
| int pc = codeStream.position; |
| codeStream.generateConstant(this.constant, this.implicitConversion); |
| codeStream.recordPositionsFrom(pc, this.sourceStart); |
| } else { |
| // actual non-constant code generation |
| throw new ShouldNotImplement(Messages.ast_missingCode); |
| } |
| } |
| public void addPatternVariables(BlockScope scope, CodeStream codeStream) { |
| // Nothing by default |
| } |
| public LocalDeclaration getPatternVariableIntroduced() { |
| return null; |
| } |
| public void collectPatternVariablesToScope(LocalVariableBinding[] variables, BlockScope scope) { |
| new ASTVisitor() { |
| LocalVariableBinding[] patternVariablesInScope; |
| @Override |
| public boolean visit(Argument argument, BlockScope skope) { |
| // Most likely to be a lambda parameter |
| argument.addPatternVariablesWhenTrue(this.patternVariablesInScope); |
| return true; |
| } |
| @Override |
| public boolean visit( |
| QualifiedNameReference nameReference, |
| BlockScope skope) { |
| nameReference.addPatternVariablesWhenTrue(this.patternVariablesInScope); |
| return true; |
| } |
| @Override |
| public boolean visit( |
| SingleNameReference nameReference, |
| BlockScope skope) { |
| nameReference.addPatternVariablesWhenTrue(this.patternVariablesInScope); |
| return true; |
| } |
| |
| public void propagatePatternVariablesInScope(LocalVariableBinding[] vars, BlockScope skope) { |
| this.patternVariablesInScope = vars; |
| Expression.this.traverse(this, skope); |
| } |
| }.propagatePatternVariablesInScope(variables, scope); |
| } |
| |
| /** |
| * Default generation of a boolean value |
| * @param currentScope |
| * @param codeStream |
| * @param trueLabel |
| * @param falseLabel |
| * @param valueRequired |
| */ |
| public void generateOptimizedBoolean(BlockScope currentScope, CodeStream codeStream, BranchLabel trueLabel, BranchLabel 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 |
| |
| Constant cst = optimizedBooleanConstant(); |
| generateCode(currentScope, codeStream, valueRequired && cst == Constant.NotAConstant); |
| if ((cst != Constant.NotAConstant) && (cst.typeID() == TypeIds.T_boolean)) { |
| int pc = codeStream.position; |
| if (cst.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; |
| } |
| // 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 |
| } |
| } |
| } |
| codeStream.recordPositionsFrom(position, this.sourceEnd); |
| } |
| |
| /* 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 == TypeIds.T_JavaLangString && this.constant != 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(TypeIds.T_JavaLangObject); |
| return; |
| case T_JavaLangString : |
| case T_null : |
| if (this.constant != Constant.NotAConstant) { |
| String stringValue = this.constant.stringValue(); |
| if (stringValue.length() == 0) { // optimize ""+<str> |
| codeStream.invokeStringConcatenationDefaultConstructor(); |
| return; |
| } |
| codeStream.ldc(stringValue); |
| } else { |
| // null case is not a constant |
| generateCode(blockScope, codeStream, true); |
| codeStream.invokeStringValueOf(TypeIds.T_JavaLangObject); |
| } |
| break; |
| default : |
| generateCode(blockScope, codeStream, true); |
| codeStream.invokeStringValueOf(typeID); |
| } |
| codeStream.invokeStringConcatenationStringConstructor(); |
| } |
| |
| private MethodBinding[] getAllOriginalInheritedMethods(ReferenceBinding binding) { |
| ArrayList<MethodBinding> collector = new ArrayList<>(); |
| getAllInheritedMethods0(binding, collector); |
| for (int i = 0, len = collector.size(); i < len; i++) { |
| collector.set(i, collector.get(i).original()); |
| } |
| return collector.toArray(new MethodBinding[collector.size()]); |
| } |
| |
| private void getAllInheritedMethods0(ReferenceBinding binding, ArrayList<MethodBinding> 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 static Binding getDirectBinding(Expression someExpression) { |
| if ((someExpression.bits & ASTNode.IgnoreNoEffectAssignCheck) != 0) { |
| return null; |
| } |
| if (someExpression instanceof SingleNameReference) { |
| return ((SingleNameReference)someExpression).binding; |
| } else if (someExpression instanceof FieldReference) { |
| FieldReference fieldRef = (FieldReference)someExpression; |
| if (fieldRef.receiver.isThis() && !(fieldRef.receiver instanceof QualifiedThisReference)) { |
| return fieldRef.binding; |
| } |
| } else if (someExpression instanceof Assignment) { |
| Expression lhs = ((Assignment)someExpression).lhs; |
| if ((lhs.bits & ASTNode.IsStrictlyAssigned) != 0) { |
| // i = i = ...; // eq to int i = ...; |
| return getDirectBinding (((Assignment)someExpression).lhs); |
| } else if (someExpression instanceof PrefixExpression) { |
| // i = i++; // eq to ++i; |
| return getDirectBinding (((Assignment)someExpression).lhs); |
| } |
| } else if (someExpression instanceof QualifiedNameReference) { |
| QualifiedNameReference qualifiedNameReference = (QualifiedNameReference) someExpression; |
| if (qualifiedNameReference.indexOfFirstFieldBinding != 1 |
| && qualifiedNameReference.otherBindings == null) { |
| // case where a static field is retrieved using ClassName.fieldname |
| return qualifiedNameReference.binding; |
| } |
| } else if (someExpression.isThis()) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=276741 |
| return someExpression.resolvedType; |
| } |
| // } else if (someExpression instanceof PostfixExpression) { // recurse for postfix: i++ --> i |
| // // note: "b = b++" is equivalent to doing nothing, not to "b++" |
| // return getDirectBinding(((PostfixExpression) someExpression).lhs); |
| return null; |
| } |
| |
| 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 (this.constant == Constant.NotAConstant) |
| return false; |
| if (TypeBinding.equalsEquals(constantType, targetType)) |
| return true; |
| //No free assignment conversion from anything but to integral ones. |
| if (BaseTypeBinding.isWidening(TypeIds.T_int, constantType.id) |
| && (BaseTypeBinding.isNarrowing(targetType.id, TypeIds.T_int))) { |
| //use current explicit conversion in order to get some new value to compare with current one |
| return isConstantValueRepresentable(this.constant, constantType.id, targetType.id); |
| } |
| return false; |
| } |
| |
| public boolean isTypeReference() { |
| return false; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * Mark this expression as being non null, per a specific tag in the |
| * source code. |
| */ |
| // this is no more called for now, waiting for inter procedural null reference analysis |
| public void markAsNonNull() { |
| this.bits |= ASTNode.IsNonNull; |
| } |
| |
| public int nullStatus(FlowInfo flowInfo, FlowContext flowContext) { |
| // many kinds of expression need no analysis / are always non-null, make it the default: |
| 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 boolean isPertinentToApplicability(TypeBinding targetType, MethodBinding method) { |
| return true; |
| } |
| /** |
| * Returns the type of the expression after required implicit conversions. When expression type gets promoted |
| * or inserted a generic cast, the converted type will differ from the resolved type (surface side-effects from |
| * #computeConversion(...)). |
| * @return the type after implicit conversion |
| */ |
| public TypeBinding postConversionType(Scope scope) { |
| TypeBinding convertedType = this.resolvedType; |
| int runtimeType = (this.implicitConversion & TypeIds.IMPLICIT_CONVERSION_MASK) >> 4; |
| switch (runtimeType) { |
| case T_boolean : |
| convertedType = TypeBinding.BOOLEAN; |
| break; |
| case T_byte : |
| convertedType = TypeBinding.BYTE; |
| break; |
| case T_short : |
| convertedType = TypeBinding.SHORT; |
| break; |
| case T_char : |
| convertedType = TypeBinding.CHAR; |
| break; |
| case T_int : |
| convertedType = TypeBinding.INT; |
| break; |
| case T_float : |
| convertedType = TypeBinding.FLOAT; |
| break; |
| case T_long : |
| convertedType = TypeBinding.LONG; |
| break; |
| case T_double : |
| convertedType = TypeBinding.DOUBLE; |
| break; |
| default : |
| } |
| if ((this.implicitConversion & TypeIds.BOXING) != 0) { |
| convertedType = scope.environment().computeBoxingType(convertedType); |
| } |
| return convertedType; |
| } |
| |
| @Override |
| public StringBuffer print(int indent, StringBuffer output) { |
| printIndent(indent, output); |
| return printExpression(indent, output); |
| } |
| |
| public abstract StringBuffer printExpression(int indent, StringBuffer output); |
| |
| @Override |
| public StringBuffer printStatement(int indent, StringBuffer output) { |
| return print(indent, output).append(";"); //$NON-NLS-1$ |
| } |
| |
| @Override |
| public void resolve(BlockScope scope) { |
| // drops the returning expression's type whatever the type is. |
| this.resolveType(scope); |
| return; |
| } |
| @Override |
| public TypeBinding resolveExpressionType(BlockScope scope) { |
| return resolveType(scope); |
| } |
| |
| /** |
| * Resolve the type of this expression in the context of a blockScope |
| * |
| * @param scope |
| * @return |
| * Return the actual type of this expression after resolution |
| */ |
| public TypeBinding resolveType(BlockScope scope) { |
| // by default... subclasses should implement a better TB if required. |
| return null; |
| } |
| |
| /** |
| * Resolve the type of this expression in the context of a classScope |
| * |
| * @param scope |
| * @return |
| * Return the actual type of this expression after resolution |
| */ |
| public TypeBinding resolveType(ClassScope scope) { |
| // by default... subclasses should implement a better TB if required. |
| return null; |
| } |
| |
| public TypeBinding resolveTypeExpecting(BlockScope scope, TypeBinding expectedType) { |
| setExpectedType(expectedType); // needed in case of generic method invocation |
| TypeBinding expressionType = this.resolveType(scope); |
| if (expressionType == null) return null; |
| if (TypeBinding.equalsEquals(expressionType, expectedType)) return expressionType; |
| |
| if (!expressionType.isCompatibleWith(expectedType)) { |
| if (scope.isBoxingCompatibleWith(expressionType, expectedType)) { |
| computeConversion(scope, expectedType, expressionType); |
| } else { |
| scope.problemReporter().typeMismatchError(expressionType, expectedType, this, null); |
| return null; |
| } |
| } |
| return expressionType; |
| } |
| |
| public Expression resolveExpressionExpecting(TypeBinding targetType, Scope scope, InferenceContext18 context) { |
| return this; // subclasses should implement for a better resolved expression if required. |
| } |
| |
| /** |
| * Returns true if the receiver is forced to be of raw type either to satisfy the contract imposed |
| * by a super type or because it *is* raw and the current type has no control over it (i.e the rawness |
| * originates from some other file.) |
| */ |
| public boolean forcedToBeRaw(ReferenceContext referenceContext) { |
| if (this instanceof NameReference) { |
| final Binding receiverBinding = ((NameReference) this).binding; |
| if (receiverBinding.isParameter() && (((LocalVariableBinding) receiverBinding).tagBits & TagBits.ForcedToBeRawType) != 0) { |
| return true; // parameter is forced to be raw since super method uses raw types. |
| } else if (receiverBinding instanceof FieldBinding) { |
| FieldBinding field = (FieldBinding) receiverBinding; |
| if (field.type.isRawType()) { |
| if (referenceContext instanceof AbstractMethodDeclaration) { |
| AbstractMethodDeclaration methodDecl = (AbstractMethodDeclaration) referenceContext; |
| ReferenceBinding declaringClass = methodDecl.binding != null |
| ? methodDecl.binding.declaringClass |
| : methodDecl.scope.enclosingReceiverType(); |
| if (TypeBinding.notEquals(field.declaringClass, declaringClass)) { // inherited raw field, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=337962 |
| return true; |
| } |
| } else if (referenceContext instanceof TypeDeclaration) { |
| TypeDeclaration type = (TypeDeclaration) referenceContext; |
| if (TypeBinding.notEquals(field.declaringClass, type.binding)) { // inherited raw field, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=337962 |
| return true; |
| } |
| } |
| } |
| } |
| } else if (this instanceof MessageSend) { |
| if (!CharOperation.equals(((MessageSend) this).binding.declaringClass.getFileName(), |
| referenceContext.compilationResult().getFileName())) { // problem is rooted elsewhere |
| return true; |
| } |
| } else if (this instanceof FieldReference) { |
| FieldBinding field = ((FieldReference) this).binding; |
| if (!CharOperation.equals(field.declaringClass.getFileName(), |
| referenceContext.compilationResult().getFileName())) { // problem is rooted elsewhere |
| return true; |
| } |
| if (field.type.isRawType()) { |
| if (referenceContext instanceof AbstractMethodDeclaration) { |
| AbstractMethodDeclaration methodDecl = (AbstractMethodDeclaration) referenceContext; |
| ReferenceBinding declaringClass = methodDecl.binding != null |
| ? methodDecl.binding.declaringClass |
| : methodDecl.scope.enclosingReceiverType(); |
| if (TypeBinding.notEquals(field.declaringClass, declaringClass)) { // inherited raw field, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=337962 |
| return true; |
| } |
| } else if (referenceContext instanceof TypeDeclaration) { |
| TypeDeclaration type = (TypeDeclaration) referenceContext; |
| if (TypeBinding.notEquals(field.declaringClass, type.binding)) { // inherited raw field, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=337962 |
| return true; |
| } |
| } |
| } |
| } else if (this instanceof ConditionalExpression) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=337751 |
| ConditionalExpression ternary = (ConditionalExpression) this; |
| if (ternary.valueIfTrue.forcedToBeRaw(referenceContext) || ternary.valueIfFalse.forcedToBeRaw(referenceContext)) { |
| return true; |
| } |
| } else if (this instanceof SwitchExpression) { |
| SwitchExpression se = (SwitchExpression) this; |
| for (Expression e : se.resultExpressions) { |
| if (e.forcedToBeRaw(referenceContext)) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Returns an object which can be used to identify identical JSR sequence targets |
| * (see TryStatement subroutine codegen) |
| * or <code>null</null> if not reusable |
| */ |
| public Object reusableJSRTarget() { |
| if (this.constant != Constant.NotAConstant && (this.implicitConversion & TypeIds.BOXING) == 0) { |
| return this.constant; |
| } |
| return null; |
| } |
| |
| /** |
| * 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. |
| * |
| * @param expectedType |
| * The type denoting an expectation in the context of an assignment conversion |
| */ |
| public void setExpectedType(TypeBinding expectedType) { |
| // do nothing by default |
| } |
| |
| public void setExpressionContext(ExpressionContext context) { |
| // don't care. Subclasses that are poly expressions in specific contexts should listen in and make note. |
| } |
| |
| public boolean isCompatibleWith(TypeBinding left, Scope scope) { |
| return this.resolvedType != null && this.resolvedType.isCompatibleWith(left, scope); |
| } |
| |
| public boolean isBoxingCompatibleWith(TypeBinding left, Scope scope) { |
| return this.resolvedType != null && isBoxingCompatible(this.resolvedType, left, this, scope); |
| } |
| |
| public boolean sIsMoreSpecific(TypeBinding s, TypeBinding t, Scope scope) { |
| return s.isCompatibleWith(t, scope); |
| } |
| |
| public boolean isExactMethodReference() { |
| return false; |
| } |
| |
| /* Answer if the receiver is a poly expression in the prevailing context. Caveat emptor: Some constructs (notably method calls) |
| cannot answer this question until after resolution is over and may throw unsupported operation exception if queried ahead of |
| resolution. Default implementation here returns false which is true for vast majority of AST nodes. The ones that are poly |
| expressions under one or more contexts should override and return suitable value. |
| */ |
| public boolean isPolyExpression() throws UnsupportedOperationException { |
| return false; |
| } |
| /** Variant of isPolyExpression() to be used during type inference, when a resolution candidate exists. */ |
| public boolean isPolyExpression(MethodBinding method) { |
| return false; |
| } |
| |
| |
| public void tagAsNeedCheckCast() { |
| // do nothing by default |
| } |
| |
| /** |
| * Record the fact a cast expression got detected as being unnecessary. |
| * |
| * @param scope |
| * @param castType |
| */ |
| public void tagAsUnnecessaryCast(Scope scope, TypeBinding castType) { |
| // 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; |
| } |
| |
| /** |
| * Traverse an expression in the context of a blockScope |
| * @param visitor |
| * @param scope |
| */ |
| @Override |
| public void traverse(ASTVisitor visitor, BlockScope scope) { |
| // nothing to do |
| } |
| |
| /** |
| * Traverse an expression in the context of a classScope |
| * @param visitor |
| * @param scope |
| */ |
| public void traverse(ASTVisitor visitor, ClassScope scope) { |
| // nothing to do |
| } |
| // return true if this expression can be a stand alone statement when terminated with a semicolon |
| public boolean statementExpression() { |
| return false; |
| } |
| // for switch statement |
| public boolean isTrulyExpression() { |
| return true; |
| } |
| |
| /** |
| * Used on the lhs of an assignment for detecting null spec violation. |
| * If this expression represents a null-annotated variable return the variable binding, |
| * otherwise null. |
| * @param supportTypeAnnotations if true this causes any variable binding to be used |
| * independent of declaration annotations (for in-depth analysis of type annotations) |
| */ |
| public VariableBinding nullAnnotatedVariableBinding(boolean supportTypeAnnotations) { |
| return null; |
| } |
| |
| public boolean isFunctionalType() { |
| return false; |
| } |
| |
| /** Returns contained poly expressions, result could be 0, 1 or more (for conditional expression) */ |
| public Expression [] getPolyExpressions() { |
| return isPolyExpression() ? new Expression [] { this } : NO_EXPRESSIONS; |
| } |
| |
| public boolean isPotentiallyCompatibleWith(TypeBinding targetType, Scope scope) { |
| return isCompatibleWith(targetType, scope); // for all but functional expressions, potential compatibility is the same as compatibility. |
| } |
| |
| protected Constant optimizedNullComparisonConstant() { |
| return Constant.NotAConstant; |
| } |
| } |