From ec322eb428970890cced7717eb2715453ec2c1c5 Mon Sep 17 00:00:00 2001 From: Rhodri James Date: Wed, 26 Apr 2017 00:01:56 +0200 Subject: [PATCH] Test to catch Issue #17 --- expat/CMakeLists.txt | 4 +- expat/Makefile.in | 13 +- expat/tests/memcheck.c | 173 +++++++++++++++++++++++++++ expat/tests/memcheck.h | 34 ++++++ expat/tests/runtests.c | 26 ++++ expat/tests/runtests.vcxproj | 2 + expat/tests/runtests.vcxproj.filters | 6 + 7 files changed, 250 insertions(+), 8 deletions(-) create mode 100644 expat/tests/memcheck.c create mode 100644 expat/tests/memcheck.h diff --git a/expat/CMakeLists.txt b/expat/CMakeLists.txt index 103d31c7..d3bb7b80 100755 --- a/expat/CMakeLists.txt +++ b/expat/CMakeLists.txt @@ -142,12 +142,12 @@ endif(BUILD_examples) if(BUILD_tests) ## these are unittests that can be run on any platform - add_executable(runtests tests/runtests.c tests/chardata.c tests/minicheck.c) + add_executable(runtests tests/runtests.c tests/chardata.c tests/minicheck.c tests/memcheck.c) set_property(TARGET runtests PROPERTY RUNTIME_OUTPUT_DIRECTORY tests) target_link_libraries(runtests expat) add_test(runtests tests/runtests) - add_executable(runtestspp tests/runtestspp.cpp tests/chardata.c tests/minicheck.c) + add_executable(runtestspp tests/runtestspp.cpp tests/chardata.c tests/minicheck.c tests/memcheck.c) set_property(TARGET runtestspp PROPERTY RUNTIME_OUTPUT_DIRECTORY tests) target_link_libraries(runtestspp expat) add_test(runtestspp tests/runtestspp) diff --git a/expat/Makefile.in b/expat/Makefile.in index 07c7f46e..3fcbca34 100644 --- a/expat/Makefile.in +++ b/expat/Makefile.in @@ -165,12 +165,13 @@ examples/outline: examples/outline.@OBJEXT@ $(LIBRARY) 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) diff --git a/expat/tests/memcheck.c b/expat/tests/memcheck.c new file mode 100644 index 00000000..0b1e5330 --- /dev/null +++ b/expat/tests/memcheck.c @@ -0,0 +1,173 @@ +/* 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 +#include +#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; +} diff --git a/expat/tests/memcheck.h b/expat/tests/memcheck.h new file mode 100644 index 00000000..2c927241 --- /dev/null +++ b/expat/tests/memcheck.h @@ -0,0 +1,34 @@ +/* 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 diff --git a/expat/tests/runtests.c b/expat/tests/runtests.c index 84b5577f..8e74c34e 100644 --- a/expat/tests/runtests.c +++ b/expat/tests/runtests.c @@ -24,6 +24,7 @@ #include "chardata.h" #include "internal.h" /* for UNUSED_P only */ #include "minicheck.h" +#include "memcheck.h" #if defined(__amigaos__) && defined(__USE_INLINE__) #include @@ -2892,6 +2893,30 @@ START_TEST(test_misc_version) } 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 = ""; + 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) @@ -3431,6 +3456,7 @@ make_suite(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); diff --git a/expat/tests/runtests.vcxproj b/expat/tests/runtests.vcxproj index fc30805b..3ac7f3aa 100644 --- a/expat/tests/runtests.vcxproj +++ b/expat/tests/runtests.vcxproj @@ -126,11 +126,13 @@ + + diff --git a/expat/tests/runtests.vcxproj.filters b/expat/tests/runtests.vcxproj.filters index 95ebe9cd..ca66cb56 100644 --- a/expat/tests/runtests.vcxproj.filters +++ b/expat/tests/runtests.vcxproj.filters @@ -21,6 +21,9 @@ Source Files + + Source Files + Source Files @@ -32,5 +35,8 @@ Header Files + + Header Files + \ No newline at end of file -- 2.40.0