Skip to content

Commit 29d8c34

Browse files
Reland "[flang] Avoid undefined behaviour when parsing format expressions (llvm#147539)" (llvm#148169)
This reverts commit e8e5d07. This previously failed because the flang-rt build could not find the llvm header file. It passed on some machines but only because they had globally installed copies of older llvm. To fix this, I've copied the required routines from llvm into flang. With the following justification: * Flang can, and does, use llvm headers. * Some Flang headers are also used in Flang-rt. * Flang-rt cannot use llvm headers. * Therefore any Flang header using in Flang-rt cannot use llvm headers either. To support that conclusion, https://flang.llvm.org/docs/IORuntimeInternals.html states: "The Fortran I/O runtime support library is written in C++17, and uses some C++17 standard library facilities, but it is intended to not have any link-time dependences on the C++ runtime support library or any LLVM libraries." This talks about libraries but I assume it applies to llvm in general. Nothing in flang/include/flang/Common, or flang/include/flang/Common includes any llvm header, and I see some very similar headers there that duplicate llvm functionality. Like float128.h. I can only assume this means these files must remain free of dependencies on LLVM. I have copied the two routines literally and put them in the flang::common namespace, for lack of a better place for them. So they don't clash with something. I have specialised the function to the 1 type flang needs, as it might save a bit of compile time.
1 parent df54961 commit 29d8c34

File tree

1 file changed

+73
-5
lines changed

1 file changed

+73
-5
lines changed

flang/include/flang/Common/format.h

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "Fortran-consts.h"
1313
#include "enum-set.h"
1414
#include <cstring>
15+
#include <limits>
1516

1617
// Define a FormatValidator class template to validate a format expression
1718
// of a given CHAR type. To enable use in runtime library code as well as
@@ -28,6 +29,71 @@
2829

2930
namespace Fortran::common {
3031

32+
// AddOverflow and MulOverflow are copied from
33+
// llvm/include/llvm/Support/MathExtras.h and specialised to int64_t.
34+
35+
/// Add two signed integers, computing the two's complement truncated result,
36+
/// returning true if overflow occurred.
37+
static inline bool AddOverflow(int64_t X, int64_t Y, int64_t &Result) {
38+
#if __has_builtin(__builtin_add_overflow)
39+
return __builtin_add_overflow(X, Y, &Result);
40+
#else
41+
// Perform the unsigned addition.
42+
const uint64_t UX = static_cast<uint64_t>(X);
43+
const uint64_t UY = static_cast<uint64_t>(Y);
44+
const uint64_t UResult = UX + UY;
45+
46+
// Convert to signed.
47+
Result = static_cast<int64_t>(UResult);
48+
49+
// Adding two positive numbers should result in a positive number.
50+
if (X > 0 && Y > 0) {
51+
return Result <= 0;
52+
}
53+
// Adding two negatives should result in a negative number.
54+
if (X < 0 && Y < 0) {
55+
return Result >= 0;
56+
}
57+
return false;
58+
#endif
59+
}
60+
61+
/// Multiply two signed integers, computing the two's complement truncated
62+
/// result, returning true if an overflow occurred.
63+
static inline bool MulOverflow(int64_t X, int64_t Y, int64_t &Result) {
64+
#if __has_builtin(__builtin_mul_overflow)
65+
return __builtin_mul_overflow(X, Y, &Result);
66+
#else
67+
// Perform the unsigned multiplication on absolute values.
68+
const uint64_t UX =
69+
X < 0 ? (0 - static_cast<uint64_t>(X)) : static_cast<uint64_t>(X);
70+
const uint64_t UY =
71+
Y < 0 ? (0 - static_cast<uint64_t>(Y)) : static_cast<uint64_t>(Y);
72+
const uint64_t UResult = UX * UY;
73+
74+
// Convert to signed.
75+
const bool IsNegative = (X < 0) ^ (Y < 0);
76+
Result = IsNegative ? (0 - UResult) : UResult;
77+
78+
// If any of the args was 0, result is 0 and no overflow occurs.
79+
if (UX == 0 || UY == 0) {
80+
return false;
81+
}
82+
83+
// UX and UY are in [1, 2^n], where n is the number of digits.
84+
// Check how the max allowed absolute value (2^n for negative, 2^(n-1) for
85+
// positive) divided by an argument compares to the other.
86+
if (IsNegative) {
87+
return UX > (static_cast<uint64_t>(std::numeric_limits<int64_t>::max()) +
88+
uint64_t(1)) /
89+
UY;
90+
} else {
91+
return UX >
92+
(static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) / UY;
93+
}
94+
#endif
95+
}
96+
3197
struct FormatMessage {
3298
const char *text; // message text; may have one %s argument
3399
const char *arg; // optional %s argument value
@@ -214,16 +280,18 @@ template <typename CHAR> void FormatValidator<CHAR>::NextToken() {
214280
case '7':
215281
case '8':
216282
case '9': {
217-
int64_t lastValue;
218283
const CHAR *lastCursor;
219284
integerValue_ = 0;
220285
bool overflow{false};
221286
do {
222-
lastValue = integerValue_;
223287
lastCursor = cursor_;
224-
integerValue_ = 10 * integerValue_ + c - '0';
225-
if (lastValue > integerValue_) {
226-
overflow = true;
288+
if (!overflow) {
289+
overflow =
290+
MulOverflow(static_cast<int64_t>(10), integerValue_, integerValue_);
291+
}
292+
if (!overflow) {
293+
overflow = AddOverflow(
294+
integerValue_, static_cast<int64_t>(c - '0'), integerValue_);
227295
}
228296
c = NextChar();
229297
} while (c >= '0' && c <= '9');

0 commit comments

Comments
 (0)