diff --git a/include/sonic/internal/arch/simd_skip.h b/include/sonic/internal/arch/simd_skip.h index 1f4ec25..31a6371 100644 --- a/include/sonic/internal/arch/simd_skip.h +++ b/include/sonic/internal/arch/simd_skip.h @@ -677,6 +677,16 @@ class SkipScanner2 { JsonGeneratorInterface *jsonGenerator, const JsonGeneratorFactory &jsonGeneratorFactory, const int64_t idx) { + // On-demand scanning is forward-only, so a negative index cannot be + // resolved without knowing the array length up front. Report that + // explicitly: the loop below compares `cur_idx == idx` with cur_idx >= 0, + // which a negative idx never satisfies, so without this check the scan + // walks the whole array and the caller receives kErrorNoneNoMatch -- the + // same result as a key that genuinely is not there. + if (idx < 0) { + setError(SonicError::kUnsupportedJsonPath); + return false; + } RETURN_FALSE_IF_PARSE_ERROR(consume('[')); int64_t cur_idx = 0; bool dirty = false; diff --git a/tests/jsonpath_test.cpp b/tests/jsonpath_test.cpp index acf9154..d5a2df1 100644 --- a/tests/jsonpath_test.cpp +++ b/tests/jsonpath_test.cpp @@ -779,6 +779,33 @@ TEST(JsonPathDom, NegativeIndexSupportedButOnDemandUnsupported) { EXPECT_EQ(std::get<0>(ondemand_got), ""); } +TEST(JsonPathOnDemand, NegativeIndexReportsUnsupportedNotNoMatch) { + // A negative index is a feature this backend does not implement, which is + // not the same thing as the path being absent from the document. Reporting + // both as kErrorNoneNoMatch leaves callers unable to tell "sonic cannot + // evaluate this path" from "the document has no such member", so a path the + // DOM backend resolves fine looks merely missing here. + for (const char* path : {"$[-1]", "$[-3]", "$[-9]"}) { + auto got = GetByJsonPathOnDemand("[0,1,2]", path); + EXPECT_EQ(std::get<1>(got), kUnsupportedJsonPath) << "path: " << path; + EXPECT_EQ(std::get<0>(got), "") << "path: " << path; + } + + // Nested arrays reach the same code through a different call site. + auto nested = GetByJsonPathOnDemand( + R"({"a":[10,11,12]})", "$.a[-1]"); + EXPECT_EQ(std::get<1>(nested), kUnsupportedJsonPath); + + // A genuinely absent member must keep reporting no-match, so the two + // outcomes stay distinguishable. + auto absent_key = + GetByJsonPathOnDemand(R"({"a":1})", "$.zz"); + EXPECT_EQ(std::get<1>(absent_key), kErrorNoneNoMatch); + auto absent_index = + GetByJsonPathOnDemand("[0,1,2]", "$[9]"); + EXPECT_EQ(std::get<1>(absent_index), kErrorNoneNoMatch); +} + TEST(JsonPathDump, SerializeCoversEmptySingleAndMulti) { Document doc; doc.Parse(R"({"a":null,"s":"abc","arr":[null,"x",1]})");