Skip to content

Add static assert macro #3113

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 7, 2016
Merged

Add static assert macro #3113

merged 3 commits into from
Nov 7, 2016

Conversation

geky
Copy link
Contributor

@geky geky commented Oct 21, 2016

Add static assert macro

Adds MBED_STATIC_ASSERT for compile-time assertions, results in compile-time error if condition is false.

Example:

MBED_STATIC_ASSERT(MBED_LIBRARY_VERSION > 120);

Assertion failure on GCC 5.3.1:

test.cpp:3:69: error: size of array is negative
 #define MBED_STATIC_ASSERT(expr) void _static_assert(int[(expr)?1:-1])
                                                                     ^
test.cpp:5:1: note: in expansion of macro 'MBED_STATIC_ASSERT'
 MBED_STATIC_ASSERT(MBED_LIBRARY_VERSION > 120);
 ^

Assertion failure on ARMC5:

"test.cpp", line 5: Error:  #94: the size of an array must be greater than zero

  MBED_STATIC_ASSERT(MBED_LIBRARY_VERSION > 120);
  ^
test.cpp: 0 warnings, 1 error

Assertion failure on IAR 7.4:

  MBED_STATIC_ASSERT(MBED_LIBRARY_VERSION > 120);
  ^
"C:\Users\chrhas02\mbed\test\test.cpp",5  Error[Pe094]: the size of an array
          must be greater than zero

Errors: 1
Warnings: none

Open to suggestions
cc @theotherjimmy, @pan-

Copy link
Contributor

@theotherjimmy theotherjimmy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯 👍

@jupe
Copy link
Contributor

jupe commented Oct 22, 2016

error messages sounds little strange for me. Could it be more descriptive ?

@geky
Copy link
Contributor Author

geky commented Oct 22, 2016

I'm open to suggestions if you know of a construct that provides the error more clearly, but unfortunately the error message is a bit inflexible without language support.

The macro can be improved by support for compiler-specific static-assert builtins, but I couldn't find any.

@pan-
Copy link
Member

pan- commented Oct 22, 2016

@geky It would be nice to add a string describing the failure (similar to static_assert in C++11).
The IAR implementation can rely on the static_assert keyword exposed by the compiler.

@jupe
Copy link
Contributor

jupe commented Oct 23, 2016

@kjbracey-arm @SeppoTakalo any thoughts about this ?

@geky
Copy link
Contributor Author

geky commented Oct 23, 2016

It would be nice to add a string describing the failure

Can do. I was wondering if anyone would request this since I haven't seen the message used for much. An alternative would be to stringize the expr argument as the message.

I also found this construct for gcc in c, but I can't find anything similar for c++03:

#define MBED_STATIC_ASSERT(expr, msg) __extension__ _Static_assert(expr, msg)

@kjbracey
Copy link
Contributor

I found static_assert very useful, so put a version in ns_types.h - this does the necessary rooting around to find a real version:

https://github.com/ARMmbed/nanostack-libservice/blob/master/mbed-client-libservice/ns_types.h

But it doesn't fall back to a "portable" one, because I failed to find a construct that worked in all compilers and all circumstances. Including it as a structure member is particularly problematic - and that's what I actually needed first. Would yours work there? Is a function declaration legal in a C structure? Not sure...

Maybe you could borrow the checks before falling back to your base one,.

Figured that one was good enough, because it worked at least on any GCC we were using, and most static asserts would be valid across all compilers, so at least they'd show up on the GCC builds.

Note that the version checking there can be fragile, thanks to compilers amusingly defining the macros from other compilers. ARMCC deciding to define GNUC and the relevant version number without providing the GNU static assert extension was very amusing.

@pan-
Copy link
Member

pan- commented Oct 24, 2016

@geky

  • The identifier used is always the same, this prevent usage of multiple static assertions in the same scope. You can cat you identifier with the predefined __LINE__ macro:
#define CONCAT_(lhs, rhs) lhs##rhs
#define CONCAT(lhs, rhs) CONCAT_(lhs, rhs)

// identifier
CONCAT(my_identifier_at_, __LINE__)
  • I'm not sure if it is a good thing to declare a new symbol which will not be implemented. It might create unwanted or strange conflicts. Maybe using an anonymous enum declaration is better:
#define STATIC_ASSERTION(cond, msg) \
  enum { CONCAT(static_assertion_at_, __LINE__) =  sizeof(char[cond ? 1 : -1]) }
  • It is would be interesting to have tests which ensure that the static assertion work as expected in every case. It will also help with compiler upgrade.

