wxWidgets / Phoenix

wxPython's Project Phoenix. A new implementation of wxPython, better, stronger, faster than he was before.
http://wxpython.org/
2.29k stars 515 forks source link

GenericCalendarCtrl crashes with "double free or corruption (fasttop)" #2073

Open reticulatus opened 2 years ago

reticulatus commented 2 years ago

Operating system: Linux Mint 20.3 wxPython version & source: wxPython 4.1.1 gtk3 (phoenix) wxWidgets 3.1.5 (built from https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-20.04 using pip). Python version & source: Python 3.8.10 from Linux Mint distribution.

Description of the problem: I have been experimenting with highlighting certain days in a GenericCalendarCtrl.

My code creates a CalendarDateAttr with a bold font and applies it to the required days in calendar control.

If I create the CalendarDateAttr as an instance attribute, the program crashes with "double free or corruption (fasttop)" when you change the month or year in the calendar control or just exit the program (see example generic_calendar_exp_2.py below).

I have modified that program so that a new CalendarDateAttr is created just before being applied to the required days. This version of the program (see generic_calendar_exp_3.py) doesn't crash.

Is the CalendarDateAttr being deleted prematurely?

Code Example (click to expand) ```python # generic_calendar_exp_2.py # This program crashes when you change month or year, or just exit. import wx import wx.adv import calendar special_dates = { (2022, 1) : (2,5,7,11,30), (2022, 2) : (6,12,13,20,27), (2022, 3) : (1,27) } class MyFrame(wx.Frame): def __init__(self, *args, **kwds): kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE wx.Frame.__init__(self, *args, **kwds) self.SetSize((400, 300)) self.SetTitle("Calendar Test") self.panel_1 = wx.Panel(self, wx.ID_ANY) sizer_1 = wx.BoxSizer(wx.VERTICAL) cal_style = wx.adv.CAL_MONDAY_FIRST | wx.adv.CAL_SHOW_SURROUNDING_WEEKS self.calendar_ctrl = wx.adv.GenericCalendarCtrl(self.panel_1, wx.ID_ANY, style=cal_style) self.calendar_ctrl.SetMinSize((216, 166)) sizer_1.Add(self.calendar_ctrl, 0, wx.ALL, 8) self.panel_1.SetSizer(sizer_1) self.Layout() self.Bind(wx.adv.EVT_CALENDAR_SEL_CHANGED, self.OnCalChanged, self.calendar_ctrl) attr = self.calendar_ctrl.GetClassDefaultAttributes() bold_font = attr.font.Bold() self.highlight_attr = wx.adv.CalendarDateAttr(font=bold_font) self.highlightDates() def highlightDates(self): date = self.calendar_ctrl.GetDate() year = date.year month = date.month + 1 last_day = calendar.monthrange(year, month)[1] bold_days = special_dates.get((year, month), ()) print("Bold days : ", bold_days) for d in range(1, last_day+1): attr = self.calendar_ctrl.GetAttr(d) if d in bold_days: if attr != self.highlight_attr: self.calendar_ctrl.SetAttr(d, self.highlight_attr) else: if attr is not None: self.calendar_ctrl.ResetAttr(d) self.calendar_ctrl.Refresh() def OnCalChanged(self, event): date = event.GetDate() print("Date changed :", date.Format("%d/%m/%Y")) self.highlightDates() class MyApp(wx.App): def OnInit(self): self.frame = MyFrame(None, wx.ID_ANY, "") self.SetTopWindow(self.frame) self.frame.Show() return True if __name__ == "__main__": app = MyApp(0) app.MainLoop() ``` ```python # generic_calendar_exp_3.py # This program doesn't crash when you change month or year, or exit import wx import wx.adv import calendar special_dates = { (2022, 1) : (2,5,7,11,30), (2022, 2) : (6,12,13,20,27), (2022, 3) : (1,27) } class MyFrame(wx.Frame): def __init__(self, *args, **kwds): kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE wx.Frame.__init__(self, *args, **kwds) self.SetSize((400, 300)) self.SetTitle("Calendar Test") self.panel_1 = wx.Panel(self, wx.ID_ANY) sizer_1 = wx.BoxSizer(wx.VERTICAL) cal_style = wx.adv.CAL_MONDAY_FIRST | wx.adv.CAL_SHOW_SURROUNDING_WEEKS self.calendar_ctrl = wx.adv.GenericCalendarCtrl(self.panel_1, wx.ID_ANY, style=cal_style) self.calendar_ctrl.SetMinSize((216, 166)) sizer_1.Add(self.calendar_ctrl, 0, wx.ALL, 8) self.panel_1.SetSizer(sizer_1) self.Layout() self.Bind(wx.adv.EVT_CALENDAR_SEL_CHANGED, self.OnCalChanged, self.calendar_ctrl) attr = self.calendar_ctrl.GetClassDefaultAttributes() self.bold_font = attr.font.Bold() self.highlightDates() def highlightDates(self): date = self.calendar_ctrl.GetDate() year = date.year month = date.month + 1 last_day = calendar.monthrange(year, month)[1] bold_days = special_dates.get((year, month), ()) print("Bold days : ", bold_days) for d in range(1, last_day+1): attr = self.calendar_ctrl.GetAttr(d) if d in bold_days: highlight_attr = wx.adv.CalendarDateAttr(font=self.bold_font) if attr != highlight_attr: self.calendar_ctrl.SetAttr(d, highlight_attr) else: if attr is not None: self.calendar_ctrl.ResetAttr(d) self.calendar_ctrl.Refresh() def OnCalChanged(self, event): date = event.GetDate() print("Date changed :", date.Format("%d/%m/%Y")) self.highlightDates() class MyApp(wx.App): def OnInit(self): self.frame = MyFrame(None, wx.ID_ANY, "") self.SetTopWindow(self.frame) self.frame.Show() return True if __name__ == "__main__": app = MyApp(0) app.MainLoop() ```
swt2c commented 2 years ago

