Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a MISRA-compliant way to use enum flags in C99?

Tags:

c

standards

misra

I have a project I'm developing in C99 and I'm trying to make it compliant with the MISRA 2012 standard.

In one file I define an enum where each value should be treated as a flag:

/**
 * Enumerates the configurable options for performing calibration.
 */
typedef enum
{
  CALIBRATION_DEFAULT_OPTIONS=0,  /**< Calibrate with default options */
  CALIBRATION_RESET_POSITION=1,   /**< Ensure window is fully open and motor re-homed */
  CALIBRATION_FORCE_RECALIBRATE=2 /**< Force recalibration even if calibration data exists */
} CALIBRATION_OPTIONS_T;

I'd like to be able to declare something like:

CALIBRATION_OPTIONS_T options = CALIBRATION_RESET_POSITION | CALIBRATION_FORCE_RECALIBRATE;

I also define one function that accepts a CALIBRATION_OPTIONS_T parameter and performs different logic depending on which flags are set:

// If forced to recalibrate, do so regardless of whether metrics exist in
// EEPROM or not.
if ((options & CALIBRATION_FORCE_RECALIBRATE) != 0U)
{
  MOTION_ResetCalibrationData();
  calibration = performCalibrationRoutine();
}

// Otherwise try fetching existing metrics from EEPROM. If they exist, return
// these metrics.
else if (tryFetchStoredMetrics(&calibration))
{
  if ((options & CALIBRATION_RESET_POSITION) != 0U)
  {
    calibration.lastPosition = 0;
    resetMotorPosition();
    storeMetrics(calibration);
  }
}

However, when I lint my project with PC-lint Plus I get the following output explaining that this code violates MISRA 2012 Rule 10.1:

  if ((options & CALIBRATION_FORCE_RECALIBRATE) != 0U)
       ~~~~~~~ ^
*** LINT: src\c\motionCalibrator.c(645) note 9027: an enum value is not an appropriate left operand to & [MISRA 2012 Rule 10.1, required]

  if ((options & CALIBRATION_FORCE_RECALIBRATE) != 0U)
               ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*** LINT: src\c\motionCalibrator.c(645) note 9027: an enum value is not an appropriate right operand to & [MISRA 2012 Rule 10.1, required]

  if ((options & CALIBRATION_FORCE_RECALIBRATE) != 0U)
       ^
*** LINT: src\c\motionCalibrator.c(645) warning 641: implicit conversion of enum 'CALIBRATION_OPTIONS_T' to integral type 'unsigned int'

    if ((options & CALIBRATION_RESET_POSITION) != 0U)
         ~~~~~~~ ^
*** LINT: src\c\motionCalibrator.c(655) note 9027: an enum value is not an appropriate left operand to & [MISRA 2012 Rule 10.1, required]

    if ((options & CALIBRATION_RESET_POSITION) != 0U)
                 ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~
*** LINT: src\c\motionCalibrator.c(655) note 9027: an enum value is not an appropriate right operand to & [MISRA 2012 Rule 10.1, required]

    if ((options & CALIBRATION_RESET_POSITION) != 0U)
         ^
*** LINT: src\c\motionCalibrator.c(655) warning 641: implicit conversion of enum 'CALIBRATION_OPTIONS_T' to integral type 'unsigned int'

In particular, the MISRA 2012 standard advises against using & with enums for these two reasons:

  1. An operand of essentially enum type should not be used in an arithmetic operation because an enum object uses an implementation-defined integer type. An operation involving an enum object may therefore yield a result with an unexpected type. Note that an enumeration constant from an anonymous enum has essentially signed type.

  2. Shift and bitwise operations should only be performed on operands of essentially unsigned type. The numeric value resulting from their use on essentially signed types is implementation-defined.

I'd like to know if there's a MISRA-compliant way I can use flag-like enums and test that specific flags are set.

like image 952
Tagc Avatar asked Mar 05 '23 14:03

Tagc


1 Answers

This boils down to the essential type model and rule 10.1. You are only allowed to do bitwise operations on types that are essentially unsigned. Enums are treated as their own unique type by MISRA-C.

Doing things like CALIBRATION_OPTIONS_T options = CALIBRATION_RESET_POSITION | CALIBRATION_FORCE_RECALIBRATE; is otherwise fine and pretty canonical C, but you have to resort to using unsigned constants. To take type safety a bit to the extreme, you could do this:

typedef uint32_t CALIBRATION_OPTIONS_T;
#define CALIBRATION_DEFAULT_OPTIONS   ((CALIBRATION_OPTIONS_T)0x00u) /**< Calibrate with default options */
#define CALIBRATION_RESET_POSITION    ((CALIBRATION_OPTIONS_T)0x01u) /**< Ensure window is fully open and motor re-homed */
#define CALIBRATION_FORCE_RECALIBRATE ((CALIBRATION_OPTIONS_T)0x02u) /**< Force recalibration even if calibration data exists */

where the hex notation is self-documentating code showing that these are bit masks, the u suffix is required by MISRA in some circumstances and the uint32_t is there to block potential implicit type promotions.

Please note that using enums don't necessarily give increased type safety, but rather the opposite. They are in many cases treated like plain int, in other cases as implementation-defined size integers. Their type safety is pretty much broken by C language design. though you can make them safe with some tricks, see my posts at How to create type safe enums?.

like image 159
Lundin Avatar answered Mar 10 '23 11:03

Lundin