@kjbracey-arm It is possible to have a portable implementation of static assert which work in C struct; but it will only work outside of a c struct! The trick is to use a zero width bitfield:

The expression that specifies the width of a bit-field shall be an integer constant
expression with a nonnegative value that does not exceed the width of an object of the
type that would be specified were the colon and expression omitted. If the value is zero,
the declaration shall have no declarator.

The bitfield declared does not occupy any size:

A bit-field declaration with no declarator, but only a colon and a width, indicates an
unnamed bit-field.108) As a special case, a bit-field structure member with a width of 0
indicates that no further bit-field is to be packed into the unit in which the previous bit-
field, if any, was placed.

As a result it is possible to write:

#define C_STRUCT_STATIC_ASSERTION(cond, msg) int : cond ? 0 : -1

struct foo_t { 
  int foo;
  /* uncomment this to get a compile time error
  C_STRUCT_STATIC_ASSERTION(0 == 1, "error"); 
  */
  C_STRUCT_STATIC_ASSERTION(1 == 1, "success");
};

@kjbracey
Copy link
Contributor

@pan- sneaky, yes, you could have two different macros for block/file versus struct scope. I was trying to come up with a single definition, but that's not a hard requirement.

@geky
Copy link
Contributor Author

geky commented Oct 24, 2016

It is would be interesting to have tests which ensure that the static assertion work as expected in every case. It will also help with compiler upgrade.

+1, I'll look into adding these

The identifier used is always the same, this prevent usage of multiple static assertions in the same scope.

Is this problematic in the case of a function declaration? Redeclaring the function with the same type should have no issues.

EDIT: This appears to break-down for member functions:
http://stackoverflow.com/a/7518785

I'm not sure if it is a good thing to declare a new symbol which will not be implemented.

It should probably be given an mbed prefix, but with a prefix I'm not sure we should be too concerned with conflicts.

@pan-
Copy link
Member

pan- commented Oct 24, 2016

Is this problematic in the case of a function declaration? Redeclaring the function with the same type should have no issues.

I'm quoting the spec:

A member shall not be declared twice in the member-specification, except that a nested class or member class template can be declared and then later defined, and except that an enumeration can be introduced with an opaque-enum-declaration and later redeclared with an enum-specifier.

So yes, it is problematic. IIRC, The compilation of the static assert from this PR in a struct fail with ARMCC and IAR.

It should probably be given an mbed prefix, but with a prefix I'm not sure we should be too concerned with conflicts.

Is it not problematic, but it let the possibility to the user to use something unspecified (it will fail at link time,,,); I'm not sure it is the most correct things to do.
In my opinion, if we can avoid holes in our system, it is better for the user, maintainer and supporter.

@geky
Copy link
Contributor Author

geky commented Oct 24, 2016

@pan- makes good points, so I've updated the pr with the enum construct presented by @pan-. Although I did move the unique-symbol logic into a separate macro in an effort to make the error message cleaner.

Also added tests

I also adopted @kjbracey-arm's compiler checks (and a bit of his documentation ;) ) which gets us the gcc definitions when available, which is a big improvement on gcc builds.

In regards to default behaviour, static assertions need to garuntee program conditions across compilers. It seems risky to just disable the assertions for the majority of C compilers supported by mbed. I went ahead and added the @pan-'s static-struct-assertion to support static assertion's in structs, though I'm not too opinionated on how struct assertions are handled.

Let me know if there are other concerns

Copy link
Member

@pan- pan- left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good PR, but it requires few changes to be perfect. There is two points which raise questions:

  • Should we encourage users (and ourselves) to use MBED_STRUCT_STATIC_ASSERT in C and C++ to enforce consistency ?
  • Is it possible write test which pass when their compilation fail ? This is the main point of this feature, makes the compilation fail when something incorrect is detected at compile time. Right now, only the case where the assertion should be true are tested. It would be helpful to test the cases where the assertion is false and ensure that the compilation fail as expected.

@@ -49,6 +49,65 @@ do { \
} while (0)
#endif


#define _MBED_CONCAT_(a, b) a##b
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just wondering if these kind of macro can be packed together. Preprocessor token concatenation is a common operation and in many places across the code base it is not done correctly (arguments not expanded...).

This might be done in another PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just wondering if these kind of macro can be packed together.

Good point

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What file should these belong to do you think? toolchain.h? new file for preprocessor declarations? mbed_preprocessor.h?


#define _MBED_CONCAT_(a, b) a##b
#define _MBED_CONCAT(a, b) _MBED_CONCAT_(a, b)
#define _MBED_ASSERTION _MBED_CONCAT(_MBED_ASSERTION_AT_, __LINE__)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_MBED_CONCAT and _MBED_ASSERTION are reserved identifiers according to the C standard:

All identifiers that begin with an underscore and either an uppercase letter or another underscore are always reserved for any use.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops, my bad, will fix

* }
* @endcode
*/
#ifdef __cplusplus
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When IAR is used, it is possible to delegate handling of static assertion to the extension static_assert.

/** MBED_STATIC_ASSERT
* Declare compile-time assertions, results in compile-time error if condition is false
*
* The assertion acts as a declaration that can be placed at file scope, in a
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to enhance the documentation by explaining that this macro is valid in C and C++ at file scope and in a code block. Then split the structure case in two:

  • C++: this macro is valid.
  • C: should use MBED_STRUCT_STATIC_ASSERT which is also valid for C++ code.

* };
* @endcode
*/
#if defined(__cplusplus)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incorrect, a C++ compiler can treat a structure as a C one if the structure is imported as extern "C".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing with gcc 5.3.1, armc 5, and iar 7.4 show this is not the case. That being said I'll go ahead and drop the c++ special case to be safe.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your correct, "extern C" statement just affect linkage, it doesn't change anything in the compilation process. Indeed, when an element can't be translated to C linkage then it fallback to C++ linkage.

At the end, the change I've requested is not needed.

@sg-
Copy link
Contributor

sg- commented Nov 1, 2016

@geky @pan- next steps to move this along??

@geky
Copy link
Contributor Author

geky commented Nov 1, 2016

Still needs a bit of work for feedback, sounds like another file should be added for preprocessor definitions. mbed_preprocessor.h?

@pan-
Copy link
Member

pan- commented Nov 1, 2016

@sg- There is two things which can be improved with the PR:

  • Separate macro concatenation from the static assert module.
  • Use builtin static_assert in IAR.

Both can be done at a latter stage but is should be referenced at least if it didn't happen in this PR.

@sg-
Copy link
Contributor

sg- commented Nov 1, 2016

Lets get them resolved in this PR.

Todos

  • Separate macro concatenation from the static assert module
  • Use builtin static_assert in IAR.

Not fully onboard with using a builtin where this seems a language feature, not a tool, but guess it doesn't involve much. #bikesheding

geky added 2 commits November 1, 2016 21:39
Added MBED_STATIC_ASSERT for compile-time assertions, results in
compile-time error if condition is false

The assertion acts as a declaration that can be placed at file scope, in
a code block (except after a label), or as a member of
a C++ class/struct/union.

Unfortunately, there does not exist a backup construct for use in
C class/struct/union contexts. An alternative macro,
MBED_STRUCT_STATIC_ASSERT provides this ability to avoid disabling
static assertions for the majority of mbed-supported C compilers.
@geky
Copy link
Contributor Author

geky commented Nov 2, 2016

@pan-, pr should be updated, let me know if I missed anything

@0xc0170
Copy link
Contributor

0xc0170 commented Nov 3, 2016

Open for review !

@pan-
Copy link
Member

pan- commented Nov 3, 2016

@geky You forgot to add mbed_preprocesor.h in the last commit.

@geky
Copy link
Contributor Author

geky commented Nov 3, 2016

Woops! Glad you noticed 😆

@sg-
Copy link
Contributor

sg- commented Nov 3, 2016

/morph nightly-build

@bridadan
Copy link
Contributor

bridadan commented Nov 3, 2016

/morph test-nightly

@mbed-bot
Copy link

mbed-bot commented Nov 4, 2016

Result: ABORTED

Your command has finished executing! Here's what you wrote!

/morph test-nightly

@bridadan
Copy link
Contributor

bridadan commented Nov 4, 2016

Failure was my fault, a few machines were stuck in maintenance mode and halted the whole process. Restarting now.

/morph test-nightly

@mbed-bot
Copy link

mbed-bot commented Nov 5, 2016

Result: SUCCESS

Your command has finished executing! Here's what you wrote!

/morph test-nightly

Output

mbed Build Number: 999

All builds and test passed!

@sg- sg- merged commit 074555b into ARMmbed:master Nov 7, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants