Phoenix
Phoenix copied to clipboard
GenericCalendarCtrl crashes with "double free or corruption (fasttop)"
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)
# 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()
# 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()
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?
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().
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.
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()"?
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.
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
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
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