| Ticket UUID: | b0f84119c8f040ed2cb4910e2aea33fc2ddc2d94 | |||
| Title: | TEBCresume(): undefined behavior for INST_LSHIFT | |||
| Type: | Patch | Version: | 8.6.12 | |
| Submitter: | chrstphrchvz | Created on: | 2021-12-16 17:22:45 | |
| Subsystem: | 16. Commands A-H | Assigned To: | jan.nijtmans | |
| Priority: | 5 Medium | Severity: | Minor | |
| Status: | Closed | Last Modified: | 2021-12-18 14:56:19 | |
| Resolution: | Fixed | Closed By: | jan.nijtmans | |
| Closed on: | 2021-12-18 14:56:19 | |||
| Description: |
Yet another case where I do not observe Tcl behaving unexpectedly despite complaints from UBSan. Example to trigger complaint from -fsanitize=shift-base (left shift by CHAR_BIT*sizeof(long)-1 causes signed integer overflow) and -fsanitize=signed-integer-overflow (2’s complement negation of LONG_MIN causes signed integer overflow) for sizeof(long)==4:
% expr {1<<0}
tcl/generic/tclExecute.c:6384:9: runtime error: left shift of 1 by 31 places cannot be represented in type 'long int'
tcl/generic/tclExecute.c:6384:5: runtime error: negation of -2147483648 cannot be represented in type 'long int'; cast to an unsigned type to negate this value to itself
1
Likewise for sizeof(long)==8:
% expr {1<<0}
tcl/generic/tclExecute.c:6384:9: runtime error: left shift of 1 by 63 places cannot be represented in type 'long'
tcl/generic/tclExecute.c:6384:5: runtime error: negation of -9223372036854775808 cannot be represented in type 'long'; cast to an unsigned type to negate this value to itself
1
Another example to trigger complaint from -fsanitize=shift-base (left-shifting negative signed integer is undefined behavior):
% expr {-1<<1}
tcl/generic/tclExecute.c:6385:17: runtime error: left shift of negative value -1
-2
Suggested fix for both of these is to use unsigned long literals and casts in a few places (this should avoid undefined behavior without changing actual behavior): diff --git generic/tclExecute.c generic/tclExecute.c index 8963472e5..19b0565 100644 --- generic/tclExecute.c +++ generic/tclExecute.c @@ -6381,8 +6381,8 @@ TEBCresume( if ((size_t) shift < CHAR_BIT*sizeof(long) && (l1 != 0) && !((l1>0 ? l1 : ~l1) & - -(1L<<(CHAR_BIT*sizeof(long) - 1 - shift)))) { - lResult = l1 << shift; + -(1UL<<(CHAR_BIT*sizeof(long) - 1 - shift)))) { + lResult = (unsigned long) l1 << shift; goto longResultOfArithmetic; } } (I might also point out that l1 != 0 in this condition is always true because of the l1 == 0 check on line 6353, though I’m not sure whether removing it makes more sense than keeping it.) | |||
| User Comments: |
jan.nijtmans added on 2021-12-18 14:56:19:
Fix now merged to 8.6 and up. Thanks very much for the report and for the fix! jan.nijtmans added on 2021-12-16 21:03:18: Suggested fix committed [c8fd4fa71448bcf6|here] | |||