Skip to content

Commit 4cb9d97

Browse files
authored
Merge pull request #5118 from MicrosoftDocs/main
11/30/2023 AM Publish
2 parents 137dd29 + fa55e4b commit 4cb9d97

File tree

8 files changed

+582
-1
lines changed

8 files changed

+582
-1
lines changed

docs/code-quality/c26837.md

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
---
2+
description: "Learn more about: Warning C26837"
3+
title: Warning C26837
4+
ms.date: 11/29/2023
5+
f1_keywords: ["C26837", "INTERLOCKED_COMPARE_EXCHANGE_MISUSE", "__WARNING_INTERLOCKED_COMPARE_EXCHANGE_MISUSE"]
6+
helpviewer_keywords: ["C26837"]
7+
---
8+
# Warning C26837
9+
10+
> Value for the comparand `comp` for function `func` has been loaded from the destination location `dest` through non-volatile read.
11+
12+
This rule was added in Visual Studio 2022 17.8.
13+
14+
## Remarks
15+
16+
The [`InterlockedCompareExchange`](/windows/win32/api/winnt/nf-winnt-interlockedcompareexchange) function, and its derivatives such as [`InterlockedCompareExchangePointer`](/windows/win32/api/winnt/nf-winnt-interlockedcompareexchangepointer), perform an atomic compare-and-exchange operation on the specified values. If the `Destination` value is equal to the `Comparand` value, the *exchange* value is stored in the address specified by `Destination`. Otherwise, no operation is performed. The `interlocked` functions provide a simple mechanism for synchronizing access to a variable that is shared by multiple threads. This function is atomic with respect to calls to other `interlocked` functions. Misuse of these functions can generate object code that behaves differently than you expect because optimization can change the behavior of the code in unexpected ways.
17+
18+
Consider the following code:
19+
20+
```cpp
21+
#include <Windows.h>
22+
23+
bool TryLock(__int64* plock)
24+
{
25+
__int64 lock = *plock;
26+
return (lock & 1) &&
27+
_InterlockedCompareExchange64(plock, lock & ~1, lock) == lock;
28+
}
29+
```
30+
31+
The intent of this code is:
32+
33+
1. Read the current value from the `plock` pointer.
34+
1. Check if this current value has the least significant bit set.
35+
1. If it does have least significant bit set, clear the bit while preserving the other bits of the current value.
36+
37+
To accomplish this, a copy of the current value is read from the`plock` pointer and saved to a stack variable `lock`. `lock` is used three times:
38+
39+
1. First, to check if the least-significant bit is set.
40+
1. Second, as the `Comparand` value to `InterlockedCompareExchange64`.
41+
1. Finally, in the comparison of the return value from `InterlockedCompareExchange64`
42+
43+
This assumes that the current value saved to the stack variable is read once at the start of the function and doesn't change. This is necessary because the current value is first checked before attempting the operation, then explicitly used as the `Comparand` in `InterlockedCompareExchange64`, and finally used to compare the return value from `InterlockedCompareExchange64`.
44+
45+
Unfortunately, the previous code can be compiled into assembly that behaves differently than from what you expect from the source code. Compile the previous code with the Microsoft Visual C++ (MSVC) compiler and the [`/O1`](../build/reference/o1-o2-minimize-size-maximize-speed.md) option and inspect the resultant assembly code to see how the value of the lock for each of the references to `lock` is obtained. The MSVC compiler version v19.37 produces assembly code that looks like:
46+
47+
```x86asm
48+
plock$ = 8
49+
bool TryLock(__int64 *) PROC ; TryLock, COMDAT
50+
mov r8b, 1
51+
test BYTE PTR [rcx], r8b
52+
je SHORT $LN3@TryLock
53+
mov rdx, QWORD PTR [rcx]
54+
mov rax, QWORD PTR [rcx]
55+
and rdx, -2
56+
lock cmpxchg QWORD PTR [rcx], rdx
57+
je SHORT $LN4@TryLock
58+
$LN3@TryLock:
59+
xor r8b, r8b
60+
$LN4@TryLock:
61+
mov al, r8b
62+
ret 0
63+
bool TryLock(__int64 *) ENDP ; TryLock
64+
```
65+
66+
`rcx` holds the value of the parameter `plock`. Rather than make a copy of the current value on the stack, the assembly code is re-reading the value from `plock` every time. This means the value could be different each time it's read. This invalidates the sanitization that the developer is performing. The value is re-read from `plock` after it's verified that it has its least-significant bit set. Because it's re-read after this validation is performed, the new value might no longer have the least-significant bit set. Under a race condition, this code might behave as if it successfully obtained the specified lock when it was already locked by another thread.
67+
68+
The compiler is allowed to remove or add memory reads or writes as long as the behavior of the code isn't altered. To prevent the compiler from making such changes, force reads to be `volatile` when you read the value from memory and cache it in a variable. Objects that are declared as `volatile` aren't used in certain optimizations because their values can change at any time. The generated code always reads the current value of a `volatile` object when it's requested, even if a previous instruction asked for a value from the same object. The reverse also applies for the same reason. The value of the `volatile` object isn't read again unless requested. For more information about `volatile`, see [`volatile`](..\cpp\volatile-cpp.md). For example:
69+
70+
```cpp
71+
#include <Windows.h>
72+
73+
bool TryLock(__int64* plock)
74+
{
75+
__int64 lock = *static_cast<volatile __int64*>(plock);
76+
return (lock & 1) &&
77+
_InterlockedCompareExchange64(plock, lock & ~1, lock) == lock;
78+
}
79+
```
80+
81+
Compile this code with same [`/O1`](../build/reference/o1-o2-minimize-size-maximize-speed.md) option as before. The generated assembly no longer reads `plock` for use of the cached value in `lock`.
82+
83+
For more examples of how the code can be fixed, see [Example](#example).
84+
85+
Code analysis name: `INTERLOCKED_COMPARE_EXCHANGE_MISUSE`
86+
87+
## Example
88+
89+
The compiler might optimize the following code to read `plock` multiple times instead of using the cached value in `lock`:
90+
91+
```cpp
92+
#include <Windows.h>
93+
94+
bool TryLock(__int64* plock)
95+
{
96+
__int64 lock = *plock;
97+
return (lock & 1) &&
98+
_InterlockedCompareExchange64(plock, lock & ~1, lock) == lock;
99+
}
100+
```
101+
102+
To fix the problem, force reads to be `volatile` so that the compiler doesn't optimize code to read successively from the same memory unless explicitly instructed. This prevents the optimizer from introducing unexpected behavior.
103+
104+
The first method to treat memory as `volatile` is to take the destination address as `volatile` pointer:
105+
106+
```cpp
107+
#include <Windows.h>
108+
109+
bool TryLock(volatile __int64* plock)
110+
{
111+
__int64 lock = *plock;
112+
return (lock & 1) &&
113+
_InterlockedCompareExchange64(plock, lock & ~1, lock) == lock;
114+
}
115+
```
116+
117+
The second method is using `volatile` read from the destination address. There are a few different ways to do this:
118+
119+
- Casting the pointer to `volatile` pointer before dereferencing the pointer
120+
- Creating a `volatile` pointer from the provided pointer
121+
- Using `volatile` read helper functions.
122+
123+
For example:
124+
125+
```cpp
126+
#include <Windows.h>
127+
128+
bool TryLock(__int64* plock)
129+
{
130+
__int64 lock = ReadNoFence64(plock);
131+
return (lock & 1) &&
132+
_InterlockedCompareExchange64(plock, lock & ~1, lock) == lock;
133+
}
134+
```
135+
136+
## Heuristics
137+
138+
This rule is enforced by detecting if the value in the `Destination` of the `InterlockedCompareExchange` function, or any of its derivatives, is loaded through a non-`volatile` read, and then used as the `Comparand` value. However, it doesn't explicitly check if the loaded value is used to determine the *exchange* value. It assumes the *exchange* value is related to the `Comparand` value.
139+
140+
## See also
141+
142+
[`InterlockedCompareExchange` function (winnt.h)](/windows/win32/api/winnt/nf-winnt-interlockedcompareexchange)\
143+
[`_InterlockedCompareExchange` intrinsic functions](../intrinsics/interlockedcompareexchange-intrinsic-functions.md)

docs/code-quality/c26861.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
description: "Learn more about: Warning C26861"
3+
title: Warning C26861
4+
ms.date: 11/29/2023
5+
f1_keywords: ["C26861", "DATETIME_MANIPULATION_WITHOUT_LEAPYEAR_CHECK", "__WARNING_DATETIME_MANIPULATION_WITHOUT_LEAPYEAR_CHECK"]
6+
helpviewer_keywords: ["C26861"]
7+
---
8+
# Warning C26861
9+
10+
> Field of a date-time object `var` has been modified without proper leap year checking: `expr`
11+
12+
This rule was added in Visual Studio 2022 17.8.
13+
14+
## Remarks
15+
16+
In the Gregorian calendar, every year exactly divisible by four is a leap year--except for years that are exactly divisible by 100. The centurial years are also leap years if they're exactly divisible by 400.
17+
18+
A leap year bug occurs when software doesn't account for this leap year logic, or uses flawed logic. The can affect reliability, availability, or even the security of the affected system.
19+
20+
It isn't safe to add or subtract some number to or from the year, month, or day field of a date-time object without taking leap years into account. This calculation is commonly performed to determine the expiration date for a certificate, for example. On many dates, a naive calculation may produce the desired result. However, when the result is February 29 (a leap day) and the year isn't a leap year, the result is invalid.
21+
22+
For example, adding a year to 2020-01-31 produces 2021-01-31. But adding a year to 2020-02-29 produces 2021-02-29, which isn't a valid date because 2021 isn't a leap year.
23+
24+
Be cautious when manipulating variables that represent date values. Handle leap years and leap days properly, or use an API or library that handles date arithmetic safely.
25+
26+
Code analysis name: `DATETIME_MANIPULATION_WITHOUT_LEAPYEAR_CHECK`
27+
28+
## Example
29+
30+
The following code advances the system time by a year by incrementing the year field of the date-time object representing the system time. However, it can produce an invalid date-time object if the date was February 29 before the modification, because the next year isn't a leap year:
31+
32+
```cpp
33+
SYSTEMTIME st;
34+
GetSystemTime(&st);
35+
st.wYear++; // warning C26861
36+
```
37+
38+
To avoid creating an invalid date-time object due to a leap year, check if the resulting date is still valid and make the necessary adjustments to make it valid, as in this example:
39+
40+
```cpp
41+
SYSTEMTIME st;
42+
GetSystemTime(&st);
43+
st.wYear++;
44+
if (st.wMonth == 2 && st.wDay == 29)
45+
{
46+
// move back a day when landing on Feb 29 in a non-leap year
47+
bool isLeapYear = st.wYear % 4 == 0 && (st.wYear % 100 != 0 || st.wYear % 400 == 0);
48+
if (!isLeapYear)
49+
{
50+
st.wDay = 28;
51+
}
52+
}
53+
```
54+
55+
## Heuristics
56+
57+
Currently, this rule only recognizes the Windows `SYSTEMTIME` struct and C `tm` struct.
58+
59+
This rule employs a simplified heuristic to find potentially risky changes and reports warnings unless there's appropriate leap year or leap day checking. It doesn't try to verify if the leap year or leap day checking is performed correctly for the modified date-time object.
60+
61+
This rule is an opt-in rule, which means that code analysis should use a ruleset file, and the rule should be explicitly included in the ruleset file, and enabled for it to be applied. For more information on creating a custom ruleset for code analysis, see: [Use Rule Sets to Specify the `C++` Rules to Run](using-rule-sets-to-specify-the-cpp-rules-to-run.md).
62+
63+
## See also
64+
65+
[C6393](c6393.md)\
66+
[C6394](c6394.md)\
67+
[C26862](c26862.md)\
68+
[C26863](c26863.md)\
69+
[C26864](c26864.md)

docs/code-quality/c26862.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
---
2+
description: "Learn more about: Warning C26862"
3+
title: Warning C26862
4+
ms.date: 11/29/2023
5+
f1_keywords: ["C26862", "INCOMPLETE_DATETIME_CONVERSION", "__WARNING_INCOMPLETE_DATETIME_CONVERSION"]
6+
helpviewer_keywords: ["C26862"]
7+
---
8+
# Warning C26862
9+
10+
> A date-time object `var` has been created from a different type of date-time object but conversion was incomplete: `expr`
11+
12+
This rule was added in Visual Studio 2022 17.8.
13+
14+
## Remarks
15+
16+
Proper enforcement of leap year and leap day handling rules require tracking the proper conversion between date-time objects of different types such as the Windows `SYSTEMTIME` struct and the C `tm` struct. Different date-time types may have different bases for the year, month, and day fields. For example, `SYSTEMTIME` has a 0-based year, but 1-based month and day fields. On the other hand, `tm` has a 1900-based year, a 0-based month, and a 1-based day fields. To convert an object of one of these types to an object of another type, the year, month, and day fields must be adjusted appropriately.
17+
18+
Code analysis name: `INCOMPLETE_DATETIME_CONVERSION`
19+
20+
## Example
21+
22+
The following code tries to convert an instance of `tm` into an instance of `SYSTEMTIME`. It makes the necessary adjustment to the year field, but doesn't properly adjust the month field:
23+
24+
```cpp
25+
#include <Windows.h>
26+
#include <ctime>
27+
28+
void ConvertTmToSystemTime1b(const tm& tm)
29+
{
30+
SYSTEMTIME st;
31+
st.wYear = tm.tm_year + 1900;
32+
st.wMonth = tm.tm_mon; // C26862, Adjustment is missing
33+
st.wDay = tm.tm_mday;
34+
}
35+
```
36+
37+
To fix this problem, adjust the month and year fields:
38+
39+
```cpp
40+
#include <Windows.h>
41+
#include <ctime>
42+
43+
void ConvertTmToSystemTime(const tm& tm)
44+
{
45+
SYSTEMTIME st;
46+
st.wYear = tm.tm_year + 1900;
47+
st.wMonth = tm.tm_mon + 1;
48+
st.wDay = tm.tm_mday;
49+
}
50+
```
51+
52+
## Heuristics
53+
54+
This rule only recognizes the Windows `SYSTEMTIME` struct and the C `tm` struct.
55+
56+
This rule is an opt-in rule, meaning that code analysis should use a ruleset file, and the rule should be explicitly included in the ruleset file, and enabled for it to be applied. For more information on creating a custom ruleset for code analysis, see [Use Rule Sets to Specify the `C++` Rules to Run](using-rule-sets-to-specify-the-cpp-rules-to-run.md).
57+
58+
## See also
59+
60+
[C6393](c6393.md)\
61+
[C6394](c6394.md)\
62+
[C26861](c26861.md)\
63+
[C26863](c26863.md)\
64+
[C26864](c26864.md)

docs/code-quality/c26863.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
---
2+
description: "Learn more about: Warning C26863"
3+
title: Warning C26863
4+
ms.date: 11/29/2023
5+
f1_keywords: ["C26863", "DATETIME_MANIPULATION_FUNCTION_RETURN_IGNORED", "__WARNING_DATETIME_MANIPULATION_FUNCTION_RETURN_IGNORED"]
6+
helpviewer_keywords: ["C26863"]
7+
---
8+
# Warning C26863
9+
10+
> Return value from a date-time handling function `func` is ignored
11+
12+
This rule was added in Visual Studio 2022 17.8.
13+
14+
## Remarks
15+
16+
It's important to verify the return value of a function that transforms a date structure when the year, month, or date input argument was manipulated without proper leap year handling. Otherwise, the function may have failed and execution continues with an output parameter containing invalid data.
17+
18+
The following is a list of the functions that this warning covers:
19+
20+
- [`FileTimeToSystemTime`](/windows/win32/api/timezoneapi/nf-timezoneapi-filetimetosystemtime)
21+
- [`SystemTimeToFileTime`](/windows/win32/api/timezoneapi/nf-timezoneapi-systemtimetofiletime)
22+
- [`SystemTimeToTzSpecificLocalTime`](/windows/win32/api/timezoneapi/nf-timezoneapi-systemtimetotzspecificlocaltime)
23+
- [`SystemTimeToTzSpecificLocalTimeEx`](/windows/win32/api/timezoneapi/nf-timezoneapi-systemtimetotzspecificlocaltimeex)
24+
- [`TzSpecificLocalTimeToSystemTime`](/windows/win32/api/timezoneapi/nf-timezoneapi-tzspecificlocaltimetosystemtime)
25+
- [`TzSpecificLocalTimeToSystemTimeEx`](/windows/win32/api/timezoneapi/nf-timezoneapi-tzspecificlocaltimetosystemtimeex)
26+
- [`RtlLocalTimeToSystemTime`](/windows/win32/api/winternl/nf-winternl-rtllocaltimetosystemtime)
27+
- [`RtlTimeToSecondsSince1970`](/windows/win32/api/winternl/nf-winternl-rtltimetosecondssince1970)
28+
29+
Code analysis name: `DATETIME_MANIPULATION_FUNCTION_RETURN_IGNORED`
30+
31+
## Example
32+
33+
The following code tries to get current system time, advance the month field by one month, and get the file time that corresponds to the updated system time via [`SystemTimeToFileTime`](/windows/win32/api/timezoneapi/nf-timezoneapi-systemtimetofiletime). However, [`SystemTimeToFileTime`](/windows/win32/api/timezoneapi/nf-timezoneapi-systemtimetofiletime) might fail, as the updated system time may become invalid:
34+
35+
```cpp
36+
#include <Windows.h>
37+
38+
void foo()
39+
{
40+
FILETIME ft;
41+
SYSTEMTIME st;
42+
GetSystemTime(&st);
43+
st.wMonth++; // Advance month by one
44+
// Get the file time
45+
SystemTimeToFileTime(&st, &ft); // C26863
46+
}
47+
```
48+
49+
To fix the problem, always check the return value from date-time manipulation functions and handle failures appropriately:
50+
51+
```cpp
52+
#include <Windows.h>
53+
54+
void foo()
55+
{
56+
FILETIME ft;
57+
SYSTEMTIME st;
58+
GetSystemTime(&st);
59+
60+
st.wMonth++; // Advance month by one
61+
// Get file time
62+
if (SystemTimeToFileTime(&st, &ft))
63+
{
64+
// Use file time
65+
}
66+
}
67+
```
68+
69+
## Heuristics
70+
71+
This rule only recognizes the Windows `SYSTEMTIME` struct and the C `tm` struct.
72+
73+
This rule is enforced regardless of whether the input arguments were validated before calling these functions. If all the input arguments are validated before calling the function, this rule can report false warning.
74+
75+
This rule is an opt-in rule, meaning that code analysis should use a ruleset file, and the rule should be explicitly included in the ruleset file, and enabled for it to be applied. For more information on creating a custom ruleset for code analysis, see [Use Rule Sets to Specify the `C++` Rules to Run](using-rule-sets-to-specify-the-cpp-rules-to-run.md).
76+
77+
## See also
78+
79+
[C6393](c6393.md)\
80+
[C6394](c6394.md)\
81+
[C26861](c26861.md)\
82+
[C26862](c26862.md)\
83+
[C26864](c26864.md)

0 commit comments

Comments
 (0)