Search code examples
visual-c++mfccode-analysismonthcalendar

Resolving code analysis warnings with the BOLDDAY macro (used with CMonthCalCtrl)


I have some issues with the CMonthCalCtrl control and modernizing my code. The first problem is related to the BOLDDAY macro.

This macro is used to adjust day states (making specific dates bold on the calendar) and the concept is described in detail here. As documented, you need to define a macro:

#define  BOLDDAY(ds, iDay) if(iDay > 0 && iDay < 32) \
            (ds) |= (0x00000001 << (iDay-1))

Here is my code that uses this macro so that you have some context:

void CMeetingScheduleAssistantDlg::InitDayStateArray(int iMonthCount, LPMONTHDAYSTATE pDayState, COleDateTime datStart)
{
    int                 iMonth = 0;
    COleDateTimeSpan    spnDay;
    CString             strKey;
    SPECIAL_EVENT_S     *psEvent = nullptr;

    if (pDayState == nullptr)
        return;

    memset(pDayState, 0, sizeof(MONTHDAYSTATE)*iMonthCount);

    if (m_pMapSPtrEvents == nullptr && m_Reminders.Count() == 0)
    {
        return;
    }

    spnDay.SetDateTimeSpan(1, 0, 0, 0);

    auto datDay = datStart;
    const auto iStartMonth = datStart.GetMonth();
    auto iThisMonth = iStartMonth;
    auto iLastMonth = iThisMonth;
    do
    {
        strKey = datDay.Format(_T("%Y-%m-%d"));

        if (m_pMapSPtrEvents != nullptr)
        {
            psEvent = nullptr;
            m_pMapSPtrEvents->Lookup(strKey, reinterpret_cast<void*&>(psEvent));
            if (psEvent != nullptr)
            {
                BOLDDAY(pDayState[iMonth], datDay.GetDay());
            }
        }

        if (m_Reminders.HasReminder(datDay))
        {
            BOLDDAY(pDayState[iMonth], datDay.GetDay());
        }

        datDay = datDay + spnDay;
        iThisMonth = datDay.GetMonth();
        if (iThisMonth != iLastMonth)
        {
            iLastMonth = iThisMonth;
            iMonth++;
        }
    } while (iMonth < iMonthCount);
}

Everywhere I use this BOLDDAY macro I get a code analysis warning (C26481):

warning C26481: Don't use pointer arithmetic. Use span instead (bounds.1).

enter image description here

It is not clear to me if the problem is with the BOLDDAY macro or my own code?


Update

I still get the warning when I turn the macro into a function:

enter image description here


Update 2

If it helps, I currently call the InitDayStateArray function in the following ways:

  • Method 1:
void CMeetingScheduleAssistantDlg::SetDayStates(CMonthCalCtrl &rCalendar)
{
    COleDateTime        datFrom, datUntil;

    const auto iMonthCount = rCalendar.GetMonthRange(datFrom, datUntil, GMR_DAYSTATE);
    auto pDayState = new MONTHDAYSTATE[iMonthCount];
    if (pDayState != nullptr)
    {
        InitDayStateArray(iMonthCount, pDayState, datFrom);
        VERIFY(rCalendar.SetDayState(iMonthCount, pDayState));
        delete[] pDayState;
    }
}
  • Method 2
void CMeetingScheduleAssistantDlg::OnGetDayStateEnd(NMHDR* pNMHDR, LRESULT* pResult)
{
    NMDAYSTATE* pDayState = reinterpret_cast<NMDAYSTATE*>(pNMHDR);
    MONTHDAYSTATE   mdState[3]{}; // 1 = prev 2 = curr 3 = next
    const COleDateTime  datStart(pDayState->stStart);

    if (pDayState != nullptr)
    {
        InitDayStateArray(pDayState->cDayState, &mdState[0], datStart);
        pDayState->prgDayState = &mdState[0];
    }

    if (pResult != nullptr)
        *pResult = 0;
}

Perhaps if the container for the LPMONTHDAYSTATE information is tweaked somehow it would contribute to resolve this span issue?


Solution

  • Sample code provided by Microsoft used to be published as code that compiles both with a C and C++ compiler. That limits availability of language features, frequently producing code that particularly C++ clients shouldn't be using verbatim.

    The case here being the BOLDDAY function-like macro, that's working around not having reference types in C. C++, on the other hand, does, and the macro can be replaced with a function instead:

    void bold_day(DWORD& day_state, int const day) noexcept {
        if (day > 0 && day < 32) {
            day_state |= (0x00000001 << (day - 1));
        }
    }
    

    Using this function in place of the BOLDDAY macro silences the C26481 diagnostic.

    While that works, I'm at a complete loss to understand where the compiler is seeing pointer arithmetic in the macro version. Regardless, replacing a function-like macro with an actual function (or function template) where possible is always desirable.

    Update

    Things are starting to make sense now. While replacing the function-like macro with a function, as suggested above, is desirable, it will not resolve the issue. My test happened to have used pDayState[0] which still raises C26481 for the macro, but not for the function. Using pDayState[1] instead, the diagnostic is raised in either case.

    Let's put the pieces of the puzzle together: Recall that the array subscript expression p[N] is exactly identical to the expression *(p + N) when p is a pointer type and N an integral type. That explains why the compiler is complaining about "pointer arithmetic" when it sees pDayState[iMonth].

    Solving that is fairly straight forward. As suggested by the diagnostic, use a std::span (requires C++20). The following changes to InitDayStateArray() make the C26481 diagnostic go away:

    void CMeetingScheduleAssistantDlg::InitDayStateArray(int iMonthCount,
            LPMONTHDAYSTATE pDayState,
            COleDateTime datStart)
    {
        std::span const day_month_state(pDayState, iMonthCount);
        // ...
    
        // memset(pDayState, 0, sizeof(MONTHDAYSTATE)*iMonthCount);
        std::fill(begin(day_month_state), end(day_month_state), 0);
    
        // ...
    
        do
        {
            // ...
                {
                    bold_day(day_month_state[iMonth], datDay.GetDay());
                }
            }
    
            if (m_Reminders.HasReminder(datDay))
            {
                bold_day(day_month_state[iMonth], datDay.GetDay());
            }
            // ...
        } while (iMonth < day_month_state.size());
    }
    

    A std::span "describes an object that can refer to a contiguous sequence of objects". It takes the decomposed pointer and size arguments that describe an array and reunites them into a single object, recovering the full fidelity of the array.

    That sounds great. But remember, this is C++, and there's a caveat: Just like its evil C++17 ancestor std::string_view, a std::span is an unhesitating factory for dangling pointers. You can freely pass them around, and hang on to them far beyond the referenced data being alive. And this is guaranteed for every specialization, starting with C++23.

    The other issue is, that addressing this one diagnostic now has several others pop out of nowhere, suggesting that std::span isn't good enough, and gsl::span should be used instead. Addressing those would probably warrant another Q&A altogether.