Skip to content

Commit e46ead9

Browse files
committed
More clear code to read from unicode console in Windows.
Avoid big stack/static buffers.
1 parent 81f0917 commit e46ead9

File tree

1 file changed

+43
-98
lines changed

1 file changed

+43
-98
lines changed

src/isql/isql.epp

Lines changed: 43 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -304,16 +304,15 @@ static inline int fb_isdigit(const char c)
304304
return isdigit((int)(UCHAR)c);
305305
}
306306

307+
typedef Array<char> CharBuffer;
308+
GlobalPtr<CharBuffer> charBuffer;
307309

308310
#ifdef WIN_NT
309-
// This function is highly based on code written by https://github.com/xenu
310-
// He permitted our usage here: https://github.com/Perl/perl5/pull/18702#issuecomment-1156050577
311-
static int win32ReadConsole(FILE* file, char* buffer, size_t bufferSize)
311+
312+
GlobalPtr<Array<WCHAR>> wideBuffer;
313+
314+
static int win32ReadConsole(FILE* file, CharBuffer& mbBuffer)
312315
{
313-
// This function is a workaround for a bug in Windows:
314-
// https://github.com/microsoft/terminal/issues/4551
315-
// tl;dr: ReadFile() and ReadConsoleA() return garbage when reading
316-
// non-ASCII characters from the console with the 65001 codepage.
317316
auto handle = (HANDLE) _get_osfhandle(fileno(file));
318317

319318
if (handle == INVALID_HANDLE_VALUE)
@@ -322,107 +321,49 @@ static int win32ReadConsole(FILE* file, char* buffer, size_t bufferSize)
322321
return -1;
323322
}
324323

325-
DWORD mode;
326-
if (!GetConsoleMode(handle, &mode))
324+
DWORD oldMode, newMode;
325+
if (GetConsoleMode(handle, &oldMode))
326+
{
327+
newMode = oldMode | (ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);
328+
if (newMode != oldMode)
329+
SetConsoleMode(handle, oldMode);
330+
}
331+
else
327332
{
328333
fb_assert(false);
329-
return -1;
334+
newMode = oldMode = 0;
330335
}
331336

332-
const size_t MAX_LINE_LENGTH = 32765;
333-
static WCHAR wideBuf[MAX_LINE_LENGTH +1];
337+
Cleanup consoleMode([&] {
338+
if (oldMode != newMode)
339+
SetConsoleMode(handle, oldMode);
340+
});
334341

335-
WCHAR* pWide;
336-
DWORD charsRead = 0;
337-
size_t leftToRead = bufferSize;
342+
const size_t MAX_LINE_LENGTH = MAX_USHORT;
343+
WCHAR* wideBuf = wideBuffer->getBuffer(MAX_LINE_LENGTH, false);
338344

339-
while (leftToRead)
345+
DWORD charsRead;
346+
if (!ReadConsoleW(handle, wideBuf, MAX_LINE_LENGTH, &charsRead, NULL))
340347
{
341-
// The purpose of convertedBuf is to preserve partial UTF-8 (or of any
342-
// other multibyte encoding) code points between read() calls. Since
343-
// there's only one console, the buffer is global. It's needed because
344-
// ReadConsoleW() returns a string of UTF-16 code units and its result,
345-
// after conversion to the current console codepage, may not fit in the
346-
// return buffer.
347-
//
348-
// The buffer's size is 8 because it will contain at most two UTF-8 code
349-
// points.
350-
static char convertedBuf[8];
351-
static size_t convertedBufLen = 0;
352-
353-
if (convertedBufLen)
354-
{
355-
bool newline = false;
356-
const size_t toWrite = MIN(convertedBufLen, leftToRead);
357-
358-
// Don't read anything if the *first* character is ^Z and
359-
// ENABLE_PROCESSED_INPUT is enabled. On some versions of Windows,
360-
// ReadFile() ignores ENABLE_PROCESSED_INPUT, but apparently it's a
361-
// bug: https://github.com/microsoft/terminal/issues/4958
362-
if (leftToRead == bufferSize && (mode & ENABLE_PROCESSED_INPUT) && convertedBuf[0] == 0x1A)
363-
break;
364-
365-
// Are we returning a newline?
366-
if (memchr(convertedBuf, '\n', toWrite) != 0)
367-
newline = true;
368-
369-
memcpy(buffer, convertedBuf, toWrite);
370-
buffer += toWrite;
371-
372-
// If there's anything left in convertedBuf, move it to the beginning of the buffer.
373-
convertedBufLen -= toWrite;
374-
375-
if (convertedBufLen)
376-
memmove(convertedBuf, convertedBuf + toWrite, convertedBufLen);
377-
378-
leftToRead -= toWrite;
379-
380-
// With ENABLE_LINE_INPUT enabled, we stop reading after the first
381-
// newline, otherwise we stop reading after the first character.
382-
if (!leftToRead || newline || (mode & ENABLE_LINE_INPUT) == 0)
383-
break;
384-
}
385-
386-
if (!charsRead)
387-
{
388-
if (!ReadConsoleW(handle, wideBuf, MAX_LINE_LENGTH, &charsRead, NULL))
389-
return -1;
390-
391-
if (!charsRead)
392-
break;
393-
394-
pWide = wideBuf;
395-
}
396-
397-
DWORD wideBufLen = 1;
398-
charsRead--;
399-
400-
if (IS_HIGH_SURROGATE(*pWide))
401-
{
402-
// High surrogate, read one more code unit.
403-
if (charsRead)
404-
charsRead--;
405-
else
406-
{
407-
DWORD read2;
408-
if (!ReadConsoleW(handle, pWide + 1, 1, &read2, NULL))
409-
return -1;
410-
}
348+
fb_assert(false);
349+
return -1;
350+
}
411351

412-
++wideBufLen;
413-
}
352+
if (!charsRead)
353+
return 0;
414354

415-
convertedBufLen = WideCharToMultiByte(GetConsoleCP(), 0, pWide, wideBufLen,
416-
convertedBuf, sizeof(convertedBuf), NULL, NULL);
355+
int mbLength = WideCharToMultiByte(GetConsoleCP(), 0, wideBuf, charsRead,
356+
NULL, 0, NULL, NULL);
417357

418-
if (!convertedBufLen)
419-
return -1;
358+
if (!mbLength)
359+
return -1;
420360

421-
pWide += wideBufLen;
422-
}
361+
mbLength = WideCharToMultiByte(GetConsoleCP(), 0, wideBuf, charsRead,
362+
mbBuffer.getBuffer(mbLength, false), mbLength, NULL, NULL);
423363

424-
return bufferSize - leftToRead;
364+
return mbLength;
425365
}
366+
426367
#endif
427368

428369

@@ -1108,16 +1049,20 @@ static void readNextInputLine(const char* prompt)
11081049
do
11091050
{
11101051
// Read the line
1111-
char buffer[MAX_USHORT];
1052+
char* buffer;
11121053
int lineSize;
11131054

11141055
#ifdef WIN_NT
11151056
if (!Input_file && isatty(fileno(Filelist->Ifp().indev_fpointer)))
1116-
lineSize = win32ReadConsole(Filelist->Ifp().indev_fpointer, buffer, sizeof(buffer));
1057+
{
1058+
lineSize = win32ReadConsole(Filelist->Ifp().indev_fpointer, charBuffer);
1059+
buffer = charBuffer->begin();
1060+
}
11171061
else
11181062
#endif
11191063
{
1120-
if (fgets(buffer, sizeof(buffer), Filelist->Ifp().indev_fpointer) != NULL)
1064+
buffer = charBuffer->getBuffer(MAX_USHORT);
1065+
if (fgets(buffer, charBuffer->getCapacity(), Filelist->Ifp().indev_fpointer) != NULL)
11211066
lineSize = strlen(buffer);
11221067
else
11231068
lineSize = -1;

0 commit comments

Comments
 (0)