\r
if(BUILD_tests)\r
## these are unittests that can be run on any platform\r
- add_executable(runtests tests/runtests.c tests/chardata.c tests/minicheck.c)\r
+ add_executable(runtests tests/runtests.c tests/chardata.c tests/minicheck.c tests/memcheck.c)\r
set_property(TARGET runtests PROPERTY RUNTIME_OUTPUT_DIRECTORY tests)\r
target_link_libraries(runtests expat)\r
add_test(runtests tests/runtests)\r
\r
- add_executable(runtestspp tests/runtestspp.cpp tests/chardata.c tests/minicheck.c)\r
+ add_executable(runtestspp tests/runtestspp.cpp tests/chardata.c tests/minicheck.c tests/memcheck.c)\r
set_property(TARGET runtestspp PROPERTY RUNTIME_OUTPUT_DIRECTORY tests)\r
target_link_libraries(runtestspp expat)\r
add_test(runtestspp tests/runtestspp)\r
tests/chardata.@OBJEXT@: tests/chardata.c tests/chardata.h
tests/minicheck.@OBJEXT@: tests/minicheck.c tests/minicheck.h
-tests/runtests.@OBJEXT@: tests/runtests.c tests/chardata.h
-tests/runtests: tests/runtests.@OBJEXT@ tests/chardata.@OBJEXT@ tests/minicheck.@OBJEXT@ $(LIBRARY)
- $(LINK_EXE) tests/runtests.@OBJEXT@ tests/chardata.@OBJEXT@ tests/minicheck.@OBJEXT@ $(LIBRARY)
-tests/runtestspp.@OBJEXT@: tests/runtestspp.cpp tests/runtests.c tests/chardata.h
-tests/runtestspp: tests/runtestspp.@OBJEXT@ tests/chardata.@OBJEXT@ tests/minicheck.@OBJEXT@ $(LIBRARY)
- $(LINK_CXX_EXE) tests/runtestspp.@OBJEXT@ tests/chardata.@OBJEXT@ tests/minicheck.@OBJEXT@ $(LIBRARY)
+tests/memcheck.@OBJEXT@: tests/memcheck.c tests/memcheck.h
+tests/runtests.@OBJEXT@: tests/runtests.c tests/chardata.h tests/memcheck.h
+tests/runtests: tests/runtests.@OBJEXT@ tests/chardata.@OBJEXT@ tests/minicheck.@OBJEXT@ tests/memcheck.@OBJEXT@ $(LIBRARY)
+ $(LINK_EXE) tests/runtests.@OBJEXT@ tests/chardata.@OBJEXT@ tests/minicheck.@OBJEXT@ tests/memcheck.@OBJEXT@ $(LIBRARY)
+tests/runtestspp.@OBJEXT@: tests/runtestspp.cpp tests/runtests.c tests/chardata.h tests/memcheck.h
+tests/runtestspp: tests/runtestspp.@OBJEXT@ tests/chardata.@OBJEXT@ tests/minicheck.@OBJEXT@ tests/memcheck.@OBJEXT@ $(LIBRARY)
+ $(LINK_CXX_EXE) tests/runtestspp.@OBJEXT@ tests/chardata.@OBJEXT@ tests/minicheck.@OBJEXT@ tests/memcheck.@OBJEXT@ $(LIBRARY)
tests/benchmark/benchmark.@OBJEXT@: tests/benchmark/benchmark.c
tests/benchmark/benchmark: tests/benchmark/benchmark.@OBJEXT@ $(LIBRARY)
--- /dev/null
+/* Copyright (c) 2017 The Expat Maintainers
+ * Copying is permitted under the MIT license. See the file COPYING
+ * for details.
+ *
+ * memcheck.c : debug allocators for the Expat test suite
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "memcheck.h"
+
+
+/* Structures to keep track of what has been allocated. Speed isn't a
+ * big issue for the tests this is required for, so we will use a
+ * doubly-linked list to make deletion easier.
+ */
+
+typedef struct allocation_entry {
+ struct allocation_entry * next;
+ struct allocation_entry * prev;
+ void * allocation;
+ size_t num_bytes;
+} AllocationEntry;
+
+static AllocationEntry *alloc_head = NULL;
+static AllocationEntry *alloc_tail = NULL;
+
+static AllocationEntry *find_allocation(void *ptr);
+
+
+/* Allocate some memory and keep track of it. */
+void *
+tracking_malloc(size_t size)
+{
+ AllocationEntry *entry = malloc(sizeof(AllocationEntry));
+
+ if (entry == NULL) {
+ printf("Allocator failure\n");
+ return NULL;
+ }
+ entry->num_bytes = size;
+ entry->allocation = malloc(size);
+ if (entry->allocation == NULL) {
+ free(entry);
+ return NULL;
+ }
+ entry->next = NULL;
+
+ /* Add to the list of allocations */
+ if (alloc_head == NULL) {
+ entry->prev = NULL;
+ alloc_head = alloc_tail = entry;
+ } else {
+ entry->prev = alloc_tail;
+ alloc_tail->next = entry;
+ alloc_tail = entry;
+ }
+
+ return entry->allocation;
+}
+
+static AllocationEntry *
+find_allocation(void *ptr)
+{
+ AllocationEntry *entry;
+
+ for (entry = alloc_head; entry != NULL; entry = entry->next) {
+ if (entry->allocation == ptr) {
+ return entry;
+ }
+ }
+ return NULL;
+}
+
+/* Free some memory and remove the tracking for it */
+void
+tracking_free(void *ptr)
+{
+ AllocationEntry *entry;
+
+ if (ptr == NULL) {
+ /* There won't be an entry for this */
+ return;
+ }
+
+ entry = find_allocation(ptr);
+ if (entry != NULL) {
+ /* This is the relevant allocation. Unlink it */
+ if (entry->prev != NULL)
+ entry->prev->next = entry->next;
+ else
+ alloc_head = entry->next;
+ if (entry->next != NULL)
+ entry->next->prev = entry->prev;
+ else
+ alloc_tail = entry->next;
+ free(entry);
+ } else {
+ printf("Attempting to free unallocated memory at %p\n", ptr);
+ }
+ free(ptr);
+}
+
+/* Reallocate some memory and keep track of it */
+void *
+tracking_realloc(void *ptr, size_t size)
+{
+ AllocationEntry *entry;
+
+ if (ptr == NULL) {
+ /* By definition, this is equivalent to malloc(size) */
+ return tracking_malloc(size);
+ }
+ if (size == 0) {
+ /* By definition, this is equivalent to free(ptr) */
+ tracking_free(ptr);
+ return NULL;
+ }
+
+ /* Find the allocation entry for this memory */
+ entry = find_allocation(ptr);
+ if (entry == NULL) {
+ printf("Attempting to realloc unallocated memory at %p\n", ptr);
+ entry = malloc(sizeof(AllocationEntry));
+ if (entry == NULL) {
+ printf("Reallocator failure\n");
+ return NULL;
+ }
+ entry->allocation = realloc(ptr, size);
+ if (entry->allocation == NULL) {
+ free(entry);
+ return NULL;
+ }
+
+ /* Add to the list of allocations */
+ entry->next = NULL;
+ if (alloc_head == NULL) {
+ entry->prev = NULL;
+ alloc_head = alloc_tail = entry;
+ } else {
+ entry->prev = alloc_tail;
+ alloc_tail->next = entry;
+ alloc_tail = entry;
+ }
+ } else {
+ entry->allocation = realloc(ptr, size);
+ if (entry->allocation == NULL) {
+ /* Realloc semantics say the original is still allocated */
+ entry->allocation = ptr;
+ return NULL;
+ }
+ }
+
+ entry->num_bytes = size;
+ return entry->allocation;
+}
+
+int
+tracking_report(void)
+{
+ AllocationEntry *entry;
+
+ if (alloc_head == NULL)
+ return 1;
+
+ /* Otherwise we have allocations that haven't been freed */
+ for (entry = alloc_head; entry != NULL; entry = entry->next)
+ {
+ printf("Allocated %lu bytes at %p\n",
+ entry->num_bytes, entry->allocation);
+ }
+ return 0;
+}
--- /dev/null
+/* Copyright (c) 2017 The Expat Maintainers
+ * Copying is permitted under the MIT license. See the file COPYING
+ * for details.
+ *
+ * memcheck.h
+ *
+ * Interface to allocation functions that will track what has or has
+ * not been freed.
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef XML_MEMCHECK_H
+#define XML_MEMCHECK_H 1
+
+/* Allocation declarations */
+
+void *tracking_malloc(size_t size);
+void tracking_free(void *ptr);
+void *tracking_realloc(void *ptr, size_t size);
+
+/* End-of-test check to see if unfreed allocations remain. Returns
+ * TRUE (1) if there is nothing, otherwise prints a report of the
+ * remaining allocations and returns FALSE (0).
+ */
+int tracking_report(void);
+
+#endif /* XML_MEMCHECK_H */
+
+#ifdef __cplusplus
+}
+#endif
#include "chardata.h"
#include "internal.h" /* for UNUSED_P only */
#include "minicheck.h"
+#include "memcheck.h"
#if defined(__amigaos__) && defined(__USE_INLINE__)
#include <proto/expat.h>
}
END_TEST
+/* Regression test for GitHub Issue #17: memory leak parsing attribute
+ * values with mixed bound and unbound namespaces.
+ */
+START_TEST(test_misc_attribute_leak)
+{
+ const char *text = "<D xmlns:L=\"D\" l:a='' L:a=''/>";
+ XML_Memory_Handling_Suite memsuite = {
+ tracking_malloc,
+ tracking_realloc,
+ tracking_free
+ };
+
+ parser = XML_ParserCreate_MM("UTF-8", &memsuite, "\n");
+ expect_failure(text, XML_ERROR_UNBOUND_PREFIX,
+ "Unbound prefixes not found");
+ XML_ParserFree(parser);
+ /* Prevent the teardown trying to double free */
+ parser = NULL;
+
+ if (!tracking_report())
+ fail("Memory leak found");
+}
+END_TEST
+
static void
alloc_setup(void)
tcase_add_test(tc_misc, test_misc_alloc_ns_parse_buffer);
tcase_add_test(tc_misc, test_misc_error_string);
tcase_add_test(tc_misc, test_misc_version);
+ tcase_add_test(tc_misc, test_misc_attribute_leak);
suite_add_tcase(s, tc_alloc);
tcase_add_checked_fixture(tc_alloc, alloc_setup, alloc_teardown);
<ItemGroup>
<ClCompile Include="chardata.c" />
<ClCompile Include="minicheck.c" />
+ <ClCompile Include="memcheck.h" />
<ClCompile Include="runtests.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="chardata.h" />
<ClInclude Include="minicheck.h" />
+ <ClInclude Include="memcheck.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<ClCompile Include="minicheck.c">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="memcheck.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="runtests.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClInclude Include="minicheck.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="memcheck.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
</ItemGroup>
</Project>
\ No newline at end of file