Skip to content

Ensure signed integer type for stse_ReturnCode_t enum#85

Open
parmi93 wants to merge 10 commits into
STMicroelectronics:mainfrom
parmi93:core/stse-return-codes-signed
Open

Ensure signed integer type for stse_ReturnCode_t enum#85
parmi93 wants to merge 10 commits into
STMicroelectronics:mainfrom
parmi93:core/stse-return-codes-signed

Conversation

@parmi93
Copy link
Copy Markdown
Contributor

@parmi93 parmi93 commented May 1, 2026

This PR adds a sentinel value __STSE_SIGNED = -1 to the stse_ReturnCode_t enum to guarantee that the enum is always treated as a signed integer type across all compilers and platforms.

Why This Change?

In C, enumerations are implicitly convertible to integers, but the C standard does not mandate whether an enum type should be signed or unsigned. The actual underlying type depends on:

  1. Compiler implementation: Different compilers (GCC, Clang, MSVC, etc.) may make different choices
  2. Enum values: The compiler chooses a type that can represent all enumeration constants
  3. Platform: 32-bit vs 64-bit systems may yield different results

The Problem

Without this fix, users cannot write portable code to print stse_ReturnCode_t values. Depending on the platform and compiler, %u may be required on some systems while %d is needed on others, resulting in non-portable and potentially buggy code.

Without this fix, code like:

stse_ReturnCode_t rc = STSE_OK;
printf("Return code: %d\n", rc);

Could behave inconsistently:

  • On some compilers/platforms, the enum is unsigned, so %d gives incorrect output (user should use %u)
  • On others it's signed, so %d gives correct output

The Solution

By adding __STSE_SIGNED = -1 as the last enum value, we force the compiler to choose a signed integer type (since the enum now contains a negative value that must be representable).

This guarantees:

  • Consistent behavior across all compilers
  • Safe use of %d format specifier for printing enum values
  • No undefined behavior when comparing with negative values
  • Predictable enum promotion rules

Technical Details

Per C99/C11 standards, the underlying type of an enum is implementation-defined but must be capable of representing all enumerator values. By including a negative value, we ensure the enum's underlying type is signed integer, making printf() with %d portable and safe.

TofMassilia13320 and others added 10 commits April 3, 2026 10:14
Mark the read-only buffers as `const` in `xxx_ecc_verify_signature()` to
reflect that the input buffer is not modified. This improves
const-correctness and clarifies the API contract.
Add `__STSE_SIGNED = -1` as the last enum value to guarantee that
`stse_ReturnCode_t` is treated as a signed integer type across all
compilers. This allows printing enum values using the `%d` format
specifier reliably on all platforms.
@parmi93 parmi93 changed the title Ensure signed integer type for stse_ReturnCode_t enum Ensure signed integer type for stse_ReturnCode_t enum May 1, 2026
@Grom- Grom- self-requested a review May 29, 2026 17:02
@Grom- Grom- self-assigned this May 29, 2026
@Grom-
Copy link
Copy Markdown
Contributor

Grom- commented May 29, 2026

Hello @parmi93,

Thanks for your contribution, and sorry for the delayed review.

I completely agree with your portability concerns regarding how enums are handled in printf. However, instead of forcing the compiler to treat stse_ReturnCode_t as a signed integer by default via enum definition hacks, I think the best long-term choice is to explicitly fix the type to a predictable, standard 32-bit size.

Since most of our API functions rely on this enum as a direct return type, we are exposing the library's ABI directly to how different compilers choose to interpret and optimize that enum. If you look at industry-standard cryptographic APIs (like PKCS#11), they completely avoid returning raw enums for this exact reason. Instead, they map return codes to fixed-width integer types in the public header.

For example:

typedef int32_t stse_ReturnCode_t; // The type is explicitly a 32-bit signed integer

#define STSE_OK      ((stse_ReturnCode_t)0)
#define STSE_ERROR   ((stse_ReturnCode_t)1)

// Function signature remains perfectly portable
stse_ReturnCode_t stse_init(stse_handler_t * p_stse);

Ensuring stse_ReturnCode_t is an explicit int32_t will guarantee identical ABI behavior whether this library is running on an 8-bit MCU or a 64-bit Android/Linux system, while naturally solving the printf issue.

I can open a dedicated issue for this architectural change and take care of the refactoring.

What do you think?

@parmi93
Copy link
Copy Markdown
Contributor Author

parmi93 commented May 29, 2026

Hi, thank you for the detailed explanation!

I just wanted to point out a couple of things for consideration:

  1. Debugger support: One advantage of keeping a proper enum type is that debuggers (GDB, LLDB, etc.) can automatically resolve enum values to their symbolic names (e.g. STSE_OK instead of 0). With a typedef int32_t + #define approach, this information is lost at debug time, which can make debugging sessions slightly less comfortable.

  2. ABI concern: Since STSELib is distributed as source code rather than precompiled binaries, the ABI instability issue technically shouldn't arise in practice, everything gets compiled together by the same compiler with the same flags on the target platform.

That said, either approach works fine for me, so feel free to go with whichever solution you prefer. It will make debugging slightly more difficult, but I'll manage somehow.

@nils-cercariolo-st
Copy link
Copy Markdown
Contributor

To try getting both of your points, we could add a last enum of this value : 0x7FFFFFFF, to force using 32 bits, and if my understanding is correct, compiler must respect this order when choosing for type :

  • signed int
  • unsigned int
  • signed long int
  • unsigned long int
  • ...

So with this, since our value fits in a signed int of 32 bits, every compiler should make this variable into the desired type right ?

I could find examples of this practice here for reference : https://codebrowser.dev/imgui/imgui/dxsdk/Include/d3d9types.h.html
https://learn.microsoft.com/en-us/windows/win32/api/wincodec/ne-wincodec-wicjpegindexingoptions

Let me know your thoughts.

@parmi93
Copy link
Copy Markdown
Contributor Author

parmi93 commented Jun 2, 2026

After revisiting the C standard, I realised that my proposal of adding __STSE_SIGNED = -1 in order to print an enum type without casting it to int is not actually valid. Unfortunately, an explicit cast to int is still required when printing an enumeration value in a portable way.

C99 §6.7.2.2 ¶1 defines the syntax of an enumeration as:

enum-specifier:
      enum identifieropt { enumerator-list }
      enum identifieropt { enumerator-list , }
      enum identifier

enumerator-list:
      enumerator
      enumerator-list , enumerator

enumerator:
      enumeration-constant
      enumeration-constant = constant-expression

C99 §6.7.2.2 ¶2 specifies that the result of a constant-expression must be representable as an int:

The expression that defines the value of an enumeration constant shall be an integer constant expression that has a value representable as an int.

C99 §6.7.2.2 ¶3 further states that each enumeration-constant has type int:

The identifiers in an enumerator list are declared as constants that have type int and may appear wherever such are permitted.

However, C99 §6.7.2.2 ¶4 states that the type chosen for the enumeration itself is implementation-defined:

Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined, but shall be capable of representing the values of all the members of the enumeration.

Therefore, adding __STSE_SIGNED = -1 merely restricts the compiler to selecting a signed integer type for the enumeration (signed char, short, int, long, long long, etc.). It does not guarantee that the enum type itself will be int.

In other words, to print an enumeration value portably, an explicit cast remains necessary:

stse_ReturnCode_t rc = STSE_OK;
printf("Return code: %d\n", (int)rc);

Regarding @nils-cercariolo-st's proposal to add the enumerator value 0x7FFFFFFF, I believe this would violate C99 §6.7.2.2 ¶2, which requires every enumerator constant-expression value to be representable as an int. Since C only guarantees that an int can represent values in a range of at least -32767 to +32767 (C99 §5.2.4.2.1) (at least 16-bits), 0x7FFFFFFF (32-bits) cannot be assumed to be representable as an int on all conforming implementations.

As a result, my original proposal of adding __STSE_SIGNED = -1 would not provide any meaningful benefit.

The only practical advantage of defining stse_ReturnCode_t as an enum rather than as int32_t is improved debugger support, since debuggers can display symbolic enumerator names.

Therefore, if ABI stability is a concern, I think it is reasonable to define stse_ReturnCode_t as int32_t instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants