jtdaugherty / vty

A high-level ncurses alternative written in Haskell
BSD 3-Clause "New" or "Revised" License
319 stars 57 forks source link

Early input triggers segfault #266

Closed jtdaugherty closed 11 months ago

jtdaugherty commented 11 months ago

Keyboard input queued up on stdin results in a segfault once Vty is initialized. Here's some output from one of the Brick demo programs when keyboard input is (quickly) entered between program start and Vty initialization:

brick-table-demo(96668,0x1ff01de00) malloc: *** error for object 0x1: pointer being freed was not allocated
brick-table-demo(96668,0x1ff01de00) malloc: *** set a breakpoint in malloc_error_break to debug

See also https://github.com/haskell/ghcup-hs/issues/887

jtdaugherty commented 11 months ago

Here's a minimal test program to reproduce the behavior, given sufficiently quick keyboard input:

module Main where

import Graphics.Vty

main :: IO ()
main = do
    vty <- mkVty defaultConfig
    shutdown vty

I just cabal run this and start typing right after hitting Enter and that pretty consistently reproduces the behavior.

ShrykeWindgrace commented 11 months ago

I don't have an access to a Linux machine, so here come some speculations)

I wonder if that's somehow related to the change of input buffering - per haddocks of hSetBufferingMode there's a flush if the mode is changed to the NoBuffering.

Is the problem reproducible if you hit non-enter keys?

jtdaugherty commented 11 months ago

Yes, non-Enter input also triggers the bug.

ShrykeWindgrace commented 11 months ago

I managed to reproduce the segfault if I run this app as stack exec -- vtyCrash. If I run it directly as ./.stack-work/bla-bla-some-hash/bin/vtyCrash, everything runs smoothly. I guess that I hit enter not fast enough =)

Anyway, "flushing" stdin prior to configuring vty seems to fix the problem:

module Main (main) where

import Graphics.Vty
import System.IO
import Control.Monad.Extra

main :: IO ()
main = do
    flushStdin
    vty <- mkVty defaultConfig
    shutdown vty

flushStdin :: IO ()
flushStdin = do
    hSetBuffering stdin NoBuffering
    whileM consume

consume :: IO Bool
consume = do
    ava <- hReady stdin
    when ava $ void getChar
    pure ava

with this version I can no longer reproduce the bug, whether I run the executable directly or via stack exec.

jtdaugherty commented 11 months ago

Thanks for your testing, @ShrykeWindgrace - I will try your code today.

jtdaugherty commented 11 months ago

@ShrykeWindgrace I confirmed that I am unable to get your test program to exhibit the crash behavior, whereas I can pretty easily reproduce it with my test case. So I'm going to proceed to integrate the fix into the library. Thanks!