1

I have following test in foo.cpp which I build with g++ ./foo.cpp -lgtest -lgtest_main -lpthread -O0 -ggdb:

#include "gtest/gtest.h"

struct s {};

class FooTest : public testing::TestWithParam<s> {};

TEST_P(FooTest, DoesBlah) {}

INSTANTIATE_TEST_SUITE_P(MeenyMinyMoe, FooTest, testing::Values(s()));

When I run it with valgrind ./a.out, valgrind reports a bunch of errors like the following:

==33778== Conditional jump or move depends on uninitialised value(s)
==33778==    at 0x4C317CC: _itoa_word (_itoa.c:180)
==33778==    by 0x4C4D6F4: __vfprintf_internal (vfprintf-internal.c:1687)
==33778==    by 0x4C62119: __vsnprintf_internal (vsnprintf.c:114)
==33778==    by 0x4C37F75: snprintf (snprintf.c:31)
==33778==    by 0x14200A: testing::(anonymous namespace)::PrintByteSegmentInObjectTo(unsigned char const*, unsigned long, unsigned long, std::ostream*) (in /home/scandit/private/a.out)
==33778==    by 0x1420AB: testing::(anonymous namespace)::PrintBytesInObjectToImpl(unsigned char const*, unsigned long, std::ostream*) (in /home/scandit/private/a.out)
==33778==    by 0x142151: testing::internal::PrintBytesInObjectTo(unsigned char const*, unsigned long, std::ostream*) (in /home/scandit/private/a.out)
==33778==    by 0x11C519: void testing::internal::RawBytesPrinter::PrintValue<s, 1ul>(s const&, std::ostream*) (gtest-printers.h:268)
==33778==    by 0x11C3C2: void testing::internal::PrintWithFallback<s>(s const&, std::ostream*) (gtest-printers.h:310)
==33778==    by 0x11C158: void testing::internal::PrintTo<s>(s const&, std::ostream*) (gtest-printers.h:439)
==33778==    by 0x11BF2E: testing::internal::UniversalPrinter<s>::Print(s const&, std::ostream*) (gtest-printers.h:701)
==33778==    by 0x11BB5B: void testing::internal::UniversalPrint<s>(s const&, std::ostream*) (gtest-printers.h:998)

When I use e.g. std::string instead of struct s in TestWithParam<...>, then I don't see any errors reported by valgrind. What is missing in my struct ?

