check-flagged-apis: handle nested flags

Consider

  @FlaggedApi(FLAG_OUTER) Clazz {
      @FlaggedApi(FLAG_INNER) method();
  }

If FLAG_OUTER is disabled, any class members are ignored. Teach
check-flagged-apis to recognize this and stop reporting false positives.

Bug: 339183637
Test: atest --host check-flagged-apis-test
Change-Id: Ie6799e952dc33874c1239231f841d7dfd947c7ce
This commit is contained in:
Mårten Kongstad 2024-05-08 10:00:32 +02:00
parent 32b652fd68
commit 0d44e721ed
2 changed files with 75 additions and 1 deletions

View file

@ -269,6 +269,42 @@ class CheckFlaggedApisTest {
assertEquals(expected, actual)
}
@Test
fun testNestedFlagsOuterFlagWins() {
val apiSignature =
"""
// Signature format: 2.0
package android {
@FlaggedApi("android.flag.foo") public final class A {
method @FlaggedApi("android.flag.bar") public boolean method();
}
@FlaggedApi("android.flag.bar") public final class B {
method @FlaggedApi("android.flag.foo") public boolean method();
}
}
"""
.trim()
val apiVersions =
"""
<?xml version="1.0" encoding="utf-8"?>
<api version="3">
<class name="android/B" since="1">
<extends name="java/lang/Object"/>
</class>
</api>
"""
.trim()
val expected = setOf<ApiError>()
val actual =
findErrors(
parseApiSignature("in-memory", apiSignature.byteInputStream()),
parseFlagValues(generateFlagsProto(DISABLED, ENABLED)),
parseApiVersions(apiVersions.byteInputStream()))
assertEquals(expected, actual)
}
@Test
fun testFindErrorsDisabledFlaggedApiIsPresent() {
val expected =

View file

@ -385,10 +385,48 @@ internal fun findErrors(
return false
}
/**
* Returns whether the given flag is enabled for the given symbol.
*
* A flagged member inside a flagged class is ignored (and the flag value considered disabled) if
* the class' flag is disabled.
*
* @param symbol the symbol to check
* @param flag the flag to check
* @return whether the flag is enabled for the given symbol
*/
fun isFlagEnabledForSymbol(symbol: Symbol, flag: Flag): Boolean {
when (symbol) {
is ClassSymbol -> return flags.getValue(flag)
is MemberSymbol -> {
val memberFlagValue = flags.getValue(flag)
if (!memberFlagValue) {
return false
}
// Special case: if the MemberSymbol's flag is enabled, but the outer
// ClassSymbol's flag (if the class is flagged) is disabled, consider
// the MemberSymbol's flag as disabled:
//
// @FlaggedApi(this-flag-is-disabled) Clazz {
// @FlaggedApi(this-flag-is-enabled) method(); // The Clazz' flag "wins"
// }
//
// Note: the current implementation does not handle nested classes.
val classFlagValue =
flaggedSymbolsInSource
.find { it.first.toPrettyString() == symbol.clazz }
?.let { flags.getValue(it.second) }
?: true
return classFlagValue
}
}
}
val errors = mutableSetOf<ApiError>()
for ((symbol, flag) in flaggedSymbolsInSource) {
try {
if (flags.getValue(flag)) {
if (isFlagEnabledForSymbol(symbol, flag)) {
if (!symbolsInOutput.containsSymbol(symbol)) {
errors.add(EnabledFlaggedApiNotPresentError(symbol, flag))
}