blob: b562caf78c56c6e31a03582c1aa29a29b1c3fc08 [file]
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
import 'package:analyzer/src/dart/error/ffi_code.dart';
/// A visitor used to find problems with the way the `dart:ffi` APIs are being
/// used. See 'pkg/vm/lib/transformations/ffi_checks.md' for the specification
/// of the desired hints.
class FfiVerifier extends RecursiveAstVisitor<void> {
/// The inheritance manager used to find overridden methods.
final InheritanceManager3 _inheritance;
/// The error reporter used to report errors.
final ErrorReporter _errorReporter;
/// A flag indicating whether we are currently visiting inside a subclass of
/// `Struct`.
bool inStruct = false;
/// Initialize a newly created verifier.
FfiVerifier(this._inheritance, this._errorReporter);
@override
void visitClassDeclaration(ClassDeclaration node) {
inStruct = false;
// Only the Struct class may be extended.
ExtendsClause extendsClause = node.extendsClause;
if (extendsClause != null) {
final TypeName superclass = extendsClause.superclass;
if (_isDartFfiClass(superclass)) {
if (superclass.name.staticElement.name == 'Struct') {
inStruct = true;
NodeList<TypeAnnotation> typeArguments =
superclass.typeArguments?.arguments;
if (typeArguments == null) {
_errorReporter.reportTypeErrorForNode(
FfiCode.MISSING_TYPE_ARGUMENT_FOR_STRUCT,
superclass.name,
[node.name.name]);
} else if (typeArguments.length == 1) {
if (typeArguments[0].type.element != node.declaredElement) {
// TODO(brianwilkerson) If the type argument is not a subclass of
// Struct, then we'll get two diagnostics generated. We should
// test for that case here and suppress the hint.
_errorReporter.reportTypeErrorForNode(
FfiCode.INVALID_TYPE_ARGUMENT_FOR_STRUCT,
typeArguments[0],
[node.name.name]);
}
}
} else {
_errorReporter.reportTypeErrorForNode(
FfiCode.SUBTYPE_OF_FFI_CLASS_IN_EXTENDS,
superclass.name,
[node.name.name, superclass.name.name]);
}
} else if (_isSubtypeOfStruct(superclass)) {
_errorReporter.reportTypeErrorForNode(
FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_EXTENDS,
superclass,
[node.name.name, superclass.name.name]);
}
}
// No classes from the FFI may be explicitly implemented.
void checkSupertype(TypeName typename, FfiCode subtypeOfFfiCode,
FfiCode subtypeOfStructCode) {
if (_isDartFfiClass(typename)) {
_errorReporter.reportTypeErrorForNode(
subtypeOfFfiCode, typename, [node.name, typename.name]);
} else if (_isSubtypeOfStruct(typename)) {
_errorReporter.reportTypeErrorForNode(
subtypeOfStructCode, typename, [node.name, typename.name]);
}
}
ImplementsClause implementsClause = node.implementsClause;
if (implementsClause != null) {
for (TypeName type in implementsClause.interfaces) {
checkSupertype(type, FfiCode.SUBTYPE_OF_FFI_CLASS_IN_IMPLEMENTS,
FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_IMPLEMENTS);
}
}
WithClause withClause = node.withClause;
if (withClause != null) {
for (TypeName type in withClause.mixinTypes) {
checkSupertype(type, FfiCode.SUBTYPE_OF_FFI_CLASS_IN_WITH,
FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_WITH);
}
}
if (inStruct && node.declaredElement.typeParameters.isNotEmpty) {
_errorReporter.reportErrorForNode(
FfiCode.GENERIC_STRUCT_SUBCLASS, node.name, [node.name]);
}
super.visitClassDeclaration(node);
}
@override
void visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
if (inStruct) {
_errorReporter.reportErrorForNode(
FfiCode.FIELD_INITIALIZER_IN_STRUCT, node);
}
super.visitConstructorFieldInitializer(node);
}
@override
void visitFieldDeclaration(FieldDeclaration node) {
if (inStruct) {
_validateFieldsInStruct(node);
}
super.visitFieldDeclaration(node);
}
@override
void visitMethodInvocation(MethodInvocation node) {
Element element = node.methodName.staticElement;
if (element is MethodElement &&
element.name == 'asFunction' &&
_isPointer(element.enclosingElement)) {
element.type.typeArguments;
}
super.visitMethodInvocation(node);
}
/// Return `true` if the [typeName] is the name of a type from `dart:ffi`.
bool _isDartFfiClass(TypeName typeName) =>
_isDartFfiElement(typeName.name.staticElement);
/// Return `true` if the [element] is a class element from `dart:ffi`.
bool _isDartFfiElement(Element element) {
if (element is ConstructorElement) {
element = element.enclosingElement;
}
return element is ClassElement && element.library.name == 'dart.ffi';
}
/// Return `true` if the given [element] represents the class `Pointer`.
bool _isPointer(ClassElement element) =>
element.name == 'Pointer' && element.library.name == 'dart.ffi';
/// Return `true` if the [typeName] represents a subtype of `Struct`.
bool _isSubtypeOfStruct(TypeName typeName) {
Element superType = typeName.name.staticElement;
if (superType is ClassElement) {
bool isStruct(InterfaceType type) {
return type != null &&
type.element.name == 'Struct' &&
type.element.library.name == 'dart.ffi';
}
return isStruct(superType.supertype) ||
superType.interfaces.any(isStruct) ||
superType.mixins.any(isStruct);
}
return false;
}
/// Return `true` if the [foundType] matches one of the [requiredTypes].
bool _matchesAllowed(_FoundType foundType, _RequiredTypes requiredTypes) {
return requiredTypes == _RequiredTypes.any ||
(requiredTypes == _RequiredTypes.int && foundType == _FoundType.int) ||
(requiredTypes == _RequiredTypes.double &&
foundType == _FoundType.double);
}
/// Return an indication of the Dart type associated with the [annotation].
_FoundType _typeForAnnotation(Annotation annotation) {
Element element = annotation.element;
if (element is ConstructorElement) {
String name = element.enclosingElement.name;
if ([
'Int8',
'Int16',
'Int32',
'Int64',
'Uint8',
'Uint16',
'Uint32',
'Uint64'
].contains(name)) {
return _FoundType.int;
} else if (['Double', 'Float'].contains(name)) {
return _FoundType.double;
}
}
return _FoundType.none;
}
/// Validate that the [annotations] include exactly one annotation that
/// satisfies the [requiredTypes]. If an error is produced that cannot be
/// associated with an annotation, associate it with the [errorNode].
void _validateAnnotations(AstNode errorNode, NodeList<Annotation> annotations,
_RequiredTypes requiredTypes) {
bool requiredFound = false;
List<Annotation> extraAnnotations = [];
for (Annotation annotation in annotations) {
if (_isDartFfiElement(annotation.element)) {
if (requiredFound) {
extraAnnotations.add(annotation);
} else {
_FoundType foundType = _typeForAnnotation(annotation);
if (foundType == _FoundType.none) {
// This should never happen because `_typeForAnnotation` should
// handle all of the classes from dart:ffi that can be used as an
// annotation.
} else if (_matchesAllowed(foundType, requiredTypes)) {
requiredFound = true;
} else {
extraAnnotations.add(annotation);
}
}
}
}
if (extraAnnotations.isNotEmpty) {
if (!requiredFound) {
Annotation invalidAnnotation = extraAnnotations.removeAt(0);
_errorReporter.reportErrorForNode(
FfiCode.MISMATCHED_ANNOTATION_ON_STRUCT_FIELD, invalidAnnotation);
}
for (Annotation extraAnnotation in extraAnnotations) {
_errorReporter.reportErrorForNode(
FfiCode.EXTRA_ANNOTATION_ON_STRUCT_FIELD, extraAnnotation);
}
} else if (!requiredFound) {
_errorReporter.reportErrorForNode(
FfiCode.MISSING_ANNOTATION_ON_STRUCT_FIELD, errorNode);
}
}
/// Validate that the fields declared by the given [node] meet the
/// requirements for fields within a struct class.
void _validateFieldsInStruct(FieldDeclaration node) {
VariableDeclarationList fields = node.fields;
NodeList<Annotation> annotations = node.metadata;
TypeAnnotation fieldType = fields.type;
if (fieldType == null) {
_errorReporter.reportErrorForNode(
FfiCode.MISSING_FIELD_TYPE_IN_STRUCT, fields.variables[0].name);
} else {
DartType declaredType = fieldType.type;
if (declaredType.isDartCoreInt) {
_validateAnnotations(fieldType, annotations, _RequiredTypes.int);
} else if (declaredType.isDartCoreDouble) {
_validateAnnotations(fieldType, annotations, _RequiredTypes.double);
} else if (_isPointer(declaredType.element)) {
_validateNoAnnotations(annotations);
} else {
_errorReporter.reportErrorForNode(FfiCode.INVALID_FIELD_TYPE_IN_STRUCT,
fieldType, [fieldType.toSource()]);
}
}
for (VariableDeclaration field in fields.variables) {
if (field.initializer != null) {
_errorReporter.reportErrorForNode(
FfiCode.FIELD_IN_STRUCT_WITH_INITIALIZER, field.name);
}
}
}
/// Validate that none of the [annotations] are from `dart:ffi`.
void _validateNoAnnotations(NodeList<Annotation> annotations) {
for (Annotation annotation in annotations) {
if (_isDartFfiElement(annotation.element)) {
_errorReporter.reportErrorForNode(
FfiCode.ANNOTATION_ON_POINTER_FIELD, annotation);
}
}
}
}
/// An enumeration of the type corresponding to an annotation.
enum _FoundType {
double,
int,
none,
}
/// An enumeration of the type of annotation that is required to be on a field.
enum _RequiredTypes {
any,
double,
int,
}