Bug 105466

Summary: Memory leak in cairo_show_text
Product: cairo Reporter: Philip Chimento <philip.chimento>
Component: freetype font backendAssignee: David Turner <david>
Status: RESOLVED INVALID QA Contact: cairo-bugs mailing list <cairo-bugs>
Severity: normal    
Priority: medium    
Version: unspecified   
Hardware: Other   
OS: All   
Whiteboard:
i915 platform: i915 features:

Description Philip Chimento 2018-03-12 21:25:18 UTC
Here's a valgrind trace that I don't think is caused by my code:

==28514==    at 0x4C2DB6B: malloc (vg_replace_malloc.c:299)
==28514==    by 0xBCC752D: ??? (in /usr/lib64/libfontconfig.so.1.10.1)
==28514==    by 0xBCC7CD6: ??? (in /usr/lib64/libfontconfig.so.1.10.1)
==28514==    by 0xBCC8777: FcPatternDuplicate (in /usr/lib64/libfontconfig.so.1.10.1)
==28514==    by 0x9907AA2: _cairo_ft_font_face_create_for_pattern (cairo-ft-font.c:2976)
==28514==    by 0x9907CAF: _cairo_ft_font_face_create_for_toy (cairo-ft-font.c:2817)
==28514==    by 0x98BD9FF: _cairo_toy_font_face_create_impl_face (cairo-toy-font-face.c:168)
==28514==    by 0x98BD9FF: _cairo_toy_font_face_init (cairo-toy-font-face.c:197)
==28514==    by 0x98BD9FF: cairo_toy_font_face_create (cairo-toy-font-face.c:321)
==28514==    by 0x986E14F: _cairo_gstate_ensure_font_face (cairo-gstate.c:1828)
==28514==    by 0x986E14F: _cairo_gstate_ensure_scaled_font (cairo-gstate.c:1850)
==28514==    by 0x9870E40: _cairo_gstate_get_scaled_font (cairo-gstate.c:1733)
==28514==    by 0x986973F: _cairo_default_context_get_scaled_font (cairo-default-context.c:1282)
==28514==    by 0x9863A02: cairo_show_text (cairo.c:3218)

Cairo 1.14.10. It looks like there's a FcPattern being leaked.
Comment 1 Uli Schlachter 2018-03-15 20:42:01 UTC
Could you provide some self-contained example that reproduces the problem, please? Preferably a self-contained one that additionally calls cairo_debug_reset_static_data() before exiting.
Comment 2 Philip Chimento 2018-03-17 06:18:28 UTC
cairo_debug_reset_static_data() makes it go away, thanks.

Is it OK to call that function unconditionally before program exit, or should it only be done when running Valgrind?
Comment 3 Uli Schlachter 2018-03-17 10:24:11 UTC
Uhm, why would it be bad to call that function unconditionally? It's not like it starts crashing when not run under valgrind, or anything...?!? Sorry, I don't understand the question. Do the docs help you perhaps? https://www.cairographics.org/manual/cairo-Error-handling.html#cairo-debug-reset-static-data
Comment 4 Philip Chimento 2018-03-17 19:57:20 UTC
To me, the sentence from the docs "This function is intended to be useful when using memory-checking tools such as valgrind", as well as the prefix of "cairo_debug" implied that it might not be good to call in production. Thanks for clarifying that.
Comment 5 loplrf 2018-04-01 02:48:07 UTC
```
#include <cairo.h>

int main(int argc, char *argv[])
{
	cairo_surface_t *surf = cairo_image_surface_create(CAIRO_FORMAT_RGB24, 100, 100);
	cairo_t *ctx = cairo_create(surf);
	cairo_set_source_rgb(ctx, 1., .6863, 0.);
	cairo_move_to(ctx, 0, 50);
	cairo_show_text(ctx, "Hello, Cairo!");
	cairo_destroy(ctx);
	cairo_surface_write_to_png(surf, "greeting.png");
	cairo_surface_destroy(surf);
	cairo_debug_reset_static_data();
	return 0;
}
```
and the log file created by Valgrind is
```
==17769== Memcheck, a memory error detector
==17769== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==17769== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==17769== Command: ./a.out
==17769== Parent PID: 7856
==17769== 
==17769== 
==17769== HEAP SUMMARY:
==17769==     in use at exit: 191,425 bytes in 2,593 blocks
==17769==   total heap usage: 5,111 allocs, 2,518 frees, 1,600,799 bytes allocated
==17769== 
==17769== 128 bytes in 1 blocks are definitely lost in loss record 66 of 108
==17769==    at 0x482E37C: malloc (in /usr/local/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==17769==    by 0x4C59026: ??? (in /usr/lib/i386-linux-gnu/libfontconfig.so.1.8.0)
==17769==    by 0x4C548A6: FcConfigSubstituteWithPat (in /usr/lib/i386-linux-gnu/libfontconfig.so.1.8.0)
==17769==    by 0x4C54965: FcConfigSubstitute (in /usr/lib/i386-linux-gnu/libfontconfig.so.1.8.0)
==17769==    by 0x492A761: _cairo_ft_resolve_pattern (cairo-ft-font.c:3364)
==17769==    by 0x492A008: _cairo_ft_font_face_get_implementation (cairo-ft-font.c:3054)
==17769==    by 0x48D2DF3: _cairo_toy_font_face_get_implementation (cairo-toy-font-face.c:401)
==17769==    by 0x48B4DB6: cairo_scaled_font_create (cairo-scaled-font.c:1134)
==17769==    by 0x486DC15: _cairo_gstate_ensure_scaled_font (cairo-gstate.c:1915)
==17769==    by 0x486DADC: _cairo_gstate_get_scaled_font (cairo-gstate.c:1787)
==17769==    by 0x4866292: _cairo_default_context_get_scaled_font (cairo-default-context.c:1300)
==17769==    by 0x485AFBF: cairo_get_scaled_font (cairo.c:3348)
==17769== 
==17769== LEAK SUMMARY:
==17769==    definitely lost: 128 bytes in 1 blocks
==17769==    indirectly lost: 20 bytes in 1 blocks
==17769==      possibly lost: 0 bytes in 0 blocks
==17769==    still reachable: 191,149 bytes in 2,590 blocks
==17769==         suppressed: 128 bytes in 1 blocks
==17769== Reachable blocks (those to which a pointer was found) are not shown.
==17769== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==17769== 
==17769== For counts of detected and suppressed errors, rerun with: -v
==17769== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 1 from 1)
```

