
org.apache.royale.compiler.internal.semantics.MethodBodySemanticChecker Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of compiler Show documentation
Show all versions of compiler Show documentation
The Apache Royale Compiler
/*
*
* 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 extends Object> args)
{
LanguageIdentifierNode super_node = (LanguageIdentifierNode)((IFunctionCallNode)iNode).getNameNode();
// Check that this super() call is in a constructor.
if ( !SemanticUtils.isInConstructor(iNode) )
{
addProblem(new InvalidSuperStatementProblem(iNode));
}
else
{
// Check that this super call does not follow a construct
// that invalidates it.
if ( this.superState != SuperState.Initial )
addProblem(new ExtraneousSuperStatementProblem(iNode));
else
this.superState = SuperState.Armed;
}
// Check parameters if possible.
ClassDefinition super_def = (ClassDefinition) super_node.resolveType(project);
if (super_def != null)
{
IFunctionDefinition ctor = super_def.getConstructor();
if (ctor instanceof FunctionDefinition)
{
checkFormalsVsActuals(super_node, (FunctionDefinition)ctor, args);
}
}
}
/**
* Check that formal and actual parameters correspond and are compatible.
*/
private void checkFormalsVsActuals(IASNode iNode, FunctionDefinition func, Vector extends Object> actuals)
{
// If the call is through a function variable then we don't know much about it.
// If we get a setter function assume a getter is what was meant since calling a setter
// directly is not legal and resolving the name for a getter/setter could
// return either. Code generation does the right thing so changing this check
// to just return for setters as well.
if ( func instanceof GetterDefinition || func instanceof SetterDefinition)
return;
// Check the formal parameter definitions, and ensure we have
// a corresponding number of actual parameters.
ParameterDefinition[] formals = func.getParameters();
if ( formals == null )
return;
boolean last_is_rest = formals.length > 0 && formals[formals.length - 1].isRest();
int required_count = 0;
if ( actuals.size() > formals.length && !last_is_rest )
{
addProblem(new TooManyFunctionParametersProblem(iNode, formals.length));
}
// Compute the number of required parameters.
for ( int i = 0; i < formals.length; i++ )
{
if ( formals[i].hasDefaultValue() || formals[i].isRest() )
break;
required_count++;
}
if ( actuals.size() < required_count )
{
addProblem(new TooFewFunctionParametersProblem(iNode, required_count));
}
// Check that the actuals are compatible with the formals.
IASNode actuals_container = null;
if( iNode instanceof FunctionCallNode )
actuals_container = ((FunctionCallNode)iNode).getArgumentsNode();
if ( actuals_container != null )
{
for ( int i = 0; i < actuals_container.getChildCount() && i < formals.length; i++ )
{
if ( !formals[i].isRest() )
checkImplicitConversion( actuals_container.getChild(i), formals[i].resolveType(project), func );
}
}
}
/**
* Check a function body's overall characteristics.
*/
public void checkFunctionBody(IASNode iNode)
{
if ( iNode instanceof FunctionNode )
{
FunctionNode func = (FunctionNode)iNode;
IDefinition def = func.getDefinition();
if ( !( def.hasModifier(ASModifier.NATIVE ) || def.hasModifier(ASModifier.DYNAMIC) || func.isConstructor() ) )
{
if ( !func.hasBody() )
{
addProblem(new FunctionWithoutBodyProblem(SemanticUtils.getFunctionProblemNode(func)));
}
}
}
}
public void checkNativeMethod(IASNode iNode)
{
if( iNode instanceof FunctionNode )
{
if ( ((FunctionNode)iNode).hasBody() )
{
addProblem(new NativeMethodWithBodyProblem(iNode));
}
}
}
/**
* Check a function call.
*/
public void checkFunctionCall(IASNode iNode, Binding method_binding, Vector extends Object>actuals)
{
// Skip synthetic calls.
if ( method_binding.getName() == null )
return;
// Check for calls to attributes.
if ( method_binding.getName().isAttributeName() )
{
addProblem(new AttributesAreNotCallableProblem(roundUpUsualSuspects(method_binding, iNode)));
}
IDefinition def = method_binding.getDefinition();
if ( def == null && utils.definitionCanBeAnalyzed(method_binding) )
{
if ( utils.isInaccessible(iNode, method_binding) )
{
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:
*
*
*
* *
* Object
* void
* Array
* XML
* Function
* Class
* String
* Boolean
* Number
* uint
* int
*
*
* CONSTANT_Int
* No 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_UInt
* No 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_Double
* No 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_Utf8
* No 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_True
* No 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_False
* No 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_Undefined
* No error
* No error
* No error
* No error
* No error
* No error
* No error
* No error
* No error
* No error
* No error
* No error
*
*
* CONSTANT_Null
* No 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_*Ns
* No 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 extends Object> args)
{
IDefinition def = class_binding.getDefinition();
if ( class_binding.isLocal() )
{
// No checking required.
}
else if ( def == null && utils.definitionCanBeAnalyzed(class_binding) && !(class_binding.getName().isTypeName()) )
{
// Note: don't have to check accessability because
// AS3 mandates constructors be public.
addProblem(new CallUndefinedMethodProblem(
roundUpUsualSuspects(class_binding, iNode),
class_binding.getName().getBaseName()
));
}
else if ( def instanceof InterfaceDefinition )
{
addProblem(new InterfaceCannotBeInstantiatedProblem(
roundUpUsualSuspects(class_binding, iNode)
));
}
else if ( def instanceof ClassDefinition )
{
ClassDefinition class_def = (ClassDefinition)def;
IFunctionDefinition ctor = class_def.getConstructor();
if ( ctor instanceof FunctionDefinition )
{
FunctionDefinition func = (FunctionDefinition)ctor;
checkFormalsVsActuals(iNode, func, args);
}
}
else if ( def instanceof GetterDefinition )
{
}
else if ( def instanceof FunctionDefinition )
{
FunctionDefinition func_def = (FunctionDefinition) def;
IFunctionDefinition.FunctionClassification func_type = func_def.getFunctionClassification();
if ( func_type.equals(IFunctionDefinition.FunctionClassification.CLASS_MEMBER) || func_type.equals(IFunctionDefinition.FunctionClassification.INTERFACE_MEMBER) )
{
addProblem(new MethodCannotBeConstructorProblem(
roundUpUsualSuspects(class_binding, iNode)
));
}
}
checkReference(class_binding);
}
/**
* Check a return expression that returns a value.
* @param iNode - the return statement.
* Known to have a child 0 expression.
*/
public void checkReturnValue(IASNode iNode)
{
FunctionDefinition func_def = SemanticUtils.getFunctionDefinition(iNode);
if ( func_def != null )
{
try
{
IExpressionNode returnExpression = ((IReturnNode)iNode).getReturnValueNode();
IDefinition return_type = func_def.resolveReturnType(project);
// void has its own special type logic.
if ( ClassDefinition.getVoidClassDefinition().equals(return_type) )
{
IDefinition value_type = ((ExpressionNodeBase)returnExpression).resolveType(project);
if ( value_type != null && ! value_type.equals(ClassDefinition.getVoidClassDefinition()) )
addProblem(new ReturnValueMustBeUndefinedProblem(returnExpression));
}
else if ( func_def.isConstructor() )
{
addProblem(new ReturnValueInConstructorProblem(returnExpression));
}
else
{
checkImplicitConversion(returnExpression, return_type, null);
}
}
catch ( Exception namerezo_problem )
{
// Ignore until CMP-869 is fixed.
}
}
else if ( isInPackageDefinition(iNode) )
{
addProblem(new ReturnCannotBeUsedInPackageProblem(iNode));
}
else if ( isInClassDefinition(iNode) )
{
addProblem(new ReturnCannotBeUsedInStaticProblem(iNode));
}
else if ( iNode.getAncestorOfType(MXMLDocumentNode.class) != null )
{
// TODO: Remove this once CMP-874 is resolved.
}
else
{
addProblem(new ReturnCannotBeUsedInGlobalProblem(iNode));
}
if ( this.superState == SuperState.Initial )
this.superState = SuperState.Armed;
}
/**
* Check a return expression that returns void.
* @param iNode - the return statement.
*/
public void checkReturnVoid(IASNode iNode)
{
if ( SemanticUtils.isInFunction(iNode) )
{
if ( SemanticUtils.functionMustReturnValue(iNode, this.project) )
addProblem(new ReturnMustReturnValueProblem(iNode));
}
else if ( isInClassDefinition(iNode) )
{
addProblem(new ReturnCannotBeUsedInStaticProblem(iNode));
}
else if ( isInPackageDefinition(iNode) )
{
addProblem(new ReturnCannotBeUsedInPackageProblem(iNode));
}
else if ( iNode.getAncestorOfType(MXMLDocumentNode.class) != null )
{
// TODO: Remove this once CMP-874 is resolved.
}
else
{
addProblem(new ReturnCannotBeUsedInGlobalProblem(iNode));
}
if ( this.superState == SuperState.Initial )
this.superState = SuperState.Armed;
}
/**
* Perform semantic checks that require flow-aware analysis.
* @param iNode - an AST within a function.
* @param mi - the function's MethodInfo.
* @param mbi - the function's MethodBodyInfo.
*/
public void checkControlFlow(IASNode iNode, MethodInfo mi, MethodBodyInfo mbi)
{
InstructionList il = mbi.getInstructionList();
// Walk the control flow graph if there are any issues that
// require it.
if (
il.size() > 0 &&
il.lastElement() == ABCGeneratingReducer.synthesizedReturnVoid &&
SemanticUtils.functionMustReturnValue(iNode, this.project)
)
{
for ( IBasicBlock b: mbi.getCfg().blocksInControlFlowOrder() )
{
if ( b.size() > 0 )
{
// If at some point it's necessary to walk the CFG regardless,
// then this check can be for any OP_returnvoid and the alternate
// problem injection site in checkReturnVoid can be removed.
if ( b.get(b.size()-1) == ABCGeneratingReducer.synthesizedReturnVoid )
{
addProblem(new ReturnMustReturnValueProblem(iNode));
break;
}
}
}
}
}
/**
* Check a throw statement.
*/
public void checkThrow(IASNode iNode)
{
if ( this.superState == SuperState.Initial )
this.superState = SuperState.Armed;
}
/**
* Check an import directive.
* @param importNode - the import node.
*/
public void checkImportDirective(IImportNode importNode)
{
IASNode site = importNode.getImportNameNode();
if (!SemanticUtils.isValidImport(importNode, project, currentScope.getInInvisibleCompilationUnit()))
{
String importName = importNode.getImportName();
if (importNode.isWildcardImport())
addProblem(new UnknownWildcardImportProblem(site, importName));
else
addProblem(new UnknownImportProblem(site, importName));
}
// Check if a deprecated definition is being imported.
if (!importNode.isWildcardImport())
{
IDefinition importedDefinition = importNode.resolveImport(project);
checkDeprecated(site, importedDefinition);
}
}
/**
* Check a use namespace directive.
* @param iNode - the use namespace node.
* @param ns_name - the namespace's Binding.
*/
public void checkUseNamespaceDirective(IASNode iNode, Binding ns_name)
{
if ( ns_name.getDefinition() == null && ns_name.getName() != null )
{
// For error reporting, let's get the fully qualified name of the namespace, if possible
String fullNamespaceName=null;
IASNode n = ns_name.getNode();
if (n instanceof IIdentifierNode)
{
// In cases I've seen we go through this path
fullNamespaceName = ((IIdentifierNode)n).getName();
}
else
{
// This is purely defensive programming - if for some reason we can't get the full name,
// go back to this older code that gets the short name.
fullNamespaceName = ns_name.getName().getBaseName();
}
addProblem(
new UnknownNamespaceProblem (
roundUpUsualSuspects(ns_name, iNode),
fullNamespaceName
)
);
}
checkReference(ns_name);
}
/**
* Check a unary operator.
* @param iNode - the operator's i-node.
* @param opcode - the corresponding opcode.
*/
public void checkUnaryOperator(IASNode iNode, int opcode)
{
switch(opcode)
{
case ABCConstants.OP_negate:
case ABCConstants.OP_convert_d:
case ABCConstants.OP_bitnot:
checkImplicitConversion(((IUnaryOperatorNode)iNode).getOperandNode(), utils.numberType(), null);
break;
}
}
/**
* @return true if the i-node is in a class definition.
*/
private boolean isInClassDefinition(IASNode iNode)
{
return iNode.getAncestorOfType(ClassNode.class) != null;
}
/**
* @return true if the i-node is in a package definition.
*/
private boolean isInPackageDefinition(IASNode iNode)
{
return iNode.getAncestorOfType(PackageNode.class) != null;
}
/**
* Ensure that a definition does not have the same modifier more than once
*/
public void checkForDuplicateModifiers(BaseDefinitionNode bdn)
{
ModifiersContainerNode mcn = bdn.getModifiersContainer();
ModifiersSet modifierSet = bdn.getModifiers();
if( mcn != null && modifierSet != null )
{
int modifierNodeCount = mcn.getChildCount();
if( modifierNodeCount > modifierSet.getAllModifiers().length )
{
ModifiersSet tempSet = new ModifiersSet();
// More children than modifiers - must have dups
for( int i = 0; i < modifierNodeCount; ++i )
{
IASNode node = mcn.getChild(i);
if( node instanceof ModifierNode )
{
ModifierNode modNode = (ModifierNode)node;
if( tempSet.hasModifier(modNode.getModifier()) )
{
currentScope.addProblem(new DuplicateAttributeProblem(modNode, modNode.getModifierString()));
}
tempSet.addModifier(modNode);
}
}
}
}
}
/**
* Looks for namespace prefixes on definitions inside of functions.
* Example:
*
* 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