Search code examples
c++pointersgoogletestgooglemock

c++: Why does this print statement deallocate the value stored inside of a pointer?


I have a googletest that is meant to unittest a function that calls an external fortran function by mocking the external call:

#include <iostream>
#include <gtest/gtest.h>
#include <gmock/gmock.h>

class InterfaceTest : public ::testing::Test {};
class MockFortranFuncInterfacePointerArgs {
public:
  MOCK_METHOD(void, fortran_interface_test_func_pointerargs, (int*));
};

void testfunc_pointer(MockFortranFuncInterfacePointerArgs* func_interface) {
  int testscalar = 123;
  func_interface->fortran_interface_test_func_pointerargs(&testscalar);
}
TEST_F(InterfaceTest, TestFuncInterfacePointerArgs) {
  MockFortranFuncInterfacePointerArgs mock_func_interface;
  int* passed_scalar_pointer = nullptr;
  EXPECT_CALL(mock_func_interface, fortran_interface_test_func_pointerargs(testing::_))
    .WillOnce(testing::DoAll(
        testing::SaveArg<0>(&passed_scalar_pointer)
    ));
  testfunc_pointer(&mock_func_interface);
  std::cout << *passed_scalar_pointer << std::endl; //prints 123

}

int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

I want to verify what value the fortran function is being called with, which works fine. However, simply adding another cout call before the std::cout << *passed_scalar_pointer << std::endl; statement suddenly causes the program to deallocate the value stored in the original pointer:

#include <iostream>
#include <gtest/gtest.h>
#include <gmock/gmock.h>

class InterfaceTest : public ::testing::Test {};
class MockFortranFuncInterfacePointerArgs {
public:
  MOCK_METHOD(void, fortran_interface_test_func_pointerargs, (int*));
};

void testfunc_pointer(MockFortranFuncInterfacePointerArgs* func_interface) {
  int testscalar = 123;
  func_interface->fortran_interface_test_func_pointerargs(&testscalar);
}
TEST_F(InterfaceTest, TestFuncInterfacePointerArgs) {
  MockFortranFuncInterfacePointerArgs mock_func_interface;
  int* passed_scalar_pointer = nullptr;
  EXPECT_CALL(mock_func_interface, fortran_interface_test_func_pointerargs(testing::_))
    .WillOnce(testing::DoAll(
        testing::SaveArg<0>(&passed_scalar_pointer)
    ));
  testfunc_pointer(&mock_func_interface);
  std::cout << "testprint" << std::endl;
  std::cout << *passed_scalar_pointer << std::endl; //prints random integer

}

int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

How can a simple print statement cause such behaviour? What exactly does std::cout << "testprint" << std::endl; do that could modify a value that is stored inside of a variable?


Solution

  • I want to verify what value the fortran function is being called with, which works fine. However, simply adding another cout call before the std::cout << *passed_scalar_pointer << std::endl; statement suddenly causes the program to deallocate the value stored in the original pointer:

    Here is your code run with address sanitizer.

    Basically problem is that you are passing a pointer to local variable which lifetime ends when testfunc_pointer execution ends. So this is source of UB in your test.

    IMO your test is invalid since tries to verify implementation detail (value of testscalar inside a testfunc_pointer). It is impossible to fix your test since testfunc_pointer is oversimplified and can't tel what actually test should do.

    By filing gaps (I ca be wrong), this should have following form:

    #include <gmock/gmock.h>
    #include <gtest/gtest.h>
    #include <iostream>
    
    class InterfaceTest : public ::testing::Test { };
    
    class MockFortranFuncInterfacePointerArgs {
    public:
        MOCK_METHOD(void, fortran_interface_test_func_pointerargs, (int*));
    };
    
    int testfunc_pointer(MockFortranFuncInterfacePointerArgs* func_interface)
    {
        int testscalar;
        func_interface->fortran_interface_test_func_pointerargs(&testscalar);
    
        return testscalar + 55;
    }
    
    TEST_F(InterfaceTest, WhenFortranFunctionProvidesValueResultIsGraterBy55)
    {
        MockFortranFuncInterfacePointerArgs mock_func_interface;
    
        const int fortranValue = 11;
    
        EXPECT_CALL(mock_func_interface, fortran_interface_test_func_pointerargs(testing::NotNull()))
            .WillOnce(testing::Invoke([fortranValue](auto* p) { *p = fortranValue; }));
    
        EXPECT_THAT(testfunc_pointer(&mock_func_interface), fortranValue + 55);
    }
    

    https://godbolt.org/z/5qTscK47G

    If you expecting that Fortran function should get value (not provide a value) by pointer, the proper solution is:

    #include <gmock/gmock.h>
    #include <gtest/gtest.h>
    #include <iostream>
    
    class InterfaceTest : public ::testing::Test { };
    
    class MockFortranFuncInterfacePointerArgs {
    public:
        MOCK_METHOD(void, fortran_interface_test_func_pointerargs, (int*));
    };
    
    void testfunc_pointer(MockFortranFuncInterfacePointerArgs* func_interface)
    {
        int testscalar = 123;
        func_interface->fortran_interface_test_func_pointerargs(&testscalar);
    }
    
    TEST_F(InterfaceTest, FortranFunctionProvidesValue123)
    {
        MockFortranFuncInterfacePointerArgs mock_func_interface;
        EXPECT_CALL(mock_func_interface, fortran_interface_test_func_pointerargs(testing::Pointee(123)));
        testfunc_pointer(&mock_func_interface);
    }
    

    https://godbolt.org/z/G5jaW3c91 failure example

    Side note:

    Do not use std::cout in tests! Use assertion to express what is expected. If it fails gtest will print actual value.