I'm using cairo-1.15.10 on Debian 9.4. Thanks :)
Comment 6 Philip Chimento 2018-04-04 04:49:22 UTC
That trace looks similar to the trace from the memory leak that I still get even when calling cairo_debug_reset_static_data(). I was wondering if I was doing something else wrong, but I suspect my original reproducer was a red herring.

==20862==    at 0x4C2DBAB: malloc (vg_replace_malloc.c:299)
==20862==    by 0xB5B0531: FcPatternCreate (fcpat.c:33)
==20862==    by 0xB5B8364: FcParsePattern (fcxml.c:2896)
==20862==    by 0xB5B8364: FcEndElement (fcxml.c:3027)
==20862==    by 0xF744445: doContent (xmlparse.c:2954)
==20862==    by 0xF744EEF: contentProcessor (xmlparse.c:2531)
==20862==    by 0xF74294A: doProlog (xmlparse.c:4556)
==20862==    by 0xF743898: prologProcessor (xmlparse.c:4270)
==20862==    by 0xF7475D9: XML_ParseBuffer (xmlparse.c:1983)
==20862==    by 0xB5B748A: FcConfigParseAndLoadFromMemoryInternal (fcxml.c:3342)
==20862==    by 0xB5B7899: _FcConfigParse (fcxml.c:3462)
==20862==    by 0xB5B7AAA: FcConfigParseAndLoadDir (fcxml.c:3240)
==20862==    by 0xB5B7AAA: _FcConfigParse (fcxml.c:3438)
==20862==    by 0xB5A981C: FcInitLoadOwnConfig (fcinit.c:97)
==20862==    by 0xB5A9A79: FcInitLoadOwnConfigAndFonts (fcinit.c:169)
==20862==    by 0xB59C116: FcConfigEnsure.part.8 (fccfg.c:45)
==20862==    by 0xB59E1A8: FcConfigEnsure (fccfg.c:38)
==20862==    by 0xB59E1A8: IA__FcConfigGetCurrent (fccfg.c:481)
==20862==    by 0xB59E1A8: FcConfigSubstituteWithPat (fccfg.c:1532)
==20862==    by 0x9172F3B: _cairo_ft_resolve_pattern (cairo-ft-font.c:3364)
==20862==    by 0x9172F3B: _cairo_ft_font_face_get_implementation (cairo-ft-font.c:3054)
==20862==    by 0x910C282: cairo_scaled_font_create (cairo-scaled-font.c:1134)
==20862==    by 0x90CF1CD: _cairo_gstate_ensure_scaled_font (cairo-gstate.c:1915)
==20862==    by 0x90D2344: _cairo_gstate_get_scaled_font (cairo-gstate.c:1787)
==20862==    by 0x90CA0F3: _cairo_default_context_get_scaled_font (cairo-default-context.c:1300)
==20862==    by 0x90C3F42: cairo_show_text (cairo.c:3529)
==20862==    by 0x4EA68FA: ??? (cairo-context.cpp:666)
Comment 7 Uli Schlachter 2018-04-07 07:50:55 UTC
Call FcFini() to shutdown fontconfig.

And if you start seeing a 256k memory leak from pixman_glyph_cache_create(), update to a cairo version that includes 1ed124ace20194.

With the above changes, here is my valgrind output for the example from comment #5:

==5212== HEAP SUMMARY:
==5212==     in use at exit: 12,384 bytes in 6 blocks
==5212==   total heap usage: 5,704 allocs, 5,698 frees, 2,172,379 bytes allocated
==5212== 
==5212== 2,064 bytes in 1 blocks are still reachable in loss record 1 of 6
==5212==    at 0x4C2EBA5: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5212==    by 0x5A27FCF: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x5A26B3E: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x5A28675: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x59E0508: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x400EF09: call_init.part.0 (dl-init.c:72)
==5212==    by 0x400F015: call_init (dl-init.c:118)
==5212==    by 0x400F015: _dl_init (dl-init.c:119)
==5212==    by 0x40010C9: ??? (in /lib/x86_64-linux-gnu/ld-2.27.so)
==5212== 
==5212== 2,064 bytes in 1 blocks are still reachable in loss record 2 of 6
==5212==    at 0x4C2EBA5: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5212==    by 0x5A27FCF: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x5A2563F: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x5A28707: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x59E0508: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x400EF09: call_init.part.0 (dl-init.c:72)
==5212==    by 0x400F015: call_init (dl-init.c:118)
==5212==    by 0x400F015: _dl_init (dl-init.c:119)
==5212==    by 0x40010C9: ??? (in /lib/x86_64-linux-gnu/ld-2.27.so)
==5212== 
==5212== 2,064 bytes in 1 blocks are still reachable in loss record 3 of 6
==5212==    at 0x4C2EBA5: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5212==    by 0x5A27FCF: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x5A4280F: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x5A0CB50: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x5A28690: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x59E0508: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x400EF09: call_init.part.0 (dl-init.c:72)
==5212==    by 0x400F015: call_init (dl-init.c:118)
==5212==    by 0x400F015: _dl_init (dl-init.c:119)
==5212==    by 0x40010C9: ??? (in /lib/x86_64-linux-gnu/ld-2.27.so)
==5212== 
==5212== 2,064 bytes in 1 blocks are still reachable in loss record 4 of 6
==5212==    at 0x4C2EBA5: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5212==    by 0x5A27FCF: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x5A6306F: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x5A0CB18: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x5A28690: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x59E0508: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x400EF09: call_init.part.0 (dl-init.c:72)
==5212==    by 0x400F015: call_init (dl-init.c:118)
==5212==    by 0x400F015: _dl_init (dl-init.c:119)
==5212==    by 0x40010C9: ??? (in /lib/x86_64-linux-gnu/ld-2.27.so)
==5212== 
==5212== 2,064 bytes in 1 blocks are still reachable in loss record 5 of 6
==5212==    at 0x4C2EBA5: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5212==    by 0x5A27FCF: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x5A6372F: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x5A28690: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x59E0508: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x400EF09: call_init.part.0 (dl-init.c:72)
==5212==    by 0x400F015: call_init (dl-init.c:118)
==5212==    by 0x400F015: _dl_init (dl-init.c:119)
==5212==    by 0x40010C9: ??? (in /lib/x86_64-linux-gnu/ld-2.27.so)
==5212== 
==5212== 2,064 bytes in 1 blocks are still reachable in loss record 6 of 6
==5212==    at 0x4C2EBA5: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5212==    by 0x5A27FCF: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x5A2B01F: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x5A286B0: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x59E0508: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
==5212==    by 0x400EF09: call_init.part.0 (dl-init.c:72)
==5212==    by 0x400F015: call_init (dl-init.c:118)
==5212==    by 0x400F015: _dl_init (dl-init.c:119)
==5212==    by 0x40010C9: ??? (in /lib/x86_64-linux-gnu/ld-2.27.so)
==5212== 
==5212== LEAK SUMMARY:
==5212==    definitely lost: 0 bytes in 0 blocks
==5212==    indirectly lost: 0 bytes in 0 blocks
==5212==      possibly lost: 0 bytes in 0 blocks
==5212==    still reachable: 12,384 bytes in 6 blocks
==5212==         suppressed: 0 bytes in 0 blocks

(No, pixman does not provide a function to destroy its global_implementation variable that is allocated by _pixman_choose_implementation(); no, this is not a real memory leak; no, this is not cairo's "fault")
Comment 8 Philip Chimento 2018-04-09 20:50:19 UTC
(In reply to Uli Schlachter from comment #7)
> Call FcFini() to shutdown fontconfig.

Would you consider a patch to call this in cairo_debug_reset_static_data()? It's probably fine as it is, but on the other hand it's a not-easily-discoverable burden to callers if they have to link directly to all of Cairo's dependent libraries to call their respective deinitialization functions. (I can't think of a use case when you _wouldn't_ want to deinit fontconfig while deiniting Cairo, but maybe there is one?)

> And if you start seeing a 256k memory leak from pixman_glyph_cache_create(),
> update to a cairo version that includes 1ed124ace20194.
> 
> With the above changes, here is my valgrind output for the example from
> comment #5:
> 
> [snip]
>
> (No, pixman does not provide a function to destroy its global_implementation
> variable that is allocated by _pixman_choose_implementation(); no, this is
> not a real memory leak; no, this is not cairo's "fault")

Yes, I know the difference between a memory leak and static data that is held until program shutdown. I am not accusing anybody or saying that anything is anybody's fault.

Use of freedesktop.org services, including Bugzilla, is subject to our Code of Conduct. How we collect and use information is described in our Privacy Policy.