Related
I am working on emulating rpm -qa using my own code that uses the librpm library. I am doing this as initial experimentation for a larger program that will analyze installed software for security purposes.
For now, I only open the RPM DB and close it without reading anything.
When I compare the output of valgrind for my code and against the valgrind output for rpm -qa, here are the results:
$ valgrind ./leaky
==8201== Memcheck, a memory error detector
==8201== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==8201== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==8201== Command: ./leaky
==8201==
==8201==
==8201== HEAP SUMMARY:
==8201== in use at exit: 104,700 bytes in 2,352 blocks
==8201== total heap usage: 10,430 allocs, 8,078 frees, 2,292,650 bytes allocated
==8201==
==8201== LEAK SUMMARY:
==8201== definitely lost: 0 bytes in 0 blocks
==8201== indirectly lost: 0 bytes in 0 blocks
==8201== possibly lost: 25,740 bytes in 325 blocks
==8201== still reachable: 78,960 bytes in 2,027 blocks
==8201== suppressed: 0 bytes in 0 blocks
==8201== Rerun with --leak-check=full to see details of leaked memory
==8201==
==8201== For lists of detected and suppressed errors, rerun with: -s
==8201== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
$ valgrind rpm -qa > /dev/null
==8101== Memcheck, a memory error detector
==8101== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==8101== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==8101== Command: rpm -qa
==8101==
==8101==
==8101== HEAP SUMMARY:
==8101== in use at exit: 287 bytes in 2 blocks
==8101== total heap usage: 170,103 allocs, 170,101 frees, 120,309,981 bytes allocated
==8101==
==8101== LEAK SUMMARY:
==8101== definitely lost: 0 bytes in 0 blocks
==8101== indirectly lost: 0 bytes in 0 blocks
==8101== possibly lost: 0 bytes in 0 blocks
==8101== still reachable: 287 bytes in 2 blocks
==8101== suppressed: 0 bytes in 0 blocks
==8101== Rerun with --leak-check=full to see details of leaked memory
==8101==
==8101== For lists of detected and suppressed errors, rerun with: -s
==8101== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
As you can see, my program possibly lost 25,740 bytes, whereas rpm -qa lost 0 bytes.
Here is my code:
#include <rpm/rpmdb.h>
#include <rpm/rpmlib.h>
#include <rpm/rpmts.h>
bool openDb(rpmts & ts, rpmdbMatchIterator & mi);
void closeDb(rpmts & ts, rpmdbMatchIterator & mi);
int main()
{
rpmts ts;
rpmdbMatchIterator mi;
if (!openDb(ts, mi)) {
return 1;
}
closeDb(ts, mi);
return 0;
}
bool openDb(rpmts & ts, rpmdbMatchIterator & mi)
{
{
static volatile bool s_bHereBefore = false;
if (!s_bHereBefore) {
s_bHereBefore = true;
rpmReadConfigFiles(NULL, NULL);
}
}
mi = NULL;
ts = rpmtsCreate();
if (!ts) {
printf("RPM open failed\n");
} else {
mi = rpmtsInitIterator(ts, (rpmTag)RPMDBI_PACKAGES, NULL, 0);
if (!mi) {
printf("RPM iterator failed\n");
rpmtsFree(ts);
}
}
return mi != NULL;
}
void closeDb(rpmts & ts, rpmdbMatchIterator & mi)
{
mi = rpmdbFreeIterator(mi);
if (ts) {
rpmtsFree(ts);
}
}
I compile with g++ -Wall -Wextra -Wunused -Og -g try_to_fix_mem_leak.cpp -lrpm -o leaky.
I closely inspected my program, but I was unable to spot any memory leaks from manual inspection.
When I run valgrind --leak-check=full ./leaky and search the output for try_to_fix_mem_leak.cpp, all of the hits are for line 27, i.e., the rpmReadConfigFiles(NULL, NULL); line (technically there are also hits for line 13, but that is just because that is where the openDb call is made in main). (See pastebin link below.) But I don't know how this line could cause any memory leaks. The function's documentation for my version of librpm (4.16.1) doesn't mention anything about needing to free any memory.
How can I correctly open and close the RPM DB without leaking memory? Or, to put my question another way, how can I open and close the RPM DB while leaking at worst only as many bytes as rpm -qa does?
Edit
pastebin link with full output of valgrind --leak-check=full ./leaky.
A colleague of mine found that, in the code for the rpm binary itself, the RPM devs call the following two functions to free their memory:
rpmFreeMacros(NULL);
rpmFreeRpmrc();
To use rpmFreeMacros(), include the rpm/rpmmacro.h system header in your source code, and link against librpmio.so.
Here is my updated code:
#include <rpm/rpmdb.h>
#include <rpm/rpmlib.h>
#include <rpm/rpmts.h>
#include <rpm/rpmmacro.h>
bool openDb(rpmts & ts, rpmdbMatchIterator & mi);
void closeDb(rpmts & ts, rpmdbMatchIterator & mi);
int main()
{
rpmts ts;
rpmdbMatchIterator mi;
if (!openDb(ts, mi)) {
return 1;
}
closeDb(ts, mi);
return 0;
}
bool openDb(rpmts & ts, rpmdbMatchIterator & mi)
{
{
static volatile bool s_bHereBefore = false;
if (!s_bHereBefore) {
s_bHereBefore = true;
rpmReadConfigFiles(NULL, NULL);
}
}
mi = NULL;
ts = rpmtsCreate();
if (!ts) {
printf("RPM open failed\n");
} else {
mi = rpmtsInitIterator(ts, (rpmTag)RPMDBI_PACKAGES, NULL, 0);
if (!mi) {
printf("RPM iterator failed\n");
rpmtsFree(ts);
}
}
return mi != NULL;
}
void closeDb(rpmts & ts, rpmdbMatchIterator & mi)
{
mi = rpmdbFreeIterator(mi);
if (ts) {
rpmtsFree(ts);
}
rpmFreeMacros(NULL);
rpmFreeRpmrc();
}
Here is a diff between my new code (leak_fixed.cpp) and my old leaky code (try_to_fix_mem_leak.cpp):
$ diff -u try_to_fix_mem_leak.cpp leak_fixed.cpp
--- try_to_fix_mem_leak.cpp 2023-01-04 10:19:28.084587731 -0500
+++ leak_fixed.cpp 2023-01-04 10:19:57.484483431 -0500
## -1,6 +1,7 ##
#include <rpm/rpmdb.h>
#include <rpm/rpmlib.h>
#include <rpm/rpmts.h>
+#include <rpm/rpmmacro.h>
bool openDb(rpmts & ts, rpmdbMatchIterator & mi);
void closeDb(rpmts & ts, rpmdbMatchIterator & mi);
## -50,4 +51,6 ##
if (ts) {
rpmtsFree(ts);
}
+ rpmFreeMacros(NULL);
+ rpmFreeRpmrc();
}
I compile with g++ -Wall -Wextra -Wunused -Og -g leak_fixed.cpp -lrpm -lrpmio -o rpm_open_close. Note that I needed to add -lrpmio in order to be able to use the rpmFreeMacros() function.
I get the same leak summary output from valgrind for both my binary and for the system rpm binary:
$ valgrind ./rpm_open_close
==3470== Memcheck, a memory error detector
==3470== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==3470== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==3470== Command: ./rpm_open_close
==3470==
==3470==
==3470== HEAP SUMMARY:
==3470== in use at exit: 287 bytes in 2 blocks
==3470== total heap usage: 10,495 allocs, 10,493 frees, 2,317,132 bytes allocated
==3470==
==3470== LEAK SUMMARY:
==3470== definitely lost: 0 bytes in 0 blocks
==3470== indirectly lost: 0 bytes in 0 blocks
==3470== possibly lost: 0 bytes in 0 blocks
==3470== still reachable: 287 bytes in 2 blocks
==3470== suppressed: 0 bytes in 0 blocks
==3470== Rerun with --leak-check=full to see details of leaked memory
==3470==
==3470== For lists of detected and suppressed errors, rerun with: -s
==3470== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
$ valgrind rpm -qa > /dev/null
==3483== Memcheck, a memory error detector
==3483== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==3483== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==3483== Command: rpm -qa
==3483==
==3483==
==3483== HEAP SUMMARY:
==3483== in use at exit: 287 bytes in 2 blocks
==3483== total heap usage: 172,604 allocs, 172,602 frees, 121,827,169 bytes allocated
==3483==
==3483== LEAK SUMMARY:
==3483== definitely lost: 0 bytes in 0 blocks
==3483== indirectly lost: 0 bytes in 0 blocks
==3483== possibly lost: 0 bytes in 0 blocks
==3483== still reachable: 287 bytes in 2 blocks
==3483== suppressed: 0 bytes in 0 blocks
==3483== Rerun with --leak-check=full to see details of leaked memory
==3483==
==3483== For lists of detected and suppressed errors, rerun with: -s
==3483== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
I am testing a class that takes multiple std::unique_ptr in initialisation. Raw pointers of mock objects are allocated with new, and then passed to an injected unique_ptr.
A test without any expectations passes fine. When I add an expectation, I get memory leaks.
The sources:
Tested class:
class TemperatureController : public IController
{
public:
TemperatureController(std::unique_ptr<IOutput> output,
std::unique_ptr<ISensor> sensor,
std::unique_ptr<IRegulator> regulator,
std::unique_ptr<ISetpoint> setpoint,
std::unique_ptr<IEnabler> enabler) :
output(std::move(output)),
sensor(std::move(sensor)),
regulator(std::move(regulator)),
setpoint(std::move(setpoint)),
enabler(std::move(enabler))
{ }
virtual ~TemperatureController(){}
virtual void setup() override;
virtual void controlLoop() override;
private:
std::unique_ptr<IOutput> output;
std::unique_ptr<ISensor> sensor;
std::unique_ptr<IRegulator> regulator;
std::unique_ptr<ISetpoint> setpoint;
std::unique_ptr<IEnabler> enabler;
};
void TemperatureController::controlLoop()
{
cout << "enabler address in sut = " << std::hex << *((int*)(enabler.get())) << endl;
cout << "regulator address in sut = 4" << std::hex << *((int*)(regulator.get())) << endl;
if(enabler->isEnabled())
{
regulator->controllOutput(*output,
*setpoint,
*sensor);
}
}
Test suite:
template <typename T>
auto injectMock(T* ptr)
{
return unique_ptr<T>(ptr);
}
struct TestTemperatureController : public Test
{
MockIEnabler* enabler = new MockIEnabler();
MockIOutput* output = new MockIOutput();
MockIRegulator* regulator = new MockIRegulator();
MockISensor* sensor = new MockISensor();
MockISetpoint* setpoint = new MockISetpoint();
TemperatureController sut{injectMock(output),
injectMock(sensor),
injectMock(regulator),
injectMock(setpoint),
injectMock(enabler)};
TestTemperatureController()
{
cout << "enabler address in test = "
<< std::hex << *((int*)(enabler)) << endl
<< "regulator address in test = "
<< std::hex << *((int*)(regulator)) << endl;
}
};
TEST_F(TestTemperatureController,
WhenEnabled_ShouldAct)
{
EXPECT_CALL(*enabler, isEnabled())
.WillOnce(Return(true));
//EXPECT_CALL(*regulator, controllOutput(_,_,_)) // commented out to make simpler logs
// .Times(AtLeast(1));
sut.controlLoop();
}
I ommited some noise code (includes, namespaces etc.).
When run with valgrind, I get the following output:
==10176== Memcheck, a memory error detector
==10176== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==10176== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==10176== Command: ./unit_tests
==10176==
Running main() from gmock_main.cc
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from TestTemperatureController
[ RUN ] TestTemperatureController.WhenEnabled_ShouldAct
enabler address in test = 54edc8
regulator address in test = 54eb90
enabler address in sut = 54edc8
regulator address in sut = 454eb90
GMOCK WARNING:
Uninteresting mock function call - returning directly.
Function call: controllOutput(#0x5cdf1b0 8-byte object <08-ED 54-00 00-00 00-00>, #0x5cdf430 8-byte object <C0-EA 54-00 00-00 00-00>, #0x5cdf390 8-byte object <D8-EA 54-00 00-00 00-00>)
NOTE: You can safely ignore the above warning unless this call should not happen. Do not suppress it by blindly adding an EXPECT_CALL() if you don't mean to enforce the call. See https://github.com/google/googletest/blob/master/googlemock/docs/CookBook.md#knowing-when-to-expect for details.
[ OK ] TestTemperatureController.WhenEnabled_ShouldAct (282 ms)
[----------] 1 test from TestTemperatureController (297 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (369 ms total)
[ PASSED ] 1 test.
/home/lukasz/workspace/arduino-thermostat/ut/tests/TestTemperatureController/TestTemperatureController.cpp:52: ERROR: this mock object (used in test TestTemperatureController.WhenEnabled_ShouldAct) should be deleted but never is. Its address is #0x5cdf110.
ERROR: 1 leaked mock object found at program exit.
==10176==
==10176== HEAP SUMMARY:
==10176== in use at exit: 74,863 bytes in 37 blocks
==10176== total heap usage: 231 allocs, 194 frees, 124,341 bytes allocated
==10176==
==10176== 403 (16 direct, 387 indirect) bytes in 1 blocks are definitely lost in loss record 35 of 37
==10176== at 0x4C2E0EF: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==10176== by 0x4DE485: __gnu_cxx::new_allocator<testing::internal::linked_ptr<testing::internal::ExpectationBase> >::allocate(unsigned long, void const*) (in /home/lukasz/workspace/build-arduino-thermostat-Desktop-Domyślna/unit_tests)
==10176== by 0x4DE0F3: std::allocator_traits<std::allocator<testing::internal::linked_ptr<testing::internal::ExpectationBase> > >::allocate(std::allocator<testing::internal::linked_ptr<testing::internal::ExpectationBase> >&, unsigned long) (in /home/lukasz/workspace/build-arduino-thermostat-Desktop-Domyślna/unit_tests)
==10176== by 0x4DDA47: std::_Vector_base<testing::internal::linked_ptr<testing::internal::ExpectationBase>, std::allocator<testing::internal::linked_ptr<testing::internal::ExpectationBase> > >::_M_allocate(unsigned long) (in /home/lukasz/workspace/build-arduino-thermostat-Desktop-Domyślna/unit_tests)
==10176== by 0x4DD077: void std::vector<testing::internal::linked_ptr<testing::internal::ExpectationBase>, std::allocator<testing::internal::linked_ptr<testing::internal::ExpectationBase> > >::_M_emplace_back_aux<testing::internal::linked_ptr<testing::internal::ExpectationBase> const&>(testing::internal::linked_ptr<testing::internal::ExpectationBase> const&) (in /home/lukasz/workspace/build-arduino-thermostat-Desktop-Domyślna/unit_tests)
==10176== by 0x4DC562: std::vector<testing::internal::linked_ptr<testing::internal::ExpectationBase>, std::allocator<testing::internal::linked_ptr<testing::internal::ExpectationBase> > >::push_back(testing::internal::linked_ptr<testing::internal::ExpectationBase> const&) (in /home/lukasz/workspace/build-arduino-thermostat-Desktop-Domyślna/unit_tests)
==10176== by 0x4DB252: testing::internal::FunctionMockerBase<bool ()>::AddNewExpectation(char const*, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::tuple<> const&) (in /home/lukasz/workspace/build-arduino-thermostat-Desktop-Domyślna/unit_tests)
==10176== by 0x4D9459: testing::internal::MockSpec<bool ()>::InternalExpectedAt(char const*, int, char const*, char const*) (in /home/lukasz/workspace/build-arduino-thermostat-Desktop-Domyślna/unit_tests)
==10176== by 0x4D4862: TestTemperatureController_WhenEnabled_ShouldAct_Test::TestBody() (in /home/lukasz/workspace/build-arduino-thermostat-Desktop-Domyślna/unit_tests)
==10176== by 0x52DF4D: void testing::internal::HandleSehExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) (in /home/lukasz/workspace/build-arduino-thermostat-Desktop-Domyślna/unit_tests)
==10176== by 0x5281B6: void testing::internal::HandleExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) (in /home/lukasz/workspace/build-arduino-thermostat-Desktop-Domyślna/unit_tests)
==10176== by 0x50C9A9: testing::Test::Run() (in /home/lukasz/workspace/build-arduino-thermostat-Desktop-Domyślna/unit_tests)
==10176==
==10176== LEAK SUMMARY:
==10176== definitely lost: 16 bytes in 1 blocks
==10176== indirectly lost: 387 bytes in 7 blocks
==10176== possibly lost: 0 bytes in 0 blocks
==10176== still reachable: 74,460 bytes in 29 blocks
==10176== suppressed: 0 bytes in 0 blocks
==10176== Reachable blocks (those to which a pointer was found) are not shown.
==10176== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==10176==
==10176== For counts of detected and suppressed errors, rerun with: -v
==10176== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
I'm not really good at googlemock implementation, but for me i seems like there are some expectations allocated, which are not released later. Or is it a false positive?
The question is: How do I deal with that? I want to inject mocks with unique_ptr's. I don't want to change the tested object's interface, of course.
I´m running a big code with lots of MYSQL database access that and I´m noticing a memory leak that consumes the whole server memory after one day running.
By isolating pieces I end up with the following test code:
#include <iostream>
#include <string>
#include <sstream>
#include <thread>
#include <chrono>
#include "mysql_connection.h"
#include <cppconn/driver.h>
#include <cppconn/exception.h>
#include <cppconn/resultset.h>
#include <cppconn/statement.h>
int main() {
try
{
sql::Driver *driver = NULL;
sql::Connection *connection = NULL;
driver = get_driver_instance();
connection = driver->connect("tcp://127.0.0.1:3306", "user", "pass");
connection->setSchema("myschema");
connection->setAutoCommit(true);
while (true)
{
std::string sql("INSERT INTO tablename ('field1', 'field2', 'field3') VALUES ('1', '2', '3')");
sql::Statement *stmt;
stmt = connection->createStatement();
stmt->execute(sql.c_str());
stmt->close();
std::cout << sql << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
catch (std::exception& ex)
{
std::cout << "Error occurred: " << std::endl;
std::cout << ex.what() << std::endl;
}
catch(...)
{
std::cout << "Unknown failure occurred." << std::endl;
}
}
As the main while runs, the process memory usage starts increasing like 1Mb per minute.
Using valgrid --leak-check=full I got the following result after hitting to exit from the main loop:
==11988== Process terminating with default action of signal 2 (SIGINT)
==11988== at 0x57F5D6D: recv (recv.c:29)
==11988== by 0x5B1B022: vio_read (in /usr/lib/x86_64-linux-gnu/libmysqlclient.so.18.1.0)
==11988== by 0x5B1B0A4: vio_read_buff (in /usr/lib/x86_64-linux-gnu/libmysqlclient.so.18.1.0)
==11988== by 0x5AFA702: ??? (in /usr/lib/x86_64-linux-gnu/libmysqlclient.so.18.1.0)
==11988== by 0x5AFA976: ??? (in /usr/lib/x86_64-linux-gnu/libmysqlclient.so.18.1.0)
==11988== by 0x5AFB6A3: my_net_read (in /usr/lib/x86_64-linux-gnu/libmysqlclient.so.18.1.0)
==11988== by 0x5AF151C: cli_safe_read (in /usr/lib/x86_64-linux-gnu/libmysqlclient.so.18.1.0)
==11988== by 0x5AF2A15: ??? (in /usr/lib/x86_64-linux-gnu/libmysqlclient.so.18.1.0)
==11988== by 0x5AF43A5: mysql_real_query (in /usr/lib/x86_64-linux-gnu/libmysqlclient.so.18.1.0)
==11988== by 0x4F15BEE: sql::mysql::NativeAPI::LibmysqlStaticProxy::real_query(st_mysql*, char const*, unsigned long) (in /usr/lib/libmysqlcppconn.so.7.1.1.3)
==11988== by 0x4F176F5: sql::mysql::NativeAPI::MySQL_NativeConnectionWrapper::query(sql::SQLString const&) (in /usr/lib/libmysqlcppconn.so.7.1.1.3)
==11988== by 0x4F10579: sql::mysql::MySQL_Statement::do_query(sql::SQLString const&) (in /usr/lib/libmysqlcppconn.so.7.1.1.3)
==11988==
==11988== HEAP SUMMARY:
==11988== in use at exit: 333,479 bytes in 1,869 blocks
==11988== total heap usage: 5,566 allocs, 3,697 frees, 1,100,674 bytes allocated
==11988==
==11988== 144,880 bytes in 1,811 blocks are definitely lost in loss record 42 of 42
==11988== at 0x4C2C12F: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==11988== by 0x4EC4334: sql::mysql::MySQL_Connection::createStatement() (in /usr/lib/libmysqlcppconn.so.7.1.1.3)
==11988== by 0x4011B7: main (test.cpp:32)
==11988==
==11988== LEAK SUMMARY:
==11988== definitely lost: 144,880 bytes in 1,811 blocks
==11988== indirectly lost: 0 bytes in 0 blocks
==11988== possibly lost: 0 bytes in 0 blocks
==11988== still reachable: 188,599 bytes in 58 blocks
==11988== suppressed: 0 bytes in 0 blocks
==11988== Reachable blocks (those to which a pointer was found) are not shown.
==11988== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==11988==
==11988== For counts of detected and suppressed errors, rerun with: -v
==11988== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
So, am I doing something wrong or is this a know MYSQL bug ? How to fix the code and/or mySQL ?
I´m running MySql 5.6.27-0ubuntu1 running on Ubuntu 15.10.
You're missing delete stmt. Better would be to use a std::unique_ptr<sql::Statement>.
(See e.g. https://dev.mysql.com/doc/connector-cpp/en/connector-cpp-examples-query.html)
Try
std::unique_ptr<sql::Statement> stmt(connection->CreateStatement());
This is my first experience with Cairo. I create the surface using this function:
//This function should give us a new x11 surface to draw on.
cairo_surface_t *create_x11_surface(Display *d, int *x, int *y) {
Drawable da;
int screen;
cairo_surface_t *sfc;
Screen *scr;
screen = DefaultScreen(d);
scr = DefaultScreenOfDisplay(d);
if (!*x || !*y) {
*x = WidthOfScreen(scr) / 2;
*y = HeightOfScreen(scr) / 2;
da =
XCreateSimpleWindow(d, DefaultRootWindow(d), 0, 0, *x, *y, 0, 0, 0);
} else
da =
XCreateSimpleWindow(d, DefaultRootWindow(d), 0, 0, *x, *y, 0, 0, 0);
XSelectInput(d, da, ButtonPressMask | ButtonReleaseMask | KeyPressMask |
ButtonMotionMask | StructureNotifyMask);
// http://www.lemoda.net/c/xlib-wmclose/index.html
/* "wm_delete_window" is the Atom which corresponds to the delete
window message sent by the window manager. */
Atom wm_delete_window;
wm_delete_window = XInternAtom(d, "WM_DELETE_WINDOW", False);
/* Set up the window manager protocols. The third argument here is
meant to be an array, and the fourth argument is the size of
the array. */
XSetWMProtocols(d, da, &wm_delete_window, 1);
XMapWindow(d, da);
sfc = cairo_xlib_surface_create(d, da, DefaultVisual(d, screen), *x, *y);
return sfc;
}
My main() consists of creating a Drawer object and calling its run method. Drawer is defined as follows (slightly simplified, the unused template parameter is not shown):
struct Drawer {
Drawer() {
d = XOpenDisplay(NULL);
if (d == NULL) {
fprintf(stderr, "Failed to open display\n");
exit(-1);
}
// create a new cairo surface in an x11 window as well as a cairo_t* to
// draw on the x11 window with.
int x=500, y=500;
surface = create_x11_surface(d, &x, &y);
cr = cairo_create(surface);
}
// https://stackoverflow.com/a/19308254/2725810
~Drawer() {
cairo_destroy(cr);
cairo_surface_destroy(surface);
XCloseDisplay(d);
}
// Returns true if need to continue or false if quiting
bool processEvents() {
XEvent e;
if (XPending(cairo_xlib_surface_get_display(surface))) {
XNextEvent(cairo_xlib_surface_get_display(surface), &e);
switch (e.type) {
case ButtonPress:
drag_start_x = e.xbutton.x;
drag_start_y = e.xbutton.y;
break;
case ButtonRelease:
last_delta_x = 0;
last_delta_y = 0;
break;
case MotionNotify:
// http://cairographics.org/manual/cairo-Transformations.html#cairo-translate
cairo_translate(cr, e.xmotion.x - drag_start_x - last_delta_x,
e.xmotion.y - drag_start_y - last_delta_y);
last_delta_x = e.xmotion.x - drag_start_x;
last_delta_y = e.xmotion.y - drag_start_y;
break;
case ConfigureNotify:
cairo_xlib_surface_set_size(surface, e.xconfigure.width,
e.xconfigure.height);
break;
case ClientMessage:
return false;
default:
fprintf(stderr, "Dropping unhandled XEevent.type = %d.\n",
e.type);
}
}
return true;
}
void draw() {
cairo_push_group(cr);
// Clear the background
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_paint(cr);
cairo_set_source_rgb(cr, 0, 1, 0);
cairo_move_to(cr, 0, 0);
cairo_line_to(cr, 256, 256);
cairo_move_to(cr, 256, 0);
cairo_line_to(cr, 0, 256);
cairo_set_line_width(cr, 10.0);
cairo_stroke(cr);
cairo_pop_group_to_source(cr);
cairo_paint(cr);
cairo_surface_flush(surface);
XFlush(d);
}
void run() {
while (1) {
if (!processEvents()) break;
draw();
sleep(0.1);
}
}
private:
Display *d;
cairo_surface_t* surface;
cairo_t* cr;
int last_delta_x = 0, last_delta_y = 0;
int drag_start_x, drag_start_y;
};
The program is compiled with gcc version 4.8.2.
valgrind reports memory leaks and points to calls to cairo_stroke and other Cairo functions as the leak causes. It also says that some memory remains not freed when the program finishes despite the fact that Drawer's destructor gets called. Here is the output of valgrind:
==6897== Memcheck, a memory error detector
==6897== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==6897== Using Valgrind-3.10.0.SVN and LibVEX; rerun with -h for copyright info
==6897== Command: ./Test
==6897==
Dropping unhandled XEevent.type = 21.
Dropping unhandled XEevent.type = 19.
Dropping unhandled XEevent.type = 65.
==6897==
==6897== HEAP SUMMARY:
==6897== in use at exit: 12,696 bytes in 12 blocks
==6897== total heap usage: 19,039 allocs, 19,027 frees, 8,088,426 bytes allocated
==6897==
==6897== 72 bytes in 1 blocks are still reachable in loss record 1 of 11
==6897== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==6897== by 0x4E59F7C: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4E5B5B9: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4E5BAE5: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4E5CAB0: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4E5D87D: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4EA2050: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4EA3142: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4E5FECF: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4E57F01: cairo_push_group_with_content (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x40590D: Drawer<NoGraph<StateNeighbor<Pancake, true> > >::draw() (Drawer.h:115)
==6897== by 0x403A7A: Drawer<NoGraph<StateNeighbor<Pancake, true> > >::run() (Drawer.h:138)
==6897==
==6897== 72 bytes in 1 blocks are still reachable in loss record 2 of 11
==6897== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==6897== by 0x4E59F7C: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4E5B5B9: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4E5BAE5: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4E5CDB2: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4EB4DE3: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4E5DA63: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4ECEA3C: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4EA2411: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4E651E1: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4E5F168: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4E58994: cairo_stroke (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897==
==6897== 160 bytes in 1 blocks are still reachable in loss record 3 of 11
==6897== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==6897== by 0x4E850BC: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4E851BC: cairo_pattern_create_rgba (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4E6028A: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4E57FC9: cairo_set_source_rgb (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x405959: Drawer<NoGraph<StateNeighbor<Pancake, true> > >::draw() (Drawer.h:121)
==6897== by 0x403A7A: Drawer<NoGraph<StateNeighbor<Pancake, true> > >::run() (Drawer.h:138)
==6897== by 0x402269: main (Test.cpp:48)
==6897==
==6897== 256 bytes in 2 blocks are still reachable in loss record 4 of 11
==6897== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==6897== by 0x4E8529A: cairo_pattern_create_for_surface (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4E5FD7F: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4E57F48: cairo_pop_group (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4E5809D: cairo_pop_group_to_source (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x405A38: Drawer<NoGraph<StateNeighbor<Pancake, true> > >::draw() (Drawer.h:129)
==6897== by 0x403A7A: Drawer<NoGraph<StateNeighbor<Pancake, true> > >::run() (Drawer.h:138)
==6897== by 0x402269: main (Test.cpp:48)
==6897==
==6897== 352 bytes in 1 blocks are definitely lost in loss record 5 of 11
==6897== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==6897== by 0x4ECC831: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4ECC933: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4ECD497: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4EB3922: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4EB4E32: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4E5DA63: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4ECEA3C: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4EA2411: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4E651E1: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4E5F168: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x4E58994: cairo_stroke (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897==
==6897== 1,424 bytes in 1 blocks are still reachable in loss record 6 of 11
==6897== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==6897== by 0x4E604E7: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==6897== by 0x403A04: Drawer<NoGraph<StateNeighbor<Pancake, true> > >::Drawer(NoGraph<StateNeighbor<Pancake, true> > const&) (Drawer.h:66)
==6897== by 0x40225D: main (Test.cpp:47)
==6897==
==6897== 2,072 bytes in 1 blocks are still reachable in loss record 7 of 11
==6897== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==6897== by 0x5FCCE9A: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==6897== by 0x5FCBACE: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==6897== by 0x5FCD585: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==6897== by 0x5F7F508: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==6897== by 0x4010139: call_init.part.0 (dl-init.c:78)
==6897== by 0x4010222: _dl_init (dl-init.c:36)
==6897== by 0x4001309: ??? (in /lib/x86_64-linux-gnu/ld-2.19.so)
==6897==
==6897== 2,072 bytes in 1 blocks are still reachable in loss record 8 of 11
==6897== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==6897== by 0x5FCCE9A: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==6897== by 0x5FCA61F: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==6897== by 0x5FCD5A0: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==6897== by 0x5F7F508: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==6897== by 0x4010139: call_init.part.0 (dl-init.c:78)
==6897== by 0x4010222: _dl_init (dl-init.c:36)
==6897== by 0x4001309: ??? (in /lib/x86_64-linux-gnu/ld-2.19.so)
==6897==
==6897== 2,072 bytes in 1 blocks are still reachable in loss record 9 of 11
==6897== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==6897== by 0x5FCCE9A: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==6897== by 0x5FE5A8F: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==6897== by 0x5FBC1A5: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==6897== by 0x5FCD5AB: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==6897== by 0x5F7F508: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==6897== by 0x4010139: call_init.part.0 (dl-init.c:78)
==6897== by 0x4010222: _dl_init (dl-init.c:36)
==6897== by 0x4001309: ??? (in /lib/x86_64-linux-gnu/ld-2.19.so)
==6897==
==6897== 2,072 bytes in 1 blocks are still reachable in loss record 10 of 11
==6897== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==6897== by 0x5FCCE9A: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==6897== by 0x60053CF: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==6897== by 0x5FCD5AB: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==6897== by 0x5F7F508: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==6897== by 0x4010139: call_init.part.0 (dl-init.c:78)
==6897== by 0x4010222: _dl_init (dl-init.c:36)
==6897== by 0x4001309: ??? (in /lib/x86_64-linux-gnu/ld-2.19.so)
==6897==
==6897== 2,072 bytes in 1 blocks are still reachable in loss record 11 of 11
==6897== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==6897== by 0x5FCCE9A: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==6897== by 0x5FCFCBF: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==6897== by 0x5F7F508: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==6897== by 0x4010139: call_init.part.0 (dl-init.c:78)
==6897== by 0x4010222: _dl_init (dl-init.c:36)
==6897== by 0x4001309: ??? (in /lib/x86_64-linux-gnu/ld-2.19.so)
==6897==
==6897== LEAK SUMMARY:
==6897== definitely lost: 352 bytes in 1 blocks
==6897== indirectly lost: 0 bytes in 0 blocks
==6897== possibly lost: 0 bytes in 0 blocks
==6897== still reachable: 12,344 bytes in 11 blocks
==6897== suppressed: 0 bytes in 0 blocks
==6897==
==6897== For counts of detected and suppressed errors, rerun with: -v
==6897== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
What am I not doing right?
UPDATE: After inserting a call to cairo_debug_reset_static_data() from the destructor as suggested here, the output of valgrind became somewhat shorter:
==7310== Memcheck, a memory error detector
==7310== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==7310== Using Valgrind-3.10.0.SVN and LibVEX; rerun with -h for copyright info
==7310== Command: ./Test
==7310==
Dropping unhandled XEevent.type = 21.
Dropping unhandled XEevent.type = 19.
Dropping unhandled XEevent.type = 65.
==7310==
==7310== HEAP SUMMARY:
==7310== in use at exit: 10,712 bytes in 6 blocks
==7310== total heap usage: 29,352 allocs, 29,346 frees, 12,459,938 bytes allocated
==7310==
==7310== 352 bytes in 1 blocks are definitely lost in loss record 1 of 6
==7310== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7310== by 0x4ECC831: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==7310== by 0x4ECC933: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==7310== by 0x4ECD497: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==7310== by 0x4EB3922: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==7310== by 0x4EB4E32: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==7310== by 0x4E5DA63: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==7310== by 0x4ECEA3C: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==7310== by 0x4EA2411: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==7310== by 0x4E651E1: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==7310== by 0x4E5F168: ??? (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==7310== by 0x4E58994: cairo_stroke (in /usr/lib/x86_64-linux-gnu/libcairo.so.2.11301.0)
==7310==
==7310== 2,072 bytes in 1 blocks are still reachable in loss record 2 of 6
==7310== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7310== by 0x5FCCE9A: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==7310== by 0x5FCBACE: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==7310== by 0x5FCD585: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==7310== by 0x5F7F508: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==7310== by 0x4010139: call_init.part.0 (dl-init.c:78)
==7310== by 0x4010222: _dl_init (dl-init.c:36)
==7310== by 0x4001309: ??? (in /lib/x86_64-linux-gnu/ld-2.19.so)
==7310==
==7310== 2,072 bytes in 1 blocks are still reachable in loss record 3 of 6
==7310== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7310== by 0x5FCCE9A: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==7310== by 0x5FCA61F: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==7310== by 0x5FCD5A0: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==7310== by 0x5F7F508: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==7310== by 0x4010139: call_init.part.0 (dl-init.c:78)
==7310== by 0x4010222: _dl_init (dl-init.c:36)
==7310== by 0x4001309: ??? (in /lib/x86_64-linux-gnu/ld-2.19.so)
==7310==
==7310== 2,072 bytes in 1 blocks are still reachable in loss record 4 of 6
==7310== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7310== by 0x5FCCE9A: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==7310== by 0x5FE5A8F: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==7310== by 0x5FBC1A5: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==7310== by 0x5FCD5AB: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==7310== by 0x5F7F508: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==7310== by 0x4010139: call_init.part.0 (dl-init.c:78)
==7310== by 0x4010222: _dl_init (dl-init.c:36)
==7310== by 0x4001309: ??? (in /lib/x86_64-linux-gnu/ld-2.19.so)
==7310==
==7310== 2,072 bytes in 1 blocks are still reachable in loss record 5 of 6
==7310== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7310== by 0x5FCCE9A: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==7310== by 0x60053CF: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==7310== by 0x5FCD5AB: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==7310== by 0x5F7F508: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==7310== by 0x4010139: call_init.part.0 (dl-init.c:78)
==7310== by 0x4010222: _dl_init (dl-init.c:36)
==7310== by 0x4001309: ??? (in /lib/x86_64-linux-gnu/ld-2.19.so)
==7310==
==7310== 2,072 bytes in 1 blocks are still reachable in loss record 6 of 6
==7310== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7310== by 0x5FCCE9A: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==7310== by 0x5FCFCBF: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==7310== by 0x5F7F508: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
==7310== by 0x4010139: call_init.part.0 (dl-init.c:78)
==7310== by 0x4010222: _dl_init (dl-init.c:36)
==7310== by 0x4001309: ??? (in /lib/x86_64-linux-gnu/ld-2.19.so)
==7310==
==7310== LEAK SUMMARY:
==7310== definitely lost: 352 bytes in 1 blocks
==7310== indirectly lost: 0 bytes in 0 blocks
==7310== possibly lost: 0 bytes in 0 blocks
==7310== still reachable: 10,360 bytes in 5 blocks
==7310== suppressed: 0 bytes in 0 blocks
==7310==
==7310== For counts of detected and suppressed errors, rerun with: -v
==7310== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
BTW, why doesn't valgrind show the full callstack for the lost record and stops at cairo_stroke instead? My program is compiled with gcc with the -g flag...
UPDATE: As requested in the comments, here is the whole working example:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <cairo-xlib.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <unistd.h>
//This function should give us a new x11 surface to draw on.
cairo_surface_t *create_x11_surface(Display *d, int *x, int *y) {
Drawable da;
int screen;
cairo_surface_t *sfc;
Screen *scr;
screen = DefaultScreen(d);
scr = DefaultScreenOfDisplay(d);
if (!*x || !*y) {
*x = WidthOfScreen(scr) / 2;
*y = HeightOfScreen(scr) / 2;
da =
XCreateSimpleWindow(d, DefaultRootWindow(d), 0, 0, *x, *y, 0, 0, 0);
} else
da =
XCreateSimpleWindow(d, DefaultRootWindow(d), 0, 0, *x, *y, 0, 0, 0);
XSelectInput(d, da, ButtonPressMask | ButtonReleaseMask | KeyPressMask |
ButtonMotionMask | StructureNotifyMask);
// http://www.lemoda.net/c/xlib-wmclose/index.html
/* "wm_delete_window" is the Atom which corresponds to the delete
window message sent by the window manager. */
Atom wm_delete_window;
wm_delete_window = XInternAtom(d, "WM_DELETE_WINDOW", False);
/* Set up the window manager protocols. The third argument here is
meant to be an array, and the fourth argument is the size of
the array. */
XSetWMProtocols(d, da, &wm_delete_window, 1);
XMapWindow(d, da);
sfc = cairo_xlib_surface_create(d, da, DefaultVisual(d, screen), *x, *y);
return sfc;
}
struct Drawer {
Drawer() {
d = XOpenDisplay(NULL);
if (d == NULL) {
fprintf(stderr, "Failed to open display\n");
exit(-1);
}
// create a new cairo surface in an x11 window as well as a cairo_t* to
// draw on the x11 window with.
int x=500, y=500;
surface = create_x11_surface(d, &x, &y);
cr = cairo_create(surface);
}
// https://stackoverflow.com/a/19308254/2725810
~Drawer() {
cairo_destroy(cr);
cairo_surface_destroy(surface);
XCloseDisplay(d);
}
// Returns true if need to continue or false if quiting
bool processEvents() {
XEvent e;
if (XPending(cairo_xlib_surface_get_display(surface))) {
XNextEvent(cairo_xlib_surface_get_display(surface), &e);
switch (e.type) {
case ButtonPress:
drag_start_x = e.xbutton.x;
drag_start_y = e.xbutton.y;
break;
case ButtonRelease:
last_delta_x = 0;
last_delta_y = 0;
break;
case MotionNotify:
// http://cairographics.org/manual/cairo-Transformations.html#cairo-translate
cairo_translate(cr, e.xmotion.x - drag_start_x - last_delta_x,
e.xmotion.y - drag_start_y - last_delta_y);
last_delta_x = e.xmotion.x - drag_start_x;
last_delta_y = e.xmotion.y - drag_start_y;
break;
case ConfigureNotify:
cairo_xlib_surface_set_size(surface, e.xconfigure.width,
e.xconfigure.height);
break;
case ClientMessage:
return false;
default:
fprintf(stderr, "Dropping unhandled XEevent.type = %d.\n",
e.type);
}
}
return true;
}
void draw() {
cairo_push_group(cr);
// Clear the background
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_paint(cr);
cairo_set_source_rgb(cr, 0, 1, 0);
cairo_move_to(cr, 0, 0);
cairo_line_to(cr, 256, 256);
cairo_move_to(cr, 256, 0);
cairo_line_to(cr, 0, 256);
cairo_set_line_width(cr, 10.0);
cairo_stroke(cr);
cairo_pop_group_to_source(cr);
cairo_paint(cr);
cairo_surface_flush(surface);
XFlush(d);
}
void run() {
while (1) {
if (!processEvents()) break;
draw();
sleep(0.1);
}
}
private:
Display *d;
cairo_surface_t* surface;
cairo_t* cr;
int last_delta_x = 0, last_delta_y = 0;
int drag_start_x, drag_start_y;
};
int main() {
Drawer d;
d.run();
return 0;
}
TL;DR: I don't see any leak.
Here is my version of the valgrind output (with a debug version of cairo and pixman (which means "with symbols"); (Oh and I also added a call to cairo_debug_reset_static_data()):
==4035== HEAP SUMMARY:
==4035== in use at exit: 86,640 bytes in 9 blocks
==4035== total heap usage: 6,736 allocs, 6,727 frees, 6,728,014 bytes allocated
==4035==
==4035== 128 bytes in 1 blocks are still reachable in loss record 1 of 9
==4035== at 0x4C28C4F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4035== by 0x4EA6044: cairo_pattern_create_for_surface (cairo-pattern.c:739)
==4035== by 0x4E6507F: _cairo_default_context_pop_group (cairo-default-context.c:238)
==4035== by 0x4E59E10: cairo_pop_group (cairo.c:554)
==4035== by 0x4E59E10: cairo_pop_group_to_source (cairo.c:594)
==4035== by 0x4017E0: Drawer::draw() (test.cpp:120)
==4035== by 0x40183E: Drawer::run() (test.cpp:129)
==4035== by 0x4013C6: main (test.cpp:144)
==4035==
==4035== 1,424 bytes in 1 blocks are still reachable in loss record 2 of 9
==4035== at 0x4C28C4F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4035== by 0x4E65D0B: _cairo_default_context_create (cairo-default-context.c:1463)
==4035== by 0x4014B8: Drawer::Drawer() (test.cpp:59)
==4035== by 0x4013BA: main (test.cpp:143)
==4035==
==4035== 2,064 bytes in 1 blocks are still reachable in loss record 3 of 9
==4035== at 0x4C28C4F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4035== by 0x636E8AA: _pixman_implementation_create (pixman-implementation.c:38)
==4035== by 0x636D3EE: _pixman_implementation_create_general (pixman-general.c:250)
==4035== by 0x636EF45: _pixman_choose_implementation (pixman-implementation.c:388)
==4035== by 0x63262C8: pixman_constructor (pixman.c:39)
==4035== by 0x400EA09: call_init.part.0 (dl-init.c:78)
==4035== by 0x400EAF2: call_init (dl-init.c:36)
==4035== by 0x400EAF2: _dl_init (dl-init.c:126)
==4035== by 0x40011C9: ??? (in /lib/x86_64-linux-gnu/ld-2.19.so)
==4035==
==4035== 2,064 bytes in 1 blocks are still reachable in loss record 4 of 9
==4035== at 0x4C28C4F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4035== by 0x636E8AA: _pixman_implementation_create (pixman-implementation.c:38)
==4035== by 0x636BF3F: _pixman_implementation_create_fast_path (pixman-fast-path.c:3286)
==4035== by 0x636EF60: _pixman_choose_implementation (pixman-implementation.c:391)
==4035== by 0x63262C8: pixman_constructor (pixman.c:39)
==4035== by 0x400EA09: call_init.part.0 (dl-init.c:78)
==4035== by 0x400EAF2: call_init (dl-init.c:36)
==4035== by 0x400EAF2: _dl_init (dl-init.c:126)
==4035== by 0x40011C9: ??? (in /lib/x86_64-linux-gnu/ld-2.19.so)
==4035==
==4035== 2,064 bytes in 1 blocks are still reachable in loss record 5 of 9
==4035== at 0x4C28C4F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4035== by 0x636E8AA: _pixman_implementation_create (pixman-implementation.c:38)
==4035== by 0x63871DF: _pixman_implementation_create_mmx (pixman-mmx.c:4021)
==4035== by 0x63543C5: _pixman_x86_get_implementations (pixman-x86.c:234)
==4035== by 0x636EF6B: _pixman_choose_implementation (pixman-implementation.c:393)
==4035== by 0x63262C8: pixman_constructor (pixman.c:39)
==4035== by 0x400EA09: call_init.part.0 (dl-init.c:78)
==4035== by 0x400EAF2: call_init (dl-init.c:36)
==4035== by 0x400EAF2: _dl_init (dl-init.c:126)
==4035== by 0x40011C9: ??? (in /lib/x86_64-linux-gnu/ld-2.19.so)
==4035==
==4035== 2,064 bytes in 1 blocks are still reachable in loss record 6 of 9
==4035== at 0x4C28C4F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4035== by 0x636E8AA: _pixman_implementation_create (pixman-implementation.c:38)
==4035== by 0x63A82BF: _pixman_implementation_create_sse2 (pixman-sse2.c:6487)
==4035== by 0x63543A5: _pixman_x86_get_implementations (pixman-x86.c:239)
==4035== by 0x636EF6B: _pixman_choose_implementation (pixman-implementation.c:393)
==4035== by 0x63262C8: pixman_constructor (pixman.c:39)
==4035== by 0x400EA09: call_init.part.0 (dl-init.c:78)
==4035== by 0x400EAF2: call_init (dl-init.c:36)
==4035== by 0x400EAF2: _dl_init (dl-init.c:126)
==4035== by 0x40011C9: ??? (in /lib/x86_64-linux-gnu/ld-2.19.so)
==4035==
==4035== 2,064 bytes in 1 blocks are still reachable in loss record 7 of 9
==4035== at 0x4C28C4F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4035== by 0x636E8AA: _pixman_implementation_create (pixman-implementation.c:38)
==4035== by 0x63A895F: _pixman_implementation_create_ssse3 (pixman-ssse3.c:345)
==4035== by 0x636EF6B: _pixman_choose_implementation (pixman-implementation.c:393)
==4035== by 0x63262C8: pixman_constructor (pixman.c:39)
==4035== by 0x400EA09: call_init.part.0 (dl-init.c:78)
==4035== by 0x400EAF2: call_init (dl-init.c:36)
==4035== by 0x400EAF2: _dl_init (dl-init.c:126)
==4035== by 0x40011C9: ??? (in /lib/x86_64-linux-gnu/ld-2.19.so)
==4035==
==4035== 2,064 bytes in 1 blocks are still reachable in loss record 8 of 9
==4035== at 0x4C28C4F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4035== by 0x636E8AA: _pixman_implementation_create (pixman-implementation.c:38)
==4035== by 0x63714CF: _pixman_implementation_create_noop (pixman-noop.c:155)
==4035== by 0x63262C8: pixman_constructor (pixman.c:39)
==4035== by 0x400EA09: call_init.part.0 (dl-init.c:78)
==4035== by 0x400EAF2: call_init (dl-init.c:36)
==4035== by 0x400EAF2: _dl_init (dl-init.c:126)
==4035== by 0x40011C9: ??? (in /lib/x86_64-linux-gnu/ld-2.19.so)
==4035==
==4035== 72,704 bytes in 1 blocks are still reachable in loss record 9 of 9
==4035== at 0x4C28C4F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4035== by 0x554E11F: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==4035== by 0x400EA09: call_init.part.0 (dl-init.c:78)
==4035== by 0x400EAF2: call_init (dl-init.c:36)
==4035== by 0x400EAF2: _dl_init (dl-init.c:126)
==4035== by 0x40011C9: ??? (in /lib/x86_64-linux-gnu/ld-2.19.so)
==4035==
==4035== LEAK SUMMARY:
==4035== definitely lost: 0 bytes in 0 blocks
==4035== indirectly lost: 0 bytes in 0 blocks
==4035== possibly lost: 0 bytes in 0 blocks
==4035== still reachable: 86,640 bytes in 9 blocks
==4035== suppressed: 0 bytes in 0 blocks
==4035==
==4035== For counts of detected and suppressed errors, rerun with: -v
==4035== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
No idea about the leaks in libstdc++, but since this in _dl_init, it should be harmless (one-time "leak" during startup).
The same holds for pixman_constructor: Pixman uses optimized code paths based on the CPU on which it runs. This is a one-time initialization that allocates memory that cannot be freed. It's harmless.
That leaves only two leaks. The pattern that is internally allocated in cairo_pop_group_to_source() isn't freed, but so is the cairo context created by cairo_create(). These leaks occurred because I mis-placed the call to cairo_debug_reset_static_data(). I was calling this in main which is before the destructor runs. Moving it to the destructor makes this leak disappear... Sorry for not noticing this earlier and sorry for being too lazy to update the above.
So for me there is an expected leak from pixman and an unexpected leak from libstdc++. Neither is related to cairo. I used cairo version 1.14.2-95-g98d01cd for this.
I'm trying to write a C program with Lua embedded inside.. And, I tried a very simple program to start, it just creates the Lua context, and then destroys it:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
extern "C" {
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
}
int main(int argc, char *argv[]) {
lua_State *L = lua_open();
luaL_openlibs(L);
lua_close(L);
fprintf(stderr, "%s: %d\n", __FILE__, __LINE__);
return(0);
}
I'm compiling it like so: (I'm actually using Torch7, so..)
g++ -c -g3 -O2 -Wall -Werror -I/usr/local/torch/install/include/ -fPIC pure_lua_test.C -o pure_lua_test.o
g++ -g3 -O2 -Wall -Werror -I/usr/local/torch/install/include/ -fPIC -o pure_lua_test pure_lua_test.o -L/usr/local/torch/install/lib/ -lluajit
When I run it on it's own, it prints
pure_lua_test.C: 16
as expected, (just before the return).
But, when I run it with valgrind, (as valgrind ./pure_lua_test )
I get
==9165== Memcheck, a memory error detector
==9165== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==9165== Using Valgrind-3.10.0.SVN and LibVEX; rerun with -h for copyright info
==9165== Command: ./pure_lua_test
==9165==
==9165== Invalid read of size 4
==9165== at 0x4E9EE97: lua_pushcclosure (in /usr/local/src/torch-2015-05-25/install/lib/libluajit.so)
==9165== by 0x4EB4CDD: luaL_openlibs (in /usr/local/src/torch-2015-05-25/install/lib/libluajit.so)
==9165== by 0x400700: main (pure_lua_test.C:13)
==9165== Address 0x8 is not stack'd, malloc'd or (recently) free'd
==9165==
==9165==
==9165== Process terminating with default action of signal 11 (SIGSEGV)
==9165== Access not within mapped region at address 0x8
==9165== at 0x4E9EE97: lua_pushcclosure (in /usr/local/src/torch-2015-05-25/install/lib/libluajit.so)
==9165== by 0x4EB4CDD: luaL_openlibs (in /usr/local/src/torch-2015-05-25/install/lib/libluajit.so)
==9165== by 0x400700: main (pure_lua_test.C:13)
==9165== If you believe this happened as a result of a stack
==9165== overflow in your program's main thread (unlikely but
==9165== possible), you can try to increase the size of the
==9165== main thread stack using the --main-stacksize= flag.
==9165== The main thread stack size used in this run was 8388608.
==9165==
==9165== HEAP SUMMARY:
==9165== in use at exit: 0 bytes in 0 blocks
==9165== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==9165==
==9165== All heap blocks were freed -- no leaks are possible
==9165==
==9165== For counts of detected and suppressed errors, rerun with: -v
==9165== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Does anyone know what's happening? Why is it SIGSEGV'ing in valgrind? Is this something I should worry about? Basically, I was hoping to verify that a plugin I was writing for Torch had no memory leaks... But, if it fails with this, then, I'm kind of stuck.
The reason for this issue seems to be Valgrind, not LuaJIT. Valgrind is blocking MAP_32BIT which breaks LuaJIT. To demonstrate, add a check for NULL on lua_State * L and you'll see it is NULL while run under Valgrind, and non-NULL while running regularly.
Here is the modifications I did to your sample:
if(L == NULL) {
printf("Could not create luaL_newstate()\n");
} else {
luaL_openlibs(L);
lua_close(L);
printf("I can create luaL_newstate fine!\n");
}
When I run this normally:
$ ./pure_lua_test
I can create luaL_newstate fine!
But when I run it under Valgrind:
$ valgrind ./pure_lua_test
==8211== Memcheck, a memory error detector
==8211== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==8211== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==8211== Command: ./pure_lua_test
==8211==
Could not create luaL_newstate()
==8211==
GDB also report that the application exited as it should:
(gdb) run
Starting program: /tmp/pure_lua_test
I can create luaL_newstate fine!
[Inferior 1 (process 8621) exited normally]
Here is a complete MCVE:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
extern "C" {
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
}
int main(int argc, char *argv[]) {
lua_State *L;
L = luaL_newstate();
if(L == NULL) {
printf("Could not create luaL_newstate()\n");
} else {
luaL_openlibs(L);
lua_close(L);
printf("I can create luaL_newstate fine!\n");
}
return(0);
}
Here is a nice article on MAP_32BIT. Hope this helps in any way.