google / googletest

GoogleTest - Google Testing and Mocking Framework

Home Page:https://google.github.io/googletest/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Valgrind reports usage of uninitialized value(s) on simple gtest

leonardopsantos opened this issue · comments

Describe the bug

I'm having the exact same problem described at Valgrind reports usage of uninitialized value(s) on simple test.

When I build and run the very simple test below, I get several valgrind errors summarized below (full valgrind log is attached):

$ valgrind --track-origins=yes build/hello_test 
==2177342== Memcheck, a memory error detector
==2177342== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2177342== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
==2177342== Command: build/hello_test
==2177342== 
Running main() from /home/leo/work/googletest/bug/my_project/build/_deps/googletest-src/googletest/src/gtest_main.cc
==2177342== Use of uninitialised value of size 8
==2177342==    at 0x4BDC10A: _itoa_word (_itoa.c:180)
==2177342==    by 0x4BF7964: __vfprintf_internal (vfprintf-internal.c:1646)
==2177342==    by 0x4C0A439: __vsnprintf_internal (vsnprintf.c:114)
==2177342==    by 0x4BE2645: snprintf (snprintf.c:31)
==2177342==    by 0x125C87: testing::(anonymous namespace)::PrintByteSegmentInObjectTo(unsigned char const*, unsigned long, unsigned long, std::ostream*) (in /home/leo/work/googletest/bug/my_project/build/hello_test)
==2177342== Conditional jump or move depends on uninitialised value(s)
==2177342==    at 0x4BDC11C: _itoa_word (_itoa.c:180)
==2177342==    by 0x4BF7964: __vfprintf_internal (vfprintf-internal.c:1646)
==2177342==    by 0x4C0A439: __vsnprintf_internal (vsnprintf.c:114)
==2177342==    by 0x4BE2645: snprintf (snprintf.c:31)
==2177342==    by 0x125C87: testing::(anonymous namespace)::PrintByteSegmentInObjectTo(unsigned char const*, unsigned long, unsigned long, std::ostream*) (in /home/leo/work/googletest/bug/my_project/build/hello_test)
==2177342== Conditional jump or move depends on uninitialised value(s)
==2177342==    at 0x4BF85E3: __vfprintf_internal (vfprintf-internal.c:1646)
==2177342==    by 0x4C0A439: __vsnprintf_internal (vsnprintf.c:114)
==2177342==    by 0x4BE2645: snprintf (snprintf.c:31)
==2177342==    by 0x125C87: testing::(anonymous namespace)::PrintByteSegmentInObjectTo(unsigned char const*, unsigned long, unsigned long, std::ostream*) (in /home/leo/work/googletest/bug/my_project/build/hello_test)
==2177342== Conditional jump or move depends on uninitialised value(s)
==2177342==    at 0x4BF7A87: __vfprintf_internal (vfprintf-internal.c:1646)
==2177342==    by 0x4C0A439: __vsnprintf_internal (vsnprintf.c:114)
==2177342==    by 0x4BE2645: snprintf (snprintf.c:31)
==2177342==    by 0x125C87: testing::(anonymous namespace)::PrintByteSegmentInObjectTo(unsigned char const*, unsigned long, unsigned long, std::ostream*) (in /home/leo/work/googletest/bug/my_project/build/hello_test)
==2177342==  Uninitialised value was created by a heap allocation
==2177342==    at 0x4843FB3: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==2177342==    by 0x11C932: testing::internal::ValuesInIteratorRangeGenerator<s>::Iterator::Current() const (in /home/leo/work/googletest/bug/my_project/build/hello_test)

Steps to reproduce the bug

Follow the Quickstart: Building with CMake tutorial.

$ cd my_project/
$ rm -fr build && cmake -S . -B build && cmake --build build
-- The C compiler identification is GNU 11.2.0
-- The CXX compiler identification is GNU 11.2.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found Python: /usr/bin/python3.9 (found version "3.9.7") found components: Interpreter 
-- Looking for pthread.h
-- Looking for pthread.h - found
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Success
-- Found Threads: TRUE  
-- Configuring done
-- Generating done
-- Build files have been written to: /home/leo/work/googletest/bug/my_project/build
[ 10%] Building CXX object _deps/googletest-build/googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.o
[ 20%] Linking CXX static library ../../../lib/libgtest.a
[ 20%] Built target gtest
[ 30%] Building CXX object _deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/src/gtest_main.cc.o
[ 40%] Linking CXX static library ../../../lib/libgtest_main.a
[ 40%] Built target gtest_main
[ 50%] Building CXX object CMakeFiles/hello_test.dir/hello_test.cc.o
[ 60%] Linking CXX executable hello_test
[ 60%] Built target hello_test
[ 70%] Building CXX object _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/src/gmock-all.cc.o
[ 80%] Linking CXX static library ../../../lib/libgmock.a
[ 80%] Built target gmock
[ 90%] Building CXX object _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/src/gmock_main.cc.o
[100%] Linking CXX static library ../../../lib/libgmock_main.a
[100%] Built target gmock_main

$ valgrind --track-origins=yes build/hello_test &> valgrind.log

CMakeLists.txt:

cmake_minimum_required(VERSION 3.14)
project(my_project)

# GoogleTest requires at least C++11
set(CMAKE_CXX_STANDARD 11)

include(FetchContent)
FetchContent_Declare(
  googletest
  URL https://github.com/google/googletest/archive/bf66935e07825318ae519675d73d0f3e313b3ec6.zip
)

# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)

enable_testing()

add_executable(
  hello_test
  hello_test.cc
)
target_link_libraries(
  hello_test
  gtest_main
)

include(GoogleTest)
gtest_discover_tests(hello_test)

hello_test.cc

#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()));

Does the bug persist in the most recent commit?

Yes, it happens with SHA bf66935e07825318ae519675d73d0f3e313b3ec6

What operating system and version are you using?

  • Ubuntu 21.10

What compiler and version are you using?

  • gcc (Ubuntu 11.2.0-7ubuntu2) 11.2.0
  • g++ (Ubuntu 11.2.0-7ubuntu2) 11.2.0

What build system are you using?

  • cmake version 3.23.0

Additional context

valgrind.log

Not sure this problem isn't related to #108. I believe that in my case I am building GTest with Ubuntu's 21.10 stock GCC.

Digging a bit further, the problem seems to be that obj_bytes in PrintBytesInObjectTo indeed points to an uninitialized area. The code below triggers the exact same warnigs. Uncommenting the memset line make them go away:

main.cpp

#include <cstdio>
#include <cstring>

struct s {};

int main (void) {

	struct s value = s();

	const unsigned char* obj_bytes = (const unsigned char*) &value;
	size_t count = sizeof(value);

	// The next line make sthe warnings go away:
//	memset((void*)obj_bytes, 0, count);

	printf("[%d %s] count : %ld, obj_bytes : %p\n", __LINE__, __FUNCTION__, count, obj_bytes);
	for (size_t i = 0; i < count; i++) {
	  	printf("%ld: %02x", i, obj_bytes[i]);
	}
	printf("\n");
	

	return 0;
}

The first warning with the original GTest code is:

==2548045== Use of uninitialised value of size 8
==2548045==    at 0x4BDB10A: _itoa_word (_itoa.c:180)
==2548045==    by 0x4BF6964: __vfprintf_internal (vfprintf-internal.c:1646)
==2548045==    by 0x4BE158E: printf (printf.c:33)
==2548045==    by 0x127864: testing::internal::PrintBytesInObjectTo(unsigned char const*, unsigned long, std::ostream*) (gtest-printers.cc:147)
==2548045==    by 0x11D7C5: void testing::internal::RawBytesPrinter::PrintValue<s, 1ul>(s const&, std::ostream*) (gtest-printers.h:281)
==2548045==    by 0x11D66E: void testing::internal::PrintWithFallback<s>(s const&, std::ostream*) (gtest-printers.h:321)
==2548045==    by 0x11D404: void testing::internal::PrintTo<s>(s const&, std::ostream*) (gtest-printers.h:450)
==2548045==    by 0x11D1D9: testing::internal::UniversalPrinter<s>::Print(s const&, std::ostream*) (gtest-printers.h:712)
==2548045==    by 0x11CE09: void testing::internal::UniversalPrint<s>(s const&, std::ostream*) (gtest-printers.h:1009)
==2548045==    by 0x11C5FF: testing::internal::UniversalTersePrinter<s>::Print(s const&, std::ostream*) (gtest-printers.h:894)
==2548045==    by 0x11BBA1: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > testing::PrintToString<s>(s const&) (gtest-printers.h:1047)
==2548045==    by 0x11B138: testing::internal::ParameterizedTestSuiteInfo<FooTest>::RegisterTests() (gtest-param-util.h:596)

So maybe the problem is in RegisterTests (ParamGenerator?).

I think the problem is that in C++, the size of an empty struct is 1 (in C it is 0), and I guess there is no way to initialize that byte. I think this is the reason that PrintByteSegmentInObjectTo is tagged with GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ and friends. Perhaps we can use std::is_empty to detect this case.

@derekmauro thanks for taking a look into this. I was playing a little more and the problem is actually the alignment padding:

#include <cstdio>
#include <cstring>
#include <string>

struct Foo {};

struct Bar {
	char value;
};

struct FooBar {
	char a;
	unsigned short int b;

	FooBar(char _a, unsigned short int _b) : a(_a), b(_b) {};
};

void print_something(std::string name, const unsigned char* obj_bytes, const size_t count) {
	printf("Printing name: %s, count : %ld, obj_bytes : %p\n", name.c_str(), count, obj_bytes);
	for (size_t i = 0; i < count; i++) {
	  	printf("%ld: %02x\n", i, obj_bytes[i]);
	}
	printf("\n");
}

int main (void) {

	struct Foo foo = Foo();
	struct Bar bar = Bar();
	struct FooBar foobar = FooBar(0xAA, 0xBBCC);

	print_something("foo", (const unsigned char*) &foo, sizeof(foo));
	print_something("bar", (const unsigned char*) &bar, sizeof(bar));
	print_something("foobar", (const unsigned char*) &foobar, sizeof(foobar));

	return 0;
}

Calling print_something on:

  • foo will cause several warnings because its size is 1.
  • bar is OK because value is initialized to zero.
  • foobar will cause several warnings because of the padding byte between a and b.

I guess we just have to be mindful when using custom structures/classes as parameters.

@leonardopsantos Your analysis seems correct to me. We will have to think about whether there is anything we can do to make this more user friendly.

We currently consider valgrind to be unsupported. Today we use sanitizers like MemorySanitizer, which we consider a replacement, and the code is annotated to suppress the MemorySanitizer report.

For those not wanting to deal with the sanitiser, overloading new makes the errors go away:

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;
    }
};

and the code is annotated to suppress the MemorySanitizer report.

@derekmauro Is that the right thing to do, though? Reading uninitialized data is Undefined Behavior, why should the sanitizer warnings be suppressed?