diff --git a/src/ua_types.c b/src/ua_types.c index e81b960b2..13fa294fd 100644 --- a/src/ua_types.c +++ b/src/ua_types.c @@ -133,6 +133,9 @@ UA_DataType_copy(const UA_DataType *t1, UA_DataType *t2) { t2->xmlEncodingId = UA_NODEID_NULL; t2->members = NULL; t2->membersSize = 0; +#ifdef UA_ENABLE_TYPEDESCRIPTION + t2->typeName = NULL; +#endif /* Copy the typename */ #ifdef UA_ENABLE_TYPEDESCRIPTION @@ -173,6 +176,7 @@ UA_DataType_copy(const UA_DataType *t1, UA_DataType *t2) { if(m1->memberType == t1) m2->memberType = t2; #ifdef UA_ENABLE_TYPEDESCRIPTION + m2->memberName = NULL; nameLen = strlen(m1->memberName) + 1; char *mName = (char*)UA_malloc(nameLen); if(!mName) { diff --git a/src/ua_types_definition.c b/src/ua_types_definition.c index 836286182..92ffc16dc 100644 --- a/src/ua_types_definition.c +++ b/src/ua_types_definition.c @@ -168,8 +168,11 @@ UA_DataType_fromStructureDescription(UA_DataType *type, type->typeKind = UA_DATATYPEKIND_UNION; break; case UA_STRUCTURETYPE_STRUCTUREWITHSUBTYPEDVALUES: case UA_STRUCTURETYPE_UNIONWITHSUBTYPEDVALUES: + UA_DataType_clear(type); return UA_STATUSCODE_BADNOTIMPLEMENTED; - default: return UA_STATUSCODE_BADINTERNALERROR; + default: + UA_DataType_clear(type); + return UA_STATUSCODE_BADINTERNALERROR; } /* Copy NodeIds */ @@ -384,9 +387,18 @@ UA_DataType_toStructureDescription(const UA_DataType *type, UA_StructureField *sf = &sd->fields[i]; const UA_DataTypeMember *dtm = &type->members[i]; #ifdef UA_ENABLE_TYPEDESCRIPTION - sf->name = UA_String_fromChars(dtm->memberName); + UA_String name = UA_STRING((char*)(uintptr_t)dtm->memberName); + res = UA_String_copy(&name, &sf->name); + if(res != UA_STATUSCODE_GOOD) { + UA_StructureDescription_clear(descr); + return res; + } #endif - res |= UA_NodeId_copy(&dtm->memberType->typeId, &sf->dataType); + res = UA_NodeId_copy(&dtm->memberType->typeId, &sf->dataType); + if(res != UA_STATUSCODE_GOOD) { + UA_StructureDescription_clear(descr); + return res; + } sf->valueRank = (dtm->isArray) ? UA_VALUERANK_ONE_DIMENSION : UA_VALUERANK_SCALAR; sf->isOptional = dtm->isOptional; if(dtm->isArray && (type->typeKind == UA_DATATYPEKIND_STRUCTURE || @@ -486,8 +498,15 @@ UA_DataType_toEnumDescription(const UA_DataType *type, UA_EnumField *ef = &descr->enumDefinition.fields[i]; ef->value = (UA_Int64)(uintptr_t)dtm->memberType; #ifdef UA_ENABLE_TYPEDESCRIPTION - ef->name = UA_STRING_ALLOC(dtm->memberName); - ef->displayName = UA_LOCALIZEDTEXT_ALLOC("", dtm->memberName); + UA_String name = UA_STRING((char*)(uintptr_t)dtm->memberName); + ef->displayName.locale = UA_STRING_ALLOC(""); + res = UA_String_copy(&name, &ef->name); + if(res == UA_STATUSCODE_GOOD) + res = UA_String_copy(&name, &ef->displayName.text); + if(res != UA_STATUSCODE_GOOD) { + UA_EnumDescription_clear(descr); + return res; + } #endif } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 10904cb32..fd7af702f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -254,6 +254,7 @@ ua_add_test(check_utils_trustlist_path.c) ua_add_test(check_types_copy_complex.c) ua_add_test(check_types_print.c) ua_add_test(check_types_definition.c) +ua_add_test(check_types_allocation_failures.c) ua_add_test(check_encoding_roundtrip.c) ua_add_test(check_types_builtin_binary.c) ua_add_test(check_utils_url_kvm.c) diff --git a/tests/check_types_allocation_failures.c b/tests/check_types_allocation_failures.c new file mode 100644 index 000000000..15a078a15 --- /dev/null +++ b/tests/check_types_allocation_failures.c @@ -0,0 +1,159 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include +#include +#include +#include + +#if defined(UA_ENABLE_MALLOC_SINGLETON) && defined(UA_TYPES_STRUCTUREDESCRIPTION) +static size_t allocationIndex, failingAllocation, live; +static void *owned[1024]; + +static void * +remember(void *ptr) { + if(ptr) { + for(size_t i = 0; i < 1024; i++) { + if(!owned[i]) { owned[i] = ptr; live++; return ptr; } + } + ck_abort_msg("allocation tracker exhausted"); + } + return ptr; +} +static void *testMalloc(size_t size) { + return allocationIndex++ == failingAllocation ? NULL : remember(malloc(size)); +} +static void *testCalloc(size_t count, size_t size) { + return allocationIndex++ == failingAllocation ? NULL : remember(calloc(count, size)); +} +static void testFree(void *ptr) { + if(!ptr) return; + for(size_t i = 0; i < 1024; i++) { + if(owned[i] == ptr) { owned[i] = NULL; live--; free(ptr); return; } + } + ck_abort_msg("attempt to free borrowed datatype storage"); +} +static void *testRealloc(void *ptr, size_t size) { + if(allocationIndex++ == failingAllocation) return NULL; + if(!ptr) return remember(realloc(NULL, size)); + for(size_t i = 0; i < 1024; i++) { + if(owned[i] == ptr) { + void *next = realloc(ptr, size); + if(next || size == 0) { owned[i] = next; if(!next) live--; } + return next; + } + } + ck_abort_msg("attempt to resize borrowed datatype storage"); + return NULL; +} +static void setup(void) { + allocationIndex = 0; failingAllocation = (size_t)-1; live = 0; + memset(owned, 0, sizeof(owned)); + UA_mallocSingleton = testMalloc; UA_callocSingleton = testCalloc; + UA_reallocSingleton = testRealloc; UA_freeSingleton = testFree; +} +static void teardown(void) { + UA_mallocSingleton = malloc; UA_callocSingleton = calloc; + UA_reallocSingleton = realloc; UA_freeSingleton = free; + for(size_t i = 0; i < 1024; i++) free(owned[i]); +} + +static UA_StatusCode +operation(int mode) { + typedef struct { UA_UInt32 number; UA_String text; } Record; + UA_DataTypeMember members[] = { + {UA_TYPENAME("Number") &UA_TYPES[UA_TYPES_UINT32], 0, false, false}, + {UA_TYPENAME("Text") &UA_TYPES[UA_TYPES_STRING], + offsetof(Record, text) - sizeof(UA_UInt32), false, false}}; + UA_DataType type; + memset(&type, 0, sizeof(type)); +#ifdef UA_ENABLE_TYPEDESCRIPTION + type.typeName = "AllocationProbe"; +#endif + type.typeId = UA_NODEID_STRING(1, "ProbeType"); + type.binaryEncodingId = UA_NODEID_STRING(1, "Probe.Binary"); + type.xmlEncodingId = UA_NODEID_STRING(1, "Probe.XML"); + type.typeKind = UA_DATATYPEKIND_STRUCTURE; type.memSize = sizeof(Record); + type.membersSize = 2; type.members = members; + if(mode == 0) { + UA_DataType copy; + UA_StatusCode res = UA_DataType_copy(&type, ©); + if(res == UA_STATUSCODE_GOOD) { +#ifdef UA_ENABLE_TYPEDESCRIPTION + ck_assert_str_eq(copy.typeName, "AllocationProbe"); + ck_assert_str_eq(copy.members[1].memberName, "Text"); +#endif + ck_assert(UA_NodeId_equal(&type.typeId, ©.typeId)); + ck_assert_ptr_ne(type.typeId.identifier.string.data, copy.typeId.identifier.string.data); + } + UA_DataType_clear(©); + return res; + } + if(mode == 3) { + UA_StructureDescription description; + UA_StructureDescription_init(&description); + description.dataTypeId = type.typeId; + description.name = UA_QUALIFIEDNAME(0, "Unsupported"); + description.structureDefinition.structureType = UA_STRUCTURETYPE_STRUCTUREWITHSUBTYPEDVALUES; + UA_ExtensionObject object; + UA_ExtensionObject_setValueNoDelete(&object, &description, &UA_TYPES[UA_TYPES_STRUCTUREDESCRIPTION]); + UA_DataType output; + UA_StatusCode res = UA_DataType_fromDescription(&output, &object, NULL); + ck_assert_uint_ne(res, UA_STATUSCODE_GOOD); + ck_assert(UA_NodeId_isNull(&output.typeId)); + UA_DataType_clear(&output); + return res; + } + if(mode == 2) { + type.typeKind = UA_DATATYPEKIND_ENUM; type.memSize = sizeof(UA_Int32); + members[0].memberType = (const UA_DataType*)(intptr_t)-7; + members[1].memberType = (const UA_DataType*)(intptr_t)42; + } + UA_ExtensionObject description; + UA_StatusCode res = UA_DataType_toDescription(&type, &description); + if(res == UA_STATUSCODE_GOOD) { +#ifdef UA_ENABLE_TYPEDESCRIPTION + UA_String expected = UA_STRING("Text"); + if(mode == 2) { + UA_EnumDescription *d = (UA_EnumDescription*)description.content.decoded.data; + ck_assert(UA_String_equal(&d->enumDefinition.fields[1].name, &expected)); + } else { + UA_StructureDescription *d = (UA_StructureDescription*)description.content.decoded.data; + ck_assert(UA_String_equal(&d->structureDefinition.fields[1].name, &expected)); + } +#endif + } + UA_ExtensionObject_clear(&description); + return res; +} + +START_TEST(everyAllocationCanFail) { + UA_StatusCode result = operation(_i); + ck_assert_uint_eq(result, _i == 3 ? UA_STATUSCODE_BADNOTIMPLEMENTED : UA_STATUSCODE_GOOD); + size_t count = allocationIndex; + ck_assert_uint_gt(count, 0); + ck_assert_uint_eq(live, 0); + for(size_t failure = 0; failure < count; failure++) { + allocationIndex = 0; failingAllocation = failure; + result = operation(_i); + ck_assert_uint_eq(result, UA_STATUSCODE_BADOUTOFMEMORY); + ck_assert_uint_eq(live, 0); + } +} END_TEST +#endif + +int main(void) { + Suite *s = suite_create("Datatype allocation failures"); + TCase *tc = tcase_create("Ownership and description conversion"); +#if defined(UA_ENABLE_MALLOC_SINGLETON) && defined(UA_TYPES_STRUCTUREDESCRIPTION) + tcase_add_checked_fixture(tc, setup, teardown); + tcase_add_loop_test(tc, everyAllocationCanFail, 0, 4); +#endif + suite_add_tcase(s, tc); + SRunner *runner = srunner_create(s); + srunner_set_fork_status(runner, CK_NOFORK); + srunner_run_all(runner, CK_NORMAL); + int failed = srunner_ntests_failed(runner); + srunner_free(runner); + return failed ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/tests/check_types_definition.c b/tests/check_types_definition.c index cdef4f9ab..297a5e91e 100644 --- a/tests/check_types_definition.c +++ b/tests/check_types_definition.c @@ -111,8 +111,45 @@ START_TEST(definition_emptyStructureHasStorage) { UA_DataType_clear(&type); } END_TEST +START_TEST(definition_copyOwnedNodeIds) { + UA_DataType original = UA_TYPES[UA_TYPES_ARGUMENT]; + original.typeId = UA_NODEID_STRING(2, "Argument"); + original.binaryEncodingId = UA_NODEID_STRING(3, "Argument.Binary"); + original.xmlEncodingId = UA_NODEID_STRING(4, "Argument.XML"); + UA_DataType copied; + ck_assert_uint_eq(UA_DataType_copy(&original, &copied), UA_STATUSCODE_GOOD); + ck_assert(UA_NodeId_equal(&original.typeId, &copied.typeId)); + ck_assert_ptr_ne(original.typeId.identifier.string.data, + copied.typeId.identifier.string.data); + ck_assert(UA_NodeId_equal(&original.binaryEncodingId, &copied.binaryEncodingId)); + ck_assert(UA_NodeId_equal(&original.xmlEncodingId, &copied.xmlEncodingId)); + UA_DataType_clear(&copied); + /* Clearing the copy must leave the borrowed original strings intact. */ + ck_assert_uint_eq(original.xmlEncodingId.identifier.string.data[0], 'A'); +} END_TEST + +START_TEST(definition_invalidStructureKindClearsOutput) { + UA_StructureDescription description; + UA_StructureDescription_init(&description); + description.dataTypeId = UA_NODEID_STRING(1, "Unsupported"); + description.name = UA_QUALIFIEDNAME(1, "Unsupported"); + description.structureDefinition.structureType = (_i == 0) ? + UA_STRUCTURETYPE_STRUCTUREWITHSUBTYPEDVALUES : (UA_StructureType)99; + UA_ExtensionObject object; + UA_ExtensionObject_setValueNoDelete(&object, &description, &UA_TYPES[UA_TYPES_STRUCTUREDESCRIPTION]); + UA_DataType output; + ck_assert_uint_ne(UA_DataType_fromDescription(&output, &object, NULL), UA_STATUSCODE_GOOD); + ck_assert(UA_NodeId_isNull(&output.typeId)); +#ifdef UA_ENABLE_TYPEDESCRIPTION + ck_assert_ptr_eq(output.typeName, NULL); +#endif + UA_DataType_clear(&output); +} END_TEST + static Suite *testSuite_definition(void) { TCase *tc = tcase_create("DataTypeDefinition"); + tcase_add_loop_test(tc, definition_invalidStructureKindClearsOutput, 0, 2); + tcase_add_test(tc, definition_copyOwnedNodeIds); tcase_add_test(tc, definition_roundtrip_structure); tcase_add_test(tc, definition_roundtrip_structure_nested); tcase_add_test(tc, definition_roundtrip_simpleType);