All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.royale.compiler.internal.semantics.MethodBodySemanticChecker Maven / Gradle / Ivy

There is a newer version: 0.9.12
Show newest version
/*
 *
 *  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.royale.compiler.internal.semantics;

import static org.apache.royale.abc.ABCConstants.OP_findproperty;
import static org.apache.royale.abc.ABCConstants.OP_findpropstrict;
import static org.apache.royale.abc.ABCConstants.OP_getlex;
import static org.apache.royale.abc.ABCConstants.OP_getouterscope;
import static org.apache.royale.abc.ABCConstants.OP_getscopeobject;
import static org.apache.royale.abc.ABCConstants.OP_newactivation;
import static org.apache.royale.abc.ABCConstants.OP_newcatch;
import static org.apache.royale.abc.ABCConstants.OP_newclass;
import static org.apache.royale.abc.ABCConstants.OP_newfunction;
import static org.apache.royale.abc.ABCConstants.OP_popscope;
import static org.apache.royale.abc.ABCConstants.OP_pushscope;
import static org.apache.royale.abc.ABCConstants.OP_pushwith;
import static org.apache.royale.abc.ABCConstants.OP_returnvalue;
import static org.apache.royale.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.royale.abc.ABCConstants;
import org.apache.royale.abc.graph.IBasicBlock;
import org.apache.royale.abc.instructionlist.InstructionList;
import org.apache.royale.abc.semantics.ECMASupport;
import org.apache.royale.abc.semantics.Instruction;
import org.apache.royale.abc.semantics.MethodInfo;
import org.apache.royale.abc.semantics.MethodBodyInfo;
import org.apache.royale.abc.semantics.Name;
import org.apache.royale.abc.semantics.PooledValue;
import org.apache.royale.compiler.common.ISourceLocation;
import org.apache.royale.compiler.common.ModifiersSet;
import org.apache.royale.compiler.common.ASModifier;
import org.apache.royale.compiler.constants.IASKeywordConstants;
import org.apache.royale.compiler.constants.IASLanguageConstants;
import org.apache.royale.compiler.constants.IASLanguageConstants.BuiltinType;
import org.apache.royale.compiler.definitions.IAccessorDefinition;
import org.apache.royale.compiler.definitions.IClassDefinition;
import org.apache.royale.compiler.definitions.IConstantDefinition;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.definitions.IFunctionDefinition;
import org.apache.royale.compiler.definitions.IGetterDefinition;
import org.apache.royale.compiler.definitions.IInterfaceDefinition;
import org.apache.royale.compiler.definitions.INamespaceDefinition;
import org.apache.royale.compiler.definitions.ISetterDefinition;
import org.apache.royale.compiler.definitions.ITypeDefinition;
import org.apache.royale.compiler.definitions.IVariableDefinition;
import org.apache.royale.compiler.definitions.references.INamespaceReference;
import org.apache.royale.compiler.internal.definitions.AmbiguousDefinition;
import org.apache.royale.compiler.internal.definitions.VariableDefinition;
import org.apache.royale.compiler.problems.*;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.tree.as.IASNode;
import org.apache.royale.compiler.tree.as.IBinaryOperatorNode;
import org.apache.royale.compiler.tree.as.ICompoundAssignmentNode;
import org.apache.royale.compiler.tree.as.IContainerNode;
import org.apache.royale.compiler.tree.as.IExpressionNode;
import org.apache.royale.compiler.tree.as.IFunctionCallNode;
import org.apache.royale.compiler.tree.as.IFunctionNode;
import org.apache.royale.compiler.tree.as.IIdentifierNode;
import org.apache.royale.compiler.tree.as.IImportNode;
import org.apache.royale.compiler.tree.as.IMemberAccessExpressionNode;
import org.apache.royale.compiler.tree.as.INamespaceDecorationNode;
import org.apache.royale.compiler.tree.as.INumericLiteralNode;
import org.apache.royale.compiler.tree.as.IParameterNode;
import org.apache.royale.compiler.tree.as.IReturnNode;
import org.apache.royale.compiler.tree.as.IUnaryOperatorNode;
import org.apache.royale.compiler.tree.as.IVariableNode;
import org.apache.royale.compiler.internal.as.codegen.ABCGeneratingReducer;
import org.apache.royale.compiler.internal.as.codegen.Binding;
import org.apache.royale.compiler.internal.as.codegen.InlineFunctionLexicalScope;
import org.apache.royale.compiler.internal.as.codegen.LexicalScope;
import org.apache.royale.compiler.internal.definitions.AccessorDefinition;
import org.apache.royale.compiler.internal.definitions.ClassDefinition;
import org.apache.royale.compiler.internal.definitions.ClassTraitsDefinition;
import org.apache.royale.compiler.internal.definitions.ConstantDefinition;
import org.apache.royale.compiler.internal.definitions.DefinitionBase;
import org.apache.royale.compiler.internal.definitions.FunctionDefinition;
import org.apache.royale.compiler.internal.definitions.GetterDefinition;
import org.apache.royale.compiler.internal.definitions.InterfaceDefinition;
import org.apache.royale.compiler.internal.definitions.NamespaceDefinition;
import org.apache.royale.compiler.internal.definitions.ParameterDefinition;
import org.apache.royale.compiler.internal.definitions.SetterDefinition;
import org.apache.royale.compiler.internal.definitions.TypeDefinitionBase;
import org.apache.royale.compiler.internal.scopes.ASScope;
import org.apache.royale.compiler.internal.semantics.SemanticUtils.MultiDefinitionType;
import org.apache.royale.compiler.internal.tree.as.BaseDefinitionNode;
import org.apache.royale.compiler.internal.tree.as.BinaryOperatorLogicalAndNode;
import org.apache.royale.compiler.internal.tree.as.BinaryOperatorLogicalOrNode;
import org.apache.royale.compiler.internal.tree.as.ClassNode;
import org.apache.royale.compiler.internal.tree.as.ExpressionNodeBase;
import org.apache.royale.compiler.internal.tree.as.FunctionCallNode;
import org.apache.royale.compiler.internal.tree.as.FunctionNode;
import org.apache.royale.compiler.internal.tree.as.IdentifierNode;
import org.apache.royale.compiler.internal.tree.as.LanguageIdentifierNode;
import org.apache.royale.compiler.internal.tree.as.LiteralNode;
import org.apache.royale.compiler.internal.tree.as.MemberAccessExpressionNode;
import org.apache.royale.compiler.internal.tree.as.ModifierNode;
import org.apache.royale.compiler.internal.tree.as.ModifiersContainerNode;
import org.apache.royale.compiler.internal.tree.as.NamespaceAccessExpressionNode;
import org.apache.royale.compiler.internal.tree.as.NamespaceNode;
import org.apache.royale.compiler.internal.tree.as.NodeBase;
import org.apache.royale.compiler.internal.tree.as.NumericLiteralNode;
import org.apache.royale.compiler.internal.tree.as.PackageNode;
import org.apache.royale.compiler.internal.tree.as.ParameterNode;
import org.apache.royale.compiler.internal.tree.as.ScopedBlockNode;
import org.apache.royale.compiler.internal.tree.as.TernaryOperatorNode;
import org.apache.royale.compiler.internal.tree.as.VariableNode;
import org.apache.royale.compiler.internal.tree.as.VectorLiteralNode;
import org.apache.royale.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 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 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 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, Vectoractuals)
    {
        //  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) )
            {
            	if (!method_binding.getName().getBaseName().equals("toString"))
	                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)}.
     * 

* Conversion rules: *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*ObjectvoidArrayXMLFunctionClassStringBooleanNumberuintint
CONSTANT_IntNo error No error No error Error/transform to null Error/transform to null Error/transform to null Error/transform to null Error/coerce to string Error/coerce to Boolean No Error If negative error and coerce otherwise no error -
CONSTANT_UIntNo error No error No error Error/transform to null Error/transform to null Error/transform to null Error/transform to null Error/coerce to string Error/coerce to Boolean No Error - Error/coerce if not an integer in int range
CONSTANT_DoubleNo error No error No error Error/transform to null Error/transform to null Error/transform to null Error/transform to null Error/coerce to string Error/coerce to Boolean - Error/coerce if not a positive error in uint range Error/coerce if not an integer in int range
CONSTANT_Utf8No error No error No error Error/transform to null Error/transform to null Error/transform to null Error/transform to null - Error/coerce to Boolean Error/coerce to Number Error/coerce to uint Error/coerce to int
CONSTANT_TrueNo error No error No error Error/transform to null Error/transform to null Error/transform to null Error/transform to null Error/coerce to string - Error/coerce to Number Error/coerce to uint Error/coerce to int
CONSTANT_FalseNo error No error No error Error/transform to null Error/transform to null Error/transform to null Error/transform to null Error/coerce to string - Error/coerce to Number Error/coerce to uint Error/coerce to int
CONSTANT_UndefinedNo error No error No error No error No error No error No error No error No error No error No error No error
CONSTANT_NullNo error No error No error No error No error No error No error No error Error/coerce to Boolean Error/coerce to Number Error/coerce to uint Error/coerce to int
CONSTANT_Namespace, CONSTANT_*NsNo error No error No error Error/transform to null Error/transform to null Error/transform to null Error/transform to null Error/transform to null Error/transform to true Error/transform to 0 Error/transform to 0 Error/transform to 0
* * @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: *
    *
  • an {@link IParameterNode} for a function parameter.
  • *
  • an {@link IVariableNode} for a var or const in any scope.
  • *
* 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 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: *
     *  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 ..."
     *  }
     *  
* * 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 typeDef 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 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()); // 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; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy