C ++ - using tests with GTest parameter parameters using structs causes valgrind errors

Can you help me understand what is happening with GTest packaging and structure?

The problem seems to be related to how the structure is packaged when used as a value in a parameterized parameter in GTest. If you take a simple approach to creating a structure for each value, you will get valgrind errors related to uninitialized values.

Here is the code:

#include <gtest/gtest.h> struct TestItem { const char * aString; int anInt0; int anInt1; int anInt2; }; class TestBase : public ::testing::Test, public ::testing::WithParamInterface<TestItem> {}; TEST_P(TestBase, TestAtoi) { TestItem item = GetParam(); std::cout << sizeof(TestItem) << std::endl; ASSERT_FALSE(0); // actual test doesn't matter } INSTANTIATE_TEST_CASE_P( TestBaseInstantiation, TestBase, ::testing::Values( TestItem { "0", 0, 0, 0 } )); 

When this is compiled and linked (I built GTest with cmake):

 g++ gtest_valgrind.c -o gtest_valgrind -I gtest-1.7.0/include -L gtest-1.7.0/build -lgtest -lpthread -lgtest_main -std=c++11 

And then executed with:

 valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./gtest_valgrind 

The following conclusion is made:

 ==17290== Memcheck, a memory error detector ==17290== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al. ==17290== Using Valgrind-3.10.0.SVN and LibVEX; rerun with -h for copyright info ==17290== Command: ./gtest_valgrind ==17290== Running main() from gtest_main.cc ==17290== Use of uninitialised value of size 8 ==17290== at 0x55B89F1: _itoa_word (_itoa.c:180) ==17290== by 0x55BC6F6: vfprintf (vfprintf.c:1660) ==17290== by 0x55E1578: vsnprintf (vsnprintf.c:119) ==17290== by 0x55C3531: snprintf (snprintf.c:33) ==17290== by 0x41F107: testing::(anonymous namespace)::PrintByteSegmentInObjectTo(unsigned char const*, unsigned long, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x41F1AA: testing::(anonymous namespace)::PrintBytesInObjectToImpl(unsigned char const*, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x41F252: testing::internal2::PrintBytesInObjectTo(unsigned char const*, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409B8E: testing::internal2::TypeWithoutFormatter<TestItem, (testing::internal2::TypeKind)2>::PrintValue(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409B63: std::basic_ostream<char, std::char_traits<char> >& testing::internal2::operator<< <char, std::char_traits<char>, TestItem>(std::basic_ostream<char, std::char_traits<char> >&, TestItem const&) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409B3E: void testing_internal::DefaultPrintNonContainerTo<TestItem>(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409B19: void testing::internal::DefaultPrintTo<TestItem>(char, testing::internal::bool_constant<false>, TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409ADB: void testing::internal::PrintTo<TestItem>(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== Uninitialised value was created by a stack allocation ==17290== at 0x404AAE: gtest_TestBaseInstantiationTestBase_EvalGenerator_() (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== ==17290== Conditional jump or move depends on uninitialised value(s) ==17290== at 0x55B89F8: _itoa_word (_itoa.c:180) ==17290== by 0x55BC6F6: vfprintf (vfprintf.c:1660) ==17290== by 0x55E1578: vsnprintf (vsnprintf.c:119) ==17290== by 0x55C3531: snprintf (snprintf.c:33) ==17290== by 0x41F107: testing::(anonymous namespace)::PrintByteSegmentInObjectTo(unsigned char const*, unsigned long, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x41F1AA: testing::(anonymous namespace)::PrintBytesInObjectToImpl(unsigned char const*, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x41F252: testing::internal2::PrintBytesInObjectTo(unsigned char const*, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409B8E: testing::internal2::TypeWithoutFormatter<TestItem, (testing::internal2::TypeKind)2>::PrintValue(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409B63: std::basic_ostream<char, std::char_traits<char> >& testing::internal2::operator<< <char, std::char_traits<char>, TestItem>(std::basic_ostream<char, std::char_traits<char> >&, TestItem const&) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409B3E: void testing_internal::DefaultPrintNonContainerTo<TestItem>(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409B19: void testing::internal::DefaultPrintTo<TestItem>(char, testing::internal::bool_constant<false>, TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409ADB: void testing::internal::PrintTo<TestItem>(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== Uninitialised value was created by a stack allocation ==17290== at 0x404AAE: gtest_TestBaseInstantiationTestBase_EvalGenerator_() (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== ==17290== Conditional jump or move depends on uninitialised value(s) ==17290== at 0x55BC742: vfprintf (vfprintf.c:1660) ==17290== by 0x55E1578: vsnprintf (vsnprintf.c:119) ==17290== by 0x55C3531: snprintf (snprintf.c:33) ==17290== by 0x41F107: testing::(anonymous namespace)::PrintByteSegmentInObjectTo(unsigned char const*, unsigned long, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x41F1AA: testing::(anonymous namespace)::PrintBytesInObjectToImpl(unsigned char const*, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x41F252: testing::internal2::PrintBytesInObjectTo(unsigned char const*, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409B8E: testing::internal2::TypeWithoutFormatter<TestItem, (testing::internal2::TypeKind)2>::PrintValue(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409B63: std::basic_ostream<char, std::char_traits<char> >& testing::internal2::operator<< <char, std::char_traits<char>, TestItem>(std::basic_ostream<char, std::char_traits<char> >&, TestItem const&) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409B3E: void testing_internal::DefaultPrintNonContainerTo<TestItem>(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409B19: void testing::internal::DefaultPrintTo<TestItem>(char, testing::internal::bool_constant<false>, TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409ADB: void testing::internal::PrintTo<TestItem>(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409AA6: testing::internal::UniversalPrinter<TestItem>::Print(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== Uninitialised value was created by a stack allocation ==17290== at 0x404AAE: gtest_TestBaseInstantiationTestBase_EvalGenerator_() (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== ==17290== Conditional jump or move depends on uninitialised value(s) ==17290== at 0x55B9659: vfprintf (vfprintf.c:1660) ==17290== by 0x55E1578: vsnprintf (vsnprintf.c:119) ==17290== by 0x55C3531: snprintf (snprintf.c:33) ==17290== by 0x41F107: testing::(anonymous namespace)::PrintByteSegmentInObjectTo(unsigned char const*, unsigned long, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x41F1AA: testing::(anonymous namespace)::PrintBytesInObjectToImpl(unsigned char const*, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x41F252: testing::internal2::PrintBytesInObjectTo(unsigned char const*, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409B8E: testing::internal2::TypeWithoutFormatter<TestItem, (testing::internal2::TypeKind)2>::PrintValue(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409B63: std::basic_ostream<char, std::char_traits<char> >& testing::internal2::operator<< <char, std::char_traits<char>, TestItem>(std::basic_ostream<char, std::char_traits<char> >&, TestItem const&) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409B3E: void testing_internal::DefaultPrintNonContainerTo<TestItem>(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409B19: void testing::internal::DefaultPrintTo<TestItem>(char, testing::internal::bool_constant<false>, TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409ADB: void testing::internal::PrintTo<TestItem>(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409AA6: testing::internal::UniversalPrinter<TestItem>::Print(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== Uninitialised value was created by a stack allocation ==17290== at 0x404AAE: gtest_TestBaseInstantiationTestBase_EvalGenerator_() (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== ==17290== Conditional jump or move depends on uninitialised value(s) ==17290== at 0x55B96DC: vfprintf (vfprintf.c:1660) ==17290== by 0x55E1578: vsnprintf (vsnprintf.c:119) ==17290== by 0x55C3531: snprintf (snprintf.c:33) ==17290== by 0x41F107: testing::(anonymous namespace)::PrintByteSegmentInObjectTo(unsigned char const*, unsigned long, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x41F1AA: testing::(anonymous namespace)::PrintBytesInObjectToImpl(unsigned char const*, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x41F252: testing::internal2::PrintBytesInObjectTo(unsigned char const*, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409B8E: testing::internal2::TypeWithoutFormatter<TestItem, (testing::internal2::TypeKind)2>::PrintValue(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409B63: std::basic_ostream<char, std::char_traits<char> >& testing::internal2::operator<< <char, std::char_traits<char>, TestItem>(std::basic_ostream<char, std::char_traits<char> >&, TestItem const&) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409B3E: void testing_internal::DefaultPrintNonContainerTo<TestItem>(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409B19: void testing::internal::DefaultPrintTo<TestItem>(char, testing::internal::bool_constant<false>, TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409ADB: void testing::internal::PrintTo<TestItem>(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== by 0x409AA6: testing::internal::UniversalPrinter<TestItem>::Print(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== Uninitialised value was created by a stack allocation ==17290== at 0x404AAE: gtest_TestBaseInstantiationTestBase_EvalGenerator_() (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind) ==17290== [==========] Running 1 test from 1 test case. [----------] Global test environment set-up. [----------] 1 test from TestBaseInstantiation/TestBase [ RUN ] TestBaseInstantiation/TestBase.TestAtoi/0 24 [ OK ] TestBaseInstantiation/TestBase.TestAtoi/0 (76 ms) [----------] 1 test from TestBaseInstantiation/TestBase (126 ms total) [----------] Global test environment tear-down [==========] 1 test from 1 test case ran. (246 ms total) [ PASSED ] 1 test. ==17290== ==17290== HEAP SUMMARY: ==17290== in use at exit: 0 bytes in 0 blocks ==17290== total heap usage: 239 allocs, 239 frees, 46,622 bytes allocated ==17290== ==17290== All heap blocks were freed -- no leaks are possible ==17290== ==17290== For counts of detected and suppressed errors, rerun with: -v ==17290== ERROR SUMMARY: 20 errors from 5 contexts (suppressed: 0 from 0) 

This is a lot of output, but essentially I think this indicates that there is something unusual in the structure created because of the TestItem { "0", 0, 0, 0 } in INSTANTIATE_TEST_CASE_P .

Please note that the size of TestItem is displayed as 24. On a 64-bit system, I'm not quite sure how to put up with this. 8 bytes for char * and 4 * 3 = 12 bytes for int members. If they are aligned with 8 bytes, this should be 24 + 4 = 28 bytes in size (or 32 if packaging is included). If they are aligned with 4 bytes, then it should be 20. There is something I don’t understand here about the packaging of the structure.

The valgrind warning can be eliminated by packing the structure in different ways. For example, all three of these modifications result in a clean run of valgrind:

Using the #pragma package:

 #pragma pack(1) struct TestItem { const char * aString; int anInt0; int anInt1; int anInt2; }; 

This outputs 20 .

Using the GNU g ++ Packaging Attribute:

 struct TestItem { const char * aString; int anInt0; int anInt1; int anInt2; } __attribute__((packed)); 

This also outputs 20 .

Finally, adding a dummy value to the struct:

 struct TestItem { const char * aString; int anInt0; int anInt1; int anInt2; int anInt3; // add an extra member }; 

This outputs 24 .

I have a sufficient amount of project code that uses this exact method for tests with parameterization of parameters, and in all cases valgrind will complain if one of the avoidance strategies is not used.

I would like to know if there is something fundamentally wrong with my approach to creating values ​​for this test, or is it a consequence of something that gtest does with creating test instances? Or, which is unlikely, is this the most dangerous mistake?

+5
source share
1 answer

The sizeof output for the TestItem structure is the result of compiler alignment of the structure and a delayed addition .

From the above link:

[...] This is the first address following the structure data that has the same alignment as the structure .

The general rule for filling in the complement structure is as follows: the compiler will behave as if the structure ends with the address of the step. This rule controls the return of sizeof ().

Consider this example on a 64-bit x86 or ARM machine:

 struct foo3 { char *p; /* 8 bytes */ char c; /* 1 byte */ }; struct foo3 singleton; struct foo3 quad[4]; 

You might think that sizeof(struct foo3) should be 9, but actually it is 16. The step address is the address (& p) [2]. Thus, in a four-dimensional array, each member has 7 bytes of delayed laying because the first member of each next struct wants to be self-installing on an 8-byte boundary. The memory layout, as if the structure was declared as follows:

 struct foo3 { char *p; /* 8 bytes */ char c; /* 1 byte */ char pad[7]; }; 

This explains why you get 24 for sizeof(TestItem) , since the final sizeof(TestItem) will align the structure to a multiple of sizeof (const char*) , which is 8.

These trailing padding bytes are not initialized, and this is what valgrind reports. Gtest runs some code to print the actual value of the TestItem parameter when the test fails. This can be confirmed if you make your test pass, and valgrind does not show an error.

When you force the compiler to use a specific alignment or add a new member to the structure so that the structure does not need any trailing addition, valgrind does not detect any uninitialized bytes in TestItem instances.

Gtest usually calls operator<< when printing values ​​and returns back to print the raw byte array in the object if it is not available. This is probably accessed by uninitialized padding bytes. This way you can also get rid of valgrind errors by specifying operator<< for TestItem .

+5
source

Source: https://habr.com/ru/post/1236100/


All Articles