[clang] [Clang] Add wraps attribute (for granular integer overflow handling) (PR #86618)

Justin Stitt via cfe-commits cfe-commits at lists.llvm.org
Wed Apr 24 11:37:18 PDT 2024


JustinStitt wrote:

Hi @efriedma-quic, thanks for dumping all these cases! I will respond to each one individually.

I understand you think adding a type would be better so I am exploring that option (I am thinking something similar to [extended vectors](https://clang.llvm.org/docs/LanguageExtensions.html#vectors-and-extended-vectors)). But for now, I am trying to make this attribute as useful and compatible as possible.

> If you have a select with both wrapping and non-wrapping operands, is the result wrapping? 

I assume you're referring to a ternary statement? Currently, no. Although, I should add support for this similar to how BinaryOperators are considered wrapping if any of their operands are.
```
`-ConditionalOperator 0x559ddad37998 <col:4, col:15> 'int'
  |-ImplicitCastExpr 0x559ddad37950 <col:4> 'int' <IntegralCast>
  | `-ImplicitCastExpr 0x559ddad37938 <col:4> '_Bool' <LValueToRValue>
  |   `-DeclRefExpr 0x559ddad378d8 <col:4> 'volatile _Bool' lvalue Var 0x559ddad7a350 'cond' 'volatile _Bool'
  |-ImplicitCastExpr 0x559ddad37968 <col:11> 'wrap_int':'int' <LValueToRValue>
  | `-DeclRefExpr 0x559ddad378f8 <col:11> 'const wrap_int':'const int' lvalue Var 0x559ddad37780 'a' 'const wrap_int':'const int'
  `-ImplicitCastExpr 0x559ddad37980 <col:15> 'int' <LValueToRValue>
    `-DeclRefExpr 0x559ddad37918 <col:15> 'const int' lvalue Var 0x559ddad37838 'b' 'const int'
```
---

> If you declare a variable as both wrapping and non-wrapping, is it wrapping? 

I am not sure how to do this. I am sure that with the magic of C anything is possible but I can't conceive a way to have a variable both have the attribute and not have the attribute (to be clear, there doesn't exist a `__attribute__((no-wraps))` currently)


> If you declare a function parameter both wrapping and non-wrapping, is it wrapping? 

Same here, not sure what you mean.


> If you assign to a wrapping variable, is the result wrapping? 

It depends on the type of the assignee, which I discuss later with a `const` analogy.

```c
wrap_int a;

int b = a; // b is not wrapping

wrap_int c = a; // c is wrapping
```

---

> If you mark a `short` wrapping, is the type after promotion wrapping? 

This concerns the `-fsanitize=implicit-signed-integer-truncation` sanitizer which the wraps attribute disables. So the type boundaries pre-promotion are not enforced by this sanitizer for wrapping types (e.g. for short, [-32768,32767]).

> If the operands of an equality operation are wrapping, is the resulting boolean wrapping? 

I don't think you can get a less-than-int from an equality operator as the result. With that being said, and for all BinaryOperators, the resulting type will be wrapping if any of the operands are wrapping.

```
    `-IfStmt 0x56518e85e710 <line:9:3, line:11:3>
      |-BinaryOperator 0x56518e85e6e0 <line:9:7, col:12> 'int __attribute__((wraps))':'int' '=='
      | |-ImplicitCastExpr 0x56518e85e6b0 <col:7> 'wrap_int':'int' <LValueToRValue>
      | | `-DeclRefExpr 0x56518e85e670 <col:7> 'wrap_int':'int' lvalue Var 0x56518e85e560 'a' 'wrap_int':'int'
      | `-ImplicitCastExpr 0x56518e85e6c8 <col:12> 'wrap_int':'int' <LValueToRValue>
      |   `-DeclRefExpr 0x56518e85e690 <col:12> 'wrap_int':'int' lvalue Var 0x56518e85e5d8 'b' 'wrap_int':'int'
      `-CompoundStmt 0x56518e85e700 <col:15, line:11:3>

```
---
> If you mark a bitfield wrapping, does it wrap?

Ah, nice corner case. This concerns the `-fsanitize=implicit-bitfield-conversion` sanitizer and is something I did not consider. So, currently the wraps attribute won't do anything on bitfields. I could add support for disabling this sanitizer for wrapping bitfields.

> The fact that with this patch, a wrapping int is "just" an int is both the strength and weakness: you don't need to write out all these interactions... but the result is just based on clang's internal preferences for preserving type sugar, which are not documented.

In the cases where the attribute is "lost" it is similar to how `const` qualifiers disappear during assignments/type changes.

```c
const int a;
int b = a; // b is not const
// or 
extern void foo(int some);
foo(a); // const-ness is stripped, @some is not const
```

In the cases where the attribute sticks around, it follows these rules:

* Persists through Unary operators 👍
* If any operand within a BinaryOperator expression are wrapping then the entire expression result is considered wrapping.
* When used in assignments or passed as arguments to a function, the wrapping attribute will be present if the left-hand side or the function argument is wrapping. (similar to `const`, I suppose).


---

So, after reading all your cases my TODO list is:
* add support for ternary operator results gaining the wrapping attribute if any of its operands have it
* add support for disabling `-fsanitize=implicit-bitfield-conversion` when bitfields are marked as wrapping.

https://github.com/llvm/llvm-project/pull/86618


More information about the cfe-commits mailing list