/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.flex.compiler.internal.semantics; import static org.apache.flex.abc.ABCConstants.OP_findproperty; import static org.apache.flex.abc.ABCConstants.OP_findpropstrict; import static org.apache.flex.abc.ABCConstants.OP_getlex; import static org.apache.flex.abc.ABCConstants.OP_getouterscope; import static org.apache.flex.abc.ABCConstants.OP_getscopeobject; import static org.apache.flex.abc.ABCConstants.OP_newactivation; import static org.apache.flex.abc.ABCConstants.OP_newcatch; import static org.apache.flex.abc.ABCConstants.OP_newclass; import static org.apache.flex.abc.ABCConstants.OP_newfunction; import static org.apache.flex.abc.ABCConstants.OP_popscope; import static org.apache.flex.abc.ABCConstants.OP_pushscope; import static org.apache.flex.abc.ABCConstants.OP_pushwith; import static org.apache.flex.abc.ABCConstants.OP_returnvalue; import static org.apache.flex.abc.ABCConstants.OP_returnvoid; import java.util.ArrayList; import java.util.List; import java.util.Vector; import java.util.concurrent.atomic.AtomicInteger; import org.apache.flex.abc.ABCConstants; import org.apache.flex.abc.graph.IBasicBlock; import org.apache.flex.abc.instructionlist.InstructionList; import org.apache.flex.abc.semantics.ECMASupport; import org.apache.flex.abc.semantics.Instruction; import org.apache.flex.abc.semantics.MethodInfo; import org.apache.flex.abc.semantics.MethodBodyInfo; import org.apache.flex.abc.semantics.Name; import org.apache.flex.abc.semantics.PooledValue; import org.apache.flex.compiler.common.ISourceLocation; import org.apache.flex.compiler.common.ModifiersSet; import org.apache.flex.compiler.common.ASModifier; import org.apache.flex.compiler.constants.IASKeywordConstants; import org.apache.flex.compiler.constants.IASLanguageConstants; import org.apache.flex.compiler.constants.IASLanguageConstants.BuiltinType; import org.apache.flex.compiler.definitions.IAccessorDefinition; import org.apache.flex.compiler.definitions.IClassDefinition; import org.apache.flex.compiler.definitions.IConstantDefinition; import org.apache.flex.compiler.definitions.IDefinition; import org.apache.flex.compiler.definitions.IFunctionDefinition; import org.apache.flex.compiler.definitions.IGetterDefinition; import org.apache.flex.compiler.definitions.IInterfaceDefinition; import org.apache.flex.compiler.definitions.INamespaceDefinition; import org.apache.flex.compiler.definitions.ISetterDefinition; import org.apache.flex.compiler.definitions.ITypeDefinition; import org.apache.flex.compiler.definitions.IVariableDefinition; import org.apache.flex.compiler.definitions.references.INamespaceReference; import org.apache.flex.compiler.internal.definitions.AmbiguousDefinition; import org.apache.flex.compiler.internal.definitions.VariableDefinition; import org.apache.flex.compiler.problems.*; import org.apache.flex.compiler.projects.ICompilerProject; import org.apache.flex.compiler.tree.as.IASNode; import org.apache.flex.compiler.tree.as.IBinaryOperatorNode; import org.apache.flex.compiler.tree.as.ICompoundAssignmentNode; import org.apache.flex.compiler.tree.as.IContainerNode; import org.apache.flex.compiler.tree.as.IExpressionNode; import org.apache.flex.compiler.tree.as.IFunctionCallNode; import org.apache.flex.compiler.tree.as.IFunctionNode; import org.apache.flex.compiler.tree.as.IIdentifierNode; import org.apache.flex.compiler.tree.as.IImportNode; import org.apache.flex.compiler.tree.as.IMemberAccessExpressionNode; import org.apache.flex.compiler.tree.as.INamespaceDecorationNode; import org.apache.flex.compiler.tree.as.INumericLiteralNode; import org.apache.flex.compiler.tree.as.IParameterNode; import org.apache.flex.compiler.tree.as.IReturnNode; import org.apache.flex.compiler.tree.as.IUnaryOperatorNode; import org.apache.flex.compiler.tree.as.IVariableNode; import org.apache.flex.compiler.internal.as.codegen.ABCGeneratingReducer; import org.apache.flex.compiler.internal.as.codegen.Binding; import org.apache.flex.compiler.internal.as.codegen.InlineFunctionLexicalScope; import org.apache.flex.compiler.internal.as.codegen.LexicalScope; import org.apache.flex.compiler.internal.definitions.AccessorDefinition; import org.apache.flex.compiler.internal.definitions.ClassDefinition; import org.apache.flex.compiler.internal.definitions.ClassTraitsDefinition; import org.apache.flex.compiler.internal.definitions.ConstantDefinition; import org.apache.flex.compiler.internal.definitions.DefinitionBase; import org.apache.flex.compiler.internal.definitions.FunctionDefinition; import org.apache.flex.compiler.internal.definitions.GetterDefinition; import org.apache.flex.compiler.internal.definitions.InterfaceDefinition; import org.apache.flex.compiler.internal.definitions.NamespaceDefinition; import org.apache.flex.compiler.internal.definitions.ParameterDefinition; import org.apache.flex.compiler.internal.definitions.SetterDefinition; import org.apache.flex.compiler.internal.definitions.TypeDefinitionBase; import org.apache.flex.compiler.internal.scopes.ASScope; import org.apache.flex.compiler.internal.semantics.SemanticUtils.MultiDefinitionType; import org.apache.flex.compiler.internal.tree.as.BaseDefinitionNode; import org.apache.flex.compiler.internal.tree.as.BinaryOperatorLogicalAndNode; import org.apache.flex.compiler.internal.tree.as.BinaryOperatorLogicalOrNode; import org.apache.flex.compiler.internal.tree.as.ClassNode; import org.apache.flex.compiler.internal.tree.as.ExpressionNodeBase; import org.apache.flex.compiler.internal.tree.as.FunctionCallNode; import org.apache.flex.compiler.internal.tree.as.FunctionNode; import org.apache.flex.compiler.internal.tree.as.IdentifierNode; import org.apache.flex.compiler.internal.tree.as.LanguageIdentifierNode; import org.apache.flex.compiler.internal.tree.as.LiteralNode; import org.apache.flex.compiler.internal.tree.as.MemberAccessExpressionNode; import org.apache.flex.compiler.internal.tree.as.ModifierNode; import org.apache.flex.compiler.internal.tree.as.ModifiersContainerNode; import org.apache.flex.compiler.internal.tree.as.NamespaceAccessExpressionNode; import org.apache.flex.compiler.internal.tree.as.NamespaceNode; import org.apache.flex.compiler.internal.tree.as.NodeBase; import org.apache.flex.compiler.internal.tree.as.NumericLiteralNode; import org.apache.flex.compiler.internal.tree.as.PackageNode; import org.apache.flex.compiler.internal.tree.as.ParameterNode; import org.apache.flex.compiler.internal.tree.as.ScopedBlockNode; import org.apache.flex.compiler.internal.tree.as.TernaryOperatorNode; import org.apache.flex.compiler.internal.tree.as.VariableNode; import org.apache.flex.compiler.internal.tree.as.VectorLiteralNode; import org.apache.flex.compiler.internal.tree.mxml.MXMLDocumentNode; /** * The MethodBodySemanticChecker contains the logic that checks method body semantics. */ public class MethodBodySemanticChecker { /** * The current LexicalScope for this compilation. */ private LexicalScope currentScope; /** * The current ICompilerProject for this compilation. */ private final ICompilerProject project; /** * Semantic utilities, which know how to interface with name resolution */ private final SemanticUtils utils; /** * Current state of diagnostics regarding "super" */ private enum SuperState { Invalid, Initial, Armed }; /** * Current state of diagnostics regarding "super" */ private SuperState superState = SuperState.Invalid; /** * Construct a new MethodBodySemanticChecker from the current lexical scope. */ public MethodBodySemanticChecker(LexicalScope current_scope) { this.currentScope = current_scope; this.project = currentScope.getProject(); this.utils = new SemanticUtils(this.project); } /** * Do a semantic analysis of all the arguments of a function * * @param funcNode is the function to be analyzed */ public void checkFunctionDecl(IFunctionNode funcNode ) { IParameterNode[] paramNodes = funcNode.getParameterNodes(); for (IParameterNode paramNode : paramNodes) { IDefinition paramDef = paramNode.getDefinition(); ITypeDefinition paramTypeDef = ((IVariableDefinition)paramDef).resolveType(project); if (!SemanticUtils.isType(paramTypeDef) ) { IExpressionNode typeExpression = paramNode.getVariableTypeNode(); String typeName = paramDef.getTypeAsDisplayString(); addTypeProblem(typeExpression, paramTypeDef, typeName, true); } } } /** * Semantic analysis of a function declared inside another function. This is only for function declarations, * and not function expressions. * @param funcNode The node of the nested function */ public void checkNestedFunctionDecl(IFunctionNode funcNode) { IFunctionDefinition funcDef = funcNode.getDefinition(); List<IDefinition> defs = SemanticUtils.findPotentialFunctionConflicts(currentScope.getProject(), funcDef); // Check for potential dups - functions can be redeclared and won't be ambiguous, but in strict mode // we want to issue an error. // Don't need to worry about getter/setter pairs as you can't declare nested getter/setters if( defs.size() > 1 ) { ICompilerProblem problem = new DuplicateFunctionDefinitionProblem(funcNode, funcDef.getBaseName()); this.currentScope.addProblem(problem); } } /** * Convenience method to add a problem. * @param problem - the problem to add. */ private void addProblem(ICompilerProblem problem) { // Some of the "figure out this error scenario" // methods pass in null to mean "ignore." if ( problem != null ) this.currentScope.addProblem(problem); } /** * Perform semantic checks on an assignment. */ public void checkAssignment(IASNode iNode, Binding binding) { checkLValue(iNode, binding); if ( SemanticUtils.isUnprotectedAssignmentInConditional(iNode) ) addProblem(new AssignmentInConditionalProblem(SemanticUtils.getNthChild(iNode, 0))); // Check the assignment's type logic and values. ITypeDefinition leftType = null; if ( binding.getDefinition() != null ) { IDefinition leftDef = binding.getDefinition(); leftType = binding.getDefinition().resolveType(project); IASNode rightNode = SemanticUtils.getNthChild(iNode, 1); checkImplicitConversion(rightNode, leftType, null); checkAssignmentValue(leftDef, rightNode); } } /** * Checks that the value (RHS) is appropriate, given the type of the LHS * @param leftDefinition is the definition of the variable on the LHS * @param rightNode is the tree node for the RHS of the assignment */ public void checkAssignmentValue( IDefinition leftDefinition, IASNode rightNode) { ITypeDefinition leftType = leftDefinition.resolveType(project); if (rightNode instanceof IExpressionNode) { IDefinition rightType = ((IExpressionNode)rightNode).resolveType(project); final boolean leftIsNumericOrBoolean = SemanticUtils.isNumericTypeOrBoolean(leftType, project); final boolean rightIsNull = SemanticUtils.isBuiltin(rightType, BuiltinType.NULL, project); if (leftIsNumericOrBoolean && rightIsNull) { final boolean leftIsConstant = leftDefinition instanceof IConstantDefinition; addProblem(leftIsConstant ? new IncompatibleDefaultValueOfTypeNullProblem(rightNode, leftType.getBaseName()) : new NullUsedWhereOtherExpectedProblem(rightNode, leftType.getBaseName())); } } } /** * Perform semantic checks on an initialization */ public void checkInitialization(IASNode iNode, Binding binding) { // Check the assignment's type logic. if ( binding.getDefinition() != null ) checkImplicitConversion(SemanticUtils.getNthChild(iNode, 2), binding.getDefinition().resolveType(project), null); } /** * Perform semantic checks on an x[i] = rvalue assignment expression. */ public void checkAssignToBracketExpr(IASNode iNode) { } /** * Check a binary operator. * @param iNode - the operator node. * @param opcode - the opcode. */ public void checkBinaryOperator(IASNode iNode, int opcode) { final IASNode left = ((IBinaryOperatorNode)iNode).getLeftOperandNode(); final IASNode right = ((IBinaryOperatorNode)iNode).getRightOperandNode(); checkBinaryOperator(iNode, left, right, opcode); } /** * Check a (possibly implicit) binary operator. * @param root - the operator node. * @param left - the left-hand operand. * @param right - the right-hand operand. * @param opcode - the opcode. */ public void checkBinaryOperator(IASNode root, IASNode left, IASNode right, final int opcode) { switch(opcode) { case ABCConstants.OP_multiply: case ABCConstants.OP_divide: case ABCConstants.OP_modulo: case ABCConstants.OP_subtract: case ABCConstants.OP_lshift: case ABCConstants.OP_rshift: case ABCConstants.OP_urshift: case ABCConstants.OP_bitand: case ABCConstants.OP_bitor: case ABCConstants.OP_bitxor: checkImplicitConversion(left, utils.numberType(), null); checkImplicitConversion(right, utils.numberType(), null); break; case ABCConstants.OP_istypelate: case ABCConstants.OP_astypelate: checkTypeCheckImplicitConversion(right); break; case ABCConstants.OP_equals: case ABCConstants.OP_strictequals: case ABCConstants.OP_lessthan: case ABCConstants.OP_lessequals: case ABCConstants.OP_greaterthan: case ABCConstants.OP_greaterequals: if ((left instanceof IExpressionNode) && (right instanceof IExpressionNode)) { checkComparison((IExpressionNode)left, (IExpressionNode)right); } break; case ABCConstants.OP_instanceof: addProblem(new InstanceOfProblem(root)); break; } } /** * Check an implicit conversion. * @param iNode - the expression being checked. * @param expected_type - the type to convert to. */ public void checkImplicitConversion(IASNode iNode, IDefinition expected_type, FunctionDefinition func) { if (iNode instanceof BinaryOperatorLogicalOrNode || iNode instanceof BinaryOperatorLogicalAndNode || iNode instanceof TernaryOperatorNode) { // For these logical nodes, just check both sides with a recursive call. // Note that we need to recurse, because this may be a tree of binary logical nodes final IExpressionNode leftOp = ((IBinaryOperatorNode)iNode).getLeftOperandNode(); checkImplicitConversion(leftOp, expected_type, null); final IExpressionNode rightOp = ((IBinaryOperatorNode)iNode).getRightOperandNode(); checkImplicitConversion(rightOp, expected_type, null); } else if (iNode instanceof ExpressionNodeBase) { checkImplicitConversion(iNode, ((ExpressionNodeBase)iNode).resolveType(project), expected_type, func); } } /** * Check an implicit conversion in an 'is' or 'as' binop * @param iNode - the expression being checked. */ public void checkTypeCheckImplicitConversion(IASNode iNode) { if (!(iNode instanceof ExpressionNodeBase)) return; final IDefinition actual_type = ((ExpressionNodeBase)iNode).resolveType(project); // expected type is always of type CLASS; final IDefinition expected_type = utils.getBuiltinType(BuiltinType.CLASS); if (!SemanticUtils.isValidTypeConversion(expected_type, actual_type, project, currentScope.getInInvisibleCompilationUnit())) { addProblem(new ImplicitTypeCheckCoercionToUnrelatedTypeProblem(iNode, actual_type.getBaseName(), expected_type.getBaseName())); } else { SpecialValue value = getSpecialValue((IExpressionNode)iNode); if (value == SpecialValue.UNDEFINED) // test for undefined { addProblem(new ImplicitTypeCheckCoercionToUnrelatedTypeProblem(iNode, IASLanguageConstants.UNDEFINED, expected_type.getBaseName())); } else if (iNode instanceof LiteralNode && IASKeywordConstants.NULL.equals(((LiteralNode)iNode).getValue())) // test for null { addProblem(new ImplicitTypeCheckCoercionToUnrelatedTypeProblem(iNode, IASKeywordConstants.NULL, expected_type.getBaseName())); } } } enum SpecialValue { NONE, NAN, UNDEFINED } SpecialValue getSpecialValue(IExpressionNode node) { SpecialValue ret = SpecialValue.NONE; IDefinition def = node.resolve(project); if (def instanceof IConstantDefinition) { // it's easy to find the word "undefined", just compare to the // singleton definition if (def == project.getUndefinedValue()) { ret = SpecialValue.UNDEFINED; } else { Object initialValue = ((ConstantDefinition) def).resolveValueFrom(project, (NodeBase)node); if (initialValue != null) { if (initialValue instanceof Double) { Double d = (Double)initialValue; if (ECMASupport.isNan(d)) ret = SpecialValue.NAN; } } } } return ret; } /** * determine if it is reasonable to compare two operands of perhaps different types. * Create a CompilerProblem if not. * */ private void checkComparison(IExpressionNode leftNode, IExpressionNode rightNode) { SpecialValue leftValue = getSpecialValue(leftNode); SpecialValue rightValue = getSpecialValue(rightNode); if (leftValue == SpecialValue.NAN || rightValue == SpecialValue.NAN) { addProblem( new IllogicalComparionWithNaNProblem(leftNode)); } IDefinition left_type = leftNode.resolveType(project); IDefinition right_type = rightNode.resolveType(project); if (left_type==null || right_type==null) { return; // if we can't resolve both side, some other check will catch it. } final IDefinition anyType = project.getBuiltinType(BuiltinType.ANY_TYPE); if (rightValue == SpecialValue.UNDEFINED && !left_type.equals(anyType)) { addProblem(new IllogicalComparisonWithUndefinedProblem(leftNode)); } if (leftValue == SpecialValue.UNDEFINED && !right_type.equals(anyType)) { addProblem(new IllogicalComparisonWithUndefinedProblem(rightNode)); } // Harmless pre-optimizition. If both types are the same they must be comparable. // (but only after we have checked special value cases above. if (left_type.equals(right_type)) { return; } final boolean leftIsNumeric = SemanticUtils.isNumericType(left_type, project); final boolean rightIsNumeric = SemanticUtils.isNumericType(right_type, project); final boolean leftIsNull = SemanticUtils.isBuiltin(left_type, BuiltinType.NULL, project); final boolean rightIsNull = SemanticUtils.isBuiltin(right_type, BuiltinType.NULL, project); // Interfaces can be compared against any Object if ((left_type instanceof IInterfaceDefinition && !rightIsNumeric) || ( right_type instanceof IInterfaceDefinition && !leftIsNumeric)) { return; } boolean isBad = false; // Numeric types can never be null if ((leftIsNumeric&&rightIsNull) || (rightIsNumeric&&leftIsNull)) { isBad = true; } // If all the speical cases have passed, the try isValidTypeConversion in both directions. if (!isBad) { if (SemanticUtils.isValidTypeConversion(left_type, right_type, project, this.currentScope.getInInvisibleCompilationUnit())) return; if (SemanticUtils.isValidTypeConversion(right_type, left_type, project, this.currentScope.getInInvisibleCompilationUnit())) return; } addProblem(new ComparisonBetweenUnrelatedTypesProblem(leftNode, left_type.getBaseName(), right_type.getBaseName())); } /** * Check a compound assignment. * @param iNode - the node at the root of the assignment subtree. * @param lvalue - the resolved lvalue (which is also an implicit rvalue). * @param opcode - the opcode of the implied binary operator. */ public void checkCompoundAssignment(IASNode iNode, Binding lvalue, final int opcode) { ICompoundAssignmentNode compoundNode = (ICompoundAssignmentNode)iNode; IBinaryOperatorNode binop = (IBinaryOperatorNode)iNode; checkLValue(iNode, lvalue); if ( SemanticUtils.isUnprotectedAssignmentInConditional(iNode) ) addProblem(new AssignmentInConditionalProblem(binop.getLeftOperandNode())); // Check the implicit binary operator. checkBinaryOperator(iNode, binop.getLeftOperandNode(), binop.getRightOperandNode(), opcode); // Check the assignment's types are compatible. if ( lvalue.getDefinition() != null ) { // Do own checks, then call common logic to emit diagnostics. ITypeDefinition lhsType = lvalue.getDefinition().resolveType(this.project); ITypeDefinition compoundType = compoundNode.resolveTypeOfRValue(this.project); if ( ! SemanticUtils.isValidImplicitOpAssignment(lhsType, compoundType, opcode, this.project, this.currentScope.getInInvisibleCompilationUnit()) ) { checkImplicitConversion(binop.getRightOperandNode(), lhsType, null); } else if ( opcode == ABCConstants.OP_iffalse || opcode == ABCConstants.OP_iftrue ) { // check the RHS type of a logical operation on its own; // this is rather strange behavior, but it replicates ASC's logic. checkImplicitConversion(binop.getRightOperandNode(), lhsType, null); } } } /** * Check an implicit conversion. * @param actual_type - the type of the expression being checked. * @param expected_type - the type to convert to. */ private void checkImplicitConversion(IASNode iNode, IDefinition actual_type, IDefinition expected_type, FunctionDefinition func) { if ( !SemanticUtils.isValidTypeConversion(expected_type, actual_type, this.project, this.currentScope.getInInvisibleCompilationUnit()) ) { if (project.isValidTypeConversion(iNode, actual_type, expected_type, func)) return; // If we're assigning to a class, this will generate an "Illegal assignment to class" error, // so we don't need another error for implicit coercion if( !(expected_type instanceof ClassTraitsDefinition) ) { if ( utils.isInstanceOf(expected_type, actual_type) ) { addProblem(new ImplicitCoercionToSubtypeProblem(iNode, actual_type.getBaseName(), expected_type.getBaseName())); } else { addProblem(new ImplicitCoercionToUnrelatedTypeProblem(iNode, actual_type.getBaseName(), expected_type.getBaseName())); } } } } /** * Check if we are allowed to declare a bindable variable at this location. */ public void checkBindableVariableDeclaration(IASNode iNode, IDefinition d) { assert d != null; if( !(d.getParent() instanceof IClassDefinition)) { this.currentScope.addProblem(new LocalBindablePropertyProblem(iNode)); } } /** * Check a constant value used in a non-initializer context. */ public void checkConstantValue(IASNode iNode) { // Check for a node resolving to a deprecated constant definition. if (iNode instanceof IExpressionNode) { IDefinition definition = ((IExpressionNode)iNode).resolve(project); checkDeprecated(iNode, definition); } } /** * Check that a synthetic super() call is allowed by the class' superclass. */ public void checkDefaultSuperCall(IASNode iNode) { // This occurs in some error cases. if ( iNode == null ) return; ClassNode enclosing_class = (ClassNode) iNode.getAncestorOfType(ClassNode.class); if ( enclosing_class != null ) { IClassDefinition super_def = enclosing_class.getDefinition().resolveBaseClass(project); if (super_def != null) { IFunctionDefinition ctor = super_def.getConstructor(); if (ctor instanceof FunctionDefinition) { FunctionDefinition func = (FunctionDefinition)ctor; if (func.getParameters() != null && func.getParameters().length != 0) { ParameterDefinition first_param = func.getParameters()[0]; if ( !first_param.hasDefaultValue() && ! first_param.isRest() ) { if ( enclosing_class.getDefinition().getConstructor().isImplicit()) { //in this case the Error reporting site should point to the class node, //because there is no 'real' constructor node to reference in the source code addProblem(new NoDefaultConstructorInBaseClassProblem(enclosing_class, super_def.getBaseName())); } else addProblem(new NoDefaultConstructorInBaseClassProblem(iNode, super_def.getBaseName())); } } } } } } /** * Perform semantic checks on a delete expression. */ public void checkDeleteExpr(IASNode iNode, Binding binding) { IDefinition def = binding.getDefinition(); if (def != null) { // If we can resolve to a definition, check to be sure we are not trying // to delete a non-dynamic properly if (!(utils.hasDynamicBase(binding) || SemanticUtils.isInWith(iNode))) addProblem(new AttemptToDeleteFixedPropertyProblem(iNode, binding.getName())); } else if (SemanticUtils.hasBaseNode(binding) && !utils.hasDynamicBase(binding)) { // If we are trying to delete a member of a class, but the member doesn't exists, // The log the problem for that addProblem(new AccessUndefinedMemberProblem( roundUpUsualSuspects(binding, iNode), binding.getName().getBaseName(), utils.getTypeOfBase(binding.getNode()))); } else { // This checked knows about packages and undefined properties, // could use the more specific // addProblem(accessUndefinedProperty(binding, roundUpUsualSuspects(binding, iNode))); checkLValue(iNode, binding); } } /** * Check a super() or super(a,b,c) call. */ public void checkExplicitSuperCall(IASNode iNode, Vector<? extends Object> args) { LanguageIdentifierNode super_node = (LanguageIdentifierNode)((IFunctionCallNode)iNode).getNameNode(); // Check that this super() call is in a constructor. if ( !SemanticUtils.isInConstructor(iNode) ) { addProblem(new InvalidSuperStatementProblem(iNode)); } else { // Check that this super call does not follow a construct // that invalidates it. if ( this.superState != SuperState.Initial ) addProblem(new ExtraneousSuperStatementProblem(iNode)); else this.superState = SuperState.Armed; } // Check parameters if possible. ClassDefinition super_def = (ClassDefinition) super_node.resolveType(project); if (super_def != null) { IFunctionDefinition ctor = super_def.getConstructor(); if (ctor instanceof FunctionDefinition) { checkFormalsVsActuals(super_node, (FunctionDefinition)ctor, args); } } } /** * Check that formal and actual parameters correspond and are compatible. */ private void checkFormalsVsActuals(IASNode iNode, FunctionDefinition func, Vector<? extends Object> actuals) { // If the call is through a function variable then we don't know much about it. // If we get a setter function assume a getter is what was meant since calling a setter // directly is not legal and resolving the name for a getter/setter could // return either. Code generation does the right thing so changing this check // to just return for setters as well. if ( func instanceof GetterDefinition || func instanceof SetterDefinition) return; // Check the formal parameter definitions, and ensure we have // a corresponding number of actual parameters. ParameterDefinition[] formals = func.getParameters(); if ( formals == null ) return; boolean last_is_rest = formals.length > 0 && formals[formals.length - 1].isRest(); int required_count = 0; if ( actuals.size() > formals.length && !last_is_rest ) { addProblem(new TooManyFunctionParametersProblem(iNode, formals.length)); } // Compute the number of required parameters. for ( int i = 0; i < formals.length; i++ ) { if ( formals[i].hasDefaultValue() || formals[i].isRest() ) break; required_count++; } if ( actuals.size() < required_count ) { addProblem(new TooFewFunctionParametersProblem(iNode, required_count)); } // Check that the actuals are compatible with the formals. IASNode actuals_container = null; if( iNode instanceof FunctionCallNode ) actuals_container = ((FunctionCallNode)iNode).getArgumentsNode(); if ( actuals_container != null ) { for ( int i = 0; i < actuals_container.getChildCount() && i < formals.length; i++ ) { if ( !formals[i].isRest() ) checkImplicitConversion( actuals_container.getChild(i), formals[i].resolveType(project), func ); } } } /** * Check a function body's overall characteristics. */ public void checkFunctionBody(IASNode iNode) { if ( iNode instanceof FunctionNode ) { FunctionNode func = (FunctionNode)iNode; IDefinition def = func.getDefinition(); if ( !( def.hasModifier(ASModifier.NATIVE ) || def.hasModifier(ASModifier.DYNAMIC) || func.isConstructor() ) ) { if ( !func.hasBody() ) { addProblem(new FunctionWithoutBodyProblem(SemanticUtils.getFunctionProblemNode(func))); } } } } public void checkNativeMethod(IASNode iNode) { if( iNode instanceof FunctionNode ) { if ( ((FunctionNode)iNode).hasBody() ) { addProblem(new NativeMethodWithBodyProblem(iNode)); } } } /** * Check a function call. */ public void checkFunctionCall(IASNode iNode, Binding method_binding, Vector<? extends Object>actuals) { // Skip synthetic calls. if ( method_binding.getName() == null ) return; // Check for calls to attributes. if ( method_binding.getName().isAttributeName() ) { addProblem(new AttributesAreNotCallableProblem(roundUpUsualSuspects(method_binding, iNode))); } IDefinition def = method_binding.getDefinition(); if ( def == null && utils.definitionCanBeAnalyzed(method_binding) ) { if ( utils.isInaccessible(iNode, method_binding) ) { addProblem(new InaccessibleMethodReferenceProblem( roundUpUsualSuspects(method_binding, iNode), method_binding.getName().getBaseName(), utils.getTypeOfStem(iNode) )); } else if ( SemanticUtils.hasExplicitStem(iNode) && utils.hasUnderlyingType(iNode) ) { addProblem(new StrictUndefinedMethodProblem( roundUpUsualSuspects(method_binding, iNode), method_binding.getName().getBaseName(), utils.getTypeOfStem(iNode) )); } else { addProblem(new CallUndefinedMethodProblem( roundUpUsualSuspects(method_binding, iNode), method_binding.getName().getBaseName() )); } } else if ( def instanceof FunctionDefinition ) { FunctionDefinition func = (FunctionDefinition)def; checkFormalsVsActuals(iNode, func, actuals); } else if ( def instanceof VariableDefinition ) { VariableDefinition varDef = (VariableDefinition)def; IDefinition varType = varDef.resolveType(project); if (varType != null && // Null here means the ANY_TYPE (varType.equals(project.getBuiltinType(BuiltinType.NUMBER)) || varType.equals(project.getBuiltinType(BuiltinType.BOOLEAN)) || varType.equals(project.getBuiltinType(BuiltinType.INT)) || varType.equals(project.getBuiltinType(BuiltinType.UINT)) || varType.equals(project.getBuiltinType(BuiltinType.STRING)))) { addProblem(new CallNonFunctionProblem(iNode, method_binding.getName().getBaseName())); } } else if ( def == project.getBuiltinType(BuiltinType.ARRAY) ) { // Warn about calling Array as a function because developers // may not understand that this always creates a new array. // The warning is different when there is one argument // of type Array, Object, or *; in that case developers // may think they are downcasting. boolean downcast = false; if (actuals.size() == 1) { IExpressionNode argument = ((IFunctionCallNode)iNode).getArgumentNodes()[0]; IDefinition argumentType = argument.resolveType(project); if (argumentType == null || // Null here means the ANY_TYPE argumentType.equals(project.getBuiltinType(BuiltinType.ARRAY)) || argumentType.equals(project.getBuiltinType(BuiltinType.OBJECT)) || argumentType.equals(project.getBuiltinType(BuiltinType.ANY_TYPE))) { downcast = true; } } if (downcast) addProblem(new ArrayDowncastProblem(iNode)); else addProblem(new ArrayCastProblem(iNode)); } else if (def != null && // If def is an AmbiguousDefinition, // getQualifiedName() will throw an exception. !AmbiguousDefinition.isAmbiguous(def) && def.getQualifiedName().equals(IASLanguageConstants.Date)) { if (actuals.size() > 0) addProblem(new DateCastProblem(iNode)); } else if ( def instanceof ITypeDefinition ) { // We've already handled the special cases of Array(...) and Date(...) // For other cast-like calls, there should be one and only one parameter. switch ( actuals.size() ) { case 0: { addProblem(new TooFewFunctionParametersProblem(iNode, 1)); break; } case 1: { // Correct number of parameters. break; } default: { addProblem(new TooManyFunctionParametersProblem(iNode, 1)); break; } } } checkReference(method_binding); } /** * Check a function definition. * @param iNode - the top-level definition node. * @param def - the function's definition. */ public void checkFunctionDefinition(IFunctionNode iNode, FunctionDefinition def ) { SemanticUtils.checkReturnValueHasNoTypeDeclaration(this.currentScope, iNode, def); if (SemanticUtils.isInFunction(iNode)) { if (iNode instanceof BaseDefinitionNode) checkForNamespaceInFunction((BaseDefinitionNode)iNode, currentScope); } ParameterDefinition[] formals = def.getParameters(); if ( formals == null ) return; boolean found_optional = false; for ( int i = 0; i < formals.length; i++ ) { // Check the structure of the formals; // required parameters, then optionals, // then the rest parameter, if present. if ( formals[i].hasDefaultValue() ) { found_optional = true; if( def instanceof ISetterDefinition ) { addProblem(new SetterCannotHaveOptionalProblem(formals[i].getNode())); } } else if ( formals[i].isRest() ) { if ( i != formals.length -1 ) addProblem(new RestParameterMustBeLastProblem(formals[i].getNode())); // TODO: Add a check for kError_InvalidRestDecl once CMP-279 is fixed. } else { if ( found_optional ) { addProblem(new RequiredParameterAfterOptionalProblem(formals[i].getNode())); } } } // Check the return type. TypeDefinitionBase return_type = (TypeDefinitionBase)def.resolveReturnType(project); if ( return_type == null ) { // Error already emitted. } // Setter return type can only be 'void' or '*' else if( def instanceof ISetterDefinition && // Error says return type must be void, but ASC allows '*' as well (return_type != project.getBuiltinType(BuiltinType.VOID) && return_type != project.getBuiltinType(BuiltinType.ANY_TYPE)) ) { addProblem(new BadSetterReturnTypeProblem(iNode.getReturnTypeNode())); } if( def instanceof IAccessorDefinition ) { IAccessorDefinition accessorDef = (IAccessorDefinition)def; ITypeDefinition thisType = accessorDef.resolveType(project); IAccessorDefinition other = null; if( (other = accessorDef.resolveCorrespondingAccessor(project)) != null) { ITypeDefinition otherType = other.resolveType(project); IDefinition anyType = project.getBuiltinType(BuiltinType.ANY_TYPE); if( otherType != thisType // Don't complain if one of the types is '*' && otherType != anyType && thisType != anyType ) { addProblem(new AccessorTypesMustMatchProblem(getAccessorTypeNode(iNode))); } } if( def instanceof IGetterDefinition ) { if (formals.length > 0 ) { addProblem(new GetterCannotHaveParametersProblem(formals[0].getNode())); } if (SemanticUtils.isBuiltin(thisType, BuiltinType.VOID, project)) { addProblem( new GetterMustNotBeVoidProblem(iNode.getReturnTypeNode())); } } if( def instanceof ISetterDefinition ) { if( formals.length != 1 ) { addProblem(new SetterMustHaveOneParameterProblem(iNode.getNameExpressionNode())); } } } checkNamespaceOfDefinition(iNode, def, project); } /** * Helper to get the node to report a problem with the "type" of a getter/setter. Will return the * return type node for a setter, or the first parameter node for a getter, or the name node of the * accessor if the return type or parameter does not exist */ private IASNode getAccessorTypeNode(IFunctionNode iNode) { IASNode result = iNode.getNameExpressionNode(); if( iNode.isSetter() ) { IExpressionNode returnType = iNode.getReturnTypeNode(); if( returnType != null ) result = returnType; } else if( iNode.isGetter() ) { IParameterNode[] params = iNode.getParameterNodes(); if( params != null && params.length > 0) result = params[0]; } return result; } /** * Check a simple name reference. */ public void checkSimpleName(IASNode iNode, Binding binding) { if ( SemanticUtils.isThisKeyword(iNode) ) { LanguageIdentifierNode.Context context = ((LanguageIdentifierNode)iNode).getContext(); if ( context == LanguageIdentifierNode.Context.STATIC_CONTEXT || context == LanguageIdentifierNode.Context.PACKAGE_CONTEXT) { addProblem(new ThisUsedInStaticFunctionProblem(iNode)); } } else if ( SemanticUtils.isArgumentsReference(binding) ) { FunctionDefinition functionDef = SemanticUtils.getFunctionDefinition(iNode); if (functionDef != null) { ParameterDefinition[] parameters = functionDef.getParameters(); for (ParameterDefinition param : parameters) { if (param.isRest()) { addProblem(new RestParamAndArgumentsUsedTogetherProblem(iNode)); break; } } } } if( binding.getDefinition() == null && isPackageReference(binding) ) { // A simple name only counts as a package reference if it did not resolve to anything addProblem(new PackageCannotBeUsedAsValueProblem(iNode, binding.getName().getBaseName())); } } /** * Translate a {@link PooledValue} into a {@link BuiltinType}. * @param value The {@link PooledValue} to translate. * @return The {@link BuiltinType} for the specified {@link PooledValue}. */ private static BuiltinType getBuiltinTypeOfPooledValue(PooledValue value) { switch ( value.getKind() ) { case ABCConstants.CONSTANT_Int: return BuiltinType.INT; case ABCConstants.CONSTANT_UInt: return BuiltinType.UINT; case ABCConstants.CONSTANT_Double: return BuiltinType.NUMBER; case ABCConstants.CONSTANT_Utf8: return BuiltinType.STRING; case ABCConstants.CONSTANT_True: case ABCConstants.CONSTANT_False: return BuiltinType.BOOLEAN; case ABCConstants.CONSTANT_Undefined: return BuiltinType.VOID; case ABCConstants.CONSTANT_Null: return BuiltinType.NULL; case ABCConstants.CONSTANT_Namespace: case ABCConstants.CONSTANT_PrivateNs: case ABCConstants.CONSTANT_PackageNs: case ABCConstants.CONSTANT_PackageInternalNs: case ABCConstants.CONSTANT_ProtectedNs: case ABCConstants.CONSTANT_ExplicitNamespace: case ABCConstants.CONSTANT_StaticProtectedNs: return BuiltinType.NAMESPACE; default: assert false : "Unknown default value kind " + value.getKind(); } return BuiltinType.ANY_TYPE; } /** * Translate a {@link PooledValue} into an {@link IDefinition} type. * @return the {@link IDefinition} for the value's type. */ private IDefinition getTypeOfPooledValue(PooledValue value) { BuiltinType builtinType = getBuiltinTypeOfPooledValue(value); return utils.getBuiltinType(builtinType); } /** * Helper method called by * {@link #checkInitialValue(IVariableNode, Binding, PooledValue)}. * <p> * Conversion rules: * <table> * <tr> * <td></td> * <td>*</td> * <td>Object</td> * <td>void</td> * <td>Array</td> * <td>XML</td> * <td>Function</td> * <td>Class</td> * <td>String</td> * <td>Boolean</td> * <td>Number</td> * <td>uint</td> * <td>int</td> * </tr> * <tr> * <td>CONSTANT_Int</td> * <td>No error</td> <!-- * --> * <td>No error</td> <!-- Object --> * <td>No error</td> <!-- void --> * <td>Error/transform to null</td> <!-- Array --> * <td>Error/transform to null</td> <!-- XML --> * <td>Error/transform to null</td> <!-- Function --> * <td>Error/transform to null</td> <!-- Class --> * <td>Error/coerce to string</td> <!-- String --> * <td>Error/coerce to Boolean</td> <!-- Boolean --> * <td>No Error</td> <!-- Number --> * <td>If negative error and coerce otherwise no error</td> <!-- uint --> * <td>-</td> <!-- int --> * </tr> * <tr> * <td>CONSTANT_UInt</td> * <td>No error</td> <!-- * --> * <td>No error</td> <!-- Object --> * <td>No error</td> <!-- void --> * <td>Error/transform to null</td> <!-- Array --> * <td>Error/transform to null</td> <!-- XML --> * <td>Error/transform to null</td> <!-- Function --> * <td>Error/transform to null</td> <!-- Class --> * <td>Error/coerce to string</td> <!-- String --> * <td>Error/coerce to Boolean</td> <!-- Boolean --> * <td>No Error</td> <!-- Number --> * <td>-</td> <!-- uint --> * <td>Error/coerce if not an integer in int range</td> <!-- int --> * </tr> * <tr> * <td>CONSTANT_Double</td> * <td>No error</td> <!-- * --> * <td>No error</td> <!-- Object --> * <td>No error</td> <!-- void --> * <td>Error/transform to null</td> <!-- Array --> * <td>Error/transform to null</td> <!-- XML --> * <td>Error/transform to null</td> <!-- Function --> * <td>Error/transform to null</td> <!-- Class --> * <td>Error/coerce to string</td> <!-- String --> * <td>Error/coerce to Boolean</td> <!-- Boolean --> * <td>-</td> <!-- Number --> * <td>Error/coerce if not a positive error in uint range</td> <!-- uint --> * <td>Error/coerce if not an integer in int range</td> <!-- int --> * </tr> * <tr> * <td>CONSTANT_Utf8</td> * <td>No error</td> <!-- * --> * <td>No error</td> <!-- Object --> * <td>No error</td> <!-- void --> * <td>Error/transform to null</td> <!-- Array --> * <td>Error/transform to null</td> <!-- XML --> * <td>Error/transform to null</td> <!-- Function --> * <td>Error/transform to null</td> <!-- Class --> * <td>-</td> <!-- String --> * <td>Error/coerce to Boolean</td> <!-- Boolean --> * <td>Error/coerce to Number</td> <!-- Number --> * <td>Error/coerce to uint</td> <!-- uint --> * <td>Error/coerce to int</td> <!-- int --> * </tr> * <tr> * <td>CONSTANT_True</td> * <td>No error</td> <!-- * --> * <td>No error</td> <!-- Object --> * <td>No error</td> <!-- void --> * <td>Error/transform to null</td> <!-- Array --> * <td>Error/transform to null</td> <!-- XML --> * <td>Error/transform to null</td> <!-- Function --> * <td>Error/transform to null</td> <!-- Class --> * <td>Error/coerce to string</td> <!-- String --> * <td>-</td> <!-- Boolean --> * <td>Error/coerce to Number</td> <!-- Number --> * <td>Error/coerce to uint</td> <!-- uint --> * <td>Error/coerce to int</td> <!-- int --> * </tr> * <tr> * <td>CONSTANT_False</td> * <td>No error</td> <!-- * --> * <td>No error</td> <!-- Object --> * <td>No error</td> <!-- void --> * <td>Error/transform to null</td> <!-- Array --> * <td>Error/transform to null</td> <!-- XML --> * <td>Error/transform to null</td> <!-- Function --> * <td>Error/transform to null</td> <!-- Class --> * <td>Error/coerce to string</td> <!-- String --> * <td>-</td> <!-- Boolean --> * <td>Error/coerce to Number</td> <!-- Number --> * <td>Error/coerce to uint</td> <!-- uint --> * <td>Error/coerce to int</td> <!-- int --> * </tr> * <tr> * <td>CONSTANT_Undefined</td> * <td>No error</td> <!-- * --> * <td>No error</td> <!-- Object --> * <td>No error</td> <!-- void --> * <td>No error</td> <!-- Array --> * <td>No error</td> <!-- XML --> * <td>No error</td> <!-- Function --> * <td>No error</td> <!-- Class --> * <td>No error</td> <!-- String --> * <td>No error</td> <!-- Boolean --> * <td>No error</td> <!-- Number --> * <td>No error</td> <!-- uint --> * <td>No error</td> <!-- int --> * </tr> * <tr> * <td>CONSTANT_Null</td> * <td>No error</td> <!-- * --> * <td>No error</td> <!-- Object --> * <td>No error</td> <!-- void --> * <td>No error</td> <!-- Array --> * <td>No error</td> <!-- XML --> * <td>No error</td> <!-- Function --> * <td>No error</td> <!-- Class --> * <td>No error</td> <!-- String --> * <td>Error/coerce to Boolean</td> <!-- Boolean --> * <td>Error/coerce to Number</td> <!-- Number --> * <td>Error/coerce to uint</td> <!-- uint --> * <td>Error/coerce to int</td> <!-- int --> * </tr> * <tr> * <td>CONSTANT_Namespace, CONSTANT_*Ns</td> * <td>No error</td> <!-- * --> * <td>No error</td> <!-- Object --> * <td>No error</td> <!-- void --> * <td>Error/transform to null</td> <!-- Array --> * <td>Error/transform to null</td> <!-- XML --> * <td>Error/transform to null</td> <!-- Function --> * <td>Error/transform to null</td> <!-- Class --> * <td>Error/transform to null</td> <!-- String --> * <td>Error/transform to true</td> <!-- Boolean --> * <td>Error/transform to 0</td> <!-- Number --> * <td>Error/transform to 0</td> <!-- uint --> * <td>Error/transform to 0</td> <!-- int --> * </tr> * <tr> * </table> * * @param initial_value_location The {@link ISourceLocation} for the * variable's initializer expression. * @param desired_type {@link IDefinition} that the initial value's type * should be compatible with. * @param initial_value {@link PooledValue} containing the constant * initializer for the variable. * @return A {@link PooledValue} whose type is compatible with desired_type. */ private PooledValue checkInitialValue(ISourceLocation initial_value_location, IDefinition desired_type, PooledValue initial_value) { IDefinition value_type = getTypeOfPooledValue(initial_value); if (desired_type == null || desired_type.equals(value_type) || utils.isInstanceOf(value_type, desired_type)) { return initial_value; } else if (utils.isBuiltin(desired_type, BuiltinType.OBJECT) || utils.isBuiltin(desired_type, BuiltinType.ANY_TYPE)) { return initial_value; } else if (utils.isBuiltin(value_type, BuiltinType.VOID)) { return initial_value; } else if ( utils.isBuiltin(desired_type, BuiltinType.ARRAY) || utils.isBuiltin(desired_type, BuiltinType.XML) || utils.isBuiltin(desired_type, BuiltinType.FUNCTION) || utils.isBuiltin(desired_type, BuiltinType.CLASS) ) { if (utils.isBuiltin(value_type, BuiltinType.NULL)) return initial_value; addProblem(new IncompatibleInitializerTypeProblem(initial_value_location, value_type.getBaseName(), desired_type.getBaseName(), "null")); // transform initial_value to null. return new PooledValue(ABCConstants.NULL_VALUE); } else if (utils.isBuiltin(desired_type, BuiltinType.STRING)) { assert !(utils.isBuiltin(value_type, BuiltinType.STRING)); if (utils.isBuiltin(value_type, BuiltinType.NULL)) return initial_value; String initial_value_string = ECMASupport.toString(initial_value.getValue()); String initial_value_quoted = "\"" + initial_value_string + "\""; addProblem(new IncompatibleInitializerTypeProblem(initial_value_location, value_type.getBaseName(), desired_type.getBaseName(), initial_value_quoted)); // transform initial_value to a string constant. return new PooledValue(initial_value_string); } else if (utils.isBuiltin(desired_type, BuiltinType.BOOLEAN)) { assert !(utils.isBuiltin(value_type, BuiltinType.BOOLEAN)); // transform initial_value to a boolean constant. boolean initial_value_boolean = ECMASupport.toBoolean(initial_value.getValue()); // tell the programmer about the transformation we did. addProblem(new IncompatibleInitializerTypeProblem(initial_value_location, value_type.getBaseName(), desired_type.getBaseName(), String.valueOf(initial_value_boolean))); return new PooledValue(initial_value_boolean); } else if (utils.isBuiltin(desired_type, BuiltinType.NUMBER)) { if (utils.isBuiltin(value_type, BuiltinType.INT) || utils.isBuiltin(value_type, BuiltinType.UINT) || utils.isBuiltin(value_type, BuiltinType.NUMBER)) return initial_value; Number initial_value_numeric = ECMASupport.toNumeric(initial_value.getValue()); double initial_value_double = initial_value_numeric != null ? initial_value_numeric.doubleValue() : 0; addProblem(new IncompatibleInitializerTypeProblem(initial_value_location, value_type.getBaseName(), desired_type.getBaseName(), String.valueOf(initial_value_double))); return new PooledValue(initial_value_double); } else if (utils.isBuiltin(desired_type, BuiltinType.UINT)) { return checkInitialUIntValue(initial_value_location, desired_type, initial_value, value_type); } else if (utils.isBuiltin(desired_type, BuiltinType.INT)) { return checkInitialIntValue(initial_value_location, desired_type, initial_value, value_type); } addProblem(new IncompatibleInitializerTypeProblem(initial_value_location, value_type.getBaseName(), desired_type.getBaseName(), "null")); return new PooledValue(ABCConstants.NULL_VALUE); } /** * Helper method called by * {@link #checkInitialValue(ISourceLocation, IDefinition, PooledValue)}. * * @param initial_value_location The {@link ISourceLocation} for the * variable's initializer expression. * @param desired_type {@link IDefinition} that the initial value's type * should be compatible with. This should always by the {@link IDefinition} * for int. This is passed in as a convenience. * @param initial_value {@link PooledValue} containing the constant * initializer for the variable. * @param value_type {@link IDefinition} for the type of value contained by * the specified {@link PooledValue}. * @return A {@link PooledValue} whose type is compatible with desired_type. */ private PooledValue checkInitialIntValue(ISourceLocation initial_value_location, IDefinition desired_type, PooledValue initial_value, IDefinition value_type) { assert !(utils.isBuiltin(value_type, BuiltinType.INT)); if (utils.isBuiltin(value_type, BuiltinType.UINT)) { long initial_value_long = initial_value.getLongValue(); if (initial_value_long > Integer.MAX_VALUE) return addIntOutOfRangeProblem(initial_value_location, desired_type, initial_value_long); else return initial_value; } else if (utils.isBuiltin(value_type, BuiltinType.NUMBER)) { double initial_value_double = initial_value.getDoubleValue(); double initial_value_rounded = ECMASupport.toInteger(initial_value_double); int initial_value_int = ECMASupport.toInt32(initial_value_double); if (initial_value_rounded != initial_value_double) { addProblem(new InitializerValueNotAnIntegerProblem( initial_value_location, desired_type.getBaseName(), String.valueOf(initial_value_double), String.valueOf(initial_value_int))); return new PooledValue(initial_value_int); } else if ((initial_value_rounded < Integer.MIN_VALUE) || (initial_value_rounded > Integer.MAX_VALUE)) { return addIntOutOfRangeProblem(initial_value_location, desired_type, initial_value_rounded); } else { // transform the number that happens to be an integer into // an integer. return new PooledValue(initial_value_int); } } else { Number initial_value_numeric = ECMASupport.toNumeric(initial_value.getValue()); double initial_value_double = initial_value_numeric != null ? initial_value_numeric.doubleValue() : 0; int initial_value_int = ECMASupport.toInt32(initial_value_double); addProblem(new IncompatibleInitializerTypeProblem(initial_value_location, value_type.getBaseName(), desired_type.getBaseName(), String.valueOf(initial_value_int))); return new PooledValue(initial_value_int); } } /** * Helper method called by * {@link #checkInitialValue(ISourceLocation, IDefinition, PooledValue)}. * * @param initial_value_location The {@link ISourceLocation} for the * variable's initializer expression. * @param desired_type {@link IDefinition} that the initial value's type * should be compatible with. This should always by the {@link IDefinition} * for uint. This is passed in as a convenience. * @param initial_value {@link PooledValue} containing the constant * initializer for the variable. * @param value_type {@link IDefinition} for the type of value contained by * the specified {@link PooledValue}. * @return A {@link PooledValue} whose type is compatible with desired_type. */ private PooledValue checkInitialUIntValue(ISourceLocation initial_value_location, IDefinition desired_type, PooledValue initial_value, IDefinition value_type) { assert !(utils.isBuiltin(value_type, BuiltinType.UINT)); if (utils.isBuiltin(value_type, BuiltinType.INT)) { int initial_value_int = initial_value.getIntegerValue(); if (initial_value_int < 0) return addUIntOutOfRangeProblem(initial_value_location, desired_type, initial_value_int); else return initial_value; } else if (utils.isBuiltin(value_type, BuiltinType.NUMBER)) { double initial_value_double = initial_value.getDoubleValue(); double initial_value_int = ECMASupport.toInteger(initial_value_double); long initial_value_long = ECMASupport.toUInt32(initial_value_int); if (initial_value_double != initial_value_int) { addProblem(new InitializerValueNotAnIntegerProblem( initial_value_location, desired_type.getBaseName(), String.valueOf(initial_value_double), String.valueOf(initial_value_long))); return new PooledValue(initial_value_long); } else if ((initial_value_int < 0) || (initial_value_int > 0xFFFFFFFFL)) { return addUIntOutOfRangeProblem(initial_value_location, desired_type, initial_value_int); } else { // transform the number that happens to be an unsigned integer into // an unsigned integer. return new PooledValue(initial_value_long); } } else { Number initial_value_numeric = ECMASupport.toNumeric(initial_value.getValue()); double initial_value_double = initial_value_numeric != null ? initial_value_numeric.doubleValue() : 0; long initial_value_long = ECMASupport.toUInt32(initial_value_double); addProblem(new IncompatibleInitializerTypeProblem(initial_value_location, value_type.getBaseName(), desired_type.getBaseName(), String.valueOf(initial_value_long))); return new PooledValue(initial_value_long); } } private PooledValue addUIntOutOfRangeProblem(ISourceLocation initial_value_location, IDefinition required_type, double initial_value_int) { long initial_value_long = ECMASupport.toUInt32(initial_value_int); addProblem(new InitializerValueOutOfRangeProblem( initial_value_location, required_type.getBaseName(), String.valueOf((long)initial_value_int), "0", String.valueOf(0xFFFFFFFFL), String.valueOf(initial_value_long))); return new PooledValue(initial_value_long); } private PooledValue addIntOutOfRangeProblem(ISourceLocation initial_value_location, IDefinition required_type, double initial_value_double) { int initial_value_int = ECMASupport.toInt32(initial_value_double); addProblem(new InitializerValueOutOfRangeProblem( initial_value_location, required_type.getBaseName(), String.valueOf((long)initial_value_double), String.valueOf(Integer.MIN_VALUE), String.valueOf(Integer.MAX_VALUE), String.valueOf(initial_value_int))); return new PooledValue(initial_value_int); } /** * Check a getproperty operation. * @param binding - the Binding which is being fetched. */ public void checkGetProperty(Binding binding) { Name name = binding.getName(); assert name != null; if ( utils.isWriteOnlyDefinition(binding.getDefinition()) ) { addProblem(new PropertyIsWriteOnlyProblem( binding.getNode(), name.getBaseName() )); } switch ( name.getKind() ) { case ABCConstants.CONSTANT_QnameA: case ABCConstants.CONSTANT_MultinameA: case ABCConstants.CONSTANT_MultinameLA: case ABCConstants.CONSTANT_RTQnameA: case ABCConstants.CONSTANT_RTQnameLA: { // TODO: Attribute checks break; } case ABCConstants.CONSTANT_TypeName: { // TODO: Type name checks break; } case ABCConstants.CONSTANT_RTQname: case ABCConstants.CONSTANT_RTQnameL: case ABCConstants.CONSTANT_MultinameL: { // Not much to be done with these. } default: { if ( binding.getDefinition() == null && SemanticUtils.definitionCanBeAnalyzed(binding, this.project) ) { addProblem(accessUndefinedProperty(binding, binding.getNode())); } checkReference(binding); } } } /** * Check for possible problems with a reference for an r-value. * This will find 2 kinds of problems: * 1. Ambiguous references * 2. References to deprecated symbols * * @param b the binding to check */ public void checkReference(Binding b) { checkReference(b, false); } /** * Check for possible problems with a reference. * This will find 2 kinds of problems: * 1. Ambiguous references * 2. References to deprecated symbols * * @param b the binding to check * @param forLValue Whether the binding is for an l-value. */ public void checkReference(Binding b, boolean forLValue) { if (b != null) { IDefinition definition = b.getDefinition(); if (definition != null) { // Since the BURM doesn't currently understand whether a Binding // is for an l-value or an r-value, a Binding for an l-value // may contain a getter and a Binding for an r-value may contain // a setter. If that's the case, we need to find the corresponding // accessor. if (forLValue && definition instanceof IGetterDefinition || !forLValue && definition instanceof ISetterDefinition) { definition = ((IAccessorDefinition)definition).resolveCorrespondingAccessor(project); } IASNode node = b.getNode(); checkAmbiguousReference(b); checkDeprecated(node, definition); } } } /** * Log a problem if the binding is an ambiguous reference * @param b the binding to check */ private void checkAmbiguousReference(Binding b) { if( SemanticUtils.isAmbiguousReference(b) ) { addProblem(new AmbiguousReferenceProblem(b.getNode(), b.getName().getBaseName())); } } /** * Log a problem if the definition passed in has been marked deprecated * and the node passed in is not within a deprecated API. * * @param site the Node to use to obtain location info for any problems * @param def the definition to check */ private void checkDeprecated(IASNode site, IDefinition def) { if (def != null && def.isDeprecated()) { if (!SemanticUtils.hasDeprecatedAncestor(site)) { ICompilerProblem problem = SemanticUtils.createDeprecationProblem(def, site); addProblem(problem); } } } /** * Check the initial value of an {@link IVariableNode}. The specified * {@link IVariableNode} could be: * <ul> * <li>an {@link IParameterNode} for a function parameter.</li> * <li>an {@link IVariableNode} for a var or const in any scope.</li> * </ul> * This method will create {@link ICompilerProblem}s if the specified * initial value is not the correct type and will return an initializer * value that is the correct type that was created by apply the AS3 coercion * rules. * * @param iNode The {@link IVariableNode} whose initializer value should be * checked. * @param type A {@link Binding} for the specified {@link IVariableNode}'s * type annotation. * @param initial_value A {@link PooledValue} that contains the constant * initializer value computed by the constant propagation code. * @return A {@link PooledValue} with the correct type to initialize the * specified {@link IVariableNode}. This can be the same as the specified * {@link PooledValue} if it was already of the correct type. */ public PooledValue checkInitialValue(IVariableNode iNode, Binding type, PooledValue initial_value) { assert initial_value != null : "Caller's should generate a compiler problem when a default_value is missing and *not* call this method!"; ISourceLocation assignedValueExpressionLocation = iNode.getAssignedValueNode(); IDefinition target_type = type.getDefinition(); return checkInitialValue(assignedValueExpressionLocation, target_type, initial_value); } /** * Analyze an undefined binding and create an appropriate problem. * @return the problem that best fits this error condition. */ private ICompilerProblem accessUndefinedProperty(Binding b, IASNode iNode) { ICompilerProblem problem; String unknown_name = null; if ( b.getName() != null ) unknown_name = b.getName().getBaseName(); else if (SemanticUtils.isThisKeyword(iNode)) unknown_name = "this"; else return null; if ( b.getNode() instanceof MemberAccessExpressionNode ) { MemberAccessExpressionNode maex = (MemberAccessExpressionNode)b.getNode(); if ( maex.stemIsPackage() && maex.getLeftOperandNode() instanceof IdentifierNode ) { return new AccessUndefinedPropertyInPackageProblem( iNode, unknown_name, ((IdentifierNode)maex.getLeftOperandNode()).getName() ); } } else if ((problem = isMissingMember(b.getNode())) != null) { return problem; } else if ( utils.isInInstanceFunction(iNode) && utils.isInaccessible((ASScope)utils.getEnclosingFunctionDefinition(iNode).getContainingScope(), b) ) { return new InaccessiblePropertyReferenceProblem( iNode, b.getName().getBaseName(), utils.getEnclosingClassName(iNode) ); } return new AccessUndefinedPropertyProblem(iNode, unknown_name); } public ICompilerProblem isMissingMember(IASNode iNode) { if (iNode instanceof IdentifierNode && iNode.getParent() instanceof MemberAccessExpressionNode) { MemberAccessExpressionNode mae = (MemberAccessExpressionNode)(iNode.getParent()); if (iNode == mae.getRightOperandNode()) { ITypeDefinition leftDef = mae.getLeftOperandNode().resolveType(project); if (!leftDef.isDynamic()) return new AccessUndefinedMemberProblem(iNode, ((IdentifierNode)iNode).getName(), leftDef.getQualifiedName()); } } return null; } /** * Ensure a super-qualified access is in an instance method. */ public void checkSuperAccess(IASNode iNode) { IDefinition def = utils.getDefinition(iNode); // If def isn't null, then we know this is a valid expression. // if def is null, allow super.foo if the reference is in an // instance function. if ( def == null && !utils.isInInstanceFunction(iNode) ) { addProblem(new InvalidSuperExpressionProblem(iNode)); } // For super.f(...), check whether f is deprecated. if (iNode instanceof IFunctionCallNode) { IExpressionNode nameNode = ((IFunctionCallNode)iNode).getNameNode(); IExpressionNode site = ((IMemberAccessExpressionNode)nameNode).getRightOperandNode(); def = ((IFunctionCallNode)iNode).resolveCalledExpression(project); checkDeprecated(site, def); } if ( this.superState == SuperState.Initial ) this.superState = SuperState.Armed; } private boolean isPackageReference(Binding b) { IASNode n = b.getNode(); return n instanceof ExpressionNodeBase && ((ExpressionNodeBase)n).isPackageReference(); } /** * Check foo++ and foo-- expressions. */ public void checkIncDec(IASNode iNode, boolean is_incr) { IASNode operand = ((IUnaryOperatorNode)iNode).getOperandNode(); checkImplicitConversion(operand, utils.numberType(), null); IDefinition def = utils.getDefinition(operand); if ( utils.isReadOnlyDefinition(def) ) { if ( is_incr ) addProblem(new InvalidIncrementOperandProblem(iNode)); else addProblem(new InvalidDecrementOperandProblem(iNode)); } else if ( def instanceof SetterDefinition || def instanceof GetterDefinition ) { // No error, just avoid the else if ( def instanceof IFunctionDefinition ) below } else if ( def instanceof IFunctionDefinition || def instanceof ClassDefinition ) { if ( is_incr ) addProblem(new InvalidIncrementOperandProblem(iNode)); else addProblem(new InvalidDecrementOperandProblem(iNode)); } else if ( SemanticUtils.isConstDefinition(def) ) { // TODO: See CMP-1177; enhanced error message would be appropriate. if ( is_incr ) addProblem(new InvalidIncrementOperandProblem(iNode)); else addProblem(new InvalidDecrementOperandProblem(iNode)); } } /** * Check foo++ and foo-- expressions. * @param iNode the inc/dec Node * @param is_incr whether this is an increment or decrement operation * @param binding the binding for the name we are incrementing/decrementing */ public void checkIncDec(IASNode iNode, boolean is_incr, Binding binding) { checkIncDec(iNode, is_incr); if ( binding.getName() != null ) { checkGetProperty(binding); } else { addProblem( is_incr? new IncrementMustBeReferenceProblem(iNode): new DecrementMustBeReferenceProblem(iNode) ); } } /** * Perform semantic checks on an lvalue, * i.e., the target of an assignment * or other storage-mutating operation. */ public void checkLValue(IASNode iNode, Binding binding) { IDefinition def = binding.getDefinition(); if ( SemanticUtils.isThisKeyword(binding.getNode()) ) { addProblem(new AssignToNonReferenceValueProblem(binding.getNode())); } else if ( def == null && !utils.hasDynamicBase(binding) && !SemanticUtils.isInWith(binding.getNode())) { addProblem(accessUndefinedProperty(binding, roundUpUsualSuspects(binding, iNode))); } else if ( def instanceof IConstantDefinition ) { addProblem(new AssignToConstProblem(iNode)); } else if ( utils.isReadOnlyDefinition(def) ) { addProblem(new AssignToReadOnlyPropertyProblem(iNode, binding.getName().getBaseName())); } else if ( def instanceof SetterDefinition || def instanceof GetterDefinition) { // No error, just avoid the else if ( def instanceof IFunctionDefinition ) below } else if ( def instanceof IFunctionDefinition ) { addProblem(new AssignToFunctionProblem(iNode, binding.getName().getBaseName())); } else if ( def instanceof ClassDefinition ) { addProblem(new IllegalAssignmentToClassProblem( roundUpUsualSuspects(binding, iNode), binding.getName().getBaseName() )); } checkReference(binding, true); } /** * Check a member access expression. */ public void checkMemberAccess(IASNode iNode, Binding member, int opcode) { // Don't check synthetic bindings. IASNode member_node = member.getNode(); if ( member_node == null ) return; IDefinition def = utils.getDefinition(member_node); if ( def == null && utils.definitionCanBeAnalyzed(member) ) { // if it is foo.mx_internal::someProp, just say it passes if (member_node.getParent() instanceof NamespaceAccessExpressionNode) return; if ( utils.isInaccessible(iNode, member) ) { addProblem(new InaccessiblePropertyReferenceProblem( member_node, member.getName().getBaseName(), utils.getTypeOfStem(iNode)) ); } else { ICompilerProblem p = null; // Look for the special case of "something." (empty right hand side). // Return a less perplexing error for this case. if (iNode instanceof IMemberAccessExpressionNode) { IMemberAccessExpressionNode maen = (IMemberAccessExpressionNode)iNode; IExpressionNode right = maen.getRightOperandNode(); if (right instanceof IIdentifierNode) { if (((IIdentifierNode)right).getName().isEmpty()) { p = new MissingPropertyNameProblem(right); } } } if (p== null) { // If right side not blank, then use normal undefined member p = new AccessUndefinedMemberProblem( member_node, member.getName().getBaseName(), utils.getTypeOfStem(iNode)); } addProblem(p); } } else if ( utils.isWriteOnlyDefinition(member.getDefinition()) ) { addProblem(new PropertyIsWriteOnlyProblem( member_node, member.getName().getBaseName() )); } else { checkReference(member); } } /** * Check a "new expression". * * @param call_node {@link FunctionCallNode} that has the "new" call. */ public void checkNewExpr(IASNode call_node) { final ExpressionNodeBase name = ((FunctionCallNode)call_node).getNameNode(); if (name instanceof MemberAccessExpressionNode) { final MemberAccessExpressionNode func_name = (MemberAccessExpressionNode)name; final IDefinition def = func_name.resolve(project); if ( def instanceof InterfaceDefinition ) { addProblem(new InterfaceCannotBeInstantiatedProblem(call_node)); } else if ( def instanceof ClassDefinition ) { // pass } else if ( def instanceof GetterDefinition ) { // pass } else if (def instanceof FunctionDefinition) { final FunctionDefinition func_def = (FunctionDefinition)def; switch (func_def.getFunctionClassification()) { case CLASS_MEMBER: case INTERFACE_MEMBER: addProblem(new MethodCannotBeConstructorProblem(call_node)); break; } } } } /** * Check a new expression. */ public void checkNewExpr(IASNode iNode, Binding class_binding, Vector<? extends Object> args) { IDefinition def = class_binding.getDefinition(); if ( class_binding.isLocal() ) { // No checking required. } else if ( def == null && utils.definitionCanBeAnalyzed(class_binding) && !(class_binding.getName().isTypeName()) ) { // Note: don't have to check accessability because // AS3 mandates constructors be public. addProblem(new CallUndefinedMethodProblem( roundUpUsualSuspects(class_binding, iNode), class_binding.getName().getBaseName() )); } else if ( def instanceof InterfaceDefinition ) { addProblem(new InterfaceCannotBeInstantiatedProblem( roundUpUsualSuspects(class_binding, iNode) )); } else if ( def instanceof ClassDefinition ) { ClassDefinition class_def = (ClassDefinition)def; IFunctionDefinition ctor = class_def.getConstructor(); if ( ctor instanceof FunctionDefinition ) { FunctionDefinition func = (FunctionDefinition)ctor; checkFormalsVsActuals(iNode, func, args); } } else if ( def instanceof GetterDefinition ) { } else if ( def instanceof FunctionDefinition ) { FunctionDefinition func_def = (FunctionDefinition) def; IFunctionDefinition.FunctionClassification func_type = func_def.getFunctionClassification(); if ( func_type.equals(IFunctionDefinition.FunctionClassification.CLASS_MEMBER) || func_type.equals(IFunctionDefinition.FunctionClassification.INTERFACE_MEMBER) ) { addProblem(new MethodCannotBeConstructorProblem( roundUpUsualSuspects(class_binding, iNode) )); } } checkReference(class_binding); } /** * Check a return expression that returns a value. * @param iNode - the return statement. * Known to have a child 0 expression. */ public void checkReturnValue(IASNode iNode) { FunctionDefinition func_def = SemanticUtils.getFunctionDefinition(iNode); if ( func_def != null ) { try { IExpressionNode returnExpression = ((IReturnNode)iNode).getReturnValueNode(); IDefinition return_type = func_def.resolveReturnType(project); // void has its own special type logic. if ( ClassDefinition.getVoidClassDefinition().equals(return_type) ) { IDefinition value_type = ((ExpressionNodeBase)returnExpression).resolveType(project); if ( value_type != null && ! value_type.equals(ClassDefinition.getVoidClassDefinition()) ) addProblem(new ReturnValueMustBeUndefinedProblem(returnExpression)); } else if ( func_def.isConstructor() ) { addProblem(new ReturnValueInConstructorProblem(returnExpression)); } else { checkImplicitConversion(returnExpression, return_type, null); } } catch ( Exception namerezo_problem ) { // Ignore until CMP-869 is fixed. } } else if ( isInPackageDefinition(iNode) ) { addProblem(new ReturnCannotBeUsedInPackageProblem(iNode)); } else if ( isInClassDefinition(iNode) ) { addProblem(new ReturnCannotBeUsedInStaticProblem(iNode)); } else if ( iNode.getAncestorOfType(MXMLDocumentNode.class) != null ) { // TODO: Remove this once CMP-874 is resolved. } else { addProblem(new ReturnCannotBeUsedInGlobalProblem(iNode)); } if ( this.superState == SuperState.Initial ) this.superState = SuperState.Armed; } /** * Check a return expression that returns void. * @param iNode - the return statement. */ public void checkReturnVoid(IASNode iNode) { if ( SemanticUtils.isInFunction(iNode) ) { if ( SemanticUtils.functionMustReturnValue(iNode, this.project) ) addProblem(new ReturnMustReturnValueProblem(iNode)); } else if ( isInClassDefinition(iNode) ) { addProblem(new ReturnCannotBeUsedInStaticProblem(iNode)); } else if ( isInPackageDefinition(iNode) ) { addProblem(new ReturnCannotBeUsedInPackageProblem(iNode)); } else if ( iNode.getAncestorOfType(MXMLDocumentNode.class) != null ) { // TODO: Remove this once CMP-874 is resolved. } else { addProblem(new ReturnCannotBeUsedInGlobalProblem(iNode)); } if ( this.superState == SuperState.Initial ) this.superState = SuperState.Armed; } /** * Perform semantic checks that require flow-aware analysis. * @param iNode - an AST within a function. * @param mi - the function's MethodInfo. * @param mbi - the function's MethodBodyInfo. */ public void checkControlFlow(IASNode iNode, MethodInfo mi, MethodBodyInfo mbi) { InstructionList il = mbi.getInstructionList(); // Walk the control flow graph if there are any issues that // require it. if ( il.size() > 0 && il.lastElement() == ABCGeneratingReducer.synthesizedReturnVoid && SemanticUtils.functionMustReturnValue(iNode, this.project) ) { for ( IBasicBlock b: mbi.getCfg().blocksInControlFlowOrder() ) { if ( b.size() > 0 ) { // If at some point it's necessary to walk the CFG regardless, // then this check can be for any OP_returnvoid and the alternate // problem injection site in checkReturnVoid can be removed. if ( b.get(b.size()-1) == ABCGeneratingReducer.synthesizedReturnVoid ) { addProblem(new ReturnMustReturnValueProblem(iNode)); break; } } } } } /** * Check a throw statement. */ public void checkThrow(IASNode iNode) { if ( this.superState == SuperState.Initial ) this.superState = SuperState.Armed; } /** * Check an import directive. * @param importNode - the import node. */ public void checkImportDirective(IImportNode importNode) { IASNode site = importNode.getImportNameNode(); if (!SemanticUtils.isValidImport(importNode, project, currentScope.getInInvisibleCompilationUnit())) { String importName = importNode.getImportName(); if (importNode.isWildcardImport()) addProblem(new UnknownWildcardImportProblem(site, importName)); else addProblem(new UnknownImportProblem(site, importName)); } // Check if a deprecated definition is being imported. if (!importNode.isWildcardImport()) { IDefinition importedDefinition = importNode.resolveImport(project); checkDeprecated(site, importedDefinition); } } /** * Check a use namespace directive. * @param iNode - the use namespace node. * @param ns_name - the namespace's Binding. */ public void checkUseNamespaceDirective(IASNode iNode, Binding ns_name) { if ( ns_name.getDefinition() == null && ns_name.getName() != null ) { // For error reporting, let's get the fully qualified name of the namespace, if possible String fullNamespaceName=null; IASNode n = ns_name.getNode(); if (n instanceof IIdentifierNode) { // In cases I've seen we go through this path fullNamespaceName = ((IIdentifierNode)n).getName(); } else { // This is purely defensive programming - if for some reason we can't get the full name, // go back to this older code that gets the short name. fullNamespaceName = ns_name.getName().getBaseName(); } addProblem( new UnknownNamespaceProblem ( roundUpUsualSuspects(ns_name, iNode), fullNamespaceName ) ); } checkReference(ns_name); } /** * Check a unary operator. * @param iNode - the operator's i-node. * @param opcode - the corresponding opcode. */ public void checkUnaryOperator(IASNode iNode, int opcode) { switch(opcode) { case ABCConstants.OP_negate: case ABCConstants.OP_convert_d: case ABCConstants.OP_bitnot: checkImplicitConversion(((IUnaryOperatorNode)iNode).getOperandNode(), utils.numberType(), null); break; } } /** * @return true if the i-node is in a class definition. */ private boolean isInClassDefinition(IASNode iNode) { return iNode.getAncestorOfType(ClassNode.class) != null; } /** * @return true if the i-node is in a package definition. */ private boolean isInPackageDefinition(IASNode iNode) { return iNode.getAncestorOfType(PackageNode.class) != null; } /** * Ensure that a definition does not have the same modifier more than once */ public void checkForDuplicateModifiers(BaseDefinitionNode bdn) { ModifiersContainerNode mcn = bdn.getModifiersContainer(); ModifiersSet modifierSet = bdn.getModifiers(); if( mcn != null && modifierSet != null ) { int modifierNodeCount = mcn.getChildCount(); if( modifierNodeCount > modifierSet.getAllModifiers().length ) { ModifiersSet tempSet = new ModifiersSet(); // More children than modifiers - must have dups for( int i = 0; i < modifierNodeCount; ++i ) { IASNode node = mcn.getChild(i); if( node instanceof ModifierNode ) { ModifierNode modNode = (ModifierNode)node; if( tempSet.hasModifier(modNode.getModifier()) ) { currentScope.addProblem(new DuplicateAttributeProblem(modNode, modNode.getModifierString())); } tempSet.addModifier(modNode); } } } } } /** * Looks for namespace prefixes on definitions inside of functions. * Example: * <code><pre> * function foo() : void { * private var bar:int;// this will give "Access modifier not allowed..." * ns var baz:int; // this will give "Namespace override not allowed ..." * } * </pre></code> * * This function must only be called for nodes that are in fact withing a function. */ private void checkForNamespaceInFunction(BaseDefinitionNode node, LexicalScope scope) { assert SemanticUtils.isInFunction(node); DefinitionBase def = node.getDefinition(); INamespaceReference nsRef = def.getNamespaceReference(); if (!(nsRef instanceof NamespaceDefinition.IInternalNamespaceDefinition) && !(nsRef instanceof NamespaceDefinition.IFilePrivateNamespaceDefinition)) { IASNode site = node.getNamespaceNode(); if (site == null) site = node; boolean isAccessor = nsRef instanceof NamespaceDefinition.ILanguageNamespaceDefinition; currentScope.addProblem(new NamespaceOverrideInsideFunctionProblem(site, isAccessor)); } } /** * Check a variable declaration. */ public void checkVariableDeclaration(IASNode iNode) { VariableNode var = (VariableNode)iNode; ModifiersSet modifiersSet = var.getModifiers(); boolean isInFunction = SemanticUtils.isInFunction(iNode); if (isInFunction) { checkForNamespaceInFunction(var, currentScope); } // Variable decls inside methods can't have attributes other than namespaces. if ( isInFunction && modifiersSet != null) { IASNode site = var.getNameExpressionNode(); for ( ASModifier modifier : modifiersSet.getAllModifiers() ) { if( modifier == ASModifier.NATIVE ) { currentScope.addProblem(new NativeVariableProblem(site)); } else if (modifier == ASModifier.DYNAMIC ) { currentScope.addProblem(new DynamicNotOnClassProblem(site)); } else if( modifier == ASModifier.FINAL ) { currentScope.addProblem(new FinalOutsideClassProblem(site)); } else if( modifier == ASModifier.OVERRIDE ) { currentScope.addProblem(new InvalidOverrideProblem(site)); } else if( modifier == ASModifier.VIRTUAL ) { currentScope.addProblem(new VirtualOutsideClassProblem(site)); } else if( modifier == ASModifier.STATIC ) { currentScope.addProblem(new StaticOutsideClassProblem(site)); } } } // Check for ambiguity. IDefinition def = utils.getDefinition(var); checkVariableForConflictingDefinitions(iNode, (VariableDefinition)def); checkNamespaceOfDefinition(var, def, project); ///////////////////////////////////// // Check for a type on the variable declaration String type = var.getTypeName(); if (type.isEmpty()) // empty string means no declaration at all (not *) { // don't check things that didn't come from source. They tend to give false negatives if (var.getStart() != var.getEnd()) { // get a node that has the best display location for problem IASNode location = var; IExpressionNode nameExpression = var.getNameExpressionNode(); if (nameExpression != null) location = nameExpression; this.currentScope.addProblem( new VariableHasNoTypeDeclarationProblem(location, var.getShortName())); } } // check if thise is an assignment in this declaration. // If so, do checks on that final ExpressionNodeBase rightNode = var.getAssignedValueNode(); if( var.isConst() && rightNode == null ) { addProblem(new ConstNotInitializedProblem(var, var.getName())); } // if there is an initializer, check that the value is reasonable if (rightNode != null) { checkAssignmentValue(def, rightNode); } if(SemanticUtils.isNestedClassProperty(iNode, (VariableDefinition)def)) { // TODO: Issue a better, mor specific diagnostic // TODO: once we are allowed to add new error strings. addProblem(new BURMDiagnosticNotAllowedHereProblem(iNode)); } } /** * Verify that a named type exists. * @param typename - the name of the type. */ public void checkTypeName(Binding typename) { if ( typename.getNode() == null || typename.getName() == null ) { // Some kind of synthetic Binding, ignore. } else { IDefinition typeDef = utils.getDefinition(typename.getNode()); if ( !SemanticUtils.isType(typeDef) ) { Name name = typename.getName(); while ( name != null && name.isTypeName() && name.getTypeNameParameter() != null ) name = name.getTypeNameParameter(); if ( name != null ) { if ( !name.isTypeName() ) { addTypeProblem(typename.getNode(), typeDef, name.getBaseName(), false); } else { // Render as best able. addTypeProblem(typename.getNode(), typeDef, name.toString(), false); } } } } checkReference(typename); } /** * Check a class field declaration. */ public void checkClassField(VariableNode var) { checkVariableDeclaration(var); // Check the variable's type. IASNode typeNode = var.getTypeNode(); IDefinition typeDef = utils.getDefinition(typeNode); if ( !SemanticUtils.isType(typeDef)) { IASNode problematicType = utils.getPotentiallyParameterizedType(typeNode); String typeDesc; if ( problematicType instanceof IdentifierNode ) { typeDesc = ((IdentifierNode)problematicType).getName(); } else { typeDesc = var.getTypeName(); } addTypeProblem(problematicType, typeDef, typeDesc, true); } } /** * Add the appropriate Problem when a type annotation did not resolve to a Type * @param typeNode the Node to use for location info for the problem * @param typeDef The IDefinition the type annotation resolved to * @param typeDesc A String to use as the description of the type annotation in the diagnostic * @param reportAmbiguousReference A flag indicating whether an AmbiguousReferenceProblem * should be reported, if the <code>typeDef</code> is ambiguous. */ public void addTypeProblem (IASNode typeNode, IDefinition typeDef, String typeDesc, boolean reportAmbiguousReference) { if( AmbiguousDefinition.isAmbiguous(typeDef) ) { if (reportAmbiguousReference) addProblem(new AmbiguousReferenceProblem(typeNode, typeDesc)); } else { addProblem(new UnknownTypeProblem(typeNode, typeDesc)); } } /** * Check the qualifier of a qualified name ('a' in 'a::foo') * @param iNode the node representing the qualifier */ public void checkQualifier(IASNode iNode) { IDefinition qualifier = utils.getDefinition(iNode); // If a qualifier is used in a context hwere it is not valid, then the qualifier will resolve // to the CM Implicit namespace. // This happens for code like: private::foo // when the code is outside of a class (so there is no private). if( qualifier == NamespaceDefinition.getCodeModelImplicitDefinitionNamespace() ) { INamespaceDecorationNode nsNode = (INamespaceDecorationNode)iNode; String nsString = nsNode.getName(); if( nsString == IASKeywordConstants.PUBLIC ) { addProblem(new InvalidPublicNamespaceProblem(nsNode) ); } else if( nsString == IASKeywordConstants.PROTECTED ) { addProblem(new InvalidProtectedNamespaceProblem(nsNode) ); } else if( nsString == IASKeywordConstants.PRIVATE ) { addProblem(new InvalidPrivateNamespaceProblem(nsNode)); } } checkDeprecated(iNode, qualifier); } /** * Check a namespace declaration. */ public void checkNamespaceDeclaration(IASNode iNode, Binding ns_name) { NamespaceNode nsNode = (NamespaceNode)iNode; IDefinition def = utils.getDefinition(nsNode); checkNamespaceOfDefinition(nsNode, def, project); SemanticUtils.checkScopedToDefaultNamespaceProblem(currentScope, nsNode, def, null); if (SemanticUtils.isInFunction(iNode)) { if (iNode instanceof BaseDefinitionNode) checkForNamespaceInFunction((BaseDefinitionNode)iNode, currentScope); } // Check whether the namespace is being initialized to a deprecated namespace. IExpressionNode namespaceInitialValueNode = nsNode.getNamespaceURINode(); if (namespaceInitialValueNode != null) { IDefinition namespaceInitialvalueDefinition = namespaceInitialValueNode.resolve(project); checkDeprecated(namespaceInitialValueNode, namespaceInitialvalueDefinition); } } /** * Check that the namespace of the definition is valid */ public void checkNamespaceOfDefinition(IASNode iNode, IDefinition def, ICompilerProject project) { INamespaceReference nsRef = def.getNamespaceReference(); // If it's not a language namespace, then it is only valid if the def is declared in a class if( !nsRef.isLanguageNamespace() && !(def.getParent() instanceof IClassDefinition) ) { // if in function, we will already generate the error that no overrides at all are allowed if (!SemanticUtils.isInFunction(iNode)) addProblem(new InvalidNamespaceProblem(iNode instanceof BaseDefinitionNode ? ((BaseDefinitionNode)iNode).getNamespaceNode() : iNode)); } if( nsRef == NamespaceDefinition.getCodeModelImplicitDefinitionNamespace() // constructors are left in the CMImplicit namespace so that FB continues to work right && !isConstructor(def) ) { // This should only happen if an invalid access namespace was specified // e.g. private outside of a class BaseDefinitionNode bdn = iNode instanceof BaseDefinitionNode ? (BaseDefinitionNode)iNode : null; if( bdn != null ) { INamespaceDecorationNode nsNode = bdn.getNamespaceNode(); if( nsNode != null ) { String nsString = nsNode.getName(); if( nsString == IASKeywordConstants.PUBLIC ) { addProblem(new InvalidPublicNamespaceAttrProblem(nsNode) ); } else if( nsString == IASKeywordConstants.PROTECTED ) { addProblem(new InvalidProtectedNamespaceAttrProblem(nsNode) ); } else if( nsString == IASKeywordConstants.PRIVATE ) { addProblem(new InvalidPrivateNamespaceAttrProblem(nsNode)); } } } } IASNode nsNode = iNode instanceof BaseDefinitionNode ? ((BaseDefinitionNode)iNode).getNamespaceNode() : iNode; INamespaceDefinition nsDef = nsRef.resolveNamespaceReference(project); if (nsRef.resolveNamespaceReference(project) == null) addProblem(new UnresolvedNamespaceProblem(nsNode)); checkDeprecated(nsNode, nsDef); } private boolean isConstructor(IDefinition d) { if( d instanceof IFunctionDefinition && ((IFunctionDefinition)d).isConstructor() ) return true; return false; } /** * Check a Vector literal. */ public void checkVectorLiteral(IASNode iNode, Binding type_param) { IDefinition type_def = type_param.getDefinition(); IContainerNode contentsNode = ((VectorLiteralNode)iNode).getContentsNode(); if ( type_def != null ) { boolean isNumericTypeOrBoolean = SemanticUtils.isNumericTypeOrBoolean(type_def, project); String typeName = type_def.getBaseName(); boolean isInt = SemanticUtils.isBuiltin(type_def, BuiltinType.INT, project); boolean isUint = SemanticUtils.isBuiltin(type_def, BuiltinType.UINT, project); for ( int i = 0; i < contentsNode.getChildCount(); i++ ) { IASNode literal_element = contentsNode.getChild(i); if (isNumericTypeOrBoolean) { // check for loss of precision if ( literal_element instanceof NumericLiteralNode ) { INumericLiteralNode.INumericValue numeric = ((NumericLiteralNode)literal_element).getNumericValue(); if ( (isInt && ( numeric.toNumber() != numeric.toInt32())) || (isUint && ( numeric.toNumber() != numeric.toUint32())) ) { addProblem(new LossyConversionProblem(literal_element, typeName)); } } // check for null values where they don't make sense if (literal_element instanceof IExpressionNode) { IDefinition elementType = ((IExpressionNode)literal_element).resolveType(project); boolean elementIsNull = SemanticUtils.isBuiltin(elementType, BuiltinType.NULL, project); if (elementIsNull) { addProblem( new NullUsedWhereOtherExpectedProblem(literal_element, typeName)); } } } checkImplicitConversion(literal_element, type_def, null); } } return; } /** * Find at least some AS3 context for a diagnostic. * @return the i-node from the problematic Binding, or the nearest * known i-node if the Binding has no i-node (i.e., it's synthetic). */ private IASNode roundUpUsualSuspects(Binding offender, IASNode bystander) { if (offender.getNode() != null) return offender.getNode(); else return bystander; } /** * Check a rest parameter declaration (...rest) * @param iNode - the ParameterNode for the rest decl. * @param param_type - the type of the rest param */ public void checkRestParameter(IASNode iNode, Binding param_type) { IDefinition type = param_type.getDefinition(); if( !utils.isBuiltin(type, BuiltinType.ARRAY) && !utils.isBuiltin(type, BuiltinType.ANY_TYPE) ) addProblem(new InvalidRestParameterDeclarationProblem(((ParameterNode)iNode).getTypeNode())); } /** * Check for other definitions that might conflict with the function passed in. This method will issue * a diagnostic if there are any definitions with the same name in the same declaring scope. It will * not find conflicts in base classes - if a method conflicts in something in a base class some sort of * illegal override error will already have been issued. * * @param iNode The Node that produced the function definition. Used for location info for any diagnostics. * @param funcDef The FunctionDefinition of the function to check */ public boolean checkFunctionForConflictingDefinitions (IASNode iNode, FunctionDefinition funcDef) { boolean foundConflict = false; // Only have to check for dups in the current class if this is a class method // If a base class has a conflict some sort of illegal override error will have been generated already // if this is a global or nested function we don't have to check the base class as there won't be one switch( SemanticUtils.getMultiDefinitionType(funcDef, project)) { case AMBIGUOUS: String namespaceName = getNamespaceStringFromDef(funcDef); addProblem(new ConflictingNameInNamespaceProblem(iNode, funcDef.getBaseName(), namespaceName)); foundConflict = true; break; case NONE: break; case MULTIPLE: addProblem(new DuplicateFunctionDefinitionProblem(iNode, funcDef.getBaseName())); break; default:; assert false; } // getter or setter with same name as a package gets a warning/ // we don't return true, however, as it isn't "really" a multiple definition if (funcDef instanceof IAccessorDefinition) { ASScope cs = (ASScope)funcDef.getContainingScope(); if (cs.isPackageName(funcDef.getBaseName())) { IFunctionNode funcNode = (IFunctionNode)iNode; IASNode funcNameNode = funcNode.getNameExpressionNode(); addProblem(new DefinitionShadowedByPackageNameProblem(funcNameNode)); } } return foundConflict; } /** * Check for other definitions that might conflict with the function passed in. This method is for interface * functions, so it will check for dups in the current interface, and also look for any conflicts in base interfaces. * * @param iNode The node that produced the function definition. Used for location info for any diagnostics. * @param funcDef The FunctionDefinition of the interface function to check */ public void checkInterfaceFunctionForConflictingDefinitions (IASNode iNode, FunctionDefinition funcDef) { // Look for conflicts in this interface checkFunctionForConflictingDefinitions(iNode, funcDef); // Look for methods from base interfaces we may be overriding. List<IFunctionDefinition> conflicts = funcDef.resolveOverridenInterfaceFunctions(project); if( conflicts.size() > 0 ) { for( IFunctionDefinition overriden : conflicts ) { if ((overriden instanceof SetterDefinition && funcDef instanceof GetterDefinition) || (overriden instanceof GetterDefinition && funcDef instanceof SetterDefinition)) continue; addProblem(new InterfaceMethodOverrideProblem(iNode, funcDef.getBaseName(), overriden.getParent().getBaseName())); } } } /** * Check for other definitions that might conflict with the variable passed in. This method will check for conflicts * in the declaring scope of the variable, and if it is declared in a class it will check for conflicts in the base classes. * * @param iNode The node that produced the variable definition. Used for location info for any diagnostics. * @param varDef The VariableDefinition of the variable to check */ public void checkVariableForConflictingDefinitions( IASNode iNode, VariableDefinition varDef ) { MultiDefinitionType ambiguity = SemanticUtils.getMultiDefinitionType(varDef, project); if (ambiguity != MultiDefinitionType.NONE) { final String varName = varDef.getBaseName(); ICompilerProblem problem = null; if (ambiguity == MultiDefinitionType.AMBIGUOUS) { problem = new ConflictingNameInNamespaceProblem(iNode, varName, getNamespaceStringFromDef(varDef)); } else { IVariableNode varNode = (IVariableNode)iNode; IASNode varNameNode = varNode.getNameExpressionNode(); if (ambiguity == MultiDefinitionType.MULTIPLE) problem = new DuplicateVariableDefinitionProblem(varNameNode, varName); else if (ambiguity == MultiDefinitionType.SHADOWS_PARAM) problem = new VariableDefinitionDuplicatesParameterProblem(varNameNode, varName); } assert problem != null; if (problem != null) addProblem(problem); } if (!varDef.isStatic() && SemanticUtils.hasBaseClassDefinition(iNode, project)) { addProblem(new ConflictingInheritedNameInNamespaceProblem(iNode, varDef.getBaseName(), getNamespaceStringFromDef(varDef) )); } // Now look to see if the variable name is also a package name ASScope cs = (ASScope)varDef.getContainingScope(); if (cs.isPackageName(varDef.getBaseName())) { IVariableNode varNode = (IVariableNode)iNode; IASNode varNameNode = varNode.getNameExpressionNode(); addProblem(new DefinitionShadowedByPackageNameProblem(varNameNode)); } } private String getNamespaceStringFromDef (IDefinition funcDef) { String namespaceName = null; INamespaceDefinition n = funcDef.resolveNamespace(project); if (n != null) { namespaceName = n.getBaseName(); } return namespaceName; } /** * Signal the semantic checker that its caller is entering a constructor: * initialize constructor-specific state variables. */ public void enterConstructor() { assert this.superState == SuperState.Invalid: String.format("Unexpected super() tracking state %s", this.superState); this.superState = SuperState.Initial; } /** * Signal the semantic checker that its caller is leaving a constructor: * reset constructor-specific state variables. */ public void leaveConstructor() { assert this.superState != SuperState.Invalid: String.format("Unexpected super() tracking state %s", this.superState); this.superState = SuperState.Invalid; } /** * Test whether the getter for the specified accessor can be inlined * * @param accessor The accessor to test whether the getter can be inlined * @return true if the getter can be inlined */ public boolean canGetterBeInlined(AccessorDefinition accessor) { if (accessor instanceof SetterDefinition) accessor = accessor.resolveCorrespondingAccessor(currentScope.getProject()); if (accessor == null) return false; return canFunctionBeInlined(accessor); } /** * Test whether the setter for the specified accessor can be inlined * * @param accessor The accessor to test whether the setter can be inlined * @return true if the setter can be inlined */ public boolean canSetterBeInlined(AccessorDefinition accessor) { if (accessor instanceof GetterDefinition) accessor = accessor.resolveCorrespondingAccessor(currentScope.getProject()); if (accessor == null) return false; return canFunctionBeInlined(accessor); } /** * Test whether the specified function can be inlined. If the function * can't be inlined, a problem will be if the user explicity requested * the function be inlined, rather than the compiler trying to * inline it optimistically. * * @param function The function to test whether it can be inlined * @return true if the function can be inlined */ public boolean canFunctionBeInlined(FunctionDefinition function) { if (!currentScope.getProject().isInliningEnabled()) return false; // only report a problem when a function can't be inlined if the // function has been explicitly marked as inline final boolean reportInlineProblems = function.isInline(); // don't support inlining functions inside inlined functions if (currentScope.insideInlineFunction()) { if (reportInlineProblems) currentScope.addProblem(new InlineNestedInliningNotSupportedProblem(function.getBaseName())); return false; } // can't inline the function if we don't have a node for it FunctionNode functionNode = (FunctionNode)function.getFunctionNode(); if (functionNode == null) { if (reportInlineProblems) currentScope.addProblem(new InlineNoSourceProblem(function.getBaseName())); return false; } if (!function.inlineFunction()) { if (reportInlineProblems) currentScope.addProblem(new InlineFunctionNotFinalStaticOrGlobalProblem(functionNode, function.getBaseName())); return false; } // Pass in a new collection for the compiler problems, as we don't care // about any problems parsing the body, as they will be reported when // parsing the non-inlined version of the function. functionNode.parseFunctionBody(new ArrayList<ICompilerProblem>()); // If we meet all the requirements for an inlined method, parse and scan the // body to make sure there isn't any constructs we can't inline. final ScopedBlockNode functionBody = functionNode.getScopedNode(); if (functionBodyHasNonInlineableNodes(functionBody, reportInlineProblems, function.getBaseName(), new AtomicInteger())) { functionNode.discardFunctionBody(); return false; } return true; } /** * Check for any constructs we know we can't inline, or if the function is too large * * @param n the node to test * @param reportInlineProblems whether or not to report inline problems * @param functionName the name of the function being inlined * @param exprCount a running count of the expressions in the body * @return true if the function contains un-inlineable nodes, false if * the function body can be inlined */ public boolean functionBodyHasNonInlineableNodes(IASNode n, boolean reportInlineProblems, String functionName, AtomicInteger exprCount) { if (n == null) return false; if (n instanceof ExpressionNodeBase) { exprCount.getAndIncrement(); if (exprCount.get() > InlineFunctionLexicalScope.MAX_EXPR_IN_BODY) { if (reportInlineProblems) currentScope.addProblem(new InlineFunctionTooLargeProblem(functionName, exprCount.get(), InlineFunctionLexicalScope.MAX_EXPR_IN_BODY)); return true; } } switch (n.getNodeID()) { case AnonymousFunctionID: case CatchID: case FinallyID: case FunctionID: case FunctionObjectID: case TryID: case WithID: { if (reportInlineProblems) currentScope.addProblem(new InlineUnsupportedNodeProblem(n, functionName)); return true; } default: { for (int i = 0; i < n.getChildCount(); i++) { if (functionBodyHasNonInlineableNodes(n.getChild(i), reportInlineProblems, functionName, exprCount)) return true; } } } return false; } /** * Iterate through the Instructions which are to be inlined and * ensure the can all be inlined. Any Instruction which makes * use of the scope chain will cause the inline to fail. * * @param insns inlined instructions * @param reportInlineProblems whether or not to report inline problems * @param functionName the name of the function being inlined * @return true if all instructions are OK to be inlined */ public boolean functionBodyHasNonInlineableInstructions(InstructionList insns, boolean reportInlineProblems, String functionName) { // if there are no instructions in the function body, then something went wrong (bad AS) // doing codegen, so we can't inline this function if (insns.isEmpty()) return true; for (Instruction insn : insns.getInstructions()) { switch (insn.getOpcode()) { case OP_pushwith: case OP_popscope: case OP_pushscope: case OP_newfunction: case OP_returnvoid: case OP_returnvalue: case OP_newactivation: case OP_newclass: case OP_newcatch: case OP_findpropstrict: case OP_findproperty: case OP_getlex: case OP_getscopeobject: case OP_getouterscope: { if (reportInlineProblems) currentScope.addProblem(new InlineUnsupportedInstructionProblem(functionName)); return true; } } } return false; } }