I am using g++ (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0, valgrind-3.15.0 and gtest 1.11.0.

Update: Yes, I did try with a standard constructor, without any difference. I run now valgrind --track-origins=yes --num-callers=100 ./a.out.

#include "gtest/gtest.h"

struct s {
    s() {};
};

class FooTest : public testing::TestWithParam<s> {};

TEST_P(FooTest, DoesBlah) {}

INSTANTIATE_TEST_SUITE_P(MeenyMinyMoe, FooTest, testing::Values(s()));
==60500== Conditional jump or move depends on uninitialised value(s)
==60500==    at 0x4C317CC: _itoa_word (_itoa.c:180)
==60500==    by 0x4C4D6F4: __vfprintf_internal (vfprintf-internal.c:1687)
==60500==    by 0x4C62119: __vsnprintf_internal (vsnprintf.c:114)
==60500==    by 0x4C37F75: snprintf (snprintf.c:31)
==60500==    by 0x141868: testing::(anonymous namespace)::PrintByteSegmentInObjectTo(unsigned char const*, unsigned long, unsigned long, std::ostream*) (in /home/user/private/a.out)
==60500==    by 0x141909: testing::(anonymous namespace)::PrintBytesInObjectToImpl(unsigned char const*, unsigned long, std::ostream*) (in /home/user/private/a.out)
==60500==    by 0x1419AF: testing::internal::PrintBytesInObjectTo(unsigned char const*, unsigned long, std::ostream*) (in /home/user/private/a.out)
==60500==    by 0x11C407: void testing::internal::RawBytesPrinter::PrintValue<s, 1ul>(s const&, std::ostream*) (gtest-printers.h:270)
==60500==    by 0x11C2B0: void testing::internal::PrintWithFallback<s>(s const&, std::ostream*) (gtest-printers.h:312)
==60500==    by 0x11C046: void testing::internal::PrintTo<s>(s const&, std::ostream*) (gtest-printers.h:441)
==60500==    by 0x11BE1C: testing::internal::UniversalPrinter<s>::Print(s const&, std::ostream*) (gtest-printers.h:691)
==60500==    by 0x11BA49: void testing::internal::UniversalPrint<s>(s const&, std::ostream*) (gtest-printers.h:980)
==60500==    by 0x11B240: testing::internal::UniversalTersePrinter<s>::Print(s const&, std::ostream*) (gtest-printers.h:865)
==60500==    by 0x11A861: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > testing::PrintToString<s>(s const&) (gtest-printers.h:1018)
==60500==    by 0x119E9E: testing::internal::ParameterizedTestSuiteInfo<FooTest>::RegisterTests() (gtest-param-util.h:590)
==60500==    by 0x147534: testing::internal::ParameterizedTestSuiteRegistry::RegisterTests() (in /home/user/private/a.out)
==60500==    by 0x126425: testing::internal::UnitTestImpl::RegisterParameterizedTests() (in /home/user/private/a.out)
==60500==    by 0x135FD7: testing::internal::UnitTestImpl::PostFlagParsingInit() (in /home/user/private/a.out)
==60500==    by 0x153A81: void testing::internal::InitGoogleTestImpl<char>(int*, char**) (in /home/user/private/a.out)
==60500==    by 0x138B27: testing::InitGoogleTest(int*, char**) (in /home/user/private/a.out)
==60500==    by 0x16CFA6: main (in /home/user/private/a.out)
==60500==  Uninitialised value was created by a heap allocation
==60500==    at 0x483BE63: operator new(unsigned long) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==60500==    by 0x11C74A: testing::internal::ValuesInIteratorRangeGenerator<s>::Iterator::Current() const (gtest-param-util.h:334)
==60500==    by 0x11A5C4: testing::internal::ParamIterator<s>::operator*() const (gtest-param-util.h:137)
==60500==    by 0x119B18: testing::internal::ParameterizedTestSuiteInfo<FooTest>::RegisterTests() (gtest-param-util.h:572)
==60500==    by 0x147534: testing::internal::ParameterizedTestSuiteRegistry::RegisterTests() (in /home/user/private/a.out)
==60500==    by 0x126425: testing::internal::UnitTestImpl::RegisterParameterizedTests() (in /home/user/private/a.out)
==60500==    by 0x135FD7: testing::internal::UnitTestImpl::PostFlagParsingInit() (in /home/user/private/a.out)
==60500==    by 0x153A81: void testing::internal::InitGoogleTestImpl<char>(int*, char**) (in /home/user/private/a.out)
==60500==    by 0x138B27: testing::InitGoogleTest(int*, char**) (in /home/user/private/a.out)
==60500==    by 0x16CFA6: main (in /home/user/private/a.out)
user7005976
  • 345
  • 2
  • 5
  • Have you tried to add a standard constructor to your struct? – Tobias Lukoschek Nov 17 '21 at 09:03
  • Exactly! std::string *has* a default constructor which initializes it. Your struct does *not* have that, so it is uninitialized, just as Valgrind reports. – U. W. Nov 17 '21 at 09:07
  • This stack trace is truncated by 12 entries and therefore is unclear. Consider to rerun valgrind with `--num-callers=100` to see full stack trace. Also `--track-origins=yes` will be useful to track the origin of uninitialised values. – ks1322 Nov 17 '21 at 09:57
  • Yes, I did try a standard constructor. I updated my post. – user7005976 Nov 17 '21 at 13:07
  • This doesn't ring any bells, but can you try Valgrind 3.18.1? – Paul Floyd Nov 17 '21 at 23:29
  • I tried with valgrind 3.18.1, still the same unfortunately. – user7005976 Nov 18 '21 at 10:07
  • 1
    @user7005976, did you get any progress on this? I've [opened a bug](https://github.com/google/googletest/issues/3805) with GoogleTest using your example. – Leonardo Apr 25 '22 at 20:23
  • No, no progress. We ended up suppressing these errors. Thanks for opening the bug report! – user7005976 Apr 27 '22 at 06:35
  • 1
    For the next reader's information: The [bug](https://github.com/google/googletest/issues/3805) @Leonardo kindly opened and followed up on has been closed with the argumentation that Google uses Clang's MemorySanitizer and doesn't support valgrind. Unfortunately MemorySanitizer requires much more setup effort (requiring instrumentation of all used libraries) than valgrind. – Roland Sarrazin Jan 19 '23 at 09:25
  • I ended up creating a matching [valgrind suppression file](https://gist.github.com/rsarrazin2/917c00f470585bcec5a9d5d2375438c3). I haven't found out yet, though, how to restrict this suppression to the sole google test warnings. As we don't use printf and the like in our code base, we found it to be working. – Roland Sarrazin Jan 19 '23 at 10:04

2 Answers2

1

There's nothing wrong with your structure. The errors come from C++ not initializing the padding byte (sizeof(struct s) == 1), so valgrind duly reports that particular byte wasn't initialized (because it really wasn't). The C++ standard doesn't mandate zero-initialisation

Quoting cppreference.com Zero-initialization:

Note that this is not the syntax for zero-initialization, which does not have a dedicated syntax in the language. These are examples of other types of initializations, which might perform zero-initialization.

I've taken your example

#include "gtest/gtest.h"

struct s {};

class FooTest : public testing::TestWithParam<s> {};

TEST_P(FooTest, DoesBlah) {}

INSTANTIATE_TEST_SUITE_P(MeenyMinyMoe, FooTest, testing::Values(s()));

I've tried using the -ftrivial-auto-var-init=zero flag (from here), but that doesn't work because Google Test initialises using new (so not an automatic variable). So much for the easy fixes.

The solution I found is to overload the new and new[] operators:

#include <iostream>

#include "gtest/gtest.h"

struct s {

    static void* operator new(std::size_t count)
    {
        std::cout << "custom new for size " << count << '\n';
        void *p = ::operator new(count);
        memset(p, 0, count);
        return p;
    }
 
    static void* operator new[](std::size_t count)
    {
        std::cout << "custom new[] for size " << count << '\n';

        void *p = ::operator new[](count);
        memset(p, 0, count);
        return p;
    }
};

class FooTest : public testing::TestWithParam<s> {};

TEST_P(FooTest, DoesBlah) {}

INSTANTIATE_TEST_SUITE_P(MeenyMinyMoe, FooTest, testing::Values(s()));
$ g++ -O3 -Wall -g -o test test.cpp -lgtest -lgtest_main
$ valgrind --track-origins=yes ./test 
==73422== Memcheck, a memory error detector
==73422== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==73422== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==73422== Command: ./test
==73422== 
Running main() from ./googletest/src/gtest_main.cc
custom new for size 1
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from MeenyMinyMoe/FooTest
[ RUN      ] MeenyMinyMoe/FooTest.DoesBlah/0
[       OK ] MeenyMinyMoe/FooTest.DoesBlah/0 (6 ms)
[----------] 1 test from MeenyMinyMoe/FooTest (10 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (38 ms total)
[  PASSED  ] 1 test.
==73422== 
==73422== HEAP SUMMARY:
==73422==     in use at exit: 0 bytes in 0 blocks
==73422==   total heap usage: 217 allocs, 217 frees, 114,909 bytes allocated
==73422== 
==73422== All heap blocks were freed -- no leaks are possible
==73422== 
==73422== For lists of detected and suppressed errors, rerun with: -s
==73422== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Also check this answer for a better explanation.

Unfortunately I could not come up with a global replacement for new. If I use the example code in that page I get a ton of mismatched-new-delete warnings and running the resulting binary with valgrind generates a ton of Mismatched free() / delete / delete [] errors.

Cheers!

Leonardo
  • 1,533
  • 17
  • 28
0

A quick google for the gtest source:

https://clickhouse.com/codebrowser/html_report/ClickHouse/contrib/googletest/googletest/src/gtest-printers.cc.html

GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_
GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_
GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_
void PrintByteSegmentInObjectTo(const unsigned char* obj_bytes, size_t start,
                                size_t count, ostream* os) {
  char text[5] = "";
  for (size_t i = 0; i != count; i++) {
    const size_t j = start + i;
    if (i != 0) {
      // Organizes the bytes into groups of 2 for easy parsing by
      // human.
      if ((j % 2) == 0)
        *os << ' ';
      else
        *os << '-';
    }
    GTEST_SNPRINTF_(text, sizeof(text), "%02X", obj_bytes[j]);
    *os << text;
  }
}

With gdb you may be able to get from there back to your code. Only 15 layers to wade through.

Paul Floyd
  • 5,530
  • 5
  • 29
  • 43