The issue is that SetAttr() on the C++ side takes control of the wxCalendarDateAttr that you pass in. If you pass in the same instance of wxCalendarDateAttr, then there is going to be an issue because the C++ side is going to try to delete it twice. As a workaround, do as you are doing and always pass a unique wxCalendarDateAttr into SetAttr(). We might be able to make this better on the Python side - internally create a copy of the wxCalendarDateAttr when you call SetAttr?

swt2c commented 2 years ago

It looks like it would be somewhat challenging to do what I proposed earlier - there's no copy constructor for wxCalendarDateAttr, nor is there an equality operator, so we'd have to implement those and maintain them in wxPython. So I'd suggest just always using a unique wxCalendarDateAttr when calling SetAttr().

reticulatus commented 2 years ago

Thanks for your replies, Scott, they certainly clear up my confusion. The reason for my initial approach was that, in my real application, there is quite a lot of processing happening when the date in the calendar is changed, so I was trying not to add too much additional load for the highlighting of the special days. Hopefully, it won't be too much of an issue.

fullwehrer commented 2 years ago

It looks like it would be somewhat challenging to do what I proposed earlier - there's no copy constructor for wxCalendarDateAttr, nor is there an equality operator, so we'd have to implement those and maintain them in wxPython. So I'd suggest just always using a unique wxCalendarDateAttr when calling SetAttr().

Might a noob ask, how one would go about "using a unique wxCalendarDateAttr when calling SetAttr()"?

reticulatus commented 2 years ago

It looks like it would be somewhat challenging to do what I proposed earlier - there's no copy constructor for wxCalendarDateAttr, nor is there an equality operator, so we'd have to implement those and maintain them in wxPython. So I'd suggest just always using a unique wxCalendarDateAttr when calling SetAttr().

Might a noob ask, how one would go about "using a unique wxCalendarDateAttr when calling SetAttr()"?

Click on the "Code Example (click to expand)" button in my original post above. This shows two versions of my demo program.

In the first version I created a single wxCalendarDateAttr object and tried to pass it to multiple calls of SetAttr(). This was causing the crash.

In the second version, I create a new wxCalendarDateAttr object before every call of SetAttr(). This stopped the crash from happening.

RobinD42 commented 2 years ago

This issue has been mentioned on Discuss wxPython. There might be relevant details there:

https://discuss.wxpython.org/t/wx-adv-genericcalendarctrl-crashes-the-frame-when-using-setattr/36096/2

RobinD42 commented 1 year ago

This issue has been mentioned on Discuss wxPython. There might be relevant details there:

https://discuss.wxpython.org/t/a-wx-datepickerctrl-with-a-customisable-format/36295/13

RobinD42 commented 1 year ago

This issue has been mentioned on Discuss wxPython. There might be relevant details there:

https://discuss.wxpython.org/t/a-wx-datepickerctrl-with-a-customisable-format/36295/24