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