Skip to content

Fix/persisted player map flag - #126

Open
BehrRiley wants to merge 1 commit into
DenizenScript:masterfrom
BehrRiley:fix/persisted-player-map-flag
Open

Fix/persisted player map flag#126
BehrRiley wants to merge 1 commit into
DenizenScript:masterfrom
BehrRiley:fix/persisted-player-map-flag

Conversation

@BehrRiley

Copy link
Copy Markdown

Summary

fixes a SavableMapFlagTracker.doTotalClean() NPE caused by persisted flag values that begin with map@ but cannot be parsed as valid MapTags

Problem

the cleanup optimization parsed these values into quickMap and immediately dereferenced the result; for malformed map-like values, parsing returns null.

Changes

  • falls back to treating an unparseable map@... value as a literal flag value;
  • prevents the cleanup NPE;
  • clears the ambiguous raw serialization so a later save rewrites it as el@map@...;
  • leaves valid MapTag values on the existing path.

this was reproduced with real archived player-flag data; a malformed chat_history_list record could cause an unrelated <server.players_flagged[fishing_list]> lookup to fail, because loading a player tracker cleans all of that player’s flags

Testing:

it takes several steps to replicate, so here's each step:
https://paste.denizenscript.com/View/141477 - shows me throwing the async cleanup NPE
https://paste.denizenscript.com/View/141478 - shows the exact outer <server.players_flagged[fishing_list]> throwing quickMap is null
https://paste.denizenscript.com/View/141479 - fixed: same real profile/data and outer tag; it returns 1.

// Many commands and features that require a duration can be satisfied by specifying a number and unit of time, especially command arguments that are prefixed 'duration:', etc.
// The unit of time can be specified by using one of the following:
// t=ticks (0.05 seconds), s=seconds, m=minutes (60 seconds), h=hours (60 minutes), d=days (24 hours), w=weeks (7 days), y=years (365 days).
// ms=milliseconds (0.001 seconds), t=ticks (0.05 seconds), s=seconds, m=minutes (60 seconds), h=hours (60 minutes), d=days (24 hours), w=weeks (7 days), y=years (365 days).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

branchwonk

- avoid dereferencing quickMap when a persisted `map`@ value fails MapTag parsing
- treat value as a literal instead, rewrite it in the unambiguous el@ form on save to preserve the raw value and allows unrelated player flags to load normally
@BehrRiley
BehrRiley force-pushed the fix/persisted-player-map-flag branch from b24c0a6 to f36cd61 Compare September 5, 2026 01:51
@mcmonkey4eva

Copy link
Copy Markdown
Member

Can you clarify how exactly an invalid map is getting stored there? There should be protections in the code to prevent that from happening in the first place.

@BehrRiley

Copy link
Copy Markdown
Author

i'll need some help with words, but basically what's happening is the player flag is wrapped as a root MapTag, roughly;


uses PropertyParser.escapePropertyValue(...)
then,
public static String escapePropertyValue(String input) {

around here, it sees the first [, the last ], and decides that the whole region is valid object property syntax so it preserves that instead of escaping it. this is fine for real nested object properties but these bad chat_history_list values contain magic Denizen formatting here. that text was sneaking through the property syntax and when read back, MapTag.valueOf treats that [ and ] as map nesting and a raw ; to then look like a new map entry, which, when it reaches plain text such as - where it expects key=value, it returns null, the old cleanup then code dereferences that.
So I think the NPE fix is still correct as protection for existing bad data, but the serializer escaping behavior is likely the reason this data could be written in the first place.

to answer your question in regards to a script, it's actually very hard to do. something like this:

map_escape_repro:
    type: world
    events:
        on player joins:
        - define chat_text "<&ss>[click=RUN_COMMAND;/help]hello; world<&ss>[/click]"
        - flag <player> chat_history_list:<list[<[chat_text]>]> expire:1h

you would:

  • join once and write the flag
  • restart
  • run <server.players_flagged[fishing_list]> or have that tag run on startup

in this example, it effectively generates the plaintext §[click=RUN_COMMAND;/help]hello; world§[/click], where the serializer sees the first [ and the last ] and treats that full region as nested property syntax, so it leaves the middle ; unescaped. on reload, the MapTag parser closes click= only to find hello; at map depth zero and misakes that for a map-entry delimiter. the next text isn't a valid key=value pair so it parses null.

@mcmonkey4eva

Copy link
Copy Markdown
Member

So the flag contains a real flag-data-map, but the readback is corrupted by invalid escaping? This sounds like an X/Y problem where this PR is addressing the Y but we need to fix the X (the bad escaping logic)

@BehrRiley

Copy link
Copy Markdown
Author

that exactly, yes basically. the readback is not corrupting a valid map because the map is already written in an invalid form. our magic denizen §[click=...] gets mistaken for nested Denizen property syntax by the property-value escaping logic leaving structural characters like ; unescaped inside the persisted flag map, so it cannot be parsed back as a MapTag. This PR does only address the Y: do not NPE when existing malformed data is encountered; preserve it as a literal value instead.

the X is the serializer escaping/round-trip issue that allowed it to be written - but i felt like i would have been told it should be fixed separately or added here if you prefer the broader scope because that feels much bigger than just this issue, it branches off into separate things that extend past what i touched here; should i go for that next?

@BehrRiley

Copy link
Copy Markdown
Author

to clarify also i do not mind taking on the next step of this if it extends to another pull request - but from what i found is that PropertyParser.escapePropertyValue(...) is shared serialization logic for MapTags and object-property values, so changing it can affect more than just player_flags: like server/world/NPC flags, nested MapTags, and any other serialized object value that contains bracket-like text that i can't think of immediately

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants