diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -141,6 +141,9 @@ ``-std=gnu2x`` as aliases for C23 and GNU C23, respectively. - Clang now supports `requires c23` for module maps. +- Clang now supports ```` which defines several macros for performing + checked integer arithmetic. + Non-comprehensive list of changes in this release ------------------------------------------------- diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -8673,10 +8673,13 @@ InGroup>, DefaultIgnore; def err_overflow_builtin_must_be_int : Error< - "operand argument to overflow builtin must be an integer (%0 invalid)">; + "operand argument to %select{overflow builtin|checked integer operation}0 " + "must be an integer type %select{|other than plain 'char', 'bool', bit-precise, " + "or an enumeration }0(%1 invalid)">; def err_overflow_builtin_must_be_ptr_int : Error< - "result argument to overflow builtin must be a pointer " - "to a non-const integer (%0 invalid)">; + "result argument to %select{overflow builtin|checked integer operation}0 " + "must be a pointer to a non-const integer type %select{|other than plain 'char', " + "'bool', bit-precise, or an enumeration }0(%1 invalid)">; def err_overflow_builtin_bit_int_max_size : Error< "__builtin_mul_overflow does not support 'signed _BitInt' operands of more " "than %0 bits">; diff --git a/clang/lib/Headers/CMakeLists.txt b/clang/lib/Headers/CMakeLists.txt --- a/clang/lib/Headers/CMakeLists.txt +++ b/clang/lib/Headers/CMakeLists.txt @@ -17,6 +17,7 @@ __stdarg_va_list.h stdatomic.h stdbool.h + stdckdint.h stddef.h __stddef_max_align_t.h __stddef_null.h diff --git a/clang/lib/Headers/stdckdint.h b/clang/lib/Headers/stdckdint.h new file mode 100644 --- /dev/null +++ b/clang/lib/Headers/stdckdint.h @@ -0,0 +1,44 @@ +/*===---- stdckdint.h - Standard header for checking integer----------------=== + * + * Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. + * See https://llvm.org/LICENSE.txt for license information. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + * + *===-----------------------------------------------------------------------=== + */ + +#ifndef __STDCKDINT_H +#define __STDCKDINT_H + +/* If we're hosted, fall back to the system's stdckdint.h. FreeBSD, for + * example, already has a Clang-compatible stdckdint.h header. + * + * The `stdckdint.h` header requires C 23 or newer. + */ +#if __STDC_HOSTED__ && __has_include_next() +#include_next +#else + +/* C23 7.20.1 Defines several macros for performing checked integer arithmetic*/ + +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L +#define __STDC_VERSION_STDCKDINT_H__ 202311L + +// Both A and B shall be any integer type other than "plain" char, bool, a bit- +// precise integer type, or an enumerated type, and they need not be the same. + +// R shall be a modifiable lvalue of any integer type other than "plain" char, +// bool, a bit-precise integer type, or an enumerated type. It shouldn't be +// short type, either. Otherwise, it may be unable to hold two the result of +// operating two 'int's. + +// A diagnostic message will be produced if A or B are not suitable integer +// types, or if R is not a modifiable lvalue of a suitable integer type or R +// is short type. +#define ckd_add(R, A, B) __builtin_add_overflow((A), (B), (R)) +#define ckd_sub(R, A, B) __builtin_sub_overflow((A), (B), (R)) +#define ckd_mul(R, A, B) __builtin_mul_overflow((A), (B), (R)) +#endif + +#endif /* __STDC_HOSTED__ */ +#endif /* __STDCKDINT_H */ diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp --- a/clang/lib/Lex/PPDirectives.cpp +++ b/clang/lib/Lex/PPDirectives.cpp @@ -231,9 +231,10 @@ .Cases("assert.h", "complex.h", "ctype.h", "errno.h", "fenv.h", true) .Cases("float.h", "inttypes.h", "iso646.h", "limits.h", "locale.h", true) .Cases("math.h", "setjmp.h", "signal.h", "stdalign.h", "stdarg.h", true) - .Cases("stdatomic.h", "stdbool.h", "stddef.h", "stdint.h", "stdio.h", true) - .Cases("stdlib.h", "stdnoreturn.h", "string.h", "tgmath.h", "threads.h", true) - .Cases("time.h", "uchar.h", "wchar.h", "wctype.h", true) + .Cases("stdatomic.h", "stdbool.h", "stdckdint.h", "stddef.h", true) + .Cases("stdint.h", "stdio.h", "stdlib.h", "stdnoreturn.h", true) + .Cases("string.h", "tgmath.h", "threads.h", "time.h", "uchar.h", true) + .Cases("wchar.h", "wctype.h", true) // C++ headers for C library facilities .Cases("cassert", "ccomplex", "cctype", "cerrno", "cfenv", true) diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -370,6 +370,32 @@ if (checkArgCount(S, TheCall, 3)) return true; + std::pair Builtins[] = { + { Builtin::BI__builtin_add_overflow, "ckd_add" }, + { Builtin::BI__builtin_sub_overflow, "ckd_sub" }, + { Builtin::BI__builtin_mul_overflow, "ckd_mul" }, + }; + + bool CkdOperation = llvm::any_of(Builtins, [&](const std::pair &P) { + return BuiltinID == P.first && TheCall->getExprLoc().isMacroID() && + Lexer::getImmediateMacroName(TheCall->getExprLoc(), + S.getSourceManager(), S.getLangOpts()) == P.second; + }); + + auto ValidCkdIntType = [](QualType QT) { + // A valid checked integer type is an integer type other than a plain char, + // bool, a bit-precise type, or an enumeration type. + if (const auto *BT = QT.getCanonicalType()->getAs()) + return (BT->getKind() >= BuiltinType::Short && + BT->getKind() <= BuiltinType::Int128) || ( + BT->getKind() >= BuiltinType::UShort && + BT->getKind() <= BuiltinType::UInt128) || + BT->getKind() == BuiltinType::UChar || + BT->getKind() == BuiltinType::SChar; + return false; + }; + // First two arguments should be integers. for (unsigned I = 0; I < 2; ++I) { ExprResult Arg = S.DefaultFunctionArrayLvalueConversion(TheCall->getArg(I)); @@ -377,9 +403,10 @@ TheCall->setArg(I, Arg.get()); QualType Ty = Arg.get()->getType(); - if (!Ty->isIntegerType()) { + bool IsValid = CkdOperation ? ValidCkdIntType(Ty) : Ty->isIntegerType(); + if (!IsValid) { S.Diag(Arg.get()->getBeginLoc(), diag::err_overflow_builtin_must_be_int) - << Ty << Arg.get()->getSourceRange(); + << CkdOperation << Ty << Arg.get()->getSourceRange(); return true; } } @@ -396,10 +423,11 @@ const auto *PtrTy = Ty->getAs(); if (!PtrTy || !PtrTy->getPointeeType()->isIntegerType() || + (!ValidCkdIntType(PtrTy->getPointeeType()) && CkdOperation) || PtrTy->getPointeeType().isConstQualified()) { S.Diag(Arg.get()->getBeginLoc(), diag::err_overflow_builtin_must_be_ptr_int) - << Ty << Arg.get()->getSourceRange(); + << CkdOperation << Ty << Arg.get()->getSourceRange(); return true; } } diff --git a/clang/test/C/C2x/n2359.c b/clang/test/C/C2x/n2359.c --- a/clang/test/C/C2x/n2359.c +++ b/clang/test/C/C2x/n2359.c @@ -34,3 +34,7 @@ // expected-error@-1 {{"__STDC_VERSION_STDINT_H__ not defined"}} #endif +#include +#ifndef __STDC_VERSION_STDCKDINT_H__ +#error "__STDC_VERSION_STDCKDINT_H__ not defined" +#endif diff --git a/clang/test/C/C2x/n2683.c b/clang/test/C/C2x/n2683.c new file mode 100644 --- /dev/null +++ b/clang/test/C/C2x/n2683.c @@ -0,0 +1,50 @@ +// RUN: %clang_cc1 -verify -ffreestanding -std=c23 %s + +/* WG14 N2683: Clang 18 + * Towards Integer Safety + */ +#include +#include +#include + +void test_semantic() { + int64_t result64 = 0; + int32_t result32 = 0; + wchar_t wide_char = L'A'; // The ascii value of `A` is 65 + + bool flag_add = ckd_add(&result64, INT32_MAX, 1); + bool flag_sub = ckd_sub(&result32, INT32_MAX, -1); + bool flag_mul = ckd_mul(&result64, INT64_MAX, 1); + + bool flag = ckd_add(&result64, wide_char, result32); // In C, wchar_t is a typedef to some integer type and is allowed. + + // FIXME: add static_assert calls to check the resulting values for correctness + // once the constant expression interpreter is able to handle the checked arithmetic + // builtins in C. Currently, they're only a valid constant expression in C++ due to + // looking for an ICE in C. Also all values in the tests of n2683_2.c should be checked. +} + +void test_invalid_input() { + _BitInt(33) a33 = 1; + char char_var = 'd'; // The ascii value of `d` is 100 + bool bool_var = 1; + const int const_result = 0; + enum week{Mon, Tue, Wed}; + enum week day = Mon; + int a = 100; + int b = 55; + int result = 10; + char plain_char[] = {U'牛'}; /* expected-warning {{implicit conversion from 'unsigned int' to 'char' changes value from 29275 to 91}} */ + + // invalid operand argument + bool flag_no_bitint = ckd_add(&result, a33, a); /* expected-error {{operand argument to checked integer operation must be an integer type other than plain 'char', 'bool', bit-precise, or an enumeration ('_BitInt(33)' invalid)}} */ + bool flag_no_bool = ckd_sub(&result, bool_var, b); /* expected-error {{operand argument to checked integer operation must be an integer type other than plain 'char', 'bool', bit-precise, or an enumeration ('bool' invalid)}} */ + bool flag_no_char = ckd_mul(&result, char_var, a); /* expected-error {{operand argument to checked integer operation must be an integer type other than plain 'char', 'bool', bit-precise, or an enumeration ('char' invalid)}} */ + bool flag_no_enum = ckd_mul(&result, day, b); /* expected-error {{operand argument to checked integer operation must be an integer type other than plain 'char', 'bool', bit-precise, or an enumeration ('enum week' invalid)}} */ + + // invalid result type + bool flag_nostr = ckd_sub(&plain_char, a, b); /* expected-error {{result argument to checked integer operation must be a pointer to a non-const integer type other than plain 'char', 'bool', bit-precise, or an enumeration ('char (*)[1]' invalid)}} */ + bool flag_nobool = ckd_mul(&bool_var, a, b); /* expected-error {{result argument to checked integer operation must be a pointer to a non-const integer type other than plain 'char', 'bool', bit-precise, or an enumeration ('bool *' invalid)}} */ + bool flag_noptr = ckd_add(result, a, b); /* expected-error {{result argument to checked integer operation must be a pointer to a non-const integer type other than plain 'char', 'bool', bit-precise, or an enumeration ('int' invalid)}} */ + bool flag_noconst = ckd_sub(&const_result, a, b); /* expected-error {{result argument to checked integer operation must be a pointer to a non-const integer type other than plain 'char', 'bool', bit-precise, or an enumeration ('const int *' invalid)}} */ +} diff --git a/clang/test/C/C2x/n2683_2.c b/clang/test/C/C2x/n2683_2.c new file mode 100644 --- /dev/null +++ b/clang/test/C/C2x/n2683_2.c @@ -0,0 +1,65 @@ +// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 3 +// RUN: %clang_cc1 -emit-llvm -o - -std=c23 %s | FileCheck %s + +#include +#include +// CHECK-LABEL: define dso_local void @test_add_overflow_to64( +// CHECK-SAME: ) #[[ATTR0:[0-9]+]] { +// CHECK-NEXT: entry: +// CHECK-NEXT: [[RESULT64:%.*]] = alloca i64, align 8 +// CHECK-NEXT: [[FLAG_ADD:%.*]] = alloca i8, align 1 +// CHECK-NEXT: store i64 0, ptr [[RESULT64]], align 8 +// CHECK-NEXT: [[TMP0:%.*]] = call { i64, i1 } @llvm.sadd.with.overflow.i64(i64 2147483647, i64 1) +// CHECK-NEXT: [[TMP1:%.*]] = extractvalue { i64, i1 } [[TMP0]], 1 +// CHECK-NEXT: [[TMP2:%.*]] = extractvalue { i64, i1 } [[TMP0]], 0 +// CHECK-NEXT: store i64 [[TMP2]], ptr [[RESULT64]], align 8 +// CHECK-NEXT: [[FROMBOOL:%.*]] = zext i1 [[TMP1]] to i8 +// CHECK-NEXT: store i8 [[FROMBOOL]], ptr [[FLAG_ADD]], align 1 +// CHECK-NEXT: ret void +// +void test_add_overflow_to64() { + int64_t result64 = 0; + bool flag_add = ckd_add(&result64, INT32_MAX, 1); +} + +// CHECK-LABEL: define dso_local void @test_sub_overflow( +// CHECK-SAME: ) #[[ATTR0]] { +// CHECK-NEXT: entry: +// CHECK-NEXT: [[RESULT32:%.*]] = alloca i32, align 4 +// CHECK-NEXT: [[FLAG_SUB:%.*]] = alloca i8, align 1 +// CHECK-NEXT: store i32 0, ptr [[RESULT32]], align 4 +// CHECK-NEXT: [[TMP0:%.*]] = call { i32, i1 } @llvm.ssub.with.overflow.i32(i32 2147483647, i32 -1) +// CHECK-NEXT: [[TMP1:%.*]] = extractvalue { i32, i1 } [[TMP0]], 1 +// CHECK-NEXT: [[TMP2:%.*]] = extractvalue { i32, i1 } [[TMP0]], 0 +// CHECK-NEXT: store i32 [[TMP2]], ptr [[RESULT32]], align 4 +// CHECK-NEXT: [[FROMBOOL:%.*]] = zext i1 [[TMP1]] to i8 +// CHECK-NEXT: store i8 [[FROMBOOL]], ptr [[FLAG_SUB]], align 1 +// CHECK-NEXT: ret void +// +void test_sub_overflow() { + int32_t result32 = 0; + bool flag_sub = ckd_sub(&result32, INT32_MAX, -1); +} + +// CHECK-LABEL: define dso_local void @test_mul_normal( +// CHECK-SAME: ) #[[ATTR0]] { +// CHECK-NEXT: entry: +// CHECK-NEXT: [[A:%.*]] = alloca i32, align 4 +// CHECK-NEXT: [[RESULT:%.*]] = alloca i32, align 4 +// CHECK-NEXT: [[FLAG_MUL:%.*]] = alloca i8, align 1 +// CHECK-NEXT: store i32 3, ptr [[A]], align 4 +// CHECK-NEXT: store i32 0, ptr [[RESULT]], align 4 +// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[A]], align 4 +// CHECK-NEXT: [[TMP1:%.*]] = call { i32, i1 } @llvm.smul.with.overflow.i32(i32 [[TMP0]], i32 2) +// CHECK-NEXT: [[TMP2:%.*]] = extractvalue { i32, i1 } [[TMP1]], 1 +// CHECK-NEXT: [[TMP3:%.*]] = extractvalue { i32, i1 } [[TMP1]], 0 +// CHECK-NEXT: store i32 [[TMP3]], ptr [[RESULT]], align 4 +// CHECK-NEXT: [[FROMBOOL:%.*]] = zext i1 [[TMP2]] to i8 +// CHECK-NEXT: store i8 [[FROMBOOL]], ptr [[FLAG_MUL]], align 1 +// CHECK-NEXT: ret void +// +void test_mul_normal() { + int a = 3; + int result = 0; + bool flag_mul = ckd_mul(&result, a, 2); +} diff --git a/clang/test/Headers/stdckdint.c b/clang/test/Headers/stdckdint.c new file mode 100644 --- /dev/null +++ b/clang/test/Headers/stdckdint.c @@ -0,0 +1,51 @@ +// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 3 +// RUN: %clang_cc1 -emit-llvm -verify -std=c23 %s -o - | FileCheck %s +// expected-no-diagnostics +#include + +_Static_assert(__STDC_VERSION_STDCKDINT_H__ == 202311L, ""); + +// CHECK-LABEL: define dso_local zeroext i1 @test_ckd_add( +// CHECK-SAME: ) #[[ATTR0:[0-9]+]] { +// CHECK-NEXT: entry: +// CHECK-NEXT: [[RESULT:%.*]] = alloca i32, align 4 +// CHECK-NEXT: [[TMP0:%.*]] = call { i32, i1 } @llvm.sadd.with.overflow.i32(i32 -1073741826, i32 -1073741826) +// CHECK-NEXT: [[TMP1:%.*]] = extractvalue { i32, i1 } [[TMP0]], 1 +// CHECK-NEXT: [[TMP2:%.*]] = extractvalue { i32, i1 } [[TMP0]], 0 +// CHECK-NEXT: store i32 [[TMP2]], ptr [[RESULT]], align 4 +// CHECK-NEXT: ret i1 [[TMP1]] +// +bool test_ckd_add() { + int result; + return ckd_add(&result, -1073741826, -1073741826); +} + +// CHECK-LABEL: define dso_local zeroext i1 @test_ckd_sub( +// CHECK-SAME: ) #[[ATTR0]] { +// CHECK-NEXT: entry: +// CHECK-NEXT: [[RESULT:%.*]] = alloca i32, align 4 +// CHECK-NEXT: [[TMP0:%.*]] = call { i32, i1 } @llvm.ssub.with.overflow.i32(i32 -1073741826, i32 1073741826) +// CHECK-NEXT: [[TMP1:%.*]] = extractvalue { i32, i1 } [[TMP0]], 1 +// CHECK-NEXT: [[TMP2:%.*]] = extractvalue { i32, i1 } [[TMP0]], 0 +// CHECK-NEXT: store i32 [[TMP2]], ptr [[RESULT]], align 4 +// CHECK-NEXT: ret i1 [[TMP1]] +// +bool test_ckd_sub() { + int result; + return ckd_sub(&result, -1073741826, 1073741826); +} + +// CHECK-LABEL: define dso_local zeroext i1 @test_ckd_mul( +// CHECK-SAME: ) #[[ATTR0]] { +// CHECK-NEXT: entry: +// CHECK-NEXT: [[RESULT:%.*]] = alloca i32, align 4 +// CHECK-NEXT: [[TMP0:%.*]] = call { i32, i1 } @llvm.smul.with.overflow.i32(i32 -1073741826, i32 2) +// CHECK-NEXT: [[TMP1:%.*]] = extractvalue { i32, i1 } [[TMP0]], 1 +// CHECK-NEXT: [[TMP2:%.*]] = extractvalue { i32, i1 } [[TMP0]], 0 +// CHECK-NEXT: store i32 [[TMP2]], ptr [[RESULT]], align 4 +// CHECK-NEXT: ret i1 [[TMP1]] +// +bool test_ckd_mul() { + int result; + return ckd_mul(&result, -1073741826, 2); +} diff --git a/clang/test/Sema/builtins-overflow.c b/clang/test/Sema/builtins-overflow.c --- a/clang/test/Sema/builtins-overflow.c +++ b/clang/test/Sema/builtins-overflow.c @@ -14,11 +14,11 @@ __builtin_add_overflow(); // expected-error {{too few arguments to function call, expected 3, have 0}} __builtin_add_overflow(1, 1, 1, 1); // expected-error {{too many arguments to function call, expected 3, have 4}} - __builtin_add_overflow(c, 1, &r); // expected-error {{operand argument to overflow builtin must be an integer ('const char *' invalid)}} - __builtin_add_overflow(1, c, &r); // expected-error {{operand argument to overflow builtin must be an integer ('const char *' invalid)}} - __builtin_add_overflow(1, 1, 3); // expected-error {{result argument to overflow builtin must be a pointer to a non-const integer ('int' invalid)}} - __builtin_add_overflow(1, 1, &f); // expected-error {{result argument to overflow builtin must be a pointer to a non-const integer ('float *' invalid)}} - __builtin_add_overflow(1, 1, &q); // expected-error {{result argument to overflow builtin must be a pointer to a non-const integer ('const unsigned int *' invalid)}} + __builtin_add_overflow(c, 1, &r); // expected-error {{operand argument to overflow builtin must be an integer type ('const char *' invalid)}} + __builtin_add_overflow(1, c, &r); // expected-error {{operand argument to overflow builtin must be an integer type ('const char *' invalid)}} + __builtin_add_overflow(1, 1, 3); // expected-error {{result argument to overflow builtin must be a pointer to a non-const integer type ('int' invalid)}} + __builtin_add_overflow(1, 1, &f); // expected-error {{result argument to overflow builtin must be a pointer to a non-const integer type ('float *' invalid)}} + __builtin_add_overflow(1, 1, &q); // expected-error {{result argument to overflow builtin must be a pointer to a non-const integer type ('const unsigned int *' invalid)}} { _BitInt(128) x = 1; diff --git a/clang/www/c_status.html b/clang/www/c_status.html --- a/clang/www/c_status.html +++ b/clang/www/c_status.html @@ -887,6 +887,11 @@ N2672 Yes + + Towards Integer Safety + N2683 + Clang 18 + Adding Fundamental Type for N-bit Integers