fix(persistence): stop ProtobufPersistedData.isArray() lying for maps - #5379
fix(persistence): stop ProtobufPersistedData.isArray() lying for maps#5379soloturn wants to merge 1 commit into
Conversation
#5069: PersistedData.isArray() can return true for data that is actually a value map, forcing callers to add an extra isValueMap() check of their own to tell the two apart - as GenericMapTypeHandler had to when read from save-game data (#5062). ## Root cause Gson-backed PersistedData (used for JSON config/prefabs) already scopes isArray() correctly - AbstractGsonPersistedData just delegates to JsonElement.isJsonArray(), which is never true for a JsonObject. ProtobufPersistedData - the class backing save-game entity serialization - is different. A single EntityData.Value protobuf message doubles as a scalar, an array (repeated scalar/nested-value fields) and a value map (name-value pairs), and isArray() ignored all of that and unconditionally returned true: public boolean isArray() { return true; } So for any save-game field actually shaped as a map, isArray() lied, and the only way to tell was the separate isValueMap() check GenericMapTypeHandler already had to add. Every other isArray()/getAsArray() caller in the type-handling code has the same latent exposure, just less likely to hit it in practice since Map fields are where array/map shape confusion naturally arises. ## Fix Mirror isValueMap()'s own check instead of hardcoding true: public boolean isArray() { return data.getNameValueCount() == 0; } Every other kind this class represents - scalars (already treated as size-1 arrays by getAsArray()/getArrayItem() elsewhere in this class), genuine repeated-value arrays, and null - keeps reporting isArray() as true exactly as before; only the one case that was actually wrong (a real value map) now correctly reports false. getAsArray() is gated on isArray(), so this also means it now correctly throws instead of silently succeeding when called on map-shaped data. GenericMapTypeHandler's existing isValueMap() check is now redundant for this case but harmless, so it's left in place rather than touched here. ## Verification :engine:compileJava clean. engine-tests:unitTest and subsystems:TypeHandlerLibrary:test both pass in full, no failures - notably EntitySerializerTest (14 tests), which round-trips components through the real EntityData.Entity protobuf path this class backs, and GenericMapTypeHandlerTest/CollectionTypeHandlerTest/ ArrayTypeHandlerTest, which cover the array/map-shape-detection callers most exposed to this bug. Fixes #5069 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review. 📝 WalkthroughSummary by CodeRabbit
Walkthrough
ChangesProtobuf persisted data
Estimated code review effort: 2 (Simple) | ~10 minutes Merge Risk: ⚪ Minimal · up to This localized fix makes map-shaped persisted data report the correct array status and prevents invalid array access; no actionable merge-blocking risk remains after normal checks and review. Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Fixes #5069 -
PersistedData.isArray()can returntruefor data that is actually a value map, forcing callers to add their ownisValueMap()check to tell the two apart, asGenericMapTypeHandlerhad to when reading save-game data (#5062).Root cause
Gson-backed
PersistedData(JSON config/prefabs) already scopesisArray()correctly -AbstractGsonPersistedDatajust delegates toJsonElement.isJsonArray(), which is nevertruefor aJsonObject.ProtobufPersistedData- the class backing save-game entity serialization - is different. A singleEntityData.Valueprotobuf message doubles as a scalar, an array (repeated scalar/nested-value fields) and a value map (name-value pairs), andisArray()ignored all of that:So for any save-game field actually shaped as a map,
isArray()lied, and the only way to tell was the separateisValueMap()checkGenericMapTypeHandleralready had to add. Every otherisArray()/getAsArray()caller in the type-handling code has the same latent exposure, just less likely to hit it in practice sinceMapfields are where array/map shape confusion naturally arises.Fix
Mirror
isValueMap()'s own check instead of hardcodingtrue:Every other kind this class represents - scalars (already treated as size-1 arrays by
getAsArray()/getArrayItem()elsewhere in this class), genuine repeated-value arrays, and null - keeps reportingisArray()astrueexactly as before; only the one case that was actually wrong (a real value map) now correctly reportsfalse.getAsArray()is gated onisArray(), so this also means it now correctly throws instead of silently succeeding when called on map-shaped data.GenericMapTypeHandler's existingisValueMap()check is now redundant for this case but harmless, so it's left in place rather than touched here.Verification
:engine:compileJavaclean.engine-tests:unitTestandsubsystems:TypeHandlerLibrary:testboth pass in full, no failures - notablyEntitySerializerTest(14 tests), which round-trips components through the realEntityData.Entityprotobuf path this class backs, andGenericMapTypeHandlerTest/CollectionTypeHandlerTest/ArrayTypeHandlerTest, which cover the array/map-shape-detection callers most exposed to this bug.