[tmf] add compile-time validation for URI paths lookup array (#12017)

This change introduces compile-time validation for the `kEntries`
array in `uri_paths.cpp` to ensure the array's order matches the
`Uri` enum definitions.

A new `constexpr` function, `AreConstStringsEqual()`, is added to allow
for string comparisons at compile time, which is necessary for use
within `static_assert` in C++11.

A series of `static_assert()` checks are added to `uri_paths.cpp`.
These assertions verify that each URI path string in the `kEntries`
lookup table is correctly placed at the index corresponding to its
`UriPath` enum value.

This prevents potential bugs caused by accidental reordering of either
the enum or the array, ensuring the mapping between them remains
correct. If the order is changed incorrectly, the build will now fail,
immediately alerting the developer.
This commit is contained in:
Abtin Keshavarzian
2025-10-13 14:55:42 +02:00
committed by GitHub
parent f8d6319368
commit e1fd751eb8
2 changed files with 100 additions and 40 deletions
+18
View File
@@ -378,6 +378,24 @@ inline constexpr bool AreStringsInOrder(const char *aFirst, const char *aSecond)
: ((*aFirst > *aSecond) || (*aFirst == kNullChar) ? false : AreStringsInOrder(aFirst + 1, aSecond + 1));
}
/**
* This `constexpr` function checks whether two given C strings are equal.
*
* This is intended for use from `static_assert`, e.g., checking if a lookup table entries are correct. It is not
* recommended to use this function in other situations as it uses recursion so that it can be `constexpr`.
*
* @param[in] aFirst The first string.
* @param[in] aSecond The second string.
*
* @retval TRUE If @p aFirst is equal to @p aSecond.
* @retval FALSE If @p aFirst is not equal to @p aSecond.
*/
inline constexpr bool AreConstStringsEqual(const char *aFirst, const char *aSecond)
{
return (*aFirst == *aSecond) ? (*aFirst == kNullChar ? true : AreConstStringsEqual(aFirst + 1, aSecond + 1))
: false;
}
/**
* Implements writing to a string buffer.
*/
+82 -40
View File
@@ -53,50 +53,92 @@ struct Entry
// The list of URI paths (MUST be sorted alphabetically)
static constexpr Entry kEntries[] = {
{"a/ae"}, // (0) kUriAddressError
{"a/an"}, // (1) kUriAddressNotify
{"a/aq"}, // (2) kUriAddressQuery
{"a/ar"}, // (3) kUriAddressRelease
{"a/as"}, // (4) kUriAddressSolicit
{"a/sd"}, // (5) kUriServerData
{"a/yl"}, // (6) kUriAnycastLocate
{"b/ba"}, // (7) kUriBackboneAnswer
{"b/bmr"}, // (8) kUriBackboneMlr
{"b/bq"}, // (9) kUriBackboneQuery
{"c/ab"}, // (10) kUriAnnounceBegin
{"c/ag"}, // (11) kUriActiveGet
{"c/ar"}, // (12) kUriActiveReplace
{"c/as"}, // (13) kUriActiveSet
{"c/ca"}, // (14) kUriCommissionerKeepAlive
{"c/cg"}, // (15) kUriCommissionerGet
{"c/cp"}, // (16) kUriCommissionerPetition
{"c/cs"}, // (17) kUriCommissionerSet
{"c/dc"}, // (18) kUriDatasetChanged
{"c/er"}, // (19) kUriEnergyReport
{"c/es"}, // (20) kUriEnergyScan
{"c/je"}, // (21) kUriJoinerEntrust
{"c/jf"}, // (22) kUriJoinerFinalize
{"c/la"}, // (23) kUriLeaderKeepAlive
{"c/lp"}, // (24) kUriLeaderPetition
{"c/pc"}, // (25) kUriPanIdConflict
{"c/pg"}, // (26) kUriPendingGet
{"c/pq"}, // (27) kUriPanIdQuery
{"c/ps"}, // (28) kUriPendingSet
{"c/rx"}, // (29) kUriRelayRx
{"c/tx"}, // (30) kUriRelayTx
{"c/ur"}, // (31) kUriProxyRx
{"c/ut"}, // (32) kUriProxyTx
{"d/da"}, // (33) kUriDiagnosticGetAnswer
{"d/dg"}, // (34) kUriDiagnosticGetRequest
{"d/dq"}, // (35) kUriDiagnosticGetQuery
{"d/dr"}, // (36) kUriDiagnosticReset
{"n/dn"}, // (37) kUriDuaRegistrationNotify
{"n/dr"}, // (38) kUriDuaRegistrationRequest
{"n/mr"}, // (39) kUriMlr
{"a/ae"}, // kUriAddressError
{"a/an"}, // kUriAddressNotify
{"a/aq"}, // kUriAddressQuery
{"a/ar"}, // kUriAddressRelease
{"a/as"}, // kUriAddressSolicit
{"a/sd"}, // kUriServerData
{"a/yl"}, // kUriAnycastLocate
{"b/ba"}, // kUriBackboneAnswer
{"b/bmr"}, // kUriBackboneMlr
{"b/bq"}, // kUriBackboneQuery
{"c/ab"}, // kUriAnnounceBegin
{"c/ag"}, // kUriActiveGet
{"c/ar"}, // kUriActiveReplace
{"c/as"}, // kUriActiveSet
{"c/ca"}, // kUriCommissionerKeepAlive
{"c/cg"}, // kUriCommissionerGet
{"c/cp"}, // kUriCommissionerPetition
{"c/cs"}, // kUriCommissionerSet
{"c/dc"}, // kUriDatasetChanged
{"c/er"}, // kUriEnergyReport
{"c/es"}, // kUriEnergyScan
{"c/je"}, // kUriJoinerEntrust
{"c/jf"}, // kUriJoinerFinalize
{"c/la"}, // kUriLeaderKeepAlive
{"c/lp"}, // kUriLeaderPetition
{"c/pc"}, // kUriPanIdConflict
{"c/pg"}, // kUriPendingGet
{"c/pq"}, // kUriPanIdQuery
{"c/ps"}, // kUriPendingSet
{"c/rx"}, // kUriRelayRx
{"c/tx"}, // kUriRelayTx
{"c/ur"}, // kUriProxyRx
{"c/ut"}, // kUriProxyTx
{"d/da"}, // kUriDiagnosticGetAnswer
{"d/dg"}, // kUriDiagnosticGetRequest
{"d/dq"}, // kUriDiagnosticGetQuery
{"d/dr"}, // kUriDiagnosticReset
{"n/dn"}, // kUriDuaRegistrationNotify
{"n/dr"}, // kUriDuaRegistrationRequest
{"n/mr"}, // kUriMlr
};
static_assert(BinarySearch::IsSorted(kEntries), "kEntries is not sorted");
// Validate that the `kEntries[]` order match the `kUri*` enum constants
static_assert(AreConstStringsEqual(kEntries[kUriAddressError].mPath, "a/ae"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriAddressNotify].mPath, "a/an"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriAddressQuery].mPath, "a/aq"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriAddressRelease].mPath, "a/ar"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriAddressSolicit].mPath, "a/as"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriServerData].mPath, "a/sd"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriAnycastLocate].mPath, "a/yl"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriBackboneAnswer].mPath, "b/ba"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriBackboneMlr].mPath, "b/bmr"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriBackboneQuery].mPath, "b/bq"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriAnnounceBegin].mPath, "c/ab"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriActiveGet].mPath, "c/ag"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriActiveReplace].mPath, "c/ar"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriActiveSet].mPath, "c/as"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriCommissionerKeepAlive].mPath, "c/ca"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriCommissionerGet].mPath, "c/cg"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriCommissionerPetition].mPath, "c/cp"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriCommissionerSet].mPath, "c/cs"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriDatasetChanged].mPath, "c/dc"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriEnergyReport].mPath, "c/er"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriEnergyScan].mPath, "c/es"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriJoinerEntrust].mPath, "c/je"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriJoinerFinalize].mPath, "c/jf"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriLeaderKeepAlive].mPath, "c/la"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriLeaderPetition].mPath, "c/lp"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriPanIdConflict].mPath, "c/pc"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriPendingGet].mPath, "c/pg"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriPanIdQuery].mPath, "c/pq"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriPendingSet].mPath, "c/ps"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriRelayRx].mPath, "c/rx"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriRelayTx].mPath, "c/tx"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriProxyRx].mPath, "c/ur"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriProxyTx].mPath, "c/ut"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriDiagnosticGetAnswer].mPath, "d/da"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriDiagnosticGetRequest].mPath, "d/dg"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriDiagnosticGetQuery].mPath, "d/dq"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriDiagnosticReset].mPath, "d/dr"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriDuaRegistrationNotify].mPath, "n/dn"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriDuaRegistrationRequest].mPath, "n/dr"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriMlr].mPath, "n/mr"), "kEntries is invalid");
struct UriEnumCheck
{
InitEnumValidatorCounter();