We are working on writing external device drivers for certain Accelerometers/Gyros and Magnetometers using the abstract peripheral interface. While going through the design pattern(object oriented C having virtual function implementations), I found a potential flaw in the design pattern:
- ChibiOS version or subversion revision number : 17.6.2
- Compiler : gcc
- Platform and board : STM32, STM32L476RG-Nucleo
- Nature of the problem : Potential pointer dereference in polymorphic type inheritance models written in C for abstract peripheral devices
- Failure mode: Accessing polymorphic base class member data variables from a derived class object, using a base class pointer.
Eg: in the driver lis302dl.h, driver for accelerometer, the derived class of LIS302DLDriver from BaseAccelerometer, which in turn is derived from BaseSensor, has 3 virtual tables and 3 virtual pointers for each of them. So, if user creates an instance of LIS302DLDriver, typecasts it's address to a pointer of type BaseAccelerometer, then tries to access data members of BaseAccelerometer using that pointer, it would get garbage data since, the offset of the _base_accelerometer_data is actually the second virtual table of LIS302DLDriver declaration. (Luckily, for accelerometer abstract peripheral interface, there is no data for the base class, so this bug is missed.)
- The following example code snippet wouldn't work(assume BaseSensor has a data variable called 'msg_t stateSensor') (just for illustration):
In hal_sensors.h:And in main.c:Code: Select all
/**
* @brief @p BaseSensor specific data.
* @note It is empty because @p BaseSensor is only an interface
* without implementation.
*/
#define _base_sensor_data \
msg_t stateSensor;In above snippet, stateViewedFromBaseAccelerometerPointer doesn't get updated with value of '1'.Code: Select all
LIS302DLDriver derivedAccelerometerObject = {0};
LIS302DLDriver * derivedAccelerometerPointer = &derivedAccelerometerObject;
BaseSensor * baseAccelerometerPointer = (BaseSensor *)&derivedAccelerometerObject;
derivedAccelerometerPointer->stateSensor = 1;
msg_t stateViewedFromBaseAccelerometerPointer = baseAccelerometerPointer->stateSensor; - A potential solution would be is to define class heirarchy of hal_sensors and it's derived forms as:And then the macro definitions for function call helpers as:
Code: Select all
/**
* @brief Base sensor class.
* @details This class represents a generic blocking unbuffered sequential
* data stream.
*/
typedef struct {
/** @brief Virtual Methods Table.*/
const struct BaseSensorVMT *vptr;
_base_sensor_data
} BaseSensor;
/**
* @brief Base accelerometer class.
* @details This class represents a generic a generic accelerometer.
*/
typedef struct {
/** @brief Virtual Methods Table.*/
const struct BaseAccelerometerVMT *vptr;
_base_sensor_data
} BaseAccelerometer;
/**
* @extends BaseAccelerometer
*
* @brief LIS302DL 3-axis accelerometer class.
* @details This class extends @p BaseAccelerometer by adding physical
* driver implementation.
*/
struct LIS302DLDriver {
/** @brief LIS302DL Virtual Methods Table. */
const struct LIS302DLVMT *vptr;
_lis302dl_data
};Code: Select all
/**
* @name Macro Functions (BaseSensor)
* @{
*/
/**
* @brief Sensors get channels number.
*
* @param[in] ip pointer to a @p BaseSensor or derived class.
* @return The number of channels of the BaseSensor
*
* @api
*/
#define sensorGetChannelNumber(ip) (ip)->vptr->get_channels_number(ip)
/**
* @brief Sensors read raw data.
*
* @param[in] ip pointer to a @p BaseSensor or derived class.
* @param[in] dp pointer to a data array.
*
* @return The operation status.
* @retval MSG_OK if the function succeeded.
* @retval MSG_RESET if one or more errors occurred.
*
* @api
*/
#define sensorReadRaw(ip, dp) (ip)->vptr->read_raw(ip, dp)
...similarly for all derived classes of BaseSensor
Yusuf Husainy