Skip to content

Commit 3c55d8b

Browse files
dcharkescommit-bot@chromium.org
authored andcommitted
[analyzer/ffi] Support packed Structss
This CL does not add packed structs to `dart:ffi` yet, only to the mock SDK in the analyzer for testing the analyzer. This means we can review and land it separately. Bug: #38158 Split off https://dart-review.googlesource.com/c/sdk/+/186143. Change-Id: I9c9ac9154e3dec0e05242f57cf7dddf8406df5fb Cq-Include-Trybots: luci.dart.try:analyzer-analysis-server-linux-try,analyzer-linux-release-try,analyzer-nnbd-linux-release-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/191700 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Daco Harkes <[email protected]>
1 parent bed0589 commit 3c55d8b

File tree

8 files changed

+360
-0
lines changed

8 files changed

+360
-0
lines changed

pkg/analyzer/lib/error/error.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,9 @@ const List<ErrorCode> errorCodeValues = [
476476
FfiCode.NON_CONSTANT_TYPE_ARGUMENT,
477477
FfiCode.NON_NATIVE_FUNCTION_TYPE_ARGUMENT_TO_POINTER,
478478
FfiCode.NON_SIZED_TYPE_ARGUMENT,
479+
FfiCode.PACKED_ANNOTATION,
480+
FfiCode.PACKED_ANNOTATION_ALIGNMENT,
481+
FfiCode.PACKED_NESTING_NON_PACKED,
479482
FfiCode.SIZE_ANNOTATION_DIMENSIONS,
480483
FfiCode.SUBTYPE_OF_FFI_CLASS_IN_EXTENDS,
481484
FfiCode.SUBTYPE_OF_FFI_CLASS_IN_IMPLEMENTS,

pkg/analyzer/lib/src/dart/error/ffi_code.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,35 @@ class FfiCode extends AnalyzerErrorCode {
206206
correction: "Try using a native integer, 'Float', 'Double', 'Pointer', "
207207
"or subtype of 'Struct'.");
208208

209+
/**
210+
* No parameters.
211+
*/
212+
static const FfiCode PACKED_ANNOTATION = FfiCode(
213+
name: 'PACKED_ANNOTATION',
214+
message: "Structs must have at most one 'Packed' annotation.",
215+
correction: "Try removing extra 'Packed' annotations.");
216+
217+
/**
218+
* No parameters.
219+
*/
220+
static const FfiCode PACKED_ANNOTATION_ALIGNMENT = FfiCode(
221+
name: 'PACKED_ANNOTATION_ALIGNMENT',
222+
message: "Only packing to 1, 2, 4, 8, and 16 bytes is supported.",
223+
correction:
224+
"Try changing the 'Packed' annotation alignment to 1, 2, 4, 8, or 16.");
225+
226+
/**
227+
* Parameters:
228+
* 0: the name of the outer struct
229+
* 1: the name of the struct being nested
230+
*/
231+
static const FfiCode PACKED_NESTING_NON_PACKED = FfiCode(
232+
name: 'PACKED_NESTING_NON_PACKED',
233+
message:
234+
"Nesting the non-packed or less tightly packed struct '{0}' in a packed struct '{1}' is not supported.",
235+
correction:
236+
"Try packing the nested struct or packing the nested struct more tightly.");
237+
209238
/**
210239
* No parameters.
211240
*/

pkg/analyzer/lib/src/generated/ffi_verifier.dart

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,16 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
5151
/// `Struct`.
5252
bool inStruct = false;
5353

54+
/// Subclass of `Struct` we are currently visiting, or `null`.
55+
ClassDeclaration? struct;
56+
5457
/// Initialize a newly created verifier.
5558
FfiVerifier(this.typeSystem, this._errorReporter);
5659

5760
@override
5861
void visitClassDeclaration(ClassDeclaration node) {
5962
inStruct = false;
63+
struct = null;
6064
// Only the Allocator, Opaque and Struct class may be extended.
6165
var extendsClause = node.extendsClause;
6266
if (extendsClause != null) {
@@ -66,10 +70,12 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
6670
final className = ffiClass.name;
6771
if (className == _structClassName) {
6872
inStruct = true;
73+
struct = node;
6974
if (node.declaredElement!.isEmptyStruct) {
7075
_errorReporter
7176
.reportErrorForNode(FfiCode.EMPTY_STRUCT, node, [node.name]);
7277
}
78+
_validatePackedAnnotation(node.metadata);
7379
} else if (className != _allocatorClassName &&
7480
className != _opaqueClassName) {
7581
_errorReporter.reportErrorForNode(
@@ -561,12 +567,20 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
561567
}
562568
final arrayDimensions = declaredType.arrayDimensions;
563569
_validateSizeOfAnnotation(fieldType, annotations, arrayDimensions);
570+
final arrayElement = declaredType.arrayElementType;
571+
if (arrayElement.isStructSubtype) {
572+
final elementClass = (arrayElement as InterfaceType).element;
573+
_validatePackingNesting(struct!.declaredElement!, elementClass,
574+
errorNode: fieldType);
575+
}
564576
} else if (declaredType.isStructSubtype) {
565577
final clazz = (declaredType as InterfaceType).element;
566578
if (clazz.isEmptyStruct) {
567579
_errorReporter
568580
.reportErrorForNode(FfiCode.EMPTY_STRUCT, node, [clazz.name]);
569581
}
582+
_validatePackingNesting(struct!.declaredElement!, clazz,
583+
errorNode: fieldType);
570584
} else {
571585
_errorReporter.reportErrorForNode(FfiCode.INVALID_FIELD_TYPE_IN_STRUCT,
572586
fieldType, [fieldType.toSource()]);
@@ -664,6 +678,54 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
664678
}
665679
}
666680

681+
/// Validate that the [annotations] include at most one packed annotation.
682+
void _validatePackedAnnotation(NodeList<Annotation> annotations) {
683+
final ffiPackedAnnotations =
684+
annotations.where((annotation) => annotation.isPacked).toList();
685+
686+
if (ffiPackedAnnotations.isEmpty) {
687+
return;
688+
}
689+
690+
if (ffiPackedAnnotations.length > 1) {
691+
final extraAnnotations = ffiPackedAnnotations.skip(1);
692+
for (final annotation in extraAnnotations) {
693+
_errorReporter.reportErrorForNode(
694+
FfiCode.PACKED_ANNOTATION, annotation);
695+
}
696+
}
697+
698+
// Check number of dimensions.
699+
final annotation = ffiPackedAnnotations.first;
700+
final value = annotation.elementAnnotation?.packedMemberAlignment;
701+
if (![1, 2, 4, 8, 16].contains(value)) {
702+
_errorReporter.reportErrorForNode(
703+
FfiCode.PACKED_ANNOTATION_ALIGNMENT, annotation);
704+
}
705+
}
706+
707+
void _validatePackingNesting(ClassElement outer, ClassElement nested,
708+
{required TypeAnnotation errorNode}) {
709+
final outerPacking = outer.structPacking;
710+
if (outerPacking == null) {
711+
// No packing for outer class, so we're done.
712+
return;
713+
}
714+
bool error = false;
715+
final nestedPacking = nested.structPacking;
716+
if (nestedPacking == null) {
717+
// The outer struct packs, but the nested struct does not.
718+
error = true;
719+
} else if (outerPacking < nestedPacking) {
720+
// The outer struct packs tighter than the nested struct.
721+
error = true;
722+
}
723+
if (error) {
724+
_errorReporter.reportErrorForNode(FfiCode.PACKED_NESTING_NON_PACKED,
725+
errorNode, [nested.name, outer.name]);
726+
}
727+
}
728+
667729
void _validateRefIndexed(IndexExpression node) {
668730
var targetType = node.realTarget.staticType;
669731
if (!_isValidFfiNativeType(targetType,
@@ -774,6 +836,30 @@ enum _PrimitiveDartType {
774836
none,
775837
}
776838

839+
extension on Annotation {
840+
bool get isPacked {
841+
final element = this.element;
842+
return element is ConstructorElement &&
843+
element.ffiClass != null &&
844+
element.enclosingElement.name == 'Packed';
845+
}
846+
}
847+
848+
extension on ElementAnnotation {
849+
bool get isPacked {
850+
final element = this.element;
851+
return element is ConstructorElement &&
852+
element.ffiClass != null &&
853+
element.enclosingElement.name == 'Packed';
854+
}
855+
856+
int get packedMemberAlignment {
857+
assert(isPacked);
858+
final value = computeConstantValue();
859+
return value!.getField('memberAlignment')!.toIntValue()!;
860+
}
861+
}
862+
777863
extension on Element? {
778864
/// Return `true` if this represents the extension `AllocatorAlloc`.
779865
bool get isAllocatorExtension {
@@ -855,6 +941,17 @@ extension on ClassElement {
855941
bool get isFfiClass {
856942
return library.name == FfiVerifier._dartFfiLibraryName;
857943
}
944+
945+
int? get structPacking {
946+
final packedAnnotations =
947+
metadata.where((annotation) => annotation.isPacked);
948+
949+
if (packedAnnotations.isEmpty) {
950+
return null;
951+
}
952+
953+
return packedAnnotations.first.packedMemberAlignment;
954+
}
858955
}
859956

860957
extension on ExtensionElement {
@@ -886,6 +983,16 @@ extension on DartType {
886983
return dimensions;
887984
}
888985

986+
DartType get arrayElementType {
987+
DartType iterator = this;
988+
while (iterator is InterfaceType &&
989+
iterator.element.name == FfiVerifier._arrayClassName &&
990+
iterator.element.isFfiClass) {
991+
iterator = iterator.typeArguments.single;
992+
}
993+
return iterator;
994+
}
995+
889996
bool get isPointer {
890997
final self = this;
891998
return self is InterfaceType && self.element.isPointer;

pkg/analyzer/lib/src/test_utilities/mock_sdk.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,12 @@ extension NativeFunctionPointer<NF extends Function>
677677
678678
class Struct extends NativeType {}
679679
680+
class Packed {
681+
final int memberAlignment;
682+
683+
const Packed(this.memberAlignment);
684+
}
685+
680686
abstract class DynamicLibrary {}
681687
682688
extension DynamicLibraryExtension on DynamicLibrary {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analyzer/src/dart/error/ffi_code.dart';
6+
import 'package:test_reflective_loader/test_reflective_loader.dart';
7+
8+
import '../dart/resolution/context_collection_resolution.dart';
9+
10+
main() {
11+
defineReflectiveSuite(() {
12+
defineReflectiveTests(PackedAnnotationAlignment);
13+
});
14+
}
15+
16+
@reflectiveTest
17+
class PackedAnnotationAlignment extends PubPackageResolutionTest {
18+
test_error() async {
19+
await assertErrorsInCode(r'''
20+
import 'dart:ffi';
21+
22+
@Packed(3)
23+
class C extends Struct {
24+
external Pointer<Uint8> notEmpty;
25+
}
26+
''', [
27+
error(FfiCode.PACKED_ANNOTATION_ALIGNMENT, 20, 10),
28+
]);
29+
}
30+
31+
test_no_error() async {
32+
await assertNoErrorsInCode(r'''
33+
import 'dart:ffi';
34+
35+
@Packed(1)
36+
class C extends Struct {
37+
external Pointer<Uint8> notEmpty;
38+
}
39+
''');
40+
}
41+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analyzer/src/dart/error/ffi_code.dart';
6+
import 'package:test_reflective_loader/test_reflective_loader.dart';
7+
8+
import '../dart/resolution/context_collection_resolution.dart';
9+
10+
main() {
11+
defineReflectiveSuite(() {
12+
defineReflectiveTests(PackedAnnotation);
13+
});
14+
}
15+
16+
@reflectiveTest
17+
class PackedAnnotation extends PubPackageResolutionTest {
18+
test_error() async {
19+
await assertErrorsInCode(r'''
20+
import 'dart:ffi';
21+
22+
@Packed(1)
23+
@Packed(1)
24+
class C extends Struct {
25+
external Pointer<Uint8> notEmpty;
26+
}
27+
''', [
28+
error(FfiCode.PACKED_ANNOTATION, 31, 10),
29+
]);
30+
}
31+
32+
test_no_error_1() async {
33+
await assertNoErrorsInCode(r'''
34+
import 'dart:ffi';
35+
36+
class C extends Struct {
37+
external Pointer<Uint8> notEmpty;
38+
}
39+
''');
40+
}
41+
42+
test_no_error_2() async {
43+
await assertNoErrorsInCode(r'''
44+
import 'dart:ffi';
45+
46+
@Packed(1)
47+
class C extends Struct {
48+
external Pointer<Uint8> notEmpty;
49+
}
50+
''');
51+
}
52+
}

0 commit comments

Comments
 (0)