contour: Coredumps when drawing Unicode block characters

Description

When attempting to draw box drawing characters, contour attempts to free() an invalid pointer and dumps core.

Environment

  • Contour Version or Git commit hash: 2814f42f705756cd2dbaeb8579618801c714c7fd
  • Operating System (name / distribution / version): Debian GNU/Linux 11
  • Contour configuration: None
  • TERM environment variable: xterm-256color
  • Compiler version: g++ 10.2.1

Steps to Reproduce

  1. Install ugrep
  2. Try running ugrep 0..10FFFF which will show every assigned unicode character.
  3. Eventually, Contour will crash

The point appears to be in the range of box drawing glyphs (U+2500) as I’m able to get it to coredump immediately when I type:

ugrep box draw

Update:

This does not crash:

ugrep 2500..70

Nor does this:

ugrep 2580..FF

But this crashes:

ugrep 2570..F

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 34 (26 by maintainers)

Most upvoted comments

/cc @data-man

My way is to rm -rf your_build_dir/* and then run cmake ... && make ... again. The problem is, whenever dependencies are updated inside of the repo, CPM is getting confused. CPM is a CMake based package manager.

@hackerb9 How do you compile Contour? Unit tests are enabled by default. Please try with -DCONTOUR_TESTING=OFF.

I’m building with:

cmake -DCMAKE_BUILD_TYPE=RELWITHDEBINFO .  &&  make -j

It’s compiling fine at the moment , but I’ll try turning off unit tests if it gives me more guff.

We use Catch2 v3. I doubt this version exists in all popular OS repositories.

Oh, I see that you are correct. Cmake was ignoring the system version of catch2 I had installed. I guess the git clean -f was what cmake needed to be able to get past whatever was hanging up CPM.

I think this one is fixed now. Ideally @hackerb9 would confirm it for his case. But i’m closing as I’m pretty confident it is. 😃

Yup, you fixed it!

[Sorry for the delay. I hadn’t been able to recompile contour as cmake’s package manager was hanging on trying to install catch2. I gave up on trying to figure out why and just did an apt install catch2; git clean -f and now all is well.]

@data-man This is due to the CPM related dependency updates. Is this a CPM thingy or is there any way to mitigate this? I keep having the same issue and it sometimes drives me nuts

@hackerb9 can you add some leading space(s) in front of your ugrep lines. This is sometimes so unreadable. Also fonttable would be nice to accept a list of ranges of Codepoints that i want it to print. Do you want me to create a ticket for these?

i am seeing a similar minmap_chunk():

I need more unit tests. I think that is the next ticket i gonna tackle.

Holy weck? You can disable built-in box drawing per config. But that should be fixed ASAP. Thanks.

That’s interesting! Font rendering could be the issue. In particular, perhaps my machine is using a corrupt font for just those characters. The weird thing is that it doesn’t affect any other terminals. I’m using FreeType 2.10.4:

$ apt policy libfreetype6-dev libfreetype6-dev: Installed: 2.10.4+dfsg-1 Candidate: 2.10.4+dfsg-1 Version table: *** 2.10.4+dfsg-1 500 500 http://deb.debian.org/debian bullseye/main amd64 Packages 100 /var/lib/dpkg/status

I just tried XTerm (which is linked with the same version of FreeType) and it had no issue. It is not linked with harfbuzz, though. My harfbuzz is 2.7.4-1.

$ apt policy libharfbuzz-dev libharfbuzz-dev: Installed: 2.7.4-1 Candidate: 2.7.4-1 Version table: *** 2.7.4-1 500 500 http://deb.debian.org/debian bullseye/main amd64 Packages 100 /var/lib/dpkg/status

My UnicodeData.txt is version 13.0.0, but I’m pretty sure that is unrelated.


So, I ran it under a debugger and have narrowed the crash down to src/terminal_renderer/BoxDrawingRenderer.cpp:512. The name of the file implies Contour is treating box drawing characters special, which leads me to think the problem is not in FreeType, HarfBuzz, or UnicodeData.txt.

gdb
(gdb) run
Starting program: src/contour/src/contour/contour 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[error] Superfluous config key found: profiles.main.font.regular
[error] Superfluous config key found: profiles.main.hyperlink_decoration.default
Warning: Ignoring XDG_SESSION_TYPE=wayland on Gnome. Use QT_QPA_PLATFORM=wayland to run on Wayland anyway.
[New Thread 0x7ffff2a48700 (LWP 530358)]
[New Thread 0x7ffff0e9f700 (LWP 530359)]
[New Thread 0x7ffff069e700 (LWP 530360)]
[New Thread 0x7fffefe66700 (LWP 530361)]
[New Thread 0x7fffef64f700 (LWP 530362)]
[New Thread 0x7fffeec31700 (LWP 530363)]
[New Thread 0x7fffee430700 (LWP 530365)]
[Detaching after fork from child process 530366]
[New Thread 0x7fffed82e700 (LWP 530367)]
[New Thread 0x7fffebe20700 (LWP 530369)]
[New Thread 0x7fffe328f700 (LWP 530370)]
[New Thread 0x7fffe294d700 (LWP 530371)]
[New Thread 0x7fffe214c700 (LWP 530372)]
[New Thread 0x7fffe194b700 (LWP 530373)]
[New Thread 0x7fffe114a700 (LWP 530374)]
[New Thread 0x7fffe0949700 (LWP 530375)]
[New Thread 0x7fffe0148700 (LWP 530376)]
[New Thread 0x7fffdf947700 (LWP 530377)]
[New Thread 0x7fffdf146700 (LWP 530378)]
[New Thread 0x7fffde945700 (LWP 530379)]
[New Thread 0x7fffde144700 (LWP 530380)]
[New Thread 0x7fffdd943700 (LWP 530381)]
[New Thread 0x7fffdd142700 (LWP 530382)]
[New Thread 0x7fffdc941700 (LWP 530383)]
[New Thread 0x7fffdc140700 (LWP 530384)]
[New Thread 0x7fffdb93f700 (LWP 530385)]
[New Thread 0x7fffdb13e700 (LWP 530386)]
[New Thread 0x7fffda93d700 (LWP 530387)]
[New Thread 0x7fffda13c700 (LWP 530388)]
[New Thread 0x7fffd993b700 (LWP 530389)]
[New Thread 0x7fffd913a700 (LWP 530390)]
[New Thread 0x7fffd8939700 (LWP 530391)]
[New Thread 0x7fffd8138700 (LWP 530392)]
[New Thread 0x7fffd7937700 (LWP 530393)]
[New Thread 0x7fffd7136700 (LWP 530394)]
[New Thread 0x7fffd6935700 (LWP 530395)]
[New Thread 0x7fffd6134700 (LWP 530396)]
[New Thread 0x7fffd5933700 (LWP 530397)]
[New Thread 0x7fffd5132700 (LWP 530398)]
[New Thread 0x7fffd4931700 (LWP 530399)]
[New Thread 0x7fffd4130700 (LWP 530400)]
[New Thread 0x7fffd392f700 (LWP 530401)]
[New Thread 0x7fffd312e700 (LWP 530402)]
[New Thread 0x7fffd292d700 (LWP 530403)]
[New Thread 0x7fffd212c700 (LWP 530404)]
[New Thread 0x7fffd192b700 (LWP 530405)]
[New Thread 0x7fffd112a700 (LWP 530406)]
[New Thread 0x7fffd0929700 (LWP 530407)]
[New Thread 0x7fffd0128700 (LWP 530408)]
[New Thread 0x7fffcf527700 (LWP 530409)]
[New Thread 0x7fffce8db700 (LWP 530410)]
[New Thread 0x7fffcd3e6700 (LWP 530411)]
free(): invalid pointer

Thread 1 "contour" received signal SIGABRT, Aborted.
__GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
50      ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0  __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1  0x00007ffff649d537 in __GI_abort () at abort.c:79
#2  0x00007ffff64f6768 in __libc_message (action=action@entry=do_abort, fmt=fmt@entry=0x7ffff6604e2d "%s\n") at ../sysdeps/posix/libc_fatal.c:155
#3  0x00007ffff64fda5a in malloc_printerr (str=str@entry=0x7ffff660305a "free(): invalid pointer") at malloc.c:5347
#4  0x00007ffff64ffca6 in free_check (mem=0x1b1cb10, caller=<optimized out>) at hooks.c:255
#5  0x000000000056e450 in __gnu_cxx::new_allocator<unsigned char>::deallocate(unsigned char*, unsigned long)
    (__t=<optimized out>, __p=<optimized out>, this=0x7fffffffcd80) at /usr/include/c++/10/ext/new_allocator.h:133
#6  std::allocator_traits<std::allocator<unsigned char> >::deallocate(std::allocator<unsigned char>&, unsigned char*, unsigned long)
    (__n=<optimized out>, __p=<optimized out>, __a=...) at /usr/include/c++/10/bits/alloc_traits.h:492
#7  std::_Vector_base<unsigned char, std::allocator<unsigned char> >::_M_deallocate(unsigned char*, unsigned long)
    (__n=<optimized out>, __p=<optimized out>, this=0x7fffffffcd80) at /usr/include/c++/10/bits/stl_vector.h:354
#8  std::_Vector_base<unsigned char, std::allocator<unsigned char> >::~_Vector_base() (this=0x7fffffffcd80, __in_chrg=<optimized out>)
    at /usr/include/c++/10/bits/stl_vector.h:335
#9  std::vector<unsigned char, std::allocator<unsigned char> >::~vector() (this=0x7fffffffcd80, __in_chrg=<optimized out>)
    at /usr/include/c++/10/bits/stl_vector.h:683
#10 std::_Optional_payload_base<std::vector<unsigned char, std::allocator<unsigned char> > >::_M_destroy() (this=0x7fffffffcd80)
    at /usr/include/c++/10/optional:260
#11 std::_Optional_payload_base<std::vector<unsigned char, std::allocator<unsigned char> > >::_M_reset() (this=0x7fffffffcd80)
    at /usr/include/c++/10/optional:280
#12 std::_Optional_payload<std::vector<unsigned char, std::allocator<unsigned char> >, false, false, false>::~_Optional_payload()
    (this=0x7fffffffcd80, __in_chrg=<optimized out>) at /usr/include/c++/10/optional:401
#13 std::_Optional_base<std::vector<unsigned char, std::allocator<unsigned char> >, false, false>::~_Optional_base()
    (this=0x7fffffffcd80, __in_chrg=<optimized out>) at /usr/include/c++/10/optional:474
#14 std::optional<std::vector<unsigned char, std::allocator<unsigned char> > >::~optional() (this=0x7fffffffcd80, __in_chrg=<optimized out>)
    at /usr/include/c++/10/optional:659
#15 terminal::renderer::BoxDrawingRenderer::getDataRef(unsigned char) (this=0x80c5c8, _id=113 'q')
    at src/contour/src/terminal_renderer/BoxDrawingRenderer.cpp:512
#16 0x000000000056e83b in terminal::renderer::BoxDrawingRenderer::render(crispy::boxed<int, terminal::detail::tags::LinePosition>, crispy::boxed<int, terminal::detail::tags::Column>, unsigned char, terminal::RGBColor) (this=this@entry=0x80c5c8, _line=..., _column=..., _id=<optimized out>, _color=...)
    at src/contour/src/terminal_renderer/BoxDrawingRenderer.cpp:456
#17 0x000000000057b635 in terminal::renderer::TextRenderer::renderCell(terminal::RenderCell const&) (this=this@entry=0x80c540, _cell=...)
    at src/contour/src/terminal_renderer/TextRenderer.cpp:137
#18 0x00000000005751ea in terminal::renderer::Renderer::renderCells(std::vector<terminal::RenderCell, std::allocator<terminal::RenderCell> > const&)
    (this=this@entry=0x80c278, _renderableCells=<optimized out>) at src/contour/src/terminal_renderer/Renderer.cpp:340
#19 0x0000000000575341 in terminal::renderer::Renderer::render(terminal::Terminal&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > >, bool) (this=this@entry=0x80c278, _terminal=..., _now=..., _pressure=_pressure@entry=false)
    at src/contour/src/terminal/RenderBuffer.h:61
#20 0x0000000000561307 in contour::opengl::TerminalWidget::paintGL() (this=0x80c1f0) at src/contour/src/contour/opengl/TerminalWidget.cpp:449
#21 0x00007ffff7951c5d in  () at /lib/x86_64-linux-gnu/libQt5Widgets.so.5
#22 0x00007ffff7933fae in QWidget::event(QEvent*) () at /lib/x86_64-linux-gnu/libQt5Widgets.so.5
#23 0x0000000000564ea8 in contour::opengl::TerminalWidget::event(QEvent*) (this=0x80c1f0, _event=0x7fffffffd140)
    at src/contour/src/contour/opengl/TerminalWidget.cpp:571
#24 0x00007ffff78f415f in QApplicationPrivate::notify_helper(QObject*, QEvent*) () at /lib/x86_64-linux-gnu/libQt5Widgets.so.5
#25 0x00007ffff6c17fca in QCoreApplication::notifyInternal2(QObject*, QEvent*) () at /lib/x86_64-linux-gnu/libQt5Core.so.5
#26 0x00007ffff792c116 in QWidgetPrivate::sendPaintEvent(QRegion const&) () at /lib/x86_64-linux-gnu/libQt5Widgets.so.5
#27 0x00007ffff7904ba8 in  () at /lib/x86_64-linux-gnu/libQt5Widgets.so.5
#28 0x00007ffff79346d7 in QWidget::event(QEvent*) () at /lib/x86_64-linux-gnu/libQt5Widgets.so.5
#29 0x00007ffff78f415f in QApplicationPrivate::notify_helper(QObject*, QEvent*) () at /lib/x86_64-linux-gnu/libQt5Widgets.so.5
#30 0x00007ffff6c17fca in QCoreApplication::notifyInternal2(QObject*, QEvent*) () at /lib/x86_64-linux-gnu/libQt5Core.so.5
#31 0x00007ffff6c1aa01 in QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) () at /lib/x86_64-linux-gnu/libQt5Core.so.5
#32 0x00007ffff6c6fe93 in  () at /lib/x86_64-linux-gnu/libQt5Core.so.5
#33 0x00007ffff62dae6b in g_main_context_dispatch () at /lib/x86_64-linux-gnu/libglib-2.0.so.0
#34 0x00007ffff62db118 in  () at /lib/x86_64-linux-gnu/libglib-2.0.so.0
#35 0x00007ffff62db1cf in g_main_context_iteration () at /lib/x86_64-linux-gnu/libglib-2.0.so.0
#36 0x00007ffff6c6f51f in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) () at /lib/x86_64-linux-gnu/libQt5Core.so.5
#37 0x00007ffff6c1698b in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) () at /lib/x86_64-linux-gnu/libQt5Core.so.5
#38 0x00007ffff6c1ec00 in QCoreApplication::exec() () at /lib/x86_64-linux-gnu/libQt5Core.so.5
#39 0x00000000004d6a09 in contour::terminalGUI(int, char const**, crispy::cli::FlagStore const&) (argc=<optimized out>, argv=0x7fffffffe638, _flags=...) at src/contour/src/contour/ContourGuiApp.cpp:188
#40 0x0000000000581edd in std::function<int ()>::operator()() const (this=0x668230) at /usr/include/c++/10/bits/std_function.h:622
#41 crispy::App::run(int, char const**) (this=this@entry=0x7fffffffe360, argc=argc@entry=1, argv=argv@entry=0x7fffffffe638) at src/contour/src/crispy/App.cpp:242
#42 0x00000000004d4852 in contour::ContourGuiApp::run(int, char const**) (this=this@entry=0x7fffffffe360, argc=argc@entry=1, argv=argv@entry=0x7fffffffe638) at src/contour/src/contour/ContourGuiApp.cpp:52
#43 0x000000000048e7fb in main(int, char const**) (argc=1, argv=0x7fffffffe638) at src/contour/src/contour/main.cpp:24
(gdb) up
#1  0x00007ffff649d537 in __GI_abort () at abort.c:79
79      abort.c: No such file or directory.
(gdb) 
#2  0x00007ffff64f6768 in __libc_message (action=action@entry=do_abort, fmt=fmt@entry=0x7ffff6604e2d "%s\n") at ../sysdeps/posix/libc_fatal.c:155
155     ../sysdeps/posix/libc_fatal.c: No such file or directory.
(gdb) 
#3  0x00007ffff64fda5a in malloc_printerr (str=str@entry=0x7ffff660305a "free(): invalid pointer") at malloc.c:5347
5347    malloc.c: No such file or directory.
(gdb) 
#4  0x00007ffff64ffca6 in free_check (mem=0x1b1cb10, caller=<optimized out>) at hooks.c:255
255     hooks.c: No such file or directory.
(gdb) 
#5  0x000000000056e450 in __gnu_cxx::new_allocator<unsigned char>::deallocate (__t=<optimized out>, __p=<optimized out>, this=0x7fffffffcd80)
    at /usr/include/c++/10/ext/new_allocator.h:133
133             ::operator delete(__p
(gdb) 
#6  std::allocator_traits<std::allocator<unsigned char> >::deallocate (__n=<optimized out>, __p=<optimized out>, __a=...)
    at /usr/include/c++/10/bits/alloc_traits.h:492
492           { __a.deallocate(__p, __n); }
(gdb) 
#7  std::_Vector_base<unsigned char, std::allocator<unsigned char> >::_M_deallocate (__n=<optimized out>, __p=<optimized out>, this=0x7fffffffcd80)
    at /usr/include/c++/10/bits/stl_vector.h:354
354               _Tr::deallocate(_M_impl, __p, __n);
(gdb) 
#8  std::_Vector_base<unsigned char, std::allocator<unsigned char> >::~_Vector_base (this=0x7fffffffcd80, __in_chrg=<optimized out>)
    at /usr/include/c++/10/bits/stl_vector.h:335
335             _M_deallocate(_M_impl._M_start,
(gdb) 
#9  std::vector<unsigned char, std::allocator<unsigned char> >::~vector (this=0x7fffffffcd80, __in_chrg=<optimized out>)
    at /usr/include/c++/10/bits/stl_vector.h:683
683           }
(gdb) 
#10 std::_Optional_payload_base<std::vector<unsigned char, std::allocator<unsigned char> > >::_M_destroy (this=0x7fffffffcd80)
    at /usr/include/c++/10/optional:260
260             _M_payload._M_value.~_Stored_type();
(gdb) 
#11 std::_Optional_payload_base<std::vector<unsigned char, std::allocator<unsigned char> > >::_M_reset (this=0x7fffffffcd80) at /usr/include/c++/10/optional:280
280               _M_destroy();
(gdb) 
#12 std::_Optional_payload<std::vector<unsigned char, std::allocator<unsigned char> >, false, false, false>::~_Optional_payload (this=0x7fffffffcd80, 
    __in_chrg=<optimized out>) at /usr/include/c++/10/optional:401
401           ~_Optional_payload() { this->_M_reset(); }
(gdb) 
#13 std::_Optional_base<std::vector<unsigned char, std::allocator<unsigned char> >, false, false>::~_Optional_base (this=0x7fffffffcd80, 
    __in_chrg=<optimized out>) at /usr/include/c++/10/optional:474
474         struct _Optional_base
(gdb) up
#14 std::optional<std::vector<unsigned char, std::allocator<unsigned char> > >::~optional (this=0x7fffffffcd80, __in_chrg=<optimized out>)
    at /usr/include/c++/10/optional:659
659         class optional
(gdb) 
#15 terminal::renderer::BoxDrawingRenderer::getDataRef (this=0x80c5c8, _id=113 'q') at src/contour/src/terminal_renderer/BoxDrawingRenderer.cpp:512
512             auto tmp = build(_id, gridMetrics_.cellSize, gridMetrics_.underline.thickness);
(gdb) list 500,524
500             }();
501             auto const supersamplingSize = gridMetrics_.cellSize * supersamplingFactor;
502             auto const supersamplingLineThickness = gridMetrics_.underline.thickness * 2;
503             auto tmp = build(_id, supersamplingSize, supersamplingLineThickness);
504             if (!tmp)
505                 return nullopt;
506
507             //buffer = downsample(*tmp, gridMetrics_.cellSize, supersamplingFactor);
508             buffer = downsample(*tmp, 1, supersamplingSize, gridMetrics_.cellSize);
509         }
510         else
511         {
512             auto tmp = build(_id, gridMetrics_.cellSize, gridMetrics_.underline.thickness);
513             if (!tmp)
514                 return nullopt;
515             buffer = move(*tmp);
516         }
517
518         return textureAtlas_->insert(
519             _id,
520             gridMetrics_.cellSize,
521             gridMetrics_.cellSize,
522             move(buffer)
523         );
524     }
(gdb) up
#16 0x000000000056e83b in terminal::renderer::BoxDrawingRenderer::render (this=this@entry=0x80c5c8, _line=..., _column=..., _id=<optimized out>, _color=...)
    at src/contour/src/terminal_renderer/BoxDrawingRenderer.cpp:456
456         auto data = getDataRef(_id);
(gdb) down
#15 terminal::renderer::BoxDrawingRenderer::getDataRef (this=0x80c5c8, _id=113 'q') at src/contour/src/terminal_renderer/BoxDrawingRenderer.cpp:512
512             auto tmp = build(_id, gridMetrics_.cellSize, gridMetrics_.underline.thickness);
(gdb) p _id
$6 = 113 'q'
(gdb) p gridMetrics_ .cellSize 
$7 = {
  width = {
    value = 10
  },
  height = {
    value = 19
  }
}
(gdb) p gridMetrics_.underline.
position   thickness  
(gdb) p gridMetrics_.underline.thickness 
$8 = 2