Compare commits

...

49 Commits

Author SHA1 Message Date
208a8c1f84 Add emscripten logvisorAbort 2022-08-03 18:11:46 -04:00
6926b6a7c9 Always flush log file on close to prevent incomplete logs 2022-01-10 23:27:06 -08:00
9420e3007e Update fmt to 8.0.1 2021-10-25 19:08:48 -04:00
4f7f3f2147 Use __has_cpp_attribute for NOWIDE_FALLTHROUGH 2021-06-30 16:22:27 -04:00
6eadd55f7a Merge remote-tracking branch 'origin/utf8' into utf8
# Conflicts:
#	lib/logvisor.cpp
2021-06-29 00:09:20 -04:00
df3c283186 Update fmt to 8.0.0; drop support for wchar
Adds some boost-nowide headers to assist in
UTF-8/16 conversion for Windows API.
2021-06-25 15:04:38 -04:00
139281f667 Update fmt to 8.0.0; drop support for wchar
Adds some boost-nowide headers to assist in
UTF-8/16 conversion for Windows API.
2021-06-22 15:13:37 -04:00
274ad5ef07 Fix compiling on linux 2021-06-19 14:43:23 -07:00
Henrique Gemignani Passos Lima
ed3714e55c Fix ConsoleLogger misusing Widen result lifetime 2021-06-19 13:40:17 +03:00
ee3276d7c5 Merge pull request #7 from Minty-Meeo/wide-printing-fixes 2021-05-07 23:17:57 -07:00
Minty-Meeo
f23179e168 Wide char homogeneity
If wide char stdlib functions are used on a narrow char stream, they attempt to narrow the character or string.  If this fails, nothing is written to the stream and an error is indicated.  The {fmt} library, at its core, uses stdlib functions for printing to a stream.  However, it takes it a step further and acts upon errors while printing by throwing an exception.

If narrow char stdlib functions are used on a wide char stream, they throw an assertion.  The {fmt} library somehow does not throw an assertion, but whatever it ends up printing is garbage anyhow.

So from either end, it is generally a bad idea to mix narrow and wide character printing.  While you can get away with using wide char functions on a narrow char stream, the moment you start using anything other than English characters, it all falls apart.  Just as well, narrow chars on wide char streams do not work at all.  Despite this, I found many situations where wide char printing and narrow char printing was being used at the same time.  This PR makes character printing homogeneous to avoid the previously discussed issues.
2021-05-08 00:52:03 -05:00
7901fc496d Don't install if not building directly 2021-04-13 00:48:30 -04:00
d874350205 Fetch Sentry as needed; don't reimport fmt unnecessarily 2021-04-13 00:05:09 -04:00
028617d225 Update sentry-native 2021-04-12 15:52:58 -04:00
984d87348e Set FMT_ARM_ABI_COMPATIBILITY; move fmt defines to CMake 2021-04-06 17:55:15 -04:00
33e7322595 Update submodules; set submodule tracking branch 2021-04-06 12:46:56 -04:00
bb113e03f8 Fix sentry submodule URL 2021-04-05 16:31:04 -04:00
240491ccad Compile breakpad_client with -Wno-array-bounds 2021-04-05 13:26:01 -04:00
fd343eb234 Fix compiling crashpad on Windows 2021-04-05 12:04:19 -04:00
aac06c3465 Fix compiling breakpad_client on Linux 2021-04-05 10:38:42 -04:00
0e3fe3e766 Integrate sentry-native crash reporting 2021-04-04 18:20:48 -04:00
17aa25cfa9 Merge pull request #6 from henriquegemignani/feature/keep-file-open
FileLogger: don't open/close file for each write
2021-04-03 10:27:47 -07:00
Henrique Gemignani Passos Lima
d4af1a1088 FileLogger: don't open/close file for each write 2021-04-03 19:42:44 +03:00
8913e55e34 Fix crashing on windows when registering a file logger after a console logger, windows runtime bugs annoy the hell out of me >:( 2020-12-31 13:53:26 -08:00
81fb4e4c2d Link debug libnx & remove outdated nxstl includes 2020-10-21 00:35:07 -04:00
Jack Andersen
7f63cabaea Fixes for recent windows SDK changes 2020-08-22 17:28:34 -10:00
41432143fd Only use lm service when LOGVISOR_NX_LM set 2020-05-05 00:14:41 -04:00
Jack Andersen
14ea54f8b5 Add lm service for switch build 2020-05-03 20:09:21 -10:00
Jack Andersen
d62b4ce26e Update fmtlib 2020-04-11 12:44:21 -10:00
Jack Andersen
27814c5276 Exclude CMake CXX standard from MSVC 2020-04-10 18:55:45 -10:00
Jack Andersen
f04a9777eb Merge branch 'master' of ssh://git.axiodl.com:6431/AxioDL/logvisor 2019-09-30 21:21:21 -10:00
Jack Andersen
187c35f3c4 Ensure only one console logger is able to be registered 2019-09-30 21:20:45 -10:00
8c2e711362 Merge pull request #5 from lioncash/noreturn
logvisor: Mark logvisorAbort as [[noreturn]]
2019-09-06 23:26:44 -07:00
Lioncash
63cb911d09 logvisor: Mark logvisorAbort as [[noreturn]]
All variants of logvisorAbort do what they say in their name -- abort.
Given control isn't returned from this function, we can signify that
it's a noreturn function
2019-09-06 07:37:27 -04:00
f623ace3b4 Merge pull request #4 from lioncash/docs
logvisor: Amend documentation comments
2019-08-26 15:55:18 -07:00
8ea97c524c Merge pull request #3 from lioncash/forward
logvisor: std::forward arguments where applicable
2019-08-26 15:55:08 -07:00
81c72ad220 Merge pull request #2 from lioncash/char
logvisor: Use std::fputs/std::fputc where applicable
2019-08-26 15:54:58 -07:00
Lioncash
59f651f24b logvisor: Amend documentation comments
These use fmt-style formatting specifiers, not printf.
2019-08-26 10:57:19 -04:00
Lioncash
2a40c6dc92 logvisor: std::forward arguments where applicable
Forwards arguments into functions to preserve their value category
2019-08-26 10:50:35 -04:00
Lioncash
2f3f06e5ca logvisor: Add missing override specifiers 2019-08-26 10:34:40 -04:00
Lioncash
94d1c558a0 logvisor: Use std::fputs/std::fputc where applicable
We can just use std::fputc when inserting a single character instead of
using std::fputs. Likewise, we can use std::fputs over std::fprintf when
no formatting specifiers are present.
2019-08-26 10:31:45 -04:00
aa9aa0a82c Merge pull request #1 from lioncash/cexpr
logvisor: Make Module constructor constexpr
2019-08-17 22:56:59 -07:00
Lioncash
a57409828c logvisor: Make Module constructor constexpr
Allows the module instances to be constructed at compile time.
Otherwise, this is technically a runtime static constructor when an
instance is declared at file scope.
2019-08-17 19:47:20 -04:00
Jack Andersen
dcd0ffcaec Fix custom formatting of multiple character types 2019-07-25 16:31:54 -10:00
Jack Andersen
3bedd268e8 Integrate libfmt for format strings 2019-07-19 18:21:39 -10:00
Jack Andersen
a0ef17d895 Refactor of CMake for cleaner dependency handling 2019-06-11 15:53:17 -10:00
Jack Andersen
ebe7463e67 Ignore GCC truncation warning 2019-06-09 16:48:06 -10:00
Jack Andersen
7672485d81 Make logvisor publicly link libdl on platforms that have it 2019-05-25 00:14:41 -10:00
Jack Andersen
c54e2596c2 Add cmake package config files 2019-05-22 18:07:58 -10:00
18 changed files with 2021 additions and 868 deletions

4
.gitmodules vendored Normal file
View File

@@ -0,0 +1,4 @@
[submodule "fmt"]
path = fmt
url = https://github.com/fmtlib/fmt
branch = master

View File

@@ -1,8 +1,113 @@
include_directories(include)
cmake_minimum_required(VERSION 3.10 FATAL_ERROR) # because of c++17
project(nod VERSION 0.1)
if (NOT MSVC)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
endif ()
include (CMakePackageConfigHelpers)
if (NOT TARGET fmt)
add_subdirectory(fmt)
target_compile_definitions(fmt PUBLIC
FMT_ARM_ABI_COMPATIBILITY=1
FMT_EXCEPTIONS=0)
target_compile_definitions(fmt INTERFACE
FMT_ENFORCE_COMPILE_STRING=1
FMT_UNICODE=1)
set(FMT_LIB fmt)
endif ()
add_library(logvisor
lib/logvisor.cpp
include/logvisor/logvisor.hpp)
set(LOGVISOR_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include CACHE PATH "logvisor include path" FORCE)
if ("${SENTRY_DSN}" STREQUAL "")
message(STATUS "SENTRY_DSN not set, not enabling Sentry")
target_compile_definitions(logvisor PUBLIC SENTRY_ENABLED=0)
set(SENTRY_LIB "")
set(BREAKPAD_CLIENT "")
else ()
message(STATUS "Enabling Sentry integration")
include(FetchContent)
FetchContent_Declare(sentry
GIT_REPOSITORY https://github.com/getsentry/sentry-native.git
GIT_TAG 5fcb1dc4d1c8b85fc5ab225d29c7394b375fd1e9
)
FetchContent_MakeAvailable(sentry)
target_compile_definitions(logvisor PUBLIC SENTRY_ENABLED=1)
target_compile_definitions(logvisor PRIVATE SENTRY_DSN="${SENTRY_DSN}")
set(SENTRY_LIB sentry)
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(BREAKPAD_CLIENT breakpad_client)
target_compile_options(breakpad_client PRIVATE -Wno-implicit-fallthrough -Wno-array-bounds)
target_compile_options(sentry PRIVATE "-Wno-implicit-fallthrough")
set_property(TARGET breakpad_client PROPERTY CXX_STANDARD 17)
set_property(TARGET sentry PROPERTY CXX_STANDARD 17)
else ()
set(BREAKPAD_CLIENT "")
endif ()
if (MSVC)
target_compile_options(crashpad_client PRIVATE "/W0")
target_compile_options(crashpad_util PRIVATE "/W0")
target_compile_options(crashpad_snapshot PRIVATE "/W0")
target_compile_options(mini_chromium PRIVATE "/W0")
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
target_compile_options(crashpad_zlib PRIVATE "-mpclmul")
endif ()
endif ()
endif ()
target_link_libraries(logvisor PUBLIC fmt ${SENTRY_LIB})
if(NX)
target_link_libraries(logvisor PUBLIC debug nxd optimized nx)
else()
target_link_libraries(logvisor PUBLIC ${CMAKE_DL_LIBS})
endif()
target_include_directories(logvisor PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>)
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
install(DIRECTORY include/logvisor DESTINATION include)
if (FMT_LIB)
install(DIRECTORY fmt/include/fmt DESTINATION include)
endif ()
set(version_config_file "${PROJECT_BINARY_DIR}/logvisorConfigVersion.cmake")
set(config_file "${PROJECT_BINARY_DIR}/logvisorConfig.cmake")
set(config_install_dir "lib/cmake/logvisor")
# Associate target with export
install(
TARGETS logvisor ${FMT_LIB} ${SENTRY_LIB} ${BREAKPAD_CLIENT}
EXPORT logvisorTargets
ARCHIVE DESTINATION "lib"
INCLUDES DESTINATION include # This sets the INTERFACE_INCLUDE_DIRECTORIES property of the target.
)
# Install the target config files
install(
EXPORT logvisorTargets
NAMESPACE "logvisor::"
DESTINATION "${config_install_dir}"
)
# Generate version config file
write_basic_package_version_file(
"${version_config_file}"
COMPATIBILITY SameMajorVersion
)
# Generate config file
configure_package_config_file(
"Config.cmake.in"
"${config_file}"
INSTALL_DESTINATION "lib/cmake/logvisor"
)
# Install the config files
install(
FILES "${config_file}" "${version_config_file}"
DESTINATION ${config_install_dir}
)
endif()

4
Config.cmake.in Normal file
View File

@@ -0,0 +1,4 @@
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/logvisorTargets.cmake")
check_required_components(logvisor)

1
fmt Submodule

Submodule fmt added at d141cdbeb0

View File

@@ -1,6 +1,5 @@
#pragma once
#include <cstdarg>
#include <cstring>
#include <cstdio>
#include <cstdlib>
@@ -9,19 +8,18 @@
#include <memory>
#include <mutex>
#ifdef __SWITCH__
#include "nxstl/mutex"
#include <fmt/format.h>
#if defined(__SWITCH__) && !defined(LOGVISOR_NX_LM)
#define LOGVISOR_NX_LM 0
#endif
extern "C" void logvisorBp();
#define log_typeid(type) std::hash<std::string>()(#type)
namespace logvisor {
void logvisorAbort();
#if _WIN32 && UNICODE
#define LOG_UCS2 1
#endif
[[noreturn]] void logvisorAbort();
/* True if ANSI color available */
extern bool XtermColor;
@@ -40,13 +38,16 @@ enum Level {
* @brief Backend interface for receiving app-wide log events
*/
struct ILogger {
virtual ~ILogger() {}
virtual void report(const char* modName, Level severity, const char* format, va_list ap) = 0;
virtual void report(const char* modName, Level severity, const wchar_t* format, va_list ap) = 0;
virtual void reportSource(const char* modName, Level severity, const char* file, unsigned linenum, const char* format,
va_list ap) = 0;
private:
uint64_t m_typeHash;
public:
ILogger(uint64_t typeHash) : m_typeHash(typeHash) {}
virtual ~ILogger() = default;
virtual void report(const char* modName, Level severity, fmt::string_view format, fmt::format_args args) = 0;
virtual void reportSource(const char* modName, Level severity, const char* file, unsigned linenum,
const wchar_t* format, va_list ap) = 0;
fmt::string_view format, fmt::format_args args) = 0;
[[nodiscard]] uint64_t getTypeId() const { return m_typeHash; }
};
/**
@@ -98,7 +99,7 @@ struct LogMutex {
if (enabled)
return std::unique_lock<std::recursive_mutex>(mutex);
else
return std::unique_lock<std::recursive_mutex>();
return {};
}
};
extern LogMutex _LogMutex;
@@ -107,7 +108,7 @@ extern LogMutex _LogMutex;
* @brief Take a centralized lock for the logging output stream(s)
* @return RAII mutex lock
*/
static inline std::unique_lock<std::recursive_mutex> LockLog() { return _LogMutex.lock(); }
inline std::unique_lock<std::recursive_mutex> LockLog() { return _LogMutex.lock(); }
extern uint64_t _LogCounter;
@@ -115,12 +116,12 @@ extern uint64_t _LogCounter;
* @brief Get current count of logging events
* @return Log Count
*/
static inline uint64_t GetLogCounter() { return _LogCounter; }
inline uint64_t GetLogCounter() { return _LogCounter; }
/**
* @brief Restore centralized logger vector to default state (silent operation)
*/
static inline void UnregisterLoggers() { MainLoggers.clear(); }
inline void UnregisterLoggers() { MainLoggers.clear(); }
/**
* @brief Construct and register a real-time console logger singleton
@@ -143,6 +144,16 @@ void RegisterFileLogger(const char* filepath);
*/
void RegisterStandardExceptions();
#if SENTRY_ENABLED
/**
* @brief Register Sentry crash reporting & logging.
* @param appName The application name
* @param appVersion The application version
* @param cacheDir Directory for Sentry cache files
*/
void RegisterSentry(const char* appName, const char* appVersion, const char* cacheDir);
#endif
#if _WIN32
/**
* @brief Spawn an application-owned cmd.exe window for displaying console output
@@ -150,54 +161,21 @@ void RegisterStandardExceptions();
void CreateWin32Console();
#endif
#if LOG_UCS2
/**
* @brief Construct and register a file logger (wchar_t version)
* @param filepath Path to write the file
*
* If there's already a file logger registered to the same file, this is a no-op.
*/
void RegisterFileLogger(const wchar_t* filepath);
#endif
/**
* @brief This is constructed per-subsystem in a locally centralized fashon
* @brief This is constructed per-subsystem in a locally centralized fashion
*/
class Module {
const char* m_modName;
public:
Module(const char* modName) : m_modName(modName) {}
/**
* @brief Route new log message to centralized ILogger
* @param severity Level of log report severity
* @param format Standard printf-style format string
*/
template <typename CharType>
inline void report(Level severity, const CharType* format, ...) {
if (MainLoggers.empty() && severity != Level::Fatal)
return;
va_list ap;
va_start(ap, format);
report(severity, format, ap);
va_end(ap);
}
template <typename CharType>
inline void report(Level severity, const CharType* format, va_list ap) {
template <typename Char>
void _vreport(Level severity, fmt::basic_string_view<Char> format,
fmt::basic_format_args<fmt::buffer_context<Char>> args) {
auto lk = LockLog();
++_LogCounter;
if (severity == Fatal)
RegisterConsoleLogger();
for (auto& logger : MainLoggers) {
va_list apc;
va_copy(apc, ap);
logger->report(m_modName, severity, format, apc);
va_end(apc);
}
for (auto& logger : MainLoggers)
logger->report(m_modName, severity, format, args);
if (severity == Error || severity == Fatal)
logvisorBp();
if (severity == Fatal)
@@ -206,41 +184,119 @@ public:
++ErrorCount;
}
/**
* @brief Route new log message with source info to centralized ILogger
* @param severity Level of log report severity
* @param file Source file name from __FILE__ macro
* @param linenum Source line number from __LINE__ macro
* @param format Standard printf-style format string
*/
template <typename CharType>
inline void reportSource(Level severity, const char* file, unsigned linenum, const CharType* format, ...) {
if (MainLoggers.empty() && severity != Level::Fatal)
return;
va_list ap;
va_start(ap, format);
reportSource(severity, file, linenum, format, ap);
va_end(ap);
}
template <typename CharType>
inline void reportSource(Level severity, const char* file, unsigned linenum, const CharType* format, va_list ap) {
template <typename Char>
void _vreportSource(Level severity, const char* file, unsigned linenum, fmt::basic_string_view<Char> format,
fmt::basic_format_args<fmt::buffer_context<Char>> args) {
auto lk = LockLog();
++_LogCounter;
if (severity == Fatal)
RegisterConsoleLogger();
for (auto& logger : MainLoggers) {
va_list apc;
va_copy(apc, ap);
logger->reportSource(m_modName, severity, file, linenum, format, apc);
va_end(apc);
}
for (auto& logger : MainLoggers)
logger->reportSource(m_modName, severity, file, linenum, format, args);
if (severity == Error || severity == Fatal)
logvisorBp();
if (severity == Fatal)
logvisorAbort();
else if (severity == Error)
++ErrorCount;
}
public:
constexpr Module(const char* modName) : m_modName(modName) {}
/**
* @brief Route new log message to centralized ILogger
* @param severity Level of log report severity
* @param format fmt-style format string
*/
template <typename S, typename... Args, typename Char = fmt::char_t<S>>
void report(Level severity, const S& format, Args&&... args) {
if (MainLoggers.empty() && severity != Level::Fatal)
return;
_vreport(severity, fmt::to_string_view<Char>(format),
fmt::basic_format_args<fmt::buffer_context<Char>>(
fmt::make_args_checked<Args...>(format, std::forward<Args>(args)...)));
}
template <typename Char>
void vreport(Level severity, fmt::basic_string_view<Char> format,
fmt::basic_format_args<fmt::buffer_context<Char>> args) {
if (MainLoggers.empty() && severity != Level::Fatal)
return;
_vreport(severity, format, args);
}
/**
* @brief Route new log message with source info to centralized ILogger
* @param severity Level of log report severity
* @param file Source file name from __FILE__ macro
* @param linenum Source line number from __LINE__ macro
* @param format fmt-style format string
*/
template <typename S, typename... Args, typename Char = fmt::char_t<S>>
void reportSource(Level severity, const char* file, unsigned linenum, const S& format, Args&&... args) {
if (MainLoggers.empty() && severity != Level::Fatal)
return;
_vreportSource(severity, file, linenum, fmt::to_string_view<Char>(format),
fmt::basic_format_args<fmt::buffer_context<Char>>(
fmt::make_args_checked<Args...>(format, std::forward<Args>(args)...)));
}
template <typename Char>
void vreportSource(Level severity, const char* file, unsigned linenum, fmt::basic_string_view<Char> format,
fmt::basic_format_args<fmt::buffer_context<Char>> args) {
if (MainLoggers.empty() && severity != Level::Fatal)
return;
_vreportSource(severity, file, linenum, format, args);
}
};
#define FMT_CUSTOM_FORMATTER(tp, fmtstr, ...) \
namespace fmt { \
template <> \
struct formatter<tp, char> { \
template <typename ParseContext> \
constexpr auto parse(ParseContext &ctx) { return ctx.begin(); } \
template <typename FormatContext> \
auto format(const tp &obj, FormatContext &ctx) { \
return format_to(ctx.out(), FMT_STRING(fmtstr), __VA_ARGS__); \
} \
}; \
template <> \
struct formatter<tp, wchar_t> { \
template <typename ParseContext> \
constexpr auto parse(ParseContext &ctx) { return ctx.begin(); } \
template <typename FormatContext> \
auto format(const tp &obj, FormatContext &ctx) { \
return format_to(ctx.out(), FMT_STRING(L##fmtstr), __VA_ARGS__); \
} \
}; \
template <> \
struct formatter<tp, char16_t> { \
template <typename ParseContext> \
constexpr auto parse(ParseContext &ctx) { return ctx.begin(); } \
template <typename FormatContext> \
auto format(const tp &obj, FormatContext &ctx) { \
return format_to(ctx.out(), FMT_STRING(u##fmtstr), __VA_ARGS__); \
} \
}; \
template <> \
struct formatter<tp, char32_t> { \
template <typename ParseContext> \
constexpr auto parse(ParseContext &ctx) { return ctx.begin(); } \
template <typename FormatContext> \
auto format(const tp &obj, FormatContext &ctx) { \
return format_to(ctx.out(), FMT_STRING(U##fmtstr), __VA_ARGS__); \
} \
}; \
}
} // namespace logvisor
template <typename S, typename... Args, typename Char = fmt::char_t<S>>
void quicklog(const S& format, Args&&... args) {
logvisor::MainLoggers[0]->report(
"quick", logvisor::Info, fmt::to_string_view<Char>(format),
fmt::basic_format_args<fmt::buffer_context<Char>>(
fmt::make_args_checked<Args...>(format, std::forward<Args>(args)...)));
}

194
include/nowide/args.hpp Normal file
View File

@@ -0,0 +1,194 @@
//
// Copyright (c) 2012 Artyom Beilis (Tonkikh)
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef NOWIDE_ARGS_HPP_INCLUDED
#define NOWIDE_ARGS_HPP_INCLUDED
#include <nowide/config.hpp>
#ifdef NOWIDE_WINDOWS
#include <nowide/stackstring.hpp>
#include <nowide/windows.hpp>
#include <stdexcept>
#include <vector>
#endif
namespace nowide {
#if !defined(NOWIDE_WINDOWS) && !defined(NOWIDE_DOXYGEN)
class args
{
public:
args(int&, char**&)
{}
args(int&, char**&, char**&)
{}
};
#else
///
/// \brief \c args is a class that temporarily replaces standard main() function arguments with their
/// equal, but UTF-8 encoded values under Microsoft Windows for the lifetime of the instance.
///
/// The class uses \c GetCommandLineW(), \c CommandLineToArgvW() and \c GetEnvironmentStringsW()
/// in order to obtain Unicode-encoded values.
/// It does not relate to actual values of argc, argv and env under Windows.
///
/// It restores the original values in its destructor (usually at the end of the \c main function).
///
/// If any of the system calls fails, an exception of type std::runtime_error will be thrown
/// and argc, argv, env remain unchanged.
///
/// \note The class owns the memory of the newly allocated strings.
/// So you need to keep it alive as long as you use the values.
///
/// Usage:
/// \code
/// int main(int argc, char** argv, char** env) {
/// nowide::args _(argc, argv, env); // Note the _ as a "don't care" name for the instance
/// // Use argv and env as usual, they are now UTF-8 encoded on Windows
/// return 0; // Memory held by args is released
/// }
/// \endcode
class args
{
public:
///
/// Fix command line arguments
///
args(int& argc, char**& argv) :
old_argc_(argc), old_argv_(argv), old_env_(0), old_argc_ptr_(&argc), old_argv_ptr_(&argv), old_env_ptr_(0)
{
fix_args(argc, argv);
}
///
/// Fix command line arguments and environment
///
args(int& argc, char**& argv, char**& env) :
old_argc_(argc), old_argv_(argv), old_env_(env), old_argc_ptr_(&argc), old_argv_ptr_(&argv),
old_env_ptr_(&env)
{
fix_args(argc, argv);
fix_env(env);
}
///
/// Restore original argc, argv, env values, if changed
///
~args()
{
if(old_argc_ptr_)
*old_argc_ptr_ = old_argc_;
if(old_argv_ptr_)
*old_argv_ptr_ = old_argv_;
if(old_env_ptr_)
*old_env_ptr_ = old_env_;
}
private:
class wargv_ptr
{
wchar_t** p;
int argc;
public:
wargv_ptr()
{
p = CommandLineToArgvW(GetCommandLineW(), &argc);
}
~wargv_ptr()
{
if(p)
LocalFree(p);
}
wargv_ptr(const wargv_ptr&) = delete;
wargv_ptr& operator=(const wargv_ptr&) = delete;
int size() const
{
return argc;
}
operator bool() const
{
return p != NULL;
}
const wchar_t* operator[](size_t i) const
{
return p[i];
}
};
class wenv_ptr
{
wchar_t* p;
public:
wenv_ptr() : p(GetEnvironmentStringsW())
{}
~wenv_ptr()
{
if(p)
FreeEnvironmentStringsW(p);
}
wenv_ptr(const wenv_ptr&) = delete;
wenv_ptr& operator=(const wenv_ptr&) = delete;
operator const wchar_t*() const
{
return p;
}
};
void fix_args(int& argc, char**& argv)
{
const wargv_ptr wargv;
if(!wargv)
throw std::runtime_error("Could not get command line!");
args_.resize(wargv.size() + 1, 0);
arg_values_.resize(wargv.size());
for(int i = 0; i < wargv.size(); i++)
args_[i] = arg_values_[i].convert(wargv[i]);
argc = wargv.size();
argv = &args_[0];
}
void fix_env(char**& env)
{
const wenv_ptr wstrings;
if(!wstrings)
throw std::runtime_error("Could not get environment strings!");
const wchar_t* wstrings_end = 0;
int count = 0;
for(wstrings_end = wstrings; *wstrings_end; wstrings_end += wcslen(wstrings_end) + 1)
count++;
env_.convert(wstrings, wstrings_end);
envp_.resize(count + 1, 0);
char* p = env_.get();
int pos = 0;
for(int i = 0; i < count; i++)
{
if(*p != '=')
envp_[pos++] = p;
p += strlen(p) + 1;
}
env = &envp_[0];
}
std::vector<char*> args_;
std::vector<short_stackstring> arg_values_;
stackstring env_;
std::vector<char*> envp_;
int old_argc_;
char** old_argv_;
char** old_env_;
int* old_argc_ptr_;
char*** old_argv_ptr_;
char*** old_env_ptr_;
};
#endif
} // namespace nowide
#endif

85
include/nowide/config.hpp Normal file
View File

@@ -0,0 +1,85 @@
//
// Copyright (c) 2012 Artyom Beilis (Tonkikh)
// Copyright (c) 2020 Alexander Grund
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef NOWIDE_CONFIG_HPP_INCLUDED
#define NOWIDE_CONFIG_HPP_INCLUDED
#include <nowide/replacement.hpp>
#if(defined(__WIN32) || defined(_WIN32) || defined(WIN32)) && !defined(__CYGWIN__)
#define NOWIDE_WINDOWS
#endif
#ifdef _MSC_VER
#define NOWIDE_MSVC _MSC_VER
#endif
#ifdef __GNUC__
#define NOWIDE_SYMBOL_VISIBLE __attribute__((__visibility__("default")))
#endif
#ifndef NOWIDE_SYMBOL_VISIBLE
#define NOWIDE_SYMBOL_VISIBLE
#endif
#ifdef NOWIDE_WINDOWS
#define NOWIDE_SYMBOL_EXPORT __declspec(dllexport)
#define NOWIDE_SYMBOL_IMPORT __declspec(dllimport)
#else
#define NOWIDE_SYMBOL_EXPORT NOWIDE_SYMBOL_VISIBLE
#define NOWIDE_SYMBOL_IMPORT
#endif
#if defined(NOWIDE_DYN_LINK)
#ifdef NOWIDE_SOURCE
#define NOWIDE_DECL NOWIDE_SYMBOL_EXPORT
#else
#define NOWIDE_DECL NOWIDE_SYMBOL_IMPORT
#endif // NOWIDE_SOURCE
#else
#define NOWIDE_DECL
#endif // NOWIDE_DYN_LINK
#ifndef NOWIDE_DECL
#define NOWIDE_DECL
#endif
#if defined(NOWIDE_WINDOWS)
#ifdef NOWIDE_USE_FILEBUF_REPLACEMENT
#undef NOWIDE_USE_FILEBUF_REPLACEMENT
#endif
#define NOWIDE_USE_FILEBUF_REPLACEMENT 1
#elif !defined(NOWIDE_USE_FILEBUF_REPLACEMENT)
#define NOWIDE_USE_FILEBUF_REPLACEMENT 0
#endif
#if defined(__has_cpp_attribute)
#define NOWIDE_HAS_CPP_ATTRIBUTE(attr) __has_cpp_attribute(attr)
#else
#define NOWIDE_HAS_CPP_ATTRIBUTE(attr) (0)
#endif
#if NOWIDE_HAS_CPP_ATTRIBUTE(fallthrough)
#define NOWIDE_FALLTHROUGH [[fallthrough]]
#else
#define NOWIDE_FALLTHROUGH
#endif
#if defined __GNUC__
#define NOWIDE_LIKELY(x) __builtin_expect(x, 1)
#define NOWIDE_UNLIKELY(x) __builtin_expect(x, 0)
#else
#if !defined(NOWIDE_LIKELY)
#define NOWIDE_LIKELY(x) x
#endif
#if !defined(NOWIDE_UNLIKELY)
#define NOWIDE_UNLIKELY(x) x
#endif
#endif
#endif

135
include/nowide/convert.hpp Normal file
View File

@@ -0,0 +1,135 @@
//
// Copyright (c) 2012 Artyom Beilis (Tonkikh)
// Copyright (c) 2020 Alexander Grund
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef NOWIDE_CONVERT_HPP_INCLUDED
#define NOWIDE_CONVERT_HPP_INCLUDED
#include <nowide/detail/is_string_container.hpp>
#include <nowide/utf/convert.hpp>
#include <string>
namespace nowide {
///
/// Convert wide string (UTF-16/32) in range [begin,end) to NULL terminated narrow string (UTF-8)
/// stored in \a output of size \a output_size (including NULL)
///
/// If there is not enough room NULL is returned, else output is returned.
/// Any illegal sequences are replaced with the replacement character, see #NOWIDE_REPLACEMENT_CHARACTER
///
inline char* narrow(char* output, size_t output_size, const wchar_t* begin, const wchar_t* end)
{
return utf::convert_buffer(output, output_size, begin, end);
}
///
/// Convert NULL terminated wide string (UTF-16/32) to NULL terminated narrow string (UTF-8)
/// stored in \a output of size \a output_size (including NULL)
///
/// If there is not enough room NULL is returned, else output is returned.
/// Any illegal sequences are replaced with the replacement character, see #NOWIDE_REPLACEMENT_CHARACTER
///
inline char* narrow(char* output, size_t output_size, const wchar_t* source)
{
return narrow(output, output_size, source, source + utf::strlen(source));
}
///
/// Convert narrow string (UTF-8) in range [begin,end) to NULL terminated wide string (UTF-16/32)
/// stored in \a output of size \a output_size (including NULL)
///
/// If there is not enough room NULL is returned, else output is returned.
/// Any illegal sequences are replaced with the replacement character, see #NOWIDE_REPLACEMENT_CHARACTER
///
inline wchar_t* widen(wchar_t* output, size_t output_size, const char* begin, const char* end)
{
return utf::convert_buffer(output, output_size, begin, end);
}
///
/// Convert NULL terminated narrow string (UTF-8) to NULL terminated wide string (UTF-16/32)
/// most output_size (including NULL)
///
/// If there is not enough room NULL is returned, else output is returned.
/// Any illegal sequences are replaced with the replacement character, see #NOWIDE_REPLACEMENT_CHARACTER
///
inline wchar_t* widen(wchar_t* output, size_t output_size, const char* source)
{
return widen(output, output_size, source, source + utf::strlen(source));
}
///
/// Convert wide string (UTF-16/32) to narrow string (UTF-8).
///
/// \param s Input string
/// \param count Number of characters to convert
/// Any illegal sequences are replaced with the replacement character, see #NOWIDE_REPLACEMENT_CHARACTER
///
template<typename T_Char, typename = detail::requires_wide_char<T_Char>>
inline std::string narrow(const T_Char* s, size_t count)
{
return utf::convert_string<char>(s, s + count);
}
///
/// Convert wide string (UTF-16/32) to narrow string (UTF-8).
///
/// \param s NULL terminated input string
/// Any illegal sequences are replaced with the replacement character, see #NOWIDE_REPLACEMENT_CHARACTER
///
template<typename T_Char, typename = detail::requires_wide_char<T_Char>>
inline std::string narrow(const T_Char* s)
{
return narrow(s, utf::strlen(s));
}
///
/// Convert wide string (UTF-16/32) to narrow string (UTF-8).
///
/// \param s Input string
/// Any illegal sequences are replaced with the replacement character, see #NOWIDE_REPLACEMENT_CHARACTER
///
template<typename StringOrStringView, typename = detail::requires_wide_string_container<StringOrStringView>>
inline std::string narrow(const StringOrStringView& s)
{
return utf::convert_string<char>(s.data(), s.data() + s.size());
}
///
/// Convert narrow string (UTF-8) to wide string (UTF-16/32).
///
/// \param s Input string
/// \param count Number of characters to convert
/// Any illegal sequences are replaced with the replacement character, see #NOWIDE_REPLACEMENT_CHARACTER
///
template<typename T_Char, typename = detail::requires_narrow_char<T_Char>>
inline std::wstring widen(const T_Char* s, size_t count)
{
return utf::convert_string<wchar_t>(s, s + count);
}
///
/// Convert narrow string (UTF-8) to wide string (UTF-16/32).
///
/// \param s NULL terminated input string
/// Any illegal sequences are replaced with the replacement character, see #NOWIDE_REPLACEMENT_CHARACTER
///
template<typename T_Char, typename = detail::requires_narrow_char<T_Char>>
inline std::wstring widen(const T_Char* s)
{
return widen(s, utf::strlen(s));
}
///
/// Convert narrow string (UTF-8) to wide string (UTF-16/32).
///
/// \param s Input string
/// Any illegal sequences are replaced with the replacement character, see #NOWIDE_REPLACEMENT_CHARACTER
///
template<typename StringOrStringView, typename = detail::requires_narrow_string_container<StringOrStringView>>
inline std::wstring widen(const StringOrStringView& s)
{
return utf::convert_string<wchar_t>(s.data(), s.data() + s.size());
}
} // namespace nowide
#endif

View File

@@ -0,0 +1,94 @@
//
// Copyright (c) 2020 Alexander Grund
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef NOWIDE_DETAIL_IS_STRING_CONTAINER_HPP_INCLUDED
#define NOWIDE_DETAIL_IS_STRING_CONTAINER_HPP_INCLUDED
#include <cstddef>
#include <type_traits>
namespace nowide {
namespace detail {
template<class...>
struct make_void
{
typedef void type;
};
template<class... Ts>
using void_t = typename make_void<Ts...>::type;
template<typename T>
struct is_char_type : std::false_type
{};
template<>
struct is_char_type<char> : std::true_type
{};
template<>
struct is_char_type<wchar_t> : std::true_type
{};
template<>
struct is_char_type<char16_t> : std::true_type
{};
template<>
struct is_char_type<char32_t> : std::true_type
{};
#ifdef __cpp_char8_t
template<>
struct is_char_type<char8_t> : std::true_type
{};
#endif
template<typename T>
struct is_c_string : std::false_type
{};
template<typename T>
struct is_c_string<const T*> : is_char_type<T>
{};
template<typename T>
using const_data_result = decltype(std::declval<const T>().data());
/// Return the size of the char type returned by the data() member function
template<typename T>
using get_data_width =
std::integral_constant<std::size_t, sizeof(typename std::remove_pointer<const_data_result<T>>::type)>;
template<typename T>
using size_result = decltype(std::declval<T>().size());
/// Return true if the data() member function returns a pointer to a type of size 1
template<typename T>
using has_narrow_data = std::integral_constant<bool, (get_data_width<T>::value == 1)>;
/// Return true if T is a string container, e.g. std::basic_string, std::basic_string_view
/// Requires a static value `npos`, a member function `size()` returning an integral,
/// and a member function `data()` returning a C string
template<typename T, bool isNarrow, typename = void>
struct is_string_container : std::false_type
{};
// clang-format off
template<typename T, bool isNarrow>
struct is_string_container<T, isNarrow, void_t<decltype(T::npos), size_result<T>, const_data_result<T>>>
: std::integral_constant<bool,
std::is_integral<decltype(T::npos)>::value
&& std::is_integral<size_result<T>>::value
&& is_c_string<const_data_result<T>>::value
&& isNarrow == has_narrow_data<T>::value>
{};
// clang-format on
template<typename T>
using requires_narrow_string_container = typename std::enable_if<is_string_container<T, true>::value>::type;
template<typename T>
using requires_wide_string_container = typename std::enable_if<is_string_container<T, false>::value>::type;
template<typename T>
using requires_narrow_char = typename std::enable_if<sizeof(T) == 1 && is_char_type<T>::value>::type;
template<typename T>
using requires_wide_char = typename std::enable_if<(sizeof(T) > 1) && is_char_type<T>::value>::type;
} // namespace detail
} // namespace nowide
#endif

View File

@@ -0,0 +1,19 @@
//
// Copyright (c) 2018 Artyom Beilis (Tonkikh)
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef NOWIDE_REPLACEMENT_HPP_INCLUDED
#define NOWIDE_REPLACEMENT_HPP_INCLUDED
/// @file
/// \def NOWIDE_REPLACEMENT_CHARACTER
/// Unicode character to be used to replace invalid UTF-8 sequences
#ifndef NOWIDE_REPLACEMENT_CHARACTER
#define NOWIDE_REPLACEMENT_CHARACTER 0xFFFD
#endif
#endif // boost/nowide/replacement.hpp

View File

@@ -0,0 +1,213 @@
//
// Copyright (c) 2012 Artyom Beilis (Tonkikh)
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef NOWIDE_STACKSTRING_HPP_INCLUDED
#define NOWIDE_STACKSTRING_HPP_INCLUDED
#include <nowide/convert.hpp>
#include <nowide/utf/utf.hpp>
#include <cassert>
#include <cstring>
namespace nowide {
///
/// \brief A class that allows to create a temporary wide or narrow UTF strings from
/// wide or narrow UTF source.
///
/// It uses a stack buffer if the string is short enough
/// otherwise allocates a buffer on the heap.
///
/// Invalid UTF characters are replaced by the substitution character, see #NOWIDE_REPLACEMENT_CHARACTER
///
/// If a NULL pointer is passed to the constructor or convert method, NULL will be returned by c_str.
/// Similarily a default constructed stackstring will return NULL on calling c_str.
///
template<typename CharOut = wchar_t, typename CharIn = char, size_t BufferSize = 256>
class basic_stackstring
{
public:
/// Size of the stack buffer
static const size_t buffer_size = BufferSize;
/// Type of the output character (converted to)
using output_char = CharOut;
/// Type of the input character (converted from)
using input_char = CharIn;
/// Creates a NULL stackstring
basic_stackstring() : data_(NULL)
{
buffer_[0] = 0;
}
/// Convert the NULL terminated string input and store in internal buffer
/// If input is NULL, nothing will be stored
explicit basic_stackstring(const input_char* input) : data_(NULL)
{
convert(input);
}
explicit basic_stackstring(std::string_view input) : data_(NULL) {
convert(input.data(), input.data() + input.size());
}
/// Convert the sequence [begin, end) and store in internal buffer
/// If begin is NULL, nothing will be stored
basic_stackstring(const input_char* begin, const input_char* end) : data_(NULL)
{
convert(begin, end);
}
/// Copy construct from other
basic_stackstring(const basic_stackstring& other) : data_(NULL)
{
*this = other;
}
/// Copy assign from other
basic_stackstring& operator=(const basic_stackstring& other)
{
if(this != &other)
{
clear();
const size_t len = other.length();
if(other.uses_stack_memory())
data_ = buffer_;
else if(other.data_)
data_ = new output_char[len + 1];
else
{
data_ = NULL;
return *this;
}
std::memcpy(data_, other.data_, sizeof(output_char) * (len + 1));
}
return *this;
}
~basic_stackstring()
{
clear();
}
/// Convert the NULL terminated string input and store in internal buffer
/// If input is NULL, the current buffer will be reset to NULL
output_char* convert(const input_char* input)
{
if(input)
return convert(input, input + utf::strlen(input));
clear();
return get();
}
/// Convert the sequence [begin, end) and store in internal buffer
/// If begin is NULL, the current buffer will be reset to NULL
output_char* convert(const input_char* begin, const input_char* end)
{
clear();
if(begin)
{
const size_t input_len = end - begin;
// Minimum size required: 1 output char per input char + trailing NULL
const size_t min_output_size = input_len + 1;
// If there is a chance the converted string fits on stack, try it
if(min_output_size <= buffer_size && utf::convert_buffer(buffer_, buffer_size, begin, end))
data_ = buffer_;
else
{
// Fallback: Allocate a buffer that is surely large enough on heap
// Max size: Every input char is transcoded to the output char with maximum with + trailing NULL
const size_t max_output_size = input_len * utf::utf_traits<output_char>::max_width + 1;
data_ = new output_char[max_output_size];
const bool success = utf::convert_buffer(data_, max_output_size, begin, end) == data_;
assert(success);
(void)success;
}
}
return get();
}
/// Return the converted, NULL-terminated string or NULL if no string was converted
output_char* get()
{
return data_;
}
/// Return the converted, NULL-terminated string or NULL if no string was converted
const output_char* get() const
{
return data_;
}
/// Reset the internal buffer to NULL
void clear()
{
if(!uses_stack_memory())
delete[] data_;
data_ = NULL;
}
/// Swap lhs with rhs
friend void swap(basic_stackstring& lhs, basic_stackstring& rhs)
{
if(lhs.uses_stack_memory())
{
if(rhs.uses_stack_memory())
{
for(size_t i = 0; i < buffer_size; i++)
std::swap(lhs.buffer_[i], rhs.buffer_[i]);
} else
{
lhs.data_ = rhs.data_;
rhs.data_ = rhs.buffer_;
for(size_t i = 0; i < buffer_size; i++)
rhs.buffer_[i] = lhs.buffer_[i];
}
} else if(rhs.uses_stack_memory())
{
rhs.data_ = lhs.data_;
lhs.data_ = lhs.buffer_;
for(size_t i = 0; i < buffer_size; i++)
lhs.buffer_[i] = rhs.buffer_[i];
} else
std::swap(lhs.data_, rhs.data_);
}
protected:
/// True if the stack memory is used
bool uses_stack_memory() const
{
return data_ == buffer_;
}
/// Return the current length of the string excluding the NULL terminator
/// If NULL is stored returns NULL
size_t length() const
{
if(!data_)
return 0;
size_t len = 0;
while(data_[len])
len++;
return len;
}
private:
output_char buffer_[buffer_size];
output_char* data_;
}; // basic_stackstring
///
/// Convenience typedef
///
using wstackstring = basic_stackstring<wchar_t, char, 256>;
///
/// Convenience typedef
///
using stackstring = basic_stackstring<char, wchar_t, 256>;
///
/// Convenience typedef
///
using wshort_stackstring = basic_stackstring<wchar_t, char, 16>;
///
/// Convenience typedef
///
using short_stackstring = basic_stackstring<char, wchar_t, 16>;
} // namespace nowide
#endif

View File

@@ -0,0 +1,96 @@
//
// Copyright (c) 2012 Artyom Beilis (Tonkikh)
// Copyright (c) 2020 Alexander Grund
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef NOWIDE_UTF_CONVERT_HPP_INCLUDED
#define NOWIDE_UTF_CONVERT_HPP_INCLUDED
#include <nowide/detail/is_string_container.hpp>
#include <nowide/replacement.hpp>
#include <nowide/utf/utf.hpp>
#include <iterator>
#include <string>
namespace nowide {
namespace utf {
/// Return the length of the given string in code units.
/// That is the number of elements of type Char until the first NULL character.
/// Equivalent to `std::strlen(s)` but can handle wide-strings
template<typename Char>
size_t strlen(const Char* s)
{
const Char* end = s;
while(*end)
end++;
return end - s;
}
/// Convert a buffer of UTF sequences in the range [source_begin, source_end)
/// from \a CharIn to \a CharOut to the output \a buffer of size \a buffer_size.
///
/// \return original buffer containing the NULL terminated string or NULL
///
/// If there is not enough room in the buffer NULL is returned, and the content of the buffer is undefined.
/// Any illegal sequences are replaced with the replacement character, see #NOWIDE_REPLACEMENT_CHARACTER
template<typename CharOut, typename CharIn>
CharOut*
convert_buffer(CharOut* buffer, size_t buffer_size, const CharIn* source_begin, const CharIn* source_end)
{
CharOut* rv = buffer;
if(buffer_size == 0)
return nullptr;
buffer_size--;
while(source_begin != source_end)
{
code_point c = utf_traits<CharIn>::decode(source_begin, source_end);
if(c == illegal || c == incomplete)
{
c = NOWIDE_REPLACEMENT_CHARACTER;
}
size_t width = utf_traits<CharOut>::width(c);
if(buffer_size < width)
{
rv = NULL;
break;
}
buffer = utf_traits<CharOut>::encode(c, buffer);
buffer_size -= width;
}
*buffer++ = 0;
return rv;
}
/// Convert the UTF sequences in range [begin, end) from \a CharIn to \a CharOut
/// and return it as a string
///
/// Any illegal sequences are replaced with the replacement character, see #NOWIDE_REPLACEMENT_CHARACTER
/// \tparam CharOut Output character type
template<typename CharOut, typename CharIn>
std::basic_string<CharOut> convert_string(const CharIn* begin, const CharIn* end)
{
std::basic_string<CharOut> result;
result.reserve(end - begin);
using inserter_type = std::back_insert_iterator<std::basic_string<CharOut>>;
inserter_type inserter(result);
code_point c;
while(begin != end)
{
c = utf_traits<CharIn>::decode(begin, end);
if(c == illegal || c == incomplete)
{
c = NOWIDE_REPLACEMENT_CHARACTER;
}
utf_traits<CharOut>::encode(c, inserter);
}
return result;
}
} // namespace utf
} // namespace nowide
#endif

450
include/nowide/utf/utf.hpp Normal file
View File

@@ -0,0 +1,450 @@
//
// Copyright (c) 2009-2011 Artyom Beilis (Tonkikh)
// Copyright (c) 2020 Alexander Grund
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef NOWIDE_UTF_HPP_INCLUDED
#define NOWIDE_UTF_HPP_INCLUDED
#include <nowide/config.hpp>
#include <cstdint>
namespace nowide {
///
/// \brief Namespace that holds basic operations on UTF encoded sequences
///
/// All functions defined in this namespace do not require linking with Boost.Nowide library.
/// Extracted from Boost.Locale
///
namespace utf {
///
/// \brief The integral type that can hold a Unicode code point
///
using code_point = uint32_t;
///
/// \brief Special constant that defines illegal code point
///
static const code_point illegal = 0xFFFFFFFFu;
///
/// \brief Special constant that defines incomplete code point
///
static const code_point incomplete = 0xFFFFFFFEu;
///
/// \brief the function checks if \a v is a valid code point
///
inline bool is_valid_codepoint(code_point v)
{
if(v > 0x10FFFF)
return false;
if(0xD800 <= v && v <= 0xDFFF) // surrogates
return false;
return true;
}
#ifdef NOWIDE_DOXYGEN
///
/// \brief UTF Traits class - functions to convert UTF sequences to and from Unicode code points
///
template<typename CharType, int size = sizeof(CharType)>
struct utf_traits
{
///
/// The type of the character
///
using char_type = CharType;
///
/// Read one code point from the range [p,e) and return it.
///
/// - If the sequence that was read is incomplete sequence returns \ref incomplete,
/// - If illegal sequence detected returns \ref illegal
///
/// Requirements
///
/// - Iterator is valid input iterator
///
/// Postconditions
///
/// - p points to the last consumed character
///
template<typename Iterator>
static code_point decode(Iterator& p, Iterator e);
///
/// Maximal width of valid sequence in the code units:
///
/// - UTF-8 - 4
/// - UTF-16 - 2
/// - UTF-32 - 1
///
static const int max_width;
///
/// The width of specific code point in the code units.
///
/// Requirement: value is a valid Unicode code point
/// Returns value in range [1..max_width]
///
static int width(code_point value);
///
/// Get the size of the trail part of variable length encoded sequence.
///
/// Returns -1 if C is not valid lead character
///
static int trail_length(char_type c);
///
/// Returns true if c is trail code unit, always false for UTF-32
///
static bool is_trail(char_type c);
///
/// Returns true if c is lead code unit, always true of UTF-32
///
static bool is_lead(char_type c);
///
/// Convert valid Unicode code point \a value to the UTF sequence.
///
/// Requirements:
///
/// - \a value is valid code point
/// - \a out is an output iterator should be able to accept at least width(value) units
///
/// Returns the iterator past the last written code unit.
///
template<typename Iterator>
static Iterator encode(code_point value, Iterator out);
///
/// Decodes valid UTF sequence that is pointed by p into code point.
///
/// If the sequence is invalid or points to end the behavior is undefined
///
template<typename Iterator>
static code_point decode_valid(Iterator& p);
};
#else
template<typename CharType, int size = sizeof(CharType)>
struct utf_traits;
template<typename CharType>
struct utf_traits<CharType, 1>
{
using char_type = CharType;
static int trail_length(char_type ci)
{
unsigned char c = ci;
if(c < 128)
return 0;
if(NOWIDE_UNLIKELY(c < 194))
return -1;
if(c < 224)
return 1;
if(c < 240)
return 2;
if(NOWIDE_LIKELY(c <= 244))
return 3;
return -1;
}
static const int max_width = 4;
static int width(code_point value)
{
if(value <= 0x7F)
{
return 1;
} else if(value <= 0x7FF)
{
return 2;
} else if(NOWIDE_LIKELY(value <= 0xFFFF))
{
return 3;
} else
{
return 4;
}
}
static bool is_trail(char_type ci)
{
unsigned char c = ci;
return (c & 0xC0) == 0x80;
}
static bool is_lead(char_type ci)
{
return !is_trail(ci);
}
template<typename Iterator>
static code_point decode(Iterator& p, Iterator e)
{
if(NOWIDE_UNLIKELY(p == e))
return incomplete;
unsigned char lead = *p++;
// First byte is fully validated here
int trail_size = trail_length(lead);
if(NOWIDE_UNLIKELY(trail_size < 0))
return illegal;
// OK as only ASCII may be of size = 0
// also optimize for ASCII text
if(trail_size == 0)
return lead;
code_point c = lead & ((1 << (6 - trail_size)) - 1);
// Read the rest
unsigned char tmp;
switch(trail_size)
{
case 3:
if(NOWIDE_UNLIKELY(p == e))
return incomplete;
tmp = *p++;
if(!is_trail(tmp))
return illegal;
c = (c << 6) | (tmp & 0x3F);
NOWIDE_FALLTHROUGH;
case 2:
if(NOWIDE_UNLIKELY(p == e))
return incomplete;
tmp = *p++;
if(!is_trail(tmp))
return illegal;
c = (c << 6) | (tmp & 0x3F);
NOWIDE_FALLTHROUGH;
case 1:
if(NOWIDE_UNLIKELY(p == e))
return incomplete;
tmp = *p++;
if(!is_trail(tmp))
return illegal;
c = (c << 6) | (tmp & 0x3F);
}
// Check code point validity:
// - no surrogates and valid range
// - most compact representation
if(NOWIDE_UNLIKELY(!is_valid_codepoint(c)) || NOWIDE_UNLIKELY(width(c) != trail_size + 1))
{
p -= trail_size;
return illegal;
}
return c;
}
template<typename Iterator>
static code_point decode_valid(Iterator& p)
{
unsigned char lead = *p++;
if(lead < 192)
return lead;
int trail_size;
if(lead < 224)
trail_size = 1;
else if(NOWIDE_LIKELY(lead < 240)) // non-BMP rare
trail_size = 2;
else
trail_size = 3;
code_point c = lead & ((1 << (6 - trail_size)) - 1);
switch(trail_size)
{
case 3: c = (c << 6) | (static_cast<unsigned char>(*p++) & 0x3F); NOWIDE_FALLTHROUGH;
case 2: c = (c << 6) | (static_cast<unsigned char>(*p++) & 0x3F); NOWIDE_FALLTHROUGH;
case 1: c = (c << 6) | (static_cast<unsigned char>(*p++) & 0x3F);
}
return c;
}
template<typename Iterator>
static Iterator encode(code_point value, Iterator out)
{
if(value <= 0x7F)
{
*out++ = static_cast<char_type>(value);
} else if(value <= 0x7FF)
{
*out++ = static_cast<char_type>((value >> 6) | 0xC0);
*out++ = static_cast<char_type>((value & 0x3F) | 0x80);
} else if(NOWIDE_LIKELY(value <= 0xFFFF))
{
*out++ = static_cast<char_type>((value >> 12) | 0xE0);
*out++ = static_cast<char_type>(((value >> 6) & 0x3F) | 0x80);
*out++ = static_cast<char_type>((value & 0x3F) | 0x80);
} else
{
*out++ = static_cast<char_type>((value >> 18) | 0xF0);
*out++ = static_cast<char_type>(((value >> 12) & 0x3F) | 0x80);
*out++ = static_cast<char_type>(((value >> 6) & 0x3F) | 0x80);
*out++ = static_cast<char_type>((value & 0x3F) | 0x80);
}
return out;
}
}; // utf8
template<typename CharType>
struct utf_traits<CharType, 2>
{
using char_type = CharType;
// See RFC 2781
static bool is_first_surrogate(uint16_t x)
{
return 0xD800 <= x && x <= 0xDBFF;
}
static bool is_second_surrogate(uint16_t x)
{
return 0xDC00 <= x && x <= 0xDFFF;
}
static code_point combine_surrogate(uint16_t w1, uint16_t w2)
{
return ((code_point(w1 & 0x3FF) << 10) | (w2 & 0x3FF)) + 0x10000;
}
static int trail_length(char_type c)
{
if(is_first_surrogate(c))
return 1;
if(is_second_surrogate(c))
return -1;
return 0;
}
///
/// Returns true if c is trail code unit, always false for UTF-32
///
static bool is_trail(char_type c)
{
return is_second_surrogate(c);
}
///
/// Returns true if c is lead code unit, always true of UTF-32
///
static bool is_lead(char_type c)
{
return !is_second_surrogate(c);
}
template<typename It>
static code_point decode(It& current, It last)
{
if(NOWIDE_UNLIKELY(current == last))
return incomplete;
uint16_t w1 = *current++;
if(NOWIDE_LIKELY(w1 < 0xD800 || 0xDFFF < w1))
{
return w1;
}
if(w1 > 0xDBFF)
return illegal;
if(current == last)
return incomplete;
uint16_t w2 = *current++;
if(w2 < 0xDC00 || 0xDFFF < w2)
return illegal;
return combine_surrogate(w1, w2);
}
template<typename It>
static code_point decode_valid(It& current)
{
uint16_t w1 = *current++;
if(NOWIDE_LIKELY(w1 < 0xD800 || 0xDFFF < w1))
{
return w1;
}
uint16_t w2 = *current++;
return combine_surrogate(w1, w2);
}
static const int max_width = 2;
static int width(code_point u)
{
return u >= 0x10000 ? 2 : 1;
}
template<typename It>
static It encode(code_point u, It out)
{
if(NOWIDE_LIKELY(u <= 0xFFFF))
{
*out++ = static_cast<char_type>(u);
} else
{
u -= 0x10000;
*out++ = static_cast<char_type>(0xD800 | (u >> 10));
*out++ = static_cast<char_type>(0xDC00 | (u & 0x3FF));
}
return out;
}
}; // utf16;
template<typename CharType>
struct utf_traits<CharType, 4>
{
using char_type = CharType;
static int trail_length(char_type c)
{
if(is_valid_codepoint(c))
return 0;
return -1;
}
static bool is_trail(char_type /*c*/)
{
return false;
}
static bool is_lead(char_type /*c*/)
{
return true;
}
template<typename It>
static code_point decode_valid(It& current)
{
return *current++;
}
template<typename It>
static code_point decode(It& current, It last)
{
if(NOWIDE_UNLIKELY(current == last))
return incomplete;
code_point c = *current++;
if(NOWIDE_UNLIKELY(!is_valid_codepoint(c)))
return illegal;
return c;
}
static const int max_width = 1;
static int width(code_point /*u*/)
{
return 1;
}
template<typename It>
static It encode(code_point u, It out)
{
*out++ = static_cast<char_type>(u);
return out;
}
}; // utf32
#endif
} // namespace utf
} // namespace nowide
#endif

View File

@@ -0,0 +1,32 @@
//
// Copyright (c) 2012 Artyom Beilis (Tonkikh)
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef NOWIDE_WINDOWS_HPP_INCLUDED
#define NOWIDE_WINDOWS_HPP_INCLUDED
#ifdef NOWIDE_USE_WINDOWS_H
#include <windows.h>
#else
//
// These are function prototypes... Allow to avoid including windows.h
//
extern "C" {
__declspec(dllimport) wchar_t* __stdcall GetEnvironmentStringsW(void);
__declspec(dllimport) int __stdcall FreeEnvironmentStringsW(wchar_t*);
__declspec(dllimport) wchar_t* __stdcall GetCommandLineW(void);
__declspec(dllimport) wchar_t** __stdcall CommandLineToArgvW(const wchar_t*, int*);
__declspec(dllimport) unsigned long __stdcall GetLastError();
__declspec(dllimport) void* __stdcall LocalFree(void*);
__declspec(dllimport) int __stdcall SetEnvironmentVariableW(const wchar_t*, const wchar_t*);
__declspec(dllimport) unsigned long __stdcall GetEnvironmentVariableW(const wchar_t*, wchar_t*, unsigned long);
}
#endif
#endif

View File

@@ -1,155 +0,0 @@
#ifndef _NXSTL_CONDVAR
#define _NXSTL_CONDVAR 1
#ifdef __SWITCH__
extern "C" {
#include <switch/kernel/condvar.h>
#include <switch/result.h>
}
#include <chrono>
#include <bits/std_mutex.h>
#include <ext/concurrence.h>
#include <bits/alloc_traits.h>
#include <bits/allocator.h>
#include <bits/unique_ptr.h>
#include <bits/shared_ptr.h>
#include <bits/cxxabi_forced.h>
namespace std _GLIBCXX_VISIBILITY(default)
{
/// cv_status
enum class cv_status { no_timeout, timeout };
/// condition_variable
class condition_variable
{
typedef chrono::system_clock __clock_t;
typedef CondVar __native_type;
__native_type _M_cond = {};
public:
typedef __native_type* native_handle_type;
constexpr condition_variable() noexcept = default;
~condition_variable() noexcept = default;
condition_variable(const condition_variable&) = delete;
condition_variable& operator=(const condition_variable&) = delete;
void
notify_one() noexcept
{
condvarWakeOne(&_M_cond);
}
void
notify_all() noexcept
{
condvarWakeAll(&_M_cond);
}
void
wait(unique_lock<mutex>& __lock) noexcept
{
condvarWait(&_M_cond, __lock.mutex()->native_handle());
}
template<typename _Predicate>
void
wait(unique_lock<mutex>& __lock, _Predicate __p)
{
while (!__p())
wait(__lock);
}
template<typename _Duration>
cv_status
wait_until(unique_lock<mutex>& __lock,
const chrono::time_point<__clock_t, _Duration>& __atime)
{ return __wait_until_impl(__lock, __atime); }
template<typename _Clock, typename _Duration>
cv_status
wait_until(unique_lock<mutex>& __lock,
const chrono::time_point<_Clock, _Duration>& __atime)
{
// DR 887 - Sync unknown clock to known clock.
const typename _Clock::time_point __c_entry = _Clock::now();
const __clock_t::time_point __s_entry = __clock_t::now();
const auto __delta = __atime - __c_entry;
const auto __s_atime = __s_entry + __delta;
return __wait_until_impl(__lock, __s_atime);
}
template<typename _Clock, typename _Duration, typename _Predicate>
bool
wait_until(unique_lock<mutex>& __lock,
const chrono::time_point<_Clock, _Duration>& __atime,
_Predicate __p)
{
while (!__p())
if (wait_until(__lock, __atime) == cv_status::timeout)
return __p();
return true;
}
template<typename _Rep, typename _Period>
cv_status
wait_for(unique_lock<mutex>& __lock,
const chrono::duration<_Rep, _Period>& __rtime)
{
using __dur = typename __clock_t::duration;
auto __reltime = chrono::duration_cast<__dur>(__rtime);
if (__reltime < __rtime)
++__reltime;
return wait_until(__lock, __clock_t::now() + __reltime);
}
template<typename _Rep, typename _Period, typename _Predicate>
bool
wait_for(unique_lock<mutex>& __lock,
const chrono::duration<_Rep, _Period>& __rtime,
_Predicate __p)
{
using __dur = typename __clock_t::duration;
auto __reltime = chrono::duration_cast<__dur>(__rtime);
if (__reltime < __rtime)
++__reltime;
return wait_until(__lock, __clock_t::now() + __reltime, std::move(__p));
}
native_handle_type
native_handle()
{ return &_M_cond; }
private:
template<typename _Dur>
cv_status
__wait_until_impl(unique_lock<mutex>& __lock,
const chrono::time_point<__clock_t, _Dur>& __atime)
{
auto __ns = chrono::duration_cast<chrono::nanoseconds>(__atime);
Result res = condvarWaitTimeout(&_M_cond, __lock.mutex()->native_handle(), __ns.count());
if (R_DESCRIPTION(res) == KernelError_Timeout)
return cv_status::timeout;
return cv_status::no_timeout;
}
};
void
notify_all_at_thread_exit(condition_variable&, unique_lock<mutex>);
struct __at_thread_exit_elt
{
__at_thread_exit_elt* _M_next;
void (*_M_cb)(void*);
};
}
#endif
#endif

View File

@@ -1,112 +0,0 @@
#ifndef _NXSTL_MUTEX
#define _NXSTL_MUTEX 1
#ifdef __SWITCH__
extern "C" {
#include <switch/kernel/mutex.h>
}
namespace std _GLIBCXX_VISIBILITY(default)
{
// Common base class for std::mutex
class __mutex_base
{
protected:
typedef Mutex __native_type;
__native_type _M_mutex = {};
constexpr __mutex_base() noexcept = default;
__mutex_base(const __mutex_base&) = delete;
__mutex_base& operator=(const __mutex_base&) = delete;
};
/// The standard mutex type.
class mutex : private __mutex_base
{
public:
typedef __native_type* native_handle_type;
constexpr mutex() noexcept = default;
~mutex() = default;
mutex(const mutex&) = delete;
mutex& operator=(const mutex&) = delete;
void
lock()
{
mutexLock(&_M_mutex);
}
bool
try_lock() noexcept
{
return mutexTryLock(&_M_mutex);
}
void
unlock()
{
mutexUnlock(&_M_mutex);
}
native_handle_type
native_handle() noexcept
{ return &_M_mutex; }
};
// Common base class for std::recursive_mutex
class __recursive_mutex_base
{
protected:
typedef RMutex __native_type;
__recursive_mutex_base(const __recursive_mutex_base&) = delete;
__recursive_mutex_base& operator=(const __recursive_mutex_base&) = delete;
__native_type _M_mutex = {};
__recursive_mutex_base() = default;
};
/// The standard recursive mutex type.
class recursive_mutex : private __recursive_mutex_base
{
public:
typedef __native_type* native_handle_type;
constexpr recursive_mutex() = default;
~recursive_mutex() = default;
recursive_mutex(const recursive_mutex&) = delete;
recursive_mutex& operator=(const recursive_mutex&) = delete;
void
lock()
{
rmutexLock(&_M_mutex);
}
bool
try_lock() noexcept
{
return rmutexTryLock(&_M_mutex);
}
void
unlock()
{
rmutexUnlock(&_M_mutex);
}
native_handle_type
native_handle() noexcept
{ return &_M_mutex; }
};
}
#endif
#endif

View File

@@ -1,295 +0,0 @@
#ifndef _NXSTL_THREAD
#define _NXSTL_THREAD 1
#ifdef __SWITCH__
extern "C" {
#include <switch/kernel/thread.h>
#include <switch/arm/tls.h>
#include <switch/result.h>
}
#include <memory>
namespace std _GLIBCXX_VISIBILITY(default)
{
/// thread
class thread
{
public:
// Abstract base class for types that wrap arbitrary functors to be
// invoked in the new thread of execution.
struct _State
{
virtual ~_State() = default;
virtual void _M_run() = 0;
};
using _State_ptr = unique_ptr<_State>;
typedef Thread native_handle_type;
/// thread::id
class id
{
native_handle_type _M_thread;
public:
id() noexcept : _M_thread() { }
explicit
id(native_handle_type __id) : _M_thread(__id) { }
private:
friend class thread;
friend class hash<thread::id>;
friend bool
operator==(thread::id __x, thread::id __y) noexcept;
friend bool
operator<(thread::id __x, thread::id __y) noexcept;
template<class _CharT, class _Traits>
friend basic_ostream<_CharT, _Traits>&
operator<<(basic_ostream<_CharT, _Traits>& __out, thread::id __id);
};
private:
id _M_id;
public:
thread() noexcept = default;
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 2097. packaged_task constructors should be constrained
thread(thread&) = delete;
thread(const thread&) = delete;
thread(const thread&&) = delete;
thread(thread&& __t) noexcept
{ swap(__t); }
template<typename _Callable, typename... _Args>
explicit
thread(_Callable&& __f, _Args&&... __args)
{
_M_start_thread(_S_make_state(
__make_invoker(std::forward<_Callable>(__f),
std::forward<_Args>(__args)...)));
}
~thread()
{
if (joinable())
std::terminate();
}
thread& operator=(const thread&) = delete;
thread& operator=(thread&& __t) noexcept
{
if (joinable())
std::terminate();
swap(__t);
return *this;
}
void
swap(thread& __t) noexcept
{ std::swap(_M_id, __t._M_id); }
bool
joinable() const noexcept
{ return !(_M_id == id()); }
void
join()
{
threadWaitForExit(&_M_id._M_thread);
_M_id = id();
}
void
detach()
{
_M_id = id();
}
thread::id
get_id() const noexcept
{ return _M_id; }
/** @pre thread is joinable
*/
native_handle_type
native_handle()
{ return _M_id._M_thread; }
// Returns a value that hints at the number of hardware thread contexts.
static unsigned int
hardware_concurrency() noexcept
{
return 3;
}
private:
template<typename _Callable>
struct _State_impl : public _State
{
_Callable _M_func;
_State_impl(_Callable&& __f) : _M_func(std::forward<_Callable>(__f))
{ }
void
_M_run() { _M_func(); }
};
static void
execute_native_thread_routine(void *arg)
{
reinterpret_cast<_State*>(arg)->_M_run();
}
void
_M_start_thread(_State_ptr state)
{
Result res = threadCreate(&_M_id._M_thread, execute_native_thread_routine,
state.get(), 8192, 0x2B, -2);
if (R_FAILED(res))
__throw_system_error(res);
res = threadStart(&_M_id._M_thread);
if (R_FAILED(res))
__throw_system_error(res);
state.release();
}
template<typename _Callable>
static _State_ptr
_S_make_state(_Callable&& __f)
{
using _Impl = _State_impl<_Callable>;
return _State_ptr{new _Impl{std::forward<_Callable>(__f)}};
}
private:
// A call wrapper that does INVOKE(forwarded tuple elements...)
template<typename _Tuple>
struct _Invoker
{
_Tuple _M_t;
template<size_t _Index>
static __tuple_element_t<_Index, _Tuple>&&
_S_declval();
template<size_t... _Ind>
auto
_M_invoke(_Index_tuple<_Ind...>)
noexcept(noexcept(std::__invoke(_S_declval<_Ind>()...)))
-> decltype(std::__invoke(_S_declval<_Ind>()...))
{ return std::__invoke(std::get<_Ind>(std::move(_M_t))...); }
using _Indices
= typename _Build_index_tuple<tuple_size<_Tuple>::value>::__type;
auto
operator()()
noexcept(noexcept(std::declval<_Invoker&>()._M_invoke(_Indices())))
-> decltype(std::declval<_Invoker&>()._M_invoke(_Indices()))
{ return _M_invoke(_Indices()); }
};
template<typename... _Tp>
using __decayed_tuple = tuple<typename std::decay<_Tp>::type...>;
public:
// Returns a call wrapper that stores
// tuple{DECAY_COPY(__callable), DECAY_COPY(__args)...}.
template<typename _Callable, typename... _Args>
static _Invoker<__decayed_tuple<_Callable, _Args...>>
__make_invoker(_Callable&& __callable, _Args&&... __args)
{
return { __decayed_tuple<_Callable, _Args...>{
std::forward<_Callable>(__callable), std::forward<_Args>(__args)...
} };
}
};
inline void
swap(thread& __x, thread& __y) noexcept
{ __x.swap(__y); }
inline bool
operator==(thread::id __x, thread::id __y) noexcept
{
// pthread_equal is undefined if either thread ID is not valid, so we
// can't safely use __gthread_equal on default-constructed values (nor
// the non-zero value returned by this_thread::get_id() for
// single-threaded programs using GNU libc). Assume EqualityComparable.
return __x._M_thread.handle == __y._M_thread.handle;
}
inline bool
operator!=(thread::id __x, thread::id __y) noexcept
{ return !(__x == __y); }
inline bool
operator<(thread::id __x, thread::id __y) noexcept
{
// Pthreads doesn't define any way to do this, so we just have to
// assume native_handle_type is LessThanComparable.
return __x._M_thread.handle < __y._M_thread.handle;
}
inline bool
operator<=(thread::id __x, thread::id __y) noexcept
{ return !(__y < __x); }
inline bool
operator>(thread::id __x, thread::id __y) noexcept
{ return __y < __x; }
inline bool
operator>=(thread::id __x, thread::id __y) noexcept
{ return !(__x < __y); }
// DR 889.
/// std::hash specialization for thread::id.
template<>
struct hash<thread::id>
: public __hash_base<size_t, thread::id>
{
size_t
operator()(const thread::id& __id) const noexcept
{ return std::_Hash_impl::hash(__id._M_thread); }
};
template<class _CharT, class _Traits>
inline basic_ostream<_CharT, _Traits>&
operator<<(basic_ostream<_CharT, _Traits>& __out, thread::id __id)
{
if (__id == thread::id())
return __out << "thread::id of a non-executing thread";
else
return __out << __id._M_thread;
}
namespace this_thread
{
/// get_id
inline thread::id
get_id() noexcept
{
Thread ret;
uint8_t* tls = (uint8_t*)armGetTls();
uint8_t* threadCtx = *(uint8_t**)(tls + 0x1F8);
ret.handle = *(Handle*)(threadCtx + 0x1B8);
ret.stack_mem = *(void**)(threadCtx + 0x48);
ret.stack_mirror = *(void**)(threadCtx + 0x50);
ret.stack_sz = *(size_t*)(threadCtx + 0x58);
return thread::id(ret);
}
}
}
#endif
#endif

View File

@@ -11,7 +11,7 @@
#include <TlHelp32.h>
#elif defined(__SWITCH__)
#include <cstring>
#include "nxstl/thread"
#include <switch.h>
#else
#include <sys/ioctl.h>
#include <unistd.h>
@@ -31,9 +31,15 @@
#include <unordered_map>
#include <cstdio>
#include <cinttypes>
#include <signal.h>
#include <csignal>
#include <locale>
#include <optional>
#include "logvisor/logvisor.hpp"
#if SENTRY_ENABLED
#include <sentry.h>
#endif
/* ANSI sequences */
#define RED "\x1b[1;31m"
#define YELLOW "\x1b[1;33m"
@@ -47,6 +53,10 @@
#define FOREGROUND_WHITE FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
#endif
#ifndef _MSC_VER
#pragma GCC diagnostic ignored "-Wformat-truncation"
#endif
void logvisorBp() {}
namespace logvisor {
@@ -89,32 +99,32 @@ void RegisterThreadName(const char* name) {
void KillProcessTree() {
DWORD myprocID = GetCurrentProcessId();
PROCESSENTRY32 pe = {};
pe.dwSize = sizeof(PROCESSENTRY32);
PROCESSENTRY32W pe = {};
pe.dwSize = sizeof(pe);
HANDLE hSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (::Process32First(hSnap, &pe)) {
if (::Process32FirstW(hSnap, &pe) == TRUE) {
BOOL bContinue = TRUE;
// kill child processes
while (bContinue) {
while (bContinue == TRUE) {
// only kill child processes and let console window remain
if (pe.th32ParentProcessID == myprocID && wcscmp(pe.szExeFile, L"conhost.exe")) {
if (pe.th32ParentProcessID == myprocID && std::wcscmp(pe.szExeFile, L"conhost.exe") != 0) {
HANDLE hChildProc = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID);
if (hChildProc) {
if (hChildProc != nullptr) {
::TerminateProcess(hChildProc, 1);
::CloseHandle(hChildProc);
}
}
bContinue = ::Process32Next(hSnap, &pe);
bContinue = ::Process32NextW(hSnap, &pe);
}
}
}
void logvisorAbort() {
[[noreturn]] void logvisorAbort() {
#if !WINDOWS_STORE
unsigned int i;
void* stack[100];
@@ -123,16 +133,16 @@ void logvisorAbort() {
HANDLE process;
process = GetCurrentProcess();
SymInitialize(process, NULL, TRUE);
frames = CaptureStackBackTrace(0, 100, stack, NULL);
symbol = (SYMBOL_INFO*)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1);
SymInitialize(process, nullptr, TRUE);
frames = CaptureStackBackTrace(0, 100, stack, nullptr);
symbol = static_cast<SYMBOL_INFO*>(calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1));
symbol->MaxNameLen = 255;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
for (i = 0; i < frames; i++) {
SymFromAddr(process, (DWORD64)(stack[i]), 0, symbol);
fprintf(stderr, "%i: %s - 0x%0llX", frames - i - 1, symbol->Name, symbol->Address);
std::fwprintf(stderr, L"%i: %S - 0x%0llX", frames - i - 1, symbol->Name, symbol->Address);
DWORD dwDisplacement;
IMAGEHLP_LINE64 line;
@@ -141,14 +151,14 @@ void logvisorAbort() {
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
if (SymGetLineFromAddr64(process, (DWORD64)(stack[i]), &dwDisplacement, &line)) {
// SymGetLineFromAddr64 returned success
fprintf(stderr, " LINE %d\n", int(line.LineNumber));
std::fwprintf(stderr, L" LINE %d\n", int(line.LineNumber));
} else {
fprintf(stderr, "\n");
std::fputwc('\n', stderr);
}
}
fflush(stderr);
free(symbol);
std::fflush(stderr);
std::free(symbol);
#endif
@@ -165,13 +175,21 @@ void logvisorAbort() {
}
#elif defined(__SWITCH__)
void logvisorAbort() { exit(1); }
[[noreturn]] void logvisorAbort() {
MainLoggers.clear();
nvExit();
exit(1);
}
#elif defined(EMSCRIPTEN)
[[noreturn]] void logvisorAbort() {
abort();
}
#else
void KillProcessTree() {}
#include <execinfo.h>
void logvisorAbort() {
[[noreturn]] void logvisorAbort() {
void* array[128];
size_t size = backtrace(array, 128);
@@ -182,25 +200,22 @@ void logvisorAbort() {
readlink("/proc/self/exe", exeNameBuffer, exeBufSize);
#endif
char cmdLine[1024];
#if __APPLE__
snprintf(cmdLine, 1024, "atos -p %d", getpid());
std::string cmdLineStr = fmt::format(FMT_STRING("atos -p {}"), getpid());
#else
snprintf(cmdLine, 1024, "2>/dev/null addr2line -C -f -e \"%s\"", exeNameBuffer);
std::string cmdLineStr = fmt::format(FMT_STRING("2>/dev/null addr2line -C -f -e \"{}\""), exeNameBuffer);
#endif
std::string cmdLineStr = cmdLine;
for (size_t i = 0; i < size; i++) {
#if __linux__
Dl_info dlip;
if (dladdr(array[i], &dlip))
snprintf(cmdLine, 128, " %p", (void*)((uint8_t*)array[i] - (uint8_t*)dlip.dli_fbase));
cmdLineStr += fmt::format(FMT_STRING(" 0x{:016X}"), (uintptr_t)((uint8_t*)array[i] - (uint8_t*)dlip.dli_fbase));
else
snprintf(cmdLine, 128, " %p", array[i]);
cmdLineStr += fmt::format(FMT_STRING(" 0x{:016X}"), (uintptr_t)array[i]);
#else
snprintf(cmdLine, 128, " %p", array[i]);
cmdLineStr += fmt::format(FMT_STRING(" 0x{:016X}"), (uintptr_t)array[i]);
#endif
cmdLineStr += cmdLine;
}
FILE* fp = popen(cmdLineStr.c_str(), "r");
@@ -208,26 +223,26 @@ void logvisorAbort() {
char readBuf[256];
size_t readSz;
while ((readSz = fread(readBuf, 1, 256, fp)))
fwrite(readBuf, 1, readSz, stderr);
std::fwrite(readBuf, 1, readSz, stderr);
pclose(fp);
} else {
for (size_t i = 0; i < size; i++) {
fprintf(stderr, "- ");
std::fputs("- ", stderr);
Dl_info dlip;
if (dladdr(array[i], &dlip)) {
int status;
char* demangledName = abi::__cxa_demangle(dlip.dli_sname, nullptr, nullptr, &status);
fprintf(stderr, "%p(%s+%p)\n", dlip.dli_saddr, demangledName ? demangledName : dlip.dli_sname,
std::fprintf(stderr, "%p(%s+%p)\n", dlip.dli_saddr, demangledName ? demangledName : dlip.dli_sname,
(void*)((uint8_t*)array[i] - (uint8_t*)dlip.dli_fbase));
free(demangledName);
std::free(demangledName);
} else {
fprintf(stderr, "%p\n", array[i]);
std::fprintf(stderr, "%p\n", array[i]);
}
}
}
fflush(stderr);
fflush(stdout);
std::fflush(stderr);
std::fflush(stdout);
KillProcessTree();
#ifndef NDEBUG
@@ -246,19 +261,19 @@ static void AbortHandler(int signum) {
_LogMutex.enabled = false;
switch (signum) {
case SIGSEGV:
Log.report(logvisor::Fatal, "Segmentation Fault");
Log.report(logvisor::Fatal, FMT_STRING("Segmentation Fault"));
break;
case SIGILL:
Log.report(logvisor::Fatal, "Bad Execution");
Log.report(logvisor::Fatal, FMT_STRING("Bad Execution"));
break;
case SIGFPE:
Log.report(logvisor::Fatal, "Floating Point Exception");
Log.report(logvisor::Fatal, FMT_STRING("Floating Point Exception"));
break;
case SIGABRT:
Log.report(logvisor::Fatal, "Abort Signal");
Log.report(logvisor::Fatal, FMT_STRING("Abort Signal"));
break;
default:
Log.report(logvisor::Fatal, "unknown signal %d", signum);
Log.report(logvisor::Fatal, FMT_STRING("unknown signal {}"), signum);
break;
}
}
@@ -267,9 +282,9 @@ uint64_t _LogCounter;
std::vector<std::unique_ptr<ILogger>> MainLoggers;
std::atomic_size_t ErrorCount(0);
static std::chrono::steady_clock MonoClock;
static std::chrono::steady_clock::time_point GlobalStart = MonoClock.now();
static inline std::chrono::steady_clock::duration CurrentUptime() { return MonoClock.now() - GlobalStart; }
using MonoClock = std::chrono::steady_clock;
static MonoClock::time_point GlobalStart = MonoClock::now();
static inline MonoClock::duration CurrentUptime() { return MonoClock::now() - GlobalStart; }
std::atomic_uint_fast64_t FrameIndex(0);
static inline int ConsoleWidth() {
@@ -292,6 +307,233 @@ static inline int ConsoleWidth() {
return retval;
}
#if LOGVISOR_NX_LM
struct ConsoleLogger : public ILogger {
Service m_svc{};
Service m_logger{};
bool m_ready = false;
struct MessageHeader {
enum Flags : u32 {
IsHead = 1,
IsTail = 2,
};
enum Severity : u32 {
Trace,
Info,
Warning,
Error,
Critical,
};
u64 pid;
u64 thread_context;
//union {
//BitField<0, 16, Flags> flags;
//BitField<16, 8, Severity> severity;
//BitField<24, 8, u32> verbosity;
//};
u32 flags;
u32 payload_size;
Flags GetFlags() const {
return Flags(flags & u32(0xffff));
}
void SetFlags(Flags f) {
flags &= ~u32(0xffff);
flags |= f;
}
Severity GetSeverity() const {
return Severity((flags >> u32(16)) & u32(0xff));
}
void SetSeverity(Severity f) {
flags &= ~u32(0xff0000);
flags |= f << u32(16);
}
u32 GetVerbosity() const {
return u32((flags >> u32(24)) & u32(0xff));
}
void SetVerbosity(u32 f) {
flags &= ~u32(0xff000000);
flags |= f << u32(24);
}
bool IsHeadLog() const {
return flags & IsHead;
}
bool IsTailLog() const {
return flags & IsTail;
}
};
static_assert(sizeof(MessageHeader) == 0x18, "MessageHeader is incorrect size");
enum class Field : u8 {
Skip = 1,
Message = 2,
Line = 3,
Filename = 4,
Function = 5,
Module = 6,
Thread = 7,
};
static constexpr MessageHeader::Severity LevelToSeverity(Level l) {
switch (l) {
case Level::Info:
default:
return MessageHeader::Info;
case Level::Warning:
return MessageHeader::Warning;
case Level::Error:
return MessageHeader::Error;
case Level::Fatal:
return MessageHeader::Critical;
}
}
ConsoleLogger() {
if (R_SUCCEEDED(smGetService(&m_svc, "lm"))) {
auto pid = getpid();
if (R_SUCCEEDED(serviceDispatchIn(&m_svc, 0, pid, .out_num_objects = 1, .out_objects = &m_logger))) {
m_ready = true;
MessageHeader head{};
head.pid = getpid();
head.SetFlags(MessageHeader::IsHead);
serviceDispatch(&m_logger, 0,
.buffer_attrs = { SfBufferAttr_HipcPointer | SfBufferAttr_In },
.buffers = { { &head, sizeof(head) } });
}
}
}
~ConsoleLogger() override {
if (m_ready) {
MessageHeader head{};
head.pid = getpid();
head.SetFlags(MessageHeader::IsTail);
serviceDispatch(&m_logger, 0,
.buffer_attrs = { SfBufferAttr_HipcPointer | SfBufferAttr_In },
.buffers = { { &head, sizeof(head) } });
}
serviceClose(&m_logger);
serviceClose(&m_svc);
}
void SendBuffer(const std::vector<u8>& buf) {
serviceDispatch(&m_logger, 0,
.buffer_attrs = { SfBufferAttr_HipcPointer | SfBufferAttr_In },
.buffers = { { buf.data(), buf.size() } });
}
void report(const char* modName, Level severity, fmt::string_view format, fmt::format_args args) override {
if (!m_ready)
return;
const std::thread::id thrId = std::this_thread::get_id();
const char* thrName = nullptr;
size_t thrNameSize = 0;
if (ThreadMap.find(thrId) != ThreadMap.end()) {
thrName = ThreadMap[thrId];
thrNameSize = std::min(std::strlen(thrName), size_t(255));
}
auto modNameSize = std::min(std::strlen(modName), size_t(255));
auto message = fmt::vformat(format, args);
auto messageSize = std::min(message.size(), size_t(255));
std::vector<u8> bufOut(sizeof(MessageHeader) + (thrNameSize ? 2 + thrNameSize : 0) + 2 + modNameSize + 2 + messageSize, '\0');
auto it = bufOut.begin();
auto& head = *reinterpret_cast<MessageHeader*>(&*it);
head.pid = getpid();
head.payload_size = bufOut.size() - sizeof(MessageHeader);
head.SetSeverity(LevelToSeverity(severity));
it += sizeof(MessageHeader);
if (thrNameSize) {
*it++ = u8(Field::Thread);
*it++ = thrNameSize;
std::memcpy(&*it, thrName, thrNameSize);
it += thrNameSize;
}
*it++ = u8(Field::Module);
*it++ = modNameSize;
std::memcpy(&*it, modName, modNameSize);
it += modNameSize;
*it++ = u8(Field::Message);
*it++ = messageSize;
std::memcpy(&*it, message.data(), messageSize);
it += messageSize;
SendBuffer(bufOut);
}
void reportSource(const char* modName, Level severity, const char* file, unsigned linenum, fmt::string_view format,
fmt::format_args args) override {
if (!m_ready)
return;
const std::thread::id thrId = std::this_thread::get_id();
const char* thrName = nullptr;
size_t thrNameSize = 0;
if (ThreadMap.find(thrId) != ThreadMap.end()) {
thrName = ThreadMap[thrId];
thrNameSize = std::min(std::strlen(thrName), size_t(255));
}
auto modNameSize = std::min(std::strlen(modName), size_t(255));
auto fileNameSize = std::min(std::strlen(file), size_t(255));
auto message = fmt::vformat(format, args);
auto messageSize = std::min(message.size(), size_t(255));
std::vector<u8> bufOut(sizeof(MessageHeader) + (thrNameSize ? 2 + thrNameSize : 0) + 2 + modNameSize + 2 + fileNameSize + 3 + 4 + 2 + messageSize, '\0');
auto it = bufOut.begin();
auto& head = *reinterpret_cast<MessageHeader*>(&*it);
head.pid = getpid();
head.payload_size = bufOut.size() - sizeof(MessageHeader);
head.SetSeverity(LevelToSeverity(severity));
it += sizeof(MessageHeader);
if (thrNameSize) {
*it++ = u8(Field::Thread);
*it++ = thrNameSize;
std::memcpy(&*it, thrName, thrNameSize);
it += thrNameSize;
}
*it++ = u8(Field::Module);
*it++ = modNameSize;
std::memcpy(&*it, modName, modNameSize);
it += modNameSize;
*it++ = u8(Field::Filename);
*it++ = fileNameSize;
std::memcpy(&*it, file, fileNameSize);
it += fileNameSize;
*it++ = u8(Field::Line);
*it++ = 4;
*it++ = u8(Field::Skip);
std::memcpy(&*it, &linenum, 4);
it += 4;
*it++ = u8(Field::Message);
*it++ = messageSize;
std::memcpy(&*it, message.data(), messageSize);
it += messageSize;
SendBuffer(bufOut);
}
};
#else
#if _WIN32
static HANDLE Term = 0;
#else
@@ -299,7 +541,7 @@ static const char* Term = nullptr;
#endif
bool XtermColor = false;
struct ConsoleLogger : public ILogger {
ConsoleLogger() {
ConsoleLogger() : ILogger(log_typeid(ConsoleLogger)) {
#if _WIN32
#if !WINDOWS_STORE
const char* conemuANSI = getenv("ConEmuANSI");
@@ -318,163 +560,156 @@ struct ConsoleLogger : public ILogger {
}
#endif
}
~ConsoleLogger() override = default;
static void _reportHead(const char* modName, const char* sourceInfo, Level severity) {
/* Clear current line out */
int width = ConsoleWidth();
fprintf(stderr, "\r");
for (int w = 0; w < width; ++w)
fprintf(stderr, " ");
fprintf(stderr, "\r");
// std::fprintf(stderr, "\r%*c\r", ConsoleWidth(), ' ');
std::chrono::steady_clock::duration tm = CurrentUptime();
double tmd = tm.count() * std::chrono::steady_clock::duration::period::num /
(double)std::chrono::steady_clock::duration::period::den;
std::thread::id thrId = std::this_thread::get_id();
const std::chrono::steady_clock::duration tm = CurrentUptime();
const double tmd = tm.count() * std::chrono::steady_clock::duration::period::num /
static_cast<double>(std::chrono::steady_clock::duration::period::den);
const std::thread::id thrId = std::this_thread::get_id();
const char* thrName = nullptr;
if (ThreadMap.find(thrId) != ThreadMap.end())
thrName = ThreadMap[thrId];
if (XtermColor) {
fprintf(stderr, BOLD "[");
fprintf(stderr, GREEN "%5.4f ", tmd);
uint_fast64_t fIdx = FrameIndex.load();
if (fIdx)
fprintf(stderr, "(%" PRIu64 ") ", fIdx);
std::fputs(BOLD "[", stderr);
fmt::print(stderr, FMT_STRING(GREEN "{:.4f} "), tmd);
const uint_fast64_t fIdx = FrameIndex.load();
if (fIdx != 0)
fmt::print(stderr, FMT_STRING("({}) "), fIdx);
switch (severity) {
case Info:
fprintf(stderr, BOLD CYAN "INFO");
std::fputs(BOLD CYAN "INFO", stderr);
break;
case Warning:
fprintf(stderr, BOLD YELLOW "WARNING");
std::fputs(BOLD YELLOW "WARNING", stderr);
break;
case Error:
fprintf(stderr, RED BOLD "ERROR");
std::fputs(RED BOLD "ERROR", stderr);
break;
case Fatal:
fprintf(stderr, BOLD RED "FATAL ERROR");
std::fputs(BOLD RED "FATAL ERROR", stderr);
break;
default:
break;
};
fprintf(stderr, NORMAL BOLD " %s", modName);
fmt::print(stderr, FMT_STRING(NORMAL BOLD " {}"), modName);
if (sourceInfo)
fprintf(stderr, BOLD YELLOW " {%s}", sourceInfo);
fmt::print(stderr, FMT_STRING(BOLD YELLOW " {{}}"), sourceInfo);
if (thrName)
fprintf(stderr, BOLD MAGENTA " (%s)", thrName);
fprintf(stderr, NORMAL BOLD "] " NORMAL);
fmt::print(stderr, FMT_STRING(BOLD MAGENTA " ({})"), thrName);
std::fputs(NORMAL BOLD "] " NORMAL, stderr);
} else {
#if _WIN32
#if !WINDOWS_STORE
SetConsoleTextAttribute(Term, FOREGROUND_INTENSITY | FOREGROUND_WHITE);
fprintf(stderr, "[");
std::fputc('[', stderr);
SetConsoleTextAttribute(Term, FOREGROUND_INTENSITY | FOREGROUND_GREEN);
fprintf(stderr, "%5.4f ", tmd);
uint64_t fi = FrameIndex.load();
if (fi)
fprintf(stderr, "(%" PRIu64 ") ", fi);
fmt::print(stderr, FMT_STRING("{:.4f} "), tmd);
const uint64_t fi = FrameIndex.load();
if (fi != 0)
std::fprintf(stderr, "(%" PRIu64 ") ", fi);
switch (severity) {
case Info:
SetConsoleTextAttribute(Term, FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_BLUE);
fprintf(stderr, "INFO");
std::fputs("INFO", stderr);
break;
case Warning:
SetConsoleTextAttribute(Term, FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN);
fprintf(stderr, "WARNING");
std::fputs("WARNING", stderr);
break;
case Error:
SetConsoleTextAttribute(Term, FOREGROUND_INTENSITY | FOREGROUND_RED);
fprintf(stderr, "ERROR");
std::fputs("ERROR", stderr);
break;
case Fatal:
SetConsoleTextAttribute(Term, FOREGROUND_INTENSITY | FOREGROUND_RED);
fprintf(stderr, "FATAL ERROR");
std::fputs("FATAL ERROR", stderr);
break;
default:
break;
};
}
SetConsoleTextAttribute(Term, FOREGROUND_INTENSITY | FOREGROUND_WHITE);
fprintf(stderr, " %s", modName);
fmt::print(stderr, FMT_STRING(" {}"), modName);
SetConsoleTextAttribute(Term, FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN);
if (sourceInfo)
fprintf(stderr, " {%s}", sourceInfo);
fmt::print(stderr, FMT_STRING(" {{}}"), sourceInfo);
SetConsoleTextAttribute(Term, FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_BLUE);
if (thrName)
fprintf(stderr, " (%s)", thrName);
fmt::print(stderr, FMT_STRING(" ({})"), thrName);
SetConsoleTextAttribute(Term, FOREGROUND_INTENSITY | FOREGROUND_WHITE);
fprintf(stderr, "] ");
std::fputs("] ", stderr);
SetConsoleTextAttribute(Term, FOREGROUND_WHITE);
#endif
#else
fprintf(stderr, "[");
fprintf(stderr, "%5.4f ", tmd);
std::fputc('[', stderr);
fmt::print(stderr, FMT_STRING("{:.4f} "), tmd);
uint_fast64_t fIdx = FrameIndex.load();
if (fIdx)
fprintf(stderr, "(%" PRIu64 ") ", fIdx);
fmt::print(stderr, FMT_STRING("({}) "), fIdx);
switch (severity) {
case Info:
fprintf(stderr, "INFO");
std::fputs("INFO", stderr);
break;
case Warning:
fprintf(stderr, "WARNING");
std::fputs("WARNING", stderr);
break;
case Error:
fprintf(stderr, "ERROR");
std::fputs("ERROR", stderr);
break;
case Fatal:
fprintf(stderr, "FATAL ERROR");
std::fputs("FATAL ERROR", stderr);
break;
default:
break;
};
fprintf(stderr, " %s", modName);
}
fmt::print(stderr, FMT_STRING(" {}"), modName);
if (sourceInfo)
fprintf(stderr, " {%s}", sourceInfo);
fmt::print(stderr, FMT_STRING(" {{}}"), sourceInfo);
if (thrName)
fprintf(stderr, " (%s)", thrName);
fprintf(stderr, "] ");
fmt::print(stderr, FMT_STRING(" ({})"), thrName);
std::fputs("] ", stderr);
#endif
}
}
void report(const char* modName, Level severity, const char* format, va_list ap) {
void report(const char* modName, Level severity, fmt::string_view format, fmt::format_args args) override {
_reportHead(modName, nullptr, severity);
vfprintf(stderr, format, ap);
fprintf(stderr, "\n");
fflush(stderr);
fmt::vprint(stderr, format, args);
std::fputc('\n', stderr);
std::fflush(stderr);
}
void report(const char* modName, Level severity, const wchar_t* format, va_list ap) {
_reportHead(modName, nullptr, severity);
vfwprintf(stderr, format, ap);
fprintf(stderr, "\n");
fflush(stderr);
}
void reportSource(const char* modName, Level severity, const char* file, unsigned linenum, const char* format,
va_list ap) {
char sourceInfo[128];
snprintf(sourceInfo, 128, "%s:%u", file, linenum);
_reportHead(modName, sourceInfo, severity);
vfprintf(stderr, format, ap);
fprintf(stderr, "\n");
fflush(stderr);
}
void reportSource(const char* modName, Level severity, const char* file, unsigned linenum, const wchar_t* format,
va_list ap) {
char sourceInfo[128];
snprintf(sourceInfo, 128, "%s:%u", file, linenum);
_reportHead(modName, sourceInfo, severity);
vfwprintf(stderr, format, ap);
fprintf(stderr, "\n");
fflush(stderr);
void reportSource(const char* modName, Level severity, const char* file, unsigned linenum, fmt::string_view format,
fmt::format_args args) override {
_reportHead(modName, fmt::format(FMT_STRING("{}:{}"), file, linenum).c_str(), severity);
fmt::vprint(stderr, format, args);
std::fputc('\n', stderr);
std::fflush(stderr);
}
};
#endif
static bool ConsoleLoggerRegistered = false;
void RegisterConsoleLogger() {
/* Otherwise construct new console logger */
if (!ConsoleLoggerRegistered) {
MainLoggers.emplace_back(new ConsoleLogger);
ConsoleLoggerRegistered = true;
#if _WIN32
#if 0
if (GetACP() != CP_UTF8) {
Log.report(Fatal, FMT_STRING("UTF-8 codepage not active! (Windows 10 1903+ required)"));
}
#else
SetConsoleOutputCP(CP_UTF8);
#endif
#endif
}
}
#if _WIN32
@@ -483,9 +718,9 @@ void CreateWin32Console() {
/* Debug console */
AllocConsole();
freopen("CONIN$", "r", stdin);
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
std::freopen("CONIN$", "r", stdin);
std::freopen("CONOUT$", "w", stdout);
std::freopen("CONOUT$", "w", stderr);
#endif
}
#endif
@@ -497,92 +732,108 @@ void RegisterStandardExceptions() {
signal(SIGFPE, AbortHandler);
}
#if SENTRY_ENABLED
void RegisterSentry(const char* appName, const char* appVersion, const char* cacheDir) {
sentry_options_t *options = sentry_options_new();
sentry_options_set_database_path(options, cacheDir);
sentry_options_set_auto_session_tracking(options, true);
sentry_options_set_symbolize_stacktraces(options, true);
sentry_options_set_dsn(options, SENTRY_DSN);
#ifdef NDEBUG
sentry_options_set_environment(options, "release");
#else
sentry_options_set_environment(options, "debug");
#endif
std::string release = fmt::format(FMT_STRING("{}@{}"), appName, appVersion);
sentry_options_set_release(options, release.c_str());
sentry_init(options);
}
#endif
struct FileLogger : public ILogger {
FILE* fp;
FILE* fp = nullptr;
FileLogger(uint64_t typeHash) : ILogger(typeHash) {}
virtual void openFile() = 0;
virtual void closeFile() { fclose(fp); }
void openFileIfNeeded() {
if (!fp) {
openFile();
}
}
virtual void closeFile() {
if (fp) {
std::fflush(fp);
std::fclose(fp);
fp = nullptr;
}
}
virtual ~FileLogger() { closeFile(); }
void _reportHead(const char* modName, const char* sourceInfo, Level severity) {
std::chrono::steady_clock::duration tm = CurrentUptime();
double tmd = tm.count() * std::chrono::steady_clock::duration::period::num /
(double)std::chrono::steady_clock::duration::period::den;
std::thread::id thrId = std::this_thread::get_id();
const std::chrono::steady_clock::duration tm = CurrentUptime();
const double tmd = tm.count() * std::chrono::steady_clock::duration::period::num /
static_cast<double>(std::chrono::steady_clock::duration::period::den);
const std::thread::id thrId = std::this_thread::get_id();
const char* thrName = nullptr;
if (ThreadMap.find(thrId) != ThreadMap.end())
if (ThreadMap.find(thrId) != ThreadMap.end()) {
thrName = ThreadMap[thrId];
}
fprintf(fp, "[");
fprintf(fp, "%5.4f ", tmd);
uint_fast64_t fIdx = FrameIndex.load();
if (fIdx)
fprintf(fp, "(%" PRIu64 ") ", fIdx);
std::fputc('[', fp);
std::fprintf(fp, "%5.4f ", tmd);
const uint_fast64_t fIdx = FrameIndex.load();
if (fIdx != 0) {
std::fprintf(fp, "(%" PRIu64 ") ", fIdx);
}
switch (severity) {
case Info:
fprintf(fp, "INFO");
std::fputs("INFO", fp);
break;
case Warning:
fprintf(fp, "WARNING");
std::fputs("WARNING", fp);
break;
case Error:
fprintf(fp, "ERROR");
std::fputs("ERROR", fp);
break;
case Fatal:
fprintf(fp, "FATAL ERROR");
std::fputs("FATAL ERROR", fp);
break;
default:
break;
};
fprintf(fp, " %s", modName);
if (sourceInfo)
fprintf(fp, " {%s}", sourceInfo);
if (thrName)
fprintf(fp, " (%s)", thrName);
fprintf(fp, "] ");
std::fprintf(fp, " %s", modName);
if (sourceInfo) {
std::fprintf(fp, " {%s}", sourceInfo);
}
if (thrName) {
std::fprintf(fp, " (%s)", thrName);
}
std::fputs("] ", fp);
}
void report(const char* modName, Level severity, const char* format, va_list ap) {
openFile();
void report(const char* modName, Level severity, fmt::string_view format, fmt::format_args args) override {
openFileIfNeeded();
_reportHead(modName, nullptr, severity);
vfprintf(fp, format, ap);
fprintf(fp, "\n");
closeFile();
fmt::vprint(fp, format, args);
std::fputc('\n', fp);
}
void report(const char* modName, Level severity, const wchar_t* format, va_list ap) {
openFile();
_reportHead(modName, nullptr, severity);
vfwprintf(fp, format, ap);
fprintf(fp, "\n");
closeFile();
}
void reportSource(const char* modName, Level severity, const char* file, unsigned linenum, const char* format,
va_list ap) {
openFile();
char sourceInfo[128];
snprintf(sourceInfo, 128, "%s:%u", file, linenum);
_reportHead(modName, sourceInfo, severity);
vfprintf(fp, format, ap);
fprintf(fp, "\n");
closeFile();
}
void reportSource(const char* modName, Level severity, const char* file, unsigned linenum, const wchar_t* format,
va_list ap) {
openFile();
char sourceInfo[128];
snprintf(sourceInfo, 128, "%s:%u", file, linenum);
_reportHead(modName, sourceInfo, severity);
vfwprintf(fp, format, ap);
fprintf(fp, "\n");
closeFile();
void reportSource(const char* modName, Level severity, const char* file, unsigned linenum, fmt::string_view format,
fmt::format_args args) override {
openFileIfNeeded();
_reportHead(modName, fmt::format(FMT_STRING("{}:{}"), file, linenum).c_str(), severity);
fmt::vprint(fp, format, args);
std::fputc('\n', fp);
}
};
struct FileLogger8 : public FileLogger {
const char* m_filepath;
FileLogger8(const char* filepath) : m_filepath(filepath) {}
void openFile() { fp = fopen(m_filepath, "a"); }
explicit FileLogger8(const char* filepath) : FileLogger(log_typeid(FileLogger8)), m_filepath(filepath) {}
void openFile() override { fp = std::fopen(m_filepath, "a"); }
~FileLogger8() override = default;
};
void RegisterFileLogger(const char* filepath) {
@@ -590,28 +841,4 @@ void RegisterFileLogger(const char* filepath) {
MainLoggers.emplace_back(new FileLogger8(filepath));
}
#if LOG_UCS2
struct FileLogger16 : public FileLogger {
const wchar_t* m_filepath;
FileLogger16(const wchar_t* filepath) : m_filepath(filepath) {}
void openFile() { fp = _wfopen(m_filepath, L"a"); }
};
void RegisterFileLogger(const wchar_t* filepath) {
/* Determine if file logger already added */
for (auto& logger : MainLoggers) {
FileLogger16* filelogger = dynamic_cast<FileLogger16*>(logger.get());
if (filelogger) {
if (!wcscmp(filepath, filelogger->m_filepath))
return;
}
}
/* Otherwise construct new file logger */
MainLoggers.emplace_back(new FileLogger16(filepath));
}
#endif
} // namespace logvisor