mirror of
https://github.com/microsoft/terminal.git
synced 2026-05-19 03:10:49 +00:00
Compare commits
64 Commits
v1.24.1132
...
dev/lhecke
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2c41bf82d | ||
|
|
784f3f8780 | ||
|
|
3eb298dd5a | ||
|
|
21b13ca461 | ||
|
|
81f3778bad | ||
|
|
a7158fb26a | ||
|
|
e37b10fc89 | ||
|
|
55f5f34f67 | ||
|
|
f6f81e3da8 | ||
|
|
04aa614d66 | ||
|
|
44a538d7e5 | ||
|
|
34857bc3e7 | ||
|
|
119026afaf | ||
|
|
316b91a2ff | ||
|
|
730d6873a3 | ||
|
|
d67b70da8d | ||
|
|
245279d74a | ||
|
|
c3f48da21d | ||
|
|
e1eb9db115 | ||
|
|
2c1d7fcd28 | ||
|
|
93930bb3fa | ||
|
|
495c3e5295 | ||
|
|
cc31a59669 | ||
|
|
d3ee58b9fc | ||
|
|
882f3faf0d | ||
|
|
512468d47f | ||
|
|
8b59551151 | ||
|
|
d65bb806cd | ||
|
|
f0db29ddd1 | ||
|
|
f35127abbe | ||
|
|
7b5b3657f2 | ||
|
|
669a530204 | ||
|
|
c8d0a934fb | ||
|
|
edd4c758d7 | ||
|
|
e9e6227a4e | ||
|
|
9535e49518 | ||
|
|
9c42bfcb8d | ||
|
|
29766619f0 | ||
|
|
92087fbd23 | ||
|
|
c92c5cf716 | ||
|
|
eaf364ec37 | ||
|
|
976343e1ab | ||
|
|
d3b4592b71 | ||
|
|
6b254492be | ||
|
|
b78e5bc044 | ||
|
|
1047ed4f1a | ||
|
|
a138b284a4 | ||
|
|
a3f5d59ebe | ||
|
|
6a13e5aabc | ||
|
|
0e51acd5d9 | ||
|
|
c3d5ce14dd | ||
|
|
3e346f1174 | ||
|
|
7c6c1824eb | ||
|
|
7462a20097 | ||
|
|
78ae6dda80 | ||
|
|
8de1a7543f | ||
|
|
9e3d3983d5 | ||
|
|
db2b762221 | ||
|
|
eec17eb805 | ||
|
|
89b4c09e50 | ||
|
|
73e3dd31b5 | ||
|
|
656a0492d7 | ||
|
|
17d39eb3eb | ||
|
|
fec64563a0 |
3
.github/actions/spelling/allow/apis.txt
vendored
3
.github/actions/spelling/allow/apis.txt
vendored
@@ -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
|
||||
|
||||
7
.github/actions/spelling/expect/alphabet.txt
vendored
7
.github/actions/spelling/expect/alphabet.txt
vendored
@@ -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
|
||||
|
||||
25
.github/actions/spelling/expect/expect.txt
vendored
25
.github/actions/spelling/expect/expect.txt
vendored
@@ -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
101
NOTICE.md
@@ -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)
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
}
|
||||
@@ -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",
|
||||
]
|
||||
@@ -1,5 +0,0 @@
|
||||
jschuh@chromium.org
|
||||
tsepez@chromium.org
|
||||
|
||||
|
||||
# COMPONENT: Internals
|
||||
@@ -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.
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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>
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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; }
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
namespace Microsoft::Console::Render
|
||||
{
|
||||
class Renderer;
|
||||
class VtEngine;
|
||||
}
|
||||
|
||||
class InputBuffer final : public ConsoleObjectHeader
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
@@ -31,8 +31,6 @@ SOURCES = \
|
||||
TitleTests.cpp \
|
||||
InputBufferTests.cpp \
|
||||
VtIoTests.cpp \
|
||||
VtRendererTests.cpp \
|
||||
ConptyOutputTests.cpp \
|
||||
ViewportTests.cpp \
|
||||
ConsoleArgumentsTests.cpp \
|
||||
ObjectTests.cpp \
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user