diff --git a/src/string_view.c b/src/string_view.c index 2cac9da7..b880b042 100644 --- a/src/string_view.c +++ b/src/string_view.c @@ -162,10 +162,15 @@ bool CpuFeatures_StringView_HasWord(const StringView line, if (index_of_word < 0) { return false; } else { + // index_of_word is relative to `remainder`; convert it to an absolute + // index into `line` so the boundary checks see the real neighbours. + // (On later iterations `remainder` no longer starts at `line`.) + const size_t absolute_index = + (line.size - remainder.size) + (size_t)index_of_word; const StringView before = - CpuFeatures_StringView_KeepFront(line, index_of_word); + CpuFeatures_StringView_KeepFront(line, absolute_index); const StringView after = - CpuFeatures_StringView_PopFront(line, index_of_word + word.size); + CpuFeatures_StringView_PopFront(line, absolute_index + word.size); const bool valid_before = before.size == 0 || CpuFeatures_StringView_Back(before) == separator; const bool valid_after = diff --git a/test/string_view_test.cc b/test/string_view_test.cc index 772ac3f5..3af70c70 100644 --- a/test/string_view_test.cc +++ b/test/string_view_test.cc @@ -182,6 +182,14 @@ TEST(StringViewTest, CpuFeatures_StringView_HasWord) { CpuFeatures_StringView_HasWord(str("first middle last"), "mid", ' ')); EXPECT_FALSE( CpuFeatures_StringView_HasWord(str("first middle last"), "las", ' ')); + // Regression: the searched word appears as a real token, but an earlier + // non-boundary partial occurrence forces a second loop iteration. HasWord + // used to run the separator checks with a remainder-relative index against + // the whole line, producing a false negative on the real token. + EXPECT_TRUE(CpuFeatures_StringView_HasWord(str("foox foo"), "foo", ' ')); + EXPECT_TRUE(CpuFeatures_StringView_HasWord(str("xvme vme"), "vme", ' ')); + // A repeated prefix that is never its own token must still not match. + EXPECT_FALSE(CpuFeatures_StringView_HasWord(str("foofoo bar"), "foo", ' ')); } TEST(StringViewTest, CpuFeatures_StringView_GetAttributeKeyValue) {