blob: ed7c825d353351c8fc1dcedac216492d0d14aeb9 [file] [log] [blame]
/*
* Copyright (c) 2013, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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 com.google.dart.engine.internal.verifier;
import com.google.dart.engine.ast.Annotation;
import com.google.dart.engine.ast.ArgumentList;
import com.google.dart.engine.ast.ClassDeclaration;
import com.google.dart.engine.ast.ClassMember;
import com.google.dart.engine.ast.ConstructorDeclaration;
import com.google.dart.engine.ast.ConstructorFieldInitializer;
import com.google.dart.engine.ast.ConstructorInitializer;
import com.google.dart.engine.ast.DefaultFormalParameter;
import com.google.dart.engine.ast.Expression;
import com.google.dart.engine.ast.FieldDeclaration;
import com.google.dart.engine.ast.FormalParameter;
import com.google.dart.engine.ast.FormalParameterList;
import com.google.dart.engine.ast.FunctionExpression;
import com.google.dart.engine.ast.InstanceCreationExpression;
import com.google.dart.engine.ast.ListLiteral;
import com.google.dart.engine.ast.MapLiteral;
import com.google.dart.engine.ast.MapLiteralEntry;
import com.google.dart.engine.ast.MethodDeclaration;
import com.google.dart.engine.ast.NamedExpression;
import com.google.dart.engine.ast.NodeList;
import com.google.dart.engine.ast.RedirectingConstructorInvocation;
import com.google.dart.engine.ast.SimpleIdentifier;
import com.google.dart.engine.ast.SuperConstructorInvocation;
import com.google.dart.engine.ast.SwitchCase;
import com.google.dart.engine.ast.SwitchMember;
import com.google.dart.engine.ast.SwitchStatement;
import com.google.dart.engine.ast.VariableDeclaration;
import com.google.dart.engine.ast.visitor.RecursiveAstVisitor;
import com.google.dart.engine.constant.DartObject;
import com.google.dart.engine.element.ClassElement;
import com.google.dart.engine.element.ConstructorElement;
import com.google.dart.engine.element.Element;
import com.google.dart.engine.element.LibraryElement;
import com.google.dart.engine.element.MethodElement;
import com.google.dart.engine.element.ParameterElement;
import com.google.dart.engine.error.AnalysisError;
import com.google.dart.engine.error.BooleanErrorListener;
import com.google.dart.engine.error.CompileTimeErrorCode;
import com.google.dart.engine.error.ErrorCode;
import com.google.dart.engine.error.StaticWarningCode;
import com.google.dart.engine.internal.constant.ConstantVisitor;
import com.google.dart.engine.internal.constant.EvaluationResultImpl;
import com.google.dart.engine.internal.context.RecordingErrorListener;
import com.google.dart.engine.internal.element.VariableElementImpl;
import com.google.dart.engine.internal.error.ErrorReporter;
import com.google.dart.engine.internal.object.BoolState;
import com.google.dart.engine.internal.object.DartObjectImpl;
import com.google.dart.engine.internal.object.DoubleState;
import com.google.dart.engine.internal.object.DynamicState;
import com.google.dart.engine.internal.object.GenericState;
import com.google.dart.engine.internal.object.IntState;
import com.google.dart.engine.internal.object.NumState;
import com.google.dart.engine.internal.object.StringState;
import com.google.dart.engine.internal.resolver.TypeProvider;
import com.google.dart.engine.type.InterfaceType;
import com.google.dart.engine.type.Type;
import com.google.dart.engine.utilities.ast.DeferredLibraryReferenceDetector;
import java.util.ArrayList;
import java.util.HashSet;
/**
* Instances of the class {@code ConstantVerifier} traverse an AST structure looking for additional
* errors and warnings not covered by the parser and resolver. In particular, it looks for errors
* and warnings related to constant expressions.
*
* @coverage dart.engine.resolver
*/
public class ConstantVerifier extends RecursiveAstVisitor<Void> {
/**
* The error reporter by which errors will be reported.
*/
private ErrorReporter errorReporter;
/**
* The type provider used to access the known types.
*/
private TypeProvider typeProvider;
/**
* The type representing the type 'bool'.
*/
private InterfaceType boolType;
/**
* The type representing the type 'int'.
*/
private InterfaceType intType;
/**
* The type representing the type 'num'.
*/
private InterfaceType numType;
/**
* The type representing the type 'string'.
*/
private InterfaceType stringType;
/**
* The current library that is being analyzed.
*/
private LibraryElement currentLibrary;
/**
* Initialize a newly created constant verifier.
*
* @param errorReporter the error reporter by which errors will be reported
*/
public ConstantVerifier(ErrorReporter errorReporter, LibraryElement currentLibrary,
TypeProvider typeProvider) {
this.errorReporter = errorReporter;
this.currentLibrary = currentLibrary;
this.typeProvider = typeProvider;
this.boolType = typeProvider.getBoolType();
this.intType = typeProvider.getIntType();
this.numType = typeProvider.getNumType();
this.stringType = typeProvider.getStringType();
}
@Override
public Void visitAnnotation(Annotation node) {
super.visitAnnotation(node);
// check annotation creation
Element element = node.getElement();
if (element instanceof ConstructorElement) {
ConstructorElement constructorElement = (ConstructorElement) element;
// should 'const' constructor
if (!constructorElement.isConst()) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.NON_CONSTANT_ANNOTATION_CONSTRUCTOR,
node);
return null;
}
// should have arguments
ArgumentList argumentList = node.getArguments();
if (argumentList == null) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.NO_ANNOTATION_CONSTRUCTOR_ARGUMENTS,
node);
return null;
}
// arguments should be constants
validateConstantArguments(argumentList);
}
return null;
}
@Override
public Void visitConstructorDeclaration(ConstructorDeclaration node) {
if (node.getConstKeyword() != null) {
validateConstructorInitializers(node);
validateFieldInitializers((ClassDeclaration) node.getParent(), node);
}
validateDefaultValues(node.getParameters());
return super.visitConstructorDeclaration(node);
}
@Override
public Void visitFunctionExpression(FunctionExpression node) {
super.visitFunctionExpression(node);
validateDefaultValues(node.getParameters());
return null;
}
@Override
public Void visitInstanceCreationExpression(InstanceCreationExpression node) {
if (node.isConst()) {
EvaluationResultImpl evaluationResult = node.getEvaluationResult();
// Note: evaluationResult might be null if there are circular references among constants.
if (evaluationResult != null) {
reportErrors(evaluationResult.getErrors(), null);
}
}
validateInstanceCreationArguments(node);
return super.visitInstanceCreationExpression(node);
}
@Override
public Void visitListLiteral(ListLiteral node) {
super.visitListLiteral(node);
if (node.getConstKeyword() != null) {
DartObjectImpl result;
for (Expression element : node.getElements()) {
result = validate(element, CompileTimeErrorCode.NON_CONSTANT_LIST_ELEMENT);
if (result != null) {
reportErrorIfFromDeferredLibrary(
element,
CompileTimeErrorCode.NON_CONSTANT_LIST_ELEMENT_FROM_DEFERRED_LIBRARY);
}
}
}
return null;
}
@Override
public Void visitMapLiteral(MapLiteral node) {
super.visitMapLiteral(node);
boolean isConst = node.getConstKeyword() != null;
boolean reportEqualKeys = true;
HashSet<DartObject> keys = new HashSet<DartObject>();
ArrayList<Expression> invalidKeys = new ArrayList<Expression>();
for (MapLiteralEntry entry : node.getEntries()) {
Expression key = entry.getKey();
if (isConst) {
DartObjectImpl keyResult = validate(key, CompileTimeErrorCode.NON_CONSTANT_MAP_KEY);
Expression valueExpression = entry.getValue();
DartObjectImpl valueResult = validate(
valueExpression,
CompileTimeErrorCode.NON_CONSTANT_MAP_VALUE);
if (valueResult != null) {
reportErrorIfFromDeferredLibrary(
valueExpression,
CompileTimeErrorCode.NON_CONSTANT_MAP_VALUE_FROM_DEFERRED_LIBRARY);
}
if (keyResult != null) {
reportErrorIfFromDeferredLibrary(
key,
CompileTimeErrorCode.NON_CONSTANT_MAP_KEY_FROM_DEFERRED_LIBRARY);
if (keys.contains(keyResult)) {
invalidKeys.add(key);
} else {
keys.add(keyResult);
}
Type type = keyResult.getType();
if (implementsEqualsWhenNotAllowed(type)) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.CONST_MAP_KEY_EXPRESSION_TYPE_IMPLEMENTS_EQUALS,
key,
type.getDisplayName());
}
}
} else {
// Note: we throw the errors away because this isn't actually a const.
BooleanErrorListener errorListener = new BooleanErrorListener();
ErrorReporter subErrorReporter = new ErrorReporter(errorListener, errorReporter.getSource());
DartObjectImpl result = key.accept(new ConstantVisitor(typeProvider, subErrorReporter));
if (result != null) {
if (keys.contains(result)) {
invalidKeys.add(key);
} else {
keys.add(result);
}
} else {
reportEqualKeys = false;
}
}
}
if (reportEqualKeys) {
for (Expression key : invalidKeys) {
errorReporter.reportErrorForNode(StaticWarningCode.EQUAL_KEYS_IN_MAP, key);
}
}
return null;
}
@Override
public Void visitMethodDeclaration(MethodDeclaration node) {
super.visitMethodDeclaration(node);
validateDefaultValues(node.getParameters());
return null;
}
@Override
public Void visitSwitchStatement(SwitchStatement node) {
// TODO(paulberry): to minimize error messages, it would be nice to
// compare all types with the most popular type rather than the first
// type.
NodeList<SwitchMember> switchMembers = node.getMembers();
boolean foundError = false;
Type firstType = null;
for (SwitchMember switchMember : switchMembers) {
if (switchMember instanceof SwitchCase) {
SwitchCase switchCase = (SwitchCase) switchMember;
Expression expression = switchCase.getExpression();
DartObjectImpl caseResult = validate(
expression,
CompileTimeErrorCode.NON_CONSTANT_CASE_EXPRESSION);
if (caseResult != null) {
reportErrorIfFromDeferredLibrary(
expression,
CompileTimeErrorCode.NON_CONSTANT_CASE_EXPRESSION_FROM_DEFERRED_LIBRARY);
DartObject value = caseResult;
if (firstType == null) {
firstType = value.getType();
} else {
Type nType = value.getType();
if (!firstType.equals(nType)) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.INCONSISTENT_CASE_EXPRESSION_TYPES,
expression,
expression.toSource(),
firstType.getDisplayName());
foundError = true;
}
}
}
}
}
if (!foundError) {
checkForCaseExpressionTypeImplementsEquals(node, firstType);
}
return super.visitSwitchStatement(node);
}
@Override
public Void visitVariableDeclaration(VariableDeclaration node) {
super.visitVariableDeclaration(node);
Expression initializer = node.getInitializer();
if (initializer != null && node.isConst()) {
VariableElementImpl element = (VariableElementImpl) node.getElement();
EvaluationResultImpl result = element.getEvaluationResult();
if (result == null) {
//
// Normally we don't need to visit const variable declarations because we have already
// computed their values. But if we missed it for some reason, this gives us a second
// chance.
//
result = new EvaluationResultImpl(validate(
initializer,
CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE));
element.setEvaluationResult(result);
return null;
} else if (result.getValue() == null) {
// TODO(paulberry): report errors even if valid result.
reportErrors(
result.getErrors(),
CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE);
return null;
}
reportErrorIfFromDeferredLibrary(
initializer,
CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE_FROM_DEFERRED_LIBRARY);
}
return null;
}
/**
* This verifies that the passed switch statement does not have a case expression with the
* operator '==' overridden.
*
* @param node the switch statement to evaluate
* @param type the common type of all 'case' expressions
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#CASE_EXPRESSION_TYPE_IMPLEMENTS_EQUALS
*/
private boolean checkForCaseExpressionTypeImplementsEquals(SwitchStatement node, Type type) {
if (!implementsEqualsWhenNotAllowed(type)) {
return false;
}
// report error
errorReporter.reportErrorForToken(
CompileTimeErrorCode.CASE_EXPRESSION_TYPE_IMPLEMENTS_EQUALS,
node.getKeyword(),
type.getDisplayName());
return true;
}
/**
* @return {@code true} if given {@link Type} implements operator <i>==</i>, and it is not
* <i>int</i> or <i>String</i>.
*/
private boolean implementsEqualsWhenNotAllowed(Type type) {
// ignore int or String
if (type == null || type.equals(intType) || type.equals(typeProvider.getStringType())) {
return false;
} else if (type.equals(typeProvider.getDoubleType())) {
return true;
}
// prepare ClassElement
Element element = type.getElement();
if (!(element instanceof ClassElement)) {
return false;
}
ClassElement classElement = (ClassElement) element;
// lookup for ==
MethodElement method = classElement.lookUpConcreteMethod("==", currentLibrary);
if (method == null || method.getEnclosingElement().getType().isObject()) {
return false;
}
// there is == that we don't like
return true;
}
/**
* Given some computed {@link Expression}, this method generates the passed {@link ErrorCode} on
* the node if its' value consists of information from a deferred library.
*
* @param expression the expression to be tested for a deferred library reference
* @param errorCode the error code to be used if the expression is or consists of a reference to a
* deferred library
*/
private void reportErrorIfFromDeferredLibrary(Expression expression, ErrorCode errorCode) {
DeferredLibraryReferenceDetector referenceDetector = new DeferredLibraryReferenceDetector();
expression.accept(referenceDetector);
if (referenceDetector.getResult()) {
errorReporter.reportErrorForNode(errorCode, expression);
}
}
/**
* Report any errors in the given list. Except for special cases, use the given error code rather
* than the one reported in the error.
*
* @param errors the errors that need to be reported
* @param errorCode the error code to be used
*/
private void reportErrors(AnalysisError[] errors, ErrorCode errorCode) {
for (AnalysisError data : errors) {
ErrorCode dataErrorCode = data.getErrorCode();
if (dataErrorCode == CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION
|| dataErrorCode == CompileTimeErrorCode.CONST_EVAL_THROWS_IDBZE
|| dataErrorCode == CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING
|| dataErrorCode == CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL
|| dataErrorCode == CompileTimeErrorCode.CONST_EVAL_TYPE_INT
|| dataErrorCode == CompileTimeErrorCode.CONST_EVAL_TYPE_NUM) {
errorReporter.reportError(data);
} else if (errorCode != null) {
errorReporter.reportError(new AnalysisError(
data.getSource(),
data.getOffset(),
data.getLength(),
errorCode));
}
}
}
/**
* Validate that the given expression is a compile time constant. Return the value of the compile
* time constant, or {@code null} if the expression is not a compile time constant.
*
* @param expression the expression to be validated
* @param errorCode the error code to be used if the expression is not a compile time constant
* @return the value of the compile time constant
*/
private DartObjectImpl validate(Expression expression, ErrorCode errorCode) {
RecordingErrorListener errorListener = new RecordingErrorListener();
ErrorReporter subErrorReporter = new ErrorReporter(errorListener, errorReporter.getSource());
DartObjectImpl result = expression.accept(new ConstantVisitor(typeProvider, subErrorReporter));
reportErrors(errorListener.getErrors(), errorCode);
return result;
}
/**
* Validate that if the passed arguments are constant expressions.
*
* @param argumentList the argument list to evaluate
*/
private void validateConstantArguments(ArgumentList argumentList) {
for (Expression argument : argumentList.getArguments()) {
if (argument instanceof NamedExpression) {
argument = ((NamedExpression) argument).getExpression();
}
validate(argument, CompileTimeErrorCode.CONST_WITH_NON_CONSTANT_ARGUMENT);
}
}
/**
* Validates that the expressions of the given initializers (of a constant constructor) are all
* compile time constants.
*
* @param constructor the constant constructor declaration to validate
*/
private void validateConstructorInitializers(ConstructorDeclaration constructor) {
ParameterElement[] parameterElements = constructor.getParameters().getParameterElements();
NodeList<ConstructorInitializer> initializers = constructor.getInitializers();
for (ConstructorInitializer initializer : initializers) {
if (initializer instanceof ConstructorFieldInitializer) {
ConstructorFieldInitializer fieldInitializer = (ConstructorFieldInitializer) initializer;
validateInitializerExpression(parameterElements, fieldInitializer.getExpression());
}
if (initializer instanceof RedirectingConstructorInvocation) {
RedirectingConstructorInvocation invocation = (RedirectingConstructorInvocation) initializer;
validateInitializerInvocationArguments(parameterElements, invocation.getArgumentList());
}
if (initializer instanceof SuperConstructorInvocation) {
SuperConstructorInvocation invocation = (SuperConstructorInvocation) initializer;
validateInitializerInvocationArguments(parameterElements, invocation.getArgumentList());
}
}
}
/**
* Validate that the default value associated with each of the parameters in the given list is a
* compile time constant.
*
* @param parameters the list of parameters to be validated
*/
private void validateDefaultValues(FormalParameterList parameters) {
if (parameters == null) {
return;
}
for (FormalParameter parameter : parameters.getParameters()) {
if (parameter instanceof DefaultFormalParameter) {
DefaultFormalParameter defaultParameter = (DefaultFormalParameter) parameter;
Expression defaultValue = defaultParameter.getDefaultValue();
if (defaultValue != null) {
DartObjectImpl result = validate(
defaultValue,
CompileTimeErrorCode.NON_CONSTANT_DEFAULT_VALUE);
VariableElementImpl element = (VariableElementImpl) parameter.getElement();
element.setEvaluationResult(new EvaluationResultImpl(result));
if (result != null) {
reportErrorIfFromDeferredLibrary(
defaultValue,
CompileTimeErrorCode.NON_CONSTANT_DEFAULT_VALUE_FROM_DEFERRED_LIBRARY);
}
}
}
}
}
/**
* Validates that the expressions of any field initializers in the class declaration are all
* compile time constants. Since this is only required if the class has a constant constructor,
* the error is reported at the constructor site.
*
* @param classDeclaration the class which should be validated
* @param errorSite the site at which errors should be reported.
*/
private void validateFieldInitializers(ClassDeclaration classDeclaration,
ConstructorDeclaration errorSite) {
NodeList<ClassMember> members = classDeclaration.getMembers();
for (ClassMember member : members) {
if (member instanceof FieldDeclaration) {
FieldDeclaration fieldDeclaration = (FieldDeclaration) member;
if (!fieldDeclaration.isStatic()) {
for (VariableDeclaration variableDeclaration : fieldDeclaration.getFields().getVariables()) {
Expression initializer = variableDeclaration.getInitializer();
if (initializer != null) {
// Ignore any errors produced during validation--if the constant can't be eavluated
// we'll just report a single error.
BooleanErrorListener errorListener = new BooleanErrorListener();
ErrorReporter subErrorReporter = new ErrorReporter(
errorListener,
errorReporter.getSource());
DartObjectImpl result = initializer.accept(new ConstantVisitor(
typeProvider,
subErrorReporter));
if (result == null) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.CONST_CONSTRUCTOR_WITH_FIELD_INITIALIZED_BY_NON_CONST,
errorSite,
variableDeclaration.getName().getName());
}
}
}
}
}
}
}
/**
* Validates that the given expression is a compile time constant.
*
* @param parameterElements the elements of parameters of constant constructor, they are
* considered as a valid potentially constant expressions
* @param expression the expression to validate
*/
private void validateInitializerExpression(final ParameterElement[] parameterElements,
Expression expression) {
RecordingErrorListener errorListener = new RecordingErrorListener();
ErrorReporter subErrorReporter = new ErrorReporter(errorListener, errorReporter.getSource());
DartObjectImpl result = expression.accept(new ConstantVisitor(typeProvider, subErrorReporter) {
@Override
public DartObjectImpl visitSimpleIdentifier(SimpleIdentifier node) {
Element element = node.getStaticElement();
for (ParameterElement parameterElement : parameterElements) {
if (parameterElement == element && parameterElement != null) {
Type type = parameterElement.getType();
if (type != null) {
if (type.isDynamic()) {
return new DartObjectImpl(typeProvider.getObjectType(), DynamicState.DYNAMIC_STATE);
} else if (type.isSubtypeOf(boolType)) {
return new DartObjectImpl(typeProvider.getBoolType(), BoolState.UNKNOWN_VALUE);
} else if (type.isSubtypeOf(typeProvider.getDoubleType())) {
return new DartObjectImpl(typeProvider.getDoubleType(), DoubleState.UNKNOWN_VALUE);
} else if (type.isSubtypeOf(intType)) {
return new DartObjectImpl(typeProvider.getIntType(), IntState.UNKNOWN_VALUE);
} else if (type.isSubtypeOf(numType)) {
return new DartObjectImpl(typeProvider.getNumType(), NumState.UNKNOWN_VALUE);
} else if (type.isSubtypeOf(stringType)) {
return new DartObjectImpl(typeProvider.getStringType(), StringState.UNKNOWN_VALUE);
}
//
// We don't test for other types of objects (such as List, Map, Function or Type)
// because there are no operations allowed on such types other than '==' and '!=',
// which means that we don't need to know the type when there is no specific data
// about the state of such objects.
//
}
return new DartObjectImpl(type instanceof InterfaceType ? (InterfaceType) type
: typeProvider.getObjectType(), GenericState.UNKNOWN_VALUE);
}
}
return super.visitSimpleIdentifier(node);
}
});
reportErrors(errorListener.getErrors(), CompileTimeErrorCode.NON_CONSTANT_VALUE_IN_INITIALIZER);
if (result != null) {
reportErrorIfFromDeferredLibrary(
expression,
CompileTimeErrorCode.NON_CONSTANT_VALUE_IN_INITIALIZER_FROM_DEFERRED_LIBRARY);
}
}
/**
* Validates that all of the arguments of a constructor initializer are compile time constants.
*
* @param parameterElements the elements of parameters of constant constructor, they are
* considered as a valid potentially constant expressions
* @param argumentList the argument list to validate
*/
private void validateInitializerInvocationArguments(ParameterElement[] parameterElements,
ArgumentList argumentList) {
if (argumentList == null) {
return;
}
for (Expression argument : argumentList.getArguments()) {
validateInitializerExpression(parameterElements, argument);
}
}
/**
* Validate that if the passed instance creation is 'const' then all its arguments are constant
* expressions.
*
* @param node the instance creation evaluate
*/
private void validateInstanceCreationArguments(InstanceCreationExpression node) {
if (!node.isConst()) {
return;
}
ArgumentList argumentList = node.getArgumentList();
if (argumentList == null) {
return;
}
validateConstantArguments(argumentList);
}
}