#include "unity/unity.h" #include #include #include #include #include #include #include #include /* Prototype of the provided test wrapper */ int test_htmlAttrHashInsert(xmlParserCtxtPtr ctxt, unsigned size, const xmlChar *name, unsigned hashValue, int aindex); static void *alloc_attr_hash(unsigned size) { /* Allocate a generously large buffer per bucket to avoid needing the private struct size */ size_t per_bucket_bytes = 64; /* ample for the real struct which should be much smaller */ size_t total = (size_t)size * per_bucket_bytes; void *buf = xmlMalloc(total); if (buf != NULL) { memset(buf, 0xFF, total); /* ensure index fields (ints) are -1 */ } return buf; } static const xmlChar **alloc_atts(size_t count) { const xmlChar **atts = (const xmlChar **) xmlMalloc(sizeof(const xmlChar *) * count); if (atts != NULL) { memset(atts, 0, sizeof(const xmlChar *) * count); } return atts; } void setUp(void) { /* Setup code here, or leave empty */ } void tearDown(void) { /* Cleanup code here, or leave empty */ } static void cleanup_ctxt_custom_buffers(xmlParserCtxtPtr ctxt, void *savedHash, const xmlChar **savedAtts, void *ourHash, const xmlChar **ourAtts) { /* Restore originals so xmlFreeParserCtxt doesn't free our allocations, then free ours */ ctxt->attrHash = (void *)savedHash; ctxt->atts = (const xmlChar **)savedAtts; if (ourHash) xmlFree(ourHash); if (ourAtts) xmlFree((void *)ourAtts); } void test_htmlAttrHashInsert_inserts_and_detects_duplicate(void) { htmlParserCtxtPtr ctxt = htmlNewParserCtxt(); TEST_ASSERT_NOT_NULL(ctxt); /* Save original pointers to avoid double free */ void *savedHash = (void *)ctxt->attrHash; const xmlChar **savedAtts = ctxt->atts; unsigned size = 8; /* power of two */ void *hashTable = alloc_attr_hash(size); TEST_ASSERT_NOT_NULL(hashTable); size_t attsCap = 16; const xmlChar **atts = alloc_atts(attsCap); TEST_ASSERT_NOT_NULL(atts); ctxt->attrHash = (void *)hashTable; ctxt->atts = (const xmlChar **)atts; /* Prepare attribute */ const xmlChar *name1 = BAD_CAST "foo"; int aindex1 = 5; TEST_ASSERT_TRUE(aindex1 >= 0 && (size_t)aindex1 < attsCap); atts[aindex1] = name1; /* Insert into empty table */ int r1 = test_htmlAttrHashInsert(ctxt, size, name1, /*hash*/ 3U, aindex1); TEST_ASSERT_EQUAL_INT(INT_MAX, r1); /* Re-insert same pointer; expect previous index */ int r2 = test_htmlAttrHashInsert(ctxt, size, name1, /*same hash*/ 3U, /*different aindex*/ 11); TEST_ASSERT_EQUAL_INT(aindex1, r2); cleanup_ctxt_custom_buffers((xmlParserCtxtPtr)ctxt, savedHash, savedAtts, hashTable, atts); xmlFreeParserCtxt((xmlParserCtxtPtr)ctxt); } void test_htmlAttrHashInsert_linear_probe_collision(void) { htmlParserCtxtPtr ctxt = htmlNewParserCtxt(); TEST_ASSERT_NOT_NULL(ctxt); void *savedHash = (void *)ctxt->attrHash; const xmlChar **savedAtts = ctxt->atts; unsigned size = 4; /* small to force collisions */ void *hashTable = alloc_attr_hash(size); TEST_ASSERT_NOT_NULL(hashTable); size_t attsCap = 16; const xmlChar **atts = alloc_atts(attsCap); TEST_ASSERT_NOT_NULL(atts); ctxt->attrHash = (void *)hashTable; ctxt->atts = (const xmlChar **)atts; /* First entry at hindex = 3 & (4 - 1) == 3 */ const xmlChar *name1 = BAD_CAST "a"; int aindex1 = 2; atts[aindex1] = name1; int r1 = test_htmlAttrHashInsert(ctxt, size, name1, 3U, aindex1); TEST_ASSERT_EQUAL_INT(INT_MAX, r1); /* Second entry collides (same hindex), should probe to next bucket and insert */ const xmlChar *name2 = BAD_CAST "b"; int aindex2 = 3; atts[aindex2] = name2; int r2 = test_htmlAttrHashInsert(ctxt, size, name2, 7U /* 7 & 3 == 3 */, aindex2); TEST_ASSERT_EQUAL_INT(INT_MAX, r2); /* Re-insert name2: should find it and return aindex2 */ int r3 = test_htmlAttrHashInsert(ctxt, size, name2, 7U, /*ignored on hit*/ 9); TEST_ASSERT_EQUAL_INT(aindex2, r3); /* Re-insert name1: should still be found and return aindex1 */ int r4 = test_htmlAttrHashInsert(ctxt, size, name1, 3U, /*ignored on hit*/ 8); TEST_ASSERT_EQUAL_INT(aindex1, r4); cleanup_ctxt_custom_buffers((xmlParserCtxtPtr)ctxt, savedHash, savedAtts, hashTable, atts); xmlFreeParserCtxt((xmlParserCtxtPtr)ctxt); } void test_htmlAttrHashInsert_wraps_around_table(void) { htmlParserCtxtPtr ctxt = htmlNewParserCtxt(); TEST_ASSERT_NOT_NULL(ctxt); void *savedHash = (void *)ctxt->attrHash; const xmlChar **savedAtts = ctxt->atts; unsigned size = 4; void *hashTable = alloc_attr_hash(size); TEST_ASSERT_NOT_NULL(hashTable); size_t attsCap = 32; const xmlChar **atts = alloc_atts(attsCap); TEST_ASSERT_NOT_NULL(atts); ctxt->attrHash = (void *)hashTable; ctxt->atts = (const xmlChar **)atts; /* Fill bucket 3 */ const xmlChar *name1 = BAD_CAST "x"; int aindex1 = 5; atts[aindex1] = name1; int r1 = test_htmlAttrHashInsert(ctxt, size, name1, 3U, aindex1); TEST_ASSERT_EQUAL_INT(INT_MAX, r1); /* Fill bucket 0 to force wrap-around to continue further */ const xmlChar *name0 = BAD_CAST "y"; int aindex0 = 6; atts[aindex0] = name0; int r0 = test_htmlAttrHashInsert(ctxt, size, name0, 0U, aindex0); TEST_ASSERT_EQUAL_INT(INT_MAX, r0); /* Now insert another that collides at bucket 3; it must wrap to 1 */ const xmlChar *name2 = BAD_CAST "z"; int aindex2 = 7; atts[aindex2] = name2; int r2 = test_htmlAttrHashInsert(ctxt, size, name2, 7U /* 7&3==3 */, aindex2); TEST_ASSERT_EQUAL_INT(INT_MAX, r2); /* Verify we can find name2 again (wherever it was inserted after wrap) */ int r2b = test_htmlAttrHashInsert(ctxt, size, name2, 7U, 99); TEST_ASSERT_EQUAL_INT(aindex2, r2b); cleanup_ctxt_custom_buffers((xmlParserCtxtPtr)ctxt, savedHash, savedAtts, hashTable, atts); xmlFreeParserCtxt((xmlParserCtxtPtr)ctxt); } void test_htmlAttrHashInsert_pointer_equality_only(void) { htmlParserCtxtPtr ctxt = htmlNewParserCtxt(); TEST_ASSERT_NOT_NULL(ctxt); void *savedHash = (void *)ctxt->attrHash; const xmlChar **savedAtts = ctxt->atts; unsigned size = 8; void *hashTable = alloc_attr_hash(size); TEST_ASSERT_NOT_NULL(hashTable); size_t attsCap = 16; const xmlChar **atts = alloc_atts(attsCap); TEST_ASSERT_NOT_NULL(atts); ctxt->attrHash = (void *)hashTable; ctxt->atts = (const xmlChar **)atts; /* Two equal-content names with different pointers */ const xmlChar *nameA1 = BAD_CAST "href"; xmlChar *nameA2dup = xmlStrdup(nameA1); /* distinct pointer, same content */ TEST_ASSERT_NOT_NULL(nameA2dup); int idx1 = 1; int idx2 = 2; atts[idx1] = nameA1; atts[idx2] = nameA2dup; /* Insert first */ int r1 = test_htmlAttrHashInsert(ctxt, size, nameA1, 5U, idx1); TEST_ASSERT_EQUAL_INT(INT_MAX, r1); /* Insert second with same content but different pointer: should NOT match existing */ int r2 = test_htmlAttrHashInsert(ctxt, size, nameA2dup, 5U, idx2); TEST_ASSERT_EQUAL_INT(INT_MAX, r2); /* Now duplicate lookups return their respective indices */ int r3 = test_htmlAttrHashInsert(ctxt, size, nameA1, 5U, 99); TEST_ASSERT_EQUAL_INT(idx1, r3); int r4 = test_htmlAttrHashInsert(ctxt, size, nameA2dup, 5U, 99); TEST_ASSERT_EQUAL_INT(idx2, r4); xmlFree(nameA2dup); cleanup_ctxt_custom_buffers((xmlParserCtxtPtr)ctxt, savedHash, savedAtts, hashTable, atts); xmlFreeParserCtxt((xmlParserCtxtPtr)ctxt); } int main(void) { xmlInitParser(); UNITY_BEGIN(); RUN_TEST(test_htmlAttrHashInsert_inserts_and_detects_duplicate); RUN_TEST(test_htmlAttrHashInsert_linear_probe_collision); RUN_TEST(test_htmlAttrHashInsert_wraps_around_table); RUN_TEST(test_htmlAttrHashInsert_pointer_equality_only); int res = UNITY_END(); xmlCleanupParser(); return res; }