Support @FixedSized annotation for structured parcelables
This annotation marks the parcelable as fixed size so it can be
guaranteed to stay the same size.
This can be particularly useful for passing these types over shared
memory between processes that are updated independently. No matter which
version either process is using, the type will have the same size.
Test: atest aidl_unittests
Bug: 142326204
Change-Id: I18240e2832da8578bee077026955686ec90c0e5f
diff --git a/aidl_checkapi.cpp b/aidl_checkapi.cpp
index 9473d46..a0107e4 100644
--- a/aidl_checkapi.cpp
+++ b/aidl_checkapi.cpp
@@ -192,6 +192,12 @@
<< old_fields.size() << " to " << new_fields.size() << ".";
return false;
}
+ if (newer.IsFixedSize() && old_fields.size() != new_fields.size()) {
+ AIDL_ERROR(newer) << "Number of fields in " << older.GetCanonicalName() << " is changed from "
+ << old_fields.size() << " to " << new_fields.size()
+ << ". This is an incompatible change for FixedSize types.";
+ return false;
+ }
bool compatible = true;
for (size_t i = 0; i < old_fields.size(); i++) {
diff --git a/aidl_language.cpp b/aidl_language.cpp
index f356f32..763fb00 100644
--- a/aidl_language.cpp
+++ b/aidl_language.cpp
@@ -133,6 +133,7 @@
{AidlAnnotation::Type::JAVA_PASSTHROUGH, "JavaPassthrough", {{"annotation", "String"}}},
{AidlAnnotation::Type::JAVA_DEBUG, "JavaDebug", {}},
{AidlAnnotation::Type::JAVA_ONLY_IMMUTABLE, "JavaOnlyImmutable", {}},
+ {AidlAnnotation::Type::FIXED_SIZE, "FixedSize", {}},
};
return kSchemas;
}
@@ -281,6 +282,10 @@
return GetAnnotation(annotations_, AidlAnnotation::Type::JAVA_ONLY_IMMUTABLE);
}
+bool AidlAnnotatable::IsFixedSize() const {
+ return GetAnnotation(annotations_, AidlAnnotation::Type::FIXED_SIZE);
+}
+
const AidlAnnotation* AidlAnnotatable::UnsupportedAppUsage() const {
return GetAnnotation(annotations_, AidlAnnotation::Type::UNSUPPORTED_APP_USAGE);
}
@@ -834,7 +839,8 @@
AidlAnnotation::Type::HIDE,
AidlAnnotation::Type::JAVA_PASSTHROUGH,
AidlAnnotation::Type::JAVA_DEBUG,
- AidlAnnotation::Type::JAVA_ONLY_IMMUTABLE};
+ AidlAnnotation::Type::JAVA_ONLY_IMMUTABLE,
+ AidlAnnotation::Type::FIXED_SIZE};
}
bool AidlStructuredParcelable::CheckValid(const AidlTypenames& typenames) const {
@@ -850,6 +856,13 @@
success = success && typenames.CanBeJavaOnlyImmutable(v->GetType());
duplicated = !fieldnames.emplace(CapitalizeFirstLetter(v->GetName())).second;
} else {
+ if (IsFixedSize()) {
+ success = success && typenames.CanBeFixedSize(v->GetType());
+ if (!success) {
+ AIDL_ERROR(v) << "The @FixedSize parcelable '" << this->GetName() << "' has a "
+ << "non-fixed size field named " << v->GetName() << ".";
+ }
+ }
duplicated = !fieldnames.emplace(v->GetName()).second;
}
diff --git a/aidl_language.h b/aidl_language.h
index 7a53332..74ef07d 100644
--- a/aidl_language.h
+++ b/aidl_language.h
@@ -167,6 +167,7 @@
JAVA_PASSTHROUGH,
JAVA_DEBUG,
JAVA_ONLY_IMMUTABLE,
+ FIXED_SIZE,
};
static std::string TypeToString(Type type);
@@ -231,6 +232,7 @@
bool IsUtf8InCpp() const;
bool IsVintfStability() const;
bool IsJavaOnlyImmutable() const;
+ bool IsFixedSize() const;
bool IsStableApiParcelable(Options::Language lang) const;
bool IsHide() const;
bool IsJavaDebug() const;
diff --git a/aidl_typenames.cpp b/aidl_typenames.cpp
index 4b44ffd..4d50ce3 100644
--- a/aidl_typenames.cpp
+++ b/aidl_typenames.cpp
@@ -242,6 +242,25 @@
return t->IsJavaOnlyImmutable();
}
+// Only FixedSize Parcelable, primitive types, and enum types can be FixedSize.
+bool AidlTypenames::CanBeFixedSize(const AidlTypeSpecifier& type) const {
+ const string& name = type.GetName();
+ if (type.IsGeneric() || type.IsArray()) {
+ return false;
+ }
+ if (IsPrimitiveTypename(name)) {
+ return true;
+ }
+ const AidlDefinedType* t = TryGetDefinedType(type.GetName());
+ AIDL_FATAL_IF(t == nullptr, type)
+ << "Failed to look up type. Cannot determine if it can be fixed size.";
+
+ if (t->AsEnumDeclaration()) {
+ return true;
+ }
+ return t->IsFixedSize();
+}
+
// Only T[], List, Map, ParcelFileDescriptor and mutable Parcelable can be an out parameter.
bool AidlTypenames::CanBeOutParameter(const AidlTypeSpecifier& type) const {
const string& name = type.GetName();
diff --git a/aidl_typenames.h b/aidl_typenames.h
index aaee4e2..7a8cf88 100644
--- a/aidl_typenames.h
+++ b/aidl_typenames.h
@@ -69,6 +69,7 @@
ResolvedTypename ResolveTypename(const string& type_name) const;
bool CanBeOutParameter(const AidlTypeSpecifier& type) const;
bool CanBeJavaOnlyImmutable(const AidlTypeSpecifier& type) const;
+ bool CanBeFixedSize(const AidlTypeSpecifier& type) const;
bool IsIgnorableImport(const string& import) const;
// Returns the AidlEnumDeclaration of the given type, or nullptr if the type
diff --git a/aidl_unittest.cpp b/aidl_unittest.cpp
index c7a0632..1a3124f 100644
--- a/aidl_unittest.cpp
+++ b/aidl_unittest.cpp
@@ -348,7 +348,7 @@
const string expected_stderr =
"ERROR: a/Foo.aidl:1.32-36: 'nullable' is not a supported annotation for this node. "
"It must be one of: Hide, UnsupportedAppUsage, VintfStability, JavaPassthrough, JavaDebug, "
- "JavaOnlyImmutable\n";
+ "JavaOnlyImmutable, FixedSize\n";
CaptureStderr();
EXPECT_EQ(nullptr, Parse("a/Foo.aidl", method, typenames_, GetLanguage(), &error));
EXPECT_EQ(expected_stderr, GetCapturedStderr());
@@ -1995,6 +1995,78 @@
EXPECT_EQ(expected_stderr, GetCapturedStderr());
}
+TEST_F(AidlTestIncompatibleChanges, FixedSizeAddedField) {
+ const string expected_stderr =
+ "ERROR: new/p/Foo.aidl:1.33-37: Number of fields in p.Foo is changed from 1 to 2. "
+ "This is an incompatible change for FixedSize types.\n";
+ io_delegate_.SetFileContents("old/p/Foo.aidl",
+ "package p; @FixedSize parcelable Foo { int A = 1; }");
+ io_delegate_.SetFileContents("new/p/Foo.aidl",
+ "package p; @FixedSize parcelable Foo { int A = 1; int B = 2; }");
+ CaptureStderr();
+ EXPECT_FALSE(::android::aidl::check_api(options_, io_delegate_));
+ EXPECT_EQ(expected_stderr, GetCapturedStderr());
+}
+
+TEST_F(AidlTestIncompatibleChanges, FixedSizeRemovedField) {
+ const string expected_stderr =
+ "ERROR: new/p/Foo.aidl:1.33-37: Number of fields in p.Foo is reduced from 2 to 1.\n";
+ io_delegate_.SetFileContents("old/p/Foo.aidl",
+ "package p; @FixedSize parcelable Foo { int A = 1; int B = 1; }");
+ io_delegate_.SetFileContents("new/p/Foo.aidl",
+ "package p; @FixedSize parcelable Foo { int A = 1; }");
+ CaptureStderr();
+ EXPECT_FALSE(::android::aidl::check_api(options_, io_delegate_));
+ EXPECT_EQ(expected_stderr, GetCapturedStderr());
+}
+
+TEST_P(AidlTest, RejectNonFixedSizeFromFixedSize) {
+ const string expected_stderr =
+ "ERROR: Foo.aidl:1.36-38: The @FixedSize parcelable 'Foo' has a non-fixed size field named "
+ "a.\n"
+ "ERROR: Foo.aidl:1.44-46: The @FixedSize parcelable 'Foo' has a non-fixed size field named "
+ "b.\n"
+ "ERROR: Foo.aidl:1.55-57: The @FixedSize parcelable 'Foo' has a non-fixed size field named "
+ "c.\n"
+ "ERROR: Foo.aidl:1.80-82: The @FixedSize parcelable 'Foo' has a non-fixed size field named "
+ "d.\n"
+ "ERROR: Foo.aidl:1.92-94: The @FixedSize parcelable 'Foo' has a non-fixed size field named "
+ "e.\n"
+ "ERROR: Foo.aidl:1.109-111: The @FixedSize parcelable 'Foo' has a non-fixed size field named "
+ "f.\n";
+
+ io_delegate_.SetFileContents("Foo.aidl",
+ "@FixedSize parcelable Foo { "
+ " int[] a;"
+ " Bar b;"
+ " String c;"
+ " ParcelFileDescriptor d;"
+ " IBinder e;"
+ " List<String> f;"
+ "}");
+ io_delegate_.SetFileContents("Bar.aidl", "parcelable Bar { int a; }");
+ Options options =
+ Options::From("aidl Foo.aidl -I . --lang=" + Options::LanguageToString(GetLanguage()));
+
+ CaptureStderr();
+ EXPECT_NE(0, ::android::aidl::compile_aidl(options, io_delegate_));
+ EXPECT_EQ(expected_stderr, GetCapturedStderr());
+}
+
+TEST_P(AidlTest, AcceptFixedSizeFromFixedSize) {
+ const string expected_stderr = "";
+
+ io_delegate_.SetFileContents("Foo.aidl", "@FixedSize parcelable Foo { int a; Bar b; }");
+ io_delegate_.SetFileContents("Bar.aidl", "@FixedSize parcelable Bar { Val c; }");
+ io_delegate_.SetFileContents("Val.aidl", "enum Val { A, B, }");
+ Options options =
+ Options::From("aidl Foo.aidl -I . --lang=" + Options::LanguageToString(GetLanguage()));
+
+ CaptureStderr();
+ EXPECT_EQ(0, ::android::aidl::compile_aidl(options, io_delegate_));
+ EXPECT_EQ(expected_stderr, GetCapturedStderr());
+}
+
TEST_F(AidlTest, RejectAmbiguousImports) {
const string expected_stderr =
"ERROR: p/IFoo.aidl: Duplicate files found for q.IBar from:\n"