File size: 8,308 Bytes
6baed57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
#include "unity/unity.h"
#include <libxml/HTMLparser.h>

#include <libxml/parser.h>
#include <libxml/parserInternals.h>
#include <libxml/xmlmemory.h>
#include <libxml/xmlstring.h>
#include <limits.h>
#include <string.h>
#include <stdlib.h>

/* 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;
}