Skip to content

[ML] Fix cross-platform isEof() in CJsonStateRestoreTraverser - #2898

Merged
edsavage merged 2 commits into
elastic:mainfrom
edsavage:fix-iseof-windows-portability
Feb 23, 2026
Merged

[ML] Fix cross-platform isEof() in CJsonStateRestoreTraverser#2898
edsavage merged 2 commits into
elastic:mainfrom
edsavage:fix-iseof-windows-portability

Conversation

@edsavage

@edsavage edsavage commented Feb 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fixes CJsonStateRestoreTraverser::isEof() to be portable across platforms by falling back to peek() when eof() returns false. On Windows, eof() is a lagging indicator that is not set until a read past the end is attempted.
  • Removes the unnecessary isEof() dependency from empty array detection in start() — the token sequence [ ] is sufficient to identify an empty JSON array without checking stream state.
  • Fixes CFieldDataCategorizerTest/testRestoreFromBadState and CFieldDataCategorizerTest/testRestoreStateRecoversWithEmptyState which were failing on Windows since [ML] Better handling of invalid JSON state documents #2895.

Test plan

  • All 800 tests pass on macOS ARM (48.5s wall-clock)
  • All 800 tests pass on Linux aarch64 (2m39s wall-clock)
  • All 800 tests pass on Windows x86_64 (8m16s wall-clock)
  • Specifically verified both previously-failing CFieldDataCategorizerTest cases pass on all three platforms

Made with Cursor

Labelling as >non-issue as this is related to an as yet unreleased code change.

The isEof() implementation was changed from peek()-based to eof()-based
in elastic#2895, but eof() is a lagging indicator on Windows - it is not set
until a read past the end is attempted. This caused
CFieldDataCategorizerTest/testRestoreFromBadState and
testRestoreStateRecoversWithEmptyState to fail on Windows.

Two fixes:
1. isEof() now falls back to peek() when eof() returns false, making
   the check portable across platforms.
2. The empty array detection in start() no longer depends on isEof()
   at all - the token sequence [,] is sufficient to identify an empty
   array without checking stream state.

Tested on macOS ARM, Linux aarch64, and Windows x86_64 - all 800 tests
pass on all three platforms.

Co-authored-by: Cursor <cursoragent@cursor.com>
@prodsecmachine

prodsecmachine commented Feb 18, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@valeriy42 valeriy42 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@edsavage
edsavage merged commit 54059a3 into elastic:main Feb 23, 2026
11 checks passed
edsavage added a commit to edsavage/ml-cpp that referenced this pull request Feb 26, 2026
…c#2898)

The isEof() implementation was changed from peek()-based to eof()-based
in elastic#2895, but eof() is a lagging indicator on Windows - it is not set
until a read past the end is attempted. This caused
CFieldDataCategorizerTest/testRestoreFromBadState and
testRestoreStateRecoversWithEmptyState to fail on Windows.

Two fixes:
1. isEof() now falls back to peek() when eof() returns false, making
   the check portable across platforms.
2. The empty array detection in start() no longer depends on isEof()
   at all - the token sequence [,] is sufficient to identify an empty
   array without checking stream state.

Tested on macOS ARM, Linux aarch64, and Windows x86_64 - all tests
pass on all three platforms.

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
edsavage added a commit that referenced this pull request Aug 13, 2026
…e token ID (#3143)

An inconsistent or truncated categorizer state document can leave a
restored category referencing a token ID at or beyond the end of the
restored token ID lookup. That ID was later used to index the token ID
lookup unchecked (for example when building a reverse search), which is
an out-of-bounds access that can crash the autodetect process with a
SIGSEGV inside libc rather than failing the restore.

Validate, at the end of CTokenListDataCategorizerBase::acceptRestoreTraverser,
that every token ID referenced by a restored category exists in the
restored token ID lookup, and fail the restore gracefully if not. This is
consistent with the graceful invalid-state handling added in #2895/#2898.

Relates to #2875

Co-authored-by: Cursor <cursoragent@cursor.com>
elastic-vault-github-plugin-prod Bot added a commit that referenced this pull request Aug 13, 2026
…e token ID (#3143) (#3152)

An inconsistent or truncated categorizer state document can leave a
restored category referencing a token ID at or beyond the end of the
restored token ID lookup. That ID was later used to index the token ID
lookup unchecked (for example when building a reverse search), which is
an out-of-bounds access that can crash the autodetect process with a
SIGSEGV inside libc rather than failing the restore.

Validate, at the end of CTokenListDataCategorizerBase::acceptRestoreTraverser,
that every token ID referenced by a restored category exists in the
restored token ID lookup, and fail the restore gracefully if not. This is
consistent with the graceful invalid-state handling added in #2895/#2898.

Relates to #2875


(cherry picked from commit 0f41e80)

Co-authored-by: Ed Savage <ed.savage@elastic.co>
Co-authored-by: Cursor <cursoragent@cursor.com>
elastic-vault-github-plugin-prod Bot added a commit that referenced this pull request Aug 13, 2026
…e token ID (#3143) (#3151)

An inconsistent or truncated categorizer state document can leave a
restored category referencing a token ID at or beyond the end of the
restored token ID lookup. That ID was later used to index the token ID
lookup unchecked (for example when building a reverse search), which is
an out-of-bounds access that can crash the autodetect process with a
SIGSEGV inside libc rather than failing the restore.

Validate, at the end of CTokenListDataCategorizerBase::acceptRestoreTraverser,
that every token ID referenced by a restored category exists in the
restored token ID lookup, and fail the restore gracefully if not. This is
consistent with the graceful invalid-state handling added in #2895/#2898.

Relates to #2875


(cherry picked from commit 0f41e80)

Co-authored-by: Ed Savage <ed.savage@elastic.co>
Co-authored-by: Cursor <cursoragent@cursor.com>
elastic-vault-github-plugin-prod Bot added a commit that referenced this pull request Aug 13, 2026
…of-range token ID (#3143) (#3150)

* [ML] Fail gracefully when restoring a categorizer with an out-of-range token ID (#3143)

An inconsistent or truncated categorizer state document can leave a
restored category referencing a token ID at or beyond the end of the
restored token ID lookup. That ID was later used to index the token ID
lookup unchecked (for example when building a reverse search), which is
an out-of-bounds access that can crash the autodetect process with a
SIGSEGV inside libc rather than failing the restore.

Validate, at the end of CTokenListDataCategorizerBase::acceptRestoreTraverser,
that every token ID referenced by a restored category exists in the
restored token ID lookup, and fail the restore gracefully if not. This is
consistent with the graceful invalid-state handling added in #2895/#2898.

Relates to #2875

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit 0f41e80)

* [ML] Add missing CJsonStateRestoreTraverser include on 8.19 backport

The cherry-picked unit tests use JSON restore; 8.19's test file only
included RapidXml headers, so all platform builds failed to compile.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Ed Savage <ed.savage@elastic.co>
Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants