Compare commits

...

64 Commits

Author SHA1 Message Date
Leonard Hecker
d2c41bf82d Remove chromium math & Rarely used til::rect helpers 2024-07-20 19:11:56 +02:00
Leonard Hecker
784f3f8780 Bad merge? 2024-07-20 03:02:13 +02:00
Leonard Hecker
3eb298dd5a Merge remote-tracking branch 'origin/main' into dev/lhecker/goodbye-vtengine 2024-07-19 22:38:02 +02:00
Leonard Hecker
21b13ca461 Avoid emitting VT for no-op SetConsoleActiveScreenBuffer calls 2024-07-18 04:29:46 +02:00
Leonard Hecker
81f3778bad Merge remote-tracking branch 'origin/main' into dev/lhecker/goodbye-vtengine 2024-07-18 04:25:46 +02:00
Leonard Hecker
a7158fb26a Fix bad merge 2024-07-17 18:13:19 +02:00
Leonard Hecker
e37b10fc89 Merge remote-tracking branch 'origin/main' into dev/lhecker/goodbye-vtengine 2024-07-17 18:08:43 +02:00
Leonard Hecker
55f5f34f67 Merge remote-tracking branch 'origin/main' into dev/lhecker/goodbye-vtengine 2024-07-17 03:16:03 +02:00
Leonard Hecker
f6f81e3da8 Merge remote-tracking branch 'origin/main' into dev/lhecker/goodbye-vtengine 2024-07-15 15:57:24 +02:00
Leonard Hecker
04aa614d66 More AuditMode fixes 2024-07-15 01:39:43 +02:00
Leonard Hecker
44a538d7e5 Address AuditMode issues 2024-07-15 01:13:37 +02:00
Leonard Hecker
34857bc3e7 should've 2024-07-15 00:47:07 +02:00
Leonard Hecker
119026afaf Right, we can't remove those 2024-07-15 00:46:17 +02:00
Leonard Hecker
316b91a2ff Proper overlapped IO, ITerminalHandoff3 2024-07-15 00:28:35 +02:00
Leonard Hecker
730d6873a3 VtIo tracing, More robust shutdown 2024-07-15 00:16:57 +02:00
Leonard Hecker
d67b70da8d More flexible CreateOverlappedPipe 2024-07-15 00:15:24 +02:00
Leonard Hecker
245279d74a Eh, NtCreateFile for consistency 2024-07-14 00:00:50 +02:00
Leonard Hecker
c3f48da21d Another one 2024-07-13 23:57:41 +02:00
Leonard Hecker
e1eb9db115 Make AuditMode happy, Improve comments 2024-07-13 23:49:32 +02:00
Leonard Hecker
2c1d7fcd28 Spell fix 2024-07-13 23:30:13 +02:00
Leonard Hecker
93930bb3fa They said anonymous overlapped pipes can't be done 2024-07-13 23:27:23 +02:00
Leonard Hecker
495c3e5295 10% faster for 128KiB of memory. Good deal. 2024-07-12 22:03:24 +02:00
Leonard Hecker
cc31a59669 Use overlapped pipes for WT 2024-07-12 21:32:18 +02:00
Leonard Hecker
d3ee58b9fc Add a missing force-wrap, Minor cleanup 2024-07-12 00:19:42 +02:00
Leonard Hecker
882f3faf0d Merge remote-tracking branch 'origin/main' into dev/lhecker/goodbye-vtengine 2024-07-11 23:50:49 +02:00
Leonard Hecker
512468d47f LNM 2024-07-11 18:38:20 +02:00
Leonard Hecker
8b59551151 Forgot to call Submit() 2024-07-11 18:33:13 +02:00
Leonard Hecker
d65bb806cd Forgot to remove LNM 2024-07-11 18:12:31 +02:00
Leonard Hecker
f0db29ddd1 Fix exception correctness, CRLF translation, C1 pictograms, Line wrap, Wide chars 2024-07-11 18:08:36 +02:00
Leonard Hecker
f35127abbe Improve comments 2024-07-11 18:04:44 +02:00
Leonard Hecker
7b5b3657f2 Merge remote-tracking branch 'origin/main' into dev/lhecker/goodbye-vtengine 2024-07-11 03:19:02 +02:00
Leonard Hecker
669a530204 Don't use RIS for cls 2024-07-08 01:49:19 +02:00
Leonard Hecker
c8d0a934fb Revert incorrect ASB fix 2024-07-08 01:28:44 +02:00
Leonard Hecker
edd4c758d7 Fix GetVtIo for alt buffers 2024-07-08 01:26:12 +02:00
Leonard Hecker
e9e6227a4e Better GetVtIo naming, Less VtEngine code, Fix control char sanitization 2024-07-08 00:00:49 +02:00
Leonard Hecker
9535e49518 Uh... 2024-07-07 22:34:50 +02:00
Leonard Hecker
9c42bfcb8d Fix VtIoTests 2024-07-07 19:34:34 +02:00
Leonard Hecker
29766619f0 Remove DECCOLM from DA1 2024-07-07 19:31:38 +02:00
Leonard Hecker
92087fbd23 AuditMode fix 2024-07-07 18:25:03 +02:00
Leonard Hecker
c92c5cf716 Fixed cls->RIS translation, Way faster pwsh cls 2024-07-07 18:07:34 +02:00
Leonard Hecker
eaf364ec37 Fix the injection of ConPTY modes 2024-07-07 17:42:57 +02:00
Leonard Hecker
976343e1ab Fix the cursor reset when entering the ASB twice 2024-07-07 17:42:09 +02:00
Leonard Hecker
d3b4592b71 Remove IsConsolePty 2024-07-07 17:42:01 +02:00
Leonard Hecker
6b254492be Remove AlwaysAcceptC1 2024-07-07 17:38:57 +02:00
Leonard Hecker
b78e5bc044 Fix x86 build, Fix a missing FMT_COMPILE 2024-07-07 03:44:48 +02:00
Leonard Hecker
1047ed4f1a A way simpler ReadConsoleOutputWImplHelper, because why not 2024-07-06 03:42:15 +02:00
Leonard Hecker
a138b284a4 Fix spelling 2024-07-06 02:52:02 +02:00
Leonard Hecker
a3f5d59ebe x86 wants its __stdcall back 2024-07-05 18:48:11 +02:00
Leonard Hecker
6a13e5aabc A common C++ L 2024-07-05 17:58:41 +02:00
Leonard Hecker
0e51acd5d9 Fix conhost feature tests 2024-07-05 16:01:16 +02:00
Leonard Hecker
c3d5ce14dd Fix AuditMode, Unstage some unnecessary changes 2024-07-05 14:27:50 +02:00
Leonard Hecker
3e346f1174 Fix synchronous reads 2024-07-05 14:24:39 +02:00
Leonard Hecker
7c6c1824eb Spel 2024-07-04 17:33:15 +02:00
Leonard Hecker
7462a20097 Fix midi crash, Fix double DSR response 2024-07-04 17:28:50 +02:00
Leonard Hecker
78ae6dda80 OVERLAPPED for stdin, Timeouts for PSEUDOCONSOLE_INHERIT_CURSOR 2024-07-04 17:28:12 +02:00
Leonard Hecker
8de1a7543f Move OVERLAPPED helpers to Types 2024-07-04 17:27:17 +02:00
Leonard Hecker
9e3d3983d5 Some leftover VtEngine logic 2024-07-04 17:26:28 +02:00
Leonard Hecker
db2b762221 Remove references to deleted tests 2024-07-04 03:49:40 +02:00
Leonard Hecker
eec17eb805 Slightly more elegant formatAttributes 2024-07-04 03:48:13 +02:00
Leonard Hecker
89b4c09e50 Fix support for far 2024-07-04 02:46:10 +02:00
Leonard Hecker
73e3dd31b5 Fix support for vim 2024-07-04 02:33:20 +02:00
Leonard Hecker
656a0492d7 Restore win32im/focus on RIS, Clean up some stuff 2024-07-03 23:21:57 +02:00
Leonard Hecker
17d39eb3eb Fix build, Fix tests 2024-07-03 04:02:36 +02:00
Leonard Hecker
fec64563a0 A minor ConPTY refactoring: Goodbye VtEngine Edition 2024-07-03 03:36:02 +02:00
163 changed files with 2159 additions and 21944 deletions

View File

@@ -65,8 +65,8 @@ GETTEXTLENGTH
Hashtable
HIGHCONTRASTON
HIGHCONTRASTW
hinternet
HIGHQUALITYSCALE
hinternet
HINTERNET
hotkeys
href
@@ -155,6 +155,7 @@ NOTIFYBYPOS
NOTIFYICON
NOTIFYICONDATA
ntprivapi
NTSYSCALLAPI
numr
oaidl
ocidl

View File

@@ -1,20 +1,15 @@
AAAAA
AAAAAAAAAAAAA
AAAAAABBBBBBCCC
AAAAABBBBBBCCC
abcd
abcd
ABCDEFGHIJ
abcdefghijk
ABCDEFGHIJKLMNO
abcdefghijklmnop
ABCDEFGHIJKLMNOPQRS
ABCDEFGHIJKLMNOPQRST
ABCG
ABE
abf
BBBBB
BBBBBBBB
BBBBBCCC
BBBBCCCCC
BBGGRR
@@ -29,10 +24,8 @@ QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQ
QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQQ
QQQQQQQQQQABCDEFGHIJPQRST
QQQQQQQQQQABCDEFGHIJPQRSTQQQQQQQQQQ
qrstuvwxyz
qwerty
qwertyuiopasdfg
YYYYYYYDDDDDDDDDDD
ZAAZZ
ZABBZ
ZBAZZ

View File

@@ -17,7 +17,6 @@ ADDALIAS
ADDREF
ADDSTRING
ADDTOOL
AFew
AFill
AFX
AHelper
@@ -66,7 +65,6 @@ ARRAYSIZE
ARROWKEYS
asan
ASBSET
asdfghjkl
ASetting
ASingle
ASYNCDONTCARE
@@ -125,6 +123,7 @@ BKCOLOR
BKGND
Bksp
Blt
blu
BLUESCROLL
bmi
BODGY
@@ -145,7 +144,6 @@ buffersize
buflen
buildtransitive
buildsystems
burriter
BValue
bytebuffer
cac
@@ -210,7 +208,6 @@ cmw
CNL
cnn
Codeflow
codenav
codepages
codepath
coinit
@@ -362,6 +359,7 @@ DBGFONTS
DBGOUTPUT
dbh
dblclk
Dcd
DColor
DCOLORVALUE
dcommon
@@ -379,7 +377,7 @@ DECALN
DECANM
DECARM
DECAUPSS
DECAWM
decawm
DECBI
DECBKM
DECCARA
@@ -421,6 +419,7 @@ DECPCCM
DECPCTERM
DECPS
DECRARA
decrc
DECRC
DECREQTPARM
DECRLM
@@ -436,6 +435,7 @@ DECRSPS
decrst
DECSACE
DECSASD
decsc
DECSC
DECSCA
DECSCNM
@@ -475,7 +475,6 @@ DEFPUSHBUTTON
defterm
DELAYLOAD
DELETEONRELEASE
Delt
depersist
deprioritized
deserializers
@@ -556,7 +555,6 @@ Efast
efghijklmn
EHsc
EINS
EJO
ELEMENTNOTAVAILABLE
EMPTYBOX
enabledelayedexpansion
@@ -626,7 +624,6 @@ FINDDOWN
FINDREGEX
FINDSTRINGEXACT
FINDUP
FIter
FITZPATRICK
FIXEDFILEINFO
Flg
@@ -723,6 +720,7 @@ GETWHEELSCROLLLINES
Gfun
gfx
GGI
GHgh
GHIJK
GHIJKL
gitcheckin
@@ -948,7 +946,6 @@ LCONTROL
LCTRL
lcx
LEFTALIGN
libpopcnt
libsancov
libtickit
licate
@@ -1046,7 +1043,6 @@ MAPBITMAP
MAPVIRTUALKEY
MAPVK
MAXDIMENSTRING
maxing
MAXSHORT
maxval
maxversiontested
@@ -1504,7 +1500,6 @@ REGSTR
RELBINPATH
remoting
renamer
renderengine
rendersize
reparented
reparenting
@@ -1844,7 +1839,6 @@ Trd
TREX
triaged
triaging
Tribool
TRIMZEROHEADINGS
trx
tsa
@@ -1938,7 +1932,6 @@ uxtheme
Vanara
vararg
vclib
vcprintf
vcxitems
vectorize
VERCTRL
@@ -1982,7 +1975,6 @@ vtio
vtmode
vtpipeterm
vtpt
vtrenderer
VTRGB
VTRGBTo
vtseq
@@ -2004,6 +1996,7 @@ wcswidth
wddm
wddmcon
WDDMCONSOLECONTEXT
WDK
wdm
webpage
websites
@@ -2071,7 +2064,6 @@ Winperf
WInplace
winres
winrt
wintelnet
winternl
winuser
winuserp
@@ -2172,7 +2164,6 @@ XTWINOPS
xunit
xutr
XVIRTUALSCREEN
XWalk
yact
YCast
YCENTER
@@ -2181,7 +2172,6 @@ YLimit
YPan
YSubstantial
YVIRTUALSCREEN
YWalk
Zab
zabcd
Zabcdefghijklmn
@@ -2189,6 +2179,5 @@ Zabcdefghijklmnopqrstuvwxyz
ZCmd
ZCtrl
ZWJs
zxcvbnm
ZYXWVU
ZYXWVUTd

101
NOTICE.md
View File

@@ -48,107 +48,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
## chromium/base/numerics
**Source**: [https://github.com/chromium/chromium/tree/master/base/numerics](https://github.com/chromium/chromium/tree/master/base/numerics)
### License
```
Copyright 2015 The Chromium Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
```
## kimwalisch/libpopcnt
**Source**: [https://github.com/kimwalisch/libpopcnt](https://github.com/kimwalisch/libpopcnt)
### License
```
BSD 2-Clause License
Copyright (c) 2016 - 2019, Kim Walisch
Copyright (c) 2016 - 2019, Wojciech Muła
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
```
## dynamic_bitset
**Source**: [https://github.com/pinam45/dynamic_bitset](https://github.com/pinam45/dynamic_bitset)
### License
```
MIT License
Copyright (c) 2019 Maxime Pinard
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
## \{fmt\}
**Source**: [https://github.com/fmtlib/fmt](https://github.com/fmtlib/fmt)

View File

@@ -131,7 +131,6 @@ EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "InteractivityWin32", "src\interactivity\win32\lib\win32.LIB.vcxproj", "{06EC74CB-9A12-429C-B551-8532EC964726}"
ProjectSection(ProjectDependencies) = postProject
{1C959542-BAC2-4E55-9A6D-13251914CBB9} = {1C959542-BAC2-4E55-9A6D-13251914CBB9}
{990F2657-8580-4828-943F-5DD657D11842} = {990F2657-8580-4828-943F-5DD657D11842}
{AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F} = {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}
EndProjectSection
EndProject
@@ -140,14 +139,9 @@ EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "InteractivityBase", "src\interactivity\base\lib\InteractivityBase.vcxproj", "{06EC74CB-9A12-429C-B551-8562EC964846}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Interactivity.Win32.Tests.Unit", "src\interactivity\win32\ut_interactivity_win32\Interactivity.Win32.UnitTests.vcxproj", "{D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}"
ProjectSection(ProjectDependencies) = postProject
{990F2657-8580-4828-943F-5DD657D11842} = {990F2657-8580-4828-943F-5DD657D11842}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CloseTest", "src\tools\closetest\CloseTest.vcxproj", "{C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RendererVt", "src\renderer\vt\lib\vt.vcxproj", "{990F2657-8580-4828-943F-5DD657D11842}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VtPipeTerm", "src\tools\vtpipeterm\VtPipeTerm.vcxproj", "{814DBDDE-894E-4327-A6E1-740504850098}"
ProjectSection(ProjectDependencies) = postProject
{9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B} = {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}
@@ -157,8 +151,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ConEchoKey", "src\tools\ech
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Types", "src\types\lib\types.vcxproj", "{18D09A24-8240-42D6-8CB6-236EEE820263}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RendererVt.unittest", "src\renderer\vt\ut_lib\vt.unittest.vcxproj", "{990F2657-8580-4828-943F-5DD657D11843}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BufferOut", "src\buffer\out\lib\bufferout.vcxproj", "{0CF235BD-2DA0-407E-90EE-C467E8BBC714}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalConnection", "src\cascadia\TerminalConnection\TerminalConnection.vcxproj", "{CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}"
@@ -1117,29 +1109,6 @@ Global
{C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Release|x64.Build.0 = Release|x64
{C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Release|x86.ActiveCfg = Release|Win32
{C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Release|x86.Build.0 = Release|Win32
{990F2657-8580-4828-943F-5DD657D11842}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32
{990F2657-8580-4828-943F-5DD657D11842}.AuditMode|ARM64.ActiveCfg = Release|ARM64
{990F2657-8580-4828-943F-5DD657D11842}.AuditMode|x64.ActiveCfg = Release|x64
{990F2657-8580-4828-943F-5DD657D11842}.AuditMode|x86.ActiveCfg = Release|Win32
{990F2657-8580-4828-943F-5DD657D11842}.Debug|Any CPU.ActiveCfg = Debug|Win32
{990F2657-8580-4828-943F-5DD657D11842}.Debug|ARM64.ActiveCfg = Debug|ARM64
{990F2657-8580-4828-943F-5DD657D11842}.Debug|ARM64.Build.0 = Debug|ARM64
{990F2657-8580-4828-943F-5DD657D11842}.Debug|x64.ActiveCfg = Debug|x64
{990F2657-8580-4828-943F-5DD657D11842}.Debug|x64.Build.0 = Debug|x64
{990F2657-8580-4828-943F-5DD657D11842}.Debug|x86.ActiveCfg = Debug|Win32
{990F2657-8580-4828-943F-5DD657D11842}.Debug|x86.Build.0 = Debug|Win32
{990F2657-8580-4828-943F-5DD657D11842}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32
{990F2657-8580-4828-943F-5DD657D11842}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64
{990F2657-8580-4828-943F-5DD657D11842}.Fuzzing|x64.ActiveCfg = Fuzzing|x64
{990F2657-8580-4828-943F-5DD657D11842}.Fuzzing|x64.Build.0 = Fuzzing|x64
{990F2657-8580-4828-943F-5DD657D11842}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32
{990F2657-8580-4828-943F-5DD657D11842}.Release|Any CPU.ActiveCfg = Release|Win32
{990F2657-8580-4828-943F-5DD657D11842}.Release|ARM64.ActiveCfg = Release|ARM64
{990F2657-8580-4828-943F-5DD657D11842}.Release|ARM64.Build.0 = Release|ARM64
{990F2657-8580-4828-943F-5DD657D11842}.Release|x64.ActiveCfg = Release|x64
{990F2657-8580-4828-943F-5DD657D11842}.Release|x64.Build.0 = Release|x64
{990F2657-8580-4828-943F-5DD657D11842}.Release|x86.ActiveCfg = Release|Win32
{990F2657-8580-4828-943F-5DD657D11842}.Release|x86.Build.0 = Release|Win32
{814DBDDE-894E-4327-A6E1-740504850098}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32
{814DBDDE-894E-4327-A6E1-740504850098}.AuditMode|ARM64.ActiveCfg = Release|ARM64
{814DBDDE-894E-4327-A6E1-740504850098}.AuditMode|x64.ActiveCfg = Release|x64
@@ -1210,28 +1179,6 @@ Global
{18D09A24-8240-42D6-8CB6-236EEE820263}.Release|x64.Build.0 = Release|x64
{18D09A24-8240-42D6-8CB6-236EEE820263}.Release|x86.ActiveCfg = Release|Win32
{18D09A24-8240-42D6-8CB6-236EEE820263}.Release|x86.Build.0 = Release|Win32
{990F2657-8580-4828-943F-5DD657D11843}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32
{990F2657-8580-4828-943F-5DD657D11843}.AuditMode|ARM64.ActiveCfg = Release|ARM64
{990F2657-8580-4828-943F-5DD657D11843}.AuditMode|x64.ActiveCfg = Release|x64
{990F2657-8580-4828-943F-5DD657D11843}.AuditMode|x86.ActiveCfg = Release|Win32
{990F2657-8580-4828-943F-5DD657D11843}.Debug|Any CPU.ActiveCfg = Debug|Win32
{990F2657-8580-4828-943F-5DD657D11843}.Debug|ARM64.ActiveCfg = Debug|ARM64
{990F2657-8580-4828-943F-5DD657D11843}.Debug|ARM64.Build.0 = Debug|ARM64
{990F2657-8580-4828-943F-5DD657D11843}.Debug|x64.ActiveCfg = Debug|x64
{990F2657-8580-4828-943F-5DD657D11843}.Debug|x64.Build.0 = Debug|x64
{990F2657-8580-4828-943F-5DD657D11843}.Debug|x86.ActiveCfg = Debug|Win32
{990F2657-8580-4828-943F-5DD657D11843}.Debug|x86.Build.0 = Debug|Win32
{990F2657-8580-4828-943F-5DD657D11843}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32
{990F2657-8580-4828-943F-5DD657D11843}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64
{990F2657-8580-4828-943F-5DD657D11843}.Fuzzing|x64.ActiveCfg = Fuzzing|x64
{990F2657-8580-4828-943F-5DD657D11843}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32
{990F2657-8580-4828-943F-5DD657D11843}.Release|Any CPU.ActiveCfg = Release|Win32
{990F2657-8580-4828-943F-5DD657D11843}.Release|ARM64.ActiveCfg = Release|ARM64
{990F2657-8580-4828-943F-5DD657D11843}.Release|ARM64.Build.0 = Release|ARM64
{990F2657-8580-4828-943F-5DD657D11843}.Release|x64.ActiveCfg = Release|x64
{990F2657-8580-4828-943F-5DD657D11843}.Release|x64.Build.0 = Release|x64
{990F2657-8580-4828-943F-5DD657D11843}.Release|x86.ActiveCfg = Release|Win32
{990F2657-8580-4828-943F-5DD657D11843}.Release|x86.Build.0 = Release|Win32
{0CF235BD-2DA0-407E-90EE-C467E8BBC714}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32
{0CF235BD-2DA0-407E-90EE-C467E8BBC714}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64
{0CF235BD-2DA0-407E-90EE-C467E8BBC714}.AuditMode|ARM64.Build.0 = AuditMode|ARM64
@@ -2444,11 +2391,9 @@ Global
{06EC74CB-9A12-429C-B551-8562EC964846} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB}
{D3B92829-26CB-411A-BDA2-7F5DA3D25DD4} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB}
{C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB} = {A10C4720-DCA4-4640-9749-67F4314F527C}
{990F2657-8580-4828-943F-5DD657D11842} = {05500DEF-2294-41E3-AF9A-24E580B82836}
{814DBDDE-894E-4327-A6E1-740504850098} = {A10C4720-DCA4-4640-9749-67F4314F527C}
{814CBEEE-894E-4327-A6E1-740504850098} = {A10C4720-DCA4-4640-9749-67F4314F527C}
{18D09A24-8240-42D6-8CB6-236EEE820263} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
{990F2657-8580-4828-943F-5DD657D11843} = {05500DEF-2294-41E3-AF9A-24E580B82836}
{0CF235BD-2DA0-407E-90EE-C467E8BBC714} = {1E4A062E-293B-4817-B20D-BF16B979E350}
{CA5CAD1A-C46D-4588-B1C0-40F31AE9100B} = {59840756-302F-44DF-AA47-441A9D673202}
{CA5CAD1A-ABCD-429C-B551-8562EC954746} = {9921CA0A-320C-4460-8623-3A3196E7F4CB}

View File

@@ -1,27 +0,0 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,17 +0,0 @@
### Notes for Future Maintainers
This was originally imported by @miniksa in January 2020.
The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme.
Please update the provenance information in that file when ingesting an updated version of the dependent library.
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards.
## What should be done to update this in the future?
1. Go to chromium/chromium repository on GitHub.
2. Take the entire contents of the base/numerics directory wholesale and drop it in the base/numerics directory here.
3. Don't change anything about it.
4. Validate that the license in the root of the repository didn't change and update it if so. It is sitting in the same directory as this readme.
If it changed dramatically, ensure that it is still compatible with our license scheme. Also update the NOTICE file in the root of our repository to declare the third-party usage.
5. Submit the pull.

View File

@@ -1,28 +0,0 @@
# Copyright (c) 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# This is a dependency-free, header-only, library, and it needs to stay that
# way to facilitate pulling it into various third-party projects. So, this
# file is here to protect against accidentally introducing external
# dependencies or depending on internal implementation details.
source_set("base_numerics") {
visibility = [ "//base/*" ]
sources = [
"checked_math_impl.h",
"clamped_math_impl.h",
"safe_conversions_arm_impl.h",
"safe_conversions_impl.h",
"safe_math_arm_impl.h",
"safe_math_clang_gcc_impl.h",
"safe_math_shared_impl.h",
]
public = [
"checked_math.h",
"clamped_math.h",
"math_constants.h",
"ranges.h",
"safe_conversions.h",
"safe_math.h",
]
}

View File

@@ -1,7 +0,0 @@
# This is a dependency-free, header-only, library, and it needs to stay that
# way to facilitate pulling it into various third-party projects. So, this
# file is here to protect against accidentally introducing dependencies.
include_rules = [
"-base",
"+base/numerics",
]

View File

@@ -1,5 +0,0 @@
jschuh@chromium.org
tsepez@chromium.org
# COMPONENT: Internals

View File

@@ -1,409 +0,0 @@
# `base/numerics`
This directory contains a dependency-free, header-only library of templates
providing well-defined semantics for safely and performantly handling a variety
of numeric operations, including most common arithmetic operations and
conversions.
The public API is broken out into the following header files:
* `checked_math.h` contains the `CheckedNumeric` template class and helper
functions for performing arithmetic and conversion operations that detect
errors and boundary conditions (e.g. overflow, truncation, etc.).
* `clamped_math.h` contains the `ClampedNumeric` template class and
helper functions for performing fast, clamped (i.e. [non-sticky](#notsticky)
saturating) arithmetic operations and conversions.
* `safe_conversions.h` contains the `StrictNumeric` template class and
a collection of custom casting templates and helper functions for safely
converting between a range of numeric types.
* `safe_math.h` includes all of the previously mentioned headers.
*** aside
**Note:** The `Numeric` template types implicitly convert from C numeric types
and `Numeric` templates that are convertable to an underlying C numeric type.
The conversion priority for `Numeric` type coercions is:
* `StrictNumeric` coerces to `ClampedNumeric` and `CheckedNumeric`
* `ClampedNumeric` coerces to `CheckedNumeric`
***
[TOC]
## Common patterns and use-cases
The following covers the preferred style for the most common uses of this
library. Please don't cargo-cult from anywhere else. 😉
### Performing checked arithmetic type conversions
The `checked_cast` template converts between arbitrary arithmetic types, and is
used for cases where a conversion failure should result in program termination:
```cpp
// Crash if signed_value is out of range for buff_size.
size_t buff_size = checked_cast<size_t>(signed_value);
```
### Performing saturated (clamped) arithmetic type conversions
The `saturated_cast` template converts between arbitrary arithmetic types, and
is used in cases where an out-of-bounds source value should be saturated to the
corresponding maximum or minimum of the destination type:
```cpp
// Convert from float with saturation to INT_MAX, INT_MIN, or 0 for NaN.
int int_value = saturated_cast<int>(floating_point_value);
```
### Enforcing arithmetic type conversions at compile-time
The `strict_cast` emits code that is identical to `static_cast`. However,
provides static checks that will cause a compilation failure if the
destination type cannot represent the full range of the source type:
```cpp
// Throw a compiler error if byte_value is changed to an out-of-range-type.
int int_value = strict_cast<int>(byte_value);
```
You can also enforce these compile-time restrictions on function parameters by
using the `StrictNumeric` template:
```cpp
// Throw a compiler error if the size argument cannot be represented by a
// size_t (e.g. passing an int will fail to compile).
bool AllocateBuffer(void** buffer, StrictCast<size_t> size);
```
### Comparing values between arbitrary arithmetic types
Both the `StrictNumeric` and `ClampedNumeric` types provide well defined
comparisons between arbitrary arithmetic types. This allows you to perform
comparisons that are not legal or would trigger compiler warnings or errors
under the normal arithmetic promotion rules:
```cpp
bool foo(unsigned value, int upper_bound) {
// Converting to StrictNumeric allows this comparison to work correctly.
if (MakeStrictNum(value) >= upper_bound)
return false;
```
*** note
**Warning:** Do not perform manual conversions using the comparison operators.
Instead, use the cast templates described in the previous sections, or the
constexpr template functions `IsValueInRangeForNumericType` and
`IsTypeInRangeForNumericType`, as these templates properly handle the full range
of corner cases and employ various optimizations.
***
### Calculating a buffer size (checked arithmetic)
When making exact calculations—such as for buffer lengths—it's often necessary
to know when those calculations trigger an overflow, undefined behavior, or
other boundary conditions. The `CheckedNumeric` template does this by storing
a bit determining whether or not some arithmetic operation has occured that
would put the variable in an "invalid" state. Attempting to extract the value
from a variable in an invalid state will trigger a check/trap condition, that
by default will result in process termination.
Here's an example of a buffer calculation using a `CheckedNumeric` type (note:
the AssignIfValid method will trigger a compile error if the result is ignored).
```cpp
// Calculate the buffer size and detect if an overflow occurs.
size_t size;
if (!CheckAdd(kHeaderSize, CheckMul(count, kItemSize)).AssignIfValid(&size)) {
// Handle an overflow error...
}
```
### Calculating clamped coordinates (non-sticky saturating arithmetic)
Certain classes of calculations—such as coordinate calculations—require
well-defined semantics that always produce a valid result on boundary
conditions. The `ClampedNumeric` template addresses this by providing
performant, non-sticky saturating arithmetic operations.
Here's an example of using a `ClampedNumeric` to calculate an operation
insetting a rectangle.
```cpp
// Use clamped arithmetic since inset calculations might overflow.
void Rect::Inset(int left, int top, int right, int bottom) {
origin_ += Vector2d(left, top);
set_width(ClampSub(width(), ClampAdd(left, right)));
set_height(ClampSub(height(), ClampAdd(top, bottom)));
}
```
*** note
<a name="notsticky"></a>
The `ClampedNumeric` type is not "sticky", which means the saturation is not
retained across individual operations. As such, one arithmetic operation may
result in a saturated value, while the next operation may then "desaturate"
the value. Here's an example:
```cpp
ClampedNumeric<int> value = INT_MAX;
++value; // value is still INT_MAX, due to saturation.
--value; // value is now (INT_MAX - 1), because saturation is not sticky.
```
***
## Conversion functions and StrictNumeric<> in safe_conversions.h
This header includes a collection of helper `constexpr` templates for safely
performing a range of conversions, assignments, and tests.
### Safe casting templates
* `as_signed()` - Returns the supplied integral value as a signed type of
the same width.
* `as_unsigned()` - Returns the supplied integral value as an unsigned type
of the same width.
* `checked_cast<>()` - Analogous to `static_cast<>` for numeric types, except
that by default it will trigger a crash on an out-of-bounds conversion (e.g.
overflow, underflow, NaN to integral) or a compile error if the conversion
error can be detected at compile time. The crash handler can be overridden
to perform a behavior other than crashing.
* `saturated_cast<>()` - Analogous to `static_cast` for numeric types, except
that it returns a saturated result when the specified numeric conversion
would otherwise overflow or underflow. An NaN source returns 0 by
default, but can be overridden to return a different result.
* `strict_cast<>()` - Analogous to `static_cast` for numeric types, except
this causes a compile failure if the destination type is not large
enough to contain any value in the source type. It performs no runtime
checking and thus introduces no runtime overhead.
### Other helper and conversion functions
* `IsValueInRangeForNumericType<>()` - A convenience function that returns
true if the type supplied as the template parameter can represent the value
passed as an argument to the function.
* `IsTypeInRangeForNumericType<>()` - A convenience function that evaluates
entirely at compile-time and returns true if the destination type (first
template parameter) can represent the full range of the source type
(second template parameter).
* `IsValueNegative()` - A convenience function that will accept any
arithmetic type as an argument and will return whether the value is less
than zero. Unsigned types always return false.
* `SafeUnsignedAbs()` - Returns the absolute value of the supplied integer
parameter as an unsigned result (thus avoiding an overflow if the value
is the signed, two's complement minimum).
### StrictNumeric<>
`StrictNumeric<>` is a wrapper type that performs assignments and copies via
the `strict_cast` template, and can perform valid arithmetic comparisons
across any range of arithmetic types. `StrictNumeric` is the return type for
values extracted from a `CheckedNumeric` class instance. The raw numeric value
is extracted via `static_cast` to the underlying type or any type with
sufficient range to represent the underlying type.
* `MakeStrictNum()` - Creates a new `StrictNumeric` from the underlying type
of the supplied arithmetic or StrictNumeric type.
* `SizeT` - Alias for `StrictNumeric<size_t>`.
## CheckedNumeric<> in checked_math.h
`CheckedNumeric<>` implements all the logic and operators for detecting integer
boundary conditions such as overflow, underflow, and invalid conversions.
The `CheckedNumeric` type implicitly converts from floating point and integer
data types, and contains overloads for basic arithmetic operations (i.e.: `+`,
`-`, `*`, `/` for all types and `%`, `<<`, `>>`, `&`, `|`, `^` for integers).
However, *the [variadic template functions
](#CheckedNumeric_in-checked_math_h-Non_member-helper-functions)
are the prefered API,* as they remove type ambiguities and help prevent a number
of common errors. The variadic functions can also be more performant, as they
eliminate redundant expressions that are unavoidable with the with the operator
overloads. (Ideally the compiler should optimize those away, but better to avoid
them in the first place.)
Type promotions are a slightly modified version of the [standard C/C++ numeric
promotions
](http://en.cppreference.com/w/cpp/language/implicit_conversion#Numeric_promotions)
with the two differences being that *there is no default promotion to int*
and *bitwise logical operations always return an unsigned of the wider type.*
### Members
The unary negation, increment, and decrement operators are supported, along
with the following unary arithmetic methods, which return a new
`CheckedNumeric` as a result of the operation:
* `Abs()` - Absolute value.
* `UnsignedAbs()` - Absolute value as an equal-width unsigned underlying type
(valid for only integral types).
* `Max()` - Returns whichever is greater of the current instance or argument.
The underlying return type is whichever has the greatest magnitude.
* `Min()` - Returns whichever is lowest of the current instance or argument.
The underlying return type is whichever has can represent the lowest
number in the smallest width (e.g. int8_t over unsigned, int over
int8_t, and float over int).
The following are for converting `CheckedNumeric` instances:
* `type` - The underlying numeric type.
* `AssignIfValid()` - Assigns the underlying value to the supplied
destination pointer if the value is currently valid and within the
range supported by the destination type. Returns true on success.
* `Cast<>()` - Instance method returning a `CheckedNumeric` derived from
casting the current instance to a `CheckedNumeric` of the supplied
destination type.
*** aside
The following member functions return a `StrictNumeric`, which is valid for
comparison and assignment operations, but will trigger a compile failure on
attempts to assign to a type of insufficient range. The underlying value can
be extracted by an explicit `static_cast` to the underlying type or any type
with sufficient range to represent the underlying type.
***
* `IsValid()` - Returns true if the underlying numeric value is valid (i.e.
has not wrapped or saturated and is not the result of an invalid
conversion).
* `ValueOrDie()` - Returns the underlying value. If the state is not valid
this call will trigger a crash by default (but may be overridden by
supplying an alternate handler to the template).
* `ValueOrDefault()` - Returns the current value, or the supplied default if
the state is not valid (but will not crash).
**Comparison operators are explicitly not provided** for `CheckedNumeric`
types because they could result in a crash if the type is not in a valid state.
Patterns like the following should be used instead:
```cpp
// Either input or padding (or both) may be arbitrary sizes.
size_t buff_size;
if (!CheckAdd(input, padding, kHeaderLength).AssignIfValid(&buff_size) ||
buff_size >= kMaxBuffer) {
// Handle an error...
} else {
// Do stuff on success...
}
```
### Non-member helper functions
The following variadic convenience functions, which accept standard arithmetic
or `CheckedNumeric` types, perform arithmetic operations, and return a
`CheckedNumeric` result. The supported functions are:
* `CheckAdd()` - Addition.
* `CheckSub()` - Subtraction.
* `CheckMul()` - Multiplication.
* `CheckDiv()` - Division.
* `CheckMod()` - Modulus (integer only).
* `CheckLsh()` - Left integer shift (integer only).
* `CheckRsh()` - Right integer shift (integer only).
* `CheckAnd()` - Bitwise AND (integer only with unsigned result).
* `CheckOr()` - Bitwise OR (integer only with unsigned result).
* `CheckXor()` - Bitwise XOR (integer only with unsigned result).
* `CheckMax()` - Maximum of supplied arguments.
* `CheckMin()` - Minimum of supplied arguments.
The following wrapper functions can be used to avoid the template
disambiguator syntax when converting a destination type.
* `IsValidForType<>()` in place of: `a.template IsValid<>()`
* `ValueOrDieForType<>()` in place of: `a.template ValueOrDie<>()`
* `ValueOrDefaultForType<>()` in place of: `a.template ValueOrDefault<>()`
The following general utility methods is are useful for converting from
arithmetic types to `CheckedNumeric` types:
* `MakeCheckedNum()` - Creates a new `CheckedNumeric` from the underlying type
of the supplied arithmetic or directly convertible type.
## ClampedNumeric<> in clamped_math.h
`ClampedNumeric<>` implements all the logic and operators for clamped
(non-sticky saturating) arithmetic operations and conversions. The
`ClampedNumeric` type implicitly converts back and forth between floating point
and integer data types, saturating on assignment as appropriate. It contains
overloads for basic arithmetic operations (i.e.: `+`, `-`, `*`, `/` for
all types and `%`, `<<`, `>>`, `&`, `|`, `^` for integers) along with comparison
operators for arithmetic types of any size. However, *the [variadic template
functions
](#ClampedNumeric_in-clamped_math_h-Non_member-helper-functions)
are the prefered API,* as they remove type ambiguities and help prevent
a number of common errors. The variadic functions can also be more performant,
as they eliminate redundant expressions that are unavoidable with the operator
overloads. (Ideally the compiler should optimize those away, but better to avoid
them in the first place.)
Type promotions are a slightly modified version of the [standard C/C++ numeric
promotions
](http://en.cppreference.com/w/cpp/language/implicit_conversion#Numeric_promotions)
with the two differences being that *there is no default promotion to int*
and *bitwise logical operations always return an unsigned of the wider type.*
*** aside
Most arithmetic operations saturate normally, to the numeric limit in the
direction of the sign. The potentially unusual cases are:
* **Division:** Division by zero returns the saturated limit in the direction
of sign of the dividend (first argument). The one exception is 0/0, which
returns zero (although logically is NaN).
* **Modulus:** Division by zero returns the dividend (first argument).
* **Left shift:** Non-zero values saturate in the direction of the signed
limit (max/min), even for shifts larger than the bit width. 0 shifted any
amount results in 0.
* **Right shift:** Negative values saturate to -1. Positive or 0 saturates
to 0. (Effectively just an unbounded arithmetic-right-shift.)
* **Bitwise operations:** No saturation; bit pattern is identical to
non-saturated bitwise operations.
***
### Members
The unary negation, increment, and decrement operators are supported, along
with the following unary arithmetic methods, which return a new
`ClampedNumeric` as a result of the operation:
* `Abs()` - Absolute value.
* `UnsignedAbs()` - Absolute value as an equal-width unsigned underlying type
(valid for only integral types).
* `Max()` - Returns whichever is greater of the current instance or argument.
The underlying return type is whichever has the greatest magnitude.
* `Min()` - Returns whichever is lowest of the current instance or argument.
The underlying return type is whichever has can represent the lowest
number in the smallest width (e.g. int8_t over unsigned, int over
int8_t, and float over int).
The following are for converting `ClampedNumeric` instances:
* `type` - The underlying numeric type.
* `RawValue()` - Returns the raw value as the underlying arithmetic type. This
is useful when e.g. assigning to an auto type or passing as a deduced
template parameter.
* `Cast<>()` - Instance method returning a `ClampedNumeric` derived from
casting the current instance to a `ClampedNumeric` of the supplied
destination type.
### Non-member helper functions
The following variadic convenience functions, which accept standard arithmetic
or `ClampedNumeric` types, perform arithmetic operations, and return a
`ClampedNumeric` result. The supported functions are:
* `ClampAdd()` - Addition.
* `ClampSub()` - Subtraction.
* `ClampMul()` - Multiplication.
* `ClampDiv()` - Division.
* `ClampMod()` - Modulus (integer only).
* `ClampLsh()` - Left integer shift (integer only).
* `ClampRsh()` - Right integer shift (integer only).
* `ClampAnd()` - Bitwise AND (integer only with unsigned result).
* `ClampOr()` - Bitwise OR (integer only with unsigned result).
* `ClampXor()` - Bitwise XOR (integer only with unsigned result).
* `ClampMax()` - Maximum of supplied arguments.
* `ClampMin()` - Minimum of supplied arguments.
The following is a general utility method that is useful for converting
to a `ClampedNumeric` type:
* `MakeClampedNum()` - Creates a new `ClampedNumeric` from the underlying type
of the supplied arithmetic or directly convertible type.

View File

@@ -1,393 +0,0 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_CHECKED_MATH_H_
#define BASE_NUMERICS_CHECKED_MATH_H_
#include <stddef.h>
#include <limits>
#include <type_traits>
#include "base/numerics/checked_math_impl.h"
namespace base {
namespace internal {
template <typename T>
class CheckedNumeric {
static_assert(std::is_arithmetic<T>::value,
"CheckedNumeric<T>: T must be a numeric type.");
public:
using type = T;
constexpr CheckedNumeric() = default;
// Copy constructor.
template <typename Src>
constexpr CheckedNumeric(const CheckedNumeric<Src>& rhs)
: state_(rhs.state_.value(), rhs.IsValid()) {}
template <typename Src>
friend class CheckedNumeric;
// This is not an explicit constructor because we implicitly upgrade regular
// numerics to CheckedNumerics to make them easier to use.
template <typename Src>
constexpr CheckedNumeric(Src value) // NOLINT(runtime/explicit)
: state_(value) {
static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
}
// This is not an explicit constructor because we want a seamless conversion
// from StrictNumeric types.
template <typename Src>
constexpr CheckedNumeric(
StrictNumeric<Src> value) // NOLINT(runtime/explicit)
: state_(static_cast<Src>(value)) {}
// IsValid() - The public API to test if a CheckedNumeric is currently valid.
// A range checked destination type can be supplied using the Dst template
// parameter.
template <typename Dst = T>
constexpr bool IsValid() const {
return state_.is_valid() &&
IsValueInRangeForNumericType<Dst>(state_.value());
}
// AssignIfValid(Dst) - Assigns the underlying value if it is currently valid
// and is within the range supported by the destination type. Returns true if
// successful and false otherwise.
template <typename Dst>
#if defined(__clang__) || defined(__GNUC__)
__attribute__((warn_unused_result))
#elif defined(_MSC_VER)
_Check_return_
#endif
constexpr bool
AssignIfValid(Dst* result) const {
return BASE_NUMERICS_LIKELY(IsValid<Dst>())
? ((*result = static_cast<Dst>(state_.value())), true)
: false;
}
// ValueOrDie() - The primary accessor for the underlying value. If the
// current state is not valid it will CHECK and crash.
// A range checked destination type can be supplied using the Dst template
// parameter, which will trigger a CHECK if the value is not in bounds for
// the destination.
// The CHECK behavior can be overridden by supplying a handler as a
// template parameter, for test code, etc. However, the handler cannot access
// the underlying value, and it is not available through other means.
template <typename Dst = T, class CheckHandler = CheckOnFailure>
constexpr StrictNumeric<Dst> ValueOrDie() const {
return BASE_NUMERICS_LIKELY(IsValid<Dst>())
? static_cast<Dst>(state_.value())
: CheckHandler::template HandleFailure<Dst>();
}
// ValueOrDefault(T default_value) - A convenience method that returns the
// current value if the state is valid, and the supplied default_value for
// any other state.
// A range checked destination type can be supplied using the Dst template
// parameter. WARNING: This function may fail to compile or CHECK at runtime
// if the supplied default_value is not within range of the destination type.
template <typename Dst = T, typename Src>
constexpr StrictNumeric<Dst> ValueOrDefault(const Src default_value) const {
return BASE_NUMERICS_LIKELY(IsValid<Dst>())
? static_cast<Dst>(state_.value())
: checked_cast<Dst>(default_value);
}
// Returns a checked numeric of the specified type, cast from the current
// CheckedNumeric. If the current state is invalid or the destination cannot
// represent the result then the returned CheckedNumeric will be invalid.
template <typename Dst>
constexpr CheckedNumeric<typename UnderlyingType<Dst>::type> Cast() const {
return *this;
}
// This friend method is available solely for providing more detailed logging
// in the the tests. Do not implement it in production code, because the
// underlying values may change at any time.
template <typename U>
friend U GetNumericValueForTest(const CheckedNumeric<U>& src);
// Prototypes for the supported arithmetic operator overloads.
template <typename Src>
constexpr CheckedNumeric& operator+=(const Src rhs);
template <typename Src>
constexpr CheckedNumeric& operator-=(const Src rhs);
template <typename Src>
constexpr CheckedNumeric& operator*=(const Src rhs);
template <typename Src>
constexpr CheckedNumeric& operator/=(const Src rhs);
template <typename Src>
constexpr CheckedNumeric& operator%=(const Src rhs);
template <typename Src>
constexpr CheckedNumeric& operator<<=(const Src rhs);
template <typename Src>
constexpr CheckedNumeric& operator>>=(const Src rhs);
template <typename Src>
constexpr CheckedNumeric& operator&=(const Src rhs);
template <typename Src>
constexpr CheckedNumeric& operator|=(const Src rhs);
template <typename Src>
constexpr CheckedNumeric& operator^=(const Src rhs);
constexpr CheckedNumeric operator-() const {
// The negation of two's complement int min is int min, so we simply
// check for that in the constexpr case.
// We use an optimized code path for a known run-time variable.
return MustTreatAsConstexpr(state_.value()) || !std::is_signed<T>::value ||
std::is_floating_point<T>::value
? CheckedNumeric<T>(
NegateWrapper(state_.value()),
IsValid() && (!std::is_signed<T>::value ||
std::is_floating_point<T>::value ||
NegateWrapper(state_.value()) !=
std::numeric_limits<T>::lowest()))
: FastRuntimeNegate();
}
constexpr CheckedNumeric operator~() const {
return CheckedNumeric<decltype(InvertWrapper(T()))>(
InvertWrapper(state_.value()), IsValid());
}
constexpr CheckedNumeric Abs() const {
return !IsValueNegative(state_.value()) ? *this : -*this;
}
template <typename U>
constexpr CheckedNumeric<typename MathWrapper<CheckedMaxOp, T, U>::type> Max(
const U rhs) const {
using R = typename UnderlyingType<U>::type;
using result_type = typename MathWrapper<CheckedMaxOp, T, U>::type;
// TODO(jschuh): This can be converted to the MathOp version and remain
// constexpr once we have C++14 support.
return CheckedNumeric<result_type>(
static_cast<result_type>(
IsGreater<T, R>::Test(state_.value(), Wrapper<U>::value(rhs))
? state_.value()
: Wrapper<U>::value(rhs)),
state_.is_valid() && Wrapper<U>::is_valid(rhs));
}
template <typename U>
constexpr CheckedNumeric<typename MathWrapper<CheckedMinOp, T, U>::type> Min(
const U rhs) const {
using R = typename UnderlyingType<U>::type;
using result_type = typename MathWrapper<CheckedMinOp, T, U>::type;
// TODO(jschuh): This can be converted to the MathOp version and remain
// constexpr once we have C++14 support.
return CheckedNumeric<result_type>(
static_cast<result_type>(
IsLess<T, R>::Test(state_.value(), Wrapper<U>::value(rhs))
? state_.value()
: Wrapper<U>::value(rhs)),
state_.is_valid() && Wrapper<U>::is_valid(rhs));
}
// This function is available only for integral types. It returns an unsigned
// integer of the same width as the source type, containing the absolute value
// of the source, and properly handling signed min.
constexpr CheckedNumeric<typename UnsignedOrFloatForSize<T>::type>
UnsignedAbs() const {
return CheckedNumeric<typename UnsignedOrFloatForSize<T>::type>(
SafeUnsignedAbs(state_.value()), state_.is_valid());
}
constexpr CheckedNumeric& operator++() {
*this += 1;
return *this;
}
constexpr CheckedNumeric operator++(int) {
CheckedNumeric value = *this;
*this += 1;
return value;
}
constexpr CheckedNumeric& operator--() {
*this -= 1;
return *this;
}
constexpr CheckedNumeric operator--(int) {
CheckedNumeric value = *this;
*this -= 1;
return value;
}
// These perform the actual math operations on the CheckedNumerics.
// Binary arithmetic operations.
template <template <typename, typename, typename> class M,
typename L,
typename R>
static constexpr CheckedNumeric MathOp(const L lhs, const R rhs) {
using Math = typename MathWrapper<M, L, R>::math;
T result = 0;
bool is_valid =
Wrapper<L>::is_valid(lhs) && Wrapper<R>::is_valid(rhs) &&
Math::Do(Wrapper<L>::value(lhs), Wrapper<R>::value(rhs), &result);
return CheckedNumeric<T>(result, is_valid);
}
// Assignment arithmetic operations.
template <template <typename, typename, typename> class M, typename R>
constexpr CheckedNumeric& MathOp(const R rhs) {
using Math = typename MathWrapper<M, T, R>::math;
T result = 0; // Using T as the destination saves a range check.
bool is_valid = state_.is_valid() && Wrapper<R>::is_valid(rhs) &&
Math::Do(state_.value(), Wrapper<R>::value(rhs), &result);
*this = CheckedNumeric<T>(result, is_valid);
return *this;
}
private:
CheckedNumericState<T> state_;
CheckedNumeric FastRuntimeNegate() const {
T result;
bool success = CheckedSubOp<T, T>::Do(T(0), state_.value(), &result);
return CheckedNumeric<T>(result, IsValid() && success);
}
template <typename Src>
constexpr CheckedNumeric(Src value, bool is_valid)
: state_(value, is_valid) {}
// These wrappers allow us to handle state the same way for both
// CheckedNumeric and POD arithmetic types.
template <typename Src>
struct Wrapper {
static constexpr bool is_valid(Src) { return true; }
static constexpr Src value(Src value) { return value; }
};
template <typename Src>
struct Wrapper<CheckedNumeric<Src>> {
static constexpr bool is_valid(const CheckedNumeric<Src> v) {
return v.IsValid();
}
static constexpr Src value(const CheckedNumeric<Src> v) {
return v.state_.value();
}
};
template <typename Src>
struct Wrapper<StrictNumeric<Src>> {
static constexpr bool is_valid(const StrictNumeric<Src>) { return true; }
static constexpr Src value(const StrictNumeric<Src> v) {
return static_cast<Src>(v);
}
};
};
// Convenience functions to avoid the ugly template disambiguator syntax.
template <typename Dst, typename Src>
constexpr bool IsValidForType(const CheckedNumeric<Src> value) {
return value.template IsValid<Dst>();
}
template <typename Dst, typename Src>
constexpr StrictNumeric<Dst> ValueOrDieForType(
const CheckedNumeric<Src> value) {
return value.template ValueOrDie<Dst>();
}
template <typename Dst, typename Src, typename Default>
constexpr StrictNumeric<Dst> ValueOrDefaultForType(
const CheckedNumeric<Src> value,
const Default default_value) {
return value.template ValueOrDefault<Dst>(default_value);
}
// Convience wrapper to return a new CheckedNumeric from the provided arithmetic
// or CheckedNumericType.
template <typename T>
constexpr CheckedNumeric<typename UnderlyingType<T>::type> MakeCheckedNum(
const T value) {
return value;
}
// These implement the variadic wrapper for the math operations.
template <template <typename, typename, typename> class M,
typename L,
typename R>
constexpr CheckedNumeric<typename MathWrapper<M, L, R>::type> CheckMathOp(
const L lhs,
const R rhs) {
using Math = typename MathWrapper<M, L, R>::math;
return CheckedNumeric<typename Math::result_type>::template MathOp<M>(lhs,
rhs);
}
// General purpose wrapper template for arithmetic operations.
template <template <typename, typename, typename> class M,
typename L,
typename R,
typename... Args>
constexpr CheckedNumeric<typename ResultType<M, L, R, Args...>::type>
CheckMathOp(const L lhs, const R rhs, const Args... args) {
return CheckMathOp<M>(CheckMathOp<M>(lhs, rhs), args...);
}
BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Add, +, +=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Sub, -, -=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Mul, *, *=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Div, /, /=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Mod, %, %=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Lsh, <<, <<=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Rsh, >>, >>=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, And, &, &=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Or, |, |=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Xor, ^, ^=)
BASE_NUMERIC_ARITHMETIC_VARIADIC(Checked, Check, Max)
BASE_NUMERIC_ARITHMETIC_VARIADIC(Checked, Check, Min)
// These are some extra StrictNumeric operators to support simple pointer
// arithmetic with our result types. Since wrapping on a pointer is always
// bad, we trigger the CHECK condition here.
template <typename L, typename R>
L* operator+(L* lhs, const StrictNumeric<R> rhs) {
uintptr_t result = CheckAdd(reinterpret_cast<uintptr_t>(lhs),
CheckMul(sizeof(L), static_cast<R>(rhs)))
.template ValueOrDie<uintptr_t>();
return reinterpret_cast<L*>(result);
}
template <typename L, typename R>
L* operator-(L* lhs, const StrictNumeric<R> rhs) {
uintptr_t result = CheckSub(reinterpret_cast<uintptr_t>(lhs),
CheckMul(sizeof(L), static_cast<R>(rhs)))
.template ValueOrDie<uintptr_t>();
return reinterpret_cast<L*>(result);
}
} // namespace internal
using internal::CheckedNumeric;
using internal::IsValidForType;
using internal::ValueOrDieForType;
using internal::ValueOrDefaultForType;
using internal::MakeCheckedNum;
using internal::CheckMax;
using internal::CheckMin;
using internal::CheckAdd;
using internal::CheckSub;
using internal::CheckMul;
using internal::CheckDiv;
using internal::CheckMod;
using internal::CheckLsh;
using internal::CheckRsh;
using internal::CheckAnd;
using internal::CheckOr;
using internal::CheckXor;
} // namespace base
#endif // BASE_NUMERICS_CHECKED_MATH_H_

View File

@@ -1,567 +0,0 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_CHECKED_MATH_IMPL_H_
#define BASE_NUMERICS_CHECKED_MATH_IMPL_H_
#include <stddef.h>
#include <stdint.h>
#include <climits>
#include <cmath>
#include <cstdlib>
#include <limits>
#include <type_traits>
#include "base/numerics/safe_conversions.h"
#include "base/numerics/safe_math_shared_impl.h"
namespace base {
namespace internal {
template <typename T>
constexpr bool CheckedAddImpl(T x, T y, T* result) {
static_assert(std::is_integral<T>::value, "Type must be integral");
// Since the value of x+y is undefined if we have a signed type, we compute
// it using the unsigned type of the same size.
using UnsignedDst = typename std::make_unsigned<T>::type;
using SignedDst = typename std::make_signed<T>::type;
UnsignedDst ux = static_cast<UnsignedDst>(x);
UnsignedDst uy = static_cast<UnsignedDst>(y);
UnsignedDst uresult = static_cast<UnsignedDst>(ux + uy);
*result = static_cast<T>(uresult);
// Addition is valid if the sign of (x + y) is equal to either that of x or
// that of y.
return (std::is_signed<T>::value)
? static_cast<SignedDst>((uresult ^ ux) & (uresult ^ uy)) >= 0
: uresult >= uy; // Unsigned is either valid or underflow.
}
template <typename T, typename U, class Enable = void>
struct CheckedAddOp {};
template <typename T, typename U>
struct CheckedAddOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
// TODO(jschuh) Make this "constexpr if" once we're C++17.
if (CheckedAddFastOp<T, U>::is_supported)
return CheckedAddFastOp<T, U>::Do(x, y, result);
// Double the underlying type up to a full machine word.
using FastPromotion = typename FastIntegerArithmeticPromotion<T, U>::type;
using Promotion =
typename std::conditional<(IntegerBitsPlusSign<FastPromotion>::value >
IntegerBitsPlusSign<intptr_t>::value),
typename BigEnoughPromotion<T, U>::type,
FastPromotion>::type;
// Fail if either operand is out of range for the promoted type.
// TODO(jschuh): This could be made to work for a broader range of values.
if (BASE_NUMERICS_UNLIKELY(!IsValueInRangeForNumericType<Promotion>(x) ||
!IsValueInRangeForNumericType<Promotion>(y))) {
return false;
}
Promotion presult = {};
bool is_valid = true;
if (IsIntegerArithmeticSafe<Promotion, T, U>::value) {
presult = static_cast<Promotion>(x) + static_cast<Promotion>(y);
} else {
is_valid = CheckedAddImpl(static_cast<Promotion>(x),
static_cast<Promotion>(y), &presult);
}
*result = static_cast<V>(presult);
return is_valid && IsValueInRangeForNumericType<V>(presult);
}
};
template <typename T>
constexpr bool CheckedSubImpl(T x, T y, T* result) {
static_assert(std::is_integral<T>::value, "Type must be integral");
// Since the value of x+y is undefined if we have a signed type, we compute
// it using the unsigned type of the same size.
using UnsignedDst = typename std::make_unsigned<T>::type;
using SignedDst = typename std::make_signed<T>::type;
UnsignedDst ux = static_cast<UnsignedDst>(x);
UnsignedDst uy = static_cast<UnsignedDst>(y);
UnsignedDst uresult = static_cast<UnsignedDst>(ux - uy);
*result = static_cast<T>(uresult);
// Subtraction is valid if either x and y have same sign, or (x-y) and x have
// the same sign.
return (std::is_signed<T>::value)
? static_cast<SignedDst>((uresult ^ ux) & (ux ^ uy)) >= 0
: x >= y;
}
template <typename T, typename U, class Enable = void>
struct CheckedSubOp {};
template <typename T, typename U>
struct CheckedSubOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
// TODO(jschuh) Make this "constexpr if" once we're C++17.
if (CheckedSubFastOp<T, U>::is_supported)
return CheckedSubFastOp<T, U>::Do(x, y, result);
// Double the underlying type up to a full machine word.
using FastPromotion = typename FastIntegerArithmeticPromotion<T, U>::type;
using Promotion =
typename std::conditional<(IntegerBitsPlusSign<FastPromotion>::value >
IntegerBitsPlusSign<intptr_t>::value),
typename BigEnoughPromotion<T, U>::type,
FastPromotion>::type;
// Fail if either operand is out of range for the promoted type.
// TODO(jschuh): This could be made to work for a broader range of values.
if (BASE_NUMERICS_UNLIKELY(!IsValueInRangeForNumericType<Promotion>(x) ||
!IsValueInRangeForNumericType<Promotion>(y))) {
return false;
}
Promotion presult = {};
bool is_valid = true;
if (IsIntegerArithmeticSafe<Promotion, T, U>::value) {
presult = static_cast<Promotion>(x) - static_cast<Promotion>(y);
} else {
is_valid = CheckedSubImpl(static_cast<Promotion>(x),
static_cast<Promotion>(y), &presult);
}
*result = static_cast<V>(presult);
return is_valid && IsValueInRangeForNumericType<V>(presult);
}
};
template <typename T>
constexpr bool CheckedMulImpl(T x, T y, T* result) {
static_assert(std::is_integral<T>::value, "Type must be integral");
// Since the value of x*y is potentially undefined if we have a signed type,
// we compute it using the unsigned type of the same size.
using UnsignedDst = typename std::make_unsigned<T>::type;
using SignedDst = typename std::make_signed<T>::type;
const UnsignedDst ux = SafeUnsignedAbs(x);
const UnsignedDst uy = SafeUnsignedAbs(y);
UnsignedDst uresult = static_cast<UnsignedDst>(ux * uy);
const bool is_negative =
std::is_signed<T>::value && static_cast<SignedDst>(x ^ y) < 0;
*result = is_negative ? 0 - uresult : uresult;
// We have a fast out for unsigned identity or zero on the second operand.
// After that it's an unsigned overflow check on the absolute value, with
// a +1 bound for a negative result.
return uy <= UnsignedDst(!std::is_signed<T>::value || is_negative) ||
ux <= (std::numeric_limits<T>::max() + UnsignedDst(is_negative)) / uy;
}
template <typename T, typename U, class Enable = void>
struct CheckedMulOp {};
template <typename T, typename U>
struct CheckedMulOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
// TODO(jschuh) Make this "constexpr if" once we're C++17.
if (CheckedMulFastOp<T, U>::is_supported)
return CheckedMulFastOp<T, U>::Do(x, y, result);
using Promotion = typename FastIntegerArithmeticPromotion<T, U>::type;
// Verify the destination type can hold the result (always true for 0).
if (BASE_NUMERICS_UNLIKELY((!IsValueInRangeForNumericType<Promotion>(x) ||
!IsValueInRangeForNumericType<Promotion>(y)) &&
x && y)) {
return false;
}
Promotion presult = {};
bool is_valid = true;
if (CheckedMulFastOp<Promotion, Promotion>::is_supported) {
// The fast op may be available with the promoted type.
is_valid = CheckedMulFastOp<Promotion, Promotion>::Do(x, y, &presult);
} else if (IsIntegerArithmeticSafe<Promotion, T, U>::value) {
presult = static_cast<Promotion>(x) * static_cast<Promotion>(y);
} else {
is_valid = CheckedMulImpl(static_cast<Promotion>(x),
static_cast<Promotion>(y), &presult);
}
*result = static_cast<V>(presult);
return is_valid && IsValueInRangeForNumericType<V>(presult);
}
};
// Division just requires a check for a zero denominator or an invalid negation
// on signed min/-1.
template <typename T, typename U, class Enable = void>
struct CheckedDivOp {};
template <typename T, typename U>
struct CheckedDivOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
if (BASE_NUMERICS_UNLIKELY(!y))
return false;
// The overflow check can be compiled away if we don't have the exact
// combination of types needed to trigger this case.
using Promotion = typename BigEnoughPromotion<T, U>::type;
if (BASE_NUMERICS_UNLIKELY(
(std::is_signed<T>::value && std::is_signed<U>::value &&
IsTypeInRangeForNumericType<T, Promotion>::value &&
static_cast<Promotion>(x) ==
std::numeric_limits<Promotion>::lowest() &&
y == static_cast<U>(-1)))) {
return false;
}
// This branch always compiles away if the above branch wasn't removed.
if (BASE_NUMERICS_UNLIKELY((!IsValueInRangeForNumericType<Promotion>(x) ||
!IsValueInRangeForNumericType<Promotion>(y)) &&
x)) {
return false;
}
Promotion presult = Promotion(x) / Promotion(y);
*result = static_cast<V>(presult);
return IsValueInRangeForNumericType<V>(presult);
}
};
template <typename T, typename U, class Enable = void>
struct CheckedModOp {};
template <typename T, typename U>
struct CheckedModOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
using Promotion = typename BigEnoughPromotion<T, U>::type;
if (BASE_NUMERICS_LIKELY(y)) {
Promotion presult = static_cast<Promotion>(x) % static_cast<Promotion>(y);
*result = static_cast<Promotion>(presult);
return IsValueInRangeForNumericType<V>(presult);
}
return false;
}
};
template <typename T, typename U, class Enable = void>
struct CheckedLshOp {};
// Left shift. Shifts less than 0 or greater than or equal to the number
// of bits in the promoted type are undefined. Shifts of negative values
// are undefined. Otherwise it is defined when the result fits.
template <typename T, typename U>
struct CheckedLshOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = T;
template <typename V>
static constexpr bool Do(T x, U shift, V* result) {
// Disallow negative numbers and verify the shift is in bounds.
if (BASE_NUMERICS_LIKELY(!IsValueNegative(x) &&
as_unsigned(shift) <
as_unsigned(std::numeric_limits<T>::digits))) {
// Shift as unsigned to avoid undefined behavior.
*result = static_cast<V>(as_unsigned(x) << shift);
// If the shift can be reversed, we know it was valid.
return *result >> shift == x;
}
// Handle the legal corner-case of a full-width signed shift of zero.
return std::is_signed<T>::value && !x &&
as_unsigned(shift) == as_unsigned(std::numeric_limits<T>::digits);
}
};
template <typename T, typename U, class Enable = void>
struct CheckedRshOp {};
// Right shift. Shifts less than 0 or greater than or equal to the number
// of bits in the promoted type are undefined. Otherwise, it is always defined,
// but a right shift of a negative value is implementation-dependent.
template <typename T, typename U>
struct CheckedRshOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = T;
template <typename V>
static bool Do(T x, U shift, V* result) {
// Use the type conversion push negative values out of range.
if (BASE_NUMERICS_LIKELY(as_unsigned(shift) <
IntegerBitsPlusSign<T>::value)) {
T tmp = x >> shift;
*result = static_cast<V>(tmp);
return IsValueInRangeForNumericType<V>(tmp);
}
return false;
}
};
template <typename T, typename U, class Enable = void>
struct CheckedAndOp {};
// For simplicity we support only unsigned integer results.
template <typename T, typename U>
struct CheckedAndOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename std::make_unsigned<
typename MaxExponentPromotion<T, U>::type>::type;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
result_type tmp = static_cast<result_type>(x) & static_cast<result_type>(y);
*result = static_cast<V>(tmp);
return IsValueInRangeForNumericType<V>(tmp);
}
};
template <typename T, typename U, class Enable = void>
struct CheckedOrOp {};
// For simplicity we support only unsigned integers.
template <typename T, typename U>
struct CheckedOrOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename std::make_unsigned<
typename MaxExponentPromotion<T, U>::type>::type;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
result_type tmp = static_cast<result_type>(x) | static_cast<result_type>(y);
*result = static_cast<V>(tmp);
return IsValueInRangeForNumericType<V>(tmp);
}
};
template <typename T, typename U, class Enable = void>
struct CheckedXorOp {};
// For simplicity we support only unsigned integers.
template <typename T, typename U>
struct CheckedXorOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename std::make_unsigned<
typename MaxExponentPromotion<T, U>::type>::type;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
result_type tmp = static_cast<result_type>(x) ^ static_cast<result_type>(y);
*result = static_cast<V>(tmp);
return IsValueInRangeForNumericType<V>(tmp);
}
};
// Max doesn't really need to be implemented this way because it can't fail,
// but it makes the code much cleaner to use the MathOp wrappers.
template <typename T, typename U, class Enable = void>
struct CheckedMaxOp {};
template <typename T, typename U>
struct CheckedMaxOp<
T,
U,
typename std::enable_if<std::is_arithmetic<T>::value &&
std::is_arithmetic<U>::value>::type> {
using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
result_type tmp = IsGreater<T, U>::Test(x, y) ? static_cast<result_type>(x)
: static_cast<result_type>(y);
*result = static_cast<V>(tmp);
return IsValueInRangeForNumericType<V>(tmp);
}
};
// Min doesn't really need to be implemented this way because it can't fail,
// but it makes the code much cleaner to use the MathOp wrappers.
template <typename T, typename U, class Enable = void>
struct CheckedMinOp {};
template <typename T, typename U>
struct CheckedMinOp<
T,
U,
typename std::enable_if<std::is_arithmetic<T>::value &&
std::is_arithmetic<U>::value>::type> {
using result_type = typename LowestValuePromotion<T, U>::type;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
result_type tmp = IsLess<T, U>::Test(x, y) ? static_cast<result_type>(x)
: static_cast<result_type>(y);
*result = static_cast<V>(tmp);
return IsValueInRangeForNumericType<V>(tmp);
}
};
// This is just boilerplate that wraps the standard floating point arithmetic.
// A macro isn't the nicest solution, but it beats rewriting these repeatedly.
#define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP) \
template <typename T, typename U> \
struct Checked##NAME##Op< \
T, U, \
typename std::enable_if<std::is_floating_point<T>::value || \
std::is_floating_point<U>::value>::type> { \
using result_type = typename MaxExponentPromotion<T, U>::type; \
template <typename V> \
static constexpr bool Do(T x, U y, V* result) { \
using Promotion = typename MaxExponentPromotion<T, U>::type; \
Promotion presult = x OP y; \
*result = static_cast<V>(presult); \
return IsValueInRangeForNumericType<V>(presult); \
} \
};
BASE_FLOAT_ARITHMETIC_OPS(Add, +)
BASE_FLOAT_ARITHMETIC_OPS(Sub, -)
BASE_FLOAT_ARITHMETIC_OPS(Mul, *)
BASE_FLOAT_ARITHMETIC_OPS(Div, /)
#undef BASE_FLOAT_ARITHMETIC_OPS
// Floats carry around their validity state with them, but integers do not. So,
// we wrap the underlying value in a specialization in order to hide that detail
// and expose an interface via accessors.
enum NumericRepresentation {
NUMERIC_INTEGER,
NUMERIC_FLOATING,
NUMERIC_UNKNOWN
};
template <typename NumericType>
struct GetNumericRepresentation {
static const NumericRepresentation value =
std::is_integral<NumericType>::value
? NUMERIC_INTEGER
: (std::is_floating_point<NumericType>::value ? NUMERIC_FLOATING
: NUMERIC_UNKNOWN);
};
template <typename T,
NumericRepresentation type = GetNumericRepresentation<T>::value>
class CheckedNumericState {};
// Integrals require quite a bit of additional housekeeping to manage state.
template <typename T>
class CheckedNumericState<T, NUMERIC_INTEGER> {
private:
// is_valid_ precedes value_ because member intializers in the constructors
// are evaluated in field order, and is_valid_ must be read when initializing
// value_.
bool is_valid_;
T value_;
// Ensures that a type conversion does not trigger undefined behavior.
template <typename Src>
static constexpr T WellDefinedConversionOrZero(const Src value,
const bool is_valid) {
using SrcType = typename internal::UnderlyingType<Src>::type;
return (std::is_integral<SrcType>::value || is_valid)
? static_cast<T>(value)
: static_cast<T>(0);
}
public:
template <typename Src, NumericRepresentation type>
friend class CheckedNumericState;
constexpr CheckedNumericState() : is_valid_(true), value_(0) {}
template <typename Src>
constexpr CheckedNumericState(Src value, bool is_valid)
: is_valid_(is_valid && IsValueInRangeForNumericType<T>(value)),
value_(WellDefinedConversionOrZero(value, is_valid_)) {
static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
}
// Copy constructor.
template <typename Src>
constexpr CheckedNumericState(const CheckedNumericState<Src>& rhs)
: is_valid_(rhs.IsValid()),
value_(WellDefinedConversionOrZero(rhs.value(), is_valid_)) {}
template <typename Src>
constexpr explicit CheckedNumericState(Src value)
: is_valid_(IsValueInRangeForNumericType<T>(value)),
value_(WellDefinedConversionOrZero(value, is_valid_)) {}
constexpr bool is_valid() const { return is_valid_; }
constexpr T value() const { return value_; }
};
// Floating points maintain their own validity, but need translation wrappers.
template <typename T>
class CheckedNumericState<T, NUMERIC_FLOATING> {
private:
T value_;
// Ensures that a type conversion does not trigger undefined behavior.
template <typename Src>
static constexpr T WellDefinedConversionOrNaN(const Src value,
const bool is_valid) {
using SrcType = typename internal::UnderlyingType<Src>::type;
return (StaticDstRangeRelationToSrcRange<T, SrcType>::value ==
NUMERIC_RANGE_CONTAINED ||
is_valid)
? static_cast<T>(value)
: std::numeric_limits<T>::quiet_NaN();
}
public:
template <typename Src, NumericRepresentation type>
friend class CheckedNumericState;
constexpr CheckedNumericState() : value_(0.0) {}
template <typename Src>
constexpr CheckedNumericState(Src value, bool is_valid)
: value_(WellDefinedConversionOrNaN(value, is_valid)) {}
template <typename Src>
constexpr explicit CheckedNumericState(Src value)
: value_(WellDefinedConversionOrNaN(
value,
IsValueInRangeForNumericType<T>(value))) {}
// Copy constructor.
template <typename Src>
constexpr CheckedNumericState(const CheckedNumericState<Src>& rhs)
: value_(WellDefinedConversionOrNaN(
rhs.value(),
rhs.is_valid() && IsValueInRangeForNumericType<T>(rhs.value()))) {}
constexpr bool is_valid() const {
// Written this way because std::isfinite is not reliably constexpr.
return MustTreatAsConstexpr(value_)
? value_ <= std::numeric_limits<T>::max() &&
value_ >= std::numeric_limits<T>::lowest()
: std::isfinite(value_);
}
constexpr T value() const { return value_; }
};
} // namespace internal
} // namespace base
#endif // BASE_NUMERICS_CHECKED_MATH_IMPL_H_

View File

@@ -1,264 +0,0 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_CLAMPED_MATH_H_
#define BASE_NUMERICS_CLAMPED_MATH_H_
#include <stddef.h>
#include <limits>
#include <type_traits>
#include "base/numerics/clamped_math_impl.h"
namespace base {
namespace internal {
template <typename T>
class ClampedNumeric {
static_assert(std::is_arithmetic<T>::value,
"ClampedNumeric<T>: T must be a numeric type.");
public:
using type = T;
constexpr ClampedNumeric() : value_(0) {}
// Copy constructor.
template <typename Src>
constexpr ClampedNumeric(const ClampedNumeric<Src>& rhs)
: value_(saturated_cast<T>(rhs.value_)) {}
template <typename Src>
friend class ClampedNumeric;
// This is not an explicit constructor because we implicitly upgrade regular
// numerics to ClampedNumerics to make them easier to use.
template <typename Src>
constexpr ClampedNumeric(Src value) // NOLINT(runtime/explicit)
: value_(saturated_cast<T>(value)) {
static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
}
// This is not an explicit constructor because we want a seamless conversion
// from StrictNumeric types.
template <typename Src>
constexpr ClampedNumeric(
StrictNumeric<Src> value) // NOLINT(runtime/explicit)
: value_(saturated_cast<T>(static_cast<Src>(value))) {}
// Returns a ClampedNumeric of the specified type, cast from the current
// ClampedNumeric, and saturated to the destination type.
template <typename Dst>
constexpr ClampedNumeric<typename UnderlyingType<Dst>::type> Cast() const {
return *this;
}
// Prototypes for the supported arithmetic operator overloads.
template <typename Src>
constexpr ClampedNumeric& operator+=(const Src rhs);
template <typename Src>
constexpr ClampedNumeric& operator-=(const Src rhs);
template <typename Src>
constexpr ClampedNumeric& operator*=(const Src rhs);
template <typename Src>
constexpr ClampedNumeric& operator/=(const Src rhs);
template <typename Src>
constexpr ClampedNumeric& operator%=(const Src rhs);
template <typename Src>
constexpr ClampedNumeric& operator<<=(const Src rhs);
template <typename Src>
constexpr ClampedNumeric& operator>>=(const Src rhs);
template <typename Src>
constexpr ClampedNumeric& operator&=(const Src rhs);
template <typename Src>
constexpr ClampedNumeric& operator|=(const Src rhs);
template <typename Src>
constexpr ClampedNumeric& operator^=(const Src rhs);
constexpr ClampedNumeric operator-() const {
// The negation of two's complement int min is int min, so that's the
// only overflow case where we will saturate.
return ClampedNumeric<T>(SaturatedNegWrapper(value_));
}
constexpr ClampedNumeric operator~() const {
return ClampedNumeric<decltype(InvertWrapper(T()))>(InvertWrapper(value_));
}
constexpr ClampedNumeric Abs() const {
// The negation of two's complement int min is int min, so that's the
// only overflow case where we will saturate.
return ClampedNumeric<T>(SaturatedAbsWrapper(value_));
}
template <typename U>
constexpr ClampedNumeric<typename MathWrapper<ClampedMaxOp, T, U>::type> Max(
const U rhs) const {
using result_type = typename MathWrapper<ClampedMaxOp, T, U>::type;
return ClampedNumeric<result_type>(
ClampedMaxOp<T, U>::Do(value_, Wrapper<U>::value(rhs)));
}
template <typename U>
constexpr ClampedNumeric<typename MathWrapper<ClampedMinOp, T, U>::type> Min(
const U rhs) const {
using result_type = typename MathWrapper<ClampedMinOp, T, U>::type;
return ClampedNumeric<result_type>(
ClampedMinOp<T, U>::Do(value_, Wrapper<U>::value(rhs)));
}
// This function is available only for integral types. It returns an unsigned
// integer of the same width as the source type, containing the absolute value
// of the source, and properly handling signed min.
constexpr ClampedNumeric<typename UnsignedOrFloatForSize<T>::type>
UnsignedAbs() const {
return ClampedNumeric<typename UnsignedOrFloatForSize<T>::type>(
SafeUnsignedAbs(value_));
}
constexpr ClampedNumeric& operator++() {
*this += 1;
return *this;
}
constexpr ClampedNumeric operator++(int) {
ClampedNumeric value = *this;
*this += 1;
return value;
}
constexpr ClampedNumeric& operator--() {
*this -= 1;
return *this;
}
constexpr ClampedNumeric operator--(int) {
ClampedNumeric value = *this;
*this -= 1;
return value;
}
// These perform the actual math operations on the ClampedNumerics.
// Binary arithmetic operations.
template <template <typename, typename, typename> class M,
typename L,
typename R>
static constexpr ClampedNumeric MathOp(const L lhs, const R rhs) {
using Math = typename MathWrapper<M, L, R>::math;
return ClampedNumeric<T>(
Math::template Do<T>(Wrapper<L>::value(lhs), Wrapper<R>::value(rhs)));
}
// Assignment arithmetic operations.
template <template <typename, typename, typename> class M, typename R>
constexpr ClampedNumeric& MathOp(const R rhs) {
using Math = typename MathWrapper<M, T, R>::math;
*this =
ClampedNumeric<T>(Math::template Do<T>(value_, Wrapper<R>::value(rhs)));
return *this;
}
template <typename Dst>
constexpr operator Dst() const {
return saturated_cast<typename ArithmeticOrUnderlyingEnum<Dst>::type>(
value_);
}
// This method extracts the raw integer value without saturating it to the
// destination type as the conversion operator does. This is useful when
// e.g. assigning to an auto type or passing as a deduced template parameter.
constexpr T RawValue() const { return value_; }
private:
T value_;
// These wrappers allow us to handle state the same way for both
// ClampedNumeric and POD arithmetic types.
template <typename Src>
struct Wrapper {
static constexpr Src value(Src value) {
return static_cast<typename UnderlyingType<Src>::type>(value);
}
};
};
// Convience wrapper to return a new ClampedNumeric from the provided arithmetic
// or ClampedNumericType.
template <typename T>
constexpr ClampedNumeric<typename UnderlyingType<T>::type> MakeClampedNum(
const T value) {
return value;
}
#if !BASE_NUMERICS_DISABLE_OSTREAM_OPERATORS
// Overload the ostream output operator to make logging work nicely.
template <typename T>
std::ostream& operator<<(std::ostream& os, const ClampedNumeric<T>& value) {
os << static_cast<T>(value);
return os;
}
#endif
// These implement the variadic wrapper for the math operations.
template <template <typename, typename, typename> class M,
typename L,
typename R>
constexpr ClampedNumeric<typename MathWrapper<M, L, R>::type> ClampMathOp(
const L lhs,
const R rhs) {
using Math = typename MathWrapper<M, L, R>::math;
return ClampedNumeric<typename Math::result_type>::template MathOp<M>(lhs,
rhs);
}
// General purpose wrapper template for arithmetic operations.
template <template <typename, typename, typename> class M,
typename L,
typename R,
typename... Args>
constexpr ClampedNumeric<typename ResultType<M, L, R, Args...>::type>
ClampMathOp(const L lhs, const R rhs, const Args... args) {
return ClampMathOp<M>(ClampMathOp<M>(lhs, rhs), args...);
}
BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Add, +, +=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Sub, -, -=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Mul, *, *=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Div, /, /=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Mod, %, %=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Lsh, <<, <<=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Rsh, >>, >>=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, And, &, &=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Or, |, |=)
BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Xor, ^, ^=)
BASE_NUMERIC_ARITHMETIC_VARIADIC(Clamped, Clamp, Max)
BASE_NUMERIC_ARITHMETIC_VARIADIC(Clamped, Clamp, Min)
BASE_NUMERIC_COMPARISON_OPERATORS(Clamped, IsLess, <)
BASE_NUMERIC_COMPARISON_OPERATORS(Clamped, IsLessOrEqual, <=)
BASE_NUMERIC_COMPARISON_OPERATORS(Clamped, IsGreater, >)
BASE_NUMERIC_COMPARISON_OPERATORS(Clamped, IsGreaterOrEqual, >=)
BASE_NUMERIC_COMPARISON_OPERATORS(Clamped, IsEqual, ==)
BASE_NUMERIC_COMPARISON_OPERATORS(Clamped, IsNotEqual, !=)
} // namespace internal
using internal::ClampedNumeric;
using internal::MakeClampedNum;
using internal::ClampMax;
using internal::ClampMin;
using internal::ClampAdd;
using internal::ClampSub;
using internal::ClampMul;
using internal::ClampDiv;
using internal::ClampMod;
using internal::ClampLsh;
using internal::ClampRsh;
using internal::ClampAnd;
using internal::ClampOr;
using internal::ClampXor;
} // namespace base
#endif // BASE_NUMERICS_CLAMPED_MATH_H_

View File

@@ -1,341 +0,0 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_CLAMPED_MATH_IMPL_H_
#define BASE_NUMERICS_CLAMPED_MATH_IMPL_H_
#include <stddef.h>
#include <stdint.h>
#include <climits>
#include <cmath>
#include <cstdlib>
#include <limits>
#include <type_traits>
#include "base/numerics/checked_math.h"
#include "base/numerics/safe_conversions.h"
#include "base/numerics/safe_math_shared_impl.h"
namespace base {
namespace internal {
template <typename T,
typename std::enable_if<std::is_integral<T>::value &&
std::is_signed<T>::value>::type* = nullptr>
constexpr T SaturatedNegWrapper(T value) {
return MustTreatAsConstexpr(value) || !ClampedNegFastOp<T>::is_supported
? (NegateWrapper(value) != std::numeric_limits<T>::lowest()
? NegateWrapper(value)
: std::numeric_limits<T>::max())
: ClampedNegFastOp<T>::Do(value);
}
template <typename T,
typename std::enable_if<std::is_integral<T>::value &&
!std::is_signed<T>::value>::type* = nullptr>
constexpr T SaturatedNegWrapper(T value) {
return T(0);
}
template <
typename T,
typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
constexpr T SaturatedNegWrapper(T value) {
return -value;
}
template <typename T,
typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
constexpr T SaturatedAbsWrapper(T value) {
// The calculation below is a static identity for unsigned types, but for
// signed integer types it provides a non-branching, saturated absolute value.
// This works because SafeUnsignedAbs() returns an unsigned type, which can
// represent the absolute value of all negative numbers of an equal-width
// integer type. The call to IsValueNegative() then detects overflow in the
// special case of numeric_limits<T>::min(), by evaluating the bit pattern as
// a signed integer value. If it is the overflow case, we end up subtracting
// one from the unsigned result, thus saturating to numeric_limits<T>::max().
return static_cast<T>(SafeUnsignedAbs(value) -
IsValueNegative<T>(SafeUnsignedAbs(value)));
}
template <
typename T,
typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
constexpr T SaturatedAbsWrapper(T value) {
return value < 0 ? -value : value;
}
template <typename T, typename U, class Enable = void>
struct ClampedAddOp {};
template <typename T, typename U>
struct ClampedAddOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V = result_type>
static constexpr V Do(T x, U y) {
if (ClampedAddFastOp<T, U>::is_supported)
return ClampedAddFastOp<T, U>::template Do<V>(x, y);
static_assert(std::is_same<V, result_type>::value ||
IsTypeInRangeForNumericType<U, V>::value,
"The saturation result cannot be determined from the "
"provided types.");
const V saturated = CommonMaxOrMin<V>(IsValueNegative(y));
V result = {};
return BASE_NUMERICS_LIKELY((CheckedAddOp<T, U>::Do(x, y, &result)))
? result
: saturated;
}
};
template <typename T, typename U, class Enable = void>
struct ClampedSubOp {};
template <typename T, typename U>
struct ClampedSubOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V = result_type>
static constexpr V Do(T x, U y) {
// TODO(jschuh) Make this "constexpr if" once we're C++17.
if (ClampedSubFastOp<T, U>::is_supported)
return ClampedSubFastOp<T, U>::template Do<V>(x, y);
static_assert(std::is_same<V, result_type>::value ||
IsTypeInRangeForNumericType<U, V>::value,
"The saturation result cannot be determined from the "
"provided types.");
const V saturated = CommonMaxOrMin<V>(!IsValueNegative(y));
V result = {};
return BASE_NUMERICS_LIKELY((CheckedSubOp<T, U>::Do(x, y, &result)))
? result
: saturated;
}
};
template <typename T, typename U, class Enable = void>
struct ClampedMulOp {};
template <typename T, typename U>
struct ClampedMulOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V = result_type>
static constexpr V Do(T x, U y) {
// TODO(jschuh) Make this "constexpr if" once we're C++17.
if (ClampedMulFastOp<T, U>::is_supported)
return ClampedMulFastOp<T, U>::template Do<V>(x, y);
V result = {};
const V saturated =
CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y));
return BASE_NUMERICS_LIKELY((CheckedMulOp<T, U>::Do(x, y, &result)))
? result
: saturated;
}
};
template <typename T, typename U, class Enable = void>
struct ClampedDivOp {};
template <typename T, typename U>
struct ClampedDivOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V = result_type>
static constexpr V Do(T x, U y) {
V result = {};
if (BASE_NUMERICS_LIKELY((CheckedDivOp<T, U>::Do(x, y, &result))))
return result;
// Saturation goes to max, min, or NaN (if x is zero).
return x ? CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y))
: SaturationDefaultLimits<V>::NaN();
}
};
template <typename T, typename U, class Enable = void>
struct ClampedModOp {};
template <typename T, typename U>
struct ClampedModOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V = result_type>
static constexpr V Do(T x, U y) {
V result = {};
return BASE_NUMERICS_LIKELY((CheckedModOp<T, U>::Do(x, y, &result)))
? result
: x;
}
};
template <typename T, typename U, class Enable = void>
struct ClampedLshOp {};
// Left shift. Non-zero values saturate in the direction of the sign. A zero
// shifted by any value always results in zero.
template <typename T, typename U>
struct ClampedLshOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = T;
template <typename V = result_type>
static constexpr V Do(T x, U shift) {
static_assert(!std::is_signed<U>::value, "Shift value must be unsigned.");
if (BASE_NUMERICS_LIKELY(shift < std::numeric_limits<T>::digits)) {
// Shift as unsigned to avoid undefined behavior.
V result = static_cast<V>(as_unsigned(x) << shift);
// If the shift can be reversed, we know it was valid.
if (BASE_NUMERICS_LIKELY(result >> shift == x))
return result;
}
return x ? CommonMaxOrMin<V>(IsValueNegative(x)) : 0;
}
};
template <typename T, typename U, class Enable = void>
struct ClampedRshOp {};
// Right shift. Negative values saturate to -1. Positive or 0 saturates to 0.
template <typename T, typename U>
struct ClampedRshOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = T;
template <typename V = result_type>
static constexpr V Do(T x, U shift) {
static_assert(!std::is_signed<U>::value, "Shift value must be unsigned.");
// Signed right shift is odd, because it saturates to -1 or 0.
const V saturated = as_unsigned(V(0)) - IsValueNegative(x);
return BASE_NUMERICS_LIKELY(shift < IntegerBitsPlusSign<T>::value)
? saturated_cast<V>(x >> shift)
: saturated;
}
};
template <typename T, typename U, class Enable = void>
struct ClampedAndOp {};
template <typename T, typename U>
struct ClampedAndOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename std::make_unsigned<
typename MaxExponentPromotion<T, U>::type>::type;
template <typename V>
static constexpr V Do(T x, U y) {
return static_cast<result_type>(x) & static_cast<result_type>(y);
}
};
template <typename T, typename U, class Enable = void>
struct ClampedOrOp {};
// For simplicity we promote to unsigned integers.
template <typename T, typename U>
struct ClampedOrOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename std::make_unsigned<
typename MaxExponentPromotion<T, U>::type>::type;
template <typename V>
static constexpr V Do(T x, U y) {
return static_cast<result_type>(x) | static_cast<result_type>(y);
}
};
template <typename T, typename U, class Enable = void>
struct ClampedXorOp {};
// For simplicity we support only unsigned integers.
template <typename T, typename U>
struct ClampedXorOp<T,
U,
typename std::enable_if<std::is_integral<T>::value &&
std::is_integral<U>::value>::type> {
using result_type = typename std::make_unsigned<
typename MaxExponentPromotion<T, U>::type>::type;
template <typename V>
static constexpr V Do(T x, U y) {
return static_cast<result_type>(x) ^ static_cast<result_type>(y);
}
};
template <typename T, typename U, class Enable = void>
struct ClampedMaxOp {};
template <typename T, typename U>
struct ClampedMaxOp<
T,
U,
typename std::enable_if<std::is_arithmetic<T>::value &&
std::is_arithmetic<U>::value>::type> {
using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V = result_type>
static constexpr V Do(T x, U y) {
return IsGreater<T, U>::Test(x, y) ? saturated_cast<V>(x)
: saturated_cast<V>(y);
}
};
template <typename T, typename U, class Enable = void>
struct ClampedMinOp {};
template <typename T, typename U>
struct ClampedMinOp<
T,
U,
typename std::enable_if<std::is_arithmetic<T>::value &&
std::is_arithmetic<U>::value>::type> {
using result_type = typename LowestValuePromotion<T, U>::type;
template <typename V = result_type>
static constexpr V Do(T x, U y) {
return IsLess<T, U>::Test(x, y) ? saturated_cast<V>(x)
: saturated_cast<V>(y);
}
};
// This is just boilerplate that wraps the standard floating point arithmetic.
// A macro isn't the nicest solution, but it beats rewriting these repeatedly.
#define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP) \
template <typename T, typename U> \
struct Clamped##NAME##Op< \
T, U, \
typename std::enable_if<std::is_floating_point<T>::value || \
std::is_floating_point<U>::value>::type> { \
using result_type = typename MaxExponentPromotion<T, U>::type; \
template <typename V = result_type> \
static constexpr V Do(T x, U y) { \
return saturated_cast<V>(x OP y); \
} \
};
BASE_FLOAT_ARITHMETIC_OPS(Add, +)
BASE_FLOAT_ARITHMETIC_OPS(Sub, -)
BASE_FLOAT_ARITHMETIC_OPS(Mul, *)
BASE_FLOAT_ARITHMETIC_OPS(Div, /)
#undef BASE_FLOAT_ARITHMETIC_OPS
} // namespace internal
} // namespace base
#endif // BASE_NUMERICS_CLAMPED_MATH_IMPL_H_

View File

@@ -1,19 +0,0 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_MATH_CONSTANTS_H_
#define BASE_NUMERICS_MATH_CONSTANTS_H_
namespace base {
constexpr double kPiDouble = 3.14159265358979323846;
constexpr float kPiFloat = 3.14159265358979323846f;
// The mean acceleration due to gravity on Earth in m/s^2.
constexpr double kMeanGravityDouble = 9.80665;
constexpr float kMeanGravityFloat = 9.80665f;
} // namespace base
#endif // BASE_NUMERICS_MATH_CONSTANTS_H_

View File

@@ -1,27 +0,0 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_RANGES_H_
#define BASE_NUMERICS_RANGES_H_
#include <algorithm>
#include <cmath>
namespace base {
// To be replaced with std::clamp() from C++17, someday.
template <class T>
constexpr const T& ClampToRange(const T& value, const T& min, const T& max) {
return std::min(std::max(value, min), max);
}
template <typename T>
constexpr bool IsApproximatelyEqual(T lhs, T rhs, T tolerance) {
static_assert(std::is_arithmetic<T>::value, "Argument must be arithmetic");
return std::abs(rhs - lhs) <= tolerance;
}
} // namespace base
#endif // BASE_NUMERICS_RANGES_H_

View File

@@ -1,358 +0,0 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_SAFE_CONVERSIONS_H_
#define BASE_NUMERICS_SAFE_CONVERSIONS_H_
#include <stddef.h>
#include <limits>
#include <type_traits>
#include "base/numerics/safe_conversions_impl.h"
#if !defined(__native_client__) && (defined(__ARMEL__) || defined(__arch64__))
#include "base/numerics/safe_conversions_arm_impl.h"
#define BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS (1)
#else
#define BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS (0)
#endif
#if !BASE_NUMERICS_DISABLE_OSTREAM_OPERATORS
#include <ostream>
#endif
namespace base {
namespace internal {
#if !BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS
template <typename Dst, typename Src>
struct SaturateFastAsmOp {
static const bool is_supported = false;
static constexpr Dst Do(Src) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<Dst>();
}
};
#endif // BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS
#undef BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS
// The following special case a few specific integer conversions where we can
// eke out better performance than range checking.
template <typename Dst, typename Src, typename Enable = void>
struct IsValueInRangeFastOp {
static const bool is_supported = false;
static constexpr bool Do(Src value) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<bool>();
}
};
// Signed to signed range comparison.
template <typename Dst, typename Src>
struct IsValueInRangeFastOp<
Dst,
Src,
typename std::enable_if<
std::is_integral<Dst>::value && std::is_integral<Src>::value &&
std::is_signed<Dst>::value && std::is_signed<Src>::value &&
!IsTypeInRangeForNumericType<Dst, Src>::value>::type> {
static const bool is_supported = true;
static constexpr bool Do(Src value) {
// Just downcast to the smaller type, sign extend it back to the original
// type, and then see if it matches the original value.
return value == static_cast<Dst>(value);
}
};
// Signed to unsigned range comparison.
template <typename Dst, typename Src>
struct IsValueInRangeFastOp<
Dst,
Src,
typename std::enable_if<
std::is_integral<Dst>::value && std::is_integral<Src>::value &&
!std::is_signed<Dst>::value && std::is_signed<Src>::value &&
!IsTypeInRangeForNumericType<Dst, Src>::value>::type> {
static const bool is_supported = true;
static constexpr bool Do(Src value) {
// We cast a signed as unsigned to overflow negative values to the top,
// then compare against whichever maximum is smaller, as our upper bound.
return as_unsigned(value) <= as_unsigned(CommonMax<Src, Dst>());
}
};
// Convenience function that returns true if the supplied value is in range
// for the destination type.
template <typename Dst, typename Src>
constexpr bool IsValueInRangeForNumericType(Src value) {
using SrcType = typename internal::UnderlyingType<Src>::type;
return internal::IsValueInRangeFastOp<Dst, SrcType>::is_supported
? internal::IsValueInRangeFastOp<Dst, SrcType>::Do(
static_cast<SrcType>(value))
: internal::DstRangeRelationToSrcRange<Dst>(
static_cast<SrcType>(value))
.IsValid();
}
// checked_cast<> is analogous to static_cast<> for numeric types,
// except that it CHECKs that the specified numeric conversion will not
// overflow or underflow. NaN source will always trigger a CHECK.
template <typename Dst,
class CheckHandler = internal::CheckOnFailure,
typename Src>
constexpr Dst checked_cast(Src value) {
// This throws a compile-time error on evaluating the constexpr if it can be
// determined at compile-time as failing, otherwise it will CHECK at runtime.
using SrcType = typename internal::UnderlyingType<Src>::type;
return BASE_NUMERICS_LIKELY((IsValueInRangeForNumericType<Dst>(value)))
? static_cast<Dst>(static_cast<SrcType>(value))
: CheckHandler::template HandleFailure<Dst>();
}
// Default boundaries for integral/float: max/infinity, lowest/-infinity, 0/NaN.
// You may provide your own limits (e.g. to saturated_cast) so long as you
// implement all of the static constexpr member functions in the class below.
template <typename T>
struct SaturationDefaultLimits : public std::numeric_limits<T> {
static constexpr T NaN() {
return std::numeric_limits<T>::has_quiet_NaN
? std::numeric_limits<T>::quiet_NaN()
: T();
}
using std::numeric_limits<T>::max;
static constexpr T Overflow() {
return std::numeric_limits<T>::has_infinity
? std::numeric_limits<T>::infinity()
: std::numeric_limits<T>::max();
}
using std::numeric_limits<T>::lowest;
static constexpr T Underflow() {
return std::numeric_limits<T>::has_infinity
? std::numeric_limits<T>::infinity() * -1
: std::numeric_limits<T>::lowest();
}
};
template <typename Dst, template <typename> class S, typename Src>
constexpr Dst saturated_cast_impl(Src value, RangeCheck constraint) {
// For some reason clang generates much better code when the branch is
// structured exactly this way, rather than a sequence of checks.
return !constraint.IsOverflowFlagSet()
? (!constraint.IsUnderflowFlagSet() ? static_cast<Dst>(value)
: S<Dst>::Underflow())
// Skip this check for integral Src, which cannot be NaN.
: (std::is_integral<Src>::value || !constraint.IsUnderflowFlagSet()
? S<Dst>::Overflow()
: S<Dst>::NaN());
}
// We can reduce the number of conditions and get slightly better performance
// for normal signed and unsigned integer ranges. And in the specific case of
// Arm, we can use the optimized saturation instructions.
template <typename Dst, typename Src, typename Enable = void>
struct SaturateFastOp {
static const bool is_supported = false;
static constexpr Dst Do(Src value) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<Dst>();
}
};
template <typename Dst, typename Src>
struct SaturateFastOp<
Dst,
Src,
typename std::enable_if<std::is_integral<Src>::value &&
std::is_integral<Dst>::value &&
SaturateFastAsmOp<Dst, Src>::is_supported>::type> {
static const bool is_supported = true;
static Dst Do(Src value) { return SaturateFastAsmOp<Dst, Src>::Do(value); }
};
template <typename Dst, typename Src>
struct SaturateFastOp<
Dst,
Src,
typename std::enable_if<std::is_integral<Src>::value &&
std::is_integral<Dst>::value &&
!SaturateFastAsmOp<Dst, Src>::is_supported>::type> {
static const bool is_supported = true;
static Dst Do(Src value) {
// The exact order of the following is structured to hit the correct
// optimization heuristics across compilers. Do not change without
// checking the emitted code.
Dst saturated = CommonMaxOrMin<Dst, Src>(
IsMaxInRangeForNumericType<Dst, Src>() ||
(!IsMinInRangeForNumericType<Dst, Src>() && IsValueNegative(value)));
return BASE_NUMERICS_LIKELY(IsValueInRangeForNumericType<Dst>(value))
? static_cast<Dst>(value)
: saturated;
}
};
// saturated_cast<> is analogous to static_cast<> for numeric types, except
// that the specified numeric conversion will saturate by default rather than
// overflow or underflow, and NaN assignment to an integral will return 0.
// All boundary condition behaviors can be overriden with a custom handler.
template <typename Dst,
template <typename> class SaturationHandler = SaturationDefaultLimits,
typename Src>
constexpr Dst saturated_cast(Src value) {
using SrcType = typename UnderlyingType<Src>::type;
return !IsCompileTimeConstant(value) &&
SaturateFastOp<Dst, SrcType>::is_supported &&
std::is_same<SaturationHandler<Dst>,
SaturationDefaultLimits<Dst>>::value
? SaturateFastOp<Dst, SrcType>::Do(static_cast<SrcType>(value))
: saturated_cast_impl<Dst, SaturationHandler, SrcType>(
static_cast<SrcType>(value),
DstRangeRelationToSrcRange<Dst, SaturationHandler, SrcType>(
static_cast<SrcType>(value)));
}
// strict_cast<> is analogous to static_cast<> for numeric types, except that
// it will cause a compile failure if the destination type is not large enough
// to contain any value in the source type. It performs no runtime checking.
template <typename Dst, typename Src>
constexpr Dst strict_cast(Src value) {
using SrcType = typename UnderlyingType<Src>::type;
static_assert(UnderlyingType<Src>::is_numeric, "Argument must be numeric.");
static_assert(std::is_arithmetic<Dst>::value, "Result must be numeric.");
// If you got here from a compiler error, it's because you tried to assign
// from a source type to a destination type that has insufficient range.
// The solution may be to change the destination type you're assigning to,
// and use one large enough to represent the source.
// Alternatively, you may be better served with the checked_cast<> or
// saturated_cast<> template functions for your particular use case.
static_assert(StaticDstRangeRelationToSrcRange<Dst, SrcType>::value ==
NUMERIC_RANGE_CONTAINED,
"The source type is out of range for the destination type. "
"Please see strict_cast<> comments for more information.");
return static_cast<Dst>(static_cast<SrcType>(value));
}
// Some wrappers to statically check that a type is in range.
template <typename Dst, typename Src, class Enable = void>
struct IsNumericRangeContained {
static const bool value = false;
};
template <typename Dst, typename Src>
struct IsNumericRangeContained<
Dst,
Src,
typename std::enable_if<ArithmeticOrUnderlyingEnum<Dst>::value &&
ArithmeticOrUnderlyingEnum<Src>::value>::type> {
static const bool value = StaticDstRangeRelationToSrcRange<Dst, Src>::value ==
NUMERIC_RANGE_CONTAINED;
};
// StrictNumeric implements compile time range checking between numeric types by
// wrapping assignment operations in a strict_cast. This class is intended to be
// used for function arguments and return types, to ensure the destination type
// can always contain the source type. This is essentially the same as enforcing
// -Wconversion in gcc and C4302 warnings on MSVC, but it can be applied
// incrementally at API boundaries, making it easier to convert code so that it
// compiles cleanly with truncation warnings enabled.
// This template should introduce no runtime overhead, but it also provides no
// runtime checking of any of the associated mathematical operations. Use
// CheckedNumeric for runtime range checks of the actual value being assigned.
template <typename T>
class StrictNumeric {
public:
using type = T;
constexpr StrictNumeric() : value_(0) {}
// Copy constructor.
template <typename Src>
constexpr StrictNumeric(const StrictNumeric<Src>& rhs)
: value_(strict_cast<T>(rhs.value_)) {}
// This is not an explicit constructor because we implicitly upgrade regular
// numerics to StrictNumerics to make them easier to use.
template <typename Src>
constexpr StrictNumeric(Src value) // NOLINT(runtime/explicit)
: value_(strict_cast<T>(value)) {}
// If you got here from a compiler error, it's because you tried to assign
// from a source type to a destination type that has insufficient range.
// The solution may be to change the destination type you're assigning to,
// and use one large enough to represent the source.
// If you're assigning from a CheckedNumeric<> class, you may be able to use
// the AssignIfValid() member function, specify a narrower destination type to
// the member value functions (e.g. val.template ValueOrDie<Dst>()), use one
// of the value helper functions (e.g. ValueOrDieForType<Dst>(val)).
// If you've encountered an _ambiguous overload_ you can use a static_cast<>
// to explicitly cast the result to the destination type.
// If none of that works, you may be better served with the checked_cast<> or
// saturated_cast<> template functions for your particular use case.
template <typename Dst,
typename std::enable_if<
IsNumericRangeContained<Dst, T>::value>::type* = nullptr>
constexpr operator Dst() const {
return static_cast<typename ArithmeticOrUnderlyingEnum<Dst>::type>(value_);
}
private:
const T value_;
};
// Convience wrapper returns a StrictNumeric from the provided arithmetic type.
template <typename T>
constexpr StrictNumeric<typename UnderlyingType<T>::type> MakeStrictNum(
const T value) {
return value;
}
#if !BASE_NUMERICS_DISABLE_OSTREAM_OPERATORS
// Overload the ostream output operator to make logging work nicely.
template <typename T>
std::ostream& operator<<(std::ostream& os, const StrictNumeric<T>& value) {
os << static_cast<T>(value);
return os;
}
#endif
#define BASE_NUMERIC_COMPARISON_OPERATORS(CLASS, NAME, OP) \
template <typename L, typename R, \
typename std::enable_if< \
internal::Is##CLASS##Op<L, R>::value>::type* = nullptr> \
constexpr bool operator OP(const L lhs, const R rhs) { \
return SafeCompare<NAME, typename UnderlyingType<L>::type, \
typename UnderlyingType<R>::type>(lhs, rhs); \
}
BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsLess, <)
BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsLessOrEqual, <=)
BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsGreater, >)
BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsGreaterOrEqual, >=)
BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsEqual, ==)
BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsNotEqual, !=)
} // namespace internal
using internal::as_signed;
using internal::as_unsigned;
using internal::checked_cast;
using internal::strict_cast;
using internal::saturated_cast;
using internal::SafeUnsignedAbs;
using internal::StrictNumeric;
using internal::MakeStrictNum;
using internal::IsValueInRangeForNumericType;
using internal::IsTypeInRangeForNumericType;
using internal::IsValueNegative;
// Explicitly make a shorter size_t alias for convenience.
using SizeT = StrictNumeric<size_t>;
} // namespace base
#endif // BASE_NUMERICS_SAFE_CONVERSIONS_H_

View File

@@ -1,51 +0,0 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_SAFE_CONVERSIONS_ARM_IMPL_H_
#define BASE_NUMERICS_SAFE_CONVERSIONS_ARM_IMPL_H_
#include <cassert>
#include <limits>
#include <type_traits>
#include "base/numerics/safe_conversions_impl.h"
namespace base {
namespace internal {
// Fast saturation to a destination type.
template <typename Dst, typename Src>
struct SaturateFastAsmOp {
static constexpr bool is_supported =
std::is_signed<Src>::value && std::is_integral<Dst>::value &&
std::is_integral<Src>::value &&
IntegerBitsPlusSign<Src>::value <= IntegerBitsPlusSign<int32_t>::value &&
IntegerBitsPlusSign<Dst>::value <= IntegerBitsPlusSign<int32_t>::value &&
!IsTypeInRangeForNumericType<Dst, Src>::value;
__attribute__((always_inline)) static Dst Do(Src value) {
int32_t src = value;
typename std::conditional<std::is_signed<Dst>::value, int32_t,
uint32_t>::type result;
if (std::is_signed<Dst>::value) {
asm("ssat %[dst], %[shift], %[src]"
: [dst] "=r"(result)
: [src] "r"(src), [shift] "n"(IntegerBitsPlusSign<Dst>::value <= 32
? IntegerBitsPlusSign<Dst>::value
: 32));
} else {
asm("usat %[dst], %[shift], %[src]"
: [dst] "=r"(result)
: [src] "r"(src), [shift] "n"(IntegerBitsPlusSign<Dst>::value < 32
? IntegerBitsPlusSign<Dst>::value
: 31));
}
return static_cast<Dst>(result);
}
};
} // namespace internal
} // namespace base
#endif // BASE_NUMERICS_SAFE_CONVERSIONS_ARM_IMPL_H_

View File

@@ -1,850 +0,0 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_SAFE_CONVERSIONS_IMPL_H_
#define BASE_NUMERICS_SAFE_CONVERSIONS_IMPL_H_
#include <stdint.h>
#include <limits>
#include <type_traits>
#if defined(__GNUC__) || defined(__clang__)
#define BASE_NUMERICS_LIKELY(x) __builtin_expect(!!(x), 1)
#define BASE_NUMERICS_UNLIKELY(x) __builtin_expect(!!(x), 0)
#else
#define BASE_NUMERICS_LIKELY(x) (x)
#define BASE_NUMERICS_UNLIKELY(x) (x)
#endif
namespace base {
namespace internal {
// The std library doesn't provide a binary max_exponent for integers, however
// we can compute an analog using std::numeric_limits<>::digits.
template <typename NumericType>
struct MaxExponent {
static const int value = std::is_floating_point<NumericType>::value
? std::numeric_limits<NumericType>::max_exponent
: std::numeric_limits<NumericType>::digits + 1;
};
// The number of bits (including the sign) in an integer. Eliminates sizeof
// hacks.
template <typename NumericType>
struct IntegerBitsPlusSign {
static const int value = std::numeric_limits<NumericType>::digits +
std::is_signed<NumericType>::value;
};
// Helper templates for integer manipulations.
template <typename Integer>
struct PositionOfSignBit {
static const size_t value = IntegerBitsPlusSign<Integer>::value - 1;
};
// Determines if a numeric value is negative without throwing compiler
// warnings on: unsigned(value) < 0.
template <typename T,
typename std::enable_if<std::is_signed<T>::value>::type* = nullptr>
constexpr bool IsValueNegative(T value) {
static_assert(std::is_arithmetic<T>::value, "Argument must be numeric.");
return value < 0;
}
template <typename T,
typename std::enable_if<!std::is_signed<T>::value>::type* = nullptr>
constexpr bool IsValueNegative(T) {
static_assert(std::is_arithmetic<T>::value, "Argument must be numeric.");
return false;
}
// This performs a fast negation, returning a signed value. It works on unsigned
// arguments, but probably doesn't do what you want for any unsigned value
// larger than max / 2 + 1 (i.e. signed min cast to unsigned).
template <typename T>
constexpr typename std::make_signed<T>::type ConditionalNegate(
T x,
bool is_negative) {
static_assert(std::is_integral<T>::value, "Type must be integral");
using SignedT = typename std::make_signed<T>::type;
using UnsignedT = typename std::make_unsigned<T>::type;
return static_cast<SignedT>(
(static_cast<UnsignedT>(x) ^ -SignedT(is_negative)) + is_negative);
}
// This performs a safe, absolute value via unsigned overflow.
template <typename T>
constexpr typename std::make_unsigned<T>::type SafeUnsignedAbs(T value) {
static_assert(std::is_integral<T>::value, "Type must be integral");
using UnsignedT = typename std::make_unsigned<T>::type;
return IsValueNegative(value) ? 0 - static_cast<UnsignedT>(value)
: static_cast<UnsignedT>(value);
}
// This allows us to switch paths on known compile-time constants.
#if defined(__clang__) || defined(__GNUC__)
constexpr bool CanDetectCompileTimeConstant() {
return true;
}
template <typename T>
constexpr bool IsCompileTimeConstant(const T v) {
return __builtin_constant_p(v);
}
#else
constexpr bool CanDetectCompileTimeConstant() {
return false;
}
template <typename T>
constexpr bool IsCompileTimeConstant(const T) {
return false;
}
#endif
template <typename T>
constexpr bool MustTreatAsConstexpr(const T v) {
// Either we can't detect a compile-time constant, and must always use the
// constexpr path, or we know we have a compile-time constant.
return !CanDetectCompileTimeConstant() || IsCompileTimeConstant(v);
}
// Forces a crash, like a CHECK(false). Used for numeric boundary errors.
// Also used in a constexpr template to trigger a compilation failure on
// an error condition.
struct CheckOnFailure {
template <typename T>
static T HandleFailure() {
#if defined(_MSC_VER)
__debugbreak();
#elif defined(__GNUC__) || defined(__clang__)
__builtin_trap();
#else
((void)(*(volatile char*)0 = 0));
#endif
return T();
}
};
enum IntegerRepresentation {
INTEGER_REPRESENTATION_UNSIGNED,
INTEGER_REPRESENTATION_SIGNED
};
// A range for a given nunmeric Src type is contained for a given numeric Dst
// type if both numeric_limits<Src>::max() <= numeric_limits<Dst>::max() and
// numeric_limits<Src>::lowest() >= numeric_limits<Dst>::lowest() are true.
// We implement this as template specializations rather than simple static
// comparisons to ensure type correctness in our comparisons.
enum NumericRangeRepresentation {
NUMERIC_RANGE_NOT_CONTAINED,
NUMERIC_RANGE_CONTAINED
};
// Helper templates to statically determine if our destination type can contain
// maximum and minimum values represented by the source type.
template <typename Dst,
typename Src,
IntegerRepresentation DstSign = std::is_signed<Dst>::value
? INTEGER_REPRESENTATION_SIGNED
: INTEGER_REPRESENTATION_UNSIGNED,
IntegerRepresentation SrcSign = std::is_signed<Src>::value
? INTEGER_REPRESENTATION_SIGNED
: INTEGER_REPRESENTATION_UNSIGNED>
struct StaticDstRangeRelationToSrcRange;
// Same sign: Dst is guaranteed to contain Src only if its range is equal or
// larger.
template <typename Dst, typename Src, IntegerRepresentation Sign>
struct StaticDstRangeRelationToSrcRange<Dst, Src, Sign, Sign> {
static const NumericRangeRepresentation value =
MaxExponent<Dst>::value >= MaxExponent<Src>::value
? NUMERIC_RANGE_CONTAINED
: NUMERIC_RANGE_NOT_CONTAINED;
};
// Unsigned to signed: Dst is guaranteed to contain source only if its range is
// larger.
template <typename Dst, typename Src>
struct StaticDstRangeRelationToSrcRange<Dst,
Src,
INTEGER_REPRESENTATION_SIGNED,
INTEGER_REPRESENTATION_UNSIGNED> {
static const NumericRangeRepresentation value =
MaxExponent<Dst>::value > MaxExponent<Src>::value
? NUMERIC_RANGE_CONTAINED
: NUMERIC_RANGE_NOT_CONTAINED;
};
// Signed to unsigned: Dst cannot be statically determined to contain Src.
template <typename Dst, typename Src>
struct StaticDstRangeRelationToSrcRange<Dst,
Src,
INTEGER_REPRESENTATION_UNSIGNED,
INTEGER_REPRESENTATION_SIGNED> {
static const NumericRangeRepresentation value = NUMERIC_RANGE_NOT_CONTAINED;
};
// This class wraps the range constraints as separate booleans so the compiler
// can identify constants and eliminate unused code paths.
class RangeCheck {
public:
constexpr RangeCheck(bool is_in_lower_bound, bool is_in_upper_bound)
: is_underflow_(!is_in_lower_bound), is_overflow_(!is_in_upper_bound) {}
constexpr RangeCheck() : is_underflow_(0), is_overflow_(0) {}
constexpr bool IsValid() const { return !is_overflow_ && !is_underflow_; }
constexpr bool IsInvalid() const { return is_overflow_ && is_underflow_; }
constexpr bool IsOverflow() const { return is_overflow_ && !is_underflow_; }
constexpr bool IsUnderflow() const { return !is_overflow_ && is_underflow_; }
constexpr bool IsOverflowFlagSet() const { return is_overflow_; }
constexpr bool IsUnderflowFlagSet() const { return is_underflow_; }
constexpr bool operator==(const RangeCheck rhs) const {
return is_underflow_ == rhs.is_underflow_ &&
is_overflow_ == rhs.is_overflow_;
}
constexpr bool operator!=(const RangeCheck rhs) const {
return !(*this == rhs);
}
private:
// Do not change the order of these member variables. The integral conversion
// optimization depends on this exact order.
const bool is_underflow_;
const bool is_overflow_;
};
// The following helper template addresses a corner case in range checks for
// conversion from a floating-point type to an integral type of smaller range
// but larger precision (e.g. float -> unsigned). The problem is as follows:
// 1. Integral maximum is always one less than a power of two, so it must be
// truncated to fit the mantissa of the floating point. The direction of
// rounding is implementation defined, but by default it's always IEEE
// floats, which round to nearest and thus result in a value of larger
// magnitude than the integral value.
// Example: float f = UINT_MAX; // f is 4294967296f but UINT_MAX
// // is 4294967295u.
// 2. If the floating point value is equal to the promoted integral maximum
// value, a range check will erroneously pass.
// Example: (4294967296f <= 4294967295u) // This is true due to a precision
// // loss in rounding up to float.
// 3. When the floating point value is then converted to an integral, the
// resulting value is out of range for the target integral type and
// thus is implementation defined.
// Example: unsigned u = (float)INT_MAX; // u will typically overflow to 0.
// To fix this bug we manually truncate the maximum value when the destination
// type is an integral of larger precision than the source floating-point type,
// such that the resulting maximum is represented exactly as a floating point.
template <typename Dst, typename Src, template <typename> class Bounds>
struct NarrowingRange {
using SrcLimits = std::numeric_limits<Src>;
using DstLimits = typename std::numeric_limits<Dst>;
// Computes the mask required to make an accurate comparison between types.
static const int kShift =
(MaxExponent<Src>::value > MaxExponent<Dst>::value &&
SrcLimits::digits < DstLimits::digits)
? (DstLimits::digits - SrcLimits::digits)
: 0;
template <
typename T,
typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
// Masks out the integer bits that are beyond the precision of the
// intermediate type used for comparison.
static constexpr T Adjust(T value) {
static_assert(std::is_same<T, Dst>::value, "");
static_assert(kShift < DstLimits::digits, "");
return static_cast<T>(
ConditionalNegate(SafeUnsignedAbs(value) & ~((T(1) << kShift) - T(1)),
IsValueNegative(value)));
}
template <typename T,
typename std::enable_if<std::is_floating_point<T>::value>::type* =
nullptr>
static constexpr T Adjust(T value) {
static_assert(std::is_same<T, Dst>::value, "");
static_assert(kShift == 0, "");
return value;
}
static constexpr Dst max() { return Adjust(Bounds<Dst>::max()); }
static constexpr Dst lowest() { return Adjust(Bounds<Dst>::lowest()); }
};
template <typename Dst,
typename Src,
template <typename> class Bounds,
IntegerRepresentation DstSign = std::is_signed<Dst>::value
? INTEGER_REPRESENTATION_SIGNED
: INTEGER_REPRESENTATION_UNSIGNED,
IntegerRepresentation SrcSign = std::is_signed<Src>::value
? INTEGER_REPRESENTATION_SIGNED
: INTEGER_REPRESENTATION_UNSIGNED,
NumericRangeRepresentation DstRange =
StaticDstRangeRelationToSrcRange<Dst, Src>::value>
struct DstRangeRelationToSrcRangeImpl;
// The following templates are for ranges that must be verified at runtime. We
// split it into checks based on signedness to avoid confusing casts and
// compiler warnings on signed an unsigned comparisons.
// Same sign narrowing: The range is contained for normal limits.
template <typename Dst,
typename Src,
template <typename> class Bounds,
IntegerRepresentation DstSign,
IntegerRepresentation SrcSign>
struct DstRangeRelationToSrcRangeImpl<Dst,
Src,
Bounds,
DstSign,
SrcSign,
NUMERIC_RANGE_CONTAINED> {
static constexpr RangeCheck Check(Src value) {
using SrcLimits = std::numeric_limits<Src>;
using DstLimits = NarrowingRange<Dst, Src, Bounds>;
return RangeCheck(
static_cast<Dst>(SrcLimits::lowest()) >= DstLimits::lowest() ||
static_cast<Dst>(value) >= DstLimits::lowest(),
static_cast<Dst>(SrcLimits::max()) <= DstLimits::max() ||
static_cast<Dst>(value) <= DstLimits::max());
}
};
// Signed to signed narrowing: Both the upper and lower boundaries may be
// exceeded for standard limits.
template <typename Dst, typename Src, template <typename> class Bounds>
struct DstRangeRelationToSrcRangeImpl<Dst,
Src,
Bounds,
INTEGER_REPRESENTATION_SIGNED,
INTEGER_REPRESENTATION_SIGNED,
NUMERIC_RANGE_NOT_CONTAINED> {
static constexpr RangeCheck Check(Src value) {
using DstLimits = NarrowingRange<Dst, Src, Bounds>;
return RangeCheck(value >= DstLimits::lowest(), value <= DstLimits::max());
}
};
// Unsigned to unsigned narrowing: Only the upper bound can be exceeded for
// standard limits.
template <typename Dst, typename Src, template <typename> class Bounds>
struct DstRangeRelationToSrcRangeImpl<Dst,
Src,
Bounds,
INTEGER_REPRESENTATION_UNSIGNED,
INTEGER_REPRESENTATION_UNSIGNED,
NUMERIC_RANGE_NOT_CONTAINED> {
static constexpr RangeCheck Check(Src value) {
using DstLimits = NarrowingRange<Dst, Src, Bounds>;
return RangeCheck(
DstLimits::lowest() == Dst(0) || value >= DstLimits::lowest(),
value <= DstLimits::max());
}
};
// Unsigned to signed: Only the upper bound can be exceeded for standard limits.
template <typename Dst, typename Src, template <typename> class Bounds>
struct DstRangeRelationToSrcRangeImpl<Dst,
Src,
Bounds,
INTEGER_REPRESENTATION_SIGNED,
INTEGER_REPRESENTATION_UNSIGNED,
NUMERIC_RANGE_NOT_CONTAINED> {
static constexpr RangeCheck Check(Src value) {
using DstLimits = NarrowingRange<Dst, Src, Bounds>;
using Promotion = decltype(Src() + Dst());
return RangeCheck(DstLimits::lowest() <= Dst(0) ||
static_cast<Promotion>(value) >=
static_cast<Promotion>(DstLimits::lowest()),
static_cast<Promotion>(value) <=
static_cast<Promotion>(DstLimits::max()));
}
};
// Signed to unsigned: The upper boundary may be exceeded for a narrower Dst,
// and any negative value exceeds the lower boundary for standard limits.
template <typename Dst, typename Src, template <typename> class Bounds>
struct DstRangeRelationToSrcRangeImpl<Dst,
Src,
Bounds,
INTEGER_REPRESENTATION_UNSIGNED,
INTEGER_REPRESENTATION_SIGNED,
NUMERIC_RANGE_NOT_CONTAINED> {
static constexpr RangeCheck Check(Src value) {
using SrcLimits = std::numeric_limits<Src>;
using DstLimits = NarrowingRange<Dst, Src, Bounds>;
using Promotion = decltype(Src() + Dst());
return RangeCheck(
value >= Src(0) && (DstLimits::lowest() == 0 ||
static_cast<Dst>(value) >= DstLimits::lowest()),
static_cast<Promotion>(SrcLimits::max()) <=
static_cast<Promotion>(DstLimits::max()) ||
static_cast<Promotion>(value) <=
static_cast<Promotion>(DstLimits::max()));
}
};
// Simple wrapper for statically checking if a type's range is contained.
template <typename Dst, typename Src>
struct IsTypeInRangeForNumericType {
static const bool value = StaticDstRangeRelationToSrcRange<Dst, Src>::value ==
NUMERIC_RANGE_CONTAINED;
};
template <typename Dst,
template <typename> class Bounds = std::numeric_limits,
typename Src>
constexpr RangeCheck DstRangeRelationToSrcRange(Src value) {
static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
static_assert(std::is_arithmetic<Dst>::value, "Result must be numeric.");
static_assert(Bounds<Dst>::lowest() < Bounds<Dst>::max(), "");
return DstRangeRelationToSrcRangeImpl<Dst, Src, Bounds>::Check(value);
}
// Integer promotion templates used by the portable checked integer arithmetic.
template <size_t Size, bool IsSigned>
struct IntegerForDigitsAndSign;
#define INTEGER_FOR_DIGITS_AND_SIGN(I) \
template <> \
struct IntegerForDigitsAndSign<IntegerBitsPlusSign<I>::value, \
std::is_signed<I>::value> { \
using type = I; \
}
INTEGER_FOR_DIGITS_AND_SIGN(int8_t);
INTEGER_FOR_DIGITS_AND_SIGN(uint8_t);
INTEGER_FOR_DIGITS_AND_SIGN(int16_t);
INTEGER_FOR_DIGITS_AND_SIGN(uint16_t);
INTEGER_FOR_DIGITS_AND_SIGN(int32_t);
INTEGER_FOR_DIGITS_AND_SIGN(uint32_t);
INTEGER_FOR_DIGITS_AND_SIGN(int64_t);
INTEGER_FOR_DIGITS_AND_SIGN(uint64_t);
#undef INTEGER_FOR_DIGITS_AND_SIGN
// WARNING: We have no IntegerForSizeAndSign<16, *>. If we ever add one to
// support 128-bit math, then the ArithmeticPromotion template below will need
// to be updated (or more likely replaced with a decltype expression).
static_assert(IntegerBitsPlusSign<intmax_t>::value == 64,
"Max integer size not supported for this toolchain.");
template <typename Integer, bool IsSigned = std::is_signed<Integer>::value>
struct TwiceWiderInteger {
using type =
typename IntegerForDigitsAndSign<IntegerBitsPlusSign<Integer>::value * 2,
IsSigned>::type;
};
enum ArithmeticPromotionCategory {
LEFT_PROMOTION, // Use the type of the left-hand argument.
RIGHT_PROMOTION // Use the type of the right-hand argument.
};
// Determines the type that can represent the largest positive value.
template <typename Lhs,
typename Rhs,
ArithmeticPromotionCategory Promotion =
(MaxExponent<Lhs>::value > MaxExponent<Rhs>::value)
? LEFT_PROMOTION
: RIGHT_PROMOTION>
struct MaxExponentPromotion;
template <typename Lhs, typename Rhs>
struct MaxExponentPromotion<Lhs, Rhs, LEFT_PROMOTION> {
using type = Lhs;
};
template <typename Lhs, typename Rhs>
struct MaxExponentPromotion<Lhs, Rhs, RIGHT_PROMOTION> {
using type = Rhs;
};
// Determines the type that can represent the lowest arithmetic value.
template <typename Lhs,
typename Rhs,
ArithmeticPromotionCategory Promotion =
std::is_signed<Lhs>::value
? (std::is_signed<Rhs>::value
? (MaxExponent<Lhs>::value > MaxExponent<Rhs>::value
? LEFT_PROMOTION
: RIGHT_PROMOTION)
: LEFT_PROMOTION)
: (std::is_signed<Rhs>::value
? RIGHT_PROMOTION
: (MaxExponent<Lhs>::value < MaxExponent<Rhs>::value
? LEFT_PROMOTION
: RIGHT_PROMOTION))>
struct LowestValuePromotion;
template <typename Lhs, typename Rhs>
struct LowestValuePromotion<Lhs, Rhs, LEFT_PROMOTION> {
using type = Lhs;
};
template <typename Lhs, typename Rhs>
struct LowestValuePromotion<Lhs, Rhs, RIGHT_PROMOTION> {
using type = Rhs;
};
// Determines the type that is best able to represent an arithmetic result.
template <
typename Lhs,
typename Rhs = Lhs,
bool is_intmax_type =
std::is_integral<typename MaxExponentPromotion<Lhs, Rhs>::type>::value&&
IntegerBitsPlusSign<typename MaxExponentPromotion<Lhs, Rhs>::type>::
value == IntegerBitsPlusSign<intmax_t>::value,
bool is_max_exponent =
StaticDstRangeRelationToSrcRange<
typename MaxExponentPromotion<Lhs, Rhs>::type,
Lhs>::value ==
NUMERIC_RANGE_CONTAINED&& StaticDstRangeRelationToSrcRange<
typename MaxExponentPromotion<Lhs, Rhs>::type,
Rhs>::value == NUMERIC_RANGE_CONTAINED>
struct BigEnoughPromotion;
// The side with the max exponent is big enough.
template <typename Lhs, typename Rhs, bool is_intmax_type>
struct BigEnoughPromotion<Lhs, Rhs, is_intmax_type, true> {
using type = typename MaxExponentPromotion<Lhs, Rhs>::type;
static const bool is_contained = true;
};
// We can use a twice wider type to fit.
template <typename Lhs, typename Rhs>
struct BigEnoughPromotion<Lhs, Rhs, false, false> {
using type =
typename TwiceWiderInteger<typename MaxExponentPromotion<Lhs, Rhs>::type,
std::is_signed<Lhs>::value ||
std::is_signed<Rhs>::value>::type;
static const bool is_contained = true;
};
// No type is large enough.
template <typename Lhs, typename Rhs>
struct BigEnoughPromotion<Lhs, Rhs, true, false> {
using type = typename MaxExponentPromotion<Lhs, Rhs>::type;
static const bool is_contained = false;
};
// We can statically check if operations on the provided types can wrap, so we
// can skip the checked operations if they're not needed. So, for an integer we
// care if the destination type preserves the sign and is twice the width of
// the source.
template <typename T, typename Lhs, typename Rhs = Lhs>
struct IsIntegerArithmeticSafe {
static const bool value =
!std::is_floating_point<T>::value &&
!std::is_floating_point<Lhs>::value &&
!std::is_floating_point<Rhs>::value &&
std::is_signed<T>::value >= std::is_signed<Lhs>::value &&
IntegerBitsPlusSign<T>::value >= (2 * IntegerBitsPlusSign<Lhs>::value) &&
std::is_signed<T>::value >= std::is_signed<Rhs>::value &&
IntegerBitsPlusSign<T>::value >= (2 * IntegerBitsPlusSign<Rhs>::value);
};
// Promotes to a type that can represent any possible result of a binary
// arithmetic operation with the source types.
template <typename Lhs,
typename Rhs,
bool is_promotion_possible = IsIntegerArithmeticSafe<
typename std::conditional<std::is_signed<Lhs>::value ||
std::is_signed<Rhs>::value,
intmax_t,
uintmax_t>::type,
typename MaxExponentPromotion<Lhs, Rhs>::type>::value>
struct FastIntegerArithmeticPromotion;
template <typename Lhs, typename Rhs>
struct FastIntegerArithmeticPromotion<Lhs, Rhs, true> {
using type =
typename TwiceWiderInteger<typename MaxExponentPromotion<Lhs, Rhs>::type,
std::is_signed<Lhs>::value ||
std::is_signed<Rhs>::value>::type;
static_assert(IsIntegerArithmeticSafe<type, Lhs, Rhs>::value, "");
static const bool is_contained = true;
};
template <typename Lhs, typename Rhs>
struct FastIntegerArithmeticPromotion<Lhs, Rhs, false> {
using type = typename BigEnoughPromotion<Lhs, Rhs>::type;
static const bool is_contained = false;
};
// Extracts the underlying type from an enum.
template <typename T, bool is_enum = std::is_enum<T>::value>
struct ArithmeticOrUnderlyingEnum;
template <typename T>
struct ArithmeticOrUnderlyingEnum<T, true> {
using type = typename std::underlying_type<T>::type;
static const bool value = std::is_arithmetic<type>::value;
};
template <typename T>
struct ArithmeticOrUnderlyingEnum<T, false> {
using type = T;
static const bool value = std::is_arithmetic<type>::value;
};
// The following are helper templates used in the CheckedNumeric class.
template <typename T>
class CheckedNumeric;
template <typename T>
class ClampedNumeric;
template <typename T>
class StrictNumeric;
// Used to treat CheckedNumeric and arithmetic underlying types the same.
template <typename T>
struct UnderlyingType {
using type = typename ArithmeticOrUnderlyingEnum<T>::type;
static const bool is_numeric = std::is_arithmetic<type>::value;
static const bool is_checked = false;
static const bool is_clamped = false;
static const bool is_strict = false;
};
template <typename T>
struct UnderlyingType<CheckedNumeric<T>> {
using type = T;
static const bool is_numeric = true;
static const bool is_checked = true;
static const bool is_clamped = false;
static const bool is_strict = false;
};
template <typename T>
struct UnderlyingType<ClampedNumeric<T>> {
using type = T;
static const bool is_numeric = true;
static const bool is_checked = false;
static const bool is_clamped = true;
static const bool is_strict = false;
};
template <typename T>
struct UnderlyingType<StrictNumeric<T>> {
using type = T;
static const bool is_numeric = true;
static const bool is_checked = false;
static const bool is_clamped = false;
static const bool is_strict = true;
};
template <typename L, typename R>
struct IsCheckedOp {
static const bool value =
UnderlyingType<L>::is_numeric && UnderlyingType<R>::is_numeric &&
(UnderlyingType<L>::is_checked || UnderlyingType<R>::is_checked);
};
template <typename L, typename R>
struct IsClampedOp {
static const bool value =
UnderlyingType<L>::is_numeric && UnderlyingType<R>::is_numeric &&
(UnderlyingType<L>::is_clamped || UnderlyingType<R>::is_clamped) &&
!(UnderlyingType<L>::is_checked || UnderlyingType<R>::is_checked);
};
template <typename L, typename R>
struct IsStrictOp {
static const bool value =
UnderlyingType<L>::is_numeric && UnderlyingType<R>::is_numeric &&
(UnderlyingType<L>::is_strict || UnderlyingType<R>::is_strict) &&
!(UnderlyingType<L>::is_checked || UnderlyingType<R>::is_checked) &&
!(UnderlyingType<L>::is_clamped || UnderlyingType<R>::is_clamped);
};
// as_signed<> returns the supplied integral value (or integral castable
// Numeric template) cast as a signed integral of equivalent precision.
// I.e. it's mostly an alias for: static_cast<std::make_signed<T>::type>(t)
template <typename Src>
constexpr typename std::make_signed<
typename base::internal::UnderlyingType<Src>::type>::type
as_signed(const Src value) {
static_assert(std::is_integral<decltype(as_signed(value))>::value,
"Argument must be a signed or unsigned integer type.");
return static_cast<decltype(as_signed(value))>(value);
}
// as_unsigned<> returns the supplied integral value (or integral castable
// Numeric template) cast as an unsigned integral of equivalent precision.
// I.e. it's mostly an alias for: static_cast<std::make_unsigned<T>::type>(t)
template <typename Src>
constexpr typename std::make_unsigned<
typename base::internal::UnderlyingType<Src>::type>::type
as_unsigned(const Src value) {
static_assert(std::is_integral<decltype(as_unsigned(value))>::value,
"Argument must be a signed or unsigned integer type.");
return static_cast<decltype(as_unsigned(value))>(value);
}
template <typename L, typename R>
constexpr bool IsLessImpl(const L lhs,
const R rhs,
const RangeCheck l_range,
const RangeCheck r_range) {
return l_range.IsUnderflow() || r_range.IsOverflow() ||
(l_range == r_range &&
static_cast<decltype(lhs + rhs)>(lhs) <
static_cast<decltype(lhs + rhs)>(rhs));
}
template <typename L, typename R>
struct IsLess {
static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
"Types must be numeric.");
static constexpr bool Test(const L lhs, const R rhs) {
return IsLessImpl(lhs, rhs, DstRangeRelationToSrcRange<R>(lhs),
DstRangeRelationToSrcRange<L>(rhs));
}
};
template <typename L, typename R>
constexpr bool IsLessOrEqualImpl(const L lhs,
const R rhs,
const RangeCheck l_range,
const RangeCheck r_range) {
return l_range.IsUnderflow() || r_range.IsOverflow() ||
(l_range == r_range &&
static_cast<decltype(lhs + rhs)>(lhs) <=
static_cast<decltype(lhs + rhs)>(rhs));
}
template <typename L, typename R>
struct IsLessOrEqual {
static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
"Types must be numeric.");
static constexpr bool Test(const L lhs, const R rhs) {
return IsLessOrEqualImpl(lhs, rhs, DstRangeRelationToSrcRange<R>(lhs),
DstRangeRelationToSrcRange<L>(rhs));
}
};
template <typename L, typename R>
constexpr bool IsGreaterImpl(const L lhs,
const R rhs,
const RangeCheck l_range,
const RangeCheck r_range) {
return l_range.IsOverflow() || r_range.IsUnderflow() ||
(l_range == r_range &&
static_cast<decltype(lhs + rhs)>(lhs) >
static_cast<decltype(lhs + rhs)>(rhs));
}
template <typename L, typename R>
struct IsGreater {
static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
"Types must be numeric.");
static constexpr bool Test(const L lhs, const R rhs) {
return IsGreaterImpl(lhs, rhs, DstRangeRelationToSrcRange<R>(lhs),
DstRangeRelationToSrcRange<L>(rhs));
}
};
template <typename L, typename R>
constexpr bool IsGreaterOrEqualImpl(const L lhs,
const R rhs,
const RangeCheck l_range,
const RangeCheck r_range) {
return l_range.IsOverflow() || r_range.IsUnderflow() ||
(l_range == r_range &&
static_cast<decltype(lhs + rhs)>(lhs) >=
static_cast<decltype(lhs + rhs)>(rhs));
}
template <typename L, typename R>
struct IsGreaterOrEqual {
static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
"Types must be numeric.");
static constexpr bool Test(const L lhs, const R rhs) {
return IsGreaterOrEqualImpl(lhs, rhs, DstRangeRelationToSrcRange<R>(lhs),
DstRangeRelationToSrcRange<L>(rhs));
}
};
template <typename L, typename R>
struct IsEqual {
static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
"Types must be numeric.");
static constexpr bool Test(const L lhs, const R rhs) {
return DstRangeRelationToSrcRange<R>(lhs) ==
DstRangeRelationToSrcRange<L>(rhs) &&
static_cast<decltype(lhs + rhs)>(lhs) ==
static_cast<decltype(lhs + rhs)>(rhs);
}
};
template <typename L, typename R>
struct IsNotEqual {
static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
"Types must be numeric.");
static constexpr bool Test(const L lhs, const R rhs) {
return DstRangeRelationToSrcRange<R>(lhs) !=
DstRangeRelationToSrcRange<L>(rhs) ||
static_cast<decltype(lhs + rhs)>(lhs) !=
static_cast<decltype(lhs + rhs)>(rhs);
}
};
// These perform the actual math operations on the CheckedNumerics.
// Binary arithmetic operations.
template <template <typename, typename> class C, typename L, typename R>
constexpr bool SafeCompare(const L lhs, const R rhs) {
static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
"Types must be numeric.");
using Promotion = BigEnoughPromotion<L, R>;
using BigType = typename Promotion::type;
return Promotion::is_contained
// Force to a larger type for speed if both are contained.
? C<BigType, BigType>::Test(
static_cast<BigType>(static_cast<L>(lhs)),
static_cast<BigType>(static_cast<R>(rhs)))
// Let the template functions figure it out for mixed types.
: C<L, R>::Test(lhs, rhs);
}
template <typename Dst, typename Src>
constexpr bool IsMaxInRangeForNumericType() {
return IsGreaterOrEqual<Dst, Src>::Test(std::numeric_limits<Dst>::max(),
std::numeric_limits<Src>::max());
}
template <typename Dst, typename Src>
constexpr bool IsMinInRangeForNumericType() {
return IsLessOrEqual<Dst, Src>::Test(std::numeric_limits<Dst>::lowest(),
std::numeric_limits<Src>::lowest());
}
template <typename Dst, typename Src>
constexpr Dst CommonMax() {
return !IsMaxInRangeForNumericType<Dst, Src>()
? Dst(std::numeric_limits<Dst>::max())
: Dst(std::numeric_limits<Src>::max());
}
template <typename Dst, typename Src>
constexpr Dst CommonMin() {
return !IsMinInRangeForNumericType<Dst, Src>()
? Dst(std::numeric_limits<Dst>::lowest())
: Dst(std::numeric_limits<Src>::lowest());
}
// This is a wrapper to generate return the max or min for a supplied type.
// If the argument is false, the returned value is the maximum. If true the
// returned value is the minimum.
template <typename Dst, typename Src = Dst>
constexpr Dst CommonMaxOrMin(bool is_min) {
return is_min ? CommonMin<Dst, Src>() : CommonMax<Dst, Src>();
}
} // namespace internal
} // namespace base
#endif // BASE_NUMERICS_SAFE_CONVERSIONS_IMPL_H_

View File

@@ -1,12 +0,0 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_SAFE_MATH_H_
#define BASE_NUMERICS_SAFE_MATH_H_
#include "base/numerics/checked_math.h"
#include "base/numerics/clamped_math.h"
#include "base/numerics/safe_conversions.h"
#endif // BASE_NUMERICS_SAFE_MATH_H_

View File

@@ -1,122 +0,0 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_SAFE_MATH_ARM_IMPL_H_
#define BASE_NUMERICS_SAFE_MATH_ARM_IMPL_H_
#include <cassert>
#include <limits>
#include <type_traits>
#include "base/numerics/safe_conversions.h"
namespace base {
namespace internal {
template <typename T, typename U>
struct CheckedMulFastAsmOp {
static const bool is_supported =
FastIntegerArithmeticPromotion<T, U>::is_contained;
// The following is much more efficient than the Clang and GCC builtins for
// performing overflow-checked multiplication when a twice wider type is
// available. The below compiles down to 2-3 instructions, depending on the
// width of the types in use.
// As an example, an int32_t multiply compiles to:
// smull r0, r1, r0, r1
// cmp r1, r1, asr #31
// And an int16_t multiply compiles to:
// smulbb r1, r1, r0
// asr r2, r1, #16
// cmp r2, r1, asr #15
template <typename V>
__attribute__((always_inline)) static bool Do(T x, U y, V* result) {
using Promotion = typename FastIntegerArithmeticPromotion<T, U>::type;
Promotion presult;
presult = static_cast<Promotion>(x) * static_cast<Promotion>(y);
*result = static_cast<V>(presult);
return IsValueInRangeForNumericType<V>(presult);
}
};
template <typename T, typename U>
struct ClampedAddFastAsmOp {
static const bool is_supported =
BigEnoughPromotion<T, U>::is_contained &&
IsTypeInRangeForNumericType<
int32_t,
typename BigEnoughPromotion<T, U>::type>::value;
template <typename V>
__attribute__((always_inline)) static V Do(T x, U y) {
// This will get promoted to an int, so let the compiler do whatever is
// clever and rely on the saturated cast to bounds check.
if (IsIntegerArithmeticSafe<int, T, U>::value)
return saturated_cast<V>(x + y);
int32_t result;
int32_t x_i32 = checked_cast<int32_t>(x);
int32_t y_i32 = checked_cast<int32_t>(y);
asm("qadd %[result], %[first], %[second]"
: [result] "=r"(result)
: [first] "r"(x_i32), [second] "r"(y_i32));
return saturated_cast<V>(result);
}
};
template <typename T, typename U>
struct ClampedSubFastAsmOp {
static const bool is_supported =
BigEnoughPromotion<T, U>::is_contained &&
IsTypeInRangeForNumericType<
int32_t,
typename BigEnoughPromotion<T, U>::type>::value;
template <typename V>
__attribute__((always_inline)) static V Do(T x, U y) {
// This will get promoted to an int, so let the compiler do whatever is
// clever and rely on the saturated cast to bounds check.
if (IsIntegerArithmeticSafe<int, T, U>::value)
return saturated_cast<V>(x - y);
int32_t result;
int32_t x_i32 = checked_cast<int32_t>(x);
int32_t y_i32 = checked_cast<int32_t>(y);
asm("qsub %[result], %[first], %[second]"
: [result] "=r"(result)
: [first] "r"(x_i32), [second] "r"(y_i32));
return saturated_cast<V>(result);
}
};
template <typename T, typename U>
struct ClampedMulFastAsmOp {
static const bool is_supported = CheckedMulFastAsmOp<T, U>::is_supported;
template <typename V>
__attribute__((always_inline)) static V Do(T x, U y) {
// Use the CheckedMulFastAsmOp for full-width 32-bit values, because
// it's fewer instructions than promoting and then saturating.
if (!IsIntegerArithmeticSafe<int32_t, T, U>::value &&
!IsIntegerArithmeticSafe<uint32_t, T, U>::value) {
V result;
if (CheckedMulFastAsmOp<T, U>::Do(x, y, &result))
return result;
return CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y));
}
assert((FastIntegerArithmeticPromotion<T, U>::is_contained));
using Promotion = typename FastIntegerArithmeticPromotion<T, U>::type;
return saturated_cast<V>(static_cast<Promotion>(x) *
static_cast<Promotion>(y));
}
};
} // namespace internal
} // namespace base
#endif // BASE_NUMERICS_SAFE_MATH_ARM_IMPL_H_

View File

@@ -1,157 +0,0 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_SAFE_MATH_CLANG_GCC_IMPL_H_
#define BASE_NUMERICS_SAFE_MATH_CLANG_GCC_IMPL_H_
#include <cassert>
#include <limits>
#include <type_traits>
#include "base/numerics/safe_conversions.h"
#if !defined(__native_client__) && (defined(__ARMEL__) || defined(__arch64__))
#include "base/numerics/safe_math_arm_impl.h"
#define BASE_HAS_ASSEMBLER_SAFE_MATH (1)
#else
#define BASE_HAS_ASSEMBLER_SAFE_MATH (0)
#endif
namespace base {
namespace internal {
// These are the non-functioning boilerplate implementations of the optimized
// safe math routines.
#if !BASE_HAS_ASSEMBLER_SAFE_MATH
template <typename T, typename U>
struct CheckedMulFastAsmOp {
static const bool is_supported = false;
template <typename V>
static constexpr bool Do(T, U, V*) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<bool>();
}
};
template <typename T, typename U>
struct ClampedAddFastAsmOp {
static const bool is_supported = false;
template <typename V>
static constexpr V Do(T, U) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<V>();
}
};
template <typename T, typename U>
struct ClampedSubFastAsmOp {
static const bool is_supported = false;
template <typename V>
static constexpr V Do(T, U) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<V>();
}
};
template <typename T, typename U>
struct ClampedMulFastAsmOp {
static const bool is_supported = false;
template <typename V>
static constexpr V Do(T, U) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<V>();
}
};
#endif // BASE_HAS_ASSEMBLER_SAFE_MATH
#undef BASE_HAS_ASSEMBLER_SAFE_MATH
template <typename T, typename U>
struct CheckedAddFastOp {
static const bool is_supported = true;
template <typename V>
__attribute__((always_inline)) static constexpr bool Do(T x, U y, V* result) {
return !__builtin_add_overflow(x, y, result);
}
};
template <typename T, typename U>
struct CheckedSubFastOp {
static const bool is_supported = true;
template <typename V>
__attribute__((always_inline)) static constexpr bool Do(T x, U y, V* result) {
return !__builtin_sub_overflow(x, y, result);
}
};
template <typename T, typename U>
struct CheckedMulFastOp {
#if defined(__clang__)
// TODO(jschuh): Get the Clang runtime library issues sorted out so we can
// support full-width, mixed-sign multiply builtins.
// https://crbug.com/613003
// We can support intptr_t, uintptr_t, or a smaller common type.
static const bool is_supported =
(IsTypeInRangeForNumericType<intptr_t, T>::value &&
IsTypeInRangeForNumericType<intptr_t, U>::value) ||
(IsTypeInRangeForNumericType<uintptr_t, T>::value &&
IsTypeInRangeForNumericType<uintptr_t, U>::value);
#else
static const bool is_supported = true;
#endif
template <typename V>
__attribute__((always_inline)) static constexpr bool Do(T x, U y, V* result) {
return CheckedMulFastAsmOp<T, U>::is_supported
? CheckedMulFastAsmOp<T, U>::Do(x, y, result)
: !__builtin_mul_overflow(x, y, result);
}
};
template <typename T, typename U>
struct ClampedAddFastOp {
static const bool is_supported = ClampedAddFastAsmOp<T, U>::is_supported;
template <typename V>
__attribute__((always_inline)) static V Do(T x, U y) {
return ClampedAddFastAsmOp<T, U>::template Do<V>(x, y);
}
};
template <typename T, typename U>
struct ClampedSubFastOp {
static const bool is_supported = ClampedSubFastAsmOp<T, U>::is_supported;
template <typename V>
__attribute__((always_inline)) static V Do(T x, U y) {
return ClampedSubFastAsmOp<T, U>::template Do<V>(x, y);
}
};
template <typename T, typename U>
struct ClampedMulFastOp {
static const bool is_supported = ClampedMulFastAsmOp<T, U>::is_supported;
template <typename V>
__attribute__((always_inline)) static V Do(T x, U y) {
return ClampedMulFastAsmOp<T, U>::template Do<V>(x, y);
}
};
template <typename T>
struct ClampedNegFastOp {
static const bool is_supported = std::is_signed<T>::value;
__attribute__((always_inline)) static T Do(T value) {
// Use this when there is no assembler path available.
if (!ClampedSubFastAsmOp<T, T>::is_supported) {
T result;
return !__builtin_sub_overflow(T(0), value, &result)
? result
: std::numeric_limits<T>::max();
}
// Fallback to the normal subtraction path.
return ClampedSubFastOp<T, T>::template Do<T>(T(0), value);
}
};
} // namespace internal
} // namespace base
#endif // BASE_NUMERICS_SAFE_MATH_CLANG_GCC_IMPL_H_

View File

@@ -1,240 +0,0 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_SAFE_MATH_SHARED_IMPL_H_
#define BASE_NUMERICS_SAFE_MATH_SHARED_IMPL_H_
#include <stddef.h>
#include <stdint.h>
#include <cassert>
#include <climits>
#include <cmath>
#include <cstdlib>
#include <limits>
#include <type_traits>
#include "base/numerics/safe_conversions.h"
#ifdef __asmjs__
// Optimized safe math instructions are incompatible with asmjs.
#define BASE_HAS_OPTIMIZED_SAFE_MATH (0)
// Where available use builtin math overflow support on Clang and GCC.
#elif !defined(__native_client__) && \
((defined(__clang__) && \
((__clang_major__ > 3) || \
(__clang_major__ == 3 && __clang_minor__ >= 4))) || \
(defined(__GNUC__) && __GNUC__ >= 5))
#include "base/numerics/safe_math_clang_gcc_impl.h"
#define BASE_HAS_OPTIMIZED_SAFE_MATH (1)
#else
#define BASE_HAS_OPTIMIZED_SAFE_MATH (0)
#endif
namespace base {
namespace internal {
// These are the non-functioning boilerplate implementations of the optimized
// safe math routines.
#if !BASE_HAS_OPTIMIZED_SAFE_MATH
template <typename T, typename U>
struct CheckedAddFastOp {
static const bool is_supported = false;
template <typename V>
static constexpr bool Do(T, U, V*) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<bool>();
}
};
template <typename T, typename U>
struct CheckedSubFastOp {
static const bool is_supported = false;
template <typename V>
static constexpr bool Do(T, U, V*) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<bool>();
}
};
template <typename T, typename U>
struct CheckedMulFastOp {
static const bool is_supported = false;
template <typename V>
static constexpr bool Do(T, U, V*) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<bool>();
}
};
template <typename T, typename U>
struct ClampedAddFastOp {
static const bool is_supported = false;
template <typename V>
static constexpr V Do(T, U) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<V>();
}
};
template <typename T, typename U>
struct ClampedSubFastOp {
static const bool is_supported = false;
template <typename V>
static constexpr V Do(T, U) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<V>();
}
};
template <typename T, typename U>
struct ClampedMulFastOp {
static const bool is_supported = false;
template <typename V>
static constexpr V Do(T, U) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<V>();
}
};
template <typename T>
struct ClampedNegFastOp {
static const bool is_supported = false;
static constexpr T Do(T) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<T>();
}
};
#endif // BASE_HAS_OPTIMIZED_SAFE_MATH
#undef BASE_HAS_OPTIMIZED_SAFE_MATH
// This is used for UnsignedAbs, where we need to support floating-point
// template instantiations even though we don't actually support the operations.
// However, there is no corresponding implementation of e.g. SafeUnsignedAbs,
// so the float versions will not compile.
template <typename Numeric,
bool IsInteger = std::is_integral<Numeric>::value,
bool IsFloat = std::is_floating_point<Numeric>::value>
struct UnsignedOrFloatForSize;
template <typename Numeric>
struct UnsignedOrFloatForSize<Numeric, true, false> {
using type = typename std::make_unsigned<Numeric>::type;
};
template <typename Numeric>
struct UnsignedOrFloatForSize<Numeric, false, true> {
using type = Numeric;
};
// Wrap the unary operations to allow SFINAE when instantiating integrals versus
// floating points. These don't perform any overflow checking. Rather, they
// exhibit well-defined overflow semantics and rely on the caller to detect
// if an overflow occured.
template <typename T,
typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
constexpr T NegateWrapper(T value) {
using UnsignedT = typename std::make_unsigned<T>::type;
// This will compile to a NEG on Intel, and is normal negation on ARM.
return static_cast<T>(UnsignedT(0) - static_cast<UnsignedT>(value));
}
template <
typename T,
typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
constexpr T NegateWrapper(T value) {
return -value;
}
template <typename T,
typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
constexpr typename std::make_unsigned<T>::type InvertWrapper(T value) {
return ~value;
}
template <typename T,
typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
constexpr T AbsWrapper(T value) {
return static_cast<T>(SafeUnsignedAbs(value));
}
template <
typename T,
typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
constexpr T AbsWrapper(T value) {
return value < 0 ? -value : value;
}
template <template <typename, typename, typename> class M,
typename L,
typename R>
struct MathWrapper {
using math = M<typename UnderlyingType<L>::type,
typename UnderlyingType<R>::type,
void>;
using type = typename math::result_type;
};
// These variadic templates work out the return types.
// TODO(jschuh): Rip all this out once we have C++14 non-trailing auto support.
template <template <typename, typename, typename> class M,
typename L,
typename R,
typename... Args>
struct ResultType;
template <template <typename, typename, typename> class M,
typename L,
typename R>
struct ResultType<M, L, R> {
using type = typename MathWrapper<M, L, R>::type;
};
template <template <typename, typename, typename> class M,
typename L,
typename R,
typename... Args>
struct ResultType {
using type =
typename ResultType<M, typename ResultType<M, L, R>::type, Args...>::type;
};
// The following macros are just boilerplate for the standard arithmetic
// operator overloads and variadic function templates. A macro isn't the nicest
// solution, but it beats rewriting these over and over again.
#define BASE_NUMERIC_ARITHMETIC_VARIADIC(CLASS, CL_ABBR, OP_NAME) \
template <typename L, typename R, typename... Args> \
constexpr CLASS##Numeric< \
typename ResultType<CLASS##OP_NAME##Op, L, R, Args...>::type> \
CL_ABBR##OP_NAME(const L lhs, const R rhs, const Args... args) { \
return CL_ABBR##MathOp<CLASS##OP_NAME##Op, L, R, Args...>(lhs, rhs, \
args...); \
}
#define BASE_NUMERIC_ARITHMETIC_OPERATORS(CLASS, CL_ABBR, OP_NAME, OP, CMP_OP) \
/* Binary arithmetic operator for all CLASS##Numeric operations. */ \
template <typename L, typename R, \
typename std::enable_if<Is##CLASS##Op<L, R>::value>::type* = \
nullptr> \
constexpr CLASS##Numeric< \
typename MathWrapper<CLASS##OP_NAME##Op, L, R>::type> \
operator OP(const L lhs, const R rhs) { \
return decltype(lhs OP rhs)::template MathOp<CLASS##OP_NAME##Op>(lhs, \
rhs); \
} \
/* Assignment arithmetic operator implementation from CLASS##Numeric. */ \
template <typename L> \
template <typename R> \
constexpr CLASS##Numeric<L>& CLASS##Numeric<L>::operator CMP_OP( \
const R rhs) { \
return MathOp<CLASS##OP_NAME##Op>(rhs); \
} \
/* Variadic arithmetic functions that return CLASS##Numeric. */ \
BASE_NUMERIC_ARITHMETIC_VARIADIC(CLASS, CL_ABBR, OP_NAME)
} // namespace internal
} // namespace base
#endif // BASE_NUMERICS_SAFE_MATH_SHARED_IMPL_H_

View File

@@ -1,15 +0,0 @@
{
"$schema": "https://json.schemastore.org/component-detection-manifest.json",
"Registrations": [
{
"component": {
"type": "git",
"git": {
"repositoryUrl": "https://github.com/chromium/chromium",
"commitHash": "d8710dd959da8e3be56f20af8cc94fbf560fbb6b"
}
}
}
],
"Version": 1
}

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2019 Maxime Pinard
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,17 +0,0 @@
### Notes for Future Maintainers
This was originally imported by @miniksa in March 2020.
The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme.
Please update the provenance information in that file when ingesting an updated version of the dependent library.
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards.
## What should be done to update this in the future?
1. Go to pinam45/dynamic_bitset repository on GitHub.
2. Take the entire contents of the include directory wholesale and drop it in the root directory here.
3. Don't change anything about it.
4. Validate that the license in the root of the repository didn't change and update it if so. It is sitting in the same directory as this readme.
If it changed dramatically, ensure that it is still compatible with our license scheme. Also update the NOTICE file in the root of our repository to declare the third-party usage.
5. Submit the pull.

View File

@@ -1,15 +0,0 @@
{
"$schema": "https://json.schemastore.org/component-detection-manifest.json",
"Registrations": [
{
"component": {
"type": "git",
"git": {
"repositoryUrl": "https://github.com/pinam45/dynamic_bitset",
"commitHash": "00f2d066ce9deebf28b006636150e5a882beb83f"
}
}
}
],
"Version": 1
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,26 +0,0 @@
BSD 2-Clause License
Copyright (c) 2016 - 2019, Kim Walisch
Copyright (c) 2016 - 2019, Wojciech Muła
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,17 +0,0 @@
### Notes for Future Maintainers
This was originally imported by @miniksa in March 2020.
The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme.
Please update the provenance information in that file when ingesting an updated version of the dependent library.
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards.
## What should be done to update this in the future?
1. Go to kimwalisch/libpopcnt repository on GitHub.
2. Take the `libpopcnt.h` file.
3. Don't change anything about it.
4. Validate that the `LICENSE` in the root of the repository didn't change and update it if so. It is sitting in the same directory as this readme.
If it changed dramatically, ensure that it is still compatible with our license scheme. Also update the NOTICE file in the root of our repository to declare the third-party usage.
5. Submit the pull.

View File

@@ -1,15 +0,0 @@
{
"$schema": "https://json.schemastore.org/component-detection-manifest.json",
"Registrations": [
{
"component": {
"type": "git",
"git": {
"repositoryUrl": "https://github.com/kimwalisch/libpopcnt",
"commitHash": "c49987e90e56191c399cab881ab87b5daecc9b8e"
}
}
}
],
"Version": 1
}

View File

@@ -1,798 +0,0 @@
/*
* libpopcnt.h - C/C++ library for counting the number of 1 bits (bit
* population count) in an array as quickly as possible using
* specialized CPU instructions i.e. POPCNT, AVX2, AVX512, NEON.
*
* Copyright (c) 2016 - 2020, Kim Walisch
* Copyright (c) 2016 - 2018, Wojciech Muła
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef LIBPOPCNT_H
#define LIBPOPCNT_H
#include <stdint.h>
#include <string.h>
#ifndef __has_builtin
#define __has_builtin(x) 0
#endif
#ifndef __has_attribute
#define __has_attribute(x) 0
#endif
#ifdef __GNUC__
#define GNUC_PREREQ(x, y) \
(__GNUC__ > x || (__GNUC__ == x && __GNUC_MINOR__ >= y))
#else
#define GNUC_PREREQ(x, y) 0
#endif
#ifdef __clang__
#define CLANG_PREREQ(x, y) \
(__clang_major__ > x || (__clang_major__ == x && __clang_minor__ >= y))
#else
#define CLANG_PREREQ(x, y) 0
#endif
#if (_MSC_VER < 1900) && \
!defined(__cplusplus)
#define inline __inline
#endif
#if (defined(__i386__) || \
defined(__x86_64__) || \
defined(_M_IX86) || \
defined(_M_X64))
#define X86_OR_X64
#endif
#if GNUC_PREREQ(4, 2) || \
__has_builtin(__builtin_popcount)
#define HAVE_BUILTIN_POPCOUNT
#endif
#if GNUC_PREREQ(4, 2) || \
CLANG_PREREQ(3, 0)
#define HAVE_ASM_POPCNT
#endif
#if defined(X86_OR_X64) && \
(defined(HAVE_ASM_POPCNT) || \
defined(_MSC_VER))
#define HAVE_POPCNT
#endif
#if defined(X86_OR_X64) && \
GNUC_PREREQ(4, 9)
#define HAVE_AVX2
#endif
#if defined(X86_OR_X64) && \
GNUC_PREREQ(5, 0)
#define HAVE_AVX512
#endif
#if defined(X86_OR_X64) && !defined(_M_ARM64EC)
/* MSVC compatible compilers (Windows) */
#if defined(_MSC_VER)
/* clang-cl (LLVM 10 from 2020) requires /arch:AVX2 or
* /arch:AVX512 to enable vector instructions */
#if defined(__clang__)
#if defined(__AVX2__)
#define HAVE_AVX2
#endif
#if defined(__AVX512__)
#define HAVE_AVX2
#define HAVE_AVX512
#endif
/* MSVC 2017 or later does not require
* /arch:AVX2 or /arch:AVX512 */
#elif _MSC_VER >= 1910
#define HAVE_AVX2
#define HAVE_AVX512
#endif
/* Clang (Unix-like OSes) */
#elif CLANG_PREREQ(3, 8) && \
__has_attribute(target) && \
(!defined(__apple_build_version__) || __apple_build_version__ >= 8000000)
#define HAVE_AVX2
#define HAVE_AVX512
#endif
#endif
/*
* Only enable CPUID runtime checks if this is really
* needed. E.g. do not enable if user has compiled
* using -march=native on a CPU that supports AVX512.
*/
#if defined(X86_OR_X64) && \
(defined(__cplusplus) || \
defined(_MSC_VER) || \
(GNUC_PREREQ(4, 2) || \
__has_builtin(__sync_val_compare_and_swap))) && \
((defined(HAVE_AVX512) && !(defined(__AVX512__) || defined(__AVX512BW__))) || \
(defined(HAVE_AVX2) && !defined(__AVX2__)) || \
(defined(HAVE_POPCNT) && !defined(__POPCNT__)))
#define HAVE_CPUID
#endif
#ifdef __cplusplus
extern "C" {
#endif
/*
* This uses fewer arithmetic operations than any other known
* implementation on machines with fast multiplication.
* It uses 12 arithmetic operations, one of which is a multiply.
* http://en.wikipedia.org/wiki/Hamming_weight#Efficient_implementation
*/
static inline uint64_t popcount64(uint64_t x)
{
uint64_t m1 = 0x5555555555555555ll;
uint64_t m2 = 0x3333333333333333ll;
uint64_t m4 = 0x0F0F0F0F0F0F0F0Fll;
uint64_t h01 = 0x0101010101010101ll;
x -= (x >> 1) & m1;
x = (x & m2) + ((x >> 2) & m2);
x = (x + (x >> 4)) & m4;
return (x * h01) >> 56;
}
#if defined(HAVE_ASM_POPCNT) && \
defined(__x86_64__)
static inline uint64_t popcnt64(uint64_t x)
{
__asm__ ("popcnt %1, %0" : "=r" (x) : "0" (x));
return x;
}
#elif defined(HAVE_ASM_POPCNT) && \
defined(__i386__)
static inline uint32_t popcnt32(uint32_t x)
{
__asm__ ("popcnt %1, %0" : "=r" (x) : "0" (x));
return x;
}
static inline uint64_t popcnt64(uint64_t x)
{
return popcnt32((uint32_t) x) +
popcnt32((uint32_t)(x >> 32));
}
#elif defined(_MSC_VER) && \
defined(_M_X64)
#include <nmmintrin.h>
static inline uint64_t popcnt64(uint64_t x)
{
return _mm_popcnt_u64(x);
}
#elif defined(_MSC_VER) && \
defined(_M_IX86)
#include <nmmintrin.h>
static inline uint64_t popcnt64(uint64_t x)
{
return _mm_popcnt_u32((uint32_t) x) +
_mm_popcnt_u32((uint32_t)(x >> 32));
}
/* non x86 CPUs */
#elif defined(HAVE_BUILTIN_POPCOUNT)
static inline uint64_t popcnt64(uint64_t x)
{
return __builtin_popcountll(x);
}
/* no hardware POPCNT,
* use pure integer algorithm */
#else
static inline uint64_t popcnt64(uint64_t x)
{
return popcount64(x);
}
#endif
#if defined(HAVE_CPUID)
#if defined(_MSC_VER)
#include <intrin.h>
#include <immintrin.h>
#endif
/* %ecx bit flags */
#define bit_POPCNT (1 << 23)
/* %ebx bit flags */
#define bit_AVX2 (1 << 5)
#define bit_AVX512 (1 << 30)
/* xgetbv bit flags */
#define XSTATE_SSE (1 << 1)
#define XSTATE_YMM (1 << 2)
#define XSTATE_ZMM (7 << 5)
static inline void run_cpuid(int eax, int ecx, int* abcd)
{
#if defined(_MSC_VER)
__cpuidex(abcd, eax, ecx);
#else
int ebx = 0;
int edx = 0;
#if defined(__i386__) && \
defined(__PIC__)
/* in case of PIC under 32-bit EBX cannot be clobbered */
__asm__ ("movl %%ebx, %%edi;"
"cpuid;"
"xchgl %%ebx, %%edi;"
: "=D" (ebx),
"+a" (eax),
"+c" (ecx),
"=d" (edx));
#else
__asm__ ("cpuid;"
: "+b" (ebx),
"+a" (eax),
"+c" (ecx),
"=d" (edx));
#endif
abcd[0] = eax;
abcd[1] = ebx;
abcd[2] = ecx;
abcd[3] = edx;
#endif
}
#if defined(HAVE_AVX2) || \
defined(HAVE_AVX512)
static inline int get_xcr0()
{
int xcr0;
#if defined(_MSC_VER)
xcr0 = (int) _xgetbv(0);
#else
__asm__ ("xgetbv" : "=a" (xcr0) : "c" (0) : "%edx" );
#endif
return xcr0;
}
#endif
static inline int get_cpuid()
{
int flags = 0;
int abcd[4];
run_cpuid(1, 0, abcd);
if ((abcd[2] & bit_POPCNT) == bit_POPCNT)
flags |= bit_POPCNT;
#if defined(HAVE_AVX2) || \
defined(HAVE_AVX512)
int osxsave_mask = (1 << 27);
/* ensure OS supports extended processor state management */
if ((abcd[2] & osxsave_mask) != osxsave_mask)
return 0;
int ymm_mask = XSTATE_SSE | XSTATE_YMM;
int zmm_mask = XSTATE_SSE | XSTATE_YMM | XSTATE_ZMM;
int xcr0 = get_xcr0();
if ((xcr0 & ymm_mask) == ymm_mask)
{
run_cpuid(7, 0, abcd);
if ((abcd[1] & bit_AVX2) == bit_AVX2)
flags |= bit_AVX2;
if ((xcr0 & zmm_mask) == zmm_mask)
{
if ((abcd[1] & bit_AVX512) == bit_AVX512)
flags |= bit_AVX512;
}
}
#endif
return flags;
}
#endif /* cpuid */
#if defined(HAVE_AVX2)
#include <immintrin.h>
#if !defined(_MSC_VER)
__attribute__ ((target ("avx2")))
#endif
static inline void CSA256(__m256i* h, __m256i* l, __m256i a, __m256i b, __m256i c)
{
__m256i u = _mm256_xor_si256(a, b);
*h = _mm256_or_si256(_mm256_and_si256(a, b), _mm256_and_si256(u, c));
*l = _mm256_xor_si256(u, c);
}
#if !defined(_MSC_VER)
__attribute__ ((target ("avx2")))
#endif
static inline __m256i popcnt256(__m256i v)
{
__m256i lookup1 = _mm256_setr_epi8(
4, 5, 5, 6, 5, 6, 6, 7,
5, 6, 6, 7, 6, 7, 7, 8,
4, 5, 5, 6, 5, 6, 6, 7,
5, 6, 6, 7, 6, 7, 7, 8
);
__m256i lookup2 = _mm256_setr_epi8(
4, 3, 3, 2, 3, 2, 2, 1,
3, 2, 2, 1, 2, 1, 1, 0,
4, 3, 3, 2, 3, 2, 2, 1,
3, 2, 2, 1, 2, 1, 1, 0
);
__m256i low_mask = _mm256_set1_epi8(0x0f);
__m256i lo = _mm256_and_si256(v, low_mask);
__m256i hi = _mm256_and_si256(_mm256_srli_epi16(v, 4), low_mask);
__m256i popcnt1 = _mm256_shuffle_epi8(lookup1, lo);
__m256i popcnt2 = _mm256_shuffle_epi8(lookup2, hi);
return _mm256_sad_epu8(popcnt1, popcnt2);
}
/*
* AVX2 Harley-Seal popcount (4th iteration).
* The algorithm is based on the paper "Faster Population Counts
* using AVX2 Instructions" by Daniel Lemire, Nathan Kurz and
* Wojciech Mula (23 Nov 2016).
* @see https://arxiv.org/abs/1611.07612
*/
#if !defined(_MSC_VER)
__attribute__ ((target ("avx2")))
#endif
static inline uint64_t popcnt_avx2(const __m256i* ptr, uint64_t size)
{
__m256i cnt = _mm256_setzero_si256();
__m256i ones = _mm256_setzero_si256();
__m256i twos = _mm256_setzero_si256();
__m256i fours = _mm256_setzero_si256();
__m256i eights = _mm256_setzero_si256();
__m256i sixteens = _mm256_setzero_si256();
__m256i twosA, twosB, foursA, foursB, eightsA, eightsB;
uint64_t i = 0;
uint64_t limit = size - size % 16;
uint64_t* cnt64;
for(; i < limit; i += 16)
{
CSA256(&twosA, &ones, ones, _mm256_loadu_si256(ptr + i + 0), _mm256_loadu_si256(ptr + i + 1));
CSA256(&twosB, &ones, ones, _mm256_loadu_si256(ptr + i + 2), _mm256_loadu_si256(ptr + i + 3));
CSA256(&foursA, &twos, twos, twosA, twosB);
CSA256(&twosA, &ones, ones, _mm256_loadu_si256(ptr + i + 4), _mm256_loadu_si256(ptr + i + 5));
CSA256(&twosB, &ones, ones, _mm256_loadu_si256(ptr + i + 6), _mm256_loadu_si256(ptr + i + 7));
CSA256(&foursB, &twos, twos, twosA, twosB);
CSA256(&eightsA, &fours, fours, foursA, foursB);
CSA256(&twosA, &ones, ones, _mm256_loadu_si256(ptr + i + 8), _mm256_loadu_si256(ptr + i + 9));
CSA256(&twosB, &ones, ones, _mm256_loadu_si256(ptr + i + 10), _mm256_loadu_si256(ptr + i + 11));
CSA256(&foursA, &twos, twos, twosA, twosB);
CSA256(&twosA, &ones, ones, _mm256_loadu_si256(ptr + i + 12), _mm256_loadu_si256(ptr + i + 13));
CSA256(&twosB, &ones, ones, _mm256_loadu_si256(ptr + i + 14), _mm256_loadu_si256(ptr + i + 15));
CSA256(&foursB, &twos, twos, twosA, twosB);
CSA256(&eightsB, &fours, fours, foursA, foursB);
CSA256(&sixteens, &eights, eights, eightsA, eightsB);
cnt = _mm256_add_epi64(cnt, popcnt256(sixteens));
}
cnt = _mm256_slli_epi64(cnt, 4);
cnt = _mm256_add_epi64(cnt, _mm256_slli_epi64(popcnt256(eights), 3));
cnt = _mm256_add_epi64(cnt, _mm256_slli_epi64(popcnt256(fours), 2));
cnt = _mm256_add_epi64(cnt, _mm256_slli_epi64(popcnt256(twos), 1));
cnt = _mm256_add_epi64(cnt, popcnt256(ones));
for(; i < size; i++)
cnt = _mm256_add_epi64(cnt, popcnt256(_mm256_loadu_si256(ptr + i)));
cnt64 = (uint64_t*) &cnt;
return cnt64[0] +
cnt64[1] +
cnt64[2] +
cnt64[3];
}
#endif
#if defined(HAVE_AVX512)
#include <immintrin.h>
#if !defined(_MSC_VER)
__attribute__ ((target ("avx512bw")))
#endif
static inline __m512i popcnt512(__m512i v)
{
__m512i m1 = _mm512_set1_epi8(0x55);
__m512i m2 = _mm512_set1_epi8(0x33);
__m512i m4 = _mm512_set1_epi8(0x0F);
__m512i vm = _mm512_and_si512(_mm512_srli_epi16(v, 1), m1);
__m512i t1 = _mm512_sub_epi8(v, vm);
__m512i tm = _mm512_and_si512(t1, m2);
__m512i tm2 = _mm512_and_si512(_mm512_srli_epi16(t1, 2), m2);
__m512i t2 = _mm512_add_epi8(tm, tm2);
__m512i tt = _mm512_add_epi8(t2, _mm512_srli_epi16(t2, 4));
__m512i t3 = _mm512_and_si512(tt, m4);
return _mm512_sad_epu8(t3, _mm512_setzero_si512());
}
#if !defined(_MSC_VER)
__attribute__ ((target ("avx512bw")))
#endif
static inline void CSA512(__m512i* h, __m512i* l, __m512i a, __m512i b, __m512i c)
{
*l = _mm512_ternarylogic_epi32(c, b, a, 0x96);
*h = _mm512_ternarylogic_epi32(c, b, a, 0xe8);
}
/*
* AVX512 Harley-Seal popcount (4th iteration).
* The algorithm is based on the paper "Faster Population Counts
* using AVX2 Instructions" by Daniel Lemire, Nathan Kurz and
* Wojciech Mula (23 Nov 2016).
* @see https://arxiv.org/abs/1611.07612
*/
#if !defined(_MSC_VER)
__attribute__ ((target ("avx512bw")))
#endif
static inline uint64_t popcnt_avx512(const __m512i* ptr, const uint64_t size)
{
__m512i cnt = _mm512_setzero_si512();
__m512i ones = _mm512_setzero_si512();
__m512i twos = _mm512_setzero_si512();
__m512i fours = _mm512_setzero_si512();
__m512i eights = _mm512_setzero_si512();
__m512i sixteens = _mm512_setzero_si512();
__m512i twosA, twosB, foursA, foursB, eightsA, eightsB;
uint64_t i = 0;
uint64_t limit = size - size % 16;
uint64_t* cnt64;
for(; i < limit; i += 16)
{
CSA512(&twosA, &ones, ones, _mm512_loadu_si512(ptr + i + 0), _mm512_loadu_si512(ptr + i + 1));
CSA512(&twosB, &ones, ones, _mm512_loadu_si512(ptr + i + 2), _mm512_loadu_si512(ptr + i + 3));
CSA512(&foursA, &twos, twos, twosA, twosB);
CSA512(&twosA, &ones, ones, _mm512_loadu_si512(ptr + i + 4), _mm512_loadu_si512(ptr + i + 5));
CSA512(&twosB, &ones, ones, _mm512_loadu_si512(ptr + i + 6), _mm512_loadu_si512(ptr + i + 7));
CSA512(&foursB, &twos, twos, twosA, twosB);
CSA512(&eightsA, &fours, fours, foursA, foursB);
CSA512(&twosA, &ones, ones, _mm512_loadu_si512(ptr + i + 8), _mm512_loadu_si512(ptr + i + 9));
CSA512(&twosB, &ones, ones, _mm512_loadu_si512(ptr + i + 10), _mm512_loadu_si512(ptr + i + 11));
CSA512(&foursA, &twos, twos, twosA, twosB);
CSA512(&twosA, &ones, ones, _mm512_loadu_si512(ptr + i + 12), _mm512_loadu_si512(ptr + i + 13));
CSA512(&twosB, &ones, ones, _mm512_loadu_si512(ptr + i + 14), _mm512_loadu_si512(ptr + i + 15));
CSA512(&foursB, &twos, twos, twosA, twosB);
CSA512(&eightsB, &fours, fours, foursA, foursB);
CSA512(&sixteens, &eights, eights, eightsA, eightsB);
cnt = _mm512_add_epi64(cnt, popcnt512(sixteens));
}
cnt = _mm512_slli_epi64(cnt, 4);
cnt = _mm512_add_epi64(cnt, _mm512_slli_epi64(popcnt512(eights), 3));
cnt = _mm512_add_epi64(cnt, _mm512_slli_epi64(popcnt512(fours), 2));
cnt = _mm512_add_epi64(cnt, _mm512_slli_epi64(popcnt512(twos), 1));
cnt = _mm512_add_epi64(cnt, popcnt512(ones));
for(; i < size; i++)
cnt = _mm512_add_epi64(cnt, popcnt512(_mm512_loadu_si512(ptr + i)));
cnt64 = (uint64_t*) &cnt;
return cnt64[0] +
cnt64[1] +
cnt64[2] +
cnt64[3] +
cnt64[4] +
cnt64[5] +
cnt64[6] +
cnt64[7];
}
#endif
/* x86 CPUs */
#if defined(X86_OR_X64)
/*
* Count the number of 1 bits in the data array
* @data: An array
* @size: Size of data in bytes
*/
static inline uint64_t popcnt(const void* data, uint64_t size)
{
uint64_t i = 0;
uint64_t cnt = 0;
const uint8_t* ptr = (const uint8_t*) data;
/*
* CPUID runtime checks are only enabled if this is needed.
* E.g. CPUID is disabled when a user compiles his
* code using -march=native on a CPU with AVX512.
*/
#if defined(HAVE_CPUID)
#if defined(__cplusplus)
/* C++11 thread-safe singleton */
static const int cpuid = get_cpuid();
#else
static int cpuid_ = -1;
int cpuid = cpuid_;
if (cpuid == -1)
{
cpuid = get_cpuid();
#if defined(_MSC_VER)
_InterlockedCompareExchange(&cpuid_, cpuid, -1);
#else
__sync_val_compare_and_swap(&cpuid_, -1, cpuid);
#endif
}
#endif
#endif
#if defined(HAVE_AVX512)
#if defined(__AVX512__) || defined(__AVX512BW__)
/* AVX512 requires arrays >= 1024 bytes */
if (i + 1024 <= size)
#else
if ((cpuid & bit_AVX512) &&
i + 1024 <= size)
#endif
{
const __m512i* ptr512 = (const __m512i*)(ptr + i);
cnt += popcnt_avx512(ptr512, (size - i) / 64);
i = size - size % 64;
}
#endif
#if defined(HAVE_AVX2)
#if defined(__AVX2__)
/* AVX2 requires arrays >= 512 bytes */
if (i + 512 <= size)
#else
if ((cpuid & bit_AVX2) &&
i + 512 <= size)
#endif
{
const __m256i* ptr256 = (const __m256i*)(ptr + i);
cnt += popcnt_avx2(ptr256, (size - i) / 32);
i = size - size % 32;
}
#endif
#if defined(HAVE_POPCNT)
/*
* The user has compiled without -mpopcnt.
* Unfortunately the MSVC compiler does not have
* a POPCNT macro so we cannot get rid of the
* runtime check for MSVC.
*/
#if !defined(__POPCNT__)
if (cpuid & bit_POPCNT)
#endif
{
/* We use unaligned memory accesses here to improve performance */
for (; i < size - size % 8; i += 8)
cnt += popcnt64(*(const uint64_t*)(ptr + i));
for (; i < size; i++)
cnt += popcnt64(ptr[i]);
return cnt;
}
#endif
#if !defined(HAVE_POPCNT) || \
!defined(__POPCNT__)
/*
* Pure integer popcount algorithm.
* We use unaligned memory accesses here to improve performance.
*/
for (; i < size - size % 8; i += 8)
cnt += popcount64(*(const uint64_t*)(ptr + i));
if (i < size)
{
uint64_t val = 0;
size_t bytes = (size_t)(size - i);
memcpy(&val, &ptr[i], bytes);
cnt += popcount64(val);
}
return cnt;
#endif
}
#elif defined(__ARM_NEON) || \
defined(__aarch64__)
#include <arm_neon.h>
static inline uint64x2_t vpadalq(uint64x2_t sum, uint8x16_t t)
{
return vpadalq_u32(sum, vpaddlq_u16(vpaddlq_u8(t)));
}
/*
* Count the number of 1 bits in the data array
* @data: An array
* @size: Size of data in bytes
*/
static inline uint64_t popcnt(const void* data, uint64_t size)
{
uint64_t i = 0;
uint64_t cnt = 0;
uint64_t chunk_size = 64;
const uint8_t* ptr = (const uint8_t*) data;
if (size >= chunk_size)
{
uint64_t iters = size / chunk_size;
uint64x2_t sum = vcombine_u64(vcreate_u64(0), vcreate_u64(0));
uint8x16_t zero = vcombine_u8(vcreate_u8(0), vcreate_u8(0));
do
{
uint8x16_t t0 = zero;
uint8x16_t t1 = zero;
uint8x16_t t2 = zero;
uint8x16_t t3 = zero;
/*
* After every 31 iterations we need to add the
* temporary sums (t0, t1, t2, t3) to the total sum.
* We must ensure that the temporary sums <= 255
* and 31 * 8 bits = 248 which is OK.
*/
uint64_t limit = (i + 31 < iters) ? i + 31 : iters;
/* Each iteration processes 64 bytes */
for (; i < limit; i++)
{
uint8x16x4_t input = vld4q_u8(ptr);
ptr += chunk_size;
t0 = vaddq_u8(t0, vcntq_u8(input.val[0]));
t1 = vaddq_u8(t1, vcntq_u8(input.val[1]));
t2 = vaddq_u8(t2, vcntq_u8(input.val[2]));
t3 = vaddq_u8(t3, vcntq_u8(input.val[3]));
}
sum = vpadalq(sum, t0);
sum = vpadalq(sum, t1);
sum = vpadalq(sum, t2);
sum = vpadalq(sum, t3);
}
while (i < iters);
i = 0;
size %= chunk_size;
uint64_t tmp[2];
vst1q_u64(tmp, sum);
cnt += tmp[0];
cnt += tmp[1];
}
#if defined(__ARM_FEATURE_UNALIGNED)
/* We use unaligned memory accesses here to improve performance */
for (; i < size - size % 8; i += 8)
cnt += popcnt64(*(const uint64_t*)(ptr + i));
#else
if (i + 8 <= size)
{
/* Align memory to an 8 byte boundary */
for (; (uintptr_t)(ptr + i) % 8; i++)
cnt += popcnt64(ptr[i]);
for (; i < size - size % 8; i += 8)
cnt += popcnt64(*(const uint64_t*)(ptr + i));
}
#endif
if (i < size)
{
uint64_t val = 0;
size_t bytes = (size_t)(size - i);
memcpy(&val, &ptr[i], bytes);
cnt += popcount64(val);
}
return cnt;
}
/* all other CPUs */
#else
/*
* Count the number of 1 bits in the data array
* @data: An array
* @size: Size of data in bytes
*/
static inline uint64_t popcnt(const void* data, uint64_t size)
{
uint64_t i = 0;
uint64_t cnt = 0;
const uint8_t* ptr = (const uint8_t*) data;
if (size >= 8)
{
/*
* Since we don't know whether this CPU architecture
* supports unaligned memory accesses we align
* memory to an 8 byte boundary.
*/
for (; (uintptr_t)(ptr + i) % 8; i++)
cnt += popcnt64(ptr[i]);
for (; i < size - size % 8; i += 8)
cnt += popcnt64(*(const uint64_t*)(ptr + i));
}
for (; i < size; i++)
cnt += popcnt64(ptr[i]);
return cnt;
}
#endif
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* LIBPOPCNT_H */

View File

@@ -42,7 +42,6 @@
<EventProvider Id="EventProvider-Microsoft.Windows.Console.Host" Name="fe1ff234-1f09-50a8-d38d-c44fab43e818"/>
<EventProvider Id="EventProvider-Microsoft.Windows.Console.Server" Name="1A541C01-589A-496E-85A7-A9E02170166D"/>
<EventProvider Id="EventProvider-Microsoft.Windows.Console.VirtualTerminal.Parser" Name="c9ba2a84-d3ca-5e19-2bd6-776a0910cb9d"/>
<EventProvider Id="EventProvider-Microsoft.Windows.Console.Render.VtEngine" Name="c9ba2a95-d3ca-5e19-2bd6-776a0910cb9d"/>
<EventProvider Id="EventProvider-Microsoft.Windows.Console.UIA" Name="e7ebce59-2161-572d-b263-2f16a6afb9e5"/>
<!-- Now define some profiles. We'll call them by ID when collecting. Also, the Base is where it is inheriting from and is a .wprpi file built... -->
<!-- ... into WPR automatically. Go look in the WPR install directory or in the documentation to find it. -->
@@ -66,7 +65,6 @@
<EventProviderId Value="EventProvider-Microsoft.Windows.Console.Host"/>
<EventProviderId Value="EventProvider-Microsoft.Windows.Console.Server"/>
<EventProviderId Value="EventProvider-Microsoft.Windows.Console.VirtualTerminal.Parser"/>
<EventProviderId Value="EventProvider-Microsoft.Windows.Console.Render.VtEngine"/>
<EventProviderId Value="EventProvider-Microsoft.Windows.Console.UIA"/>
</EventProviders>
</EventCollectorId>

View File

@@ -18,7 +18,6 @@
<EventProvider Id="EventProvider-Microsoft.Windows.Console.Host" Name="fe1ff234-1f09-50a8-d38d-c44fab43e818"/>
<EventProvider Id="EventProvider-Microsoft.Windows.Console.Server" Name="1A541C01-589A-496E-85A7-A9E02170166D"/>
<EventProvider Id="EventProvider-Microsoft.Windows.Console.VirtualTerminal.Parser" Name="c9ba2a84-d3ca-5e19-2bd6-776a0910cb9d"/>
<EventProvider Id="EventProvider-Microsoft.Windows.Console.Render.VtEngine" Name="c9ba2a95-d3ca-5e19-2bd6-776a0910cb9d"/>
<EventProvider Id="EventProvider-Microsoft.Windows.Console.UIA" Name="e7ebce59-2161-572d-b263-2f16a6afb9e5"/>
<!-- Profile for General Terminal logging -->

View File

@@ -162,7 +162,7 @@ til::CoordType CharToColumnMapper::GetTrailingColumnAt(const wchar_t* str) noexc
ROW::ROW(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, const TextAttribute& fillAttribute) :
_charsBuffer{ charsBuffer },
_chars{ charsBuffer, rowWidth },
_charOffsets{ charOffsetsBuffer, ::base::strict_cast<size_t>(rowWidth) + 1u },
_charOffsets{ charOffsetsBuffer, gsl::narrow_cast<size_t>(rowWidth) + 1u },
_attr{ rowWidth, fillAttribute },
_columnCount{ rowWidth }
{
@@ -1088,7 +1088,7 @@ DbcsAttribute ROW::DbcsAttrAt(til::CoordType column) const noexcept
attr = DbcsAttribute::Trailing;
}
// Safety: col+1 is [1, _columnCount].
else if (_uncheckedIsTrailer(::base::strict_cast<size_t>(col) + 1u))
else if (_uncheckedIsTrailer(gsl::narrow_cast<size_t>(col) + 1u))
{
attr = DbcsAttribute::Leading;
}

View File

@@ -98,7 +98,7 @@ void TextBuffer::_reserve(til::size screenBufferSize, const TextAttribute& defau
// 65535*65535 cells would result in a allocSize of 8GiB.
// --> Use uint64_t so that we can safely do our calculations even on x86.
// We allocate 1 additional row, which will be used for GetScratchpadRow().
const auto rowCount = ::base::strict_cast<uint64_t>(h) + 1;
const auto rowCount = gsl::narrow_cast<uint64_t>(h) + 1;
const auto allocSize = gsl::narrow<size_t>(rowCount * rowStride);
// NOTE: Modifications to this block of code might have to be mirrored over to ResizeTraditional().
@@ -716,13 +716,6 @@ OutputCellIterator TextBuffer::WriteLine(const OutputCellIterator givenIt,
// - true if we successfully incremented the buffer.
void TextBuffer::IncrementCircularBuffer(const TextAttribute& fillAttributes)
{
// FirstRow is at any given point in time the array index in the circular buffer that corresponds
// to the logical position 0 in the window (cursor coordinates and all other coordinates).
if (_isActiveBuffer && _renderer)
{
_renderer->TriggerFlush(true);
}
// Prune hyperlinks to delete obsolete references
_PruneHyperlinks();

View File

@@ -429,7 +429,7 @@ namespace winrt::TerminalApp::implementation
auto control = GetActiveTerminalControl();
const auto currentOffset = control.ScrollOffset();
control.ScrollViewport(::base::ClampAdd(currentOffset, delta));
control.ScrollViewport(currentOffset + delta);
}
// Method Description:

View File

@@ -5,6 +5,7 @@
#include "ConptyConnection.h"
#include <conpty-static.h>
#include <winmeta.h>
#include "CTerminalHandoff.h"
#include "LibraryResources.h"
@@ -31,29 +32,6 @@ static constexpr auto _errorFormat = L"{0} ({0:#010x})"sv;
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
// Function Description:
// - creates some basic anonymous pipes and passes them to CreatePseudoConsole
// Arguments:
// - size: The size of the conpty to create, in characters.
// - phInput: Receives the handle to the newly-created anonymous pipe for writing input to the conpty.
// - phOutput: Receives the handle to the newly-created anonymous pipe for reading the output of the conpty.
// - phPc: Receives a token value to identify this conpty
#pragma warning(suppress : 26430) // This statement sufficiently checks the out parameters. Analyzer cannot find this.
static HRESULT _CreatePseudoConsoleAndPipes(const COORD size, const DWORD dwFlags, HANDLE* phInput, HANDLE* phOutput, HPCON* phPC) noexcept
{
RETURN_HR_IF(E_INVALIDARG, phPC == nullptr || phInput == nullptr || phOutput == nullptr);
wil::unique_hfile outPipeOurSide, outPipePseudoConsoleSide;
wil::unique_hfile inPipeOurSide, inPipePseudoConsoleSide;
RETURN_IF_WIN32_BOOL_FALSE(CreatePipe(&inPipePseudoConsoleSide, &inPipeOurSide, nullptr, 0));
RETURN_IF_WIN32_BOOL_FALSE(CreatePipe(&outPipeOurSide, &outPipePseudoConsoleSide, nullptr, 0));
RETURN_IF_FAILED(ConptyCreatePseudoConsole(size, inPipePseudoConsoleSide.get(), outPipePseudoConsoleSide.get(), dwFlags, phPC));
*phInput = inPipeOurSide.release();
*phOutput = outPipeOurSide.release();
return S_OK;
}
// Function Description:
// - launches the client application attached to the new pseudoconsole
HRESULT ConptyConnection::_LaunchAttachedClient() noexcept
@@ -174,8 +152,10 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
// Who decided that?
#pragma warning(suppress : 26455) // Default constructor should not throw. Declare it 'noexcept' (f.6).
ConptyConnection::ConptyConnection()
ConptyConnection::ConptyConnection() :
_writeOverlappedEvent{ CreateEventExW(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS) }
{
THROW_LAST_ERROR_IF(!_writeOverlappedEvent);
}
// Function Description:
@@ -236,7 +216,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
_environment = settings.TryLookup(L"environment").try_as<Windows::Foundation::Collections::ValueSet>();
_profileGuid = unbox_prop_or<winrt::guid>(settings, L"profileGuid", _profileGuid);
_flags = PSEUDOCONSOLE_RESIZE_QUIRK;
_flags = 0;
// If we're using an existing buffer, we want the new connection
// to reuse the existing cursor. When not setting this flag, the
@@ -310,10 +290,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
_sessionId = Utils::CreateGuid();
wil::unique_hfile inPipePseudoConsoleSide;
wil::unique_hfile outPipePseudoConsoleSide;
THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&inPipePseudoConsoleSide, &_inPipe, nullptr, 0));
THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&_outPipe, &outPipePseudoConsoleSide, nullptr, 0));
auto pipe = Utils::CreateOverlappedPipe(PIPE_ACCESS_DUPLEX, 128 * 1024);
auto pipeClientClone = duplicateHandle(pipe.client.get());
auto ownedSignal = duplicateHandle(signal);
auto ownedReference = duplicateHandle(reference);
@@ -338,8 +316,9 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
}
CATCH_LOG()
*in = inPipePseudoConsoleSide.release();
*out = outPipePseudoConsoleSide.release();
_pipe = std::move(pipe.server);
*in = pipe.client.release();
*out = pipeClientClone.release();
}
winrt::hstring ConptyConnection::Commandline() const
@@ -366,9 +345,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
// If we do not have pipes already, then this is a fresh connection... not an inbound one that is a received
// handoff from an already-started PTY process.
if (!_inPipe)
if (!_pipe)
{
THROW_IF_FAILED(_CreatePseudoConsoleAndPipes(til::unwrap_coord_size(dimensions), _flags, &_inPipe, &_outPipe, &_hPC));
auto pipe = Utils::CreateOverlappedPipe(PIPE_ACCESS_DUPLEX, 128 * 1024);
THROW_IF_FAILED(ConptyCreatePseudoConsole(til::unwrap_coord_size(dimensions), pipe.client.get(), pipe.client.get(), _flags, &_hPC));
_pipe = std::move(pipe.server);
if (_initialParentHwnd != 0)
{
@@ -500,10 +481,39 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
return;
}
// convert from UTF-16LE to UTF-8 as ConPty expects UTF-8
// TODO GH#3378 reconcile and unify UTF-8 converters
auto str = winrt::to_string(data);
LOG_IF_WIN32_BOOL_FALSE(WriteFile(_inPipe.get(), str.c_str(), (DWORD)str.length(), nullptr, nullptr));
if (_writePending)
{
_writePending = false;
DWORD read;
if (FAILED_LOG(Utils::GetOverlappedResultSameThread(&_writeOverlapped, &read)))
{
// Not much we can do when the wait fails. This will kill the connection.
_hPC.reset();
return;
}
}
if (FAILED_LOG(til::u16u8(data, _writeBuffer)))
{
return;
}
if (!WriteFile(_pipe.get(), _writeBuffer.data(), gsl::narrow_cast<DWORD>(_writeBuffer.length()), nullptr, &_writeOverlapped))
{
switch (const auto gle = GetLastError())
{
case ERROR_BROKEN_PIPE:
_hPC.reset();
break;
case ERROR_IO_PENDING:
_writePending = true;
break;
default:
LOG_WIN32(gle);
break;
}
}
}
void ConptyConnection::Resize(uint32_t rows, uint32_t columns)
@@ -562,24 +572,15 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
_transitionToState(ConnectionState::Closing);
// .reset()ing either of these two will signal ConPTY to send out a CTRL_CLOSE_EVENT to all attached clients.
// FYI: The other members of this class are concurrently read by the _hOutputThread
// thread running in the background and so they're not safe to be .reset().
// This will signal ConPTY to send out a CTRL_CLOSE_EVENT to all attached clients.
// Once they're all disconnected it'll close its half of the pipes.
_hPC.reset();
_inPipe.reset();
if (_hOutputThread)
{
// Loop around `CancelSynchronousIo()` just in case the signal to shut down was missed.
// This may happen if we called `CancelSynchronousIo()` while not being stuck
// in `ReadFile()` and if OpenConsole refuses to exit in a timely manner.
// Loop around `CancelIoEx()` just in case the signal to shut down was missed.
for (;;)
{
// ConptyConnection::Close() blocks the UI thread, because `_TerminalOutputHandlers` might indirectly
// reference UI objects like `ControlCore`. CancelSynchronousIo() allows us to have the background
// thread exit as fast as possible by aborting any ongoing writes coming from OpenConsole.
CancelSynchronousIo(_hOutputThread.get());
// Waiting for the output thread to exit ensures that all pending TerminalOutput.raise()
// calls have returned and won't notify our caller (ControlCore) anymore. This ensures that
// we don't call a destroyed event handler asynchronously from a background thread (GH#13880).
@@ -590,15 +591,18 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
}
LOG_LAST_ERROR();
// The output thread may be stuck waiting for the OVERLAPPED to be signaled.
CancelIoEx(_pipe.get(), nullptr);
}
}
// Now that the background thread is done, we can safely clean up the other system objects, without
// race conditions, or fear of deadlocking ourselves (e.g. by calling CloseHandle() on _outPipe).
_outPipe.reset();
_hOutputThread.reset();
_piClient.reset();
_pipe.reset();
// The output thread should have already transitioned us to Closed.
// This exists just in case there was no output thread.
_transitionToState(ConnectionState::Closed);
}
CATCH_LOG()
@@ -645,72 +649,98 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
_LastConPtyClientDisconnected();
});
const wil::unique_event overlappedEvent{ CreateEventExW(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS) };
OVERLAPPED overlapped{ .hEvent = overlappedEvent.get() };
bool overlappedPending = false;
char buffer[128 * 1024];
DWORD read = 0;
til::u8state u8State;
std::wstring wstr;
// process the data of the output pipe in a loop
while (true)
// If we use overlapped IO We want to queue ReadFile() calls before processing the
// string, because TerminalOutput.raise() may take a while (relatively speaking).
// That's why the loop looks a little weird as it starts a read, processes the
// previous string, and finally converts the previous read to the next string.
for (;;)
{
const auto readFail{ !ReadFile(_outPipe.get(), &buffer[0], sizeof(buffer), &read, nullptr) };
// When we call CancelSynchronousIo() in Close() this is the branch that's taken and gets us out of here.
if (_isStateAtOrBeyond(ConnectionState::Closing))
// When we have a `wstr` that's ready for processing we must do so without blocking.
// Otherwise, whatever the user typed will be delayed until the next IO operation.
// With overlapped IO that's not a problem because the ReadFile() calls won't block.
if (!ReadFile(_pipe.get(), &buffer[0], sizeof(buffer), &read, &overlapped))
{
return 0;
}
if (readFail) // reading failed (we must check this first, because read will also be 0.)
{
// EXIT POINT
const auto lastError = GetLastError();
if (lastError == ERROR_BROKEN_PIPE)
if (GetLastError() != ERROR_IO_PENDING)
{
_LastConPtyClientDisconnected();
return S_OK;
break;
}
else
overlappedPending = true;
}
// wstr can be empty in two situations:
// * The previous call to til::u8u16 failed.
// * We're using overlapped IO, and it's the first iteration.
if (!wstr.empty())
{
if (!_receivedFirstByte)
{
_indicateExitWithStatus(HRESULT_FROM_WIN32(lastError)); // print a message
_transitionToState(ConnectionState::Failed);
return gsl::narrow_cast<DWORD>(HRESULT_FROM_WIN32(lastError));
}
}
const auto result{ til::u8u16(std::string_view{ &buffer[0], read }, wstr, u8State) };
if (FAILED(result))
{
// EXIT POINT
_indicateExitWithStatus(result); // print a message
_transitionToState(ConnectionState::Failed);
return gsl::narrow_cast<DWORD>(result);
}
if (wstr.empty())
{
return 0;
}
if (!_receivedFirstByte)
{
const auto now = std::chrono::high_resolution_clock::now();
const std::chrono::duration<double> delta = now - _startTime;
const auto now = std::chrono::high_resolution_clock::now();
const std::chrono::duration<double> delta = now - _startTime;
#pragma warning(suppress : 26477 26485 26494 26482 26446) // We don't control TraceLoggingWrite
TraceLoggingWrite(g_hTerminalConnectionProvider,
"ReceivedFirstByte",
TraceLoggingDescription("An event emitted when the connection receives the first byte"),
TraceLoggingGuid(_sessionId, "SessionGuid", "The WT_SESSION's GUID"),
TraceLoggingFloat64(delta.count(), "Duration"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
_receivedFirstByte = true;
TraceLoggingWrite(g_hTerminalConnectionProvider,
"ReceivedFirstByte",
TraceLoggingDescription("An event emitted when the connection receives the first byte"),
TraceLoggingGuid(_sessionId, "SessionGuid", "The WT_SESSION's GUID"),
TraceLoggingFloat64(delta.count(), "Duration"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
_receivedFirstByte = true;
}
try
{
TerminalOutput.raise(wstr);
}
CATCH_LOG();
}
// Pass the output to our registered event handlers
TerminalOutput.raise(wstr);
// Here's the counterpart to the start of the loop. We processed whatever was in `wstr`,
// so blocking synchronously on the pipe is now possible.
// If we used overlapped IO, we need to wait for the ReadFile() to complete.
// If we didn't, we can now safely block on our ReadFile() call.
if (overlappedPending)
{
overlappedPending = false;
if (FAILED(Utils::GetOverlappedResultSameThread(&overlapped, &read)))
{
break;
}
}
// winsock2 (WSA) handles of the \Device\Afd type are transparently compatible with
// ReadFile() and the WSARecv() documentations contains this important information:
// > For byte streams, zero bytes having been read [..] indicates graceful closure and that no more bytes will ever be read.
// --> Exit if we've read 0 bytes.
if (read == 0)
{
break;
}
if (_isStateAtOrBeyond(ConnectionState::Closing))
{
break;
}
TraceLoggingWrite(
g_hTerminalConnectionProvider,
"ReadFile",
TraceLoggingCountedUtf8String(&buffer[0], read, "buffer"),
TraceLoggingGuid(_sessionId, "session"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
// If we hit a parsing error, eat it. It's bad utf-8, we can't do anything with it.
FAILED_LOG(til::u8u16({ &buffer[0], gsl::narrow_cast<size_t>(read) }, wstr, u8State));
}
return 0;

View File

@@ -74,12 +74,16 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
bool _receivedFirstByte{ false };
std::chrono::high_resolution_clock::time_point _startTime{};
wil::unique_hfile _inPipe; // The pipe for writing input to
wil::unique_hfile _outPipe; // The pipe for reading output from
wil::unique_hfile _pipe;
wil::unique_handle _hOutputThread;
wil::unique_process_information _piClient;
wil::unique_any<HPCON, decltype(closePseudoConsoleAsync), closePseudoConsoleAsync> _hPC;
wil::unique_event _writeOverlappedEvent;
OVERLAPPED _writeOverlapped{};
std::string _writeBuffer;
bool _writePending = false;
DWORD _flags{ 0 };
til::env _initialEnv{};

View File

@@ -52,14 +52,6 @@ void Terminal::Create(til::size viewportSize, til::CoordType scrollbackLines, Re
auto dispatch = std::make_unique<AdaptDispatch>(*this, &renderer, _renderSettings, _terminalInput);
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
_stateMachine = std::make_unique<StateMachine>(std::move(engine));
// Until we have a true pass-through mode (GH#1173), the decision as to
// whether C1 controls are interpreted or not is made at the conhost level.
// If they are being filtered out, then we will simply never receive them.
// But if they are being accepted by conhost, there's a chance they may get
// passed through in some situations, so it's important that our state
// machine is always prepared to accept them.
_stateMachine->SetParserMode(StateMachine::Mode::AlwaysAcceptC1, true);
}
// Method Description:
@@ -395,7 +387,7 @@ try
const auto proposedBottom = newView.BottomExclusive();
if (proposedBottom > bufferSize.height)
{
proposedTop = ::base::ClampSub(proposedTop, ::base::ClampSub(proposedBottom, bufferSize.height));
proposedTop = proposedTop - proposedBottom + bufferSize.height;
}
// Keep the cursor in the mutable viewport
@@ -413,7 +405,7 @@ try
// If the old scrolloffset was 0, then we weren't scrolled back at all
// before, and shouldn't be now either.
_scrollOffset = originalOffsetWasZero ? 0 : static_cast<int>(::base::ClampSub(_mutableViewport.Top(), newVisibleTop));
_scrollOffset = originalOffsetWasZero ? 0 : _mutableViewport.Top() - newVisibleTop;
_mainBuffer->TriggerRedrawAll();
_NotifyScrollEvent();

View File

@@ -47,7 +47,6 @@ namespace TerminalCoreUnitTests
{
class TerminalBufferTests;
class TerminalApiTest;
class ConptyRoundtripTests;
class ScrollTest;
};
#endif
@@ -150,7 +149,6 @@ public:
void UseAlternateScreenBuffer(const TextAttribute& attrs) override;
void UseMainScreenBuffer() override;
bool IsConsolePty() const noexcept override;
bool IsVtInputEnabled() const noexcept override;
void NotifyAccessibilityChange(const til::rect& changedRect) noexcept override;
void NotifyBufferRotation(const int delta) override;
@@ -480,7 +478,6 @@ private:
#ifdef UNIT_TESTING
friend class TerminalCoreUnitTests::TerminalBufferTests;
friend class TerminalCoreUnitTests::TerminalApiTest;
friend class TerminalCoreUnitTests::ConptyRoundtripTests;
friend class TerminalCoreUnitTests::ScrollTest;
#endif
};

View File

@@ -193,8 +193,6 @@ void Terminal::PlayMidiNote(const int noteNumber, const int velocity, const std:
void Terminal::UseAlternateScreenBuffer(const TextAttribute& attrs)
{
_assertLocked();
// the new alt buffer is exactly the size of the viewport.
_altBufferSize = _mutableViewport.Dimensions();
@@ -310,11 +308,6 @@ void Terminal::ShowWindow(bool showOrHide)
}
}
bool Terminal::IsConsolePty() const noexcept
{
return false;
}
bool Terminal::IsVtInputEnabled() const noexcept
{
return false;

View File

@@ -123,7 +123,7 @@ til::point Terminal::SelectionStartForRendering() const
// flip the marker, so we skip this step.
bufferSize.DecrementInBounds(pos);
}
pos.y = base::ClampSub(pos.y, _VisibleStartIndex());
pos.y = pos.y - _VisibleStartIndex();
return til::point{ pos };
}
@@ -142,7 +142,7 @@ til::point Terminal::SelectionEndForRendering() const
// flip the marker, so we skip this step.
bufferSize.IncrementInBounds(pos);
}
pos.y = base::ClampSub(pos.y, _VisibleStartIndex());
pos.y = pos.y - _VisibleStartIndex();
return til::point{ pos };
}

View File

@@ -58,7 +58,14 @@ namespace winrt::Microsoft::Terminal::UI::implementation
// Misc
winrt::Windows::UI::Text::FontWeight Converters::DoubleToFontWeight(double value)
{
return winrt::Windows::UI::Text::FontWeight{ base::ClampedNumeric<uint16_t>(value) };
uint16_t val = 400;
if (value >= 1.0 && value <= 1000.0)
{
val = gsl::narrow_cast<uint16_t>(lrintf(value));
}
return winrt::Windows::UI::Text::FontWeight{ val };
}
winrt::Windows::UI::Xaml::Media::SolidColorBrush Converters::ColorToBrush(const winrt::Windows::UI::Color color)

View File

@@ -273,8 +273,6 @@ namespace ControlUnitTests
// contents. ConPTY will handle the actual clearing of the buffer
// contents. We can only ensure that the viewport moved when we did a
// clear scrollback.
//
// The ConptyRoundtripTests test the actual clearing of the contents.
}
void ControlCoreTests::TestClearScreen()
{
@@ -312,8 +310,6 @@ namespace ControlUnitTests
// contents. ConPTY will handle the actual clearing of the buffer
// contents. We can only ensure that the viewport moved when we did a
// clear scrollback.
//
// The ConptyRoundtripTests test the actual clearing of the contents.
}
void ControlCoreTests::TestClearAll()
{
@@ -351,8 +347,6 @@ namespace ControlUnitTests
// contents. ConPTY will handle the actual clearing of the buffer
// contents. We can only ensure that the viewport moved when we did a
// clear scrollback.
//
// The ConptyRoundtripTests test the actual clearing of the contents.
}
void ControlCoreTests::TestReadEntireBuffer()

File diff suppressed because it is too large Load Diff

View File

@@ -34,7 +34,6 @@ namespace
HRESULT StartPaint() noexcept { return S_OK; }
HRESULT EndPaint() noexcept { return S_OK; }
HRESULT Present() noexcept { return S_OK; }
HRESULT PrepareForTeardown(_Out_ bool* /*pForcePaint*/) noexcept { return S_OK; }
HRESULT ScrollFrame() noexcept { return S_OK; }
HRESULT Invalidate(const til::rect* /*psrRegion*/) noexcept { return S_OK; }
HRESULT InvalidateCursor(const til::rect* /*psrRegion*/) noexcept { return S_OK; }

View File

@@ -23,7 +23,6 @@
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="TerminalApiTest.cpp" />
<ClCompile Include="ConptyRoundtripTests.cpp" />
<ClCompile Include="TerminalBufferTests.cpp" />
<ClCompile Include="ScrollTest.cpp" />
<ClCompile Include="TilWinRtHelpersTests.cpp" />
@@ -47,45 +46,6 @@
<ProjectReference Include="..\TerminalCore\lib\TerminalCore-lib.vcxproj">
<Project>{ca5cad1a-abcd-429c-b551-8562ec954746}</Project>
</ProjectReference>
<!-- The following are all Console Host (host.lib) dependencies. We're
including them for the ConptyRoundtripTests, which instantiate a console
host, then user the output from conpty to dump directly into a Terminal,
and make sure the buffer contents align. -->
<ProjectReference Include="..\..\renderer\vt\ut_lib\vt.unittest.vcxproj">
<Project>{990F2657-8580-4828-943F-5DD657D11843}</Project>
</ProjectReference>
<ProjectReference Include="..\..\renderer\base\lib\base.vcxproj">
<Project>{af0a096a-8b3a-4949-81ef-7df8f0fee91f}</Project>
</ProjectReference>
<ProjectReference Include="$(OpenConsoleDir)\src\host\ut_lib\host.unittest.vcxproj">
<Project>{06ec74cb-9a12-429c-b551-8562ec954746}</Project>
</ProjectReference>
<ProjectReference Include="..\..\propslib\propslib.vcxproj">
<Project>{345fd5a4-b32b-4f29-bd1c-b033bd2c35cc}</Project>
</ProjectReference>
<ProjectReference Include="..\..\interactivity\base\lib\InteractivityBase.vcxproj">
<Project>{06ec74cb-9a12-429c-b551-8562ec964846}</Project>
</ProjectReference>
<ProjectReference Include="..\..\interactivity\win32\lib\win32.LIB.vcxproj">
<Project>{06ec74cb-9a12-429c-b551-8532ec964726}</Project>
</ProjectReference>
<ProjectReference Include="..\..\tsf\tsf.vcxproj">
<Project>{2fd12fbb-1ddb-46d8-b818-1023c624caca}</Project>
</ProjectReference>
<ProjectReference Include="..\..\server\lib\server.vcxproj">
<Project>{18d09a24-8240-42d6-8cb6-236eee820262}</Project>
</ProjectReference>
<ProjectReference Include="..\..\terminal\adapter\lib\adapter.vcxproj">
<Project>{dcf55140-ef6a-4736-a403-957e4f7430bb}</Project>
</ProjectReference>
<ProjectReference Include="..\..\internal\internal.vcxproj">
<Project>{ef3e32a7-5ff6-42b4-b6e2-96cd7d033f00}</Project>
</ProjectReference>
<ProjectReference Include="..\..\renderer\gdi\lib\gdi.vcxproj">
<Project>{1c959542-bac2-4e55-9a6d-13251914cbb9}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ClInclude Include="MockTermSettings.h" />

View File

@@ -1406,14 +1406,12 @@ void AppHost::_handleMoveContent(const winrt::Windows::Foundation::IInspectable&
if (args.WindowPosition() && _window)
{
// The WindowPosition is in DIPs. We need to convert it to pixels.
const til::point dragPositionInDips{ til::math::rounding, args.WindowPosition().Value() };
const auto dragPositionInDips = args.WindowPosition().Value();
const auto scale = _window->GetCurrentDpiScale();
til::point dragPositionInPixels{
til::math::rounding,
dragPositionInDips.x * scale,
dragPositionInDips.y * scale,
};
auto dragPositionInPixels = dragPositionInDips;
dragPositionInPixels.X *= scale;
dragPositionInPixels.Y *= scale;
// Fortunately, the window position is already in pixels.
til::rect windowBoundsInPixels{ _window->GetWindowRect() };
@@ -1445,17 +1443,20 @@ void AppHost::_handleMoveContent(const winrt::Windows::Foundation::IInspectable&
}
// Adjust for the non-client bounds
dragPositionInPixels.x -= nonClientFrame.left;
dragPositionInPixels.y -= nonClientFrame.top;
dragPositionInPixels.X -= nonClientFrame.left;
dragPositionInPixels.Y -= nonClientFrame.top;
windowSize = windowSize - nonClientFrame.size();
// Convert to DIPs for the size, so that dragging across a DPI boundary
// retains the correct dimensions.
const auto sizeInDips = windowSize.scale(til::math::rounding, 1.0f / scale);
til::rect inDips{ dragPositionInPixels, sizeInDips };
// Use the drag event as the new position, and the size of the actual window.
windowBoundsReference = inDips.to_winrt_rect();
const auto inverseScale = 1.0f / scale;
windowBoundsReference = winrt::Windows::Foundation::Rect{
dragPositionInPixels.X * inverseScale,
dragPositionInPixels.Y * inverseScale,
windowSize.width * inverseScale,
windowSize.height * inverseScale,
};
}
_windowManager.RequestMoveContent(args.Window(), args.Content(), args.TabIndex(), windowBoundsReference);

View File

@@ -1795,7 +1795,7 @@ til::rect IslandWindow::_getQuakeModeSize(HMONITOR hmon)
// smaller on either side to account for that, so they don't hang onto
// adjacent monitors.
const til::point origin{
::base::ClampSub(nearestMonitorInfo.rcWork.left, (ncSize.width / 2)) + 1,
nearestMonitorInfo.rcWork.left - ncSize.width / 2 + 1,
(nearestMonitorInfo.rcWork.top)
};
const til::size dimensions{

View File

@@ -141,7 +141,7 @@
<SDLCheck>true</SDLCheck>
<PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)\src\inc;$(SolutionDir)\dep;$(SolutionDir)\dep\Console;$(SolutionDir)\dep\Win32K;$(SolutionDir)\oss\chromium;$(SolutionDir)\oss\dynamic_bitset;$(SolutionDir)\oss\interval_tree;$(SolutionDir)\oss\libpopcnt;$(SolutionDir)\oss\pcg\include;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)\src\inc;$(SolutionDir)\dep;$(SolutionDir)\dep\Console;$(SolutionDir)\dep\Win32K;$(SolutionDir)\oss\interval_tree;$(SolutionDir)\oss\pcg\include;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
<RuntimeTypeInfo>false</RuntimeTypeInfo>

View File

@@ -18,7 +18,6 @@ const std::wstring_view ConsoleArguments::FILEPATH_LEADER_PREFIX = L"\\??\\";
const std::wstring_view ConsoleArguments::WIDTH_ARG = L"--width";
const std::wstring_view ConsoleArguments::HEIGHT_ARG = L"--height";
const std::wstring_view ConsoleArguments::INHERIT_CURSOR_ARG = L"--inheritcursor";
const std::wstring_view ConsoleArguments::RESIZE_QUIRK = L"--resizeQuirk";
const std::wstring_view ConsoleArguments::FEATURE_ARG = L"--feature";
const std::wstring_view ConsoleArguments::FEATURE_PTY_ARG = L"pty";
const std::wstring_view ConsoleArguments::COM_SERVER_ARG = L"-Embedding";
@@ -495,12 +494,6 @@ void ConsoleArguments::s_ConsumeArg(_Inout_ std::vector<std::wstring>& args, _In
s_ConsumeArg(args, i);
hr = S_OK;
}
else if (arg == RESIZE_QUIRK)
{
_resizeQuirk = true;
s_ConsumeArg(args, i);
hr = S_OK;
}
else if (arg == GLYPH_WIDTH)
{
hr = s_GetArgumentValue(args, i, &_textMeasurement);
@@ -652,10 +645,6 @@ bool ConsoleArguments::GetInheritCursor() const
{
return _inheritCursor;
}
bool ConsoleArguments::IsResizeQuirkEnabled() const
{
return _resizeQuirk;
}
#ifdef UNIT_TESTING
// Method Description:

View File

@@ -46,7 +46,6 @@ public:
std::wstring GetOriginalCommandLine() const;
std::wstring GetClientCommandline() const;
std::wstring GetVtMode() const;
const std::wstring& GetTextMeasurement() const;
bool GetForceV1() const;
bool GetForceNoHandoff() const;
@@ -54,7 +53,6 @@ public:
short GetWidth() const;
short GetHeight() const;
bool GetInheritCursor() const;
bool IsResizeQuirkEnabled() const;
#ifdef UNIT_TESTING
void EnableConptyModeForTests();
@@ -72,7 +70,6 @@ public:
static const std::wstring_view WIDTH_ARG;
static const std::wstring_view HEIGHT_ARG;
static const std::wstring_view INHERIT_CURSOR_ARG;
static const std::wstring_view RESIZE_QUIRK;
static const std::wstring_view FEATURE_ARG;
static const std::wstring_view FEATURE_PTY_ARG;
static const std::wstring_view COM_SERVER_ARG;
@@ -107,7 +104,6 @@ private:
_serverHandle(serverHandle),
_signalHandle(signalHandle),
_inheritCursor(inheritCursor),
_resizeQuirk(false),
_runAsComServer{ runAsComServer }
{
}
@@ -135,7 +131,6 @@ private:
DWORD _serverHandle;
DWORD _signalHandle;
bool _inheritCursor;
bool _resizeQuirk{ false };
[[nodiscard]] HRESULT _GetClientCommandline(_Inout_ std::vector<std::wstring>& args,
const size_t index,

View File

@@ -82,10 +82,8 @@ void CursorBlinker::TimerRoutine(SCREEN_INFORMATION& ScreenInfo) const noexcept
auto& cursor = buffer.GetCursor();
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto* const pAccessibilityNotifier = ServiceLocator::LocateAccessibilityNotifier();
const auto inConpty{ gci.IsInVtIoMode() };
// GH#2988: ConPTY can now be focused, but it doesn't need to do any of this work either.
if (inConpty || !WI_IsFlagSet(gci.Flags, CONSOLE_HAS_FOCUS))
if (!WI_IsFlagSet(gci.Flags, CONSOLE_HAS_FOCUS))
{
goto DoScroll;
}
@@ -165,6 +163,12 @@ DoScroll:
// guCaretBlinkTime is -1.
void CursorBlinker::SetCaretTimer() const noexcept
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (gci.IsConPTY())
{
return;
}
using filetime_duration = std::chrono::duration<int64_t, std::ratio<1, 10000000>>;
static constexpr DWORD dwDefTimeout = 0x212;

View File

@@ -179,11 +179,7 @@ void PtySignalInputThread::_DoResizeWindow(const ResizeWindowData& data)
return;
}
if (_api.ResizeWindow(data.sx, data.sy))
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
THROW_IF_FAILED(gci.GetVtIo()->SuppressResizeRepaint());
}
_api.ResizeWindow(data.sx, data.sy);
}
void PtySignalInputThread::_DoClearBuffer() const
@@ -351,5 +347,5 @@ void PtySignalInputThread::_DoSetWindowParent(const SetParentData& data)
void PtySignalInputThread::_Shutdown()
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.GetVtIo()->SendCloseEvent();
gci.GetVtIoNoCheck()->SendCloseEvent();
}

View File

@@ -55,54 +55,112 @@ DWORD WINAPI VtInputThread::StaticVtInputThreadProc(_In_ LPVOID lpParameter)
void VtInputThread::_InputThread()
{
const auto cleanup = wil::scope_exit([this]() {
ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo()->CloseInput();
_hFile.reset();
ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIoNoCheck()->SendCloseEvent();
});
OVERLAPPED* overlapped = nullptr;
OVERLAPPED overlappedBuf{};
wil::unique_event overlappedEvent;
bool overlappedPending = false;
char buffer[4096];
DWORD dwRead = 0;
DWORD read = 0;
til::u8state u8State;
std::wstring wstr;
if (Utils::HandleWantsOverlappedIo(_hFile.get()))
{
overlappedEvent.reset(CreateEventExW(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS));
if (overlappedEvent)
{
overlappedBuf.hEvent = overlappedEvent.get();
overlapped = &overlappedBuf;
}
}
// If we use overlapped IO We want to queue ReadFile() calls before processing the
// string, because LockConsole/ProcessString may take a while (relatively speaking).
// That's why the loop looks a little weird as it starts a read, processes the
// previous string, and finally converts the previous read to the next string.
for (;;)
{
const auto ok = ReadFile(_hFile.get(), buffer, ARRAYSIZE(buffer), &dwRead, nullptr);
// When we have a `wstr` that's ready for processing we must do so without blocking.
// Otherwise, whatever the user typed will be delayed until the next IO operation.
// With overlapped IO that's not a problem because the ReadFile() calls won't block.
if (overlapped)
{
if (!ReadFile(_hFile.get(), &buffer[0], sizeof(buffer), &read, overlapped))
{
if (GetLastError() != ERROR_IO_PENDING)
{
break;
}
overlappedPending = true;
}
}
// The ReadFile() documentations calls out that:
// > If the lpNumberOfBytesRead parameter is zero when ReadFile returns TRUE on a pipe, the other
// > end of the pipe called the WriteFile function with nNumberOfBytesToWrite set to zero.
// But I was unable to replicate any such behavior. I'm not sure it's true anymore.
//
// However, what the documentations fails to mention is that winsock2 (WSA) handles of the \Device\Afd type are
// transparently compatible with ReadFile() and the WSARecv() documentations contains this important information:
// wstr can be empty in two situations:
// * The previous call to til::u8u16 failed.
// * We're using overlapped IO, and it's the first iteration.
if (!wstr.empty())
{
try
{
// Make sure to call the GLOBAL Lock/Unlock, not the gci's lock/unlock.
// Only the global unlock attempts to dispatch ctrl events. If you use the
// gci's unlock, when you press C-c, it won't be dispatched until the
// next console API call. For something like `powershell sleep 60`,
// that won't happen for 60s
LockConsole();
const auto unlock = wil::scope_exit([&] { UnlockConsole(); });
_pInputStateMachine->ProcessString(wstr);
}
CATCH_LOG();
}
// Here's the counterpart to the start of the loop. We processed whatever was in `wstr`,
// so blocking synchronously on the pipe is now possible.
// If we used overlapped IO, we need to wait for the ReadFile() to complete.
// If we didn't, we can now safely block on our ReadFile() call.
if (overlapped)
{
if (overlappedPending)
{
overlappedPending = false;
if (FAILED(Utils::GetOverlappedResultSameThread(overlapped, &read)))
{
break;
}
}
}
else
{
if (!ReadFile(_hFile.get(), &buffer[0], sizeof(buffer), &read, overlapped))
{
break;
}
}
// winsock2 (WSA) handles of the \Device\Afd type are transparently compatible with
// ReadFile() and the WSARecv() documentations contains this important information:
// > For byte streams, zero bytes having been read [..] indicates graceful closure and that no more bytes will ever be read.
// In other words, for pipe HANDLE of unknown type you should consider `lpNumberOfBytesRead == 0` as an exit indicator.
//
// Here, `dwRead == 0` fixes a deadlock when exiting conhost while being in use by WSL whose hypervisor pipes are WSA.
if (!ok || dwRead == 0)
// --> Exit if we've read 0 bytes.
if (read == 0)
{
break;
}
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ConPTY ReadFile",
TraceLoggingCountedUtf8String(&buffer[0], read, "buffer"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
// If we hit a parsing error, eat it. It's bad utf-8, we can't do anything with it.
if (FAILED_LOG(til::u8u16({ buffer, gsl::narrow_cast<size_t>(dwRead) }, wstr, u8State)))
{
continue;
}
try
{
// Make sure to call the GLOBAL Lock/Unlock, not the gci's lock/unlock.
// Only the global unlock attempts to dispatch ctrl events. If you use the
// gci's unlock, when you press C-c, it won't be dispatched until the
// next console API call. For something like `powershell sleep 60`,
// that won't happen for 60s
LockConsole();
const auto unlock = wil::scope_exit([&] { UnlockConsole(); });
_pInputStateMachine->ProcessString(wstr);
}
CATCH_LOG();
FAILED_LOG(til::u8u16({ &buffer[0], gsl::narrow_cast<size_t>(read) }, wstr, u8State));
}
}

View File

@@ -4,11 +4,12 @@
#include "precomp.h"
#include "VtIo.hpp"
#include <til/unicode.h>
#include "handle.h" // LockConsole
#include "output.h" // CloseConsoleProcessState
#include "../interactivity/inc/ServiceLocator.hpp"
#include "../renderer/base/renderer.hpp"
#include "../renderer/vt/Xterm256Engine.hpp"
#include "../types/inc/CodepointWidthDetector.hpp"
#include "../types/inc/utils.hpp"
@@ -19,16 +20,9 @@ using namespace Microsoft::Console::Types;
using namespace Microsoft::Console::Utils;
using namespace Microsoft::Console::Interactivity;
VtIo::VtIo() :
_initialized(false),
_lookingForCursorPosition(false)
{
}
[[nodiscard]] HRESULT VtIo::Initialize(const ConsoleArguments* const pArgs)
{
_lookingForCursorPosition = pArgs->GetInheritCursor();
_resizeQuirk = pArgs->IsResizeQuirkEnabled();
// If we were already given VT handles, set up the VT IO engine to use those.
if (pArgs->InConptyMode())
@@ -90,6 +84,16 @@ VtIo::VtIo() :
_hOutput.reset(OutHandle);
_hSignal.reset(SignalHandle);
if (Utils::HandleWantsOverlappedIo(_hOutput.get()))
{
_overlappedEvent.reset(CreateEventExW(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS));
if (_overlappedEvent)
{
_overlappedBuf.hEvent = _overlappedEvent.get();
_overlapped = &_overlappedBuf;
}
}
// The only way we're initialized is if the args said we're in conpty mode.
// If the args say so, then at least one of in, out, or signal was specified
_initialized = true;
@@ -97,7 +101,7 @@ VtIo::VtIo() :
}
// Method Description:
// - Create the VtRenderer and the VtInputThread for this console.
// - Create the VtEngine and the VtInputThread for this console.
// MUST BE DONE AFTER CONSOLE IS INITIALIZED, to make sure we've gotten the
// buffer size from the attached client application.
// Arguments:
@@ -112,11 +116,9 @@ VtIo::VtIo() :
{
return S_FALSE;
}
auto& globals = ServiceLocator::LocateGlobals();
const auto& gci = globals.getConsoleInformation();
// SetWindowVisibility uses the console lock to protect access to _pVtRenderEngine.
assert(gci.IsConsoleLocked());
assert(ServiceLocator::LocateGlobals().getConsoleInformation().IsConsoleLocked());
try
{
@@ -124,21 +126,6 @@ VtIo::VtIo() :
{
_pVtInputThread = std::make_unique<VtInputThread>(std::move(_hInput), _lookingForCursorPosition);
}
if (IsValidHandle(_hOutput.get()))
{
auto initialViewport = Viewport::FromDimensions({ 0, 0 }, gci.GetWindowSize());
auto xterm256Engine = std::make_unique<Xterm256Engine>(std::move(_hOutput),
initialViewport);
_pVtRenderEngine = std::move(xterm256Engine);
if (_pVtRenderEngine)
{
_pVtRenderEngine->SetTerminalOwner(this);
_pVtRenderEngine->SetResizeQuirk(_resizeQuirk);
}
}
}
CATCH_RETURN();
@@ -167,48 +154,48 @@ bool VtIo::IsUsingVt() const
{
return S_FALSE;
}
auto& g = ServiceLocator::LocateGlobals();
if (_pVtRenderEngine)
{
try
{
g.pRender->AddRenderEngine(_pVtRenderEngine.get());
g.getConsoleInformation().GetActiveOutputBuffer().SetTerminalConnection(_pVtRenderEngine.get());
// Force the whole window to be put together first.
// We don't really need the handle, we just want to leverage the setup steps.
ServiceLocator::LocatePseudoWindow();
}
CATCH_RETURN();
}
if (_pVtInputThread)
{
LOG_IF_FAILED(_pVtInputThread->Start());
}
// MSFT: 15813316
// If the terminal application wants us to inherit the cursor position,
// we're going to emit a VT sequence to ask for the cursor position, then
// wait 3s until we get a response.
// If we get a response, the InteractDispatch will call SetCursorPosition,
// which will call to our VtIo::SetCursorPosition method.
if (_lookingForCursorPosition && _pVtRenderEngine && _pVtInputThread)
{
_lookingForCursorPosition = false;
LOG_IF_FAILED(_pVtRenderEngine->RequestCursor());
auto writer = GetWriter();
// Allow the input thread to momentarily gain the console lock.
const auto suspension = g.getConsoleInformation().SuspendLock();
_pVtInputThread->WaitUntilDSR(3000);
// GH#4999 - Send a sequence to the connected terminal to request
// win32-input-mode from them. This will enable the connected terminal to
// send us full INPUT_RECORDs as input. If the terminal doesn't understand
// this sequence, it'll just ignore it.
writer.WriteUTF8(
"\033[?1004h" // Focus Event Mode
"\033[?9001h" // Win32 Input Mode
);
// MSFT: 15813316
// If the terminal application wants us to inherit the cursor position,
// we're going to emit a VT sequence to ask for the cursor position, then
// wait 1s until we get a response.
// If we get a response, the InteractDispatch will call SetCursorPosition,
// which will call to our VtIo::SetCursorPosition method.
if (_lookingForCursorPosition)
{
writer.WriteUTF8("\x1b[6n"); // Cursor Position Report (DSR CPR)
}
writer.Submit();
}
// GH#4999 - Send a sequence to the connected terminal to request
// win32-input-mode from them. This will enable the connected terminal to
// send us full INPUT_RECORDs as input. If the terminal doesn't understand
// this sequence, it'll just ignore it.
LOG_IF_FAILED(_pVtRenderEngine->RequestWin32Input());
if (_lookingForCursorPosition)
{
_lookingForCursorPosition = false;
// Allow the input thread to momentarily gain the console lock.
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto suspension = gci.SuspendLock();
_pVtInputThread->WaitUntilDSR(3000);
}
if (_pPtySignalInputThread)
{
@@ -247,25 +234,6 @@ void VtIo::CreatePseudoWindow()
}
}
void VtIo::SetWindowVisibility(bool showOrHide) noexcept
{
auto& gci = ::Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation();
gci.LockConsole();
auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); });
// ConsoleInputThreadProcWin32 calls VtIo::CreatePseudoWindow,
// which calls CreateWindowExW, which causes a WM_SIZE message.
// In short, this function might be called before _pVtRenderEngine exists.
// See PtySignalInputThread::CreatePseudoWindow().
if (!_pVtRenderEngine)
{
return;
}
LOG_IF_FAILED(_pVtRenderEngine->SetWindowVisibility(showOrHide));
}
// Method Description:
// - Create and start the signal thread. The signal thread can be created
// independent of the i/o threads, and doesn't require a client first
@@ -302,69 +270,6 @@ void VtIo::SetWindowVisibility(bool showOrHide) noexcept
return S_OK;
}
// Method Description:
// - Prevent the renderer from emitting output on the next resize. This prevents
// the host from echoing a resize to the terminal that requested it.
// Arguments:
// - <none>
// Return Value:
// - S_OK if the renderer successfully suppressed the next repaint, otherwise an
// appropriate HRESULT indicating failure.
[[nodiscard]] HRESULT VtIo::SuppressResizeRepaint()
{
auto hr = S_OK;
if (_pVtRenderEngine)
{
hr = _pVtRenderEngine->SuppressResizeRepaint();
}
return hr;
}
// Method Description:
// - Attempts to set the initial cursor position, if we're looking for it.
// If we're not trying to inherit the cursor, does nothing.
// Arguments:
// - coordCursor: The initial position of the cursor.
// Return Value:
// - S_OK if we successfully inherited the cursor or did nothing, else an
// appropriate HRESULT
[[nodiscard]] HRESULT VtIo::SetCursorPosition(const til::point coordCursor)
{
auto hr = S_OK;
if (_lookingForCursorPosition)
{
if (_pVtRenderEngine)
{
hr = _pVtRenderEngine->InheritCursor(coordCursor);
}
_lookingForCursorPosition = false;
}
return hr;
}
[[nodiscard]] HRESULT VtIo::SwitchScreenBuffer(const bool useAltBuffer)
{
auto hr = S_OK;
if (_pVtRenderEngine)
{
hr = _pVtRenderEngine->SwitchScreenBuffer(useAltBuffer);
}
return hr;
}
void VtIo::CloseInput()
{
_pVtInputThread = nullptr;
SendCloseEvent();
}
void VtIo::CloseOutput()
{
auto& g = ServiceLocator::LocateGlobals();
g.getConsoleInformation().GetActiveOutputBuffer().SetTerminalConnection(nullptr);
}
void VtIo::SendCloseEvent()
{
LockConsole();
@@ -379,73 +284,16 @@ void VtIo::SendCloseEvent()
}
}
// The name of this method is an analogy to TCP_CORK. It instructs
// the VT renderer to stop flushing its buffer to the output pipe.
// Don't forget to uncork it!
void VtIo::CorkRenderer(bool corked) const noexcept
// Returns true for C0 characters and C1 [single-character] CSI.
// A copy of isActionableFromGround() from stateMachine.cpp.
static constexpr bool IsControlCharacter(wchar_t wch) noexcept
{
_pVtRenderEngine->Cork(corked);
}
#ifdef UNIT_TESTING
// Method Description:
// - This is a test helper method. It can be used to trick VtIo into responding
// true to `IsUsingVt`, which will cause the console host to act in conpty
// mode.
// Arguments:
// - vtRenderEngine: a VT renderer that our VtIo should use as the vt engine during these tests
// Return Value:
// - <none>
void VtIo::EnableConptyModeForTests(std::unique_ptr<Microsoft::Console::Render::VtEngine> vtRenderEngine, const bool resizeQuirk)
{
_initialized = true;
_resizeQuirk = resizeQuirk;
_pVtRenderEngine = std::move(vtRenderEngine);
}
#endif
// Method Description:
// - Returns true if the Resize Quirk is enabled. This changes the behavior of
// conpty to _not_ InvalidateAll the entire viewport on a resize operation.
// This is used by the Windows Terminal, because it is prepared to be
// connected to a conpty, and handles its own buffer specifically for a
// conpty scenario.
// - See also: GH#3490, #4354, #4741
// Arguments:
// - <none>
// Return Value:
// - true iff we were started with the `--resizeQuirk` flag enabled.
bool VtIo::IsResizeQuirkEnabled() const
{
return _resizeQuirk;
}
// Method Description:
// - Manually tell the renderer that it should emit a "Erase Scrollback"
// sequence to the connected terminal. We need to do this in certain cases
// that we've identified where we believe the client wanted the entire
// terminal buffer cleared, not just the viewport. For more information, see
// GH#3126.
// Arguments:
// - <none>
// Return Value:
// - S_OK if we wrote the sequences successfully, otherwise an appropriate HRESULT
[[nodiscard]] HRESULT VtIo::ManuallyClearScrollback() const noexcept
{
if (_pVtRenderEngine)
{
return _pVtRenderEngine->ManuallyClearScrollback();
}
return S_OK;
}
[[nodiscard]] HRESULT VtIo::RequestMouseMode(bool enable) const noexcept
{
if (_pVtRenderEngine)
{
return _pVtRenderEngine->RequestMouseMode(enable);
}
return S_OK;
// This is equivalent to:
// return (wch <= 0x1f) || (wch >= 0x7f && wch <= 0x9f);
// It's written like this to get MSVC to emit optimal assembly for findActionableFromGround.
// It lacks the ability to turn boolean operators into binary operations and also happens
// to fail to optimize the printable-ASCII range check into a subtraction & comparison.
return (wch <= 0x1f) | (static_cast<wchar_t>(wch - 0x7f) <= 0x20);
}
// Formats the given console attributes to their closest VT equivalent.
@@ -510,3 +358,411 @@ void VtIo::FormatAttributes(std::wstring& target, const TextAttribute& attribute
target.append(bufW, len);
}
VtIo::Writer VtIo::GetWriter() noexcept
{
_corked += 1;
return Writer{ this };
}
VtIo::Writer::Writer(VtIo* io) noexcept :
_io{ io }
{
}
VtIo::Writer::~Writer() noexcept
{
// If _io is non-null, then we didn't call Submit, e.g. because of an exception.
// We need to avoid flushing the buffer in that case.
if (_io)
{
_io->_writerTainted = true;
_io->_uncork();
}
}
VtIo::Writer::Writer(Writer&& other) noexcept :
_io{ std::exchange(other._io, nullptr) }
{
}
VtIo::Writer& VtIo::Writer::operator=(Writer&& other) noexcept
{
if (this != &other)
{
this->~Writer();
_io = std::exchange(other._io, nullptr);
}
return *this;
}
VtIo::Writer::operator bool() const noexcept
{
return _io != nullptr;
}
void VtIo::Writer::Submit()
{
const auto io = std::exchange(_io, nullptr);
io->_uncork();
}
void VtIo::_uncork()
{
_corked -= 1;
if (_corked <= 0)
{
_flushNow();
}
}
void VtIo::_flushNow()
{
size_t minSize = 0;
if (_writerRestoreCursor)
{
minSize = 4;
_writerRestoreCursor = false;
_back.append("\x1b\x38"); // DECRC: DEC Restore Cursor (+ attributes)
}
if (_overlappedPending)
{
_overlappedPending = false;
DWORD written;
if (FAILED(Utils::GetOverlappedResultSameThread(_overlapped, &written)))
{
// Not much we can do here. Let's treat this like a ERROR_BROKEN_PIPE.
_hOutput.reset();
SendCloseEvent();
}
}
_front.clear();
_front.swap(_back);
// If it's >128KiB large and twice as large as the previous buffer, free the memory.
// This ensures that there's a pathway for shrinking the buffer from large sizes.
if (const auto cap = _back.capacity(); cap > 128 * 1024 && cap / 2 > _front.size())
{
_back = std::string{};
}
// We encountered an exception and shouldn't flush the broken pieces.
if (_writerTainted)
{
_writerTainted = false;
return;
}
// If _back (now _front) was empty, we can return early. If all _front contains is
// DECSC/DECRC that was added by BackupCursor & us, we can also return early.
if (_front.size() <= minSize)
{
return;
}
// No point in calling WriteFile if we already encountered ERROR_BROKEN_PIPE.
// We do this after the above, so that _back doesn't grow indefinitely.
if (!_hOutput)
{
return;
}
const auto write = gsl::narrow_cast<DWORD>(_front.size());
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ConPTY WriteFile",
TraceLoggingCountedUtf8String(_front.data(), write, "buffer"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
for (;;)
{
if (WriteFile(_hOutput.get(), _front.data(), write, nullptr, _overlapped))
{
return;
}
switch (const auto gle = GetLastError())
{
case ERROR_BROKEN_PIPE:
_hOutput.reset();
SendCloseEvent();
return;
case ERROR_IO_PENDING:
_overlappedPending = true;
return;
default:
LOG_WIN32(gle);
return;
}
}
}
void VtIo::Writer::BackupCursor() const
{
if (!_io->_writerRestoreCursor)
{
_io->_writerRestoreCursor = true;
_io->_back.append("\x1b\x37"); // DECSC: DEC Save Cursor (+ attributes)
}
}
void VtIo::Writer::WriteUTF8(std::string_view str) const
{
_io->_back.append(str);
}
void VtIo::Writer::WriteUTF16(std::wstring_view str) const
{
if (str.empty())
{
return;
}
const auto existingUTF8Len = _io->_back.size();
const auto incomingUTF16Len = str.size();
// When converting from UTF-16 to UTF-8 the worst case is 3 bytes per UTF-16 code unit.
const auto incomingUTF8Cap = incomingUTF16Len * 3;
const auto totalUTF8Cap = existingUTF8Len + incomingUTF8Cap;
// Since WideCharToMultiByte() only supports `int` lengths, we check for an overflow past INT_MAX/3.
// We also check for an overflow of totalUTF8Cap just to be sure.
if (incomingUTF16Len > gsl::narrow_cast<size_t>(INT_MAX / 3) || totalUTF8Cap <= existingUTF8Len)
{
THROW_HR_MSG(E_INVALIDARG, "string too large");
}
// NOTE: Throwing inside resize_and_overwrite invokes undefined behavior.
_io->_back._Resize_and_overwrite(totalUTF8Cap, [&](char* buf, const size_t) noexcept {
const auto len = WideCharToMultiByte(CP_UTF8, 0, str.data(), gsl::narrow_cast<int>(incomingUTF16Len), buf + existingUTF8Len, gsl::narrow_cast<int>(incomingUTF8Cap), nullptr, nullptr);
return existingUTF8Len + std::max(0, len);
});
}
// When DISABLE_NEWLINE_AUTO_RETURN is not set (Bad! Don't do it!) we'll do newline translation for you.
// That's the only difference of this function from WriteUTF16: It does LF -> CRLF translation.
void VtIo::Writer::WriteUTF16TranslateCRLF(std::wstring_view str) const
{
const auto beg = str.begin();
const auto end = str.end();
auto begCopy = beg;
auto endCopy = beg;
// Our goal is to prepend a \r in front of \n that don't already have one.
// There's no point in replacing \n\n\n with \r\n\r\n\r\n, however. It's just fine to do \r\n\n\n.
// After all we aren't a text file, we're a terminal, and \r\n and \n are identical if we're at the first column.
for (;;)
{
// To do so, we'll first find the next LF and emit the unrelated text before it.
endCopy = std::find(endCopy, end, L'\n');
WriteUTF16({ begCopy, endCopy });
begCopy = endCopy;
// Done? Great.
if (begCopy == end)
{
break;
}
// We only need to prepend a CR if the LF isn't already preceded by one.
if (begCopy == beg || begCopy[-1] != L'\r')
{
_io->_back.push_back('\r');
}
// Now extend the end of the next WriteUTF16 *past* this series of CRs and LFs.
// We've just ensured that the LF is preceded by a CR, so we can skip all this safely.
while (++endCopy != end && (*endCopy == L'\n' || *endCopy == L'\r'))
{
}
}
}
// Same as WriteUTF16, but replaces control characters with spaces.
// We don't outright remove them because that would mess up the cursor position.
// conhost traditionally assigned control chars a width of 1 when in the raw write mode.
void VtIo::Writer::WriteUTF16StripControlChars(std::wstring_view str) const
{
auto it = str.data();
const auto end = it + str.size();
// We can picture `str` as a repeated sequence of regular characters followed by control characters.
while (it != end)
{
const auto begControlChars = FindActionableControlCharacter(it, end - it);
WriteUTF16({ it, begControlChars });
for (it = begControlChars; it != end && IsControlCharacter(*it); ++it)
{
WriteUCS2StripControlChars(*it);
}
}
}
void VtIo::Writer::WriteUCS2(wchar_t ch) const
{
char buf[4];
size_t len = 0;
if (til::is_surrogate(ch))
{
ch = UNICODE_REPLACEMENT;
}
if (ch <= 0x7f)
{
buf[len++] = static_cast<char>(ch);
}
else if (ch <= 0x7ff)
{
buf[len++] = static_cast<char>(0xc0 | (ch >> 6));
buf[len++] = static_cast<char>(0x80 | (ch & 0x3f));
}
else
{
buf[len++] = static_cast<char>(0xe0 | (ch >> 12));
buf[len++] = static_cast<char>(0x80 | ((ch >> 6) & 0x3f));
buf[len++] = static_cast<char>(0x80 | (ch & 0x3f));
}
_io->_back.append(buf, len);
}
void VtIo::Writer::WriteUCS2StripControlChars(wchar_t ch) const
{
if (ch < 0x20)
{
static constexpr wchar_t lut[] = {
// clang-format off
L' ', L'', L'', L'', L'', L'', L'', L'', L'', L'', L'', L'', L'', L'', L'', L'',
L'', L'', L'', L'', L'', L'§', L'', L'', L'', L'', L'', L'', L'', L'', L'', L'',
// clang-format on
};
ch = lut[ch];
}
else if (ch == 0x7F)
{
ch = L'';
}
else if (ch > 0x7F && ch < 0xA0)
{
ch = L'?';
}
WriteUCS2(ch);
}
// CUP: Cursor Position
void VtIo::Writer::WriteCUP(til::point position) const
{
fmt::format_to(std::back_inserter(_io->_back), FMT_COMPILE("\x1b[{};{}H"), position.y + 1, position.x + 1);
}
// DECTCEM: Text Cursor Enable
void VtIo::Writer::WriteDECTCEM(bool enabled) const
{
char buf[] = "\x1b[?25h";
buf[std::size(buf) - 2] = enabled ? 'h' : 'l';
_io->_back.append(&buf[0], std::size(buf) - 1);
}
// SGR 1006: SGR Extended Mouse Mode
void VtIo::Writer::WriteSGR1006(bool enabled) const
{
char buf[] = "\x1b[?1003;1006h";
buf[std::size(buf) - 2] = enabled ? 'h' : 'l';
_io->_back.append(&buf[0], std::size(buf) - 1);
}
// DECAWM: Autowrap Mode
void VtIo::Writer::WriteDECAWM(bool enabled) const
{
char buf[] = "\x1b[?7h";
buf[std::size(buf) - 2] = enabled ? 'h' : 'l';
_io->_back.append(&buf[0], std::size(buf) - 1);
}
// ASB: Alternate Screen Buffer
void VtIo::Writer::WriteASB(bool enabled) const
{
char buf[] = "\x1b[?1049h";
buf[std::size(buf) - 2] = enabled ? 'h' : 'l';
_io->_back.append(&buf[0], std::size(buf) - 1);
}
void VtIo::Writer::WriteAttributes(const TextAttribute& attributes) const
{
FormatAttributes(_io->_back, attributes);
}
void VtIo::Writer::WriteInfos(til::point target, std::span<const CHAR_INFO> infos) const
{
const auto beg = infos.begin();
const auto end = infos.end();
const auto last = end - 1;
WORD attributes = 0xffff;
WriteCUP(target);
for (auto it = beg; it != end; ++it)
{
const auto& ci = *it;
auto ch = ci.Char.UnicodeChar;
auto wide = WI_IsAnyFlagSet(ci.Attributes, COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE);
if (wide)
{
if (WI_IsAnyFlagSet(ci.Attributes, COMMON_LVB_LEADING_BYTE))
{
if (it == last)
{
// The leading half of a wide glyph won't fit into the last remaining column.
// --> Replace it with a space.
ch = L' ';
wide = false;
}
}
else
{
if (it == beg)
{
// The trailing half of a wide glyph won't fit into the first column. It's incomplete.
// --> Replace it with a space.
ch = L' ';
wide = false;
}
else
{
// Trailing halves of glyphs are ignored within the run. We only emit the leading half.
continue;
}
}
}
if (attributes != ci.Attributes)
{
attributes = ci.Attributes;
WriteAttributes(TextAttribute{ attributes });
}
int repeat = 1;
if (wide && (til::is_surrogate(ch) || IsControlCharacter(ch)))
{
// Control characters, U+FFFD, etc. are narrow characters, so if the caller
// asked for a wide glyph we need to repeat the replacement character twice.
repeat++;
}
do
{
WriteUCS2StripControlChars(ch);
} while (--repeat);
}
}

View File

@@ -3,77 +3,101 @@
#pragma once
#include "../renderer/vt/vtrenderer.hpp"
#include "VtInputThread.hpp"
#include "PtySignalInputThread.hpp"
class ConsoleArguments;
namespace Microsoft::Console::Render
{
class VtEngine;
}
namespace Microsoft::Console::VirtualTerminal
{
class VtIo
{
public:
struct Writer
{
Writer() = default;
Writer(VtIo* io) noexcept;
~Writer() noexcept;
Writer(const Writer&) = delete;
Writer& operator=(const Writer&) = delete;
Writer(Writer&& other) noexcept;
Writer& operator=(Writer&& other) noexcept;
explicit operator bool() const noexcept;
void Submit();
void BackupCursor() const;
void WriteUTF8(std::string_view str) const;
void WriteUTF16(std::wstring_view str) const;
void WriteUTF16TranslateCRLF(std::wstring_view str) const;
void WriteUTF16StripControlChars(std::wstring_view str) const;
void WriteUCS2(wchar_t ch) const;
void WriteUCS2StripControlChars(wchar_t ch) const;
void WriteCUP(til::point position) const;
void WriteDECTCEM(bool enabled) const;
void WriteSGR1006(bool enabled) const;
void WriteDECAWM(bool enabled) const;
void WriteASB(bool enabled) const;
void WriteAttributes(const TextAttribute& attributes) const;
void WriteInfos(til::point target, std::span<const CHAR_INFO> infos) const;
private:
VtIo* _io = nullptr;
};
friend struct Writer;
static void FormatAttributes(std::string& target, const TextAttribute& attributes);
static void FormatAttributes(std::wstring& target, const TextAttribute& attributes);
VtIo();
[[nodiscard]] HRESULT Initialize(const ConsoleArguments* const pArgs);
[[nodiscard]] HRESULT CreateAndStartSignalThread() noexcept;
[[nodiscard]] HRESULT CreateIoHandlers() noexcept;
bool IsUsingVt() const;
[[nodiscard]] HRESULT StartIfNeeded();
[[nodiscard]] HRESULT SuppressResizeRepaint();
[[nodiscard]] HRESULT SetCursorPosition(const til::point coordCursor);
[[nodiscard]] HRESULT SwitchScreenBuffer(const bool useAltBuffer);
void SendCloseEvent();
void CloseInput();
void CloseOutput();
void CorkRenderer(bool corked) const noexcept;
#ifdef UNIT_TESTING
void EnableConptyModeForTests(std::unique_ptr<Microsoft::Console::Render::VtEngine> vtRenderEngine, const bool resizeQuirk = false);
#endif
bool IsResizeQuirkEnabled() const;
[[nodiscard]] HRESULT ManuallyClearScrollback() const noexcept;
[[nodiscard]] HRESULT RequestMouseMode(bool enable) const noexcept;
void CreatePseudoWindow();
void SetWindowVisibility(bool showOrHide) noexcept;
Writer GetWriter() noexcept;
private:
[[nodiscard]] HRESULT _Initialize(const HANDLE InHandle, const HANDLE OutHandle, _In_opt_ const HANDLE SignalHandle);
void _uncork();
void _flushNow();
// After CreateIoHandlers is called, these will be invalid.
wil::unique_hfile _hInput;
wil::unique_hfile _hOutput;
// After CreateAndStartSignalThread is called, this will be invalid.
wil::unique_hfile _hSignal;
bool _initialized;
bool _lookingForCursorPosition;
bool _resizeQuirk{ false };
bool _closeEventSent{ false };
std::unique_ptr<Microsoft::Console::Render::VtEngine> _pVtRenderEngine;
std::unique_ptr<Microsoft::Console::VtInputThread> _pVtInputThread;
std::unique_ptr<Microsoft::Console::PtySignalInputThread> _pPtySignalInputThread;
[[nodiscard]] HRESULT _Initialize(const HANDLE InHandle, const HANDLE OutHandle, _In_opt_ const HANDLE SignalHandle);
// We use two buffers: A front and a back buffer. The front buffer is the one we're currently
// sending to the terminal (it's being "presented" = it's on the "front" & "visible").
// The back buffer is the one we're concurrently writing to.
std::string _front;
std::string _back;
OVERLAPPED* _overlapped = nullptr;
OVERLAPPED _overlappedBuf{};
wil::unique_event _overlappedEvent;
bool _overlappedPending = false;
bool _writerRestoreCursor = false;
bool _writerTainted = false;
bool _initialized = false;
bool _lookingForCursorPosition = false;
bool _closeEventSent = false;
int _corked = 0;
#ifdef UNIT_TESTING
friend class VtIoTests;

View File

@@ -74,6 +74,7 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon
LockConsole();
const auto unlock = wil::scope_exit([&] { UnlockConsole(); });
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto& screenBuffer = screenInfo.GetActiveBuffer();
const auto bufferSize = screenBuffer.GetBufferSize();
FillConsoleResult result;
@@ -83,6 +84,122 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon
return {};
}
if (auto writer = gci.GetVtWriterForBuffer(&screenInfo))
{
writer.BackupCursor();
const auto h = bufferSize.Height();
const auto w = bufferSize.Width();
auto y = startingCoordinate.y;
auto input = static_cast<const uint16_t*>(data);
size_t inputPos = 0;
til::small_vector<CHAR_INFO, 1024> infoBuffer;
Viewport unused;
infoBuffer.resize(gsl::narrow_cast<size_t>(w));
while (y < h && inputPos < lengthToWrite)
{
const auto beg = y == startingCoordinate.y ? startingCoordinate.x : 0;
const auto columnsAvailable = w - beg;
til::CoordType columns = 0;
const auto readViewport = Viewport::FromInclusive({ beg, y, w - 1, y });
THROW_IF_FAILED(ReadConsoleOutputWImplHelper(screenInfo, infoBuffer, readViewport, unused));
switch (mode)
{
case FillConsoleMode::WriteAttribute:
for (; columns < columnsAvailable && inputPos < lengthToWrite; ++columns, ++inputPos)
{
infoBuffer[columns].Attributes = input[inputPos];
}
break;
case FillConsoleMode::FillAttribute:
for (const auto attr = input[0]; columns < columnsAvailable && inputPos < lengthToWrite; ++columns, ++inputPos)
{
infoBuffer[columns].Attributes = attr;
}
break;
case FillConsoleMode::WriteCharacter:
for (; columns < columnsAvailable && inputPos < lengthToWrite; ++inputPos)
{
const auto ch = input[inputPos];
if (ch >= 0x80 && IsGlyphFullWidth(ch))
{
// If the wide glyph doesn't fit into the last column, pad it with whitespace.
if ((columns + 1) >= columnsAvailable)
{
auto& lead = infoBuffer[columns++];
lead.Char.UnicodeChar = L' ';
lead.Attributes = lead.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE);
break;
}
auto& lead = infoBuffer[columns++];
lead.Char.UnicodeChar = ch;
lead.Attributes = lead.Attributes & ~COMMON_LVB_TRAILING_BYTE | COMMON_LVB_LEADING_BYTE;
auto& trail = infoBuffer[columns++];
trail.Char.UnicodeChar = ch;
trail.Attributes = trail.Attributes & ~COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE;
}
else
{
auto& lead = infoBuffer[columns++];
lead.Char.UnicodeChar = ch;
lead.Attributes = lead.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE);
}
}
break;
case FillConsoleMode::FillCharacter:
// Identical to WriteCharacter above, but with the if() and for() swapped.
if (const auto ch = input[0]; ch >= 0x80 && IsGlyphFullWidth(ch))
{
for (; columns < columnsAvailable && inputPos < lengthToWrite; ++inputPos)
{
// If the wide glyph doesn't fit into the last column, pad it with whitespace.
if ((columns + 1) >= columnsAvailable)
{
auto& lead = infoBuffer[columns++];
lead.Char.UnicodeChar = L' ';
lead.Attributes = lead.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE);
break;
}
auto& lead = infoBuffer[columns++];
lead.Char.UnicodeChar = ch;
lead.Attributes = lead.Attributes & ~COMMON_LVB_TRAILING_BYTE | COMMON_LVB_LEADING_BYTE;
auto& trail = infoBuffer[columns++];
trail.Char.UnicodeChar = ch;
trail.Attributes = trail.Attributes & ~COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE;
}
}
else
{
for (; columns < columnsAvailable && inputPos < lengthToWrite; ++inputPos)
{
auto& lead = infoBuffer[columns++];
lead.Char.UnicodeChar = ch;
lead.Attributes = lead.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE);
}
}
break;
}
const auto writeViewport = Viewport::FromInclusive({ beg, y, beg + columns - 1, y });
THROW_IF_FAILED(WriteConsoleOutputWImplHelper(screenInfo, infoBuffer, w, writeViewport, unused));
y += 1;
result.cellsModified += columns;
}
result.lengthRead = inputPos;
writer.Submit();
}
else
{
// Technically we could always pass `data` as `uint16_t*`, because `wchar_t` is guaranteed to be 16 bits large.
// However, OutputCellIterator is terrifyingly unsafe code and so we don't do that.
@@ -261,13 +378,37 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon
size_t& cellsModified,
const bool enablePowershellShim) noexcept
{
UNREFERENCED_PARAMETER(enablePowershellShim);
try
{
LockConsole();
const auto unlock = wil::scope_exit([&] { UnlockConsole(); });
// GH#3126 - This is a shim for powershell's `Clear-Host` function. In
// the vintage console, `Clear-Host` is supposed to clear the entire
// buffer. In conpty however, there's no difference between the viewport
// and the entirety of the buffer. We're going to see if this API call
// exactly matched the way we expect powershell to call it. If it does,
// then let's manually emit a ^[[3J to the connected terminal, so that
// their entire buffer will be cleared as well.
if (enablePowershellShim)
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (const auto writer = gci.GetVtWriterForBuffer(&OutContext))
{
const auto currentBufferDimensions{ OutContext.GetBufferSize().Dimensions() };
const auto wroteWholeBuffer = lengthToWrite == (currentBufferDimensions.area<size_t>());
const auto startedAtOrigin = startingCoordinate == til::point{ 0, 0 };
const auto wroteSpaces = attribute == (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED);
if (wroteWholeBuffer && startedAtOrigin && wroteSpaces)
{
// PowerShell has previously called FillConsoleOutputCharacterW() which triggered a call to WriteClearScreen().
cellsModified = lengthToWrite;
return S_OK;
}
}
}
cellsModified = FillConsoleImpl(OutContext, FillConsoleMode::FillAttribute, &attribute, lengthToWrite, startingCoordinate).cellsModified;
return S_OK;
}
@@ -299,8 +440,6 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon
LockConsole();
const auto unlock = wil::scope_exit([&] { UnlockConsole(); });
cellsModified = FillConsoleImpl(OutContext, FillConsoleMode::FillCharacter, &character, lengthToWrite, startingCoordinate).lengthRead;
// GH#3126 - This is a shim for powershell's `Clear-Host` function. In
// the vintage console, `Clear-Host` is supposed to clear the entire
// buffer. In conpty however, there's no difference between the viewport
@@ -311,7 +450,7 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon
if (enablePowershellShim)
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (gci.IsInVtIoMode())
if (auto writer = gci.GetVtWriterForBuffer(&OutContext))
{
const auto currentBufferDimensions{ OutContext.GetBufferSize().Dimensions() };
const auto wroteWholeBuffer = lengthToWrite == (currentBufferDimensions.area<size_t>());
@@ -320,14 +459,15 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon
if (wroteWholeBuffer && startedAtOrigin && wroteSpaces)
{
// It's important that we flush the renderer at this point so we don't
// have any pending output rendered after the scrollback is cleared.
ServiceLocator::LocateGlobals().pRender->TriggerFlush(false);
return gci.GetVtIo()->ManuallyClearScrollback();
WriteClearScreen(OutContext);
writer.Submit();
cellsModified = lengthToWrite;
return S_OK;
}
}
}
cellsModified = FillConsoleImpl(OutContext, FillConsoleMode::FillCharacter, &character, lengthToWrite, startingCoordinate).lengthRead;
return S_OK;
}
CATCH_RETURN();

View File

@@ -14,6 +14,7 @@
#include "dbcs.h"
#include "handle.h"
#include "misc.h"
#include "VtIo.hpp"
#include "../types/inc/convert.hpp"
#include "../types/inc/GlyphWidth.hpp"
@@ -21,12 +22,14 @@
#include "../interactivity/inc/ServiceLocator.hpp"
#pragma hdrstop
using namespace Microsoft::Console::Types;
using namespace Microsoft::Console::VirtualTerminal;
using Microsoft::Console::Interactivity::ServiceLocator;
using Microsoft::Console::VirtualTerminal::StateMachine;
// Used by WriteCharsLegacy.
#define IS_GLYPH_CHAR(wch) (((wch) >= L' ') && ((wch) != 0x007F))
constexpr bool controlCharPredicate(wchar_t wch)
{
return wch < L' ' || wch == 0x007F;
}
// Routine Description:
// - This routine updates the cursor position. Its input is the non-special
@@ -109,11 +112,12 @@ static void AdjustCursorPosition(SCREEN_INFORMATION& screenInfo, _In_ til::point
}
// As the name implies, this writes text without processing its control characters.
void _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const std::wstring_view& text, til::CoordType* psScrollY)
static bool _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const std::wstring_view& text, til::CoordType* psScrollY)
{
const auto wrapAtEOL = WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT);
const auto hasAccessibilityEventing = screenInfo.HasAccessibilityEventing();
auto& textBuffer = screenInfo.GetTextBuffer();
bool wrapped = false;
RowWriteState state{
.text = text,
@@ -127,8 +131,9 @@ void _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const std::wst
state.columnBegin = cursorPosition.x;
textBuffer.Replace(cursorPosition.y, textBuffer.GetCurrentAttributes(), state);
cursorPosition.x = state.columnEnd;
wrapped = wrapAtEOL && state.columnEnd >= state.columnLimit;
if (wrapAtEOL && state.columnEnd >= state.columnLimit)
if (wrapped)
{
textBuffer.SetWrapForced(cursorPosition.y, true);
}
@@ -140,6 +145,8 @@ void _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const std::wst
AdjustCursorPosition(screenInfo, cursorPosition, psScrollY);
}
return wrapped;
}
// This routine writes a string to the screen while handling control characters.
@@ -153,15 +160,16 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t
const auto width = textBuffer.GetSize().Width();
auto& cursor = textBuffer.GetCursor();
const auto wrapAtEOL = WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT);
auto it = text.begin();
const auto beg = text.begin();
const auto end = text.end();
auto it = beg;
// In VT mode, when you have a 120-column terminal you can write 120 columns without the cursor wrapping.
// Whenever the cursor is in that 120th column IsDelayedEOLWrap() will return true. I'm not sure why the VT parts
// of the code base store this as a boolean. It's also unclear why we handle this here. The intention is likely
// so that when we exit VT mode and receive a write a potentially stored delayed wrap would still be handled.
// The way this code does it however isn't correct since it handles it like the old console APIs would and
// so writing a newline while being delay wrapped will print 2 newlines.
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto writer = gci.GetVtWriterForBuffer(&screenInfo);
// If we enter this if condition, then someone wrote text in VT mode and now switched to non-VT mode.
// Since the Console APIs don't support delayed EOL wrapping, we need to first put the cursor back
// to a position that the Console APIs expect (= not delayed).
if (cursor.IsDelayedEOLWrap() && wrapAtEOL)
{
auto pos = cursor.GetPosition();
@@ -172,6 +180,11 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t
pos.x = 0;
pos.y++;
AdjustCursorPosition(screenInfo, pos, psScrollY);
if (writer)
{
writer.WriteUTF8("\r\n");
}
}
}
@@ -179,79 +192,141 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t
// If it's not set, we can just straight up give everything to WriteCharsLegacyUnprocessed.
if (WI_IsFlagClear(screenInfo.OutputMode, ENABLE_PROCESSED_OUTPUT))
{
_writeCharsLegacyUnprocessed(screenInfo, { it, end }, psScrollY);
it = end;
const auto lastCharWrapped = _writeCharsLegacyUnprocessed(screenInfo, text, psScrollY);
if (writer)
{
// We're asked to produce VT output, but also to behave as if these control characters aren't control characters.
// So, to make it work, we simply replace all the control characters with whitespace.
writer.WriteUTF16StripControlChars(text);
if (lastCharWrapped)
{
writer.WriteUTF8("\r\n");
}
writer.Submit();
}
return;
}
while (it != end)
{
const auto nextControlChar = std::find_if(it, end, [](const auto& wch) { return !IS_GLYPH_CHAR(wch); });
const auto nextControlChar = std::find_if(it, end, controlCharPredicate);
if (nextControlChar != it)
{
_writeCharsLegacyUnprocessed(screenInfo, { it, nextControlChar }, psScrollY);
const std::wstring_view chunk{ it, nextControlChar };
const auto lastCharWrapped = _writeCharsLegacyUnprocessed(screenInfo, chunk, psScrollY);
it = nextControlChar;
if (writer)
{
writer.WriteUTF16(chunk);
if (lastCharWrapped)
{
writer.WriteUTF8("\r\n");
}
}
}
for (; it != end && !IS_GLYPH_CHAR(*it); ++it)
if (it == end)
{
switch (*it)
break;
}
do
{
auto wch = *it;
auto lastCharWrapped = false;
switch (wch)
{
case UNICODE_NULL:
_writeCharsLegacyUnprocessed(screenInfo, { &tabSpaces[0], 1 }, psScrollY);
continue;
{
lastCharWrapped = _writeCharsLegacyUnprocessed(screenInfo, { &tabSpaces[0], 1 }, psScrollY);
wch = L' ';
break;
}
case UNICODE_BELL:
{
std::ignore = screenInfo.SendNotifyBeep();
continue;
break;
}
case UNICODE_BACKSPACE:
{
auto pos = cursor.GetPosition();
pos.x = textBuffer.GetRowByOffset(pos.y).NavigateToPrevious(pos.x);
AdjustCursorPosition(screenInfo, pos, psScrollY);
continue;
break;
}
case UNICODE_TAB:
{
const auto pos = cursor.GetPosition();
const auto remaining = width - pos.x;
const auto tabCount = gsl::narrow_cast<size_t>(std::min(remaining, 8 - (pos.x & 7)));
_writeCharsLegacyUnprocessed(screenInfo, { &tabSpaces[0], tabCount }, psScrollY);
continue;
lastCharWrapped = _writeCharsLegacyUnprocessed(screenInfo, { &tabSpaces[0], tabCount }, psScrollY);
break;
}
case UNICODE_LINEFEED:
{
auto pos = cursor.GetPosition();
if (WI_IsFlagClear(screenInfo.OutputMode, DISABLE_NEWLINE_AUTO_RETURN))
if (WI_IsFlagClear(screenInfo.OutputMode, DISABLE_NEWLINE_AUTO_RETURN) && pos.x != 0)
{
pos.x = 0;
// This causes the current \n to be replaced with a \r\n in the ConPTY VT output.
wch = 0;
lastCharWrapped = true;
}
textBuffer.GetMutableRowByOffset(pos.y).SetWrapForced(false);
pos.y = pos.y + 1;
AdjustCursorPosition(screenInfo, pos, psScrollY);
continue;
break;
}
case UNICODE_CARRIAGERETURN:
{
auto pos = cursor.GetPosition();
pos.x = 0;
AdjustCursorPosition(screenInfo, pos, psScrollY);
continue;
}
default:
break;
}
// As a special favor to incompetent apps that attempt to display control chars,
// convert to corresponding OEM Glyph Chars
const auto cp = ServiceLocator::LocateGlobals().getConsoleInformation().OutputCP;
const auto ch = gsl::narrow_cast<char>(*it);
wchar_t wch = 0;
const auto result = MultiByteToWideChar(cp, MB_USEGLYPHCHARS, &ch, 1, &wch, 1);
if (result == 1)
default:
{
_writeCharsLegacyUnprocessed(screenInfo, { &wch, 1 }, psScrollY);
// As a special favor to incompetent apps that attempt to display control chars,
// convert to corresponding OEM Glyph Chars
const auto cp = ServiceLocator::LocateGlobals().getConsoleInformation().OutputCP;
const auto ch = gsl::narrow_cast<char>(wch);
const auto result = MultiByteToWideChar(cp, MB_USEGLYPHCHARS, &ch, 1, &wch, 1);
if (result != 1)
{
wch = 0;
}
if (wch)
{
lastCharWrapped = _writeCharsLegacyUnprocessed(screenInfo, { &wch, 1 }, psScrollY);
}
break;
}
}
}
if (writer)
{
if (wch)
{
writer.WriteUCS2(wch);
}
if (lastCharWrapped)
{
writer.WriteUTF8("\r\n");
}
}
++it;
} while (it != end && controlCharPredicate(*it));
}
if (writer)
{
writer.Submit();
}
}
@@ -259,7 +334,58 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t
// This wrapper around StateMachine exists so that we can add the necessary ConPTY transformations.
void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str)
{
screenInfo.GetStateMachine().ProcessString(str);
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto& stateMachine = screenInfo.GetStateMachine();
// When switch between the main and alt-buffer SCREEN_INFORMATION::GetActiveBuffer()
// may change, so get the VtIo reference now, just in case.
auto writer = gci.GetVtWriterForBuffer(&screenInfo);
stateMachine.ProcessString(str);
if (writer)
{
const auto& injections = stateMachine.GetInjections();
size_t offset = 0;
const auto write = [&](size_t beg, size_t end) {
const auto chunk = til::safe_slice_abs(str, beg, end);
if (WI_IsFlagSet(screenInfo.OutputMode, DISABLE_NEWLINE_AUTO_RETURN))
{
writer.WriteUTF16(chunk);
}
else
{
writer.WriteUTF16TranslateCRLF(chunk);
}
};
for (const auto& injection : injections)
{
write(offset, injection.offset);
offset = injection.offset;
static constexpr std::array<std::string_view, 2> mapping{ {
{ "\x1b[?1004h\x1b[?9001h" }, // RIS: Focus Event Mode + Win32 Input Mode
{ "\033[?1004h" } // DECSET_FOCUS: Focus Event Mode
} };
writer.WriteUTF8(mapping[static_cast<size_t>(injection.type)]);
}
write(offset, std::wstring_view::npos);
writer.Submit();
}
}
// Erases all contents of the given screenInfo, including the current screen and scrollback.
void WriteClearScreen(SCREEN_INFORMATION& screenInfo)
{
WriteCharsVT(
screenInfo,
L"\x1b[H" // CUP to home
L"\x1b[2J" // Erase in Display: clear the screen
L"\x1b[3J" // Erase in Display: clear the scrollback buffer
);
}
// Routine Description:
@@ -284,7 +410,7 @@ void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str)
std::unique_ptr<WriteData>& waiter)
try
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (WI_IsAnyFlagSet(gci.Flags, (CONSOLE_SUSPENDED | CONSOLE_SELECTING | CONSOLE_SCROLLBAR_TRACKING)))
{
waiter = std::make_unique<WriteData>(screenInfo,
@@ -295,25 +421,16 @@ try
return CONSOLE_STATUS_WAIT;
}
const auto vtIo = ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo();
const auto restoreVtQuirk = wil::scope_exit([&]() {
if (requiresVtQuirk)
{
screenInfo.ResetIgnoreLegacyEquivalentVTAttributes();
}
if (vtIo->IsUsingVt())
{
vtIo->CorkRenderer(false);
}
});
if (requiresVtQuirk)
{
screenInfo.SetIgnoreLegacyEquivalentVTAttributes();
}
if (vtIo->IsUsingVt())
{
vtIo->CorkRenderer(true);
}
const std::wstring_view str{ pwchBuffer, *pcbBuffer / sizeof(WCHAR) };
@@ -323,7 +440,7 @@ try
}
else
{
screenInfo.GetStateMachine().ProcessString(str);
WriteCharsVT(screenInfo, str);
}
return STATUS_SUCCESS;

View File

@@ -21,6 +21,7 @@ Revision History:
void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str, til::CoordType* psScrollY);
void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str);
void WriteClearScreen(SCREEN_INFORMATION& screenInfo);
// NOTE: console lock must be held when calling this routine
// String has been translated to unicode at this point.

View File

@@ -118,12 +118,22 @@ ErrorExit2:
return Status;
}
VtIo* CONSOLE_INFORMATION::GetVtIo()
VtIo* CONSOLE_INFORMATION::GetVtIoNoCheck() noexcept
{
return &_vtIo;
}
bool CONSOLE_INFORMATION::IsInVtIoMode() const
VtIo::Writer CONSOLE_INFORMATION::GetVtWriter() noexcept
{
return _vtIo.IsUsingVt() ? _vtIo.GetWriter() : VtIo::Writer{};
}
VtIo::Writer CONSOLE_INFORMATION::GetVtWriterForBuffer(const SCREEN_INFORMATION* context) noexcept
{
return _vtIo.IsUsingVt() && (pCurrentScreenBuffer == context || pCurrentScreenBuffer == context->GetAltBuffer()) ? _vtIo.GetWriter() : VtIo::Writer{};
}
bool CONSOLE_INFORMATION::IsConPTY() const noexcept
{
return _vtIo.IsUsingVt();
}
@@ -215,20 +225,6 @@ InputBuffer* const CONSOLE_INFORMATION::GetActiveInputBuffer() const
void CONSOLE_INFORMATION::SetTitle(const std::wstring_view newTitle)
{
_Title = std::wstring{ newTitle.begin(), newTitle.end() };
// Sanitize the input if we're in pty mode. No control chars - this string
// will get emitted back to the TTY in a VT sequence, and we don't want
// to embed control characters in that string. Note that we can't use
// IsInVtIoMode for this test, because the VT I/O thread won't have
// been created when the title is first set during startup.
if (ServiceLocator::LocateGlobals().launchArgs.InConptyMode())
{
_Title.erase(std::remove_if(_Title.begin(), _Title.end(), [](auto ch) {
return ch < UNICODE_SPACE || (ch > UNICODE_DEL && ch < UNICODE_NBSP);
}),
_Title.end());
}
_TitleAndPrefix = _Prefix + _Title;
auto* const pRender = ServiceLocator::LocateGlobals().pRender;

View File

@@ -598,6 +598,9 @@ CATCH_RETURN();
return E_INVALIDARG;
}
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto writer = gci.GetVtWriterForBuffer(&context);
for (til::CoordType y = clippedRectangle.Top(); y <= clippedRectangle.BottomInclusive(); y++)
{
const auto charInfos = buffer.subspan(totalOffset, width);
@@ -606,6 +609,11 @@ CATCH_RETURN();
// Make the iterator and write to the target position.
storageBuffer.Write(OutputCellIterator(charInfos), target);
if (writer)
{
writer.WriteInfos(target, charInfos);
}
totalOffset += bufferStride;
}
@@ -615,6 +623,11 @@ CATCH_RETURN();
// Since we've managed to write part of the request, return the clamped part that we actually used.
writtenRectangle = clippedRectangle;
if (writer)
{
writer.Submit();
}
return S_OK;
}
CATCH_RETURN();
@@ -631,12 +644,23 @@ CATCH_RETURN();
try
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto writer = gci.GetVtWriterForBuffer(&context);
if (writer)
{
writer.BackupCursor();
}
const auto codepage = gci.OutputCP;
LOG_IF_FAILED(_ConvertCellsToWInplace(codepage, buffer, requestRectangle));
RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, buffer, requestRectangle.Width(), requestRectangle, writtenRectangle));
if (writer)
{
writer.Submit();
}
return S_OK;
}
CATCH_RETURN();
@@ -652,6 +676,14 @@ CATCH_RETURN();
try
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto writer = gci.GetVtWriterForBuffer(&context);
if (writer)
{
writer.BackupCursor();
}
if (!context.GetActiveBuffer().GetCurrentFont().IsTrueTypeFont())
{
// For compatibility reasons, we must maintain the behavior that munges the data if we are writing while a raster font is enabled.
@@ -664,6 +696,11 @@ CATCH_RETURN();
RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, buffer, requestRectangle.Width(), requestRectangle, writtenRectangle));
}
if (writer)
{
writer.Submit();
}
return S_OK;
}
CATCH_RETURN();

View File

@@ -50,9 +50,6 @@
<ProjectReference Include="..\..\renderer\gdi\lib\gdi.vcxproj">
<Project>{1c959542-bac2-4e55-9a6d-13251914cbb9}</Project>
</ProjectReference>
<ProjectReference Include="..\..\renderer\vt\lib\vt.vcxproj">
<Project>{990f2657-8580-4828-943f-5dd657d11842}</Project>
</ProjectReference>
<ProjectReference Include="..\..\server\lib\server.vcxproj">
<Project>{18d09a24-8240-42d6-8cb6-236eee820262}</Project>
</ProjectReference>

View File

@@ -45,9 +45,6 @@
<ProjectReference Include="..\..\renderer\gdi\lib\gdi.vcxproj">
<Project>{1c959542-bac2-4e55-9a6d-13251914cbb9}</Project>
</ProjectReference>
<ProjectReference Include="..\..\renderer\vt\lib\vt.vcxproj">
<Project>{990f2657-8580-4828-943f-5dd657d11842}</Project>
</ProjectReference>
<ProjectReference Include="..\..\server\lib\server.vcxproj">
<Project>{18d09a24-8240-42d6-8cb6-236eee820262}</Project>
</ProjectReference>

View File

@@ -2,23 +2,19 @@
// Licensed under the MIT license.
#include "precomp.h"
#include "getset.h"
#include "_output.h"
#include "_stream.h"
#include "output.h"
#include "dbcs.h"
#include "ApiRoutines.h"
#include "directio.h"
#include "handle.h"
#include "misc.h"
#include "cmdline.h"
#include "../types/inc/convert.hpp"
#include "../types/inc/viewport.hpp"
#include "ApiRoutines.h"
#include "output.h"
#include "_output.h"
#include "_stream.h"
#include "../interactivity/inc/ServiceLocator.hpp"
#include "../types/inc/convert.hpp"
#include "../types/inc/viewport.hpp"
#pragma hdrstop
@@ -367,19 +363,27 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept
WI_ClearFlag(gci.Flags, CONSOLE_USE_PRIVATE_FLAGS);
}
if (gci.IsInVtIoMode())
if (auto writer = gci.GetVtWriter())
{
auto oldMode = context.InputMode;
auto newMode = mode;
// Mouse input should be received when mouse mode is on and quick edit mode is off
// (for more information regarding the quirks of mouse mode and why/how it relates
// to quick edit mode, see GH#9970)
const auto newQuickEditMode{ WI_IsFlagSet(gci.Flags, CONSOLE_QUICK_EDIT_MODE) };
const auto oldMouseMode{ !oldQuickEditMode && WI_IsFlagSet(context.InputMode, ENABLE_MOUSE_INPUT) };
const auto newMouseMode{ !newQuickEditMode && WI_IsFlagSet(mode, ENABLE_MOUSE_INPUT) };
WI_ClearFlagIf(oldMode, ENABLE_MOUSE_INPUT, oldQuickEditMode);
WI_ClearFlagIf(newMode, ENABLE_MOUSE_INPUT, newQuickEditMode);
if (oldMouseMode != newMouseMode)
if (const auto diff = oldMode ^ newMode)
{
LOG_IF_FAILED(gci.GetVtIo()->RequestMouseMode(newMouseMode));
if (WI_IsFlagSet(diff, ENABLE_MOUSE_INPUT))
{
writer.WriteSGR1006(WI_IsFlagSet(newMode, ENABLE_MOUSE_INPUT));
}
}
writer.Submit();
}
context.InputMode = mode;
@@ -416,7 +420,6 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept
{
try
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
@@ -426,6 +429,7 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept
auto& screenInfo = context.GetActiveBuffer();
const auto dwOldMode = screenInfo.OutputMode;
const auto dwNewMode = mode;
const auto diff = dwOldMode ^ dwNewMode;
screenInfo.OutputMode = dwNewMode;
@@ -437,19 +441,26 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept
screenInfo.GetStateMachine().ResetState();
}
// if we changed rendering modes then redraw the output buffer,
// but only do this if we're not in conpty mode.
if (!gci.IsInVtIoMode() &&
(WI_IsFlagSet(dwNewMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING) != WI_IsFlagSet(dwOldMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING) ||
WI_IsFlagSet(dwNewMode, ENABLE_LVB_GRID_WORLDWIDE) != WI_IsFlagSet(dwOldMode, ENABLE_LVB_GRID_WORLDWIDE)))
// if we changed rendering modes then redraw the output buffer.
if (WI_IsAnyFlagSet(diff, ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_LVB_GRID_WORLDWIDE))
{
auto* pRender = ServiceLocator::LocateGlobals().pRender;
if (pRender)
if (const auto pRender = ServiceLocator::LocateGlobals().pRender)
{
pRender->TriggerRedrawAll();
}
}
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (auto writer = gci.GetVtWriterForBuffer(&context))
{
if (WI_IsFlagSet(diff, ENABLE_WRAP_AT_EOL_OUTPUT))
{
writer.WriteDECAWM(WI_IsFlagSet(dwNewMode, ENABLE_WRAP_AT_EOL_OUTPUT));
}
writer.Submit();
}
return S_OK;
}
CATCH_RETURN();
@@ -466,6 +477,61 @@ void ApiRoutines::SetConsoleActiveScreenBufferImpl(SCREEN_INFORMATION& newContex
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (&newContext.GetActiveBuffer() == &gci.GetActiveOutputBuffer())
{
return;
}
if (auto writer = gci.GetVtWriter())
{
const auto viewport = gci.GetActiveOutputBuffer().GetBufferSize();
const auto size = viewport.Dimensions();
const auto area = static_cast<size_t>(viewport.Width() * viewport.Height());
auto& main = newContext.GetMainBuffer();
auto& alt = newContext.GetActiveBuffer();
const auto hasAltBuffer = &alt != &main;
// TODO GH#5094: This could use xterm's XTWINOPS "\e[8;<height>;<width>t" escape sequence here.
THROW_IF_NTSTATUS_FAILED(main.ResizeTraditional(size));
main.SetViewportSize(&size);
if (hasAltBuffer)
{
THROW_IF_NTSTATUS_FAILED(alt.ResizeTraditional(size));
alt.SetViewportSize(&size);
}
Viewport read;
til::small_vector<CHAR_INFO, 1024> infos;
infos.resize(area, CHAR_INFO{ L' ', FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED });
const auto dumpScreenInfo = [&](SCREEN_INFORMATION& screenInfo) {
THROW_IF_FAILED(ReadConsoleOutputWImpl(screenInfo, infos, viewport, read));
for (til::CoordType i = 0; i < size.height; i++)
{
writer.WriteInfos({ 0, i }, { infos.begin() + i * size.width, static_cast<size_t>(size.width) });
}
writer.WriteCUP(screenInfo.GetTextBuffer().GetCursor().GetPosition());
writer.WriteAttributes(screenInfo.GetAttributes());
writer.WriteDECTCEM(screenInfo.GetTextBuffer().GetCursor().IsVisible());
writer.WriteDECAWM(WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT));
};
writer.WriteASB(false);
dumpScreenInfo(main);
if (hasAltBuffer)
{
writer.WriteASB(true);
dumpScreenInfo(alt);
}
writer.Submit();
}
SetActiveScreenBuffer(newContext.GetActiveBuffer());
}
CATCH_LOG();
@@ -565,6 +631,8 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
cursor.SetPosition(clampedCursorPosition);
}
// TODO GH#5094: This could use xterm's XTWINOPS "\e[8;<height>;<width>t" escape sequence here.
return S_OK;
}
CATCH_RETURN();
@@ -621,7 +689,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
// Only do this if we actually changed the value of the palette though -
// this API gets called all the time to change all sorts of things, but
// not necessarily the palette.
if (changedOneTableEntry && !gci.IsInVtIoMode())
if (changedOneTableEntry)
{
if (auto* pRender{ ServiceLocator::LocateGlobals().pRender })
{
@@ -681,6 +749,8 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
cursor.SetPosition(clampedCursorPosition);
}
// TODO GH#5094: This could use xterm's XTWINOPS "\e[8;<height>;<width>t" escape sequence here.
return S_OK;
}
CATCH_RETURN();
@@ -713,7 +783,11 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
// MSFT: 15813316 - Try to use this SetCursorPosition call to inherit the cursor position.
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
RETURN_IF_FAILED(gci.GetVtIo()->SetCursorPosition(position));
if (auto writer = gci.GetVtWriterForBuffer(&context))
{
writer.WriteCUP(position);
writer.Submit();
}
RETURN_IF_NTSTATUS_FAILED(buffer.SetCursorPosition(position, true));
@@ -789,6 +863,13 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
context.SetCursorInformation(size, isVisible);
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (auto writer = gci.GetVtWriterForBuffer(&context))
{
writer.WriteDECTCEM(isVisible);
writer.Submit();
}
return S_OK;
}
CATCH_RETURN();
@@ -835,7 +916,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
// if we're headless, not so much. However, GetMaxWindowSizeInCharacters
// will only return the buffer size, so we can't use that to clip the arg here.
// So only clip the requested size if we're not headless
if (g.getConsoleInformation().IsInVtIoMode())
if (g.getConsoleInformation().IsConPTY())
{
// SetViewportRect doesn't cause the buffer to resize. Manually resize the buffer.
RETURN_IF_NTSTATUS_FAILED(context.ResizeScreenBuffer(Viewport::FromInclusive(Window).Dimensions(), false));
@@ -854,14 +935,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
context.PostUpdateWindowSize();
// Use WriteToScreen to invalidate the viewport with the renderer.
// GH#3490 - If we're in conpty mode, don't invalidate the entire
// viewport. In conpty mode, the VtEngine will later decide what
// part of the buffer actually needs to be re-sent to the terminal.
if (!(g.getConsoleInformation().IsInVtIoMode() &&
g.getConsoleInformation().GetVtIo()->IsResizeQuirkEnabled()))
{
WriteToScreen(context, context.GetViewport());
}
WriteToScreen(context, context.GetViewport());
}
return S_OK;
}
@@ -913,8 +987,8 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
const til::inclusive_rect& source,
const til::point target,
std::optional<til::inclusive_rect> clip,
const wchar_t fillCharacter,
const WORD fillAttribute,
wchar_t fillCharacter,
WORD fillAttribute,
const bool enableCmdShim) noexcept
{
try
@@ -922,44 +996,73 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
auto& buffer = context.GetActiveBuffer();
TextAttribute useThisAttr(fillAttribute);
ScrollRegion(buffer, source, clip, target, fillCharacter, useThisAttr);
auto hr = S_OK;
// GH#3126 - This is a shim for cmd's `cls` function. In the
// legacy console, `cls` is supposed to clear the entire buffer. In
// conpty however, there's no difference between the viewport and the
// entirety of the buffer. We're going to see if this API call exactly
// matched the way we expect cmd to call it. If it does, then
// let's manually emit a ^[[3J to the connected terminal, so that their
// entire buffer will be cleared as well.
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (enableCmdShim && gci.IsInVtIoMode())
if (auto writer = gci.GetVtWriterForBuffer(&context))
{
const auto currentBufferDimensions = buffer.GetBufferSize().Dimensions();
const auto sourceIsWholeBuffer = (source.top == 0) &&
(source.left == 0) &&
(source.right == currentBufferDimensions.width) &&
(source.bottom == currentBufferDimensions.height);
const auto targetIsNegativeBufferHeight = (target.x == 0) &&
(target.y == -currentBufferDimensions.height);
const auto noClipProvided = clip == std::nullopt;
const auto fillIsBlank = (fillCharacter == UNICODE_SPACE) &&
(fillAttribute == buffer.GetAttributes().GetLegacyAttributes());
auto& buffer = context.GetActiveBuffer();
if (sourceIsWholeBuffer && targetIsNegativeBufferHeight && noClipProvided && fillIsBlank)
// However, if the character is null and we were given a null attribute (represented as legacy 0),
// then we'll just fill with spaces and whatever the buffer's default colors are.
if (fillCharacter == UNICODE_NULL && fillAttribute == 0)
{
// It's important that we flush the renderer at this point so we don't
// have any pending output rendered after the scrollback is cleared.
ServiceLocator::LocateGlobals().pRender->TriggerFlush(false);
hr = gci.GetVtIo()->ManuallyClearScrollback();
fillCharacter = UNICODE_SPACE;
fillAttribute = buffer.GetAttributes().GetLegacyAttributes();
}
// GH#3126 - This is a shim for cmd's `cls` function. In the
// legacy console, `cls` is supposed to clear the entire buffer. In
// conpty however, there's no difference between the viewport and the
// entirety of the buffer. We're going to see if this API call exactly
// matched the way we expect cmd to call it. If it does, then
// let's manually emit a Full Reset (RIS).
const auto bufferSize = buffer.GetBufferSize();
if (enableCmdShim &&
source.left <= 0 && source.top <= 0 &&
source.right >= bufferSize.RightInclusive() && source.bottom >= bufferSize.BottomInclusive() &&
target.x == 0 && target.y <= -bufferSize.BottomExclusive() &&
!clip &&
fillCharacter == UNICODE_SPACE && fillAttribute == buffer.GetAttributes().GetLegacyAttributes())
{
WriteClearScreen(context);
writer.Submit();
return S_OK;
}
const auto clipViewport = clip ? Viewport::FromInclusive(*clip) : bufferSize;
const auto sourceViewport = Viewport::FromInclusive(source);
Viewport readViewport;
Viewport writtenViewport;
const auto w = std::max(0, sourceViewport.Width());
const auto h = std::max(0, sourceViewport.Height());
const auto a = static_cast<size_t>(w * h);
if (a == 0)
{
return S_OK;
}
til::small_vector<CHAR_INFO, 1024> backup;
til::small_vector<CHAR_INFO, 1024> fill;
backup.resize(a, CHAR_INFO{ fillCharacter, fillAttribute });
fill.resize(a, CHAR_INFO{ fillCharacter, fillAttribute });
writer.BackupCursor();
RETURN_IF_FAILED(ReadConsoleOutputWImplHelper(context, backup, sourceViewport, readViewport));
RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, fill, w, sourceViewport.Clamp(clipViewport), writtenViewport));
RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, backup, w, Viewport::FromDimensions(target, readViewport.Dimensions()).Clamp(clipViewport), writtenViewport));
writer.Submit();
}
else
{
auto& buffer = context.GetActiveBuffer();
TextAttribute useThisAttr(fillAttribute);
ScrollRegion(buffer, source, clip, target, fillCharacter, useThisAttr);
}
return hr;
return S_OK;
}
CATCH_RETURN();
}
@@ -984,6 +1087,13 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
const TextAttribute attr{ attribute };
context.SetAttributes(attr);
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (auto writer = gci.GetVtWriterForBuffer(&context))
{
writer.WriteAttributes(attr);
writer.Submit();
}
return S_OK;
}
CATCH_RETURN();
@@ -1103,7 +1213,6 @@ void ApiRoutines::GetConsoleWindowImpl(HWND& hwnd) noexcept
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
const IConsoleWindow* pWindow = ServiceLocator::LocateConsoleWindow();
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (pWindow != nullptr)
{
hwnd = pWindow->GetWindowHandle();
@@ -1115,7 +1224,8 @@ void ApiRoutines::GetConsoleWindowImpl(HWND& hwnd) noexcept
// doesn't actually do anything, but is a unique HWND to this
// console, so that they know that this console is in fact a real
// console window.
if (gci.IsInVtIoMode())
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (gci.IsConPTY())
{
hwnd = ServiceLocator::LocatePseudoWindow();
}
@@ -1516,6 +1626,16 @@ void ApiRoutines::GetConsoleDisplayModeImpl(ULONG& flags) noexcept
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
ServiceLocator::LocateGlobals().getConsoleInformation().SetTitle(title);
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.SetTitle(title);
if (auto writer = gci.GetVtWriter())
{
writer.WriteUTF8("\x1b]0;");
writer.WriteUTF16StripControlChars(title);
writer.WriteUTF8("\x7");
writer.Submit();
}
return S_OK;
}

View File

@@ -25,19 +25,3 @@ bool Globals::IsHeadless() const
{
return launchArgs.IsHeadless();
}
#ifdef UNIT_TESTING
// Method Description:
// - This is a test helper method. It can be used to trick us into responding
// true to `IsHeadless`, which will cause the console host to act in conpty
// mode.
// Arguments:
// - vtRenderEngine: a VT renderer that our VtIo should use as the vt engine during these tests
// Return Value:
// - <none>
void Globals::EnableConptyModeForTests(std::unique_ptr<Microsoft::Console::Render::VtEngine> vtRenderEngine, const bool resizeQuirk)
{
launchArgs.EnableConptyModeForTests();
getConsoleInformation().GetVtIo()->EnableConptyModeForTests(std::move(vtRenderEngine), resizeQuirk);
}
#endif

View File

@@ -76,10 +76,6 @@ public:
wil::unique_threadpool_wait handoffInboxConsoleExitWait;
bool defaultTerminalMarkerCheckRequired = false;
#ifdef UNIT_TESTING
void EnableConptyModeForTests(std::unique_ptr<Microsoft::Console::Render::VtEngine> vtRenderEngine, const bool resizeQuirk = false);
#endif
private:
CONSOLE_INFORMATION ciConsoleInformation;
ApiRoutines defaultApiRoutines;

View File

@@ -15,7 +15,6 @@
namespace Microsoft::Console::Render
{
class Renderer;
class VtEngine;
}
class InputBuffer final : public ConsoleObjectHeader

View File

@@ -316,8 +316,8 @@ void ScrollRegion(SCREEN_INFORMATION& screenInfo,
const til::inclusive_rect scrollRectGiven,
const std::optional<til::inclusive_rect> clipRectGiven,
const til::point destinationOriginGiven,
const wchar_t fillCharGiven,
const TextAttribute fillAttrsGiven)
wchar_t fillCharGiven,
TextAttribute fillAttrsGiven)
{
// ------ 1. PREP SOURCE ------
// Set up the source viewport.
@@ -357,17 +357,18 @@ void ScrollRegion(SCREEN_INFORMATION& screenInfo,
return;
}
// Determine the cell we will use to fill in any revealed/uncovered space.
// We generally use exactly what was given to us.
OutputCellIterator fillData(fillCharGiven, fillAttrsGiven);
// However, if the character is null and we were given a null attribute (represented as legacy 0),
// then we'll just fill with spaces and whatever the buffer's default colors are.
if (fillCharGiven == UNICODE_NULL && fillAttrsGiven == TextAttribute{ 0 })
{
fillData = OutputCellIterator(UNICODE_SPACE, screenInfo.GetAttributes());
fillCharGiven = UNICODE_SPACE;
fillAttrsGiven = screenInfo.GetAttributes();
}
// Determine the cell we will use to fill in any revealed/uncovered space.
// We generally use exactly what was given to us.
OutputCellIterator fillData(fillCharGiven, fillAttrsGiven);
// ------ 4. PREP TARGET ------
// Now it's time to think about the target. We're only given the origin of the target
// because it is assumed that it will have the same relative dimensions as the original source.
@@ -459,7 +460,7 @@ void SetActiveScreenBuffer(SCREEN_INFORMATION& screenInfo)
// mode, then the cursor will remain off until they print text. This can
// lead to alignment problems in the terminal, because we won't move the
// terminal's cursor in this _exact_ scenario.
screenInfo.GetTextBuffer().GetCursor().SetIsOn(gci.IsInVtIoMode());
screenInfo.GetTextBuffer().GetCursor().SetIsOn(gci.IsConPTY());
// set font
screenInfo.RefreshFontWithRenderer();

View File

@@ -33,6 +33,14 @@ ConhostInternalGetSet::ConhostInternalGetSet(_In_ IIoProvider& io) :
// - <none>
void ConhostInternalGetSet::ReturnResponse(const std::wstring_view response)
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
// ConPTY should not respond to requests. That's the job of the terminal.
if (gci.IsConPTY())
{
return;
}
// TODO GH#4954 During the input refactor we may want to add a "priority" input list
// to make sure that "response" input is spooled directly into the application.
// We switched this to an append (vs. a prepend) to fix GH#1637, a bug where two CPR
@@ -202,8 +210,8 @@ CursorType ConhostInternalGetSet::GetUserDefaultCursorStyle() const
// - <none>
void ConhostInternalGetSet::ShowWindow(bool showOrHide)
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto hwnd = gci.IsInVtIoMode() ? ServiceLocator::LocatePseudoWindow() : ServiceLocator::LocateConsoleWindow()->GetWindowHandle();
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto hwnd = gci.IsConPTY() ? ServiceLocator::LocatePseudoWindow() : ServiceLocator::LocateConsoleWindow()->GetWindowHandle();
// GH#13301 - When we send this ShowWindow message, if we send it to the
// conhost HWND, it's going to need to get processed by the window message
@@ -279,11 +287,17 @@ void ConhostInternalGetSet::SetWorkingDirectory(const std::wstring_view /*uri*/)
// - true if successful. false otherwise.
void ConhostInternalGetSet::PlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration)
{
const auto window = ServiceLocator::LocateConsoleWindow();
if (!window)
{
return;
}
// Unlock the console, so the UI doesn't hang while we're busy.
UnlockConsole();
// This call will block for the duration, unless shutdown early.
const auto windowHandle = ServiceLocator::LocateConsoleWindow()->GetWindowHandle();
const auto windowHandle = window->GetWindowHandle();
auto& midiAudio = ServiceLocator::LocateGlobals().getConsoleInformation().GetMidiAudio();
midiAudio.PlayNote(windowHandle, noteNumber, velocity, std::chrono::duration_cast<std::chrono::milliseconds>(duration));
@@ -332,11 +346,9 @@ bool ConhostInternalGetSet::ResizeWindow(const til::CoordType sColumns, const ti
}
// If the cursor row is now past the bottom of the viewport, we'll have to
// move the viewport down to bring it back into view. However, we don't want
// to do this in pty mode, because the conpty resize operation is dependent
// on the viewport *not* being adjusted.
// move the viewport down to bring it back into view.
const auto cursorOverflow = csbiex.dwCursorPosition.Y - newViewport.BottomInclusive();
if (cursorOverflow > 0 && !IsConsolePty())
if (cursorOverflow > 0)
{
newViewport = Viewport::Offset(newViewport, { 0, cursorOverflow });
}
@@ -353,17 +365,6 @@ bool ConhostInternalGetSet::ResizeWindow(const til::CoordType sColumns, const ti
return true;
}
// Routine Description:
// - Checks if the console host is acting as a pty.
// Arguments:
// - <none>
// Return Value:
// - true if we're in pty mode.
bool ConhostInternalGetSet::IsConsolePty() const
{
return ServiceLocator::LocateGlobals().getConsoleInformation().IsInVtIoMode();
}
// Routine Description:
// - Checks if the InputBuffer is willing to accept VT Input directly
// IsVtInputEnabled is an internal-only "API" call that the vt commands can execute,

View File

@@ -62,7 +62,6 @@ public:
void SetWorkingDirectory(const std::wstring_view uri) override;
void PlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration) override;
bool IsConsolePty() const override;
bool IsVtInputEnabled() const override;
void NotifyAccessibilityChange(const til::rect& changedRect) override;

View File

@@ -1200,20 +1200,7 @@ void SCREEN_INFORMATION::_InternalSetViewportSize(const til::size* const pcoordS
_viewport = newViewport;
Tracing::s_TraceWindowViewport(_viewport);
// In Conpty mode, call TriggerScroll here without params. By not providing
// params, the renderer will make sure to update the VtEngine with the
// updated viewport size. If we don't do this, the engine can get into a
// torn state on this frame.
//
// Without this statement, the engine won't be told about the new view size
// till the start of the next frame. If any other text gets output before
// that frame starts, there's a very real chance that it'll cause errors as
// the engine tries to invalidate those regions.
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (gci.IsInVtIoMode() && ServiceLocator::LocateGlobals().pRender)
{
ServiceLocator::LocateGlobals().pRender->TriggerScroll();
}
if (gci.HasPendingCookedRead())
{
gci.CookedReadData().RedrawAfterResize();
@@ -1761,6 +1748,11 @@ const SCREEN_INFORMATION& SCREEN_INFORMATION::GetMainBuffer() const
return *this;
}
const SCREEN_INFORMATION* SCREEN_INFORMATION::GetAltBuffer() const noexcept
{
return _psiAlternateBuffer;
}
// Routine Description:
// - Instantiates a new buffer to be used as an alternate buffer. This buffer
// does not have a driver handle associated with it and shares a state
@@ -1901,15 +1893,6 @@ void SCREEN_INFORMATION::_handleDeferredResize(SCREEN_INFORMATION& siMain)
s_RemoveScreenBuffer(psiOldAltBuffer); // this will also delete the old alt buffer
}
// GH#381: When we switch into the alt buffer:
// * flush the current frame, to clear out anything that we prepared for this buffer.
// * Emit a ?1049h/l to the remote side, to let them know that we've switched buffers.
if (gci.IsInVtIoMode() && ServiceLocator::LocateGlobals().pRender)
{
ServiceLocator::LocateGlobals().pRender->TriggerFlush(false);
LOG_IF_FAILED(gci.GetVtIo()->SwitchScreenBuffer(true));
}
::SetActiveScreenBuffer(*psiNewAltBuffer);
// Kind of a hack until we have proper signal channels: If the client app wants window size events, send one for
@@ -1937,15 +1920,6 @@ void SCREEN_INFORMATION::UseMainScreenBuffer()
{
_handleDeferredResize(*psiMain);
// GH#381: When we switch into the main buffer:
// * flush the current frame, to clear out anything that we prepared for this buffer.
// * Emit a ?1049h/l to the remote side, to let them know that we've switched buffers.
if (gci.IsInVtIoMode() && ServiceLocator::LocateGlobals().pRender)
{
ServiceLocator::LocateGlobals().pRender->TriggerFlush(false);
LOG_IF_FAILED(gci.GetVtIo()->SwitchScreenBuffer(false));
}
::SetActiveScreenBuffer(*psiMain);
psiMain->UpdateScrollBars(); // The alt had disabled scrollbars, re-enable them
@@ -1994,8 +1968,8 @@ bool SCREEN_INFORMATION::_IsAltBuffer() const
// - true iff this buffer has a main buffer.
bool SCREEN_INFORMATION::_IsInPtyMode() const
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
return _IsAltBuffer() || gci.IsInVtIoMode();
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
return _IsAltBuffer() || gci.IsConPTY();
}
// Routine Description:
@@ -2084,8 +2058,6 @@ void SCREEN_INFORMATION::SetPopupAttributes(const TextAttribute& popupAttributes
void SCREEN_INFORMATION::SetDefaultAttributes(const TextAttribute& attributes,
const TextAttribute& popupAttributes)
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto oldPrimaryAttributes = GetAttributes();
const auto oldPopupAttributes = GetPopupAttributes();
@@ -2101,10 +2073,7 @@ void SCREEN_INFORMATION::SetDefaultAttributes(const TextAttribute& attributes,
// Force repaint of entire viewport, unless we're in conpty mode. In that
// case, we don't really need to force a redraw of the entire screen just
// because the text attributes changed.
if (!(gci.IsInVtIoMode()))
{
_textBuffer->TriggerRedrawAll();
}
_textBuffer->TriggerRedrawAll();
// If we're an alt buffer, also update our main buffer.
if (_psiMainBuffer)
@@ -2180,52 +2149,14 @@ void SCREEN_INFORMATION::SetViewport(const Viewport& newViewport,
// - S_OK
[[nodiscard]] HRESULT SCREEN_INFORMATION::ClearBuffer()
{
// Rotate the buffer to bring the cursor row to the top of the viewport.
const auto cursorPos = _textBuffer->GetCursor().GetPosition();
for (auto i = 0; i < cursorPos.y; i++)
{
_textBuffer->IncrementCircularBuffer();
}
// Erase everything below that point.
RETURN_IF_FAILED(SetCursorPosition({ 0, 1 }, false));
auto& engine = reinterpret_cast<OutputStateMachineEngine&>(_stateMachine->Engine());
engine.Dispatch().EraseInDisplay(DispatchTypes::EraseType::ToEnd);
_textBuffer->Reset();
// Restore the original cursor x offset, but now on the first row.
RETURN_IF_FAILED(SetCursorPosition({ cursorPos.x, 0 }, false));
_textBuffer->GetCursor().SetYPosition(0);
_textBuffer->TriggerRedrawAll();
return S_OK;
}
// Method Description:
// - Sets up the Output state machine to be in pty mode. Sequences it doesn't
// understand will be written to the pTtyConnection passed in here.
// Arguments:
// - pTtyConnection: This is a TerminalOutputConnection that we can write the
// sequence we didn't understand to.
// Return Value:
// - <none>
void SCREEN_INFORMATION::SetTerminalConnection(_In_ VtEngine* const pTtyConnection)
{
auto& engine = reinterpret_cast<OutputStateMachineEngine&>(_stateMachine->Engine());
if (pTtyConnection)
{
engine.SetTerminalConnection(pTtyConnection,
[&stateMachine = *_stateMachine]() -> bool {
ServiceLocator::LocateGlobals().pRender->NotifyPaintFrame();
return stateMachine.FlushToTerminal();
});
}
else
{
engine.SetTerminalConnection(nullptr,
nullptr);
}
}
// Routine Description:
// - Writes cells to the output buffer at the cursor position.
// Arguments:

View File

@@ -47,14 +47,6 @@ Revision History:
#include "../types/inc/Viewport.hpp"
class ConversionAreaInfo; // forward decl window. circular reference
// fwdecl unittest classes
#ifdef UNIT_TESTING
namespace TerminalCoreUnitTests
{
class ConptyRoundtripTests;
};
#endif
class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console::IIoProvider
{
public:
@@ -199,7 +191,7 @@ public:
SCREEN_INFORMATION& GetMainBuffer();
const SCREEN_INFORMATION& GetMainBuffer() const;
const SCREEN_INFORMATION* GetAltBuffer() const noexcept;
SCREEN_INFORMATION& GetActiveBuffer();
const SCREEN_INFORMATION& GetActiveBuffer() const;
@@ -213,8 +205,6 @@ public:
[[nodiscard]] HRESULT ClearBuffer();
void SetTerminalConnection(_In_ Microsoft::Console::Render::VtEngine* const pTtyConnection);
void UpdateBottom();
FontInfo& GetCurrentFont() noexcept;
@@ -226,6 +216,9 @@ public:
void SetIgnoreLegacyEquivalentVTAttributes() noexcept;
void ResetIgnoreLegacyEquivalentVTAttributes() noexcept;
[[nodiscard]] NTSTATUS ResizeWithReflow(const til::size coordnewScreenSize);
[[nodiscard]] NTSTATUS ResizeTraditional(const til::size coordNewScreenSize);
private:
SCREEN_INFORMATION(_In_ Microsoft::Console::Interactivity::IWindowMetrics* pMetrics,
_In_ Microsoft::Console::Interactivity::IAccessibilityNotifier* pNotifier,
@@ -249,9 +242,6 @@ private:
_Out_ bool* const pfIsHorizontalVisible,
_Out_ bool* const pfIsVerticalVisible);
[[nodiscard]] NTSTATUS ResizeWithReflow(const til::size coordnewScreenSize);
[[nodiscard]] NTSTATUS ResizeTraditional(const til::size coordNewScreenSize);
[[nodiscard]] NTSTATUS _InitializeOutputStateMachine();
void _FreeOutputStateMachine();
@@ -297,7 +287,5 @@ private:
friend class TextBufferIteratorTests;
friend class ScreenBufferTests;
friend class CommonState;
friend class ConptyOutputTests;
friend class TerminalCoreUnitTests::ConptyRoundtripTests;
#endif
};

View File

@@ -383,20 +383,12 @@ bool Selection::HandleKeyboardLineSelectionEvent(const INPUT_KEY_INFO* const pIn
// shift + pgup/pgdn extends selection up or down one full screen
case VK_NEXT:
{
coordSelPoint.y = base::CheckAdd(coordSelPoint.y, sWindowHeight).ValueOrDefault(bufferSize.BottomInclusive());
if (coordSelPoint.y > bufferSize.BottomInclusive())
{
coordSelPoint.y = bufferSize.BottomInclusive();
}
coordSelPoint.y = std::min(coordSelPoint.y + sWindowHeight, bufferSize.BottomInclusive());
break;
}
case VK_PRIOR:
{
coordSelPoint.y = base::CheckSub(coordSelPoint.y, sWindowHeight).ValueOrDefault(bufferSize.Top());
if (coordSelPoint.y < bufferSize.Top())
{
coordSelPoint.y = bufferSize.Top();
}
coordSelPoint.y = std::max(coordSelPoint.y - sWindowHeight, bufferSize.Top());
break;
}
// shift + home/end extends selection to beginning or end of line

View File

@@ -103,7 +103,10 @@ public:
bool IsConsoleLocked() const noexcept;
ULONG GetCSRecursionCount() const noexcept;
Microsoft::Console::VirtualTerminal::VtIo* GetVtIo();
Microsoft::Console::VirtualTerminal::VtIo* GetVtIoNoCheck() noexcept;
Microsoft::Console::VirtualTerminal::VtIo::Writer GetVtWriter() noexcept;
Microsoft::Console::VirtualTerminal::VtIo::Writer GetVtWriterForBuffer(const SCREEN_INFORMATION* context) noexcept;
bool IsConPTY() const noexcept;
SCREEN_INFORMATION& GetActiveOutputBuffer() override;
const SCREEN_INFORMATION& GetActiveOutputBuffer() const override;
@@ -112,7 +115,6 @@ public:
InputBuffer* const GetActiveInputBuffer() const override;
bool IsInVtIoMode() const;
bool HasPendingCookedRead() const noexcept;
bool HasPendingPopup() const noexcept;
const COOKED_READ_DATA& CookedReadData() const noexcept;

View File

@@ -181,7 +181,7 @@ void Settings::ApplyCommandlineArguments(const ConsoleArguments& consoleArgs)
_dwScreenBufferSize.Y = height;
_dwWindowSize.Y = height;
}
else if (ServiceLocator::LocateGlobals().getConsoleInformation().IsInVtIoMode())
else if (ServiceLocator::LocateGlobals().getConsoleInformation().IsConPTY())
{
// If we're a PTY but we weren't explicitly told a size, use the window size as the buffer size.
_dwScreenBufferSize = _dwWindowSize;

View File

@@ -380,8 +380,8 @@ HRESULT ConsoleCreateIoThread(_In_ HANDLE Server,
// The conpty i/o threads need an actual client to be connected before they
// can start, so they're started below, in ConsoleAllocateConsole
auto& gci = g.getConsoleInformation();
RETURN_IF_FAILED(gci.GetVtIo()->Initialize(args));
RETURN_IF_FAILED(gci.GetVtIo()->CreateAndStartSignalThread());
RETURN_IF_FAILED(gci.GetVtIoNoCheck()->Initialize(args));
RETURN_IF_FAILED(gci.GetVtIoNoCheck()->CreateAndStartSignalThread());
return S_OK;
}
@@ -574,7 +574,7 @@ try
// GH#13211 - Make sure the terminal obeys the resizing quirk. Otherwise,
// defterm connections to the Terminal are going to have weird resizing.
const auto commandLine = fmt::format(FMT_COMPILE(L" --headless --resizeQuirk --signal {:#x}"),
const auto commandLine = fmt::format(FMT_COMPILE(L" --headless --signal {:#x}"),
(int64_t)signalPipeOurSide.release());
ConsoleArguments consoleArgs(commandLine, inPipeOurSide.release(), outPipeOurSide.release());
@@ -841,24 +841,25 @@ PWSTR TranslateConsoleTitle(_In_ PCWSTR pwszConsoleTitle, const BOOL fUnexpand,
// No matter what, create a renderer.
try
{
g.pRender = nullptr;
if (!gci.IsConPTY())
{
auto renderThread = std::make_unique<RenderThread>();
// stash a local pointer to the thread here -
// We're going to give ownership of the thread to the Renderer,
// but the thread also need to be told who its renderer is,
// and we can't do that until the renderer is constructed.
auto* const localPointerToThread = renderThread.get();
auto renderThread = std::make_unique<RenderThread>();
// stash a local pointer to the thread here -
// We're going to give ownership of the thread to the Renderer,
// but the thread also need to be told who its renderer is,
// and we can't do that until the renderer is constructed.
auto* const localPointerToThread = renderThread.get();
g.pRender = new Renderer(gci.GetRenderSettings(), &gci.renderData, nullptr, 0, std::move(renderThread));
g.pRender = new Renderer(gci.GetRenderSettings(), &gci.renderData, nullptr, 0, std::move(renderThread));
THROW_IF_FAILED(localPointerToThread->Initialize(g.pRender));
THROW_IF_FAILED(localPointerToThread->Initialize(g.pRender));
// Set up the renderer to be used to calculate the width of a glyph,
// should we be unable to figure out its width another way.
CodepointWidthDetector::Singleton().SetFallbackMethod([](const std::wstring_view& glyph) {
return ServiceLocator::LocateGlobals().pRender->IsGlyphWideByFont(glyph);
});
// Set up the renderer to be used to calculate the width of a glyph,
// should we be unable to figure out its width another way.
CodepointWidthDetector::Singleton().SetFallbackMethod([](const std::wstring_view& glyph) {
return ServiceLocator::LocateGlobals().pRender->IsGlyphWideByFont(glyph);
});
}
}
catch (...)
{
@@ -876,7 +877,10 @@ PWSTR TranslateConsoleTitle(_In_ PCWSTR pwszConsoleTitle, const BOOL fUnexpand,
}
// Allow the renderer to paint once the rest of the console is hooked up.
g.pRender->EnablePainting();
if (g.pRender)
{
g.pRender->EnablePainting();
}
if (SUCCEEDED_NTSTATUS(Status) && ConsoleConnectionDeservesVisibleWindow(p))
{
@@ -941,7 +945,7 @@ PWSTR TranslateConsoleTitle(_In_ PCWSTR pwszConsoleTitle, const BOOL fUnexpand,
// We'll need the size of the screen buffer in the vt i/o initialization
if (SUCCEEDED_NTSTATUS(Status))
{
auto hr = gci.GetVtIo()->CreateIoHandlers();
auto hr = gci.GetVtIoNoCheck()->CreateIoHandlers();
if (hr == S_FALSE)
{
// We're not in VT I/O mode, this is fine.
@@ -949,7 +953,7 @@ PWSTR TranslateConsoleTitle(_In_ PCWSTR pwszConsoleTitle, const BOOL fUnexpand,
else if (SUCCEEDED(hr))
{
// Actually start the VT I/O threads
hr = gci.GetVtIo()->StartIfNeeded();
hr = gci.GetVtIoNoCheck()->StartIfNeeded();
// Don't convert S_FALSE to an NTSTATUS - the equivalent NTSTATUS
// is treated as an error
if (hr != S_FALSE)

View File

@@ -1,465 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include <wextestclass.h>
#include "../../inc/consoletaeftemplates.hpp"
#include "../../types/inc/Viewport.hpp"
#include "../../renderer/base/Renderer.hpp"
#include "../../renderer/vt/Xterm256Engine.hpp"
#include "../../renderer/vt/XtermEngine.hpp"
#include "../Settings.hpp"
#include "CommonState.hpp"
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace Microsoft::Console::Types;
using namespace Microsoft::Console::Interactivity;
using namespace Microsoft::Console::VirtualTerminal;
using namespace Microsoft::Console;
using namespace Microsoft::Console::Render;
using namespace Microsoft::Console::Types;
class ConptyOutputTests
{
// !!! DANGER: Many tests in this class expect the Terminal and Host buffers
// to be 80x32. If you change these, you'll probably inadvertently break a
// bunch of tests !!!
static const til::CoordType TerminalViewWidth = 80;
static const til::CoordType TerminalViewHeight = 32;
// This test class is to write some things into the PTY and then check that
// the rendering that is coming out of the VT-sequence generator is exactly
// as we expect it to be.
BEGIN_TEST_CLASS(ConptyOutputTests)
TEST_CLASS_PROPERTY(L"IsolationLevel", L"Class")
END_TEST_CLASS()
TEST_CLASS_SETUP(ClassSetup)
{
m_state = std::make_unique<CommonState>();
m_state->InitEvents();
m_state->PrepareGlobalInputBuffer();
m_state->PrepareGlobalScreenBuffer(TerminalViewWidth, TerminalViewHeight, TerminalViewWidth, TerminalViewHeight);
return true;
}
TEST_CLASS_CLEANUP(ClassCleanup)
{
m_state->CleanupGlobalScreenBuffer();
m_state->CleanupGlobalInputBuffer();
m_state.release();
return true;
}
TEST_METHOD_SETUP(MethodSetup)
{
// Set up some sane defaults
auto& g = ServiceLocator::LocateGlobals();
auto& gci = g.getConsoleInformation();
gci.SetColorTableEntry(TextColor::DEFAULT_FOREGROUND, INVALID_COLOR);
gci.SetColorTableEntry(TextColor::DEFAULT_BACKGROUND, INVALID_COLOR);
gci.SetFillAttribute(0x07); // DARK_WHITE on DARK_BLACK
gci.CalculateDefaultColorIndices();
g.pRender = new Renderer(gci.GetRenderSettings(), &gci.renderData, nullptr, 0, nullptr);
m_state->PrepareNewTextBufferInfo(true, TerminalViewWidth, TerminalViewHeight);
auto& currentBuffer = gci.GetActiveOutputBuffer();
// Make sure a test hasn't left us in the alt buffer on accident
VERIFY_IS_FALSE(currentBuffer._IsAltBuffer());
VERIFY_SUCCEEDED(currentBuffer.SetViewportOrigin(true, { 0, 0 }, true));
VERIFY_ARE_EQUAL(til::point{}, currentBuffer.GetTextBuffer().GetCursor().GetPosition());
// Set up an xterm-256 renderer for conpty
auto hFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
auto initialViewport = currentBuffer.GetViewport();
auto vtRenderEngine = std::make_unique<Xterm256Engine>(std::move(hFile),
initialViewport);
auto pfn = std::bind(&ConptyOutputTests::_writeCallback, this, std::placeholders::_1, std::placeholders::_2);
vtRenderEngine->SetTestCallback(pfn);
g.pRender->AddRenderEngine(vtRenderEngine.get());
gci.GetActiveOutputBuffer().SetTerminalConnection(vtRenderEngine.get());
expectedOutput.clear();
// Manually set the console into conpty mode. We're not actually going
// to set up the pipes for conpty, but we want the console to behave
// like it would in conpty mode.
g.EnableConptyModeForTests(std::move(vtRenderEngine));
return true;
}
TEST_METHOD_CLEANUP(MethodCleanup)
{
m_state->CleanupNewTextBufferInfo();
auto& g = ServiceLocator::LocateGlobals();
delete g.pRender;
VERIFY_ARE_EQUAL(0u, expectedOutput.size(), L"Tests should drain all the output they push into the expected output buffer.");
return true;
}
TEST_METHOD(ConptyOutputTestCanary);
TEST_METHOD(SimpleWriteOutputTest);
TEST_METHOD(WriteTwoLinesUsesNewline);
TEST_METHOD(WriteAFewSimpleLines);
TEST_METHOD(InvalidateUntilOneBeforeEnd);
TEST_METHOD(SetConsoleTitleWithControlChars);
TEST_METHOD(IncludeBackgroundColorChangesInFirstFrame);
TEST_METHOD(MoveCursorAfterWrapForced);
private:
bool _writeCallback(const char* const pch, const size_t cch);
void _flushFirstFrame();
std::deque<std::string> expectedOutput;
std::unique_ptr<CommonState> m_state;
};
bool ConptyOutputTests::_writeCallback(const char* const pch, const size_t cch)
{
// Since rendering happens on a background thread that doesn't have the exception handler on it
// we need to rely on VERIFY's return codes instead of exceptions.
const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope;
auto actualString = std::string(pch, cch);
RETURN_BOOL_IF_FALSE(VERIFY_IS_GREATER_THAN(expectedOutput.size(),
static_cast<size_t>(0),
NoThrowString().Format(L"writing=\"%hs\", expecting %u strings", actualString.c_str(), expectedOutput.size())));
auto first = expectedOutput.front();
expectedOutput.pop_front();
Log::Comment(NoThrowString().Format(L"Expected =\t\"%hs\"", first.c_str()));
Log::Comment(NoThrowString().Format(L"Actual =\t\"%hs\"", actualString.c_str()));
RETURN_BOOL_IF_FALSE(VERIFY_ARE_EQUAL(first.length(), cch));
RETURN_BOOL_IF_FALSE(VERIFY_ARE_EQUAL(first, actualString));
return true;
}
void ConptyOutputTests::_flushFirstFrame()
{
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
expectedOutput.push_back("\x1b[2J");
expectedOutput.push_back("\x1b[m");
expectedOutput.push_back("\x1b[H"); // Go Home
expectedOutput.push_back("\x1b[?25h");
VERIFY_SUCCEEDED(renderer.PaintFrame());
}
// Function Description:
// - Helper function to validate that a number of characters in a row are all
// the same. Validates that the next end-start characters are all equal to the
// provided string. Will move the provided iterator as it validates. The
// caller should ensure that `iter` starts where they would like to validate.
// Arguments:
// - expectedChar: The character (or characters) we're expecting
// - iter: a iterator pointing to the cell we'd like to start validating at.
// - start: the first index in the range we'd like to validate
// - end: the last index in the range we'd like to validate
// Return Value:
// - <none>
void _verifySpanOfText(const wchar_t* const expectedChar,
TextBufferCellIterator& iter,
const int start,
const int end)
{
for (auto x = start; x < end; x++)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
if (iter->Chars() != expectedChar)
{
Log::Comment(NoThrowString().Format(L"character [%d] was mismatched", x));
}
VERIFY_ARE_EQUAL(expectedChar, (iter++)->Chars());
}
Log::Comment(NoThrowString().Format(
L"Successfully validated %d characters were '%s'", end - start, expectedChar));
}
void ConptyOutputTests::ConptyOutputTestCanary()
{
Log::Comment(NoThrowString().Format(
L"This is a simple test to make sure that everything is working as expected."));
_flushFirstFrame();
}
void ConptyOutputTests::SimpleWriteOutputTest()
{
Log::Comment(NoThrowString().Format(
L"Write some simple output, and make sure it gets rendered largely "
L"unmodified to the terminal"));
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& sm = si.GetStateMachine();
_flushFirstFrame();
expectedOutput.push_back("Hello World");
sm.ProcessString(L"Hello World");
VERIFY_SUCCEEDED(renderer.PaintFrame());
}
void ConptyOutputTests::WriteTwoLinesUsesNewline()
{
Log::Comment(NoThrowString().Format(
L"Write two lines of output. We should use \r\n to move the cursor"));
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& sm = si.GetStateMachine();
auto& tb = si.GetTextBuffer();
_flushFirstFrame();
sm.ProcessString(L"AAA");
sm.ProcessString(L"\x1b[2;1H");
sm.ProcessString(L"BBB");
{
auto iter = tb.GetCellDataAt({ 0, 0 });
VERIFY_ARE_EQUAL(L"A", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"A", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"A", (iter++)->Chars());
}
{
auto iter = tb.GetCellDataAt({ 0, 1 });
VERIFY_ARE_EQUAL(L"B", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"B", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"B", (iter++)->Chars());
}
expectedOutput.push_back("AAA");
expectedOutput.push_back("\r\n");
expectedOutput.push_back("BBB");
VERIFY_SUCCEEDED(renderer.PaintFrame());
}
void ConptyOutputTests::WriteAFewSimpleLines()
{
Log::Comment(NoThrowString().Format(
L"Write more lines of output. We should use \r\n to move the cursor"));
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& sm = si.GetStateMachine();
auto& tb = si.GetTextBuffer();
_flushFirstFrame();
sm.ProcessString(L"AAA\n");
sm.ProcessString(L"BBB\n");
sm.ProcessString(L"\n");
sm.ProcessString(L"CCC");
{
auto iter = tb.GetCellDataAt({ 0, 0 });
VERIFY_ARE_EQUAL(L"A", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"A", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"A", (iter++)->Chars());
}
{
auto iter = tb.GetCellDataAt({ 0, 1 });
VERIFY_ARE_EQUAL(L"B", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"B", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"B", (iter++)->Chars());
}
{
auto iter = tb.GetCellDataAt({ 0, 2 });
VERIFY_ARE_EQUAL(L" ", (iter++)->Chars());
VERIFY_ARE_EQUAL(L" ", (iter++)->Chars());
VERIFY_ARE_EQUAL(L" ", (iter++)->Chars());
}
{
auto iter = tb.GetCellDataAt({ 0, 3 });
VERIFY_ARE_EQUAL(L"C", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"C", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"C", (iter++)->Chars());
}
expectedOutput.push_back("AAA");
expectedOutput.push_back("\r\n");
expectedOutput.push_back("BBB");
// Jump down to the fourth line because emitting spaces didn't do anything
// and we will skip to emitting the CCC segment.
expectedOutput.push_back("\x1b[4;1H");
expectedOutput.push_back("CCC");
// Cursor goes back on.
expectedOutput.push_back("\x1b[?25h");
VERIFY_SUCCEEDED(renderer.PaintFrame());
}
void ConptyOutputTests::InvalidateUntilOneBeforeEnd()
{
Log::Comment(NoThrowString().Format(
L"Make sure we don't use EL and wipe out the last column of text"));
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& sm = si.GetStateMachine();
auto& tb = si.GetTextBuffer();
_flushFirstFrame();
// Move the cursor to width-15, draw 15 characters
sm.ProcessString(L"\x1b[1;66H");
sm.ProcessString(L"ABCDEFGHIJKLMNO");
{
auto iter = tb.GetCellDataAt({ 78, 0 });
VERIFY_ARE_EQUAL(L"N", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"O", (iter++)->Chars());
}
expectedOutput.push_back("\x1b[65C");
expectedOutput.push_back("ABCDEFGHIJKLMNO");
VERIFY_SUCCEEDED(renderer.PaintFrame());
// overstrike the first with X and the middle 8 with spaces
sm.ProcessString(L"\x1b[1;66H");
// ABCDEFGHIJKLMNO
sm.ProcessString(L"X ");
{
auto iter = tb.GetCellDataAt({ 78, 0 });
VERIFY_ARE_EQUAL(L" ", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"O", (iter++)->Chars());
}
expectedOutput.push_back("\x1b[1;66H");
expectedOutput.push_back("X"); // sequence optimizer should choose ECH here
expectedOutput.push_back("\x1b[13X");
expectedOutput.push_back("\x1b[13C");
VERIFY_SUCCEEDED(renderer.PaintFrame());
}
void ConptyOutputTests::SetConsoleTitleWithControlChars()
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:control", L"{0x00, 0x0A, 0x1B, 0x80, 0x9B, 0x9C}")
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
END_TEST_METHOD_PROPERTIES()
int control;
VERIFY_SUCCEEDED(TestData::TryGetValue(L"control", control));
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
Log::Comment(NoThrowString().Format(
L"SetConsoleTitle with a control character (0x%02X) embedded in the text", control));
std::wstringstream titleText;
titleText << L"Hello " << wchar_t(control) << L"World!";
g.getConsoleInformation().SetTitle(titleText.str());
// This is the standard init sequences for the first frame.
expectedOutput.push_back("\x1b[2J");
expectedOutput.push_back("\x1b[m");
expectedOutput.push_back("\x1b[H");
// The title change is propagated as an OSC 0 sequence.
// Control characters are stripped, so it's always "Hello World".
expectedOutput.push_back("\x1b]0;Hello World!\a");
// This is also part of the standard init sequence.
expectedOutput.push_back("\x1b[?25h");
VERIFY_SUCCEEDED(renderer.PaintFrame());
}
void ConptyOutputTests::IncludeBackgroundColorChangesInFirstFrame()
{
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& sm = si.GetStateMachine();
sm.ProcessString(L"\x1b[41mRun 1 \x1b[42mRun 2 \x1b[43mRun 3 \x1b[m");
expectedOutput.push_back("\x1b[2J"); // standard init sequence for the first frame
expectedOutput.push_back("\x1b[m"); // standard init sequence for the first frame
expectedOutput.push_back("\x1b[41m");
expectedOutput.push_back("\x1b[H"); // standard init sequence for the first frame
expectedOutput.push_back("Run 1 ");
expectedOutput.push_back("\x1b[42m");
expectedOutput.push_back("Run 2 ");
expectedOutput.push_back("\x1b[43m");
expectedOutput.push_back("Run 3 ");
// This is also part of the standard init sequence.
expectedOutput.push_back("\x1b[?25h");
VERIFY_SUCCEEDED(renderer.PaintFrame());
}
void ConptyOutputTests::MoveCursorAfterWrapForced()
{
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& sm = si.GetStateMachine();
// We write a character in the rightmost column to trigger the _wrapForced
// flag. Technically this is a bug, but it's how things currently work.
sm.ProcessString(L"\x1b[1;999H*");
expectedOutput.push_back("\x1b[2J"); // standard init sequence for the first frame
expectedOutput.push_back("\x1b[m"); // standard init sequence for the first frame
expectedOutput.push_back("\x1b[1;80H");
expectedOutput.push_back("*");
expectedOutput.push_back("\x1b[?25h");
VERIFY_SUCCEEDED(renderer.PaintFrame());
// Position the cursor on line 2, and fill line 1 with A's.
sm.ProcessString(L"\x1b[2H");
sm.ProcessString(L"\033[65;1;1;1;999$x");
expectedOutput.push_back("\x1b[H");
expectedOutput.push_back("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
// The cursor must be explicitly moved to line 2 at the end of the frame.
// Although that may technically already be the next output location, we
// still need the cursor to be shown in that position when the frame ends.
expectedOutput.push_back("\r\n");
expectedOutput.push_back("\x1b[?25h");
VERIFY_SUCCEEDED(renderer.PaintFrame());
}

View File

@@ -30,8 +30,6 @@
<ClCompile Include="InputBufferTests.cpp" />
<ClCompile Include="ViewportTests.cpp" />
<ClCompile Include="VtIoTests.cpp" />
<ClCompile Include="VtRendererTests.cpp" />
<ClCompile Include="ConptyOutputTests.cpp" />
<ClCompile Include="..\precomp.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
@@ -46,9 +44,6 @@
<ProjectReference Include="..\..\renderer\atlas\atlas.vcxproj">
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
</ProjectReference>
<ProjectReference Include="..\..\renderer\vt\ut_lib\vt.unittest.vcxproj">
<Project>{990F2657-8580-4828-943F-5DD657D11843}</Project>
</ProjectReference>
<ProjectReference Include="..\..\interactivity\base\lib\InteractivityBase.vcxproj">
<Project>{06ec74cb-9a12-429c-b551-8562ec964846}</Project>
</ProjectReference>

View File

@@ -57,9 +57,6 @@
<ClCompile Include="VtIoTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="VtRendererTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="AliasTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@@ -81,9 +78,6 @@
<ClCompile Include="ObjectTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ConptyOutputTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="UnicodeLiteral.hpp">

View File

@@ -17,7 +17,6 @@
#include "../interactivity/inc/ServiceLocator.hpp"
#include "../../inc/conattrs.hpp"
#include "../../types/inc/Viewport.hpp"
#include "../../renderer/vt/Xterm256Engine.hpp"
#include "../../inc/TestUtils.h"
@@ -7931,11 +7930,9 @@ void ScreenBufferTests::TestReflowBiggerLongLineWithColor()
void ScreenBufferTests::TestDeferredMainBufferResize()
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:inConpty", L"{false, true}")
TEST_METHOD_PROPERTY(L"Data:reEnterAltBuffer", L"{false, true}")
END_TEST_METHOD_PROPERTIES();
INIT_TEST_PROPERTY(bool, inConpty, L"Should we pretend to be in conpty mode?");
INIT_TEST_PROPERTY(bool, reEnterAltBuffer, L"Should we re-enter the alt buffer when we're already in it?");
// A test for https://github.com/microsoft/terminal/pull/12719#discussion_r834860330
@@ -7946,31 +7943,6 @@ void ScreenBufferTests::TestDeferredMainBufferResize()
gci.LockConsole(); // Lock must be taken to manipulate buffer.
auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); });
// HUGELY cribbed from ConptyRoundtripTests::MethodSetup. This fakes the
// console into thinking that it's in ConPTY mode. Yes, we need all this
// just to get gci.IsInVtIoMode() to return true. The screen buffer gates
// all sorts of internal checks on that.
//
// This could theoretically be a helper if other tests need it.
if (inConpty)
{
Log::Comment(L"Set up ConPTY");
auto& currentBuffer = gci.GetActiveOutputBuffer();
// Set up an xterm-256 renderer for conpty
wil::unique_hfile hFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
auto initialViewport = currentBuffer.GetViewport();
auto vtRenderEngine = std::make_unique<Microsoft::Console::Render::Xterm256Engine>(std::move(hFile),
initialViewport);
// We don't care about the output, so let it just drain to the void.
vtRenderEngine->SetTestCallback([](auto&&, auto&&) -> bool { return true; });
gci.GetActiveOutputBuffer().SetTerminalConnection(vtRenderEngine.get());
// Manually set the console into conpty mode. We're not actually going
// to set up the pipes for conpty, but we want the console to behave
// like it would in conpty mode.
ServiceLocator::LocateGlobals().EnableConptyModeForTests(std::move(vtRenderEngine));
}
auto* siMain = &gci.GetActiveOutputBuffer();
auto& stateMachine = siMain->GetStateMachine();

View File

@@ -2,432 +2,588 @@
// Licensed under the MIT license.
#include "precomp.h"
#include <wextestclass.h>
#include "../../inc/consoletaeftemplates.hpp"
#include "../../types/inc/Viewport.hpp"
#include "../VtIo.hpp"
#include "../../interactivity/inc/ServiceLocator.hpp"
#include "../../renderer/base/Renderer.hpp"
#include "../../renderer/vt/Xterm256Engine.hpp"
#include "../../renderer/vt/XtermEngine.hpp"
#include "CommonState.hpp"
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace Microsoft::Console::Interactivity;
class Microsoft::Console::VirtualTerminal::VtIoTests
{
BEGIN_TEST_CLASS(VtIoTests)
TEST_CLASS_PROPERTY(L"IsolationLevel", L"Class")
END_TEST_CLASS()
// General Tests:
TEST_METHOD(NoOpStartTest);
TEST_METHOD(DtorTestJustEngine);
TEST_METHOD(DtorTestDeleteVtio);
TEST_METHOD(DtorTestStackAlloc);
TEST_METHOD(DtorTestStackAllocMany);
TEST_METHOD(RendererDtorAndThread);
TEST_METHOD(BasicAnonymousPipeOpeningWithSignalChannelTest);
};
using namespace Microsoft::Console;
using namespace Microsoft::Console::VirtualTerminal;
using namespace Microsoft::Console::Render;
using namespace Microsoft::Console::Types;
void VtIoTests::NoOpStartTest()
{
VtIo vtio;
VERIFY_IS_FALSE(vtio.IsUsingVt());
static constexpr WORD red = FOREGROUND_RED | BACKGROUND_GREEN;
static constexpr WORD blu = FOREGROUND_BLUE | BACKGROUND_GREEN;
Log::Comment(L"Verify we succeed at StartIfNeeded even if we weren't initialized");
VERIFY_SUCCEEDED(vtio.StartIfNeeded());
constexpr CHAR_INFO ci_red(wchar_t ch) noexcept
{
return { ch, red };
}
Viewport SetUpViewport()
constexpr CHAR_INFO ci_blu(wchar_t ch) noexcept
{
til::inclusive_rect view;
view.top = view.left = 0;
view.bottom = 31;
view.right = 79;
return Viewport::FromInclusive(view);
return { ch, blu };
}
void VtIoTests::DtorTestJustEngine()
#define cup(y, x) "\x1b[" #y ";" #x "H" // CUP: Cursor Position
#define decawm(h) "\x1b[?7" #h // DECAWM: Autowrap Mode
#define decsc() "\x1b\x37" // DECSC: DEC Save Cursor (+ attributes)
#define decrc() "\x1b\x38" // DECRC: DEC Restore Cursor (+ attributes)
// The escape sequences that ci_red() / ci_blu() result in.
#define sgr_red(s) "\x1b[0;31;42m" s
#define sgr_blu(s) "\x1b[0;34;42m" s
// What the default attributes `FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED` result in.
#define sgr_rst() "\x1b[0m"
static constexpr std::wstring_view s_initialContentVT{
// clang-format off
L""
sgr_red("AB") sgr_blu("ab") sgr_red("CD") sgr_blu("cd") "\r\n"
sgr_red("EF") sgr_blu("ef") sgr_red("GH") sgr_blu("gh") "\r\n"
sgr_blu("ij") sgr_red("IJ") sgr_blu("kl") sgr_red("KL") "\r\n"
sgr_blu("mn") sgr_red("MN") sgr_blu("op") sgr_red("OP")
// clang-format on
};
class ::Microsoft::Console::VirtualTerminal::VtIoTests
{
Log::Comment(NoThrowString().Format(
L"This test is going to instantiate a bunch of VtIos in different \n"
L"scenarios to see if something causes a weird cleanup.\n"
L"It's here because of the strange nature of VtEngine having members\n"
L"that are only defined in UNIT_TESTING"));
BEGIN_TEST_CLASS(VtIoTests)
TEST_CLASS_PROPERTY(L"IsolationLevel", L"Class")
END_TEST_CLASS()
Log::Comment(NoThrowString().Format(
L"New some engines and delete them"));
for (auto i = 0; i < 25; ++i)
CommonState commonState;
ApiRoutines routines;
SCREEN_INFORMATION* screenInfo = nullptr;
wil::unique_hfile rx;
char rxBuf[4096];
std::string_view readOutput() noexcept
{
Log::Comment(NoThrowString().Format(
L"New/Delete loop #%d", i));
wil::unique_hfile hOutputFile;
hOutputFile.reset(INVALID_HANDLE_VALUE);
auto pRenderer256 = new Xterm256Engine(std::move(hOutputFile), SetUpViewport());
Log::Comment(NoThrowString().Format(L"Made Xterm256Engine"));
delete pRenderer256;
Log::Comment(NoThrowString().Format(L"Deleted."));
hOutputFile.reset(INVALID_HANDLE_VALUE);
auto pRenderEngineXterm = new XtermEngine(std::move(hOutputFile), SetUpViewport(), false);
Log::Comment(NoThrowString().Format(L"Made XtermEngine"));
delete pRenderEngineXterm;
Log::Comment(NoThrowString().Format(L"Deleted."));
hOutputFile.reset(INVALID_HANDLE_VALUE);
auto pRenderEngineXtermAscii = new XtermEngine(std::move(hOutputFile), SetUpViewport(), true);
Log::Comment(NoThrowString().Format(L"Made XtermEngine"));
delete pRenderEngineXtermAscii;
Log::Comment(NoThrowString().Format(L"Deleted."));
}
}
void VtIoTests::DtorTestDeleteVtio()
{
Log::Comment(NoThrowString().Format(
L"This test is going to instantiate a bunch of VtIos in different \n"
L"scenarios to see if something causes a weird cleanup.\n"
L"It's here because of the strange nature of VtEngine having members\n"
L"that are only defined in UNIT_TESTING"));
Log::Comment(NoThrowString().Format(
L"New some engines and delete them"));
for (auto i = 0; i < 25; ++i)
{
Log::Comment(NoThrowString().Format(
L"New/Delete loop #%d", i));
auto hOutputFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
hOutputFile.reset(INVALID_HANDLE_VALUE);
auto vtio = new VtIo();
Log::Comment(NoThrowString().Format(L"Made VtIo"));
vtio->_pVtRenderEngine = std::make_unique<Xterm256Engine>(std::move(hOutputFile),
SetUpViewport());
Log::Comment(NoThrowString().Format(L"Made Xterm256Engine"));
delete vtio;
Log::Comment(NoThrowString().Format(L"Deleted."));
hOutputFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
vtio = new VtIo();
Log::Comment(NoThrowString().Format(L"Made VtIo"));
vtio->_pVtRenderEngine = std::make_unique<XtermEngine>(std::move(hOutputFile),
SetUpViewport(),
false);
Log::Comment(NoThrowString().Format(L"Made XtermEngine"));
delete vtio;
Log::Comment(NoThrowString().Format(L"Deleted."));
hOutputFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
vtio = new VtIo();
Log::Comment(NoThrowString().Format(L"Made VtIo"));
vtio->_pVtRenderEngine = std::make_unique<XtermEngine>(std::move(hOutputFile),
SetUpViewport(),
true);
Log::Comment(NoThrowString().Format(L"Made XtermEngine"));
delete vtio;
Log::Comment(NoThrowString().Format(L"Deleted."));
}
}
void VtIoTests::DtorTestStackAlloc()
{
Log::Comment(NoThrowString().Format(
L"This test is going to instantiate a bunch of VtIos in different \n"
L"scenarios to see if something causes a weird cleanup.\n"
L"It's here because of the strange nature of VtEngine having members\n"
L"that are only defined in UNIT_TESTING"));
Log::Comment(NoThrowString().Format(
L"make some engines and let them fall out of scope"));
for (auto i = 0; i < 25; ++i)
{
Log::Comment(NoThrowString().Format(
L"Scope Exit Auto cleanup #%d", i));
wil::unique_hfile hOutputFile;
hOutputFile.reset(INVALID_HANDLE_VALUE);
{
VtIo vtio;
vtio._pVtRenderEngine = std::make_unique<Xterm256Engine>(std::move(hOutputFile),
SetUpViewport());
}
hOutputFile.reset(INVALID_HANDLE_VALUE);
{
VtIo vtio;
vtio._pVtRenderEngine = std::make_unique<XtermEngine>(std::move(hOutputFile),
SetUpViewport(),
false);
}
hOutputFile.reset(INVALID_HANDLE_VALUE);
{
VtIo vtio;
vtio._pVtRenderEngine = std::make_unique<XtermEngine>(std::move(hOutputFile),
SetUpViewport(),
true);
}
}
}
void VtIoTests::DtorTestStackAllocMany()
{
Log::Comment(NoThrowString().Format(
L"This test is going to instantiate a bunch of VtIos in different \n"
L"scenarios to see if something causes a weird cleanup.\n"
L"It's here because of the strange nature of VtEngine having members\n"
L"that are only defined in UNIT_TESTING"));
Log::Comment(NoThrowString().Format(
L"Try an make a whole bunch all at once, and have them all fall out of scope at once."));
for (auto i = 0; i < 25; ++i)
{
Log::Comment(NoThrowString().Format(
L"Multiple engines, one scope loop #%d", i));
wil::unique_hfile hOutputFile;
{
hOutputFile.reset(INVALID_HANDLE_VALUE);
VtIo vtio1;
vtio1._pVtRenderEngine = std::make_unique<Xterm256Engine>(std::move(hOutputFile),
SetUpViewport());
hOutputFile.reset(INVALID_HANDLE_VALUE);
VtIo vtio2;
vtio2._pVtRenderEngine = std::make_unique<XtermEngine>(std::move(hOutputFile),
SetUpViewport(),
false);
hOutputFile.reset(INVALID_HANDLE_VALUE);
VtIo vtio3;
vtio3._pVtRenderEngine = std::make_unique<XtermEngine>(std::move(hOutputFile),
SetUpViewport(),
true);
}
}
}
class MockRenderData : public IRenderData
{
public:
Microsoft::Console::Types::Viewport GetViewport() noexcept override
{
return Microsoft::Console::Types::Viewport{};
DWORD read = 0;
ReadFile(rx.get(), &rxBuf[0], sizeof(rxBuf), &read, nullptr);
return { &rxBuf[0], read };
}
til::point GetTextBufferEndPosition() const noexcept override
void setupInitialContents() const
{
return {};
auto& sm = screenInfo->GetStateMachine();
sm.ProcessString(L"\033c");
sm.ProcessString(s_initialContentVT);
sm.ProcessString(L"\x1b[H" sgr_rst());
}
TextBuffer& GetTextBuffer() const noexcept override
void resetContents() const
{
FAIL_FAST_HR(E_NOTIMPL);
auto& sm = screenInfo->GetStateMachine();
sm.ProcessString(L"\033c");
}
const FontInfo& GetFontInfo() const noexcept override
TEST_CLASS_SETUP(ClassSetup)
{
FAIL_FAST_HR(E_NOTIMPL);
}
wil::unique_hfile tx;
//std::tie(tx, rx) = createOverlappedPipe(16 * 1024);
THROW_IF_WIN32_BOOL_FALSE(CreatePipe(rx.addressof(), tx.addressof(), nullptr, 16 * 1024));
std::vector<Microsoft::Console::Types::Viewport> GetSelectionRects() noexcept override
{
return std::vector<Microsoft::Console::Types::Viewport>{};
}
DWORD mode = PIPE_READMODE_BYTE | PIPE_NOWAIT;
THROW_IF_WIN32_BOOL_FALSE(SetNamedPipeHandleState(rx.get(), &mode, nullptr, nullptr));
void LockConsole() noexcept override
{
}
commonState.PrepareGlobalInputBuffer();
commonState.PrepareGlobalScreenBuffer(8, 4, 8, 4);
void UnlockConsole() noexcept override
{
}
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
THROW_IF_FAILED(gci.GetVtIoNoCheck()->_Initialize(nullptr, tx.release(), nullptr));
std::pair<COLORREF, COLORREF> GetAttributeColors(const TextAttribute& /*attr*/) const noexcept override
{
return std::make_pair(COLORREF{}, COLORREF{});
}
til::point GetCursorPosition() const noexcept override
{
return {};
}
bool IsCursorVisible() const noexcept override
{
return false;
}
bool IsCursorOn() const noexcept override
{
return false;
}
ULONG GetCursorHeight() const noexcept override
{
return 42ul;
}
CursorType GetCursorStyle() const noexcept override
{
return CursorType::FullBox;
}
ULONG GetCursorPixelWidth() const noexcept override
{
return 12ul;
}
bool IsCursorDoubleWidth() const override
{
return false;
}
const bool IsGridLineDrawingAllowed() noexcept override
{
return false;
}
const std::wstring_view GetConsoleTitle() const noexcept override
{
return std::wstring_view{};
}
const bool IsSelectionActive() const override
{
return false;
}
const bool IsBlockSelection() const noexcept override
{
return false;
}
void ClearSelection() override
{
}
void SelectNewRegion(const til::point /*coordStart*/, const til::point /*coordEnd*/) override
{
}
std::span<const til::point_span> GetSearchHighlights() const noexcept override
{
return {};
}
const til::point_span* GetSearchHighlightFocused() const noexcept override
{
return nullptr;
}
const til::point GetSelectionAnchor() const noexcept
{
return {};
}
const til::point GetSelectionEnd() const noexcept
{
return {};
}
const bool IsUiaDataInitialized() const noexcept
{
screenInfo = &gci.GetActiveOutputBuffer();
return true;
}
const std::wstring GetHyperlinkUri(uint16_t /*id*/) const
TEST_METHOD(SetConsoleCursorPosition)
{
return {};
THROW_IF_FAILED(routines.SetConsoleCursorPositionImpl(*screenInfo, { 2, 3 }));
THROW_IF_FAILED(routines.SetConsoleCursorPositionImpl(*screenInfo, { 0, 0 }));
THROW_IF_FAILED(routines.SetConsoleCursorPositionImpl(*screenInfo, { 7, 3 }));
THROW_IF_FAILED(routines.SetConsoleCursorPositionImpl(*screenInfo, { 3, 2 }));
const auto expected = cup(4, 3) cup(1, 1) cup(4, 8) cup(3, 4);
const auto actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
}
const std::wstring GetHyperlinkCustomId(uint16_t /*id*/) const
TEST_METHOD(SetConsoleOutputMode)
{
return {};
const auto initialMode = screenInfo->OutputMode;
const auto cleanup = wil::scope_exit([=]() {
screenInfo->OutputMode = initialMode;
});
screenInfo->OutputMode = 0;
THROW_IF_FAILED(routines.SetConsoleOutputModeImpl(*screenInfo, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN | ENABLE_LVB_GRID_WORLDWIDE)); // DECAWM ✔️
THROW_IF_FAILED(routines.SetConsoleOutputModeImpl(*screenInfo, ENABLE_PROCESSED_OUTPUT | DISABLE_NEWLINE_AUTO_RETURN | ENABLE_LVB_GRID_WORLDWIDE)); // DECAWM ✖️
THROW_IF_FAILED(routines.SetConsoleOutputModeImpl(*screenInfo, 0)); // DECAWM ✖️
THROW_IF_FAILED(routines.SetConsoleOutputModeImpl(*screenInfo, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | DISABLE_NEWLINE_AUTO_RETURN | ENABLE_LVB_GRID_WORLDWIDE)); // DECAWM ✔️
const auto expected =
decawm(h) // DECAWM ✔️
decawm(l) // DECAWM ✖️
decawm(h); // DECAWM ✔️
const auto actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
}
const std::vector<size_t> GetPatternId(const til::point /*location*/) const
TEST_METHOD(SetConsoleTitleW)
{
return {};
std::string_view expected;
std::string_view actual;
THROW_IF_FAILED(routines.SetConsoleTitleWImpl(
L"foobar"));
expected = "\x1b]0;foobar\a";
actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
THROW_IF_FAILED(routines.SetConsoleTitleWImpl(
L"foo"
"\u0001\u001f"
"bar"));
expected = "\x1b]0;foo☺▼bar\a";
actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
THROW_IF_FAILED(routines.SetConsoleTitleWImpl(
L"foo"
"\u0001\u001f"
"bar"
"\u007f\u009f"));
expected = "\x1b]0;foo☺▼bar⌂?\a";
actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
}
TEST_METHOD(SetConsoleCursorInfo)
{
THROW_IF_FAILED(routines.SetConsoleCursorInfoImpl(*screenInfo, 25, false));
THROW_IF_FAILED(routines.SetConsoleCursorInfoImpl(*screenInfo, 25, true));
const auto expected = "\x1b[?25l"
"\x1b[?25h";
const auto actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
}
TEST_METHOD(SetConsoleTextAttribute)
{
for (WORD i = 0; i < 16; i++)
{
THROW_IF_FAILED(routines.SetConsoleTextAttributeImpl(*screenInfo, i | BACKGROUND_RED));
}
for (WORD i = 0; i < 16; i++)
{
THROW_IF_FAILED(routines.SetConsoleTextAttributeImpl(*screenInfo, i << 4 | FOREGROUND_RED));
}
THROW_IF_FAILED(routines.SetConsoleTextAttributeImpl(*screenInfo, FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_INTENSITY | BACKGROUND_GREEN | COMMON_LVB_REVERSE_VIDEO));
THROW_IF_FAILED(routines.SetConsoleTextAttributeImpl(*screenInfo, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | COMMON_LVB_REVERSE_VIDEO));
const auto expected =
// 16 foreground colors
"\x1b[0;30;41m"
"\x1b[0;34;41m"
"\x1b[0;32;41m"
"\x1b[0;36;41m"
"\x1b[0;31;41m"
"\x1b[0;35;41m"
"\x1b[0;33;41m"
"\x1b[0;41m" // <-- default foreground (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED)
"\x1b[0;90;41m"
"\x1b[0;94;41m"
"\x1b[0;92;41m"
"\x1b[0;96;41m"
"\x1b[0;91;41m"
"\x1b[0;95;41m"
"\x1b[0;93;41m"
"\x1b[0;97;41m"
// 16 background colors
"\x1b[0;31m" // <-- default background (0)
"\x1b[0;31;44m"
"\x1b[0;31;42m"
"\x1b[0;31;46m"
"\x1b[0;31;41m"
"\x1b[0;31;45m"
"\x1b[0;31;43m"
"\x1b[0;31;47m"
"\x1b[0;31;100m"
"\x1b[0;31;104m"
"\x1b[0;31;102m"
"\x1b[0;31;106m"
"\x1b[0;31;101m"
"\x1b[0;31;105m"
"\x1b[0;31;103m"
"\x1b[0;31;107m"
// The remaining two calls
"\x1b[0;7;95;42m"
"\x1b[0;7m";
const auto actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
}
TEST_METHOD(WriteConsoleW)
{
resetContents();
size_t written;
std::unique_ptr<IWaitRoutine> waiter;
std::string_view expected;
std::string_view actual;
THROW_IF_FAILED(routines.WriteConsoleWImpl(*screenInfo, L"", written, false, waiter));
expected = "";
actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
// Force-wrap because we write up to the last column.
THROW_IF_FAILED(routines.WriteConsoleWImpl(*screenInfo, L"aaaaaaaa", written, false, waiter));
expected = "aaaaaaaa\r\n";
actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
// Force-wrap because we write up to the last column, but this time with a tab.
THROW_IF_FAILED(routines.WriteConsoleWImpl(*screenInfo, L"a\t\r\nb", written, false, waiter));
expected = "a\t\r\n\r\nb";
actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
}
TEST_METHOD(WriteConsoleOutputW)
{
resetContents();
std::array payload{ ci_red('a'), ci_red('b'), ci_blu('A'), ci_blu('B') };
const auto target = Viewport::FromDimensions({ 1, 1 }, { 4, 1 });
Viewport written;
THROW_IF_FAILED(routines.WriteConsoleOutputWImpl(*screenInfo, payload, target, written));
const auto expected = decsc() cup(2, 2) sgr_red("ab") sgr_blu("AB") decrc();
const auto actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
}
TEST_METHOD(WriteConsoleOutputAttribute)
{
setupInitialContents();
static constexpr std::array payload{ red, blu, red, blu };
static constexpr til::point target{ 6, 1 };
size_t written;
THROW_IF_FAILED(routines.WriteConsoleOutputAttributeImpl(*screenInfo, payload, target, written));
const auto expected =
decsc() //
cup(2, 7) sgr_red("g") sgr_blu("h") //
cup(3, 1) sgr_red("i") sgr_blu("j") //
decrc();
const auto actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
}
TEST_METHOD(WriteConsoleOutputCharacterW)
{
setupInitialContents();
size_t written = 0;
std::string_view expected;
std::string_view actual;
THROW_IF_FAILED(routines.WriteConsoleOutputCharacterWImpl(*screenInfo, L"foobar", { 5, 1 }, written));
expected =
decsc() //
cup(2, 6) sgr_red("f") sgr_blu("oo") //
cup(3, 1) sgr_blu("ba") sgr_red("r") //
decrc();
actual = readOutput();
VERIFY_ARE_EQUAL(6u, written);
VERIFY_ARE_EQUAL(expected, actual);
// Writing past the end of the buffer.
THROW_IF_FAILED(routines.WriteConsoleOutputCharacterWImpl(*screenInfo, L"foobar", { 5, 3 }, written));
expected =
decsc() //
cup(4, 6) sgr_blu("f") sgr_red("oo") //
decrc();
actual = readOutput();
VERIFY_ARE_EQUAL(3u, written);
VERIFY_ARE_EQUAL(expected, actual);
// Writing 3 wide chars while intersecting the last column.
THROW_IF_FAILED(routines.WriteConsoleOutputCharacterWImpl(*screenInfo, L"✨✅❌", { 5, 1 }, written));
expected =
decsc() //
cup(2, 6) sgr_red("") sgr_blu(" ") //
cup(3, 1) sgr_blu("") sgr_red("") //
decrc();
actual = readOutput();
VERIFY_ARE_EQUAL(3u, written);
VERIFY_ARE_EQUAL(expected, actual);
}
TEST_METHOD(FillConsoleOutputAttribute)
{
setupInitialContents();
size_t cellsModified = 0;
std::string_view expected;
std::string_view actual;
// Writing nothing should produce nothing.
THROW_IF_FAILED(routines.FillConsoleOutputAttributeImpl(*screenInfo, red, 0, {}, cellsModified));
expected = "";
actual = readOutput();
VERIFY_ARE_EQUAL(0u, cellsModified);
VERIFY_ARE_EQUAL(expected, actual);
// Writing at the start of a line.
THROW_IF_FAILED(routines.FillConsoleOutputAttributeImpl(*screenInfo, red, 3, { 0, 0 }, cellsModified));
expected =
decsc() //
cup(1, 1) sgr_red("ABa") //
decrc();
actual = readOutput();
VERIFY_ARE_EQUAL(3u, cellsModified);
VERIFY_ARE_EQUAL(expected, actual);
// Writing at the end of a line.
THROW_IF_FAILED(routines.FillConsoleOutputAttributeImpl(*screenInfo, red, 3, { 5, 0 }, cellsModified));
expected =
decsc() //
cup(1, 6) sgr_red("Dcd") //
decrc();
actual = readOutput();
VERIFY_ARE_EQUAL(3u, cellsModified);
VERIFY_ARE_EQUAL(expected, actual);
// Writing across 2 lines.
THROW_IF_FAILED(routines.FillConsoleOutputAttributeImpl(*screenInfo, blu, 8, { 4, 1 }, cellsModified));
expected =
decsc() //
cup(2, 5) sgr_blu("GHgh") //
cup(3, 1) sgr_blu("ijIJ") //
decrc();
actual = readOutput();
VERIFY_ARE_EQUAL(8u, cellsModified);
VERIFY_ARE_EQUAL(expected, actual);
}
TEST_METHOD(FillConsoleOutputCharacterW)
{
setupInitialContents();
size_t cellsModified = 0;
std::string_view expected;
std::string_view actual;
// Writing nothing should produce nothing.
THROW_IF_FAILED(routines.FillConsoleOutputCharacterWImpl(*screenInfo, L'a', 0, {}, cellsModified));
expected = "";
actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
// Writing at the start of a line.
THROW_IF_FAILED(routines.FillConsoleOutputCharacterWImpl(*screenInfo, L'a', 3, { 0, 0 }, cellsModified));
expected =
decsc() //
cup(1, 1) sgr_red("aa") sgr_blu("a") //
decrc();
actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
// Writing at the end of a line.
THROW_IF_FAILED(routines.FillConsoleOutputCharacterWImpl(*screenInfo, L'b', 3, { 5, 0 }, cellsModified));
expected =
decsc() //
cup(1, 6) sgr_red("b") sgr_blu("bb") //
decrc();
actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
// Writing across 2 lines.
THROW_IF_FAILED(routines.FillConsoleOutputCharacterWImpl(*screenInfo, L'c', 8, { 4, 1 }, cellsModified));
expected =
decsc() //
cup(2, 5) sgr_red("cc") sgr_blu("cc") //
cup(3, 1) sgr_blu("cc") sgr_red("cc") //
decrc();
actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
// Writing 3 wide chars while intersecting the last column.
THROW_IF_FAILED(routines.FillConsoleOutputCharacterWImpl(*screenInfo, L'', 3, { 5, 1 }, cellsModified));
expected =
decsc() //
cup(2, 6) sgr_red("") sgr_blu(" ") //
cup(3, 1) sgr_blu("") sgr_red("") //
decrc();
actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
}
TEST_METHOD(ScrollConsoleScreenBufferW)
{
std::string_view expected;
std::string_view actual;
setupInitialContents();
// Scrolling from nowhere to somewhere are no-ops and should not emit anything.
THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 0, 0, -1, -1 }, {}, std::nullopt, L' ', 0, false));
THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { -10, -10, -9, -9 }, {}, std::nullopt, L' ', 0, false));
expected = "";
actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
// Scrolling from somewhere to nowhere should clear the area.
THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 0, 0, 1, 1 }, { 10, 10 }, std::nullopt, L' ', red, false));
expected =
decsc() //
cup(1, 1) sgr_red(" ") //
cup(2, 1) sgr_red(" ") //
decrc();
actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
// cmd uses ScrollConsoleScreenBuffer to clear the buffer contents and that gets translated to a clear screen sequence.
THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 0, 0, 7, 3 }, { 0, -4 }, std::nullopt, 0, 0, true));
expected = "\x1b[H\x1b[2J\x1b[3J";
actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
//
// A B a b C D c d
//
// E F e f G H g h
//
// i j I J k l K L
//
// m n M N o p O P
//
setupInitialContents();
// Scrolling from somewhere to somewhere.
//
// +-------+
// A | Z Z | b C D c d
// | src |
// E | Z Z | f G H g h
// +-------+ +-------+
// i j I J k | B a | L
// | dst |
// m n M N o | F e | P
// +-------+
THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 1, 0, 2, 1 }, { 5, 2 }, std::nullopt, L'Z', red, false));
expected =
decsc() //
cup(1, 2) sgr_red("ZZ") //
cup(2, 2) sgr_red("ZZ") //
cup(3, 6) sgr_red("B") sgr_blu("a") //
cup(4, 6) sgr_red("F") sgr_blu("e") //
decrc();
actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
// Same, but with a partially out-of-bounds target and clip rect. Clip rects affect both
// the source area that gets filled and the target area that gets a copy of the source contents.
//
// A Z Z b C D c d
// +---+~~~~~~~~~~~~~~~~~~~~~~~+
// | E $ z z | f G H g $ h
// | $ src | +---$-------+
// | i $ z z | J k B | E $ L |
// +---$-------+ | $ dst |
// m $ n M N o F | i $ P |
// +~~~~~~~~~~~~~~~~~~~~~~~+-------+
// clip rect
THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 0, 1, 2, 2 }, { 6, 2 }, til::inclusive_rect{ 1, 1, 6, 3 }, L'z', blu, false));
expected =
decsc() //
cup(2, 2) sgr_blu("zz") //
cup(3, 2) sgr_blu("zz") //
cup(3, 7) sgr_red("E") //
cup(4, 7) sgr_blu("i") //
decrc();
actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
// Same, but with a partially out-of-bounds source.
// The boundaries of the buffer act as a clip rect for reading and so only 2 cells get copied.
//
// +-------+
// A Z Z b C D c | Y |
// | src |
// E z z f G H g | Y |
// +---+ +-------+
// i z z J | d | B E L
// |dst|
// m n M N | h | F i P
// +---+
THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 7, 0, 8, 1 }, { 4, 2 }, std::nullopt, L'Y', red, false));
expected =
decsc() //
cup(1, 8) sgr_red("Y") //
cup(2, 8) sgr_red("Y") //
cup(3, 5) sgr_blu("d") //
cup(4, 5) sgr_blu("h") //
decrc();
actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
static constexpr std::array<CHAR_INFO, 8 * 4> expectedContents{ {
// clang-format off
ci_red('A'), ci_red('Z'), ci_red('Z'), ci_blu('b'), ci_red('C'), ci_red('D'), ci_blu('c'), ci_red('Y'),
ci_red('E'), ci_blu('z'), ci_blu('z'), ci_blu('f'), ci_red('G'), ci_red('H'), ci_blu('g'), ci_red('Y'),
ci_blu('i'), ci_blu('z'), ci_blu('z'), ci_red('J'), ci_blu('d'), ci_red('B'), ci_red('E'), ci_red('L'),
ci_blu('m'), ci_blu('n'), ci_red('M'), ci_red('N'), ci_blu('h'), ci_red('F'), ci_blu('i'), ci_red('P'),
// clang-format on
} };
std::array<CHAR_INFO, 8 * 4> actualContents{};
Viewport actualContentsRead;
THROW_IF_FAILED(routines.ReadConsoleOutputWImpl(*screenInfo, actualContents, Viewport::FromDimensions({}, { 8, 4 }), actualContentsRead));
VERIFY_IS_TRUE(memcmp(expectedContents.data(), actualContents.data(), sizeof(actualContents)) == 0);
}
TEST_METHOD(SetConsoleActiveScreenBuffer)
{
SCREEN_INFORMATION* screenInfoAlt;
VERIFY_NT_SUCCESS(SCREEN_INFORMATION::CreateInstance(
screenInfo->GetViewport().Dimensions(),
screenInfo->GetCurrentFont(),
screenInfo->GetBufferSize().Dimensions(),
screenInfo->GetAttributes(),
screenInfo->GetPopupAttributes(),
screenInfo->GetTextBuffer().GetCursor().GetSize(),
&screenInfoAlt));
routines.SetConsoleActiveScreenBufferImpl(*screenInfoAlt);
setupInitialContents();
THROW_IF_FAILED(routines.SetConsoleOutputModeImpl(*screenInfoAlt, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING));
readOutput();
routines.SetConsoleActiveScreenBufferImpl(*screenInfo);
const auto expected =
"\x1b[?1049l" // ASB (Alternate Screen Buffer)
cup(1, 1) sgr_red("AB") sgr_blu("ab") sgr_red("CD") sgr_blu("cd") //
cup(2, 1) sgr_red("EF") sgr_blu("ef") sgr_red("GH") sgr_blu("gh") //
cup(3, 1) sgr_blu("ij") sgr_red("IJ") sgr_blu("kl") sgr_red("KL") //
cup(4, 1) sgr_blu("mn") sgr_red("MN") sgr_blu("op") sgr_red("OP") //
cup(1, 1) sgr_rst() //
"\x1b[?25h" // DECTCEM (Text Cursor Enable)
"\x1b[?7h"; // DECAWM (Autowrap Mode)
const auto actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
}
};
void VtIoTests::RendererDtorAndThread()
{
Log::Comment(NoThrowString().Format(
L"Test deleting a Renderer a bunch of times"));
for (auto i = 0; i < 16; ++i)
{
auto data = std::make_unique<MockRenderData>();
auto thread = std::make_unique<Microsoft::Console::Render::RenderThread>();
auto* pThread = thread.get();
auto pRenderer = std::make_unique<Microsoft::Console::Render::Renderer>(RenderSettings{}, data.get(), nullptr, 0, std::move(thread));
VERIFY_SUCCEEDED(pThread->Initialize(pRenderer.get()));
// Sleep for a hot sec to make sure the thread starts before we enable painting
// If you don't, the thread might wait on the paint enabled event AFTER
// EnablePainting gets called, and if that happens, then the thread will
// never get destructed. This will only ever happen in the vstest test runner,
// which is what CI uses.
/*Sleep(500);*/
pThread->EnablePainting();
pRenderer->TriggerTeardown();
pRenderer.reset();
}
}
void VtIoTests::BasicAnonymousPipeOpeningWithSignalChannelTest()
{
Log::Comment(L"Test using anonymous pipes for the input and adding a signal channel.");
Log::Comment(L"\tcreating pipes");
wil::unique_handle inPipeReadSide;
wil::unique_handle inPipeWriteSide;
wil::unique_handle outPipeReadSide;
wil::unique_handle outPipeWriteSide;
wil::unique_handle signalPipeReadSide;
wil::unique_handle signalPipeWriteSide;
VERIFY_WIN32_BOOL_SUCCEEDED(CreatePipe(&inPipeReadSide, &inPipeWriteSide, nullptr, 0), L"Create anonymous in pipe.");
VERIFY_WIN32_BOOL_SUCCEEDED(CreatePipe(&outPipeReadSide, &outPipeWriteSide, nullptr, 0), L"Create anonymous out pipe.");
VERIFY_WIN32_BOOL_SUCCEEDED(CreatePipe(&signalPipeReadSide, &signalPipeWriteSide, nullptr, 0), L"Create anonymous signal pipe.");
Log::Comment(L"\tinitializing vtio");
// CreateIoHandlers() assert()s on IsConsoleLocked() to guard against a race condition.
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.LockConsole();
const auto cleanup = wil::scope_exit([&]() {
gci.UnlockConsole();
});
VtIo vtio;
VERIFY_IS_FALSE(vtio.IsUsingVt());
VERIFY_ARE_EQUAL(nullptr, vtio._pPtySignalInputThread);
VERIFY_SUCCEEDED(vtio._Initialize(inPipeReadSide.release(), outPipeWriteSide.release(), signalPipeReadSide.release()));
VERIFY_SUCCEEDED(vtio.CreateAndStartSignalThread());
VERIFY_SUCCEEDED(vtio.CreateIoHandlers());
VERIFY_IS_TRUE(vtio.IsUsingVt());
VERIFY_ARE_NOT_EQUAL(nullptr, vtio._pPtySignalInputThread);
}

File diff suppressed because it is too large Load Diff

View File

@@ -31,8 +31,6 @@ SOURCES = \
TitleTests.cpp \
InputBufferTests.cpp \
VtIoTests.cpp \
VtRendererTests.cpp \
ConptyOutputTests.cpp \
ViewportTests.cpp \
ConsoleArgumentsTests.cpp \
ObjectTests.cpp \

View File

@@ -82,16 +82,6 @@
#define ENABLE_INTSAFE_SIGNED_FUNCTIONS
#include <intsafe.h>
// LibPopCnt - Fast C/C++ bit population count library (on bits in an array)
#include <libpopcnt.h>
// Dynamic Bitset (optional dependency on LibPopCnt for perf at bit counting)
// Variable-size compressed-storage header-only bit flag storage library.
#pragma warning(push)
#pragma warning(disable:4702) // unreachable code
#include <dynamic_bitset.hpp>
#pragma warning(pop)
// {fmt}, a C++20-compatible formatting library
#include <fmt/format.h>
#include <fmt/compile.h>

View File

@@ -27,9 +27,6 @@
#ifndef PSEUDOCONSOLE_INHERIT_CURSOR
#define PSEUDOCONSOLE_INHERIT_CURSOR (0x1)
#endif
#ifndef PSEUDOCONSOLE_RESIZE_QUIRK
#define PSEUDOCONSOLE_RESIZE_QUIRK (0x2)
#endif
#ifndef PSEUDOCONSOLE_GLYPH_WIDTH__MASK
#define PSEUDOCONSOLE_GLYPH_WIDTH__MASK 0x18
#define PSEUDOCONSOLE_GLYPH_WIDTH_GRAPHEMES 0x08

View File

@@ -15,11 +15,11 @@
#define _TIL_INLINEPREFIX __declspec(noinline) inline
#include "til/at.h"
#include "til/bitmap.h"
#include "til/coalesce.h"
#include "til/color.h"
#include "til/enumset.h"
#include "til/pmr.h"
#include "til/rect.h"
#include "til/string.h"
#include "til/type_traits.h"
#include "til/u8u16convert.h"

View File

@@ -1,593 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "rect.h"
#ifdef UNIT_TESTING
class BitmapTests;
#endif
namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
namespace details
{
template<typename Allocator>
class _bitmap_const_iterator
{
public:
using iterator_category = std::input_iterator_tag;
using value_type = const til::rect;
using difference_type = ptrdiff_t;
using pointer = const til::rect*;
using reference = const til::rect&;
_bitmap_const_iterator(const dynamic_bitset<size_t, Allocator>& values, til::rect rc, ptrdiff_t pos) :
_values(values),
_rc(rc),
_pos(pos),
_end(rc.size().area())
{
_calculateArea();
}
_bitmap_const_iterator& operator++()
{
_pos = _nextPos;
_calculateArea();
return (*this);
}
_bitmap_const_iterator operator++(int)
{
const auto prev = *this;
++*this;
return prev;
}
constexpr bool operator==(const _bitmap_const_iterator& other) const noexcept
{
return _pos == other._pos && _values == other._values;
}
constexpr bool operator!=(const _bitmap_const_iterator& other) const noexcept
{
return !(*this == other);
}
constexpr bool operator<(const _bitmap_const_iterator& other) const noexcept
{
return _pos < other._pos;
}
constexpr bool operator>(const _bitmap_const_iterator& other) const noexcept
{
return _pos > other._pos;
}
constexpr reference operator*() const noexcept
{
return _run;
}
constexpr pointer operator->() const noexcept
{
return &_run;
}
private:
const dynamic_bitset<size_t, Allocator>& _values;
const til::rect _rc;
size_t _pos;
size_t _nextPos;
const size_t _end;
til::rect _run;
// Update _run to contain the next rectangle of consecutively set bits within this bitmap.
// _calculateArea may be called repeatedly to yield all those rectangles.
void _calculateArea()
{
// The following logic first finds the next set bit in this bitmap and the next unset bit past that.
// The area in between those positions are thus all set bits and will end up being the next _run.
// dynamic_bitset allows you to quickly find the next set bit using find_next(prev),
// where "prev" is the position _past_ which should be searched (i.e. excluding position "prev").
// If _pos is still 0, we thus need to use the counterpart find_first().
_nextPos = _pos == 0 ? _values.find_first() : _values.find_next(_pos - 1);
// If we haven't reached the end yet...
if (_nextPos < _end)
{
// pos is now at the first on bit.
// If no next set bit can be found, npos is returned, which is SIZE_T_MAX.
// saturated_cast can ensure that this will be converted to CoordType's max (which is greater than _end).
const auto runStart = _rc.point_at(base::saturated_cast<CoordType>(_nextPos));
// We'll only count up until the end of this row.
// a run can be a max of one row tall.
const size_t rowEndIndex = _rc.index_of<size_t>(til::point(_rc.right - 1, runStart.y)) + 1;
// Find the length for the rectangle.
size_t runLength = 0;
// We have at least 1 so start with a do/while.
do
{
++_nextPos;
++runLength;
} while (_nextPos < rowEndIndex && _values[_nextPos]);
// Keep going until we reach end of row, end of the buffer, or the next bit is off.
// Assemble and store that run.
_run = til::rect{ runStart, til::size{ base::saturated_cast<CoordType>(runLength), 1 } };
}
else
{
// If we reached the end _nextPos may be >= _end (potentially even PTRDIFF_T_MAX).
// ---> Mark the end of the iterator by updating the state with _end.
_pos = _end;
_nextPos = _end;
_run = til::rect{};
}
}
};
template<typename Allocator = std::allocator<size_t>>
class bitmap
{
public:
using allocator_type = Allocator;
using const_iterator = details::_bitmap_const_iterator<allocator_type>;
private:
using run_allocator_type = typename std::allocator_traits<allocator_type>::template rebind_alloc<til::rect>;
public:
explicit bitmap(const allocator_type& allocator) noexcept :
_alloc{ allocator },
_sz{},
_rc{},
_bits{ _alloc },
_runs{ _alloc }
{
}
bitmap() noexcept :
bitmap(allocator_type{})
{
}
bitmap(til::size sz) :
bitmap(sz, false, allocator_type{})
{
}
bitmap(til::size sz, const allocator_type& allocator) :
bitmap(sz, false, allocator)
{
}
bitmap(til::size sz, bool fill, const allocator_type& allocator) :
_alloc{ allocator },
_sz(sz),
_rc(sz),
_bits(_sz.area(), fill ? std::numeric_limits<unsigned long long>::max() : 0, _alloc),
_runs{ _alloc }
{
}
bitmap(til::size sz, bool fill) :
bitmap(sz, fill, allocator_type{})
{
}
bitmap(const bitmap& other) :
_alloc{ std::allocator_traits<allocator_type>::select_on_container_copy_construction(other._alloc) },
_sz{ other._sz },
_rc{ other._rc },
_bits{ other._bits },
_runs{ other._runs }
{
// copy constructor is required to call select_on_container_copy
}
bitmap& operator=(const bitmap& other)
{
if constexpr (std::allocator_traits<allocator_type>::propagate_on_container_copy_assignment::value)
{
_alloc = other._alloc;
}
_sz = other._sz;
_rc = other._rc;
_bits = other._bits;
_runs = other._runs;
return *this;
}
bitmap(bitmap&& other) noexcept :
_alloc{ std::move(other._alloc) },
_sz{ std::move(other._sz) },
_rc{ std::move(other._rc) },
_bits{ std::move(other._bits) },
_runs{ std::move(other._runs) }
{
}
bitmap& operator=(bitmap&& other) noexcept
{
if constexpr (std::allocator_traits<allocator_type>::propagate_on_container_move_assignment::value)
{
_alloc = std::move(other._alloc);
}
_bits = std::move(other._bits);
_runs = std::move(other._runs);
_sz = std::move(other._sz);
_rc = std::move(other._rc);
return *this;
}
~bitmap() {}
void swap(bitmap& other)
{
if constexpr (std::allocator_traits<allocator_type>::propagate_on_container_swap::value)
{
std::swap(_alloc, other._alloc);
}
std::swap(_bits, other._bits);
std::swap(_runs, other._runs);
std::swap(_sz, other._sz);
std::swap(_rc, other._rc);
}
constexpr bool operator==(const bitmap& other) const noexcept
{
return _sz == other._sz &&
_rc == other._rc &&
_bits == other._bits;
// _runs excluded because it's a cache of generated state.
}
constexpr bool operator!=(const bitmap& other) const noexcept
{
return !(*this == other);
}
const_iterator begin() const
{
return const_iterator(_bits, til::rect{ _sz }, 0);
}
const_iterator end() const
{
return const_iterator(_bits, til::rect{ _sz }, _sz.area());
}
const std::span<const til::rect> runs() const
{
// If we don't have cached runs, rebuild.
if (!_runs.has_value())
{
_runs.emplace(begin(), end());
}
// Return the runs.
return _runs.value();
}
// optional fill the uncovered area with bits.
void translate(const til::point delta, bool fill = false)
{
if (delta.x == 0)
{
// fast path by using bit shifting
translate_y(delta.y, fill);
return;
}
// FUTURE: PERF: GH #4015: This could use in-place walk semantics instead of a temporary.
bitmap<allocator_type> other{ _sz, _alloc };
for (auto run : *this)
{
// Offset by the delta
run += delta;
// Intersect with the bounds of our bitmap area
// as part of it could have slid out of bounds.
run &= _rc;
// Set it into the new bitmap.
other.set(run);
}
// If we were asked to fill... find the uncovered region.
if (fill)
{
// Original Rect of As.
//
// X <-- origin
// A A A A
// A A A A
// A A A A
// A A A A
const auto originalRect = _rc;
// If Delta = (2, 2)
// Translated Rect of Bs.
//
// X <-- origin
//
//
// B B B B
// B B B B
// B B B B
// B B B B
const auto translatedRect = _rc + delta;
// Subtract the B from the A one to see what wasn't filled by the move.
// C is the overlap of A and B:
//
// X <-- origin
// A A A A 1 1 1 1
// A A A A 1 1 1 1
// A A C C B B subtract 2 2
// A A C C B B ---------> 2 2
// B B B B A - B
// B B B B
//
// 1 and 2 are the spaces to fill that are "uncovered".
const auto fillRects = originalRect - translatedRect;
for (const auto& f : fillRects)
{
other.set(f);
}
}
// Swap us with the temporary one.
std::swap(other, *this);
}
void set(const til::point pt)
{
if (_rc.contains(pt))
{
_runs.reset(); // reset cached runs on any non-const method
_bits.set(_rc.index_of(pt));
}
}
void set(til::rect rc)
{
_runs.reset(); // reset cached runs on any non-const method
rc &= _rc;
const auto width = rc.width();
const auto stride = _rc.width();
auto idx = _rc.index_of({ rc.left, rc.top });
for (auto row = rc.top; row < rc.bottom; ++row, idx += stride)
{
_bits.set(idx, width, true);
}
}
void set_all() noexcept
{
_runs.reset(); // reset cached runs on any non-const method
_bits.set();
}
void reset_all() noexcept
{
_runs.reset(); // reset cached runs on any non-const method
_bits.reset();
}
// True if we resized. False if it was the same size as before.
// Set fill if you want the new region (on growing) to be marked dirty.
bool resize(til::size size, bool fill = false)
{
_runs.reset(); // reset cached runs on any non-const method
// Don't resize if it's not different
if (_sz != size)
{
// Make a new bitmap for the other side, empty initially.
bitmap<allocator_type> newMap{ size, false, _alloc };
// Copy any regions that overlap from this map to the new one.
// Just iterate our runs...
for (const auto& run : *this)
{
// intersect them with the new map
// so we don't attempt to set bits that fit outside
// the new one.
const auto intersect = run & newMap._rc;
// and if there is still anything left, set them.
if (!intersect.empty())
{
newMap.set(intersect);
}
}
// Then, if we were requested to fill the new space on growing,
// find the space in the new rectangle that wasn't in the old
// and fill it up.
if (fill)
{
// A subtraction will yield anything in the new that isn't
// a part of the old.
const auto newAreas = newMap._rc - _rc;
for (const auto& area : newAreas)
{
newMap.set(area);
}
}
// Swap and return.
std::swap(newMap, *this);
return true;
}
else
{
return false;
}
}
constexpr bool one() const noexcept
{
return _bits.count() == 1;
}
constexpr bool any() const noexcept
{
return !none();
}
constexpr bool none() const noexcept
{
return _bits.none();
}
constexpr bool all() const noexcept
{
return _bits.all();
}
constexpr til::size size() const noexcept
{
return _sz;
}
std::wstring to_string() const
{
auto str = fmt::format(FMT_COMPILE(L"Bitmap of size {} contains the following dirty regions:\nRuns:"), _sz.to_string());
for (auto& item : *this)
{
fmt::format_to(std::back_inserter(str), FMT_COMPILE(L"\n\t- {}"), item.to_string());
}
return str;
}
private:
void translate_y(ptrdiff_t delta_y, bool fill)
{
if (delta_y == 0)
{
return;
}
const auto bitShift = delta_y * _sz.width;
#pragma warning(push)
// we can't depend on GSL here, so we use static_cast for explicit narrowing
#pragma warning(disable : 26472)
const auto newBits = static_cast<size_t>(std::abs(bitShift));
#pragma warning(pop)
const bool isLeftShift = bitShift > 0;
if (newBits >= _bits.size())
{
if (fill)
{
set_all();
}
else
{
reset_all();
}
return;
}
if (isLeftShift)
{
// This operator doesn't modify the size of `_bits`: the
// new bits are set to 0.
_bits <<= newBits;
}
else
{
_bits >>= newBits;
}
if (fill)
{
if (isLeftShift)
{
_bits.set(0, newBits, true);
}
else
{
_bits.set(_bits.size() - newBits, newBits, true);
}
}
_runs.reset(); // reset cached runs on any non-const method
}
allocator_type _alloc;
til::size _sz;
til::rect _rc;
dynamic_bitset<size_t, allocator_type> _bits;
mutable std::optional<std::vector<til::rect, run_allocator_type>> _runs;
#ifdef UNIT_TESTING
friend class ::BitmapTests;
#endif
};
}
using bitmap = ::til::details::bitmap<>;
namespace pmr
{
using bitmap = ::til::details::bitmap<std::pmr::polymorphic_allocator<size_t>>;
}
}
#ifdef __WEX_COMMON_H__
namespace WEX::TestExecution
{
template<typename T>
class VerifyOutputTraits<::til::details::bitmap<T>>
{
public:
static WEX::Common::NoThrowString ToString(const ::til::details::bitmap<T>& rect)
{
return WEX::Common::NoThrowString(rect.to_string().c_str());
}
};
template<typename T>
class VerifyCompareTraits<::til::details::bitmap<T>, ::til::details::bitmap<T>>
{
public:
static bool AreEqual(const ::til::details::bitmap<T>& expected, const ::til::details::bitmap<T>& actual) noexcept
{
return expected == actual;
}
static bool AreSame(const ::til::details::bitmap<T>& expected, const ::til::details::bitmap<T>& actual) noexcept
{
return &expected == &actual;
}
static bool IsLessThan(const ::til::details::bitmap<T>& expectedLess, const ::til::details::bitmap<T>& expectedGreater) = delete;
static bool IsGreaterThan(const ::til::details::bitmap<T>& expectedGreater, const ::til::details::bitmap<T>& expectedLess) = delete;
static bool IsNull(const ::til::details::bitmap<T>& object) noexcept
{
return object == til::details::bitmap<T>{};
}
};
};
#endif

View File

@@ -9,20 +9,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
inline constexpr CoordType CoordTypeMin = INT32_MIN;
inline constexpr CoordType CoordTypeMax = INT32_MAX;
namespace details
{
template<typename T, typename U = T>
constexpr U extract(const ::base::CheckedNumeric<T>& num)
{
U val;
if (!num.AssignIfValid(&val))
{
throw gsl::narrowing_error{};
}
return val;
}
}
struct point
{
CoordType x = 0;
@@ -81,78 +67,62 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return y > other.y || (y == other.y && x >= other.x);
}
constexpr point operator+(const point other) const
constexpr point operator+(const point other) const noexcept
{
auto copy = *this;
copy += other;
return copy;
}
constexpr point& operator+=(const point other)
constexpr point& operator+=(const point other) noexcept
{
x = details::extract(::base::CheckAdd(x, other.x));
y = details::extract(::base::CheckAdd(y, other.y));
x += other.x;
y += other.y;
return *this;
}
constexpr point operator-(const point other) const
constexpr point operator-(const point other) const noexcept
{
auto copy = *this;
copy -= other;
return copy;
}
constexpr point& operator-=(const point other)
constexpr point& operator-=(const point other) noexcept
{
x = details::extract(::base::CheckSub(x, other.x));
y = details::extract(::base::CheckSub(y, other.y));
x -= other.x;
y -= other.y;
return *this;
}
constexpr point operator*(const point other) const
constexpr point operator*(const point other) const noexcept
{
auto copy = *this;
copy *= other;
return copy;
}
constexpr point& operator*=(const point other)
constexpr point& operator*=(const point other) noexcept
{
x = details::extract(::base::CheckMul(x, other.x));
y = details::extract(::base::CheckMul(y, other.y));
x *= other.x;
y *= other.y;
return *this;
}
constexpr point operator/(const point other) const
constexpr point operator/(const point other) const noexcept
{
auto copy = *this;
copy /= other;
return copy;
}
constexpr point& operator/=(const point other)
constexpr point& operator/=(const point other) noexcept
{
x = details::extract(::base::CheckDiv(x, other.x));
y = details::extract(::base::CheckDiv(y, other.y));
x /= other.x;
y /= other.y;
return *this;
}
constexpr point operator*(const til::CoordType scale) const
{
return point{
details::extract(::base::CheckMul(x, scale)),
details::extract(::base::CheckMul(y, scale)),
};
}
constexpr point operator/(const til::CoordType scale) const
{
return point{
details::extract(::base::CheckDiv(x, scale)),
details::extract(::base::CheckDiv(y, scale)),
};
}
template<typename T>
constexpr T narrow_x() const
{

View File

@@ -67,83 +67,8 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
RETURN_WIN32(ERROR_UNHANDLED_EXCEPTION);
}
namespace details
{
class _rectangle_const_iterator
{
public:
constexpr _rectangle_const_iterator(point topLeft, point bottomRight) :
_topLeft{ topLeft },
_bottomRight{ bottomRight },
_current{ topLeft }
{
}
constexpr _rectangle_const_iterator(point topLeft, point bottomRight, point start) :
_topLeft{ topLeft },
_bottomRight{ bottomRight },
_current{ start }
{
}
_rectangle_const_iterator& operator++()
{
const auto nextX = details::extract(::base::CheckAdd(_current.x, 1));
if (nextX >= _bottomRight.x)
{
const auto nextY = details::extract(::base::CheckAdd(_current.y, 1));
// Note for the standard Left-to-Right, Top-to-Bottom walk,
// the end position is one cell below the bottom left.
// (or more accurately, on the exclusive bottom line in the inclusive left column.)
_current = { _topLeft.x, nextY };
}
else
{
_current = { nextX, _current.y };
}
return (*this);
}
constexpr bool operator==(const _rectangle_const_iterator& rhs) const noexcept
{
// `__builtin_memcmp` isn't an official standard, but it's the
// only way at the time of writing to get a constexpr `memcmp`.
return __builtin_memcmp(this, &rhs, sizeof(rhs)) == 0;
}
constexpr bool operator!=(const _rectangle_const_iterator& rhs) const noexcept
{
return __builtin_memcmp(this, &rhs, sizeof(rhs)) != 0;
}
constexpr bool operator<(const _rectangle_const_iterator& other) const
{
return _current < other._current;
}
constexpr bool operator>(const _rectangle_const_iterator& other) const
{
return _current > other._current;
}
constexpr point operator*() const
{
return _current;
}
protected:
point _current;
const point _topLeft;
const point _bottomRight;
};
}
struct rect
{
using const_iterator = details::_rectangle_const_iterator;
CoordType left = 0;
CoordType top = 0;
CoordType right = 0;
@@ -160,7 +85,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
// a math type is required. If you _don't_ provide one, you're going to
// get a compile-time error about "cannot convert from initializer-list to til::point"
template<typename TilMath, typename T>
constexpr rect(TilMath, T left, T top, T right, T bottom) :
constexpr rect(TilMath, T left, T top, T right, T bottom) noexcept :
left{ TilMath::template cast<CoordType>(left) },
top{ TilMath::template cast<CoordType>(top) },
right{ TilMath::template cast<CoordType>(right) },
@@ -184,7 +109,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
// Creates a rect at the given top-left corner point X,Y that extends
// down (+Y direction) and right (+X direction) for the given size.
constexpr rect(point topLeft, size size) :
constexpr rect(point topLeft, size size) noexcept :
rect{ topLeft, topLeft + size }
{
}
@@ -201,17 +126,17 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return __builtin_memcmp(this, &rhs, sizeof(rhs)) != 0;
}
constexpr rect to_origin(const rect& other) const
constexpr rect to_origin(const rect& other) const noexcept
{
return to_origin(other.origin());
}
constexpr rect to_origin(const point& origin) const
constexpr rect to_origin(const point& origin) const noexcept
{
const auto l = details::extract(::base::CheckSub(left, origin.x));
const auto t = details::extract(::base::CheckSub(top, origin.y));
const auto r = details::extract(::base::CheckSub(right, origin.x));
const auto b = details::extract(::base::CheckSub(bottom, origin.y));
const auto l = left - origin.x;
const auto t = top - origin.y;
const auto r = right - origin.x;
const auto b = bottom - origin.y;
return { l, t, r, b };
}
@@ -220,22 +145,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return left >= 0 && top >= 0 && right > left && bottom > top;
}
constexpr const_iterator begin() const
{
return const_iterator({ left, top }, { right, bottom });
}
constexpr const_iterator end() const
{
// For the standard walk: Left-To-Right then Top-To-Bottom
// the end box is one cell below the left most column.
// |----| 5x2 square. Remember bottom & right are exclusive
// | | while top & left are inclusive.
// X----- X is the end position.
return const_iterator({ left, top }, { right, bottom }, { left, bottom });
}
#pragma region RECTANGLE OPERATORS
// OR = union
constexpr rect operator|(const rect& other) const noexcept
@@ -442,10 +351,10 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
// ADD will translate (offset) the rect by the point.
constexpr rect operator+(const point point) const
{
const auto l = details::extract(::base::CheckAdd(left, point.x));
const auto t = details::extract(::base::CheckAdd(top, point.y));
const auto r = details::extract(::base::CheckAdd(right, point.x));
const auto b = details::extract(::base::CheckAdd(bottom, point.y));
const auto l = left + point.x;
const auto t = top + point.y;
const auto r = right + point.x;
const auto b = bottom + point.y;
return { l, t, r, b };
}
@@ -458,10 +367,10 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
// SUB will translate (offset) the rect by the point.
constexpr rect operator-(const point point) const
{
const auto l = details::extract(::base::CheckSub(left, point.x));
const auto t = details::extract(::base::CheckSub(top, point.y));
const auto r = details::extract(::base::CheckSub(right, point.x));
const auto b = details::extract(::base::CheckSub(bottom, point.y));
const auto l = left - point.x;
const auto t = top - point.y;
const auto r = right - point.x;
const auto b = bottom - point.y;
return { l, t, r, b };
}
@@ -471,48 +380,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return *this;
}
#pragma endregion
#pragma region RECTANGLE VS SIZE
// scale_up will scale the entire rect up by the size factor
constexpr rect scale_up(const size size) const
{
return rect{
details::extract(::base::CheckMul(left, size.width)),
details::extract(::base::CheckMul(top, size.height)),
details::extract(::base::CheckMul(right, size.width)),
details::extract(::base::CheckMul(bottom, size.height)),
};
}
// scale_down will scale the entire rect down by the size factor.
// The top/left corner is rounded down (floor) and
// the bottom/right corner is rounded up (ceil).
constexpr rect scale_down(const size size) const
{
// The integer ceil division `((a - 1) / b) + 1` only works for numbers >0.
// Support for negative numbers wasn't deemed useful at this point.
if ((left < 0) | (top < 0) | (right < 0) | (bottom < 0) | (size.width <= 0) | (size.height <= 0))
{
throw std::invalid_argument{ "invalid til::rect::scale_down" };
}
// Imagine a terminal of 120x30 "cells" with each cell being
// 5x10 pixels large. The terminal is therefore 600x300 pixels.
// Given a rectangle in pixel coordinates, what's the rectangle in cell coordinates?
// Clearly this requires us to floor() top/left and ceil() bottom/right to cover all pixels.
// And thus:
// {17, 24, 31, 38}.scale_down({5, 10}) == {3, 2, 7, 4}
// {3, 2, 7, 4}.scale_up({5, 10}) == {15, 20, 35, 40}
return rect{
left / size.width,
top / size.height,
right != 0 ? (right - 1) / size.width + 1 : 0,
bottom != 0 ? (bottom - 1) / size.height + 1 : 0,
};
}
#pragma endregion
template<typename T>
@@ -539,26 +406,26 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return gsl::narrow<T>(bottom);
}
constexpr CoordType width() const
constexpr CoordType width() const noexcept
{
return details::extract(::base::CheckSub(right, left));
return right - left;
}
template<typename T>
constexpr T narrow_width() const
{
return details::extract<CoordType, T>(::base::CheckSub(right, left));
return gsl::narrow<T>(width());
}
constexpr CoordType height() const
constexpr CoordType height() const noexcept
{
return details::extract(::base::CheckSub(bottom, top));
return bottom - top;
}
template<typename T>
constexpr T narrow_height() const
{
return details::extract<CoordType, T>(::base::CheckSub(bottom, top));
return gsl::narrow<T>(width());
}
constexpr point origin() const noexcept
@@ -586,40 +453,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return rc.left >= left && rc.top >= top && rc.right <= right && rc.bottom <= bottom;
}
template<typename T = CoordType>
constexpr T index_of(point pt) const
{
THROW_HR_IF(E_INVALIDARG, !contains(pt));
// Take Y away from the top to find how many rows down
auto check = ::base::CheckSub(pt.y, top);
// Multiply by the width because we've passed that many
// widths-worth of indices.
check *= width();
// Then add in the last few indices in the x position this row
// and subtract left to find the offset from left edge.
check = check + pt.x - left;
return details::extract<CoordType, T>(check);
}
point point_at(size_t index) const
{
const auto width = details::extract<CoordType, size_t>(::base::CheckSub(right, left));
const auto area = details::extract<CoordType, size_t>(::base::CheckSub(bottom, top) * width);
THROW_HR_IF(E_INVALIDARG, index >= area);
// Not checking math on these because we're presuming
// that the point can't be in bounds of a rect where
// this would overflow on addition after the division.
const auto quot = gsl::narrow_cast<CoordType>(index / width);
const auto rem = gsl::narrow_cast<CoordType>(index % width);
return point{ left + rem, top + quot };
}
#ifdef _WINCONTYPES_
// NOTE: This will convert from INCLUSIVE on the way in because
// that is generally how SMALL_RECTs are handled in console code and via the APIs.

View File

@@ -42,60 +42,35 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return width > 0 && height > 0;
}
constexpr size operator+(const size other) const
constexpr size operator+(const size other) const noexcept
{
return size{
details::extract(::base::CheckAdd(width, other.width)),
details::extract(::base::CheckAdd(height, other.height)),
width + other.width,
height + other.height,
};
}
constexpr size operator-(const size other) const
constexpr size operator-(const size other) const noexcept
{
return size{
details::extract(::base::CheckSub(width, other.width)),
details::extract(::base::CheckSub(height, other.height)),
width - other.width,
height - other.height,
};
}
constexpr size operator*(const size other) const
constexpr size operator*(const size other) const noexcept
{
return size{
details::extract(::base::CheckMul(width, other.width)),
details::extract(::base::CheckMul(height, other.height)),
width * other.width,
height * other.height,
};
}
constexpr size operator/(const size other) const
constexpr size operator/(const size other) const noexcept
{
return size{
details::extract(::base::CheckDiv(width, other.width)),
details::extract(::base::CheckDiv(height, other.height)),
};
}
template<typename TilMath, typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
[[nodiscard]] constexpr size scale(TilMath math, const T scale) const
{
return {
math,
width * scale,
height * scale,
};
}
[[nodiscard]] constexpr size divide_ceil(const size other) const
{
// The integer ceil division `((a - 1) / b) + 1` only works for numbers >0.
// Support for negative numbers wasn't deemed useful at this point.
if ((width < 0) | (height < 0) | (other.width <= 0) | (other.height <= 0))
{
throw std::invalid_argument{ "invalid til::size::divide_ceil" };
}
return {
width != 0 ? (width - 1) / other.width + 1 : 0,
height != 0 ? (height - 1) / other.height + 1 : 0,
width / other.width,
height / other.height,
};
}

View File

@@ -66,9 +66,13 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
out.clear();
RETURN_HR_IF(S_OK, in.empty());
int lengthRequired{};
if (in.length() > size_t(INT_MAX))
{
return E_ABORT;
}
// The worst ratio of UTF-8 code units to UTF-16 code units is 1 to 1 if UTF-8 consists of ASCII only.
RETURN_HR_IF(E_ABORT, !base::MakeCheckedNum(in.length()).AssignIfValid(&lengthRequired));
const auto lengthRequired = gsl::narrow_cast<int>(in.length());
out.resize(in.length()); // avoid to call MultiByteToWideChar twice only to get the required size
const int lengthOut = MultiByteToWideChar(CP_UTF8, 0ul, in.data(), lengthRequired, out.data(), lengthRequired);
out.resize(gsl::narrow_cast<size_t>(lengthOut));
@@ -100,9 +104,13 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
out.clear();
RETURN_HR_IF(S_OK, in.empty());
int capa16{};
if (in.length() > size_t(INT_MAX))
{
return E_ABORT;
}
// The worst ratio of UTF-8 code units to UTF-16 code units is 1 to 1 if UTF-8 consists of ASCII only.
RETURN_HR_IF(E_ABORT, !base::CheckAdd(in.length(), state.have).AssignIfValid(&capa16));
auto capa16 = gsl::narrow_cast<int>(in.length());
out.resize(gsl::narrow_cast<size_t>(capa16));
auto len8{ gsl::narrow_cast<int>(in.length()) };
@@ -190,12 +198,17 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
out.clear();
RETURN_HR_IF(S_OK, in.empty());
int lengthIn{};
int lengthRequired{};
if (in.length() > size_t(INT_MAX / 3))
{
return E_ABORT;
}
// Code Point U+0000..U+FFFF: 1 UTF-16 code unit --> 1..3 UTF-8 code units.
// Code Points >U+FFFF: 2 UTF-16 code units --> 4 UTF-8 code units.
// Thus, the worst ratio of UTF-16 code units to UTF-8 code units is 1 to 3.
RETURN_HR_IF(E_ABORT, !base::MakeCheckedNum(in.length()).AssignIfValid(&lengthIn) || !base::CheckMul(lengthIn, 3).AssignIfValid(&lengthRequired));
const auto lengthIn = gsl::narrow_cast<int>(in.length());
const auto lengthRequired = lengthIn * 3;
out.resize(gsl::narrow_cast<size_t>(lengthRequired)); // avoid to call WideCharToMultiByte twice only to get the required size
const int lengthOut = WideCharToMultiByte(CP_UTF8, 0ul, in.data(), lengthIn, out.data(), lengthRequired, nullptr, nullptr);
out.resize(gsl::narrow_cast<size_t>(lengthOut));
@@ -227,10 +240,14 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
out.clear();
RETURN_HR_IF(S_OK, in.empty());
int len16{};
int capa8{};
if (in.length() > size_t(INT_MAX / 3 - 1))
{
return E_ABORT;
}
auto len16 = gsl::narrow_cast<int>(in.length());
// The worst ratio of UTF-16 code units to UTF-8 code units is 1 to 3.
RETURN_HR_IF(E_ABORT, !base::MakeCheckedNum(in.length()).AssignIfValid(&len16) || !base::CheckAdd(len16, gsl::narrow_cast<int>(state.partials[0]) != 0).AssignIfValid(&capa8) || !base::CheckMul(capa8, 3).AssignIfValid(&capa8));
auto capa8 = len16 * 3 + 1;
out.resize(gsl::narrow_cast<size_t>(capa8));
int len8{};

View File

@@ -497,9 +497,12 @@ void InteractivityFactory::_WritePseudoWindowCallback(bool showOrHide)
// this message, if it's already minimized. If the window is maximized a
// restore will restore-down the window instead.
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (const auto io = gci.GetVtIo())
if (auto writer = gci.GetVtWriter())
{
io->SetWindowVisibility(showOrHide);
char buf[] = "\x1b[1t";
buf[2] = showOrHide ? '1' : '2';
writer.WriteUTF8(buf);
writer.Submit();
}
}

View File

@@ -67,15 +67,6 @@ void ServiceLocator::RundownAndExit(const HRESULT hr)
Sleep(INFINITE);
}
// MSFT:15506250
// In VT I/O Mode, a client application might die before we've rendered
// the last bit of text they've emitted. So give the VtRenderer one
// last chance to paint before it is killed.
if (s_globals.pRender)
{
s_globals.pRender->TriggerTeardown();
}
// MSFT:40226902 - HOTFIX shutdown on OneCore, by leaking the renderer, thereby
// reducing the change for existing race conditions to turn into deadlocks.
#ifndef NDEBUG

View File

@@ -63,12 +63,6 @@ BgfxEngine::BgfxEngine(PVOID SharedViewBase, LONG DisplayHeight, LONG DisplayWid
return S_OK;
}
[[nodiscard]] HRESULT BgfxEngine::PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept
{
*pForcePaint = false;
return S_FALSE;
}
[[nodiscard]] HRESULT BgfxEngine::StartPaint() noexcept
{
return S_OK;

View File

@@ -38,7 +38,6 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<til::rect>& rectangles) noexcept override;
[[nodiscard]] HRESULT InvalidateScroll(const til::point* pcoordDelta) noexcept override;
[[nodiscard]] HRESULT InvalidateAll() noexcept override;
[[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept override;
[[nodiscard]] HRESULT StartPaint() noexcept override;
[[nodiscard]] HRESULT EndPaint() noexcept override;

View File

@@ -26,9 +26,6 @@
<ProjectReference Include="..\..\..\renderer\atlas\atlas.vcxproj">
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\renderer\vt\lib\vt.vcxproj">
<Project>{990f2657-8580-4828-943f-5dd657d11842}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\types\lib\types.vcxproj">
<Project>{18d09a24-8240-42d6-8cb6-236eee820263}</Project>
</ProjectReference>

View File

@@ -935,7 +935,11 @@ DWORD WINAPI ConsoleInputThreadProcWin32(LPVOID /*lpParameter*/)
// VtIo's CreatePseudoWindow, which will make sure that the window is
// successfully created with the owner configured when the window is
// first created. See GH#13066 for details.
ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo()->CreatePseudoWindow();
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (gci.IsConPTY())
{
gci.GetVtIoNoCheck()->CreatePseudoWindow();
}
// Register the pseudoconsole window as being owned by the root process.
const auto pseudoWindow = ServiceLocator::LocatePseudoWindow();

Some files were not shown because too many files have changed in this diff Show More