Repository: nvaccess/nvda

Issue:
Crash of the latest alpha NVDA builds on a system with Russian locale
### Steps to reproduce:
Download and run the installation package of the latest NVDA alpha builds.
### Actual behavior:
The sound of the temporary copy of NVDA starting is played, after which it crash
### Expected behavior:
The temporary copy of NVDA should start correctly when the launcher starts
### System configuration
#### NVDA installed/portable/running from source:
Occurs when the installation package is started
#### NVDA version:
alpha-21882,528d5708 and higher
#### Windows version:
Windows 10 version 1809
#### Other information about your system:
The problem is reproduced if the Russian language of the interface is selected in the system parameters. If change the language to English and log in again, the problem disappears and NVDA start correctly.
NVDA log is interrupted on the wxPython version, probably the crash occurs when creating an instance of wx.App.
### Other questions
#### Does the issue still occur after restarting your computer?
Yes
#### Have you tried any other versions of NVDA? If so, please report their behaviors.
Problem appeared in new NVDA builds with Python 3.8 and WXPython 4.1.1
#### If add-ons are disabled, is your problem still occurring?
Yes
[nvda-old.log](https://github.com/nvaccess/nvda/files/6134690/nvda-old.log)



Discussion:
The same problem occurs when running NVDA from source code with Python 3.8.8.
[nvda.log](https://github.com/nvaccess/nvda/files/6139295/nvda.log)

Hi, likely caused by C locale problem, hopefully should be fixed with wxPython 4.1.2 (under development; I and several wxPython users and Robin had a conversation about this issue a while back). Thanks.


How soon is wxPython expected to update to version 4.1.2? Due to this issue, I can't test my addons for compatibility with the new NVDA versions.
Hi, maybe a few weeks to months, as it depends on what Robin says. Thanks.


@josephsl Is the problem with the C Locale that you've mentioned fixed in the development version of WX Python already?
I can't reproduce this on `alpha-21882,528d5708` nor using the latest alpha `alpha-22086,0316158b`. I'm running Windows 10 and using the main Russian locale for my windows VM. Are you using a different Russian dialect?





I use the ru_RU locale. Here is my research in the Python 3.8.8 console:
```>>> import wx, locale
>>> locale.getdefaultlocale()
('ru_RU', 'cp1251')
>>> locale.getlocale()
('Russian_Russia', '1251')
>>> app = wx.App()
>>> locale.getlocale()
('ru_RU', 'UTF-8')```
Creating an instance of wx.App for some reason changes the encoding of the current locale, which probably later causes a failure when trying to write to the NVDA log. NVDA crashes with the exit code 0xc0000374.
I commented out call the locale.setlocale function in the InitLocale method of wx.App, after which NVDA from the source code now starts without errors.
Thanks @kvark128 

Sounds like #12214 should fix this 
wxPython version 4.0.3 does not change the current locale, but I can do it manually in the Python console of NVDA 2020.4 and the same crash will occur:
```
>>> import locale
>>> locale.setlocale(locale.LC_ALL, "ru_RU")
'ru_RU'
>>> log.info("test")
```
When trying to write to the log, NVDA immediately crashes.
It looks like the cause of the problem is UCRT, as described in this issue:
https://bugs.python.org/issue36792
These commands in the Python console of NVDA 2020.4 I have also causes heap corruption and crash:
import locale, time
locale.setlocale(locale.LC_ALL, 'ru_RU')
time.localtime(time.time())

Hi @kvark128, I've tried to build a workaround for this which reverts to previous behaviour around setting the python locale to match the Windows one rather than the translation service - do you mind testing this build using the "User Default" system language like before? https://ci.appveyor.com/api/buildjobs/ymaxrabvfmfmxk1m/artifacts/output%2Fnvda_snapshot_try-fix-ru-locale-22193%2C2e816544.exe

Sorry for all the difficulties - it's just strangely impossible to reproduce this on my Russian Windows VM. 
Unfortunately, this build also crashes.
[nvda-old.log](https://github.com/nvaccess/nvda/files/6229886/nvda-old.log)

As a workaround, this problem is solved by the following code, which must be inserted before initializing the NVDA log:
```
try:
	lang, enc = locale.getdefaultlocale()
	locale.setlocale(locale.LC_ALL, lang)
except:
	pass
```
It is important that this code is executed before attempts to write to the log, otherwise the crash will occur again. It looks like errors when working with memory somewhere inside UCRT.

Resolving patch:
diff --git a/source/core.py b/source/core.py
--- a/source/core.py
+++ b/source/core.py
@@ -317,6 +317,14 @@ class App(wx.App):
 		def OnAssert(self,file,line,cond,msg):
 			message="{file}, line {line}:\nassert {cond}: {msg}".format(file=file,line=line,cond=cond,msg=msg)
 			log.debugWarning(message,codepath="WX Widgets",stack_info=True)
+
+		def InitLocale(self):
+			# Backport of `InitLocale` from wx Python 4.1.2 as the current version tries to set a Python
+			# locale to an nonexistent one when creating an instance of `wx.App`.
+			# This causes a crash when running under a particular version of Universal CRT (#12160)
+			import locale
+			locale.setlocale(locale.LC_ALL, "C")
+
 	app = App(redirect=False)
 	# We support queryEndSession events, but in general don't do anything for them.
 	# However, when running as a Windows Store application, we do want to request to be restarted for updates
diff --git a/source/logHandler.py b/source/logHandler.py
--- a/source/logHandler.py
+++ b/source/logHandler.py
@@ -10,13 +10,13 @@
 import ctypes
 import sys
 import warnings
-from encodings import utf_8
 import logging
 import inspect
 import winsound
 import traceback
-from types import MethodType, FunctionType
+from types import FunctionType
 import globalVars
+import winKernel
 import buildVersion
 from typing import Optional
 
@@ -288,6 +288,19 @@ class Formatter(logging.Formatter):
 	def formatException(self, ex):
 		return stripBasePathFromTracebackText(super(Formatter, self).formatException(ex))
 
+	def formatTime(self, record: logging.LogRecord, datefmt: Optional[str] = None) -> str:
+		"""Custom implementation of `formatTime` which avoids `time.localtime`
+		since it causes a crash under some versions of Universal CRT ( #12160, Python issue 36792)
+		"""
+		timeAsFileTime = winKernel.time_tToFileTime(record.created)
+		timeAsSystemTime = winKernel.SYSTEMTIME()
+		winKernel.FileTimeToSystemTime(timeAsFileTime, timeAsSystemTime)
+		timeAsLocalTime = winKernel.SYSTEMTIME()
+		winKernel.SystemTimeToTzSpecificLocalTime(None, timeAsSystemTime, timeAsLocalTime)
+		res = f"{timeAsLocalTime.wHour:02d}:{timeAsLocalTime.wMinute:02d}:{timeAsLocalTime.wSecond:02d}"
+		return self.default_msec_format % (res, record.msecs)
+
+
 class StreamRedirector(object):
 	"""Redirects an output stream to a logger.
 	"""
diff --git a/source/winKernel.py b/source/winKernel.py
--- a/source/winKernel.py
+++ b/source/winKernel.py
@@ -1,17 +1,17 @@
-#winKernel.py
-#A part of NonVisual Desktop Access (NVDA)
-#Copyright (C) 2006-2019 NV Access Limited, Rui Batista, Aleksey Sadovoy, Peter Vagner, Mozilla Corporation, Babbage B.V., Joseph Lee
-#This file is covered by the GNU General Public License.
-#See the file COPYING for more details.
+# A part of NonVisual Desktop Access (NVDA)
+# Copyright (C) 2006-2021 NV Access Limited, Rui Batista, Aleksey Sadovoy, Peter Vagner,
+# Mozilla Corporation, Babbage B.V., Joseph Lee, Łukasz Golonka
+# This file is covered by the GNU General Public License.
+# See the file COPYING for more details.
 
 """Functions that wrap Windows API functions from kernel32.dll and advapi32.dll"""
 
+from typing import Union
 import contextlib
 import ctypes
 import ctypes.wintypes
-from ctypes import WinError
-from ctypes import *
-from ctypes.wintypes import *
+from ctypes import byref, c_byte, POINTER, sizeof, Structure, windll, WinError
+from ctypes.wintypes import BOOL, DWORD, HANDLE, LARGE_INTEGER, LPWSTR, LPVOID, WORD
 
 kernel32=ctypes.windll.kernel32
 advapi32 = windll.advapi32
@@ -160,6 +160,63 @@ class SYSTEMTIME(ctypes.Structure):
 		("wMilliseconds", WORD)
 	)
 
+
+class FILETIME(Structure):
+	_fields_ = (
+		("dwLowDateTime", DWORD),
+		("dwHighDateTime", DWORD)
+	)
+
+
+class TIME_ZONE_INFORMATION(Structure):
+	_fields_ = (
+		("Bias", ctypes.wintypes.LONG),
+		("StandardName", ctypes.wintypes.WCHAR * 32),
+		("StandardDate", SYSTEMTIME),
+		("StandardBias", ctypes.wintypes.LONG),
+		("DaylightName", ctypes.wintypes.WCHAR * 32),
+		("DaylightDate", SYSTEMTIME),
+		("DaylightBias", ctypes.wintypes.LONG)
+	)
+
+
+def time_tToFileTime(time_tToConvert: float) -> FILETIME:
+	"""Converts time_t as returned from `time.time` to a FILETIME structure.
+	Based on a code snipped from:
+	https://docs.microsoft.com/en-us/windows/win32/sysinfo/converting-a-time-t-value-to-a-file-time
+	"""
+	timeAsFileTime = FILETIME()
+	res = (int(time_tToConvert) * 10000000) + 116444736000000000
+	timeAsFileTime.dwLowDateTime = res
+	timeAsFileTime.dwHighDateTime = res >> 32
+	return timeAsFileTime
+
+
+def FileTimeToSystemTime(lpFileTime: FILETIME, lpSystemTime: SYSTEMTIME) -> None:
+	if kernel32.FileTimeToSystemTime(byref(lpFileTime), byref(lpSystemTime)) == 0:
+		raise WinError()
+
+
+def SystemTimeToTzSpecificLocalTime(
+		lpTimeZoneInformation: Union[TIME_ZONE_INFORMATION, None],
+		lpUniversalTime: SYSTEMTIME,
+		lpLocalTime: SYSTEMTIME
+) -> None:
+	"""Wrapper for `SystemTimeToTzSpecificLocalTime` from kernel32.
+	:param lpTimeZoneInformation: Either TIME_ZONE_INFORMATION containing info about the desired time zone
+	or `None` when the current time zone as configured in Windows settings should be used.
+	:param lpUniversalTime: SYSTEMTIME structure containing time in UTC wwhich you wish to convert.
+	: param lpLocalTime: A SYSTEMTIME structure in which time converted to the desired time zone would be placed.
+	:raises WinError
+	"""
+	if lpTimeZoneInformation is not None:
+		lpTimeZoneInformation = byref(lpTimeZoneInformation)
+	if kernel32.SystemTimeToTzSpecificLocalTime(
+		lpTimeZoneInformation, byref(lpUniversalTime), byref(lpLocalTime)
+	) == 0:
+		raise WinError()
+
+
 def GetDateFormatEx(Locale,dwFlags,date,lpFormat):
 	if date is not None:
 		date=SYSTEMTIME(date.year,date.month,0,date.day,date.hour,date.minute,date.second,0)
@@ -182,8 +239,6 @@ def GetTimeFormatEx(Locale,dwFlags,date,lpFormat):
 	kernel32.GetTimeFormatEx(Locale,dwFlags,lpTime,lpFormat, buf, bufferLength)
 	return buf.value
 
-def openProcess(*args):
-	return kernel32.OpenProcess(*args)
 
 def virtualAllocEx(*args):
 	res = kernel32.VirtualAllocEx(*args)


Test patch: