diff options
Diffstat (limited to 'mozglue/misc')
82 files changed, 16780 insertions, 0 deletions
diff --git a/mozglue/misc/AutoProfilerLabel.cpp b/mozglue/misc/AutoProfilerLabel.cpp new file mode 100644 index 0000000000..5fd820cc5b --- /dev/null +++ b/mozglue/misc/AutoProfilerLabel.cpp @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/AutoProfilerLabel.h" + +#include "mozilla/Assertions.h" +#include "mozilla/PlatformMutex.h" + +namespace mozilla { + +// RAII class that encapsulates all shared static data, and enforces locking +// when accessing this data. +class MOZ_RAII AutoProfilerLabelData { + public: + AutoProfilerLabelData() { sAPLMutex.Lock(); } + + ~AutoProfilerLabelData() { sAPLMutex.Unlock(); } + + AutoProfilerLabelData(const AutoProfilerLabelData&) = delete; + void operator=(const AutoProfilerLabelData&) = delete; + + const ProfilerLabelEnter& EnterCRef() const { return sEnter; } + ProfilerLabelEnter& EnterRef() { return sEnter; } + + const ProfilerLabelExit& ExitCRef() const { return sExit; } + ProfilerLabelExit& ExitRef() { return sExit; } + + const uint32_t& GenerationCRef() const { return sGeneration; } + uint32_t& GenerationRef() { return sGeneration; } + + static bool RacyIsProfilerPresent() { return !!sGeneration; } + + private: + // Thin shell around mozglue PlatformMutex, for local internal use. + // Does not preserve behavior in JS record/replay. + class Mutex : private mozilla::detail::MutexImpl { + public: + Mutex() = default; + void Lock() { mozilla::detail::MutexImpl::lock(); } + void Unlock() { mozilla::detail::MutexImpl::unlock(); } + }; + + // Mutex protecting access to the following static members. + static Mutex sAPLMutex MOZ_UNANNOTATED; + + static ProfilerLabelEnter sEnter; + static ProfilerLabelExit sExit; + + // Current "generation" of RegisterProfilerLabelEnterExit calls. + static uint32_t sGeneration; +}; + +/* static */ AutoProfilerLabelData::Mutex AutoProfilerLabelData::sAPLMutex; +/* static */ ProfilerLabelEnter AutoProfilerLabelData::sEnter = nullptr; +/* static */ ProfilerLabelExit AutoProfilerLabelData::sExit = nullptr; +/* static */ uint32_t AutoProfilerLabelData::sGeneration = 0; + +void RegisterProfilerLabelEnterExit(ProfilerLabelEnter aEnter, + ProfilerLabelExit aExit) { + MOZ_ASSERT(!aEnter == !aExit, "Must provide both null or both non-null"); + + AutoProfilerLabelData data; + MOZ_ASSERT(!aEnter != !data.EnterRef(), + "Must go from null to non-null, or from non-null to null"); + data.EnterRef() = aEnter; + data.ExitRef() = aExit; + ++data.GenerationRef(); +} + +bool IsProfilerPresent() { + return AutoProfilerLabelData::RacyIsProfilerPresent(); +} + +ProfilerLabel ProfilerLabelBegin(const char* aLabelName, + const char* aDynamicString, void* aSp) { + const AutoProfilerLabelData data; + void* entryContext = (data.EnterCRef()) + ? data.EnterCRef()(aLabelName, aDynamicString, aSp) + : nullptr; + uint32_t generation = data.GenerationCRef(); + + return std::make_tuple(entryContext, generation); +} + +void ProfilerLabelEnd(const ProfilerLabel& aLabel) { + if (!IsValidProfilerLabel(aLabel)) { + return; + } + + const AutoProfilerLabelData data; + if (data.ExitCRef() && (std::get<1>(aLabel) == data.GenerationCRef())) { + data.ExitCRef()(std::get<0>(aLabel)); + } +} + +AutoProfilerLabel::AutoProfilerLabel(const char* aLabel, + const char* aDynamicString) { + std::tie(mEntryContext, mGeneration) = + ProfilerLabelBegin(aLabel, aDynamicString, this); +} + +AutoProfilerLabel::~AutoProfilerLabel() { + ProfilerLabelEnd(std::make_tuple(mEntryContext, mGeneration)); +} + +} // namespace mozilla diff --git a/mozglue/misc/AutoProfilerLabel.h b/mozglue/misc/AutoProfilerLabel.h new file mode 100644 index 0000000000..82299c7b15 --- /dev/null +++ b/mozglue/misc/AutoProfilerLabel.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_AutoProfilerLabel_h +#define mozilla_AutoProfilerLabel_h + +#include "mozilla/Attributes.h" + +#include "mozilla/Types.h" +#include <tuple> + +// The Gecko Profiler defines AutoProfilerLabel, an RAII class for +// pushing/popping frames to/from the ProfilingStack. +// +// This file defines a class of the same name that does much the same thing, +// but which can be used in (and only in) mozglue. A different class is +// necessary because mozglue cannot directly access sProfilingStack. +// +// Note that this class is slightly slower than the other AutoProfilerLabel, +// and it lacks the macro wrappers. It also is effectively hardwired to use +// JS::ProfilingCategory::OTHER as the category pair, because that's what +// the callbacks provided by the profiler use. (Specifying the categories in +// this file would require #including ProfilingCategory.h in mozglue, which we +// don't want to do.) + +namespace mozilla { + +// Enter should return a pointer that will be given to Exit. +typedef void* (*ProfilerLabelEnter)(const char* aLabel, + const char* aDynamicString, void* aSp); +typedef void (*ProfilerLabelExit)(void* EntryContext); + +// Register callbacks that do the entry/exit work involving sProfilingStack. +MFBT_API void RegisterProfilerLabelEnterExit(ProfilerLabelEnter aEnter, + ProfilerLabelExit aExit); + +// This #ifdef prevents this AutoProfilerLabel from being defined in libxul, +// which would conflict with the one in the profiler. +#ifdef IMPL_MFBT + +class MOZ_RAII AutoProfilerLabel { + public: + AutoProfilerLabel(const char* aLabel, const char* aDynamicString); + ~AutoProfilerLabel(); + + private: + void* mEntryContext; + // Number of RegisterProfilerLabelEnterExit calls, to avoid giving an entry + // context from one generation to the next. + uint32_t mGeneration; +}; + +using ProfilerLabel = std::tuple<void*, uint32_t>; + +bool IsProfilerPresent(); +ProfilerLabel ProfilerLabelBegin(const char* aLabelName, + const char* aDynamicString, void* aSp); +void ProfilerLabelEnd(const ProfilerLabel& aLabel); + +inline bool IsValidProfilerLabel(const ProfilerLabel& aLabel) { + return !!std::get<0>(aLabel); +} + +#endif + +} // namespace mozilla + +#endif // mozilla_AutoProfilerLabel_h diff --git a/mozglue/misc/AwakeTimeStamp.cpp b/mozglue/misc/AwakeTimeStamp.cpp new file mode 100644 index 0000000000..a5c8ed4abd --- /dev/null +++ b/mozglue/misc/AwakeTimeStamp.cpp @@ -0,0 +1,93 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AwakeTimeStamp.h" + +#ifdef XP_WIN +# include <windows.h> +#endif + +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" + +namespace mozilla { + +static constexpr uint64_t kUSperS = 1000000; +static constexpr uint64_t kUSperMS = 1000; +#ifndef XP_WIN +static constexpr uint64_t kNSperUS = 1000; +#endif + +double AwakeTimeDuration::ToSeconds() const { + return static_cast<double>(mValueUs) / kUSperS; +} +double AwakeTimeDuration::ToMilliseconds() const { + return static_cast<double>(mValueUs) / kUSperMS; +} +double AwakeTimeDuration::ToMicroseconds() const { + return static_cast<double>(mValueUs); +} + +AwakeTimeDuration AwakeTimeStamp::operator-( + AwakeTimeStamp const& aOther) const { + return AwakeTimeDuration(mValueUs - aOther.mValueUs); +} + +AwakeTimeStamp AwakeTimeStamp::operator+( + const AwakeTimeDuration& aDuration) const { + return AwakeTimeStamp(mValueUs + aDuration.mValueUs); +} + +void AwakeTimeStamp::operator+=(const AwakeTimeDuration& aOther) { + mValueUs += aOther.mValueUs; +} + +void AwakeTimeStamp::operator-=(const AwakeTimeDuration& aOther) { + MOZ_ASSERT(mValueUs >= aOther.mValueUs); + mValueUs -= aOther.mValueUs; +} + +// Apple things +#if defined(__APPLE__) && defined(__MACH__) +# include <time.h> +# include <sys/time.h> +# include <sys/types.h> +# include <mach/mach_time.h> + +AwakeTimeStamp AwakeTimeStamp::NowLoRes() { + return AwakeTimeStamp(clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / kNSperUS); +} + +#elif defined(XP_WIN) + +// Number of hundreds of nanoseconds in a microsecond +static constexpr uint64_t kHNSperUS = 10; + +AwakeTimeStamp AwakeTimeStamp::NowLoRes() { + ULONGLONG interrupt_time; + DebugOnly<bool> rv = QueryUnbiasedInterruptTime(&interrupt_time); + MOZ_ASSERT(rv); + + return AwakeTimeStamp(interrupt_time / kHNSperUS); +} + +#else // Linux and other POSIX but not macOS +# include <time.h> + +uint64_t TimespecToMicroseconds(struct timespec aTs) { + return aTs.tv_sec * kUSperS + aTs.tv_nsec / kNSperUS; +} + +AwakeTimeStamp AwakeTimeStamp::NowLoRes() { + struct timespec ts = {0}; + DebugOnly<int> rv = clock_gettime(CLOCK_MONOTONIC, &ts); + MOZ_ASSERT(!rv); + return AwakeTimeStamp(TimespecToMicroseconds(ts)); +} + +#endif + +}; // namespace mozilla diff --git a/mozglue/misc/AwakeTimeStamp.h b/mozglue/misc/AwakeTimeStamp.h new file mode 100644 index 0000000000..b95ef9e77e --- /dev/null +++ b/mozglue/misc/AwakeTimeStamp.h @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_AwakeTimeStamp_h +#define mozilla_AwakeTimeStamp_h + +#include <stdint.h> +#include <mozilla/Types.h> +#include "mozilla/Assertions.h" + +namespace mozilla { + +class AwakeTimeDuration; + +// Conceptually like mozilla::TimeStamp, but only increments when the device is +// awake, on all platforms, and with a restricted API. +// +// It is always valid, there is no way to acquire an AwakeTimeStamp that is not +// valid, unlike TimeStamp that can be null. +// +// Some arithmetic and ordering operations are supported, when they make sense. +// +// This timestamp shouldn't be considered to be high-resolution, and is suitable +// to measure time from a hundred of milliseconds (because of Windows +// limitations). +class AwakeTimeStamp { + public: + MFBT_API static AwakeTimeStamp NowLoRes(); + MFBT_API void operator+=(const AwakeTimeDuration& aOther); + MFBT_API void operator-=(const AwakeTimeDuration& aOther); + MFBT_API bool operator<(const AwakeTimeStamp& aOther) const { + return mValueUs < aOther.mValueUs; + } + MFBT_API bool operator<=(const AwakeTimeStamp& aOther) const { + return mValueUs <= aOther.mValueUs; + } + MFBT_API bool operator>=(const AwakeTimeStamp& aOther) const { + return mValueUs >= aOther.mValueUs; + } + MFBT_API bool operator>(const AwakeTimeStamp& aOther) const { + return mValueUs > aOther.mValueUs; + } + MFBT_API bool operator==(const AwakeTimeStamp& aOther) const { + return mValueUs == aOther.mValueUs; + } + MFBT_API bool operator!=(const AwakeTimeStamp& aOther) const { + return !(*this == aOther); + } + MFBT_API AwakeTimeDuration operator-(AwakeTimeStamp const& aOther) const; + MFBT_API AwakeTimeStamp operator+(const AwakeTimeDuration& aDuration) const; + + private: + explicit AwakeTimeStamp(uint64_t aValueUs) : mValueUs(aValueUs) {} + + uint64_t mValueUs; +}; + +// A duration, only counting the time the computer was awake. +// +// Can be obtained via subtracting two AwakeTimeStamp, or default-contructed to +// mean a empty duration. +// +// Arithmetic and ordering operations are defined when they make sense. +class AwakeTimeDuration { + public: + MFBT_API AwakeTimeDuration() : mValueUs(0) {} + + MFBT_API double ToSeconds() const; + MFBT_API double ToMilliseconds() const; + MFBT_API double ToMicroseconds() const; + MFBT_API void operator+=(const AwakeTimeDuration& aDuration) { + mValueUs += aDuration.mValueUs; + } + MFBT_API AwakeTimeDuration operator+(const AwakeTimeDuration& aOther) const { + return AwakeTimeDuration(mValueUs + aOther.mValueUs); + } + MFBT_API AwakeTimeDuration operator-(const AwakeTimeDuration& aOther) const { + MOZ_ASSERT(mValueUs >= aOther.mValueUs); + return AwakeTimeDuration(mValueUs - aOther.mValueUs); + } + MFBT_API void operator-=(const AwakeTimeDuration& aOther) { + MOZ_ASSERT(mValueUs >= aOther.mValueUs); + mValueUs -= aOther.mValueUs; + } + MFBT_API bool operator<(const AwakeTimeDuration& aOther) const { + return mValueUs < aOther.mValueUs; + } + MFBT_API bool operator<=(const AwakeTimeDuration& aOther) const { + return mValueUs <= aOther.mValueUs; + } + MFBT_API bool operator>=(const AwakeTimeDuration& aOther) const { + return mValueUs >= aOther.mValueUs; + } + MFBT_API bool operator>(const AwakeTimeDuration& aOther) const { + return mValueUs > aOther.mValueUs; + } + MFBT_API bool operator==(const AwakeTimeDuration& aOther) const { + return mValueUs == aOther.mValueUs; + } + MFBT_API bool operator!=(const AwakeTimeDuration& aOther) const { + return !(*this == aOther); + } + + private: + friend AwakeTimeStamp; + // Not using a default value because we want this private, but allow creating + // duration that are empty. + explicit AwakeTimeDuration(uint64_t aValueUs) : mValueUs(aValueUs) {} + + uint64_t mValueUs; +}; + +}; // namespace mozilla + +#endif // mozilla_AwakeTimeStamp_h diff --git a/mozglue/misc/ConditionVariable_noop.cpp b/mozglue/misc/ConditionVariable_noop.cpp new file mode 100644 index 0000000000..4b28861650 --- /dev/null +++ b/mozglue/misc/ConditionVariable_noop.cpp @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Assertions.h" + +#include "mozilla/PlatformConditionVariable.h" +#include "mozilla/PlatformMutex.h" +#include "MutexPlatformData_noop.h" + +using mozilla::TimeDuration; + +struct mozilla::detail::ConditionVariableImpl::PlatformData {}; + +mozilla::detail::ConditionVariableImpl::ConditionVariableImpl() {} + +mozilla::detail::ConditionVariableImpl::~ConditionVariableImpl() {} + +void mozilla::detail::ConditionVariableImpl::notify_one() {} + +void mozilla::detail::ConditionVariableImpl::notify_all() {} + +void mozilla::detail::ConditionVariableImpl::wait(MutexImpl&) { + // On WASI, there are no threads, so we never wait (either the condvar must + // be ready or there is a deadlock). +} + +mozilla::CVStatus mozilla::detail::ConditionVariableImpl::wait_for( + MutexImpl&, const TimeDuration&) { + return CVStatus::NoTimeout; +} + +mozilla::detail::ConditionVariableImpl::PlatformData* +mozilla::detail::ConditionVariableImpl::platformData() { + static_assert(sizeof platformData_ >= sizeof(PlatformData), + "platformData_ is too small"); + return reinterpret_cast<PlatformData*>(platformData_); +} diff --git a/mozglue/misc/ConditionVariable_posix.cpp b/mozglue/misc/ConditionVariable_posix.cpp new file mode 100644 index 0000000000..5c98560551 --- /dev/null +++ b/mozglue/misc/ConditionVariable_posix.cpp @@ -0,0 +1,159 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Assertions.h" +#include "mozilla/CheckedInt.h" + +#include <errno.h> +#include <pthread.h> +#include <time.h> + +#include "mozilla/PlatformConditionVariable.h" +#include "mozilla/PlatformMutex.h" +#include "MutexPlatformData_posix.h" + +using mozilla::CheckedInt; +using mozilla::TimeDuration; + +static const long NanoSecPerSec = 1000000000; + +// Android 4.4 or earlier & macOS 10.12 has the clock functions, but not +// pthread_condattr_setclock. +#if defined(HAVE_CLOCK_MONOTONIC) && \ + !(defined(__ANDROID__) && __ANDROID_API__ < 21) && !defined(__APPLE__) +# define CV_USE_CLOCK_API +#endif + +#ifdef CV_USE_CLOCK_API +// The C++ specification defines std::condition_variable::wait_for in terms of +// std::chrono::steady_clock, which is closest to CLOCK_MONOTONIC. +static const clockid_t WhichClock = CLOCK_MONOTONIC; + +// While timevaladd is widely available to work with timevals, the newer +// timespec structure is largely lacking such conveniences. Thankfully, the +// utilities available in MFBT make implementing our own quite easy. +static void moz_timespecadd(struct timespec* lhs, struct timespec* rhs, + struct timespec* result) { + // Add nanoseconds. This may wrap, but not above 2 billion. + MOZ_RELEASE_ASSERT(lhs->tv_nsec < NanoSecPerSec); + MOZ_RELEASE_ASSERT(rhs->tv_nsec < NanoSecPerSec); + result->tv_nsec = lhs->tv_nsec + rhs->tv_nsec; + + // Add seconds, checking for overflow in the platform specific time_t type. + CheckedInt<time_t> sec = CheckedInt<time_t>(lhs->tv_sec) + rhs->tv_sec; + + // If nanoseconds overflowed, carry the result over into seconds. + if (result->tv_nsec >= NanoSecPerSec) { + MOZ_RELEASE_ASSERT(result->tv_nsec < 2 * NanoSecPerSec); + result->tv_nsec -= NanoSecPerSec; + sec += 1; + } + + // Extracting the value asserts that there was no overflow. + MOZ_RELEASE_ASSERT(sec.isValid()); + result->tv_sec = sec.value(); +} +#endif + +struct mozilla::detail::ConditionVariableImpl::PlatformData { + pthread_cond_t ptCond; +}; + +mozilla::detail::ConditionVariableImpl::ConditionVariableImpl() { + pthread_cond_t* ptCond = &platformData()->ptCond; + +#ifdef CV_USE_CLOCK_API + pthread_condattr_t attr; + int r0 = pthread_condattr_init(&attr); + MOZ_RELEASE_ASSERT(!r0); + + int r1 = pthread_condattr_setclock(&attr, WhichClock); + MOZ_RELEASE_ASSERT(!r1); + + int r2 = pthread_cond_init(ptCond, &attr); + MOZ_RELEASE_ASSERT(!r2); + + int r3 = pthread_condattr_destroy(&attr); + MOZ_RELEASE_ASSERT(!r3); +#else + int r = pthread_cond_init(ptCond, NULL); + MOZ_RELEASE_ASSERT(!r); +#endif +} + +mozilla::detail::ConditionVariableImpl::~ConditionVariableImpl() { + int r = pthread_cond_destroy(&platformData()->ptCond); + MOZ_RELEASE_ASSERT(r == 0); +} + +void mozilla::detail::ConditionVariableImpl::notify_one() { + int r = pthread_cond_signal(&platformData()->ptCond); + MOZ_RELEASE_ASSERT(r == 0); +} + +void mozilla::detail::ConditionVariableImpl::notify_all() { + int r = pthread_cond_broadcast(&platformData()->ptCond); + MOZ_RELEASE_ASSERT(r == 0); +} + +void mozilla::detail::ConditionVariableImpl::wait(MutexImpl& lock) { + pthread_cond_t* ptCond = &platformData()->ptCond; + pthread_mutex_t* ptMutex = &lock.platformData()->ptMutex; + + int r = pthread_cond_wait(ptCond, ptMutex); + MOZ_RELEASE_ASSERT(r == 0); +} + +mozilla::CVStatus mozilla::detail::ConditionVariableImpl::wait_for( + MutexImpl& lock, const TimeDuration& a_rel_time) { + if (a_rel_time == TimeDuration::Forever()) { + wait(lock); + return CVStatus::NoTimeout; + } + + pthread_cond_t* ptCond = &platformData()->ptCond; + pthread_mutex_t* ptMutex = &lock.platformData()->ptMutex; + int r; + + // Clamp to 0, as time_t is unsigned. + TimeDuration rel_time = a_rel_time < TimeDuration::FromSeconds(0) + ? TimeDuration::FromSeconds(0) + : a_rel_time; + + // Convert the duration to a timespec. + struct timespec rel_ts; + rel_ts.tv_sec = static_cast<time_t>(rel_time.ToSeconds()); + rel_ts.tv_nsec = + static_cast<uint64_t>(rel_time.ToMicroseconds() * 1000.0) % NanoSecPerSec; + +#ifdef CV_USE_CLOCK_API + struct timespec now_ts; + r = clock_gettime(WhichClock, &now_ts); + MOZ_RELEASE_ASSERT(!r); + + struct timespec abs_ts; + moz_timespecadd(&now_ts, &rel_ts, &abs_ts); + + r = pthread_cond_timedwait(ptCond, ptMutex, &abs_ts); +#else + // Our non-clock-supporting platforms, OS X and Android, do support waiting + // on a condition variable with a relative timeout. + r = pthread_cond_timedwait_relative_np(ptCond, ptMutex, &rel_ts); +#endif + + if (r == 0) { + return CVStatus::NoTimeout; + } + MOZ_RELEASE_ASSERT(r == ETIMEDOUT); + return CVStatus::Timeout; +} + +mozilla::detail::ConditionVariableImpl::PlatformData* +mozilla::detail::ConditionVariableImpl::platformData() { + static_assert(sizeof platformData_ >= sizeof(PlatformData), + "platformData_ is too small"); + return reinterpret_cast<PlatformData*>(platformData_); +} diff --git a/mozglue/misc/ConditionVariable_windows.cpp b/mozglue/misc/ConditionVariable_windows.cpp new file mode 100644 index 0000000000..0c0151f1d3 --- /dev/null +++ b/mozglue/misc/ConditionVariable_windows.cpp @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Assertions.h" + +#include <float.h> +#include <intrin.h> +#include <stdlib.h> +#include <windows.h> + +#include "mozilla/PlatformConditionVariable.h" +#include "mozilla/PlatformMutex.h" +#include "MutexPlatformData_windows.h" + +// Some versions of the Windows SDK have a bug where some interlocked functions +// are not redefined as compiler intrinsics. Fix that for the interlocked +// functions that are used in this file. +#if defined(_MSC_VER) && !defined(InterlockedExchangeAdd) +# define InterlockedExchangeAdd(addend, value) \ + _InterlockedExchangeAdd((volatile long*)(addend), (long)(value)) +#endif + +#if defined(_MSC_VER) && !defined(InterlockedIncrement) +# define InterlockedIncrement(addend) \ + _InterlockedIncrement((volatile long*)(addend)) +#endif + +// Wrapper for native condition variable APIs. +struct mozilla::detail::ConditionVariableImpl::PlatformData { + CONDITION_VARIABLE cv_; +}; + +mozilla::detail::ConditionVariableImpl::ConditionVariableImpl() { + InitializeConditionVariable(&platformData()->cv_); +} + +void mozilla::detail::ConditionVariableImpl::notify_one() { + WakeConditionVariable(&platformData()->cv_); +} + +void mozilla::detail::ConditionVariableImpl::notify_all() { + WakeAllConditionVariable(&platformData()->cv_); +} + +void mozilla::detail::ConditionVariableImpl::wait(MutexImpl& lock) { + SRWLOCK* srwlock = &lock.platformData()->lock; + bool r = + SleepConditionVariableSRW(&platformData()->cv_, srwlock, INFINITE, 0); + MOZ_RELEASE_ASSERT(r); +} + +mozilla::CVStatus mozilla::detail::ConditionVariableImpl::wait_for( + MutexImpl& lock, const mozilla::TimeDuration& rel_time) { + if (rel_time == mozilla::TimeDuration::Forever()) { + wait(lock); + return CVStatus::NoTimeout; + } + + SRWLOCK* srwlock = &lock.platformData()->lock; + + // Note that DWORD is unsigned, so we have to be careful to clamp at 0. If + // rel_time is Forever, then ToMilliseconds is +inf, which evaluates as + // greater than UINT32_MAX, resulting in the correct INFINITE wait. We also + // don't want to round sub-millisecond waits to 0, as that wastes energy (see + // bug 1437167 comment 6), so we instead round submillisecond waits to 1ms. + double msecd = rel_time.ToMilliseconds(); + DWORD msec; + if (msecd < 0.0) { + msec = 0; + } else if (msecd > UINT32_MAX) { + msec = INFINITE; + } else { + msec = static_cast<DWORD>(msecd); + // Round submillisecond waits to 1ms. + if (msec == 0 && !rel_time.IsZero()) { + msec = 1; + } + } + + BOOL r = SleepConditionVariableSRW(&platformData()->cv_, srwlock, msec, 0); + if (r) return CVStatus::NoTimeout; + MOZ_RELEASE_ASSERT(GetLastError() == ERROR_TIMEOUT); + return CVStatus::Timeout; +} + +mozilla::detail::ConditionVariableImpl::~ConditionVariableImpl() { + // Native condition variables don't require cleanup. +} + +inline mozilla::detail::ConditionVariableImpl::PlatformData* +mozilla::detail::ConditionVariableImpl::platformData() { + static_assert(sizeof platformData_ >= sizeof(PlatformData), + "platformData_ is too small"); + return reinterpret_cast<PlatformData*>(platformData_); +} diff --git a/mozglue/misc/Debug.cpp b/mozglue/misc/Debug.cpp new file mode 100644 index 0000000000..c3a2ca89e0 --- /dev/null +++ b/mozglue/misc/Debug.cpp @@ -0,0 +1,123 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/glue/Debug.h" +#include "mozilla/Fuzzing.h" +#include "mozilla/Sprintf.h" + +#include <stdarg.h> +#include <stdio.h> + +#ifdef XP_WIN +# include <io.h> +# include <windows.h> +#endif + +#ifdef ANDROID +# include <android/log.h> +#endif + +#ifndef ANDROID +static void vprintf_stderr_buffered(const char* aFmt, va_list aArgs) { + // Avoid interleaving by writing to an on-stack buffer and then writing in one + // go with fputs, as long as the output fits into the buffer. + char buffer[1024]; + va_list argsCpy; + va_copy(argsCpy, aArgs); + int n = VsprintfLiteral(buffer, aFmt, aArgs); + if (n < int(sizeof(buffer))) { + fputs(buffer, stderr); + } else { + // Message too long for buffer. Just print it, not worrying about + // interleaving. (We could malloc, but the underlying write() syscall could + // get interleaved if the output is too big anyway.) + vfprintf(stderr, aFmt, argsCpy); + } + va_end(argsCpy); + fflush(stderr); +} +#endif + +#if defined(XP_WIN) +MFBT_API void vprintf_stderr(const char* aFmt, va_list aArgs) { + if (IsDebuggerPresent()) { + int lengthNeeded = _vscprintf(aFmt, aArgs); + if (lengthNeeded) { + lengthNeeded++; + auto buf = mozilla::MakeUnique<char[]>(lengthNeeded); + if (buf) { + va_list argsCpy; + va_copy(argsCpy, aArgs); + vsnprintf(buf.get(), lengthNeeded, aFmt, argsCpy); + buf[lengthNeeded - 1] = '\0'; + va_end(argsCpy); + OutputDebugStringA(buf.get()); + } + } + } + + vprintf_stderr_buffered(aFmt, aArgs); +} + +#elif defined(ANDROID) +MFBT_API void vprintf_stderr(const char* aFmt, va_list aArgs) { + __android_log_vprint(ANDROID_LOG_INFO, "Gecko", aFmt, aArgs); +} +#elif defined(FUZZING_SNAPSHOT) +MFBT_API void vprintf_stderr(const char* aFmt, va_list aArgs) { + if (nyx_puts) { + auto msgbuf = mozilla::Vsmprintf(aFmt, aArgs); + nyx_puts(msgbuf.get()); + } else { + vprintf_stderr_buffered(aFmt, aArgs); + } +} +#else +MFBT_API void vprintf_stderr(const char* aFmt, va_list aArgs) { + vprintf_stderr_buffered(aFmt, aArgs); +} +#endif + +MFBT_API void printf_stderr(const char* aFmt, ...) { + va_list args; + va_start(args, aFmt); + vprintf_stderr(aFmt, args); + va_end(args); +} + +MFBT_API void fprintf_stderr(FILE* aFile, const char* aFmt, ...) { + va_list args; + va_start(args, aFmt); + if (aFile == stderr) { + vprintf_stderr(aFmt, args); + } else { + vfprintf(aFile, aFmt, args); + } + va_end(args); +} + +MFBT_API void print_stderr(std::stringstream& aStr) { +#if defined(ANDROID) + // On Android logcat output is truncated to 1024 chars per line, and + // we usually use std::stringstream to build up giant multi-line gobs + // of output. So to avoid the truncation we find the newlines and + // print the lines individually. + std::string line; + while (std::getline(aStr, line)) { + printf_stderr("%s\n", line.c_str()); + } +#else + printf_stderr("%s", aStr.str().c_str()); +#endif +} + +MFBT_API void fprint_stderr(FILE* aFile, std::stringstream& aStr) { + if (aFile == stderr) { + print_stderr(aStr); + } else { + fprintf_stderr(aFile, "%s", aStr.str().c_str()); + } +} diff --git a/mozglue/misc/Debug.h b/mozglue/misc/Debug.h new file mode 100644 index 0000000000..43380878b9 --- /dev/null +++ b/mozglue/misc/Debug.h @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_glue_Debug_h +#define mozilla_glue_Debug_h + +#include "mozilla/Attributes.h" // For MOZ_FORMAT_PRINTF +#include "mozilla/Types.h" // For MFBT_API + +#include <cstdarg> +#include <sstream> + +/* This header file intends to supply debugging utilities for use in code + * that cannot use XPCOM debugging facilities like nsDebug.h. + * e.g. mozglue, browser/app + * + * NB: printf_stderr() is in the global namespace, so include this file with + * care; avoid including from header files. + */ + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * printf_stderr(...) is much like fprintf(stderr, ...), except that: + * - on Android and Firefox OS, *instead* of printing to stderr, it + * prints to logcat. (Newlines in the string lead to multiple lines + * of logcat, but each function call implicitly completes a line even + * if the string does not end with a newline.) + * - on Windows, if a debugger is present, it calls OutputDebugString + * in *addition* to writing to stderr + */ +MFBT_API void printf_stderr(const char* aFmt, ...) MOZ_FORMAT_PRINTF(1, 2); + +/** + * Same as printf_stderr, but taking va_list instead of varargs + */ +MFBT_API void vprintf_stderr(const char* aFmt, va_list aArgs) + MOZ_FORMAT_PRINTF(1, 0); + +/** + * fprintf_stderr is like fprintf, except that if its file argument + * is stderr, it invokes printf_stderr instead. + * + * This is useful for general debugging code that logs information to a + * file, but that you would like to be useful on Android and Firefox OS. + * If you use fprintf_stderr instead of fprintf in such debugging code, + * then callers can pass stderr to get logging that works on Android and + * Firefox OS (and also the other side-effects of using printf_stderr). + * + * Code that is structured this way needs to be careful not to split a + * line of output across multiple calls to fprintf_stderr, since doing + * so will cause it to appear in multiple lines in logcat output. + * (Producing multiple lines at once is fine.) + */ +MFBT_API void fprintf_stderr(FILE* aFile, const char* aFmt, ...) + MOZ_FORMAT_PRINTF(2, 3); + +/* + * print_stderr and fprint_stderr are like printf_stderr and fprintf_stderr, + * except they deal with Android logcat line length limitations. They do this + * by printing individual lines out of the provided stringstream using separate + * calls to logcat. + */ +MFBT_API void print_stderr(std::stringstream& aStr); +MFBT_API void fprint_stderr(FILE* aFile, std::stringstream& aStr); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // mozilla_glue_Debug_h diff --git a/mozglue/misc/DynamicallyLinkedFunctionPtr.h b/mozglue/misc/DynamicallyLinkedFunctionPtr.h new file mode 100644 index 0000000000..4313974ec5 --- /dev/null +++ b/mozglue/misc/DynamicallyLinkedFunctionPtr.h @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_DynamicallyLinkedFunctionPtr_h +#define mozilla_DynamicallyLinkedFunctionPtr_h + +#include <windows.h> + +#include <utility> + +#include "mozilla/Attributes.h" + +namespace mozilla { +namespace detail { + +template <typename T> +struct FunctionPtrCracker; + +template <typename R, typename... Args> +struct FunctionPtrCracker<R (*)(Args...)> { + using ReturnT = R; + using FunctionPtrT = R (*)(Args...); +}; + +#if defined(_M_IX86) +template <typename R, typename... Args> +struct FunctionPtrCracker<R(__stdcall*)(Args...)> { + using ReturnT = R; + using FunctionPtrT = R(__stdcall*)(Args...); +}; + +template <typename R, typename... Args> +struct FunctionPtrCracker<R(__fastcall*)(Args...)> { + using ReturnT = R; + using FunctionPtrT = R(__fastcall*)(Args...); +}; +#endif // defined(_M_IX86) + +template <typename T> +class DynamicallyLinkedFunctionPtrBase { + public: + using ReturnT = typename FunctionPtrCracker<T>::ReturnT; + using FunctionPtrT = typename FunctionPtrCracker<T>::FunctionPtrT; + + DynamicallyLinkedFunctionPtrBase(const wchar_t* aLibName, + const char* aFuncName) + : mModule(::LoadLibraryW(aLibName)), mFunction(nullptr) { + if (!mModule) { + return; + } + + mFunction = + reinterpret_cast<FunctionPtrT>(::GetProcAddress(mModule, aFuncName)); + + if (!mFunction) { + // Since the function doesn't exist, there is no point in holding a + // reference to mModule anymore. + ::FreeLibrary(mModule); + mModule = nullptr; + } + } + + DynamicallyLinkedFunctionPtrBase(const DynamicallyLinkedFunctionPtrBase&) = + delete; + DynamicallyLinkedFunctionPtrBase& operator=( + const DynamicallyLinkedFunctionPtrBase&) = delete; + + DynamicallyLinkedFunctionPtrBase(DynamicallyLinkedFunctionPtrBase&&) = delete; + DynamicallyLinkedFunctionPtrBase& operator=( + DynamicallyLinkedFunctionPtrBase&&) = delete; + + template <typename... Args> + ReturnT operator()(Args&&... args) const { + return mFunction(std::forward<Args>(args)...); + } + + explicit operator bool() const { return !!mFunction; } + + protected: + HMODULE mModule; + FunctionPtrT mFunction; +}; + +} // namespace detail + +/** + * In most cases, this class is the one that you want to use for resolving a + * dynamically-linked function pointer. It should be instantiated as a static + * local variable. + * + * NB: It has a trivial destructor, so the DLL that is loaded is never freed. + * Assuming that this function is called fairly often, this is the most + * sensible option. OTOH, if the function you are calling is a one-off, or the + * static local requirement is too restrictive, use DynamicallyLinkedFunctionPtr + * instead. + */ +template <typename T> +class MOZ_STATIC_LOCAL_CLASS StaticDynamicallyLinkedFunctionPtr final + : public detail::DynamicallyLinkedFunctionPtrBase<T> { + public: + StaticDynamicallyLinkedFunctionPtr(const wchar_t* aLibName, + const char* aFuncName) + : detail::DynamicallyLinkedFunctionPtrBase<T>(aLibName, aFuncName) {} + + /** + * We only offer this operator for the static local case, as it is not + * possible for this object to be destroyed while the returned pointer is + * being held. + */ + operator typename detail::DynamicallyLinkedFunctionPtrBase<T>::FunctionPtrT() + const { + return this->mFunction; + } +}; + +template <typename T> +class MOZ_NON_PARAM MOZ_NON_TEMPORARY_CLASS DynamicallyLinkedFunctionPtr final + : public detail::DynamicallyLinkedFunctionPtrBase<T> { + public: + DynamicallyLinkedFunctionPtr(const wchar_t* aLibName, const char* aFuncName) + : detail::DynamicallyLinkedFunctionPtrBase<T>(aLibName, aFuncName) {} + + ~DynamicallyLinkedFunctionPtr() { + if (!this->mModule) { + return; + } + + ::FreeLibrary(this->mModule); + } +}; + +} // namespace mozilla + +#endif // mozilla_DynamicallyLinkedFunctionPtr_h diff --git a/mozglue/misc/GetKnownFolderPath.cpp b/mozglue/misc/GetKnownFolderPath.cpp new file mode 100644 index 0000000000..676a1c8056 --- /dev/null +++ b/mozglue/misc/GetKnownFolderPath.cpp @@ -0,0 +1,33 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GetKnownFolderPath.h" + +namespace mozilla { + +UniquePtr<wchar_t, LoadedCoTaskMemFreeDeleter> GetKnownFolderPath( + REFKNOWNFOLDERID folderId) { + static decltype(SHGetKnownFolderPath)* shGetKnownFolderPath = nullptr; + if (!shGetKnownFolderPath) { + // We could go out of our way to `FreeLibrary` on this, decrementing its + // ref count and potentially unloading it. However doing so would be either + // effectively a no-op, or counterproductive. Just let it get cleaned up + // when the process is terminated, because we're going to load it anyway + // elsewhere. + HMODULE shell32Dll = ::LoadLibraryW(L"shell32"); + if (!shell32Dll) { + return nullptr; + } + shGetKnownFolderPath = reinterpret_cast<decltype(shGetKnownFolderPath)>( + ::GetProcAddress(shell32Dll, "SHGetKnownFolderPath")); + if (!shGetKnownFolderPath) { + return nullptr; + } + } + PWSTR path = nullptr; + shGetKnownFolderPath(folderId, 0, nullptr, &path); + return UniquePtr<wchar_t, LoadedCoTaskMemFreeDeleter>(path); +} + +} // namespace mozilla diff --git a/mozglue/misc/GetKnownFolderPath.h b/mozglue/misc/GetKnownFolderPath.h new file mode 100644 index 0000000000..3fd80440ab --- /dev/null +++ b/mozglue/misc/GetKnownFolderPath.h @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_GetKnownFolderPath_h +#define mozilla_GetKnownFolderPath_h + +#include <windows.h> +#include <objbase.h> +#include <shlobj.h> + +#include "mozilla/glue/Debug.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { + +struct LoadedCoTaskMemFreeDeleter { + void operator()(void* ptr) { + static decltype(CoTaskMemFree)* coTaskMemFree = nullptr; + if (!coTaskMemFree) { + // Just let this get cleaned up when the process is terminated, because + // we're going to load it anyway elsewhere. + HMODULE ole32Dll = ::LoadLibraryW(L"ole32"); + if (!ole32Dll) { + printf_stderr( + "Could not load ole32 - will not free with CoTaskMemFree"); + return; + } + coTaskMemFree = reinterpret_cast<decltype(coTaskMemFree)>( + ::GetProcAddress(ole32Dll, "CoTaskMemFree")); + if (!coTaskMemFree) { + printf_stderr("Could not find CoTaskMemFree"); + return; + } + } + coTaskMemFree(ptr); + } +}; + +UniquePtr<wchar_t, LoadedCoTaskMemFreeDeleter> GetKnownFolderPath( + REFKNOWNFOLDERID folderId); + +} // namespace mozilla + +#endif diff --git a/mozglue/misc/ImportDir.h b/mozglue/misc/ImportDir.h new file mode 100644 index 0000000000..6f7721d966 --- /dev/null +++ b/mozglue/misc/ImportDir.h @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "mozilla/NativeNt.h" +#include "mozilla/WinHeaderOnlyUtils.h" + +namespace mozilla { +namespace detail { + +inline LauncherResult<nt::DataDirectoryEntry> GetImageDirectoryViaFileIo( + const nsAutoHandle& aImageFile, const uint32_t aOurImportDirectoryRva) { + OVERLAPPED ov = {}; + ov.Offset = aOurImportDirectoryRva; + + DWORD bytesRead; + nt::DataDirectoryEntry result; + if (!::ReadFile(aImageFile, &result, sizeof(result), &bytesRead, &ov) || + bytesRead != sizeof(result)) { + return LAUNCHER_ERROR_FROM_LAST(); + } + + return result; +} + +} // namespace detail + +/** + * This function ensures that the import directory of a loaded binary image + * matches the version that is found in the original file on disk. We do this + * to prevent tampering by third-party code. + * + * Yes, this function may perform file I/O on the critical path during + * startup. A mitigating factor here is that this function must be called + * immediately after creating a process using the image specified by + * |aFullImagePath|; by this point, the system has already paid the price of + * pulling the image file's contents into the page cache. + * + * @param aFullImagePath Wide-character string containing the absolute path + * to the binary whose import directory we are touching. + * @param aTransferMgr Encapsulating the transfer from the current process to + * the child process whose import table we are touching. + */ +inline LauncherVoidResult RestoreImportDirectory( + const wchar_t* aFullImagePath, nt::CrossExecTransferManager& aTransferMgr) { + uint32_t importDirEntryRva; + PIMAGE_DATA_DIRECTORY importDirEntry = + aTransferMgr.LocalPEHeaders().GetImageDirectoryEntryPtr( + IMAGE_DIRECTORY_ENTRY_IMPORT, &importDirEntryRva); + if (!importDirEntry) { + return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT); + } + + nsAutoHandle file(::CreateFileW(aFullImagePath, GENERIC_READ, FILE_SHARE_READ, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, + nullptr)); + if (file.get() == INVALID_HANDLE_VALUE) { + return LAUNCHER_ERROR_FROM_LAST(); + } + + // Why do we use file I/O here instead of a memory mapping? The simple reason + // is that we do not want any kernel-mode drivers to start tampering with file + // contents under the belief that the file is being mapped for execution. + // Windows 8 supports creation of file mappings using the SEC_IMAGE_NO_EXECUTE + // flag, which may help to mitigate this, but we might as well just support + // a single implementation that works everywhere. + LauncherResult<nt::DataDirectoryEntry> realImportDirectory = + detail::GetImageDirectoryViaFileIo(file, importDirEntryRva); + if (realImportDirectory.isErr()) { + return realImportDirectory.propagateErr(); + } + + nt::DataDirectoryEntry toWrite = realImportDirectory.unwrap(); + + { // Scope for prot + AutoVirtualProtect prot = aTransferMgr.Protect( + importDirEntry, sizeof(IMAGE_DATA_DIRECTORY), PAGE_READWRITE); + if (!prot) { + return LAUNCHER_ERROR_FROM_MOZ_WINDOWS_ERROR(prot.GetError()); + } + + LauncherVoidResult writeResult = aTransferMgr.Transfer( + importDirEntry, &toWrite, sizeof(IMAGE_DATA_DIRECTORY)); + if (writeResult.isErr()) { + return writeResult.propagateErr(); + } + } + + return Ok(); +} + +} // namespace mozilla diff --git a/mozglue/misc/IntegerPrintfMacros.h b/mozglue/misc/IntegerPrintfMacros.h new file mode 100644 index 0000000000..acf33eb85c --- /dev/null +++ b/mozglue/misc/IntegerPrintfMacros.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implements the C99 <inttypes.h> interface. */ + +#ifndef mozilla_IntegerPrintfMacros_h_ +#define mozilla_IntegerPrintfMacros_h_ + +/* + * These macros should not be used with the NSPR printf-like functions or their + * users. If you need to use NSPR's facilities, see the comment on + * supported formats at the top of nsprpub/pr/include/prprf.h. + */ + +/* + * scanf is a footgun: if the input number exceeds the bounds of the target + * type, behavior is undefined (in the compiler sense: that is, this code + * could overwrite your hard drive with zeroes): + * + * uint8_t u; + * sscanf("256", "%" SCNu8, &u); // BAD + * + * For this reason, *never* use the SCN* macros provided by this header! + */ + +#include <inttypes.h> + +/* + * Fix up Android's broken [u]intptr_t inttype macros. Android's PRI*PTR + * macros are defined as "ld", but sizeof(long) is 8 and sizeof(intptr_t) + * is 4 on 32-bit Android. TestTypeTraits.cpp asserts that these new macro + * definitions match the actual type sizes seen at compile time. + */ +#if defined(ANDROID) && !defined(__LP64__) +# undef PRIdPTR /* intptr_t */ +# define PRIdPTR "d" /* intptr_t */ +# undef PRIiPTR /* intptr_t */ +# define PRIiPTR "i" /* intptr_t */ +# undef PRIoPTR /* uintptr_t */ +# define PRIoPTR "o" /* uintptr_t */ +# undef PRIuPTR /* uintptr_t */ +# define PRIuPTR "u" /* uintptr_t */ +# undef PRIxPTR /* uintptr_t */ +# define PRIxPTR "x" /* uintptr_t */ +# undef PRIXPTR /* uintptr_t */ +# define PRIXPTR "X" /* uintptr_t */ +#endif + +/* + * Fix up Android's broken macros for [u]int_fastN_t. On ARM64, Android's + * PRI*FAST16/32 macros are defined as "d", but the types themselves are defined + * as long and unsigned long. + */ +#if defined(ANDROID) && defined(__LP64__) +# undef PRIdFAST16 /* int_fast16_t */ +# define PRIdFAST16 PRId64 /* int_fast16_t */ +# undef PRIiFAST16 /* int_fast16_t */ +# define PRIiFAST16 PRIi64 /* int_fast16_t */ +# undef PRIoFAST16 /* uint_fast16_t */ +# define PRIoFAST16 PRIo64 /* uint_fast16_t */ +# undef PRIuFAST16 /* uint_fast16_t */ +# define PRIuFAST16 PRIu64 /* uint_fast16_t */ +# undef PRIxFAST16 /* uint_fast16_t */ +# define PRIxFAST16 PRIx64 /* uint_fast16_t */ +# undef PRIXFAST16 /* uint_fast16_t */ +# define PRIXFAST16 PRIX64 /* uint_fast16_t */ +# undef PRIdFAST32 /* int_fast32_t */ +# define PRIdFAST32 PRId64 /* int_fast32_t */ +# undef PRIiFAST32 /* int_fast32_t */ +# define PRIiFAST32 PRIi64 /* int_fast32_t */ +# undef PRIoFAST32 /* uint_fast32_t */ +# define PRIoFAST32 PRIo64 /* uint_fast32_t */ +# undef PRIuFAST32 /* uint_fast32_t */ +# define PRIuFAST32 PRIu64 /* uint_fast32_t */ +# undef PRIxFAST32 /* uint_fast32_t */ +# define PRIxFAST32 PRIx64 /* uint_fast32_t */ +# undef PRIXFAST32 /* uint_fast32_t */ +# define PRIXFAST32 PRIX64 /* uint_fast32_t */ +#endif + +#endif /* mozilla_IntegerPrintfMacros_h_ */ diff --git a/mozglue/misc/MmapFaultHandler.cpp b/mozglue/misc/MmapFaultHandler.cpp new file mode 100644 index 0000000000..a73b9bd24b --- /dev/null +++ b/mozglue/misc/MmapFaultHandler.cpp @@ -0,0 +1,130 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MmapFaultHandler.h" + +#if defined(XP_UNIX) && !defined(XP_DARWIN) && !defined(__wasi__) + +# include "mozilla/Assertions.h" +# include "mozilla/Atomics.h" +# include "mozilla/ThreadLocal.h" +# include <signal.h> +# include <cstring> + +static MOZ_THREAD_LOCAL(MmapAccessScope*) sMmapAccessScope; + +static struct sigaction sPrevSIGBUSHandler; + +static void MmapSIGBUSHandler(int signum, siginfo_t* info, void* context) { + MOZ_RELEASE_ASSERT(signum == SIGBUS); + + MmapAccessScope* mas = sMmapAccessScope.get(); + + if (mas && mas->IsInsideBuffer(info->si_addr)) { + // Temporarily instead of handling the signal, we crash intentionally and + // send some diagnostic information to find out why the signal is received. + mas->CrashWithInfo(info->si_addr); + + // The address is inside the buffer, handle the failure. + siglongjmp(mas->mJmpBuf, signum); + } + + // This signal is not caused by accessing region protected by MmapAccessScope. + // Forward the signal to the next handler. + if (sPrevSIGBUSHandler.sa_flags & SA_SIGINFO) { + sPrevSIGBUSHandler.sa_sigaction(signum, info, context); + } else if (sPrevSIGBUSHandler.sa_handler == SIG_DFL || + sPrevSIGBUSHandler.sa_handler == SIG_IGN) { + // There is no next handler. Uninstalling our handler and returning will + // cause a crash. + sigaction(signum, &sPrevSIGBUSHandler, nullptr); + } else { + sPrevSIGBUSHandler.sa_handler(signum); + } +} + +mozilla::Atomic<bool> gSIGBUSHandlerInstalled(false); +mozilla::Atomic<bool> gSIGBUSHandlerInstalling(false); + +void InstallMmapFaultHandler() { + // This function is called from MmapAccessScope's constructor because there is + // no single point where we could install the handler during startup. This + // means that it's called quite often, so to minimize using of the mutex we + // first check the atomic variable outside the lock. + if (gSIGBUSHandlerInstalled) { + return; + } + + if (gSIGBUSHandlerInstalling.compareExchange(false, true)) { + sMmapAccessScope.infallibleInit(); + + struct sigaction busHandler; + busHandler.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK; + busHandler.sa_sigaction = MmapSIGBUSHandler; + sigemptyset(&busHandler.sa_mask); + if (sigaction(SIGBUS, &busHandler, &sPrevSIGBUSHandler)) { + MOZ_CRASH("Unable to install SIGBUS handler"); + } + + MOZ_ASSERT(!gSIGBUSHandlerInstalled); + gSIGBUSHandlerInstalled = true; + } else { + // Just spin lock here. It should not take a substantial amount + // of time, so a mutex would likely be a spin lock anyway, and + // this avoids the need to new up a static mutex from within + // mozglue/misc, which complicates things with + // check_vanilla_allocations.py + while (!gSIGBUSHandlerInstalled) { + } + } +} + +MmapAccessScope::MmapAccessScope(void* aBuf, uint32_t aBufLen, + const char* aFilename) { + // Install signal handler if it wasn't installed yet. + InstallMmapFaultHandler(); + + // We'll handle the signal only if the crashing address is inside this buffer. + mBuf = aBuf; + mBufLen = aBufLen; + mFilename = aFilename; + + SetThreadLocalScope(); +} + +MmapAccessScope::~MmapAccessScope() { + MOZ_RELEASE_ASSERT(sMmapAccessScope.get() == this); + sMmapAccessScope.set(mPreviousScope); +} + +void MmapAccessScope::SetThreadLocalScope() { + // mJmpBuf is set outside of this classs for reasons mentioned in the header + // file, but we need to initialize the member here too to make Coverity happy. + memset(mJmpBuf, 0, sizeof(sigjmp_buf)); + + // If MmapAccessScopes are nested, save the previous one and restore it in + // the destructor. + mPreviousScope = sMmapAccessScope.get(); + + // MmapAccessScope is now set up (except mJmpBuf for reasons mentioned in the + // header file). Store the pointer in a thread-local variable sMmapAccessScope + // so we can use it in the handler if the signal is triggered. + sMmapAccessScope.set(this); +} + +bool MmapAccessScope::IsInsideBuffer(void* aPtr) { + return aPtr >= mBuf && aPtr < (void*)((char*)mBuf + mBufLen); +} + +void MmapAccessScope::CrashWithInfo(void* aPtr) { + // All we have is the buffer and the crashing address. + MOZ_CRASH_UNSAFE_PRINTF( + "SIGBUS received when accessing mmaped file [buffer=%p, " + "buflen=%u, address=%p, filename=%s]", + mBuf, mBufLen, aPtr, mFilename); +} + +#endif diff --git a/mozglue/misc/MmapFaultHandler.h b/mozglue/misc/MmapFaultHandler.h new file mode 100644 index 0000000000..fca356921c --- /dev/null +++ b/mozglue/misc/MmapFaultHandler.h @@ -0,0 +1,113 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MmapFaultHandler_h_ +#define MmapFaultHandler_h_ + +#if defined(XP_WIN) +// Windows + +# ifdef HAVE_SEH_EXCEPTIONS +# define MMAP_FAULT_HANDLER_BEGIN_HANDLE(fd) __try { +# define MMAP_FAULT_HANDLER_BEGIN_BUFFER(buf, bufLen) __try { +# define MMAP_FAULT_HANDLER_CATCH(retval) \ + } \ + __except (GetExceptionCode() == EXCEPTION_IN_PAGE_ERROR \ + ? EXCEPTION_EXECUTE_HANDLER \ + : EXCEPTION_CONTINUE_SEARCH) { \ + NS_WARNING("unexpected EXCEPTION_IN_PAGE_ERROR"); \ + return retval; \ + } +# else +# define MMAP_FAULT_HANDLER_BEGIN_HANDLE(fd) { +# define MMAP_FAULT_HANDLER_BEGIN_BUFFER(buf, bufLen) { +# define MMAP_FAULT_HANDLER_CATCH(retval) } +# endif + +#elif defined(XP_DARWIN) +// MacOS + +# define MMAP_FAULT_HANDLER_BEGIN_HANDLE(fd) { +# define MMAP_FAULT_HANDLER_BEGIN_BUFFER(buf, bufLen) { +# define MMAP_FAULT_HANDLER_CATCH(retval) } + +#elif defined(__wasi__) + +# define MMAP_FAULT_HANDLER_BEGIN_HANDLE(fd) { +# define MMAP_FAULT_HANDLER_BEGIN_BUFFER(buf, bufLen) { +# define MMAP_FAULT_HANDLER_CATCH(retval) } + +#else +// Linux + +# include "mozilla/Attributes.h" +# include "mozilla/Types.h" +# include <stdint.h> +# include <setjmp.h> + +class MOZ_RAII MmapAccessScope { + public: + MFBT_API MmapAccessScope(void* aBuf, uint32_t aBufLen, + const char* aFilename = nullptr); + MFBT_API ~MmapAccessScope(); + + MmapAccessScope(const MmapAccessScope&) = delete; + MmapAccessScope& operator=(const MmapAccessScope&) = delete; + + void SetThreadLocalScope(); + bool IsInsideBuffer(void* aPtr); + void CrashWithInfo(void* aPtr); + + // sigsetjmp cannot be called from a method that returns before calling + // siglongjmp, so the macro must call sigsetjmp directly and mJmpBuf must be + // public. + sigjmp_buf mJmpBuf; + + private: + void* mBuf; + const char* mFilename; + uint32_t mBufLen; + MmapAccessScope* mPreviousScope; +}; + +// Gets around warnings for null-checking in a macro. +template <typename T> +inline bool ValidFD(T fd) { + return !!fd; +} + +# define MMAP_FAULT_HANDLER_BEGIN_HANDLE(fd) \ + { \ + void* mmapScopeBuf = nullptr; \ + nsCString mmapScopeFilename; \ + uint32_t mmapScopeBufLen = 0; \ + if (ValidFD(fd) && fd->mMap) { \ + mmapScopeBuf = (void*)fd->mFileStart; \ + mmapScopeBufLen = fd->mTotalLen; \ + } \ + if (ValidFD(fd) && fd->mFile) { \ + nsCOMPtr<nsIFile> file = fd->mFile.GetBaseFile(); \ + if (file) { \ + file->GetNativeLeafName(mmapScopeFilename); \ + } \ + } \ + MmapAccessScope mmapScope(mmapScopeBuf, mmapScopeBufLen, \ + mmapScopeFilename.get()); \ + if (sigsetjmp(mmapScope.mJmpBuf, 0) == 0) { +# define MMAP_FAULT_HANDLER_BEGIN_BUFFER(buf, bufLen) \ + { \ + MmapAccessScope mmapScope((void*)(buf), (bufLen)); \ + if (sigsetjmp(mmapScope.mJmpBuf, 0) == 0) { +# define MMAP_FAULT_HANDLER_CATCH(retval) \ + } \ + else { \ + NS_WARNING("SIGBUS received when accessing mmapped file"); \ + return retval; \ + } \ + } + +#endif + +#endif diff --git a/mozglue/misc/MutexPlatformData_noop.h b/mozglue/misc/MutexPlatformData_noop.h new file mode 100644 index 0000000000..a2c8d060be --- /dev/null +++ b/mozglue/misc/MutexPlatformData_noop.h @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MutexPlatformData_noop_h +#define MutexPlatformData_noop_h + +#if !defined(__wasi__) +# error This code is for WASI only. +#endif + +#include "mozilla/PlatformMutex.h" + +struct mozilla::detail::MutexImpl::PlatformData {}; + +#endif // MutexPlatformData_noop_h diff --git a/mozglue/misc/MutexPlatformData_posix.h b/mozglue/misc/MutexPlatformData_posix.h new file mode 100644 index 0000000000..d1659d8d7b --- /dev/null +++ b/mozglue/misc/MutexPlatformData_posix.h @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MutexPlatformData_posix_h +#define MutexPlatformData_posix_h + +#include <pthread.h> + +#include "mozilla/PlatformMutex.h" + +struct mozilla::detail::MutexImpl::PlatformData { + pthread_mutex_t ptMutex; +}; + +#endif // MutexPlatformData_posix_h diff --git a/mozglue/misc/MutexPlatformData_windows.h b/mozglue/misc/MutexPlatformData_windows.h new file mode 100644 index 0000000000..489f921115 --- /dev/null +++ b/mozglue/misc/MutexPlatformData_windows.h @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MutexPlatformData_windows_h +#define MutexPlatformData_windows_h + +#include <windows.h> + +#include "mozilla/PlatformMutex.h" + +struct mozilla::detail::MutexImpl::PlatformData { + SRWLOCK lock; +}; + +#endif // MutexPlatformData_windows_h diff --git a/mozglue/misc/Mutex_noop.cpp b/mozglue/misc/Mutex_noop.cpp new file mode 100644 index 0000000000..eaa7959cc0 --- /dev/null +++ b/mozglue/misc/Mutex_noop.cpp @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Assertions.h" + +#include <errno.h> +#include <stdio.h> + +#include "mozilla/PlatformMutex.h" +#include "MutexPlatformData_noop.h" + +mozilla::detail::MutexImpl::MutexImpl() {} + +mozilla::detail::MutexImpl::~MutexImpl() {} + +inline void mozilla::detail::MutexImpl::mutexLock() {} + +bool mozilla::detail::MutexImpl::tryLock() { return mutexTryLock(); } + +bool mozilla::detail::MutexImpl::mutexTryLock() { return true; } + +void mozilla::detail::MutexImpl::lock() { mutexLock(); } + +void mozilla::detail::MutexImpl::unlock() {} + +mozilla::detail::MutexImpl::PlatformData* +mozilla::detail::MutexImpl::platformData() { + static_assert(sizeof(platformData_) >= sizeof(PlatformData), + "platformData_ is too small"); + return reinterpret_cast<PlatformData*>(platformData_); +} diff --git a/mozglue/misc/Mutex_posix.cpp b/mozglue/misc/Mutex_posix.cpp new file mode 100644 index 0000000000..e4cbabd5e5 --- /dev/null +++ b/mozglue/misc/Mutex_posix.cpp @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Assertions.h" + +#include <errno.h> +#include <pthread.h> +#include <stdio.h> + +#if defined(XP_DARWIN) +# include <pthread_spis.h> +#endif + +#include "mozilla/PlatformMutex.h" +#include "MutexPlatformData_posix.h" + +#define REPORT_PTHREADS_ERROR(result, msg) \ + { \ + errno = result; \ + perror(msg); \ + MOZ_CRASH(msg); \ + } + +#define TRY_CALL_PTHREADS(call, msg) \ + { \ + int result = (call); \ + if (result != 0) { \ + REPORT_PTHREADS_ERROR(result, msg); \ + } \ + } + +mozilla::detail::MutexImpl::MutexImpl() { + pthread_mutexattr_t* attrp = nullptr; + +#if defined(DEBUG) +# define MUTEX_KIND PTHREAD_MUTEX_ERRORCHECK +// Linux with glibc, FreeBSD and macOS 10.14+ support adaptive mutexes that +// spin for a short number of tries before sleeping. NSPR's locks did this, +// too, and it seems like a reasonable thing to do. +#elif (defined(__linux__) && defined(__GLIBC__)) || defined(__FreeBSD__) +# define MUTEX_KIND PTHREAD_MUTEX_ADAPTIVE_NP +#elif defined(XP_DARWIN) +# if defined(PTHREAD_MUTEX_POLICY_FIRSTFIT_NP) +# define POLICY_KIND PTHREAD_MUTEX_POLICY_FIRSTFIT_NP +# else +# define POLICY_KIND (3) // The definition is missing in old SDKs +# endif +#endif + +#if defined(MUTEX_KIND) || defined(POLICY_KIND) +# define ATTR_REQUIRED +#endif + +#if defined(ATTR_REQUIRED) + pthread_mutexattr_t attr; + + TRY_CALL_PTHREADS( + pthread_mutexattr_init(&attr), + "mozilla::detail::MutexImpl::MutexImpl: pthread_mutexattr_init failed"); + +# if defined(MUTEX_KIND) + TRY_CALL_PTHREADS(pthread_mutexattr_settype(&attr, MUTEX_KIND), + "mozilla::detail::MutexImpl::MutexImpl: " + "pthread_mutexattr_settype failed"); +# elif defined(POLICY_KIND) + TRY_CALL_PTHREADS(pthread_mutexattr_setpolicy_np(&attr, POLICY_KIND), + "mozilla::detail::MutexImpl::MutexImpl: " + "pthread_mutexattr_setpolicy_np failed"); +# endif + attrp = &attr; +#endif + + TRY_CALL_PTHREADS( + pthread_mutex_init(&platformData()->ptMutex, attrp), + "mozilla::detail::MutexImpl::MutexImpl: pthread_mutex_init failed"); + +#if defined(ATTR_REQUIRED) + TRY_CALL_PTHREADS(pthread_mutexattr_destroy(&attr), + "mozilla::detail::MutexImpl::MutexImpl: " + "pthread_mutexattr_destroy failed"); +#endif +} + +mozilla::detail::MutexImpl::~MutexImpl() { + TRY_CALL_PTHREADS( + pthread_mutex_destroy(&platformData()->ptMutex), + "mozilla::detail::MutexImpl::~MutexImpl: pthread_mutex_destroy failed"); +} + +inline void mozilla::detail::MutexImpl::mutexLock() { + TRY_CALL_PTHREADS( + pthread_mutex_lock(&platformData()->ptMutex), + "mozilla::detail::MutexImpl::mutexLock: pthread_mutex_lock failed"); +} + +bool mozilla::detail::MutexImpl::tryLock() { return mutexTryLock(); } + +bool mozilla::detail::MutexImpl::mutexTryLock() { + int result = pthread_mutex_trylock(&platformData()->ptMutex); + if (result == 0) { + return true; + } + + if (result == EBUSY) { + return false; + } + + REPORT_PTHREADS_ERROR( + result, + "mozilla::detail::MutexImpl::mutexTryLock: pthread_mutex_trylock failed"); +} + +void mozilla::detail::MutexImpl::lock() { mutexLock(); } + +void mozilla::detail::MutexImpl::unlock() { + TRY_CALL_PTHREADS( + pthread_mutex_unlock(&platformData()->ptMutex), + "mozilla::detail::MutexImpl::unlock: pthread_mutex_unlock failed"); +} + +#undef TRY_CALL_PTHREADS + +mozilla::detail::MutexImpl::PlatformData* +mozilla::detail::MutexImpl::platformData() { + static_assert(sizeof(platformData_) >= sizeof(PlatformData), + "platformData_ is too small"); + return reinterpret_cast<PlatformData*>(platformData_); +} diff --git a/mozglue/misc/Mutex_windows.cpp b/mozglue/misc/Mutex_windows.cpp new file mode 100644 index 0000000000..c4c78fd4f6 --- /dev/null +++ b/mozglue/misc/Mutex_windows.cpp @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/PlatformMutex.h" + +#include <windows.h> + +#include "MutexPlatformData_windows.h" + +mozilla::detail::MutexImpl::MutexImpl() { + InitializeSRWLock(&platformData()->lock); +} + +mozilla::detail::MutexImpl::~MutexImpl() {} + +void mozilla::detail::MutexImpl::lock() { + AcquireSRWLockExclusive(&platformData()->lock); +} + +bool mozilla::detail::MutexImpl::tryLock() { return mutexTryLock(); } + +bool mozilla::detail::MutexImpl::mutexTryLock() { + return !!TryAcquireSRWLockExclusive(&platformData()->lock); +} + +void mozilla::detail::MutexImpl::unlock() { + ReleaseSRWLockExclusive(&platformData()->lock); +} + +mozilla::detail::MutexImpl::PlatformData* +mozilla::detail::MutexImpl::platformData() { + static_assert(sizeof(platformData_) >= sizeof(PlatformData), + "platformData_ is too small"); + return reinterpret_cast<PlatformData*>(platformData_); +} diff --git a/mozglue/misc/NativeNt.h b/mozglue/misc/NativeNt.h new file mode 100644 index 0000000000..932dcd0a7b --- /dev/null +++ b/mozglue/misc/NativeNt.h @@ -0,0 +1,1758 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_NativeNt_h +#define mozilla_NativeNt_h + +#include <stdint.h> +#include <windows.h> +#include <winnt.h> +#include <winternl.h> + +#include <algorithm> +#include <utility> + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Maybe.h" +#include "mozilla/Range.h" +#include "mozilla/Span.h" +#include "mozilla/WinHeaderOnlyUtils.h" +#include "mozilla/interceptor/MMPolicies.h" +#include "mozilla/interceptor/TargetFunction.h" + +#if defined(MOZILLA_INTERNAL_API) +# include "nsString.h" +#endif // defined(MOZILLA_INTERNAL_API) + +// The declarations within this #if block are intended to be used for initial +// process initialization ONLY. You probably don't want to be using these in +// normal Gecko code! +#if !defined(MOZILLA_INTERNAL_API) + +extern "C" { + +# if !defined(STATUS_ACCESS_DENIED) +# define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L) +# endif // !defined(STATUS_ACCESS_DENIED) + +# if !defined(STATUS_DLL_NOT_FOUND) +# define STATUS_DLL_NOT_FOUND ((NTSTATUS)0xC0000135L) +# endif // !defined(STATUS_DLL_NOT_FOUND) + +# if !defined(STATUS_UNSUCCESSFUL) +# define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L) +# endif // !defined(STATUS_UNSUCCESSFUL) + +# if !defined(STATUS_INFO_LENGTH_MISMATCH) +# define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L) +# endif + +enum SECTION_INHERIT { ViewShare = 1, ViewUnmap = 2 }; + +NTSTATUS NTAPI NtMapViewOfSection( + HANDLE aSection, HANDLE aProcess, PVOID* aBaseAddress, ULONG_PTR aZeroBits, + SIZE_T aCommitSize, PLARGE_INTEGER aSectionOffset, PSIZE_T aViewSize, + SECTION_INHERIT aInheritDisposition, ULONG aAllocationType, + ULONG aProtectionFlags); + +NTSTATUS NTAPI NtUnmapViewOfSection(HANDLE aProcess, PVOID aBaseAddress); + +enum MEMORY_INFORMATION_CLASS { + MemoryBasicInformation = 0, + MemorySectionName = 2 +}; + +// NB: When allocating, space for the buffer must also be included +typedef struct _MEMORY_SECTION_NAME { + UNICODE_STRING mSectionFileName; +} MEMORY_SECTION_NAME, *PMEMORY_SECTION_NAME; + +NTSTATUS NTAPI NtQueryVirtualMemory(HANDLE aProcess, PVOID aBaseAddress, + MEMORY_INFORMATION_CLASS aMemInfoClass, + PVOID aMemInfo, SIZE_T aMemInfoLen, + PSIZE_T aReturnLen); + +LONG NTAPI RtlCompareUnicodeString(PCUNICODE_STRING aStr1, + PCUNICODE_STRING aStr2, + BOOLEAN aCaseInsensitive); + +BOOLEAN NTAPI RtlEqualUnicodeString(PCUNICODE_STRING aStr1, + PCUNICODE_STRING aStr2, + BOOLEAN aCaseInsensitive); + +NTSTATUS NTAPI RtlGetVersion(PRTL_OSVERSIONINFOW aOutVersionInformation); + +VOID NTAPI RtlAcquireSRWLockExclusive(PSRWLOCK aLock); +VOID NTAPI RtlAcquireSRWLockShared(PSRWLOCK aLock); + +VOID NTAPI RtlReleaseSRWLockExclusive(PSRWLOCK aLock); +VOID NTAPI RtlReleaseSRWLockShared(PSRWLOCK aLock); + +NTSTATUS NTAPI RtlSleepConditionVariableSRW( + PCONDITION_VARIABLE aConditionVariable, PSRWLOCK aSRWLock, + PLARGE_INTEGER aTimeOut, ULONG aFlags); +VOID NTAPI RtlWakeAllConditionVariable(PCONDITION_VARIABLE aConditionVariable); + +ULONG NTAPI RtlNtStatusToDosError(NTSTATUS aStatus); +VOID NTAPI RtlSetLastWin32Error(DWORD aError); +DWORD NTAPI RtlGetLastWin32Error(); + +VOID NTAPI RtlRunOnceInitialize(PRTL_RUN_ONCE aRunOnce); + +NTSTATUS NTAPI NtReadVirtualMemory(HANDLE aProcessHandle, PVOID aBaseAddress, + PVOID aBuffer, SIZE_T aNumBytesToRead, + PSIZE_T aNumBytesRead); + +NTSTATUS NTAPI LdrLoadDll(PWCHAR aDllPath, PULONG aFlags, + PUNICODE_STRING aDllName, PHANDLE aOutHandle); + +typedef ULONG(NTAPI* PRTL_RUN_ONCE_INIT_FN)(PRTL_RUN_ONCE, PVOID, PVOID*); +NTSTATUS NTAPI RtlRunOnceExecuteOnce(PRTL_RUN_ONCE aRunOnce, + PRTL_RUN_ONCE_INIT_FN aInitFn, + PVOID aContext, PVOID* aParameter); + +} // extern "C" + +#endif // !defined(MOZILLA_INTERNAL_API) + +extern "C" { +PVOID NTAPI RtlAllocateHeap(PVOID aHeapHandle, ULONG aFlags, SIZE_T aSize); + +PVOID NTAPI RtlReAllocateHeap(PVOID aHeapHandle, ULONG aFlags, LPVOID aMem, + SIZE_T aNewSize); + +BOOLEAN NTAPI RtlFreeHeap(PVOID aHeapHandle, ULONG aFlags, PVOID aHeapBase); + +BOOLEAN NTAPI RtlQueryPerformanceCounter(LARGE_INTEGER* aPerfCount); + +#define RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE 1 +#define RTL_DUPLICATE_UNICODE_STRING_ALLOCATE_NULL_STRING 2 +NTSTATUS NTAPI RtlDuplicateUnicodeString(ULONG aFlags, PCUNICODE_STRING aSrc, + PUNICODE_STRING aDest); + +VOID NTAPI RtlFreeUnicodeString(PUNICODE_STRING aUnicodeString); +} // extern "C" + +namespace mozilla { +namespace nt { + +/** + * This class encapsulates a UNICODE_STRING that owns its own buffer. The + * buffer is always NULL terminated, thus allowing us to cast to a wide C-string + * without requiring any mutation. + * + * We only allow creation of this owned buffer from outside XUL. + */ +class AllocatedUnicodeString final { + public: + AllocatedUnicodeString() : mUnicodeString() {} + +#if defined(MOZILLA_INTERNAL_API) + AllocatedUnicodeString(const AllocatedUnicodeString& aOther) = delete; + + AllocatedUnicodeString& operator=(const AllocatedUnicodeString& aOther) = + delete; +#else + explicit AllocatedUnicodeString(PCUNICODE_STRING aSrc) { + if (!aSrc) { + mUnicodeString = {}; + return; + } + + Duplicate(aSrc); + } + + explicit AllocatedUnicodeString(const char* aSrc) { + if (!aSrc) { + mUnicodeString = {}; + return; + } + + Duplicate(aSrc); + } + + AllocatedUnicodeString(const AllocatedUnicodeString& aOther) { + Duplicate(&aOther.mUnicodeString); + } + + AllocatedUnicodeString& operator=(const AllocatedUnicodeString& aOther) { + Clear(); + Duplicate(&aOther.mUnicodeString); + return *this; + } + + AllocatedUnicodeString& operator=(PCUNICODE_STRING aSrc) { + Clear(); + Duplicate(aSrc); + return *this; + } +#endif // defined(MOZILLA_INTERNAL_API) + + AllocatedUnicodeString(AllocatedUnicodeString&& aOther) + : mUnicodeString(aOther.mUnicodeString) { + aOther.mUnicodeString = {}; + } + + AllocatedUnicodeString& operator=(AllocatedUnicodeString&& aOther) { + Clear(); + mUnicodeString = aOther.mUnicodeString; + aOther.mUnicodeString = {}; + return *this; + } + + ~AllocatedUnicodeString() { Clear(); } + + bool IsEmpty() const { + return !mUnicodeString.Buffer || !mUnicodeString.Length; + } + + operator PCUNICODE_STRING() const { return &mUnicodeString; } + + operator const WCHAR*() const { return mUnicodeString.Buffer; } + + USHORT CharLen() const { return mUnicodeString.Length / sizeof(WCHAR); } + +#if defined(MOZILLA_INTERNAL_API) + nsDependentString AsString() const { + if (!mUnicodeString.Buffer) { + return nsDependentString(); + } + + // We can use nsDependentString here as we guaranteed null termination + // when we allocated the string. + return nsDependentString(mUnicodeString.Buffer, CharLen()); + } +#endif // defined(MOZILLA_INTERNAL_API) + + private: +#if !defined(MOZILLA_INTERNAL_API) + void Duplicate(PCUNICODE_STRING aSrc) { + MOZ_ASSERT(aSrc); + + // We duplicate with null termination so that this string may be used + // as a wide C-string without any further manipulation. + NTSTATUS ntStatus = ::RtlDuplicateUnicodeString( + RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE, aSrc, &mUnicodeString); + MOZ_ASSERT(NT_SUCCESS(ntStatus)); + if (!NT_SUCCESS(ntStatus)) { + // Make sure that mUnicodeString does not contain bogus data + // (since not all callers zero it out before invoking) + mUnicodeString = {}; + } + } + + void Duplicate(const char* aSrc) { + MOZ_ASSERT(aSrc); + + ANSI_STRING ansiStr; + RtlInitAnsiString(&ansiStr, aSrc); + NTSTATUS ntStatus = + ::RtlAnsiStringToUnicodeString(&mUnicodeString, &ansiStr, TRUE); + MOZ_ASSERT(NT_SUCCESS(ntStatus)); + if (!NT_SUCCESS(ntStatus)) { + mUnicodeString = {}; + } + } +#endif // !defined(MOZILLA_INTERNAL_API) + + void Clear() { + if (!mUnicodeString.Buffer) { + return; + } + + ::RtlFreeUnicodeString(&mUnicodeString); + mUnicodeString = {}; + } + + UNICODE_STRING mUnicodeString; +}; + +#if !defined(MOZILLA_INTERNAL_API) + +struct MemorySectionNameBuf : public _MEMORY_SECTION_NAME { + MemorySectionNameBuf() { + mSectionFileName.Length = 0; + mSectionFileName.MaximumLength = sizeof(mBuf); + mSectionFileName.Buffer = mBuf; + } + + MemorySectionNameBuf(const MemorySectionNameBuf& aOther) { *this = aOther; } + + MemorySectionNameBuf(MemorySectionNameBuf&& aOther) { + *this = std::move(aOther); + } + + // We cannot use default copy here because mSectionFileName.Buffer needs to + // be updated to point to |this->mBuf|, not |aOther.mBuf|. + MemorySectionNameBuf& operator=(const MemorySectionNameBuf& aOther) { + mSectionFileName.Length = aOther.mSectionFileName.Length; + mSectionFileName.MaximumLength = sizeof(mBuf); + MOZ_ASSERT(mSectionFileName.Length <= mSectionFileName.MaximumLength); + mSectionFileName.Buffer = mBuf; + memcpy(mBuf, aOther.mBuf, aOther.mSectionFileName.Length); + return *this; + } + + MemorySectionNameBuf& operator=(MemorySectionNameBuf&& aOther) { + mSectionFileName.Length = aOther.mSectionFileName.Length; + aOther.mSectionFileName.Length = 0; + mSectionFileName.MaximumLength = sizeof(mBuf); + MOZ_ASSERT(mSectionFileName.Length <= mSectionFileName.MaximumLength); + aOther.mSectionFileName.MaximumLength = sizeof(aOther.mBuf); + mSectionFileName.Buffer = mBuf; + memmove(mBuf, aOther.mBuf, mSectionFileName.Length); + return *this; + } + + // Native NT paths, so we can't assume MAX_PATH. Use a larger buffer. + WCHAR mBuf[2 * MAX_PATH]; + + bool IsEmpty() const { + return !mSectionFileName.Buffer || !mSectionFileName.Length; + } + + operator PCUNICODE_STRING() const { return &mSectionFileName; } +}; + +class MemorySectionNameOnHeap { + UniquePtr<uint8_t[]> mBuffer; + + MemorySectionNameOnHeap() = default; + explicit MemorySectionNameOnHeap(size_t aBufferLen) + : mBuffer(MakeUnique<uint8_t[]>(aBufferLen)) {} + + public: + static MemorySectionNameOnHeap GetBackingFilePath(HANDLE aProcess, + void* aSectionAddr) { + SIZE_T bufferLen = MAX_PATH * 2; + do { + MemorySectionNameOnHeap sectionName(bufferLen); + + SIZE_T requiredBytes; + NTSTATUS ntStatus = ::NtQueryVirtualMemory( + aProcess, aSectionAddr, MemorySectionName, sectionName.mBuffer.get(), + bufferLen, &requiredBytes); + if (NT_SUCCESS(ntStatus)) { + return sectionName; + } + + if (ntStatus != STATUS_INFO_LENGTH_MISMATCH || + bufferLen >= requiredBytes) { + break; + } + + bufferLen = requiredBytes; + } while (1); + + return MemorySectionNameOnHeap(); + } + + // Allow move & Disallow copy + MemorySectionNameOnHeap(MemorySectionNameOnHeap&&) = default; + MemorySectionNameOnHeap& operator=(MemorySectionNameOnHeap&&) = default; + MemorySectionNameOnHeap(const MemorySectionNameOnHeap&) = delete; + MemorySectionNameOnHeap& operator=(const MemorySectionNameOnHeap&) = delete; + + PCUNICODE_STRING AsUnicodeString() const { + return reinterpret_cast<PCUNICODE_STRING>(mBuffer.get()); + } +}; + +inline bool FindCharInUnicodeString(const UNICODE_STRING& aStr, WCHAR aChar, + uint16_t& aPos, uint16_t aStartIndex = 0) { + const uint16_t aMaxIndex = aStr.Length / sizeof(WCHAR); + + for (uint16_t curIndex = aStartIndex; curIndex < aMaxIndex; ++curIndex) { + if (aStr.Buffer[curIndex] == aChar) { + aPos = curIndex; + return true; + } + } + + return false; +} + +inline bool IsHexDigit(WCHAR aChar) { + return (aChar >= L'0' && aChar <= L'9') || (aChar >= L'A' && aChar <= L'F') || + (aChar >= L'a' && aChar <= L'f'); +} + +inline bool MatchUnicodeString(const UNICODE_STRING& aStr, + bool (*aPredicate)(WCHAR)) { + WCHAR* cur = aStr.Buffer; + WCHAR* end = &aStr.Buffer[aStr.Length / sizeof(WCHAR)]; + while (cur < end) { + if (!aPredicate(*cur)) { + return false; + } + + ++cur; + } + + return true; +} + +inline bool Contains12DigitHexString(const UNICODE_STRING& aLeafName) { + // Quick check: If the string is too short, don't bother + // (We need at least 12 hex digits, one char for '.', and 3 for extension) + const USHORT kMinLen = (12 + 1 + 3) * sizeof(wchar_t); + if (aLeafName.Length < kMinLen) { + return false; + } + + uint16_t start, end; + if (!FindCharInUnicodeString(aLeafName, L'.', start)) { + return false; + } + + ++start; + if (!FindCharInUnicodeString(aLeafName, L'.', end, start)) { + return false; + } + + if (end - start != 12) { + return false; + } + + UNICODE_STRING test; + test.Buffer = &aLeafName.Buffer[start]; + test.Length = (end - start) * sizeof(WCHAR); + test.MaximumLength = test.Length; + + return MatchUnicodeString(test, &IsHexDigit); +} + +inline bool IsFileNameAtLeast16HexDigits(const UNICODE_STRING& aLeafName) { + // Quick check: If the string is too short, don't bother + // (We need 16 hex digits, one char for '.', and 3 for extension) + const USHORT kMinLen = (16 + 1 + 3) * sizeof(wchar_t); + if (aLeafName.Length < kMinLen) { + return false; + } + + uint16_t dotIndex; + if (!FindCharInUnicodeString(aLeafName, L'.', dotIndex)) { + return false; + } + + if (dotIndex < 16) { + return false; + } + + UNICODE_STRING test; + test.Buffer = aLeafName.Buffer; + test.Length = dotIndex * sizeof(WCHAR); + test.MaximumLength = aLeafName.MaximumLength; + + return MatchUnicodeString(test, &IsHexDigit); +} + +inline void GetLeafName(PUNICODE_STRING aDestString, + PCUNICODE_STRING aSrcString) { + WCHAR* buf = aSrcString->Buffer; + WCHAR* end = &aSrcString->Buffer[(aSrcString->Length / sizeof(WCHAR)) - 1]; + WCHAR* cur = end; + while (cur >= buf) { + if (*cur == L'\\') { + break; + } + + --cur; + } + + // At this point, either cur points to the final backslash, or it points to + // buf - 1. Either way, we're interested in cur + 1 as the desired buffer. + aDestString->Buffer = cur + 1; + aDestString->Length = (end - aDestString->Buffer + 1) * sizeof(WCHAR); + aDestString->MaximumLength = aDestString->Length; +} + +#endif // !defined(MOZILLA_INTERNAL_API) + +#if defined(MOZILLA_INTERNAL_API) + +inline const nsDependentSubstring GetLeafName(const nsAString& aString) { + auto it = aString.EndReading(); + size_t pos = aString.Length(); + while (it > aString.BeginReading()) { + if (*(it - 1) == u'\\') { + return Substring(aString, pos); + } + + MOZ_ASSERT(pos > 0); + --pos; + --it; + } + + return Substring(aString, 0); // No backslash in the string +} + +#endif // defined(MOZILLA_INTERNAL_API) + +inline char EnsureLowerCaseASCII(char aChar) { + if (aChar >= 'A' && aChar <= 'Z') { + aChar -= 'A' - 'a'; + } + + return aChar; +} + +inline int StricmpASCII(const char* aLeft, const char* aRight) { + char curLeft, curRight; + + do { + curLeft = EnsureLowerCaseASCII(*(aLeft++)); + curRight = EnsureLowerCaseASCII(*(aRight++)); + } while (curLeft && curLeft == curRight); + + return curLeft - curRight; +} + +inline int StrcmpASCII(const char* aLeft, const char* aRight) { + char curLeft, curRight; + + do { + curLeft = *(aLeft++); + curRight = *(aRight++); + } while (curLeft && curLeft == curRight); + + return curLeft - curRight; +} + +inline size_t StrlenASCII(const char* aStr) { + size_t len = 0; + + while (*(aStr++)) { + ++len; + } + + return len; +} + +struct CodeViewRecord70 { + uint32_t signature; + GUID pdbSignature; + uint32_t pdbAge; + // A UTF-8 string, according to + // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/PDB/dbi/locator.cpp#L785 + char pdbFileName[1]; +}; + +class MOZ_RAII PEHeaders final { + /** + * This structure is documented on MSDN as VS_VERSIONINFO, but is not present + * in SDK headers because it cannot be specified as a C struct. The following + * structure contains the fixed-length fields at the beginning of + * VS_VERSIONINFO. + */ + struct VS_VERSIONINFO_HEADER { + WORD wLength; + WORD wValueLength; + WORD wType; + WCHAR szKey[16]; // ArrayLength(L"VS_VERSION_INFO") + // Additional data goes here, aligned on a 4-byte boundary + }; + + public: + // The lowest two bits of an HMODULE are used as flags. Stripping those bits + // from the HMODULE yields the base address of the binary's memory mapping. + // (See LoadLibraryEx docs on MSDN) + template <typename T> + static T HModuleToBaseAddr(HMODULE aModule) { + return reinterpret_cast<T>(reinterpret_cast<uintptr_t>(aModule) & + ~uintptr_t(3)); + } + + explicit PEHeaders(void* aBaseAddress) + : PEHeaders(reinterpret_cast<PIMAGE_DOS_HEADER>(aBaseAddress)) {} + + explicit PEHeaders(HMODULE aModule) + : PEHeaders(HModuleToBaseAddr<PIMAGE_DOS_HEADER>(aModule)) {} + + explicit PEHeaders(PIMAGE_DOS_HEADER aMzHeader) + : mMzHeader(aMzHeader), + mPeHeader(nullptr), + mImageLimit(nullptr), + mIsImportDirectoryTampered(false) { + if (!mMzHeader || mMzHeader->e_magic != IMAGE_DOS_SIGNATURE) { + return; + } + + mPeHeader = RVAToPtrUnchecked<PIMAGE_NT_HEADERS>(mMzHeader->e_lfanew); + if (!mPeHeader || mPeHeader->Signature != IMAGE_NT_SIGNATURE) { + return; + } + + if (mPeHeader->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC) { + return; + } + + DWORD imageSize = mPeHeader->OptionalHeader.SizeOfImage; + // This is a coarse-grained check to ensure that the image size is + // reasonable. It we aren't big enough to contain headers, we have a + // problem! + if (imageSize < sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS)) { + return; + } + + mImageLimit = RVAToPtrUnchecked<void*>(imageSize - 1UL); + + PIMAGE_DATA_DIRECTORY importDirEntry = + GetImageDirectoryEntryPtr(IMAGE_DIRECTORY_ENTRY_IMPORT); + if (!importDirEntry) { + return; + } + + mIsImportDirectoryTampered = (importDirEntry->VirtualAddress >= imageSize); + } + + explicit operator bool() const { return !!mImageLimit; } + + /** + * This overload computes absolute virtual addresses relative to the base + * address of the binary. + */ + template <typename T, typename R> + T RVAToPtr(R aRva) const { + return RVAToPtr<T>(mMzHeader, aRva); + } + + /** + * This overload computes a result by adding aRva to aBase, but also ensures + * that the resulting pointer falls within the bounds of this binary's memory + * mapping. + */ + template <typename T, typename R> + T RVAToPtr(void* aBase, R aRva) const { + if (!mImageLimit) { + return nullptr; + } + + char* absAddress = reinterpret_cast<char*>(aBase) + aRva; + if (absAddress < reinterpret_cast<char*>(mMzHeader) || + absAddress > reinterpret_cast<char*>(mImageLimit)) { + return nullptr; + } + + return reinterpret_cast<T>(absAddress); + } + + Maybe<Range<const uint8_t>> GetBounds() const { + if (!mImageLimit) { + return Nothing(); + } + + auto base = reinterpret_cast<const uint8_t*>(mMzHeader); + DWORD imageSize = mPeHeader->OptionalHeader.SizeOfImage; + return Some(Range(base, imageSize)); + } + + DWORD GetFileCharacteristics() const { + return mPeHeader ? mPeHeader->FileHeader.Characteristics : 0; + } + + bool IsWithinImage(const void* aAddress) const { + uintptr_t addr = reinterpret_cast<uintptr_t>(aAddress); + uintptr_t imageBase = reinterpret_cast<uintptr_t>(mMzHeader); + uintptr_t imageLimit = reinterpret_cast<uintptr_t>(mImageLimit); + return addr >= imageBase && addr <= imageLimit; + } + + PIMAGE_IMPORT_DESCRIPTOR GetImportDirectory() const { + // If the import directory is already tampered, we skip bounds check + // because it could be located outside the mapped image. + return mIsImportDirectoryTampered + ? GetImageDirectoryEntry<PIMAGE_IMPORT_DESCRIPTOR, + BoundsCheckPolicy::Skip>( + IMAGE_DIRECTORY_ENTRY_IMPORT) + : GetImageDirectoryEntry<PIMAGE_IMPORT_DESCRIPTOR>( + IMAGE_DIRECTORY_ENTRY_IMPORT); + } + + PIMAGE_RESOURCE_DIRECTORY GetResourceTable() const { + return GetImageDirectoryEntry<PIMAGE_RESOURCE_DIRECTORY>( + IMAGE_DIRECTORY_ENTRY_RESOURCE); + } + + PIMAGE_DATA_DIRECTORY GetImageDirectoryEntryPtr( + const uint32_t aDirectoryIndex, uint32_t* aOutRva = nullptr) const { + if (aOutRva) { + *aOutRva = 0; + } + + IMAGE_OPTIONAL_HEADER& optionalHeader = mPeHeader->OptionalHeader; + + const uint32_t maxIndex = std::min(optionalHeader.NumberOfRvaAndSizes, + DWORD(IMAGE_NUMBEROF_DIRECTORY_ENTRIES)); + if (aDirectoryIndex >= maxIndex) { + return nullptr; + } + + PIMAGE_DATA_DIRECTORY dirEntry = + &optionalHeader.DataDirectory[aDirectoryIndex]; + if (aOutRva) { + *aOutRva = reinterpret_cast<char*>(dirEntry) - + reinterpret_cast<char*>(mMzHeader); + MOZ_ASSERT(*aOutRva); + } + + return dirEntry; + } + + bool GetVersionInfo(uint64_t& aOutVersion) const { + // RT_VERSION == 16 + // Version resources require an id of 1 + auto root = FindResourceLeaf<VS_VERSIONINFO_HEADER*>(16, 1); + if (!root) { + return false; + } + + VS_FIXEDFILEINFO* fixedInfo = GetFixedFileInfo(root); + if (!fixedInfo) { + return false; + } + + aOutVersion = ((static_cast<uint64_t>(fixedInfo->dwFileVersionMS) << 32) | + static_cast<uint64_t>(fixedInfo->dwFileVersionLS)); + return true; + } + + bool GetTimeStamp(DWORD& aResult) const { + if (!(*this)) { + return false; + } + + aResult = mPeHeader->FileHeader.TimeDateStamp; + return true; + } + + bool GetImageSize(DWORD& aResult) const { + if (!(*this)) { + return false; + } + + aResult = mPeHeader->OptionalHeader.SizeOfImage; + return true; + } + + bool GetCheckSum(DWORD& aResult) const { + if (!(*this)) { + return false; + } + + aResult = mPeHeader->OptionalHeader.CheckSum; + return true; + } + + PIMAGE_IMPORT_DESCRIPTOR + GetImportDescriptor(const char* aModuleNameASCII) const { + for (PIMAGE_IMPORT_DESCRIPTOR curImpDesc = GetImportDirectory(); + IsValid(curImpDesc); ++curImpDesc) { + auto curName = mIsImportDirectoryTampered + ? RVAToPtrUnchecked<const char*>(curImpDesc->Name) + : RVAToPtr<const char*>(curImpDesc->Name); + if (!curName) { + return nullptr; + } + + if (StricmpASCII(aModuleNameASCII, curName)) { + continue; + } + + // curImpDesc now points to the IAT for the module we're interested in + return curImpDesc; + } + + return nullptr; + } + + template <typename CallbackT> + void EnumImportChunks(const CallbackT& aCallback) const { + for (PIMAGE_IMPORT_DESCRIPTOR curImpDesc = GetImportDirectory(); + IsValid(curImpDesc); ++curImpDesc) { + auto curName = mIsImportDirectoryTampered + ? RVAToPtrUnchecked<const char*>(curImpDesc->Name) + : RVAToPtr<const char*>(curImpDesc->Name); + if (!curName) { + continue; + } + + aCallback(curName); + } + } + + /** + * If |aBoundaries| is given, this method checks whether each IAT entry is + * within the given range, and if any entry is out of the range, we return + * Nothing(). + */ + Maybe<Span<IMAGE_THUNK_DATA>> GetIATThunksForModule( + const char* aModuleNameASCII, + const Range<const uint8_t>* aBoundaries = nullptr) const { + PIMAGE_IMPORT_DESCRIPTOR impDesc = GetImportDescriptor(aModuleNameASCII); + if (!impDesc) { + return Nothing(); + } + + auto firstIatThunk = + this->template RVAToPtr<PIMAGE_THUNK_DATA>(impDesc->FirstThunk); + if (!firstIatThunk) { + return Nothing(); + } + + // Find the length by iterating through the table until we find a null entry + PIMAGE_THUNK_DATA curIatThunk = firstIatThunk; + while (IsValid(curIatThunk)) { + if (aBoundaries) { + auto iatEntry = + reinterpret_cast<const uint8_t*>(curIatThunk->u1.Function); + if (iatEntry < aBoundaries->begin().get() || + iatEntry >= aBoundaries->end().get()) { + return Nothing(); + } + } + + ++curIatThunk; + } + + return Some(Span(firstIatThunk, curIatThunk)); + } + + /** + * Resources are stored in a three-level tree. To locate a particular entry, + * you must supply a resource type, the resource id, and then the language id. + * If aLangId == 0, we just resolve the first entry regardless of language. + */ + template <typename T> + T FindResourceLeaf(WORD aType, WORD aResId, WORD aLangId = 0) const { + PIMAGE_RESOURCE_DIRECTORY topLevel = GetResourceTable(); + if (!topLevel) { + return nullptr; + } + + PIMAGE_RESOURCE_DIRECTORY_ENTRY typeEntry = + FindResourceEntry(topLevel, aType); + if (!typeEntry || !typeEntry->DataIsDirectory) { + return nullptr; + } + + auto idDir = RVAToPtr<PIMAGE_RESOURCE_DIRECTORY>( + topLevel, typeEntry->OffsetToDirectory); + PIMAGE_RESOURCE_DIRECTORY_ENTRY idEntry = FindResourceEntry(idDir, aResId); + if (!idEntry || !idEntry->DataIsDirectory) { + return nullptr; + } + + auto langDir = RVAToPtr<PIMAGE_RESOURCE_DIRECTORY>( + topLevel, idEntry->OffsetToDirectory); + PIMAGE_RESOURCE_DIRECTORY_ENTRY langEntry; + if (aLangId) { + langEntry = FindResourceEntry(langDir, aLangId); + } else { + langEntry = FindFirstResourceEntry(langDir); + } + + if (!langEntry || langEntry->DataIsDirectory) { + return nullptr; + } + + auto dataEntry = + RVAToPtr<PIMAGE_RESOURCE_DATA_ENTRY>(topLevel, langEntry->OffsetToData); + return dataEntry ? RVAToPtr<T>(dataEntry->OffsetToData) : nullptr; + } + + template <size_t N> + Maybe<Span<const uint8_t>> FindSection(const char (&aSecName)[N], + DWORD aCharacteristicsMask) const { + static_assert((N - 1) <= IMAGE_SIZEOF_SHORT_NAME, + "Section names must be at most 8 characters excluding null " + "terminator"); + + if (!(*this)) { + return Nothing(); + } + + Span<IMAGE_SECTION_HEADER> sectionTable = GetSectionTable(); + for (auto&& sectionHeader : sectionTable) { + if (strncmp(reinterpret_cast<const char*>(sectionHeader.Name), aSecName, + IMAGE_SIZEOF_SHORT_NAME)) { + continue; + } + + if (!(sectionHeader.Characteristics & aCharacteristicsMask)) { + // We found the section but it does not have the expected + // characteristics + return Nothing(); + } + + DWORD rva = sectionHeader.VirtualAddress; + if (!rva) { + return Nothing(); + } + + DWORD size = sectionHeader.Misc.VirtualSize; + if (!size) { + return Nothing(); + } + + auto base = RVAToPtr<const uint8_t*>(rva); + return Some(Span(base, size)); + } + + return Nothing(); + } + + // There may be other code sections in the binary besides .text + Maybe<Span<const uint8_t>> GetTextSectionInfo() const { + return FindSection(".text", IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | + IMAGE_SCN_MEM_READ); + } + + static bool IsValid(PIMAGE_IMPORT_DESCRIPTOR aImpDesc) { + return aImpDesc && aImpDesc->OriginalFirstThunk != 0; + } + + static bool IsValid(PIMAGE_THUNK_DATA aImgThunk) { + return aImgThunk && aImgThunk->u1.Ordinal != 0; + } + + bool IsImportDirectoryTampered() const { return mIsImportDirectoryTampered; } + + FARPROC GetEntryPoint() const { + // Use the unchecked version because the entrypoint may be tampered. + return RVAToPtrUnchecked<FARPROC>( + mPeHeader->OptionalHeader.AddressOfEntryPoint); + } + + const CodeViewRecord70* GetPdbInfo() const { + PIMAGE_DEBUG_DIRECTORY debugDirectory = + GetImageDirectoryEntry<PIMAGE_DEBUG_DIRECTORY>( + IMAGE_DIRECTORY_ENTRY_DEBUG); + if (!debugDirectory) { + return nullptr; + } + + const CodeViewRecord70* debugInfo = + RVAToPtr<CodeViewRecord70*>(debugDirectory->AddressOfRawData); + return (debugInfo && debugInfo->signature == 'SDSR') ? debugInfo : nullptr; + } + + private: + enum class BoundsCheckPolicy { Default, Skip }; + + template <typename T, BoundsCheckPolicy Policy = BoundsCheckPolicy::Default> + T GetImageDirectoryEntry(const uint32_t aDirectoryIndex) const { + PIMAGE_DATA_DIRECTORY dirEntry = GetImageDirectoryEntryPtr(aDirectoryIndex); + if (!dirEntry) { + return nullptr; + } + + return Policy == BoundsCheckPolicy::Skip + ? RVAToPtrUnchecked<T>(dirEntry->VirtualAddress) + : RVAToPtr<T>(dirEntry->VirtualAddress); + } + + // This private variant does not have bounds checks, because we need to be + // able to resolve the bounds themselves. + template <typename T, typename R> + T RVAToPtrUnchecked(R aRva) const { + return reinterpret_cast<T>(reinterpret_cast<char*>(mMzHeader) + aRva); + } + + Span<IMAGE_SECTION_HEADER> GetSectionTable() const { + MOZ_ASSERT(*this); + auto base = RVAToPtr<PIMAGE_SECTION_HEADER>( + &mPeHeader->OptionalHeader, mPeHeader->FileHeader.SizeOfOptionalHeader); + // The Windows loader has an internal limit of 96 sections (per PE spec) + auto numSections = + std::min(mPeHeader->FileHeader.NumberOfSections, WORD(96)); + return Span{base, numSections}; + } + + PIMAGE_RESOURCE_DIRECTORY_ENTRY + FindResourceEntry(PIMAGE_RESOURCE_DIRECTORY aCurLevel, WORD aId) const { + if (!aCurLevel) { + return nullptr; + } + + // Immediately after the IMAGE_RESOURCE_DIRECTORY structure is an array + // of IMAGE_RESOURCE_DIRECTORY_ENTRY structures. Since this function + // searches by ID, we need to skip past any named entries before iterating. + auto dirEnt = + reinterpret_cast<PIMAGE_RESOURCE_DIRECTORY_ENTRY>(aCurLevel + 1) + + aCurLevel->NumberOfNamedEntries; + if (!(IsWithinImage(dirEnt) && + IsWithinImage(&dirEnt[aCurLevel->NumberOfIdEntries - 1].Id))) { + return nullptr; + } + + for (WORD i = 0; i < aCurLevel->NumberOfIdEntries; ++i) { + if (dirEnt[i].Id == aId) { + return &dirEnt[i]; + } + } + + return nullptr; + } + + PIMAGE_RESOURCE_DIRECTORY_ENTRY + FindFirstResourceEntry(PIMAGE_RESOURCE_DIRECTORY aCurLevel) const { + // Immediately after the IMAGE_RESOURCE_DIRECTORY structure is an array + // of IMAGE_RESOURCE_DIRECTORY_ENTRY structures. We just return the first + // entry, regardless of whether it is indexed by name or by id. + auto dirEnt = + reinterpret_cast<PIMAGE_RESOURCE_DIRECTORY_ENTRY>(aCurLevel + 1); + WORD numEntries = + aCurLevel->NumberOfNamedEntries + aCurLevel->NumberOfIdEntries; + if (!numEntries) { + return nullptr; + } + + return dirEnt; + } + + VS_FIXEDFILEINFO* GetFixedFileInfo(VS_VERSIONINFO_HEADER* aVerInfo) const { + WORD length = aVerInfo->wLength; + if (length < sizeof(VS_VERSIONINFO_HEADER)) { + return nullptr; + } + + const wchar_t kVersionInfoKey[] = L"VS_VERSION_INFO"; + if (::RtlCompareMemory(aVerInfo->szKey, kVersionInfoKey, + ArrayLength(kVersionInfoKey)) != + ArrayLength(kVersionInfoKey)) { + return nullptr; + } + + if (aVerInfo->wValueLength != sizeof(VS_FIXEDFILEINFO)) { + // Fixed file info does not exist + return nullptr; + } + + WORD offset = sizeof(VS_VERSIONINFO_HEADER); + + uintptr_t base = reinterpret_cast<uintptr_t>(aVerInfo); + // Align up to 4-byte boundary +#pragma warning(suppress : 4146) + offset += (-(base + offset) & 3); + + if (offset >= length) { + return nullptr; + } + + auto result = reinterpret_cast<VS_FIXEDFILEINFO*>(base + offset); + if (result->dwSignature != 0xFEEF04BD) { + return nullptr; + } + + return result; + } + + private: + PIMAGE_DOS_HEADER mMzHeader; + PIMAGE_NT_HEADERS mPeHeader; + void* mImageLimit; + bool mIsImportDirectoryTampered; +}; + +// This class represents an export section of a local/remote process. +template <typename MMPolicy> +class MOZ_RAII PEExportSection { + const MMPolicy& mMMPolicy; + uintptr_t mImageBase; + DWORD mOrdinalBase; + DWORD mRvaDirStart; + DWORD mRvaDirEnd; + mozilla::interceptor::TargetObjectArray<MMPolicy, DWORD> mExportAddressTable; + mozilla::interceptor::TargetObjectArray<MMPolicy, DWORD> mExportNameTable; + mozilla::interceptor::TargetObjectArray<MMPolicy, WORD> mExportOrdinalTable; + + explicit PEExportSection(const MMPolicy& aMMPolicy) + : mMMPolicy(aMMPolicy), + mImageBase(0), + mOrdinalBase(0), + mRvaDirStart(0), + mRvaDirEnd(0), + mExportAddressTable(mMMPolicy), + mExportNameTable(mMMPolicy), + mExportOrdinalTable(mMMPolicy) {} + + PEExportSection(const MMPolicy& aMMPolicy, uintptr_t aImageBase, + DWORD aRvaDirStart, DWORD aRvaDirEnd, + const IMAGE_EXPORT_DIRECTORY& exportDir) + : mMMPolicy(aMMPolicy), + mImageBase(aImageBase), + mOrdinalBase(exportDir.Base), + mRvaDirStart(aRvaDirStart), + mRvaDirEnd(aRvaDirEnd), + mExportAddressTable(mMMPolicy, + mImageBase + exportDir.AddressOfFunctions, + exportDir.NumberOfFunctions), + mExportNameTable(mMMPolicy, mImageBase + exportDir.AddressOfNames, + exportDir.NumberOfNames), + mExportOrdinalTable(mMMPolicy, + mImageBase + exportDir.AddressOfNameOrdinals, + exportDir.NumberOfNames) {} + + static const PEExportSection Get(uintptr_t aImageBase, + const MMPolicy& aMMPolicy) { + mozilla::interceptor::TargetObject<MMPolicy, IMAGE_DOS_HEADER> mzHeader( + aMMPolicy, aImageBase); + if (!mzHeader || mzHeader->e_magic != IMAGE_DOS_SIGNATURE) { + return PEExportSection(aMMPolicy); + } + + mozilla::interceptor::TargetObject<MMPolicy, IMAGE_NT_HEADERS> peHeader( + aMMPolicy, aImageBase + mzHeader->e_lfanew); + if (!peHeader || peHeader->Signature != IMAGE_NT_SIGNATURE) { + return PEExportSection(aMMPolicy); + } + + if (peHeader->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC) { + return PEExportSection(aMMPolicy); + } + + const IMAGE_OPTIONAL_HEADER& optionalHeader = peHeader->OptionalHeader; + + DWORD imageSize = optionalHeader.SizeOfImage; + // This is a coarse-grained check to ensure that the image size is + // reasonable. It we aren't big enough to contain headers, we have a + // problem! + if (imageSize < sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS)) { + return PEExportSection(aMMPolicy); + } + + if (optionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_EXPORT) { + return PEExportSection(aMMPolicy); + } + + const IMAGE_DATA_DIRECTORY& exportDirectoryEntry = + optionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; + if (!exportDirectoryEntry.VirtualAddress || !exportDirectoryEntry.Size) { + return PEExportSection(aMMPolicy); + } + + mozilla::interceptor::TargetObject<MMPolicy, IMAGE_EXPORT_DIRECTORY> + exportDirectory(aMMPolicy, + aImageBase + exportDirectoryEntry.VirtualAddress); + if (!exportDirectory || !exportDirectory->NumberOfFunctions) { + return PEExportSection(aMMPolicy); + } + + return PEExportSection( + aMMPolicy, aImageBase, exportDirectoryEntry.VirtualAddress, + exportDirectoryEntry.VirtualAddress + exportDirectoryEntry.Size, + *exportDirectory.GetLocalBase()); + } + + FARPROC GetProcAddressByOrdinal(WORD aOrdinal) const { + if (aOrdinal < mOrdinalBase) { + return nullptr; + } + + auto rvaToFunction = mExportAddressTable[aOrdinal - mOrdinalBase]; + if (!rvaToFunction) { + return nullptr; + } + return reinterpret_cast<FARPROC>(mImageBase + *rvaToFunction); + } + + public: + static const PEExportSection Get(HMODULE aModule, const MMPolicy& aMMPolicy) { + return Get(PEHeaders::HModuleToBaseAddr<uintptr_t>(aModule), aMMPolicy); + } + + explicit operator bool() const { + // Because PEExportSection doesn't use MMPolicy::Reserve(), a boolified + // mMMPolicy is expected to be false. We don't check mMMPolicy here. + return mImageBase && mRvaDirStart && mRvaDirEnd && mExportAddressTable && + mExportNameTable && mExportOrdinalTable; + } + + template <typename T> + T RVAToPtr(uint32_t aRva) const { + return reinterpret_cast<T>(mImageBase + aRva); + } + + PIMAGE_EXPORT_DIRECTORY GetExportDirectory() const { + if (!*this) { + return nullptr; + } + + return RVAToPtr<PIMAGE_EXPORT_DIRECTORY>(mRvaDirStart); + } + + /** + * This functions searches the export table for a given string as + * GetProcAddress does, but this returns a matched entry of the Export + * Address Table i.e. a pointer to an RVA of a matched function instead + * of a function address. If the entry is forwarded, this function + * returns nullptr. + */ + const DWORD* FindExportAddressTableEntry( + const char* aFunctionNameASCII) const { + if (!*this || !aFunctionNameASCII) { + return nullptr; + } + + struct NameTableComparator { + NameTableComparator(const PEExportSection<MMPolicy>& aExportSection, + const char* aTarget) + : mExportSection(aExportSection), + mTargetName(aTarget), + mTargetNamelength(StrlenASCII(aTarget)) {} + + int operator()(DWORD aRVAToString) const { + mozilla::interceptor::TargetObjectArray<MMPolicy, char> itemString( + mExportSection.mMMPolicy, mExportSection.mImageBase + aRVAToString, + mTargetNamelength + 1); + return StrcmpASCII(mTargetName, itemString[0]); + } + + const PEExportSection<MMPolicy>& mExportSection; + const char* mTargetName; + size_t mTargetNamelength; + }; + + const NameTableComparator comp(*this, aFunctionNameASCII); + + size_t match; + if (!mExportNameTable.BinarySearchIf(comp, &match)) { + return nullptr; + } + + const WORD* index = mExportOrdinalTable[match]; + if (!index) { + return nullptr; + } + + const DWORD* rvaToFunction = mExportAddressTable[*index]; + if (!rvaToFunction) { + return nullptr; + } + + if (*rvaToFunction >= mRvaDirStart && *rvaToFunction < mRvaDirEnd) { + // If an entry points to an address within the export section, the + // field is a forwarder RVA. We return nullptr because the entry is + // not a function address but a null-terminated string used for export + // forwarding. + return nullptr; + } + + return rvaToFunction; + } + + /** + * This functions behaves the same as the native ::GetProcAddress except + * the following cases: + * - Returns nullptr if a target entry is forwarded to another dll. + */ + FARPROC GetProcAddress(const char* aFunctionNameASCII) const { + uintptr_t maybeOdrinal = reinterpret_cast<uintptr_t>(aFunctionNameASCII); + // When the high-order word of |aFunctionNameASCII| is zero, it's not + // a string but an ordinal value. + if (maybeOdrinal < 0x10000) { + return GetProcAddressByOrdinal(static_cast<WORD>(maybeOdrinal)); + } + + auto rvaToFunction = FindExportAddressTableEntry(aFunctionNameASCII); + if (!rvaToFunction) { + return nullptr; + } + return reinterpret_cast<FARPROC>(mImageBase + *rvaToFunction); + } +}; + +inline HANDLE RtlGetProcessHeap() { + PTEB teb = ::NtCurrentTeb(); + PPEB peb = teb->ProcessEnvironmentBlock; + return peb->Reserved4[1]; +} + +inline PVOID RtlGetThreadLocalStoragePointer() { + return ::NtCurrentTeb()->Reserved1[11]; +} + +inline void RtlSetThreadLocalStoragePointerForTestingOnly(PVOID aNewValue) { + ::NtCurrentTeb()->Reserved1[11] = aNewValue; +} + +inline DWORD RtlGetCurrentThreadId() { + PTEB teb = ::NtCurrentTeb(); + CLIENT_ID* cid = reinterpret_cast<CLIENT_ID*>(&teb->Reserved1[8]); + return static_cast<DWORD>(reinterpret_cast<uintptr_t>(cid->UniqueThread) & + 0xFFFFFFFFUL); +} + +inline PVOID RtlGetThreadStackBase() { + return reinterpret_cast<_NT_TIB*>(::NtCurrentTeb())->StackBase; +} + +inline PVOID RtlGetThreadStackLimit() { + return reinterpret_cast<_NT_TIB*>(::NtCurrentTeb())->StackLimit; +} + +const HANDLE kCurrentProcess = reinterpret_cast<HANDLE>(-1); + +inline LauncherResult<DWORD> GetParentProcessId() { + struct PROCESS_BASIC_INFORMATION { + NTSTATUS ExitStatus; + PPEB PebBaseAddress; + ULONG_PTR AffinityMask; + LONG BasePriority; + ULONG_PTR UniqueProcessId; + ULONG_PTR InheritedFromUniqueProcessId; + }; + + ULONG returnLength; + PROCESS_BASIC_INFORMATION pbi = {}; + NTSTATUS status = + ::NtQueryInformationProcess(kCurrentProcess, ProcessBasicInformation, + &pbi, sizeof(pbi), &returnLength); + if (!NT_SUCCESS(status)) { + return LAUNCHER_ERROR_FROM_NTSTATUS(status); + } + + return static_cast<DWORD>(pbi.InheritedFromUniqueProcessId & 0xFFFFFFFF); +} + +inline SIZE_T WINAPI VirtualQueryEx(HANDLE aProcess, LPCVOID aAddress, + PMEMORY_BASIC_INFORMATION aMemInfo, + SIZE_T aMemInfoLen) { +#if defined(MOZILLA_INTERNAL_API) + return ::VirtualQueryEx(aProcess, aAddress, aMemInfo, aMemInfoLen); +#else + SIZE_T returnedLength; + NTSTATUS status = ::NtQueryVirtualMemory( + aProcess, const_cast<PVOID>(aAddress), MemoryBasicInformation, aMemInfo, + aMemInfoLen, &returnedLength); + if (!NT_SUCCESS(status)) { + ::RtlSetLastWin32Error(::RtlNtStatusToDosError(status)); + returnedLength = 0; + } + return returnedLength; +#endif // defined(MOZILLA_INTERNAL_API) +} + +inline SIZE_T WINAPI VirtualQuery(LPCVOID aAddress, + PMEMORY_BASIC_INFORMATION aMemInfo, + SIZE_T aMemInfoLen) { + return nt::VirtualQueryEx(kCurrentProcess, aAddress, aMemInfo, aMemInfoLen); +} + +struct DataDirectoryEntry : public _IMAGE_DATA_DIRECTORY { + DataDirectoryEntry() : _IMAGE_DATA_DIRECTORY() {} + + MOZ_IMPLICIT DataDirectoryEntry(const _IMAGE_DATA_DIRECTORY& aOther) + : _IMAGE_DATA_DIRECTORY(aOther) {} + + DataDirectoryEntry(const DataDirectoryEntry& aOther) = default; + + bool operator==(const DataDirectoryEntry& aOther) const { + return VirtualAddress == aOther.VirtualAddress && Size == aOther.Size; + } + + bool operator!=(const DataDirectoryEntry& aOther) const { + return !(*this == aOther); + } +}; + +inline LauncherResult<void*> GetProcessPebPtr(HANDLE aProcess) { + ULONG returnLength; + PROCESS_BASIC_INFORMATION pbi; + NTSTATUS status = ::NtQueryInformationProcess( + aProcess, ProcessBasicInformation, &pbi, sizeof(pbi), &returnLength); + if (!NT_SUCCESS(status)) { + return LAUNCHER_ERROR_FROM_NTSTATUS(status); + } + + return pbi.PebBaseAddress; +} + +/** + * This function relies on a specific offset into the mostly-undocumented PEB + * structure. The risk is reduced thanks to the fact that the Chromium sandbox + * relies on the location of this field. It is unlikely to change at this point. + * To further reduce the risk, we also check for the magic 'MZ' signature that + * should indicate the beginning of a PE image. + */ +inline LauncherResult<HMODULE> GetProcessExeModule(HANDLE aProcess) { + LauncherResult<void*> ppeb = GetProcessPebPtr(aProcess); + if (ppeb.isErr()) { + return ppeb.propagateErr(); + } + + PEB peb; + SIZE_T bytesRead; + +#if defined(MOZILLA_INTERNAL_API) + if (!::ReadProcessMemory(aProcess, ppeb.unwrap(), &peb, sizeof(peb), + &bytesRead) || + bytesRead != sizeof(peb)) { + return LAUNCHER_ERROR_FROM_LAST(); + } +#else + NTSTATUS ntStatus = ::NtReadVirtualMemory(aProcess, ppeb.unwrap(), &peb, + sizeof(peb), &bytesRead); + if (!NT_SUCCESS(ntStatus) || bytesRead != sizeof(peb)) { + return LAUNCHER_ERROR_FROM_NTSTATUS(ntStatus); + } +#endif + + // peb.ImageBaseAddress + void* baseAddress = peb.Reserved3[1]; + + char mzMagic[2]; +#if defined(MOZILLA_INTERNAL_API) + if (!::ReadProcessMemory(aProcess, baseAddress, mzMagic, sizeof(mzMagic), + &bytesRead) || + bytesRead != sizeof(mzMagic)) { + return LAUNCHER_ERROR_FROM_LAST(); + } +#else + ntStatus = ::NtReadVirtualMemory(aProcess, baseAddress, mzMagic, + sizeof(mzMagic), &bytesRead); + if (!NT_SUCCESS(ntStatus) || bytesRead != sizeof(mzMagic)) { + return LAUNCHER_ERROR_FROM_NTSTATUS(ntStatus); + } +#endif + + MOZ_ASSERT(mzMagic[0] == 'M' && mzMagic[1] == 'Z'); + if (mzMagic[0] != 'M' || mzMagic[1] != 'Z') { + return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT); + } + + return static_cast<HMODULE>(baseAddress); +} + +#if defined(_MSC_VER) +extern "C" IMAGE_DOS_HEADER __ImageBase; +#endif + +// This class manages data transfer from the local process's executable +// to another process's executable via WriteProcessMemory. +// Bug 1662560 told us the same executable may be mapped onto a different +// address in a different process. This means when we transfer data within +// the mapped executable such as a global variable or IAT from the current +// process to another process, we need to shift its address by the difference +// between two executable's mapped imagebase. +class CrossExecTransferManager final { + HANDLE mRemoteProcess; + uint8_t* mLocalImagebase; + PEHeaders mLocalExec; + uint8_t* mRemoteImagebase; + + static HMODULE GetLocalExecModule() { +#if defined(_MSC_VER) + return reinterpret_cast<HMODULE>(&__ImageBase); +#else + return ::GetModuleHandleW(nullptr); +#endif + } + + LauncherVoidResult EnsureRemoteImagebase() { + if (!mRemoteImagebase) { + LauncherResult<HMODULE> remoteImageBaseResult = + GetProcessExeModule(mRemoteProcess); + if (remoteImageBaseResult.isErr()) { + return remoteImageBaseResult.propagateErr(); + } + + mRemoteImagebase = + reinterpret_cast<uint8_t*>(remoteImageBaseResult.unwrap()); + } + return Ok(); + } + + template <typename T> + T* LocalExecToRemoteExec(T* aLocalAddress) const { + MOZ_ASSERT(mRemoteImagebase); + MOZ_ASSERT(mLocalExec.IsWithinImage(aLocalAddress)); + + if (!mRemoteImagebase || !mLocalExec.IsWithinImage(aLocalAddress)) { + return aLocalAddress; + } + + uintptr_t offset = reinterpret_cast<uintptr_t>(aLocalAddress) - + reinterpret_cast<uintptr_t>(mLocalImagebase); + return reinterpret_cast<T*>(mRemoteImagebase + offset); + } + + public: + explicit CrossExecTransferManager(HANDLE aRemoteProcess) + : mRemoteProcess(aRemoteProcess), + mLocalImagebase( + PEHeaders::HModuleToBaseAddr<uint8_t*>(GetLocalExecModule())), + mLocalExec(mLocalImagebase), + mRemoteImagebase(nullptr) {} + + CrossExecTransferManager(HANDLE aRemoteProcess, HMODULE aLocalImagebase) + : mRemoteProcess(aRemoteProcess), + mLocalImagebase( + PEHeaders::HModuleToBaseAddr<uint8_t*>(aLocalImagebase)), + mLocalExec(mLocalImagebase), + mRemoteImagebase(nullptr) {} + + explicit operator bool() const { return !!mLocalExec; } + HANDLE RemoteProcess() const { return mRemoteProcess; } + const PEHeaders& LocalPEHeaders() const { return mLocalExec; } + + AutoVirtualProtect Protect(void* aLocalAddress, size_t aLength, + DWORD aProtFlags) { + // If EnsureRemoteImagebase() fails, a subsequent operaion will fail. + Unused << EnsureRemoteImagebase(); + return AutoVirtualProtect(LocalExecToRemoteExec(aLocalAddress), aLength, + aProtFlags, mRemoteProcess); + } + + LauncherVoidResult Transfer(LPVOID aDestinationAddress, + LPCVOID aBufferToWrite, SIZE_T aBufferSize) { + LauncherVoidResult result = EnsureRemoteImagebase(); + if (result.isErr()) { + return result.propagateErr(); + } + + if (!::WriteProcessMemory(mRemoteProcess, + LocalExecToRemoteExec(aDestinationAddress), + aBufferToWrite, aBufferSize, nullptr)) { + return LAUNCHER_ERROR_FROM_LAST(); + } + + return Ok(); + } +}; + +#if !defined(MOZILLA_INTERNAL_API) + +inline LauncherResult<HMODULE> GetModuleHandleFromLeafName( + const UNICODE_STRING& aTarget) { + auto maybePeb = nt::GetProcessPebPtr(kCurrentProcess); + if (maybePeb.isErr()) { + return maybePeb.propagateErr(); + } + + const PPEB peb = reinterpret_cast<PPEB>(maybePeb.unwrap()); + if (!peb->Ldr) { + return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT); + } + + auto firstItem = &peb->Ldr->InMemoryOrderModuleList; + for (auto p = firstItem->Flink; p != firstItem; p = p->Flink) { + const auto currentTableEntry = + CONTAINING_RECORD(p, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); + + UNICODE_STRING leafName; + nt::GetLeafName(&leafName, ¤tTableEntry->FullDllName); + + if (::RtlCompareUnicodeString(&leafName, &aTarget, TRUE) == 0) { + return reinterpret_cast<HMODULE>(currentTableEntry->DllBase); + } + } + + return LAUNCHER_ERROR_FROM_WIN32(ERROR_MOD_NOT_FOUND); +} + +class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS SRWLock final { + public: + constexpr SRWLock() : mLock(SRWLOCK_INIT) {} + + void LockShared() { ::RtlAcquireSRWLockShared(&mLock); } + + void LockExclusive() { ::RtlAcquireSRWLockExclusive(&mLock); } + + void UnlockShared() { ::RtlReleaseSRWLockShared(&mLock); } + + void UnlockExclusive() { ::RtlReleaseSRWLockExclusive(&mLock); } + + SRWLock(const SRWLock&) = delete; + SRWLock(SRWLock&&) = delete; + SRWLock& operator=(const SRWLock&) = delete; + SRWLock& operator=(SRWLock&&) = delete; + + SRWLOCK* operator&() { return &mLock; } + + private: + SRWLOCK mLock; +}; + +class MOZ_RAII AutoExclusiveLock final { + public: + explicit AutoExclusiveLock(SRWLock& aLock) : mLock(aLock) { + aLock.LockExclusive(); + } + + ~AutoExclusiveLock() { mLock.UnlockExclusive(); } + + AutoExclusiveLock(const AutoExclusiveLock&) = delete; + AutoExclusiveLock(AutoExclusiveLock&&) = delete; + AutoExclusiveLock& operator=(const AutoExclusiveLock&) = delete; + AutoExclusiveLock& operator=(AutoExclusiveLock&&) = delete; + + private: + SRWLock& mLock; +}; + +class MOZ_RAII AutoSharedLock final { + public: + explicit AutoSharedLock(SRWLock& aLock) : mLock(aLock) { aLock.LockShared(); } + + ~AutoSharedLock() { mLock.UnlockShared(); } + + AutoSharedLock(const AutoSharedLock&) = delete; + AutoSharedLock(AutoSharedLock&&) = delete; + AutoSharedLock& operator=(const AutoSharedLock&) = delete; + AutoSharedLock& operator=(AutoSharedLock&&) = delete; + + private: + SRWLock& mLock; +}; + +#endif // !defined(MOZILLA_INTERNAL_API) + +class RtlAllocPolicy { + public: + template <typename T> + T* maybe_pod_malloc(size_t aNumElems) { + if (aNumElems & mozilla::tl::MulOverflowMask<sizeof(T)>::value) { + return nullptr; + } + + return static_cast<T*>( + ::RtlAllocateHeap(RtlGetProcessHeap(), 0, aNumElems * sizeof(T))); + } + + template <typename T> + T* maybe_pod_calloc(size_t aNumElems) { + if (aNumElems & mozilla::tl::MulOverflowMask<sizeof(T)>::value) { + return nullptr; + } + + return static_cast<T*>(::RtlAllocateHeap( + RtlGetProcessHeap(), HEAP_ZERO_MEMORY, aNumElems * sizeof(T))); + } + + template <typename T> + T* maybe_pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize) { + if (aNewSize & mozilla::tl::MulOverflowMask<sizeof(T)>::value) { + return nullptr; + } + + return static_cast<T*>(::RtlReAllocateHeap(RtlGetProcessHeap(), 0, aPtr, + aNewSize * sizeof(T))); + } + + template <typename T> + T* pod_malloc(size_t aNumElems) { + return maybe_pod_malloc<T>(aNumElems); + } + + template <typename T> + T* pod_calloc(size_t aNumElems) { + return maybe_pod_calloc<T>(aNumElems); + } + + template <typename T> + T* pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize) { + return maybe_pod_realloc<T>(aPtr, aOldSize, aNewSize); + } + + template <typename T> + void free_(T* aPtr, size_t aNumElems = 0) { + ::RtlFreeHeap(RtlGetProcessHeap(), 0, aPtr); + } + + void reportAllocOverflow() const {} + + [[nodiscard]] bool checkSimulatedOOM() const { return true; } +}; + +class AutoMappedView final { + void* mView; + + void Unmap() { + if (!mView) { + return; + } + +#if defined(MOZILLA_INTERNAL_API) + ::UnmapViewOfFile(mView); +#else + NTSTATUS status = ::NtUnmapViewOfSection(nt::kCurrentProcess, mView); + if (!NT_SUCCESS(status)) { + ::RtlSetLastWin32Error(::RtlNtStatusToDosError(status)); + } +#endif + mView = nullptr; + } + + public: + explicit AutoMappedView(void* aView) : mView(aView) {} + + AutoMappedView(HANDLE aSection, ULONG aProtectionFlags) : mView(nullptr) { +#if defined(MOZILLA_INTERNAL_API) + mView = ::MapViewOfFile(aSection, aProtectionFlags, 0, 0, 0); +#else + SIZE_T viewSize = 0; + NTSTATUS status = ::NtMapViewOfSection(aSection, nt::kCurrentProcess, + &mView, 0, 0, nullptr, &viewSize, + ViewUnmap, 0, aProtectionFlags); + if (!NT_SUCCESS(status)) { + ::RtlSetLastWin32Error(::RtlNtStatusToDosError(status)); + } +#endif + } + ~AutoMappedView() { Unmap(); } + + // Allow move & Disallow copy + AutoMappedView(AutoMappedView&& aOther) : mView(aOther.mView) { + aOther.mView = nullptr; + } + AutoMappedView& operator=(AutoMappedView&& aOther) { + if (this != &aOther) { + Unmap(); + mView = aOther.mView; + aOther.mView = nullptr; + } + return *this; + } + AutoMappedView(const AutoMappedView&) = delete; + AutoMappedView& operator=(const AutoMappedView&) = delete; + + explicit operator bool() const { return !!mView; } + template <typename T> + T* as() { + return reinterpret_cast<T*>(mView); + } + + void* release() { + void* p = mView; + mView = nullptr; + return p; + } +}; + +#if defined(_M_X64) +// CheckStack ensures that stack memory pages are committed up to a given size +// in bytes from the current stack pointer. It updates the thread stack limit, +// which points to the lowest committed stack address. +MOZ_NEVER_INLINE MOZ_NAKED inline void CheckStack(uint32_t size) { + asm volatile( + "mov %ecx, %eax;" +# if defined(__MINGW32__) + "jmp ___chkstk_ms;" +# else + "jmp __chkstk;" +# endif // __MINGW32__ + ); +} +#endif // _M_X64 + +} // namespace nt +} // namespace mozilla + +#endif // mozilla_NativeNt_h diff --git a/mozglue/misc/PlatformConditionVariable.h b/mozglue/misc/PlatformConditionVariable.h new file mode 100644 index 0000000000..61fb06ade1 --- /dev/null +++ b/mozglue/misc/PlatformConditionVariable.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ConditionVariable_h +#define mozilla_ConditionVariable_h + +#include <stdint.h> + +#include <utility> + +#include "mozilla/Attributes.h" +#include "mozilla/PlatformMutex.h" +#include "mozilla/TimeStamp.h" +#if !defined(XP_WIN) && !defined(__wasi__) +# include <pthread.h> +#endif + +namespace mozilla { + +enum class CVStatus { NoTimeout, Timeout }; + +namespace detail { + +class ConditionVariableImpl { + public: + struct PlatformData; + + MFBT_API ConditionVariableImpl(); + MFBT_API ~ConditionVariableImpl(); + + // Wake one thread that is waiting on this condition. + MFBT_API void notify_one(); + + // Wake all threads that are waiting on this condition. + MFBT_API void notify_all(); + + // Atomically release |lock| and sleep the current thread of execution on + // this condition variable. + // |lock| will be re-acquired before this function returns. + // The thread may be woken from sleep from another thread via notify_one() + // or notify_all(), but may also wake spuriously. The caller should recheck + // its predicate after this function returns, typically in a while loop. + MFBT_API void wait(MutexImpl& lock); + + MFBT_API CVStatus wait_for(MutexImpl& lock, + const mozilla::TimeDuration& rel_time); + + private: + ConditionVariableImpl(const ConditionVariableImpl&) = delete; + ConditionVariableImpl& operator=(const ConditionVariableImpl&) = delete; + + PlatformData* platformData(); + +#if !defined(XP_WIN) && !defined(__wasi__) + void* platformData_[sizeof(pthread_cond_t) / sizeof(void*)]; + static_assert(sizeof(pthread_cond_t) / sizeof(void*) != 0 && + sizeof(pthread_cond_t) % sizeof(void*) == 0, + "pthread_cond_t must have pointer alignment"); +#else + void* platformData_[4]; +#endif +}; + +} // namespace detail + +} // namespace mozilla + +#endif // mozilla_ConditionVariable_h diff --git a/mozglue/misc/PlatformMutex.h b/mozglue/misc/PlatformMutex.h new file mode 100644 index 0000000000..ac5459cf10 --- /dev/null +++ b/mozglue/misc/PlatformMutex.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_PlatformMutex_h +#define mozilla_PlatformMutex_h + +#include <utility> + +#include "mozilla/Attributes.h" +#include "mozilla/Types.h" + +#if !defined(XP_WIN) && !defined(__wasi__) +# include <pthread.h> +#endif + +namespace mozilla { + +namespace detail { + +class ConditionVariableImpl; + +class MutexImpl { + public: + struct PlatformData; + + explicit MFBT_API MutexImpl(); + MFBT_API ~MutexImpl(); + + protected: + MFBT_API void lock(); + MFBT_API void unlock(); + // We have a separate, forwarding API so internal uses don't have to go + // through the PLT. + MFBT_API bool tryLock(); + + private: + MutexImpl(const MutexImpl&) = delete; + void operator=(const MutexImpl&) = delete; + MutexImpl(MutexImpl&&) = delete; + void operator=(MutexImpl&&) = delete; + bool operator==(const MutexImpl& rhs) = delete; + + void mutexLock(); + bool mutexTryLock(); + + PlatformData* platformData(); + +#if !defined(XP_WIN) && !defined(__wasi__) + void* platformData_[sizeof(pthread_mutex_t) / sizeof(void*)]; + static_assert(sizeof(pthread_mutex_t) / sizeof(void*) != 0 && + sizeof(pthread_mutex_t) % sizeof(void*) == 0, + "pthread_mutex_t must have pointer alignment"); +#else + void* platformData_[6]; +#endif + + friend class mozilla::detail::ConditionVariableImpl; +}; + +} // namespace detail + +} // namespace mozilla +#endif // mozilla_PlatformMutex_h diff --git a/mozglue/misc/PlatformRWLock.h b/mozglue/misc/PlatformRWLock.h new file mode 100644 index 0000000000..0acd09b25b --- /dev/null +++ b/mozglue/misc/PlatformRWLock.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_PlatformRWLock_h +#define mozilla_PlatformRWLock_h + +#include "mozilla/Types.h" + +#ifndef XP_WIN +# include <pthread.h> +#endif + +namespace mozilla::detail { + +class RWLockImpl { + public: + explicit MFBT_API RWLockImpl(); + MFBT_API ~RWLockImpl(); + + protected: + [[nodiscard]] MFBT_API bool tryReadLock(); + MFBT_API void readLock(); + MFBT_API void readUnlock(); + + [[nodiscard]] MFBT_API bool tryWriteLock(); + MFBT_API void writeLock(); + MFBT_API void writeUnlock(); + + private: + RWLockImpl(const RWLockImpl&) = delete; + void operator=(const RWLockImpl&) = delete; + RWLockImpl(RWLockImpl&&) = delete; + void operator=(RWLockImpl&&) = delete; + bool operator==(const RWLockImpl& rhs) = delete; + +#ifndef XP_WIN + pthread_rwlock_t mRWLock; +#else + // SRWLock is pointer-sized. We declare it in such a fashion here to avoid + // pulling in windows.h wherever this header is used. + void* mRWLock; +#endif +}; + +} // namespace mozilla::detail + +#endif // mozilla_PlatformRWLock_h diff --git a/mozglue/misc/PreXULSkeletonUI.cpp b/mozglue/misc/PreXULSkeletonUI.cpp new file mode 100644 index 0000000000..1b17837fc1 --- /dev/null +++ b/mozglue/misc/PreXULSkeletonUI.cpp @@ -0,0 +1,2234 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PreXULSkeletonUI.h" + +#include <algorithm> +#include <math.h> +#include <limits.h> +#include <cmath> +#include <locale> +#include <string> +#include <objbase.h> +#include <shlobj.h> + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/BaseProfilerMarkers.h" +#include "mozilla/CacheNtDllThunk.h" +#include "mozilla/FStream.h" +#include "mozilla/GetKnownFolderPath.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/HelperMacros.h" +#include "mozilla/glue/Debug.h" +#include "mozilla/Maybe.h" +#include "mozilla/mscom/ProcessRuntime.h" +#include "mozilla/ResultVariant.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Try.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/Unused.h" +#include "mozilla/WindowsDpiAwareness.h" +#include "mozilla/WindowsProcessMitigations.h" + +namespace mozilla { + +// ColorRect defines an optionally-rounded, optionally-bordered rectangle of a +// particular color that we will draw. +struct ColorRect { + uint32_t color; + uint32_t borderColor; + int x; + int y; + int width; + int height; + int borderWidth; + int borderRadius; + bool flipIfRTL; +}; + +// DrawRect is mostly the same as ColorRect, but exists as an implementation +// detail to simplify drawing borders. We draw borders as a strokeOnly rect +// underneath an inner rect of a particular color. We also need to keep +// track of the backgroundColor for rounding rects, in order to correctly +// anti-alias. +struct DrawRect { + uint32_t color; + uint32_t backgroundColor; + int x; + int y; + int width; + int height; + int borderRadius; + int borderWidth; + bool strokeOnly; +}; + +struct NormalizedRGB { + double r; + double g; + double b; +}; + +NormalizedRGB UintToRGB(uint32_t color) { + double r = static_cast<double>(color >> 16 & 0xff) / 255.0; + double g = static_cast<double>(color >> 8 & 0xff) / 255.0; + double b = static_cast<double>(color >> 0 & 0xff) / 255.0; + return NormalizedRGB{r, g, b}; +} + +uint32_t RGBToUint(const NormalizedRGB& rgb) { + return (static_cast<uint32_t>(rgb.r * 255.0) << 16) | + (static_cast<uint32_t>(rgb.g * 255.0) << 8) | + (static_cast<uint32_t>(rgb.b * 255.0) << 0); +} + +double Lerp(double a, double b, double x) { return a + x * (b - a); } + +NormalizedRGB Lerp(const NormalizedRGB& a, const NormalizedRGB& b, double x) { + return NormalizedRGB{Lerp(a.r, b.r, x), Lerp(a.g, b.g, x), Lerp(a.b, b.b, x)}; +} + +// Produces a smooth curve in [0,1] based on a linear input in [0,1] +double SmoothStep3(double x) { return x * x * (3.0 - 2.0 * x); } + +static const wchar_t kPreXULSkeletonUIKeyPath[] = + L"SOFTWARE" + L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\PreXULSkeletonUISettings"; + +static bool sPreXULSkeletonUIShown = false; +static bool sPreXULSkeletonUIEnabled = false; +static HWND sPreXULSkeletonUIWindow; +static LPWSTR const gStockApplicationIcon = MAKEINTRESOURCEW(32512); +static LPWSTR const gIDCWait = MAKEINTRESOURCEW(32514); +static HANDLE sPreXULSKeletonUIAnimationThread; +static HANDLE sPreXULSKeletonUILockFile = INVALID_HANDLE_VALUE; + +static mozilla::mscom::ProcessRuntime* sProcessRuntime; +static uint32_t* sPixelBuffer = nullptr; +static Vector<ColorRect>* sAnimatedRects = nullptr; +static int sTotalChromeHeight = 0; +static volatile LONG sAnimationControlFlag = 0; +static bool sMaximized = false; +static int sNonClientVerticalMargins = 0; +static int sNonClientHorizontalMargins = 0; +static uint32_t sDpi = 0; + +// Color values needed by the animation loop +static uint32_t sAnimationColor; +static uint32_t sToolbarForegroundColor; + +static ThemeMode sTheme = ThemeMode::Invalid; + +typedef BOOL(WINAPI* EnableNonClientDpiScalingProc)(HWND); +static EnableNonClientDpiScalingProc sEnableNonClientDpiScaling = NULL; +typedef int(WINAPI* GetSystemMetricsForDpiProc)(int, UINT); +GetSystemMetricsForDpiProc sGetSystemMetricsForDpi = NULL; +typedef UINT(WINAPI* GetDpiForWindowProc)(HWND); +GetDpiForWindowProc sGetDpiForWindow = NULL; +typedef ATOM(WINAPI* RegisterClassWProc)(const WNDCLASSW*); +RegisterClassWProc sRegisterClassW = NULL; +typedef HICON(WINAPI* LoadIconWProc)(HINSTANCE, LPCWSTR); +LoadIconWProc sLoadIconW = NULL; +typedef HICON(WINAPI* LoadCursorWProc)(HINSTANCE, LPCWSTR); +LoadCursorWProc sLoadCursorW = NULL; +typedef HWND(WINAPI* CreateWindowExWProc)(DWORD, LPCWSTR, LPCWSTR, DWORD, int, + int, int, int, HWND, HMENU, HINSTANCE, + LPVOID); +CreateWindowExWProc sCreateWindowExW = NULL; +typedef BOOL(WINAPI* ShowWindowProc)(HWND, int); +ShowWindowProc sShowWindow = NULL; +typedef BOOL(WINAPI* SetWindowPosProc)(HWND, HWND, int, int, int, int, UINT); +SetWindowPosProc sSetWindowPos = NULL; +typedef HDC(WINAPI* GetWindowDCProc)(HWND); +GetWindowDCProc sGetWindowDC = NULL; +typedef int(WINAPI* FillRectProc)(HDC, const RECT*, HBRUSH); +FillRectProc sFillRect = NULL; +typedef BOOL(WINAPI* DeleteObjectProc)(HGDIOBJ); +DeleteObjectProc sDeleteObject = NULL; +typedef int(WINAPI* ReleaseDCProc)(HWND, HDC); +ReleaseDCProc sReleaseDC = NULL; +typedef HMONITOR(WINAPI* MonitorFromWindowProc)(HWND, DWORD); +MonitorFromWindowProc sMonitorFromWindow = NULL; +typedef BOOL(WINAPI* GetMonitorInfoWProc)(HMONITOR, LPMONITORINFO); +GetMonitorInfoWProc sGetMonitorInfoW = NULL; +typedef LONG_PTR(WINAPI* SetWindowLongPtrWProc)(HWND, int, LONG_PTR); +SetWindowLongPtrWProc sSetWindowLongPtrW = NULL; +typedef int(WINAPI* StretchDIBitsProc)(HDC, int, int, int, int, int, int, int, + int, const VOID*, const BITMAPINFO*, + UINT, DWORD); +StretchDIBitsProc sStretchDIBits = NULL; +typedef HBRUSH(WINAPI* CreateSolidBrushProc)(COLORREF); +CreateSolidBrushProc sCreateSolidBrush = NULL; + +static int sWindowWidth; +static int sWindowHeight; +static double sCSSToDevPixelScaling; + +static Maybe<PreXULSkeletonUIError> sErrorReason; + +static const int kAnimationCSSPixelsPerFrame = 11; +static const int kAnimationCSSExtraWindowSize = 300; + +// NOTE: these values were pulled out of thin air as round numbers that are +// likely to be too big to be seen in practice. If we legitimately see windows +// this big, we probably don't want to be drawing them on the CPU anyway. +static const uint32_t kMaxWindowWidth = 1 << 16; +static const uint32_t kMaxWindowHeight = 1 << 16; + +static const wchar_t* sEnabledRegSuffix = L"|Enabled"; +static const wchar_t* sScreenXRegSuffix = L"|ScreenX"; +static const wchar_t* sScreenYRegSuffix = L"|ScreenY"; +static const wchar_t* sWidthRegSuffix = L"|Width"; +static const wchar_t* sHeightRegSuffix = L"|Height"; +static const wchar_t* sMaximizedRegSuffix = L"|Maximized"; +static const wchar_t* sUrlbarCSSRegSuffix = L"|UrlbarCSSSpan"; +static const wchar_t* sCssToDevPixelScalingRegSuffix = L"|CssToDevPixelScaling"; +static const wchar_t* sSearchbarRegSuffix = L"|SearchbarCSSSpan"; +static const wchar_t* sSpringsCSSRegSuffix = L"|SpringsCSSSpan"; +static const wchar_t* sThemeRegSuffix = L"|Theme"; +static const wchar_t* sFlagsRegSuffix = L"|Flags"; +static const wchar_t* sProgressSuffix = L"|Progress"; + +std::wstring GetRegValueName(const wchar_t* prefix, const wchar_t* suffix) { + std::wstring result(prefix); + result.append(suffix); + return result; +} + +// This is paraphrased from WinHeaderOnlyUtils.h. The fact that this file is +// included in standalone SpiderMonkey builds prohibits us from including that +// file directly, and it hardly warrants its own header. Bug 1674920 tracks +// only including this file for gecko-related builds. +Result<UniquePtr<wchar_t[]>, PreXULSkeletonUIError> GetBinaryPath() { + DWORD bufLen = MAX_PATH; + UniquePtr<wchar_t[]> buf; + while (true) { + buf = MakeUnique<wchar_t[]>(bufLen); + DWORD retLen = ::GetModuleFileNameW(nullptr, buf.get(), bufLen); + if (!retLen) { + return Err(PreXULSkeletonUIError::FilesystemFailure); + } + + if (retLen == bufLen && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + bufLen *= 2; + continue; + } + + break; + } + + return buf; +} + +// PreXULSkeletonUIDisallowed means that we don't even have the capacity to +// enable the skeleton UI, whether because we're on a platform that doesn't +// support it or because we launched with command line arguments that we don't +// support. Some of these situations are transient, so we want to make sure we +// don't mess with registry values in these scenarios that we may use in +// other scenarios in which the skeleton UI is actually enabled. +static bool PreXULSkeletonUIDisallowed() { + return sErrorReason.isSome() && + (*sErrorReason == PreXULSkeletonUIError::Cmdline || + *sErrorReason == PreXULSkeletonUIError::EnvVars); +} + +// Note: this is specifically *not* a robust, multi-locale lowercasing +// operation. It is not intended to be such. It is simply intended to match the +// way in which we look for other instances of firefox to remote into. +// See +// https://searchfox.org/mozilla-central/rev/71621bfa47a371f2b1ccfd33c704913124afb933/toolkit/components/remote/nsRemoteService.cpp#56 +static void MutateStringToLowercase(wchar_t* ptr) { + while (*ptr) { + wchar_t ch = *ptr; + if (ch >= L'A' && ch <= L'Z') { + *ptr = ch + (L'a' - L'A'); + } + ++ptr; + } +} + +static Result<Ok, PreXULSkeletonUIError> GetSkeletonUILock() { + auto localAppDataPath = GetKnownFolderPath(FOLDERID_LocalAppData); + if (!localAppDataPath) { + return Err(PreXULSkeletonUIError::FilesystemFailure); + } + + if (sPreXULSKeletonUILockFile != INVALID_HANDLE_VALUE) { + return Ok(); + } + + // Note: because we're in mozglue, we cannot easily access things from + // toolkit, like `GetInstallHash`. We could move `GetInstallHash` into + // mozglue, and rip out all of its usage of types defined in toolkit headers. + // However, it seems cleaner to just hash the bin path ourselves. We don't + // get quite the same robustness that `GetInstallHash` might provide, but + // we already don't have that with how we key our registry values, so it + // probably makes sense to just match those. + UniquePtr<wchar_t[]> binPath; + MOZ_TRY_VAR(binPath, GetBinaryPath()); + + // Lowercase the binpath to match how we look for remote instances. + MutateStringToLowercase(binPath.get()); + + // The number of bytes * 2 characters per byte + 1 for the null terminator + uint32_t hexHashSize = sizeof(uint32_t) * 2 + 1; + UniquePtr<wchar_t[]> installHash = MakeUnique<wchar_t[]>(hexHashSize); + // This isn't perfect - it's a 32-bit hash of the path to our executable. It + // could reasonably collide, or casing could potentially affect things, but + // the theory is that that should be uncommon enough and the failure case + // mild enough that this is fine. + uint32_t binPathHash = HashString(binPath.get()); + swprintf(installHash.get(), hexHashSize, L"%08x", binPathHash); + + std::wstring lockFilePath; + lockFilePath.append(localAppDataPath.get()); + lockFilePath.append( + L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\SkeletonUILock-"); + lockFilePath.append(installHash.get()); + + // We intentionally leak this file - that is okay, and (kind of) the point. + // We want to hold onto this handle until the application exits, and hold + // onto it with exclusive rights. If this check fails, then we assume that + // another instance of the executable is holding it, and thus return false. + sPreXULSKeletonUILockFile = + ::CreateFileW(lockFilePath.c_str(), GENERIC_READ | GENERIC_WRITE, + 0, // No sharing - this is how the lock works + nullptr, CREATE_ALWAYS, + FILE_FLAG_DELETE_ON_CLOSE, // Don't leave this lying around + nullptr); + if (sPreXULSKeletonUILockFile == INVALID_HANDLE_VALUE) { + return Err(PreXULSkeletonUIError::FailedGettingLock); + } + + return Ok(); +} + +const char kGeneralSection[] = "[General]"; +const char kStartWithLastProfile[] = "StartWithLastProfile="; + +static bool ProfileDbHasStartWithLastProfile(IFStream& iniContents) { + bool inGeneral = false; + std::string line; + while (std::getline(iniContents, line)) { + size_t whitespace = 0; + while (line.length() > whitespace && + (line[whitespace] == ' ' || line[whitespace] == '\t')) { + whitespace++; + } + line.erase(0, whitespace); + + if (line.compare(kGeneralSection) == 0) { + inGeneral = true; + } else if (inGeneral) { + if (line[0] == '[') { + inGeneral = false; + } else { + if (line.find(kStartWithLastProfile) == 0) { + char val = line.c_str()[sizeof(kStartWithLastProfile) - 1]; + if (val == '0') { + return false; + } else if (val == '1') { + return true; + } + } + } + } + } + + // If we don't find it in the .ini file, we interpret that as true + return true; +} + +static Result<Ok, PreXULSkeletonUIError> CheckForStartWithLastProfile() { + auto roamingAppData = GetKnownFolderPath(FOLDERID_RoamingAppData); + if (!roamingAppData) { + return Err(PreXULSkeletonUIError::FilesystemFailure); + } + std::wstring profileDbPath(roamingAppData.get()); + profileDbPath.append( + L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\profiles.ini"); + IFStream profileDb(profileDbPath.c_str()); + if (profileDb.fail()) { + return Err(PreXULSkeletonUIError::FilesystemFailure); + } + + if (!ProfileDbHasStartWithLastProfile(profileDb)) { + return Err(PreXULSkeletonUIError::NoStartWithLastProfile); + } + + return Ok(); +} + +// We could use nsAutoRegKey, but including nsWindowsHelpers.h causes build +// failures in random places because we're in mozglue. Overall it should be +// simpler and cleaner to just step around that issue with this class: +class MOZ_RAII AutoCloseRegKey { + public: + explicit AutoCloseRegKey(HKEY key) : mKey(key) {} + ~AutoCloseRegKey() { ::RegCloseKey(mKey); } + + private: + HKEY mKey; +}; + +int CSSToDevPixels(double cssPixels, double scaling) { + return floor(cssPixels * scaling + 0.5); +} + +int CSSToDevPixels(int cssPixels, double scaling) { + return CSSToDevPixels((double)cssPixels, scaling); +} + +int CSSToDevPixelsFloor(double cssPixels, double scaling) { + return floor(cssPixels * scaling); +} + +// Some things appear to floor to device pixels rather than rounding. A good +// example of this is border widths. +int CSSToDevPixelsFloor(int cssPixels, double scaling) { + return CSSToDevPixelsFloor((double)cssPixels, scaling); +} + +double SignedDistanceToCircle(double x, double y, double radius) { + return sqrt(x * x + y * y) - radius; +} + +// For more details, see +// https://searchfox.org/mozilla-central/rev/a5d9abfda1e26b1207db9549549ab0bdd73f735d/gfx/wr/webrender/res/shared.glsl#141-187 +// which was a reference for this function. +double DistanceAntiAlias(double signedDistance) { + // Distance assumed to be in device pixels. We use an aa range of 0.5 for + // reasons detailed in the linked code above. + const double aaRange = 0.5; + double dist = 0.5 * signedDistance / aaRange; + if (dist <= -0.5 + std::numeric_limits<double>::epsilon()) return 1.0; + if (dist >= 0.5 - std::numeric_limits<double>::epsilon()) return 0.0; + return 0.5 + dist * (0.8431027 * dist * dist - 1.14453603); +} + +void RasterizeRoundedRectTopAndBottom(const DrawRect& rect) { + if (rect.height <= 2 * rect.borderRadius) { + MOZ_ASSERT(false, "Skeleton UI rect height too small for border radius."); + return; + } + if (rect.width <= 2 * rect.borderRadius) { + MOZ_ASSERT(false, "Skeleton UI rect width too small for border radius."); + return; + } + + NormalizedRGB rgbBase = UintToRGB(rect.backgroundColor); + NormalizedRGB rgbBlend = UintToRGB(rect.color); + + for (int rowIndex = 0; rowIndex < rect.borderRadius; ++rowIndex) { + int yTop = rect.y + rect.borderRadius - 1 - rowIndex; + int yBottom = rect.y + rect.height - rect.borderRadius + rowIndex; + + uint32_t* lineStartTop = &sPixelBuffer[yTop * sWindowWidth]; + uint32_t* innermostPixelTopLeft = + lineStartTop + rect.x + rect.borderRadius - 1; + uint32_t* innermostPixelTopRight = + lineStartTop + rect.x + rect.width - rect.borderRadius; + uint32_t* lineStartBottom = &sPixelBuffer[yBottom * sWindowWidth]; + uint32_t* innermostPixelBottomLeft = + lineStartBottom + rect.x + rect.borderRadius - 1; + uint32_t* innermostPixelBottomRight = + lineStartBottom + rect.x + rect.width - rect.borderRadius; + + // Add 0.5 to x and y to get the pixel center. + double pixelY = (double)rowIndex + 0.5; + for (int columnIndex = 0; columnIndex < rect.borderRadius; ++columnIndex) { + double pixelX = (double)columnIndex + 0.5; + double distance = + SignedDistanceToCircle(pixelX, pixelY, (double)rect.borderRadius); + double alpha = DistanceAntiAlias(distance); + NormalizedRGB rgb = Lerp(rgbBase, rgbBlend, alpha); + uint32_t color = RGBToUint(rgb); + + innermostPixelTopLeft[-columnIndex] = color; + innermostPixelTopRight[columnIndex] = color; + innermostPixelBottomLeft[-columnIndex] = color; + innermostPixelBottomRight[columnIndex] = color; + } + + std::fill(innermostPixelTopLeft + 1, innermostPixelTopRight, rect.color); + std::fill(innermostPixelBottomLeft + 1, innermostPixelBottomRight, + rect.color); + } +} + +void RasterizeAnimatedRoundedRectTopAndBottom( + const ColorRect& colorRect, const uint32_t* animationLookup, + int priorUpdateAreaMin, int priorUpdateAreaMax, int currentUpdateAreaMin, + int currentUpdateAreaMax, int animationMin) { + // We iterate through logical pixel rows here, from inside to outside, which + // for the top of the rounded rect means from bottom to top, and for the + // bottom of the rect means top to bottom. We paint pixels from left to + // right on the top and bottom rows at the same time for the entire animation + // window. (If the animation window does not overlap any rounded corners, + // however, we won't be called at all) + for (int rowIndex = 0; rowIndex < colorRect.borderRadius; ++rowIndex) { + int yTop = colorRect.y + colorRect.borderRadius - 1 - rowIndex; + int yBottom = + colorRect.y + colorRect.height - colorRect.borderRadius + rowIndex; + + uint32_t* lineStartTop = &sPixelBuffer[yTop * sWindowWidth]; + uint32_t* lineStartBottom = &sPixelBuffer[yBottom * sWindowWidth]; + + // Add 0.5 to x and y to get the pixel center. + double pixelY = (double)rowIndex + 0.5; + for (int x = priorUpdateAreaMin; x < currentUpdateAreaMax; ++x) { + // The column index is the distance from the innermost pixel, which + // is different depending on whether we're on the left or right + // side of the rect. It will always be the max here, and if it's + // negative that just means we're outside the rounded area. + int columnIndex = + std::max((int)colorRect.x + (int)colorRect.borderRadius - x - 1, + x - ((int)colorRect.x + (int)colorRect.width - + (int)colorRect.borderRadius)); + + double alpha = 1.0; + if (columnIndex >= 0) { + double pixelX = (double)columnIndex + 0.5; + double distance = SignedDistanceToCircle( + pixelX, pixelY, (double)colorRect.borderRadius); + alpha = DistanceAntiAlias(distance); + } + // We don't do alpha blending for the antialiased pixels at the + // shape's border. It is not noticeable in the animation. + if (alpha > 1.0 - std::numeric_limits<double>::epsilon()) { + // Overwrite the tail end of last frame's animation with the + // rect's normal, unanimated color. + uint32_t color = x < priorUpdateAreaMax + ? colorRect.color + : animationLookup[x - animationMin]; + lineStartTop[x] = color; + lineStartBottom[x] = color; + } + } + } +} + +void RasterizeColorRect(const ColorRect& colorRect) { + // We sometimes split our rect into two, to simplify drawing borders. If we + // have a border, we draw a stroke-only rect first, and then draw the smaller + // inner rect on top of it. + Vector<DrawRect, 2> drawRects; + Unused << drawRects.reserve(2); + if (colorRect.borderWidth == 0) { + DrawRect rect = {}; + rect.color = colorRect.color; + rect.backgroundColor = + sPixelBuffer[colorRect.y * sWindowWidth + colorRect.x]; + rect.x = colorRect.x; + rect.y = colorRect.y; + rect.width = colorRect.width; + rect.height = colorRect.height; + rect.borderRadius = colorRect.borderRadius; + rect.strokeOnly = false; + drawRects.infallibleAppend(rect); + } else { + DrawRect borderRect = {}; + borderRect.color = colorRect.borderColor; + borderRect.backgroundColor = + sPixelBuffer[colorRect.y * sWindowWidth + colorRect.x]; + borderRect.x = colorRect.x; + borderRect.y = colorRect.y; + borderRect.width = colorRect.width; + borderRect.height = colorRect.height; + borderRect.borderRadius = colorRect.borderRadius; + borderRect.borderWidth = colorRect.borderWidth; + borderRect.strokeOnly = true; + drawRects.infallibleAppend(borderRect); + + DrawRect baseRect = {}; + baseRect.color = colorRect.color; + baseRect.backgroundColor = borderRect.color; + baseRect.x = colorRect.x + colorRect.borderWidth; + baseRect.y = colorRect.y + colorRect.borderWidth; + baseRect.width = colorRect.width - 2 * colorRect.borderWidth; + baseRect.height = colorRect.height - 2 * colorRect.borderWidth; + baseRect.borderRadius = + std::max(0, (int)colorRect.borderRadius - (int)colorRect.borderWidth); + baseRect.borderWidth = 0; + baseRect.strokeOnly = false; + drawRects.infallibleAppend(baseRect); + } + + for (const DrawRect& rect : drawRects) { + if (rect.height <= 0 || rect.width <= 0) { + continue; + } + + // For rounded rectangles, the first thing we do is draw the top and + // bottom of the rectangle, with the more complicated logic below. After + // that we can just draw the vertically centered part of the rect like + // normal. + RasterizeRoundedRectTopAndBottom(rect); + + // We then draw the flat, central portion of the rect (which in the case of + // non-rounded rects, is just the entire thing.) + int solidRectStartY = + std::clamp(rect.y + rect.borderRadius, 0, sTotalChromeHeight); + int solidRectEndY = std::clamp(rect.y + rect.height - rect.borderRadius, 0, + sTotalChromeHeight); + for (int y = solidRectStartY; y < solidRectEndY; ++y) { + // For strokeOnly rects (used to draw borders), we just draw the left + // and right side here. Looping down a column of pixels is not the most + // cache-friendly thing, but it shouldn't be a big deal given the height + // of the urlbar. + // Also, if borderRadius is less than borderWidth, we need to ensure + // that we fully draw the top and bottom lines, so we make sure to check + // that we're inside the middle range range before excluding pixels. + if (rect.strokeOnly && y - rect.y > rect.borderWidth && + rect.y + rect.height - y > rect.borderWidth) { + int startXLeft = std::clamp(rect.x, 0, sWindowWidth); + int endXLeft = std::clamp(rect.x + rect.borderWidth, 0, sWindowWidth); + int startXRight = + std::clamp(rect.x + rect.width - rect.borderWidth, 0, sWindowWidth); + int endXRight = std::clamp(rect.x + rect.width, 0, sWindowWidth); + + uint32_t* lineStart = &sPixelBuffer[y * sWindowWidth]; + uint32_t* dataStartLeft = lineStart + startXLeft; + uint32_t* dataEndLeft = lineStart + endXLeft; + uint32_t* dataStartRight = lineStart + startXRight; + uint32_t* dataEndRight = lineStart + endXRight; + std::fill(dataStartLeft, dataEndLeft, rect.color); + std::fill(dataStartRight, dataEndRight, rect.color); + } else { + int startX = std::clamp(rect.x, 0, sWindowWidth); + int endX = std::clamp(rect.x + rect.width, 0, sWindowWidth); + uint32_t* lineStart = &sPixelBuffer[y * sWindowWidth]; + uint32_t* dataStart = lineStart + startX; + uint32_t* dataEnd = lineStart + endX; + std::fill(dataStart, dataEnd, rect.color); + } + } + } +} + +// Paints the pixels to sPixelBuffer for the skeleton UI animation (a light +// gradient which moves from left to right across the grey placeholder rects). +// Takes in the rect to draw, together with a lookup table for the gradient, +// and the bounds of the previous and current frame of the animation. +bool RasterizeAnimatedRect(const ColorRect& colorRect, + const uint32_t* animationLookup, + int priorAnimationMin, int animationMin, + int animationMax) { + int rectMin = colorRect.x; + int rectMax = colorRect.x + colorRect.width; + bool animationWindowOverlaps = + rectMax >= priorAnimationMin && rectMin < animationMax; + + int priorUpdateAreaMin = std::max(rectMin, priorAnimationMin); + int priorUpdateAreaMax = std::min(rectMax, animationMin); + int currentUpdateAreaMin = std::max(rectMin, animationMin); + int currentUpdateAreaMax = std::min(rectMax, animationMax); + + if (!animationWindowOverlaps) { + return false; + } + + bool animationWindowOverlapsBorderRadius = + rectMin + colorRect.borderRadius > priorAnimationMin || + rectMax - colorRect.borderRadius <= animationMax; + + // If we don't overlap the left or right side of the rounded rectangle, + // just pretend it's not rounded. This is a small optimization but + // there's no point in doing all of this rounded rectangle checking if + // we aren't even overlapping + int borderRadius = + animationWindowOverlapsBorderRadius ? colorRect.borderRadius : 0; + + if (borderRadius > 0) { + // Similarly to how we draw the rounded rects in DrawSkeletonUI, we + // first draw the rounded top and bottom, and then we draw the center + // rect. + RasterizeAnimatedRoundedRectTopAndBottom( + colorRect, animationLookup, priorUpdateAreaMin, priorUpdateAreaMax, + currentUpdateAreaMin, currentUpdateAreaMax, animationMin); + } + + for (int y = colorRect.y + borderRadius; + y < colorRect.y + colorRect.height - borderRadius; ++y) { + uint32_t* lineStart = &sPixelBuffer[y * sWindowWidth]; + // Overwrite the tail end of last frame's animation with the rect's + // normal, unanimated color. + for (int x = priorUpdateAreaMin; x < priorUpdateAreaMax; ++x) { + lineStart[x] = colorRect.color; + } + // Then apply the animated color + for (int x = currentUpdateAreaMin; x < currentUpdateAreaMax; ++x) { + lineStart[x] = animationLookup[x - animationMin]; + } + } + + return true; +} + +Result<Ok, PreXULSkeletonUIError> DrawSkeletonUI( + HWND hWnd, CSSPixelSpan urlbarCSSSpan, CSSPixelSpan searchbarCSSSpan, + Vector<CSSPixelSpan>& springs, const ThemeColors& currentTheme, + const EnumSet<SkeletonUIFlag, uint32_t>& flags) { + // NOTE: we opt here to paint a pixel buffer for the application chrome by + // hand, without using native UI library methods. Why do we do this? + // + // 1) It gives us a little bit more control, especially if we want to animate + // any of this. + // 2) It's actually more portable. We can do this on any platform where we + // can blit a pixel buffer to the screen, and it only has to change + // insofar as the UI is different on those platforms (and thus would have + // to change anyway.) + // + // The performance impact of this ought to be negligible. As far as has been + // observed, on slow reference hardware this might take up to a millisecond, + // for a startup which otherwise takes 30 seconds. + // + // The readability and maintainability are a greater concern. When the + // silhouette of Firefox's core UI changes, this code will likely need to + // change. However, for the foreseeable future, our skeleton UI will be mostly + // axis-aligned geometric shapes, and the thought is that any code which is + // manipulating raw pixels should not be *too* hard to maintain and + // understand so long as it is only painting such simple shapes. + + sAnimationColor = currentTheme.animationColor; + sToolbarForegroundColor = currentTheme.toolbarForegroundColor; + + bool menubarShown = flags.contains(SkeletonUIFlag::MenubarShown); + bool bookmarksToolbarShown = + flags.contains(SkeletonUIFlag::BookmarksToolbarShown); + bool rtlEnabled = flags.contains(SkeletonUIFlag::RtlEnabled); + + int chromeHorMargin = CSSToDevPixels(2, sCSSToDevPixelScaling); + int verticalOffset = sMaximized ? sNonClientVerticalMargins : 0; + int horizontalOffset = + sNonClientHorizontalMargins - (sMaximized ? 0 : chromeHorMargin); + + // found in tabs.inc.css, "--tab-min-height" + 2 * "--tab-block-margin" + int tabBarHeight = CSSToDevPixels(44, sCSSToDevPixelScaling); + int selectedTabBorderWidth = CSSToDevPixels(2, sCSSToDevPixelScaling); + // found in tabs.inc.css, "--tab-block-margin" + int titlebarSpacerWidth = horizontalOffset + + CSSToDevPixels(2, sCSSToDevPixelScaling) - + selectedTabBorderWidth; + if (!sMaximized && !menubarShown) { + // found in tabs.inc.css, ".titlebar-spacer" + titlebarSpacerWidth += CSSToDevPixels(40, sCSSToDevPixelScaling); + } + // found in tabs.inc.css, "--tab-block-margin" + int selectedTabMarginTop = + CSSToDevPixels(4, sCSSToDevPixelScaling) - selectedTabBorderWidth; + int selectedTabMarginBottom = + CSSToDevPixels(4, sCSSToDevPixelScaling) - selectedTabBorderWidth; + int selectedTabBorderRadius = CSSToDevPixels(4, sCSSToDevPixelScaling); + int selectedTabWidth = + CSSToDevPixels(221, sCSSToDevPixelScaling) + 2 * selectedTabBorderWidth; + int toolbarHeight = CSSToDevPixels(40, sCSSToDevPixelScaling); + // found in browser.css, "#PersonalToolbar" + int bookmarkToolbarHeight = CSSToDevPixels(28, sCSSToDevPixelScaling); + if (bookmarksToolbarShown) { + toolbarHeight += bookmarkToolbarHeight; + } + // found in urlbar-searchbar.inc.css, "#urlbar[breakout]" + int urlbarTopOffset = CSSToDevPixels(4, sCSSToDevPixelScaling); + int urlbarHeight = CSSToDevPixels(32, sCSSToDevPixelScaling); + // found in browser-aero.css, "#navigator-toolbox::after" border-bottom + int chromeContentDividerHeight = CSSToDevPixels(1, sCSSToDevPixelScaling); + + int tabPlaceholderBarMarginTop = CSSToDevPixels(14, sCSSToDevPixelScaling); + int tabPlaceholderBarMarginLeft = CSSToDevPixels(10, sCSSToDevPixelScaling); + int tabPlaceholderBarHeight = CSSToDevPixels(10, sCSSToDevPixelScaling); + int tabPlaceholderBarWidth = CSSToDevPixels(120, sCSSToDevPixelScaling); + + int toolbarPlaceholderHeight = CSSToDevPixels(10, sCSSToDevPixelScaling); + int toolbarPlaceholderMarginRight = + rtlEnabled ? CSSToDevPixels(11, sCSSToDevPixelScaling) + : CSSToDevPixels(9, sCSSToDevPixelScaling); + int toolbarPlaceholderMarginLeft = + rtlEnabled ? CSSToDevPixels(9, sCSSToDevPixelScaling) + : CSSToDevPixels(11, sCSSToDevPixelScaling); + int placeholderMargin = CSSToDevPixels(8, sCSSToDevPixelScaling); + + int menubarHeightDevPixels = + menubarShown ? CSSToDevPixels(28, sCSSToDevPixelScaling) : 0; + + // defined in urlbar-searchbar.inc.css as --urlbar-margin-inline: 5px + int urlbarMargin = + CSSToDevPixels(5, sCSSToDevPixelScaling) + horizontalOffset; + + int urlbarTextPlaceholderMarginTop = + CSSToDevPixels(12, sCSSToDevPixelScaling); + int urlbarTextPlaceholderMarginLeft = + CSSToDevPixels(12, sCSSToDevPixelScaling); + int urlbarTextPlaceHolderWidth = CSSToDevPixels( + std::clamp(urlbarCSSSpan.end - urlbarCSSSpan.start - 10.0, 0.0, 260.0), + sCSSToDevPixelScaling); + int urlbarTextPlaceholderHeight = CSSToDevPixels(10, sCSSToDevPixelScaling); + + int searchbarTextPlaceholderWidth = CSSToDevPixels(62, sCSSToDevPixelScaling); + + auto scopeExit = MakeScopeExit([&] { + delete sAnimatedRects; + sAnimatedRects = nullptr; + }); + + Vector<ColorRect> rects; + + ColorRect menubar = {}; + menubar.color = currentTheme.tabBarColor; + menubar.x = 0; + menubar.y = verticalOffset; + menubar.width = sWindowWidth; + menubar.height = menubarHeightDevPixels; + menubar.flipIfRTL = false; + if (!rects.append(menubar)) { + return Err(PreXULSkeletonUIError::OOM); + } + + int placeholderBorderRadius = CSSToDevPixels(4, sCSSToDevPixelScaling); + // found in browser.css "--toolbarbutton-border-radius" + int urlbarBorderRadius = CSSToDevPixels(4, sCSSToDevPixelScaling); + + // The (traditionally dark blue on Windows) background of the tab bar. + ColorRect tabBar = {}; + tabBar.color = currentTheme.tabBarColor; + tabBar.x = 0; + tabBar.y = menubar.y + menubar.height; + tabBar.width = sWindowWidth; + tabBar.height = tabBarHeight; + tabBar.flipIfRTL = false; + if (!rects.append(tabBar)) { + return Err(PreXULSkeletonUIError::OOM); + } + + // The initial selected tab + ColorRect selectedTab = {}; + selectedTab.color = currentTheme.tabColor; + selectedTab.x = titlebarSpacerWidth; + selectedTab.y = menubar.y + menubar.height + selectedTabMarginTop; + selectedTab.width = selectedTabWidth; + selectedTab.height = + tabBar.y + tabBar.height - selectedTab.y - selectedTabMarginBottom; + selectedTab.borderColor = currentTheme.tabOutlineColor; + selectedTab.borderWidth = selectedTabBorderWidth; + selectedTab.borderRadius = selectedTabBorderRadius; + selectedTab.flipIfRTL = true; + if (!rects.append(selectedTab)) { + return Err(PreXULSkeletonUIError::OOM); + } + + // A placeholder rect representing text that will fill the selected tab title + ColorRect tabTextPlaceholder = {}; + tabTextPlaceholder.color = currentTheme.toolbarForegroundColor; + tabTextPlaceholder.x = selectedTab.x + tabPlaceholderBarMarginLeft; + tabTextPlaceholder.y = selectedTab.y + tabPlaceholderBarMarginTop; + tabTextPlaceholder.width = tabPlaceholderBarWidth; + tabTextPlaceholder.height = tabPlaceholderBarHeight; + tabTextPlaceholder.borderRadius = placeholderBorderRadius; + tabTextPlaceholder.flipIfRTL = true; + if (!rects.append(tabTextPlaceholder)) { + return Err(PreXULSkeletonUIError::OOM); + } + + // The toolbar background + ColorRect toolbar = {}; + toolbar.color = currentTheme.backgroundColor; + toolbar.x = 0; + toolbar.y = tabBar.y + tabBarHeight; + toolbar.width = sWindowWidth; + toolbar.height = toolbarHeight; + toolbar.flipIfRTL = false; + if (!rects.append(toolbar)) { + return Err(PreXULSkeletonUIError::OOM); + } + + // The single-pixel divider line below the toolbar + ColorRect chromeContentDivider = {}; + chromeContentDivider.color = currentTheme.chromeContentDividerColor; + chromeContentDivider.x = 0; + chromeContentDivider.y = toolbar.y + toolbar.height; + chromeContentDivider.width = sWindowWidth; + chromeContentDivider.height = chromeContentDividerHeight; + chromeContentDivider.flipIfRTL = false; + if (!rects.append(chromeContentDivider)) { + return Err(PreXULSkeletonUIError::OOM); + } + + // The urlbar + ColorRect urlbar = {}; + urlbar.color = currentTheme.urlbarColor; + urlbar.x = CSSToDevPixels(urlbarCSSSpan.start, sCSSToDevPixelScaling) + + horizontalOffset; + urlbar.y = tabBar.y + tabBarHeight + urlbarTopOffset; + urlbar.width = CSSToDevPixels((urlbarCSSSpan.end - urlbarCSSSpan.start), + sCSSToDevPixelScaling); + urlbar.height = urlbarHeight; + urlbar.borderColor = currentTheme.urlbarBorderColor; + urlbar.borderWidth = CSSToDevPixels(1, sCSSToDevPixelScaling); + urlbar.borderRadius = urlbarBorderRadius; + urlbar.flipIfRTL = false; + if (!rects.append(urlbar)) { + return Err(PreXULSkeletonUIError::OOM); + } + + // The urlbar placeholder rect representating text that will fill the urlbar + // If rtl is enabled, it is flipped relative to the the urlbar rectangle, not + // sWindowWidth. + ColorRect urlbarTextPlaceholder = {}; + urlbarTextPlaceholder.color = currentTheme.toolbarForegroundColor; + urlbarTextPlaceholder.x = + rtlEnabled + ? ((urlbar.x + urlbar.width) - urlbarTextPlaceholderMarginLeft - + urlbarTextPlaceHolderWidth) + : (urlbar.x + urlbarTextPlaceholderMarginLeft); + urlbarTextPlaceholder.y = urlbar.y + urlbarTextPlaceholderMarginTop; + urlbarTextPlaceholder.width = urlbarTextPlaceHolderWidth; + urlbarTextPlaceholder.height = urlbarTextPlaceholderHeight; + urlbarTextPlaceholder.borderRadius = placeholderBorderRadius; + urlbarTextPlaceholder.flipIfRTL = false; + if (!rects.append(urlbarTextPlaceholder)) { + return Err(PreXULSkeletonUIError::OOM); + } + + // The searchbar and placeholder text, if present + // This is y-aligned with the urlbar + bool hasSearchbar = searchbarCSSSpan.start != 0 && searchbarCSSSpan.end != 0; + ColorRect searchbarRect = {}; + if (hasSearchbar == true) { + searchbarRect.color = currentTheme.urlbarColor; + searchbarRect.x = + CSSToDevPixels(searchbarCSSSpan.start, sCSSToDevPixelScaling) + + horizontalOffset; + searchbarRect.y = urlbar.y; + searchbarRect.width = CSSToDevPixels( + searchbarCSSSpan.end - searchbarCSSSpan.start, sCSSToDevPixelScaling); + searchbarRect.height = urlbarHeight; + searchbarRect.borderRadius = urlbarBorderRadius; + searchbarRect.borderColor = currentTheme.urlbarBorderColor; + searchbarRect.borderWidth = CSSToDevPixels(1, sCSSToDevPixelScaling); + searchbarRect.flipIfRTL = false; + if (!rects.append(searchbarRect)) { + return Err(PreXULSkeletonUIError::OOM); + } + + // The placeholder rect representating text that will fill the searchbar + // This uses the same margins as the urlbarTextPlaceholder + // If rtl is enabled, it is flipped relative to the the searchbar rectangle, + // not sWindowWidth. + ColorRect searchbarTextPlaceholder = {}; + searchbarTextPlaceholder.color = currentTheme.toolbarForegroundColor; + searchbarTextPlaceholder.x = + rtlEnabled + ? ((searchbarRect.x + searchbarRect.width) - + urlbarTextPlaceholderMarginLeft - searchbarTextPlaceholderWidth) + : (searchbarRect.x + urlbarTextPlaceholderMarginLeft); + searchbarTextPlaceholder.y = + searchbarRect.y + urlbarTextPlaceholderMarginTop; + searchbarTextPlaceholder.width = searchbarTextPlaceholderWidth; + searchbarTextPlaceholder.height = urlbarTextPlaceholderHeight; + searchbarTextPlaceholder.flipIfRTL = false; + if (!rects.append(searchbarTextPlaceholder) || + !sAnimatedRects->append(searchbarTextPlaceholder)) { + return Err(PreXULSkeletonUIError::OOM); + } + } + + // Determine where the placeholder rectangles should not go. This is + // anywhere occupied by a spring, urlbar, or searchbar + Vector<DevPixelSpan> noPlaceholderSpans; + + DevPixelSpan urlbarSpan; + urlbarSpan.start = urlbar.x - urlbarMargin; + urlbarSpan.end = urlbar.width + urlbar.x + urlbarMargin; + + DevPixelSpan searchbarSpan; + if (hasSearchbar) { + searchbarSpan.start = searchbarRect.x - urlbarMargin; + searchbarSpan.end = searchbarRect.width + searchbarRect.x + urlbarMargin; + } + + DevPixelSpan marginLeftPlaceholder; + marginLeftPlaceholder.start = toolbarPlaceholderMarginLeft; + marginLeftPlaceholder.end = toolbarPlaceholderMarginLeft; + if (!noPlaceholderSpans.append(marginLeftPlaceholder)) { + return Err(PreXULSkeletonUIError::OOM); + } + + if (rtlEnabled) { + // If we're RTL, then the springs as ordered in the DOM will be from right + // to left, which will break our comparison logic below + springs.reverse(); + } + + for (auto spring : springs) { + DevPixelSpan springDevPixels; + springDevPixels.start = + CSSToDevPixels(spring.start, sCSSToDevPixelScaling) + horizontalOffset; + springDevPixels.end = + CSSToDevPixels(spring.end, sCSSToDevPixelScaling) + horizontalOffset; + if (!noPlaceholderSpans.append(springDevPixels)) { + return Err(PreXULSkeletonUIError::OOM); + } + } + + DevPixelSpan marginRightPlaceholder; + marginRightPlaceholder.start = sWindowWidth - toolbarPlaceholderMarginRight; + marginRightPlaceholder.end = sWindowWidth - toolbarPlaceholderMarginRight; + if (!noPlaceholderSpans.append(marginRightPlaceholder)) { + return Err(PreXULSkeletonUIError::OOM); + } + + Vector<DevPixelSpan, 2> spansToAdd; + Unused << spansToAdd.reserve(2); + spansToAdd.infallibleAppend(urlbarSpan); + if (hasSearchbar) { + spansToAdd.infallibleAppend(searchbarSpan); + } + + for (auto& toAdd : spansToAdd) { + for (auto& span : noPlaceholderSpans) { + if (span.start > toAdd.start) { + if (!noPlaceholderSpans.insert(&span, toAdd)) { + return Err(PreXULSkeletonUIError::OOM); + } + break; + } + } + } + + for (size_t i = 1; i < noPlaceholderSpans.length(); i++) { + int start = noPlaceholderSpans[i - 1].end + placeholderMargin; + int end = noPlaceholderSpans[i].start - placeholderMargin; + if (start + 2 * placeholderBorderRadius >= end) { + continue; + } + + // The placeholder rects should all be y-aligned. + ColorRect placeholderRect = {}; + placeholderRect.color = currentTheme.toolbarForegroundColor; + placeholderRect.x = start; + placeholderRect.y = urlbarTextPlaceholder.y; + placeholderRect.width = end - start; + placeholderRect.height = toolbarPlaceholderHeight; + placeholderRect.borderRadius = placeholderBorderRadius; + placeholderRect.flipIfRTL = false; + if (!rects.append(placeholderRect) || + !sAnimatedRects->append(placeholderRect)) { + return Err(PreXULSkeletonUIError::OOM); + } + } + + sTotalChromeHeight = chromeContentDivider.y + chromeContentDivider.height; + if (sTotalChromeHeight > sWindowHeight) { + return Err(PreXULSkeletonUIError::BadWindowDimensions); + } + + if (!sAnimatedRects->append(tabTextPlaceholder) || + !sAnimatedRects->append(urlbarTextPlaceholder)) { + return Err(PreXULSkeletonUIError::OOM); + } + + sPixelBuffer = + (uint32_t*)calloc(sWindowWidth * sTotalChromeHeight, sizeof(uint32_t)); + + for (auto& rect : *sAnimatedRects) { + if (rtlEnabled && rect.flipIfRTL) { + rect.x = sWindowWidth - rect.x - rect.width; + } + rect.x = std::clamp(rect.x, 0, sWindowWidth); + rect.width = std::clamp(rect.width, 0, sWindowWidth - rect.x); + rect.y = std::clamp(rect.y, 0, sTotalChromeHeight); + rect.height = std::clamp(rect.height, 0, sTotalChromeHeight - rect.y); + } + + for (auto& rect : rects) { + if (rtlEnabled && rect.flipIfRTL) { + rect.x = sWindowWidth - rect.x - rect.width; + } + rect.x = std::clamp(rect.x, 0, sWindowWidth); + rect.width = std::clamp(rect.width, 0, sWindowWidth - rect.x); + rect.y = std::clamp(rect.y, 0, sTotalChromeHeight); + rect.height = std::clamp(rect.height, 0, sTotalChromeHeight - rect.y); + RasterizeColorRect(rect); + } + + HDC hdc = sGetWindowDC(hWnd); + if (!hdc) { + return Err(PreXULSkeletonUIError::FailedGettingDC); + } + auto cleanupDC = MakeScopeExit([=] { sReleaseDC(hWnd, hdc); }); + + BITMAPINFO chromeBMI = {}; + chromeBMI.bmiHeader.biSize = sizeof(chromeBMI.bmiHeader); + chromeBMI.bmiHeader.biWidth = sWindowWidth; + chromeBMI.bmiHeader.biHeight = -sTotalChromeHeight; + chromeBMI.bmiHeader.biPlanes = 1; + chromeBMI.bmiHeader.biBitCount = 32; + chromeBMI.bmiHeader.biCompression = BI_RGB; + + // First, we just paint the chrome area with our pixel buffer + int scanLinesCopied = sStretchDIBits( + hdc, 0, 0, sWindowWidth, sTotalChromeHeight, 0, 0, sWindowWidth, + sTotalChromeHeight, sPixelBuffer, &chromeBMI, DIB_RGB_COLORS, SRCCOPY); + if (scanLinesCopied == 0) { + return Err(PreXULSkeletonUIError::FailedBlitting); + } + + // Then, we just fill the rest with FillRect + RECT rect = {0, sTotalChromeHeight, sWindowWidth, sWindowHeight}; + HBRUSH brush = + sCreateSolidBrush(RGB((currentTheme.backgroundColor & 0xff0000) >> 16, + (currentTheme.backgroundColor & 0x00ff00) >> 8, + (currentTheme.backgroundColor & 0x0000ff) >> 0)); + int fillRectResult = sFillRect(hdc, &rect, brush); + + sDeleteObject(brush); + + if (fillRectResult == 0) { + return Err(PreXULSkeletonUIError::FailedFillingBottomRect); + } + + scopeExit.release(); + return Ok(); +} + +DWORD WINAPI AnimateSkeletonUI(void* aUnused) { + if (!sPixelBuffer || sAnimatedRects->empty()) { + return 0; + } + + // See the comments above the InterlockedIncrement calls below here - we + // atomically flip this up and down around sleep so the main thread doesn't + // have to wait for us if we're just sleeping. + if (InterlockedIncrement(&sAnimationControlFlag) != 1) { + return 0; + } + // Sleep for two seconds - startups faster than this don't really benefit + // from an animation, and we don't want to take away cycles from them. + // Startups longer than this, however, are more likely to be blocked on IO, + // and thus animating does not substantially impact startup times for them. + ::Sleep(2000); + if (InterlockedDecrement(&sAnimationControlFlag) != 0) { + return 0; + } + + // On each of the animated rects (which happen to all be placeholder UI + // rects sharing the same color), we want to animate a gradient moving across + // the screen from left to right. The gradient starts as the rect's color on, + // the left side, changes to the background color of the window by the middle + // of the gradient, and then goes back down to the rect's color. To make this + // faster than interpolating between the two colors for each pixel for each + // frame, we simply create a lookup buffer in which we can look up the color + // for a particular offset into the gradient. + // + // To do this we just interpolate between the two values, and to give the + // gradient a smoother transition between colors, we transform the linear + // blend amount via the cubic smooth step function (SmoothStep3) to produce + // a smooth start and stop for the gradient. We do this for the first half + // of the gradient, and then simply copy that backwards for the second half. + // + // The CSS width of 80 chosen here is effectively is just to match the size + // of the animation provided in the design mockup. We define it in CSS pixels + // simply because the rest of our UI is based off of CSS scalings. + int animationWidth = CSSToDevPixels(80, sCSSToDevPixelScaling); + UniquePtr<uint32_t[]> animationLookup = + MakeUnique<uint32_t[]>(animationWidth); + uint32_t animationColor = sAnimationColor; + NormalizedRGB rgbBlend = UintToRGB(animationColor); + + // Build the first half of the lookup table + for (int i = 0; i < animationWidth / 2; ++i) { + uint32_t baseColor = sToolbarForegroundColor; + double blendAmountLinear = + static_cast<double>(i) / (static_cast<double>(animationWidth / 2)); + double blendAmount = SmoothStep3(blendAmountLinear); + + NormalizedRGB rgbBase = UintToRGB(baseColor); + NormalizedRGB rgb = Lerp(rgbBase, rgbBlend, blendAmount); + animationLookup[i] = RGBToUint(rgb); + } + + // Copy the first half of the lookup table into the second half backwards + for (int i = animationWidth / 2; i < animationWidth; ++i) { + int j = animationWidth - 1 - i; + if (j == animationWidth / 2) { + // If animationWidth is odd, we'll be left with one pixel at the center. + // Just color that as the animation color. + animationLookup[i] = animationColor; + } else { + animationLookup[i] = animationLookup[j]; + } + } + + // The bitmap info remains unchanged throughout the animation - this just + // effectively describes the contents of sPixelBuffer + BITMAPINFO chromeBMI = {}; + chromeBMI.bmiHeader.biSize = sizeof(chromeBMI.bmiHeader); + chromeBMI.bmiHeader.biWidth = sWindowWidth; + chromeBMI.bmiHeader.biHeight = -sTotalChromeHeight; + chromeBMI.bmiHeader.biPlanes = 1; + chromeBMI.bmiHeader.biBitCount = 32; + chromeBMI.bmiHeader.biCompression = BI_RGB; + + uint32_t animationIteration = 0; + + int devPixelsPerFrame = + CSSToDevPixels(kAnimationCSSPixelsPerFrame, sCSSToDevPixelScaling); + int devPixelsExtraWindowSize = + CSSToDevPixels(kAnimationCSSExtraWindowSize, sCSSToDevPixelScaling); + + if (::InterlockedCompareExchange(&sAnimationControlFlag, 0, 0)) { + // The window got consumed before we were able to draw anything. + return 0; + } + + while (true) { + // The gradient will move across the screen at devPixelsPerFrame at + // 60fps, and then loop back to the beginning. However, we add a buffer of + // devPixelsExtraWindowSize around the edges so it doesn't immediately + // jump back, giving it a more pulsing feel. + int animationMin = ((animationIteration * devPixelsPerFrame) % + (sWindowWidth + devPixelsExtraWindowSize)) - + devPixelsExtraWindowSize / 2; + int animationMax = animationMin + animationWidth; + // The priorAnimationMin is the beginning of the previous frame's animation. + // Since we only want to draw the bits of the image that we updated, we need + // to overwrite the left bit of the animation we drew last frame with the + // default color. + int priorAnimationMin = animationMin - devPixelsPerFrame; + animationMin = std::max(0, animationMin); + priorAnimationMin = std::max(0, priorAnimationMin); + animationMax = std::min((int)sWindowWidth, animationMax); + + // The gradient only affects the specific rects that we put into + // sAnimatedRects. So we simply update those rects, and maintain a flag + // to avoid drawing when we don't need to. + bool updatedAnything = false; + for (ColorRect rect : *sAnimatedRects) { + bool hadUpdates = + RasterizeAnimatedRect(rect, animationLookup.get(), priorAnimationMin, + animationMin, animationMax); + updatedAnything = updatedAnything || hadUpdates; + } + + if (updatedAnything) { + HDC hdc = sGetWindowDC(sPreXULSkeletonUIWindow); + if (!hdc) { + return 0; + } + + sStretchDIBits(hdc, priorAnimationMin, 0, + animationMax - priorAnimationMin, sTotalChromeHeight, + priorAnimationMin, 0, animationMax - priorAnimationMin, + sTotalChromeHeight, sPixelBuffer, &chromeBMI, + DIB_RGB_COLORS, SRCCOPY); + + sReleaseDC(sPreXULSkeletonUIWindow, hdc); + } + + animationIteration++; + + // We coordinate around our sleep here to ensure that the main thread does + // not wait on us if we're sleeping. If we don't get 1 here, it means the + // window has been consumed and we don't need to sleep. If in + // ConsumePreXULSkeletonUIHandle we get a value other than 1 after + // incrementing, it means we're sleeping, and that function can assume that + // we will safely exit after the sleep because of the observed value of + // sAnimationControlFlag. + if (InterlockedIncrement(&sAnimationControlFlag) != 1) { + return 0; + } + + // Note: Sleep does not guarantee an exact time interval. If the system is + // busy, for instance, we could easily end up taking several frames longer, + // and really we could be left unscheduled for an arbitrarily long time. + // This is fine, and we don't really care. We could track how much time this + // actually took and jump the animation forward the appropriate amount, but + // its not even clear that that's a better user experience. So we leave this + // as simple as we can. + ::Sleep(16); + + // Here we bring sAnimationControlFlag back down - again, if we don't get a + // 0 here it means we consumed the skeleton UI window in the mean time, so + // we can simply exit. + if (InterlockedDecrement(&sAnimationControlFlag) != 0) { + return 0; + } + } +} + +LRESULT WINAPI PreXULSkeletonUIProc(HWND hWnd, UINT msg, WPARAM wParam, + LPARAM lParam) { + // Exposing a generic oleacc proxy for the skeleton isn't useful and may cause + // screen readers to report spurious information when the skeleton appears. + if (msg == WM_GETOBJECT && sPreXULSkeletonUIWindow) { + return E_FAIL; + } + + // NOTE: this block was copied from WinUtils.cpp, and needs to be kept in + // sync. + if (msg == WM_NCCREATE && sEnableNonClientDpiScaling) { + sEnableNonClientDpiScaling(hWnd); + } + + // NOTE: this block was paraphrased from the WM_NCCALCSIZE handler in + // nsWindow.cpp, and will need to be kept in sync. + if (msg == WM_NCCALCSIZE) { + RECT* clientRect = + wParam ? &(reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam))->rgrc[0] + : (reinterpret_cast<RECT*>(lParam)); + + // These match the margins set in browser-tabsintitlebar.js with + // default prefs on Windows. Bug 1673092 tracks lining this up with + // that more correctly instead of hard-coding it. + int horizontalOffset = + sNonClientHorizontalMargins - + (sMaximized ? 0 : CSSToDevPixels(2, sCSSToDevPixelScaling)); + int verticalOffset = + sNonClientHorizontalMargins - + (sMaximized ? 0 : CSSToDevPixels(2, sCSSToDevPixelScaling)); + clientRect->top = clientRect->top; + clientRect->left += horizontalOffset; + clientRect->right -= horizontalOffset; + clientRect->bottom -= verticalOffset; + return 0; + } + + return ::DefWindowProcW(hWnd, msg, wParam, lParam); +} + +bool IsSystemDarkThemeEnabled() { + DWORD result; + HKEY themeKey; + DWORD dataLen = sizeof(uint32_t); + LPCWSTR keyName = + L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; + + result = ::RegOpenKeyExW(HKEY_CURRENT_USER, keyName, 0, KEY_READ, &themeKey); + if (result != ERROR_SUCCESS) { + return false; + } + AutoCloseRegKey closeKey(themeKey); + + uint32_t lightThemeEnabled; + result = ::RegGetValueW( + themeKey, nullptr, L"AppsUseLightTheme", RRF_RT_REG_DWORD, nullptr, + reinterpret_cast<PBYTE>(&lightThemeEnabled), &dataLen); + if (result != ERROR_SUCCESS) { + return false; + } + return !lightThemeEnabled; +} + +ThemeColors GetTheme(ThemeMode themeId) { + ThemeColors theme = {}; + switch (themeId) { + case ThemeMode::Dark: + // Dark theme or default theme when in dark mode + + // controlled by css variable --toolbar-bgcolor + theme.backgroundColor = 0x2b2a33; + theme.tabColor = 0x42414d; + theme.toolbarForegroundColor = 0x6a6a6d; + theme.tabOutlineColor = 0x1c1b22; + // controlled by css variable --lwt-accent-color + theme.tabBarColor = 0x1c1b22; + // controlled by --toolbar-non-lwt-textcolor in browser.css + theme.chromeContentDividerColor = 0x0c0c0d; + // controlled by css variable --toolbar-field-background-color + theme.urlbarColor = 0x42414d; + theme.urlbarBorderColor = 0x42414d; + theme.animationColor = theme.urlbarColor; + return theme; + case ThemeMode::Light: + case ThemeMode::Default: + default: + // --toolbar-non-lwt-bgcolor in browser.css + theme.backgroundColor = 0xf9f9fb; + theme.tabColor = 0xf9f9fb; + theme.toolbarForegroundColor = 0xdddde1; + theme.tabOutlineColor = 0xdddde1; + // found in browser-aero.css ":root[tabsintitlebar]:not(:-moz-lwtheme)" + // (set to "hsl(235,33%,19%)") + theme.tabBarColor = 0xf0f0f4; + // --chrome-content-separator-color in browser.css + theme.chromeContentDividerColor = 0xe1e1e2; + // controlled by css variable --toolbar-color + theme.urlbarColor = 0xffffff; + theme.urlbarBorderColor = 0xdddde1; + theme.animationColor = theme.backgroundColor; + return theme; + } +} + +Result<HKEY, PreXULSkeletonUIError> OpenPreXULSkeletonUIRegKey() { + HKEY key; + DWORD disposition; + LSTATUS result = + ::RegCreateKeyExW(HKEY_CURRENT_USER, kPreXULSkeletonUIKeyPath, 0, nullptr, + 0, KEY_ALL_ACCESS, nullptr, &key, &disposition); + + if (result != ERROR_SUCCESS) { + return Err(PreXULSkeletonUIError::FailedToOpenRegistryKey); + } + + if (disposition == REG_CREATED_NEW_KEY || + disposition == REG_OPENED_EXISTING_KEY) { + return key; + } + + ::RegCloseKey(key); + return Err(PreXULSkeletonUIError::FailedToOpenRegistryKey); +} + +Result<Ok, PreXULSkeletonUIError> LoadGdi32AndUser32Procedures() { + HMODULE user32Dll = ::LoadLibraryW(L"user32"); + HMODULE gdi32Dll = ::LoadLibraryW(L"gdi32"); + + if (!user32Dll || !gdi32Dll) { + return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs); + } + + auto getThreadDpiAwarenessContext = + (decltype(GetThreadDpiAwarenessContext)*)::GetProcAddress( + user32Dll, "GetThreadDpiAwarenessContext"); + auto areDpiAwarenessContextsEqual = + (decltype(AreDpiAwarenessContextsEqual)*)::GetProcAddress( + user32Dll, "AreDpiAwarenessContextsEqual"); + if (getThreadDpiAwarenessContext && areDpiAwarenessContextsEqual && + areDpiAwarenessContextsEqual(getThreadDpiAwarenessContext(), + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) { + // EnableNonClientDpiScaling is optional - we can handle not having it. + sEnableNonClientDpiScaling = + (EnableNonClientDpiScalingProc)::GetProcAddress( + user32Dll, "EnableNonClientDpiScaling"); + } + + sGetSystemMetricsForDpi = (GetSystemMetricsForDpiProc)::GetProcAddress( + user32Dll, "GetSystemMetricsForDpi"); + if (!sGetSystemMetricsForDpi) { + return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs); + } + sGetDpiForWindow = + (GetDpiForWindowProc)::GetProcAddress(user32Dll, "GetDpiForWindow"); + if (!sGetDpiForWindow) { + return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs); + } + sRegisterClassW = + (RegisterClassWProc)::GetProcAddress(user32Dll, "RegisterClassW"); + if (!sRegisterClassW) { + return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs); + } + sCreateWindowExW = + (CreateWindowExWProc)::GetProcAddress(user32Dll, "CreateWindowExW"); + if (!sCreateWindowExW) { + return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs); + } + sShowWindow = (ShowWindowProc)::GetProcAddress(user32Dll, "ShowWindow"); + if (!sShowWindow) { + return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs); + } + sSetWindowPos = (SetWindowPosProc)::GetProcAddress(user32Dll, "SetWindowPos"); + if (!sSetWindowPos) { + return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs); + } + sGetWindowDC = (GetWindowDCProc)::GetProcAddress(user32Dll, "GetWindowDC"); + if (!sGetWindowDC) { + return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs); + } + sFillRect = (FillRectProc)::GetProcAddress(user32Dll, "FillRect"); + if (!sFillRect) { + return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs); + } + sReleaseDC = (ReleaseDCProc)::GetProcAddress(user32Dll, "ReleaseDC"); + if (!sReleaseDC) { + return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs); + } + sLoadIconW = (LoadIconWProc)::GetProcAddress(user32Dll, "LoadIconW"); + if (!sLoadIconW) { + return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs); + } + sLoadCursorW = (LoadCursorWProc)::GetProcAddress(user32Dll, "LoadCursorW"); + if (!sLoadCursorW) { + return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs); + } + sMonitorFromWindow = + (MonitorFromWindowProc)::GetProcAddress(user32Dll, "MonitorFromWindow"); + if (!sMonitorFromWindow) { + return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs); + } + sGetMonitorInfoW = + (GetMonitorInfoWProc)::GetProcAddress(user32Dll, "GetMonitorInfoW"); + if (!sGetMonitorInfoW) { + return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs); + } + sSetWindowLongPtrW = + (SetWindowLongPtrWProc)::GetProcAddress(user32Dll, "SetWindowLongPtrW"); + if (!sSetWindowLongPtrW) { + return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs); + } + sStretchDIBits = + (StretchDIBitsProc)::GetProcAddress(gdi32Dll, "StretchDIBits"); + if (!sStretchDIBits) { + return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs); + } + sCreateSolidBrush = + (CreateSolidBrushProc)::GetProcAddress(gdi32Dll, "CreateSolidBrush"); + if (!sCreateSolidBrush) { + return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs); + } + sDeleteObject = (DeleteObjectProc)::GetProcAddress(gdi32Dll, "DeleteObject"); + if (!sDeleteObject) { + return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs); + } + + return Ok(); +} + +// Strips "--", "-", and "/" from the front of the arg if one of those exists, +// returning `arg + 2`, `arg + 1`, and `arg + 1` respectively. If none of these +// prefixes are found, the argument is not a flag, and nullptr is returned. +const char* NormalizeFlag(const char* arg) { + if (strstr(arg, "--") == arg) { + return arg + 2; + } + + if (arg[0] == '-') { + return arg + 1; + } + + if (arg[0] == '/') { + return arg + 1; + } + + return nullptr; +} + +static bool EnvHasValue(const char* name) { + const char* val = getenv(name); + return (val && *val); +} + +// Ensures that we only see arguments in the command line which are acceptable. +// This is based on manual inspection of the list of arguments listed in the MDN +// page for Gecko/Firefox commandline options: +// https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options +// Broadly speaking, we want to reject any argument which causes us to show +// something other than the default window at its normal size. Here is a non- +// exhaustive list of command line options we want to *exclude*: +// +// -ProfileManager : This will display the profile manager window, which does +// not match the skeleton UI at all. +// +// -CreateProfile : This will display a firefox window with the default +// screen position and size, and not the position and size +// which we have recorded in the registry. +// +// -P <profile> : This could cause us to display firefox with a position +// and size of a different profile than that in which we +// were previously running. +// +// -width, -height : This will cause the width and height values in the +// registry to be incorrect. +// +// -kiosk : See above. +// +// -headless : This one should be rather obvious. +// +// -migration : This will start with the import wizard, which of course +// does not match the skeleton UI. +// +// -private-window : This is tricky, but the colors of the main content area +// make this not feel great with the white content of the +// default skeleton UI. +// +// NOTE: we generally want to skew towards erroneous rejections of the command +// line rather than erroneous approvals. The consequence of a bad rejection +// is that we don't show the skeleton UI, which is business as usual. The +// consequence of a bad approval is that we show it when we're not supposed to, +// which is visually jarring and can also be unpredictable - there's no +// guarantee that the code which handles the non-default window is set up to +// properly handle the transition from the skeleton UI window. +static Result<Ok, PreXULSkeletonUIError> ValidateCmdlineArguments( + int argc, char** argv, bool* explicitProfile) { + const char* approvedArgumentsArray[] = { + // These won't cause the browser to be visualy different in any way + "new-instance", "no-remote", "browser", "foreground", "setDefaultBrowser", + "attach-console", "wait-for-browser", "osint", + + // These will cause the chrome to be a bit different or extra windows to + // be created, but overall the skeleton UI should still be broadly + // correct enough. + "new-tab", "new-window", + + // To the extent possible, we want to ensure that existing tests cover + // the skeleton UI, so we need to allow marionette + "marionette", + + // These will cause the content area to appear different, but won't + // meaningfully affect the chrome + "preferences", "search", "url", + +#ifndef MOZILLA_OFFICIAL + // On local builds, we want to allow -profile, because it's how `mach run` + // operates, and excluding that would create an unnecessary blind spot for + // Firefox devs. + "profile" +#endif + + // There are other arguments which are likely okay. However, they are + // not included here because this list is not intended to be + // exhaustive - it only intends to green-light some somewhat commonly + // used arguments. We want to err on the side of an unnecessary + // rejection of the command line. + }; + + int approvedArgumentsArraySize = + sizeof(approvedArgumentsArray) / sizeof(approvedArgumentsArray[0]); + Vector<const char*> approvedArguments; + if (!approvedArguments.reserve(approvedArgumentsArraySize)) { + return Err(PreXULSkeletonUIError::OOM); + } + + for (int i = 0; i < approvedArgumentsArraySize; ++i) { + approvedArguments.infallibleAppend(approvedArgumentsArray[i]); + } + +#ifdef MOZILLA_OFFICIAL + int profileArgIndex = -1; + // If we're running mochitests or direct marionette tests, those specify a + // temporary profile, and we want to ensure that we get the added coverage + // from those. + for (int i = 1; i < argc; ++i) { + const char* flag = NormalizeFlag(argv[i]); + if (flag && !strcmp(flag, "marionette")) { + if (!approvedArguments.append("profile")) { + return Err(PreXULSkeletonUIError::OOM); + } + profileArgIndex = approvedArguments.length() - 1; + + break; + } + } +#else + int profileArgIndex = approvedArguments.length() - 1; +#endif + + for (int i = 1; i < argc; ++i) { + const char* flag = NormalizeFlag(argv[i]); + if (!flag) { + // If this is not a flag, then we interpret it as a URL, similar to + // BrowserContentHandler.sys.mjs. Some command line options take + // additional arguments, which may or may not be URLs. We don't need to + // know this, because we don't need to parse them out; we just rely on the + // assumption that if arg X is actually a parameter for the preceding + // arg Y, then X must not look like a flag (starting with "--", "-", + // or "/"). + // + // The most important thing here is the assumption that if something is + // going to meaningfully alter the appearance of the window itself, it + // must be a flag. + continue; + } + + bool approved = false; + for (const char* approvedArg : approvedArguments) { + // We do a case-insensitive compare here with _stricmp. Even though some + // of these arguments are *not* read as case-insensitive, others *are*. + // Similar to the flag logic above, we don't really care about this + // distinction, because we don't need to parse the arguments - we just + // rely on the assumption that none of the listed flags in our + // approvedArguments are overloaded in such a way that a different + // casing would visually alter the firefox window. + if (!_stricmp(flag, approvedArg)) { + approved = true; + + if (i == profileArgIndex) { + *explicitProfile = true; + } + break; + } + } + + if (!approved) { + return Err(PreXULSkeletonUIError::Cmdline); + } + } + + return Ok(); +} + +static Result<Ok, PreXULSkeletonUIError> ValidateEnvVars() { + if (EnvHasValue("MOZ_SAFE_MODE_RESTART") || + EnvHasValue("MOZ_APP_SILENT_START") || + EnvHasValue("MOZ_RESET_PROFILE_RESTART") || EnvHasValue("MOZ_HEADLESS") || + (EnvHasValue("XRE_PROFILE_PATH") && + !EnvHasValue("MOZ_SKELETON_UI_RESTARTING"))) { + return Err(PreXULSkeletonUIError::EnvVars); + } + + return Ok(); +} + +static bool VerifyWindowDimensions(uint32_t windowWidth, + uint32_t windowHeight) { + return windowWidth <= kMaxWindowWidth && windowHeight <= kMaxWindowHeight; +} + +static Result<Vector<CSSPixelSpan>, PreXULSkeletonUIError> ReadRegCSSPixelSpans( + HKEY regKey, const std::wstring& valueName) { + DWORD dataLen = 0; + LSTATUS result = ::RegQueryValueExW(regKey, valueName.c_str(), nullptr, + nullptr, nullptr, &dataLen); + if (result != ERROR_SUCCESS) { + return Err(PreXULSkeletonUIError::RegistryError); + } + + if (dataLen % (2 * sizeof(double)) != 0) { + return Err(PreXULSkeletonUIError::CorruptData); + } + + auto buffer = MakeUniqueFallible<wchar_t[]>(dataLen); + if (!buffer) { + return Err(PreXULSkeletonUIError::OOM); + } + result = + ::RegGetValueW(regKey, nullptr, valueName.c_str(), RRF_RT_REG_BINARY, + nullptr, reinterpret_cast<PBYTE>(buffer.get()), &dataLen); + if (result != ERROR_SUCCESS) { + return Err(PreXULSkeletonUIError::RegistryError); + } + + Vector<CSSPixelSpan> resultVector; + double* asDoubles = reinterpret_cast<double*>(buffer.get()); + for (size_t i = 0; i < dataLen / (2 * sizeof(double)); i++) { + CSSPixelSpan span = {}; + span.start = *(asDoubles++); + span.end = *(asDoubles++); + if (!resultVector.append(span)) { + return Err(PreXULSkeletonUIError::OOM); + } + } + + return resultVector; +} + +static Result<double, PreXULSkeletonUIError> ReadRegDouble( + HKEY regKey, const std::wstring& valueName) { + double value = 0; + DWORD dataLen = sizeof(double); + LSTATUS result = + ::RegGetValueW(regKey, nullptr, valueName.c_str(), RRF_RT_REG_BINARY, + nullptr, reinterpret_cast<PBYTE>(&value), &dataLen); + if (result != ERROR_SUCCESS || dataLen != sizeof(double)) { + return Err(PreXULSkeletonUIError::RegistryError); + } + + return value; +} + +static Result<uint32_t, PreXULSkeletonUIError> ReadRegUint( + HKEY regKey, const std::wstring& valueName) { + DWORD value = 0; + DWORD dataLen = sizeof(uint32_t); + LSTATUS result = + ::RegGetValueW(regKey, nullptr, valueName.c_str(), RRF_RT_REG_DWORD, + nullptr, reinterpret_cast<PBYTE>(&value), &dataLen); + if (result != ERROR_SUCCESS) { + return Err(PreXULSkeletonUIError::RegistryError); + } + + return value; +} + +static Result<bool, PreXULSkeletonUIError> ReadRegBool( + HKEY regKey, const std::wstring& valueName) { + uint32_t value; + MOZ_TRY_VAR(value, ReadRegUint(regKey, valueName)); + return !!value; +} + +static Result<Ok, PreXULSkeletonUIError> WriteRegCSSPixelSpans( + HKEY regKey, const std::wstring& valueName, const CSSPixelSpan* spans, + int spansLength) { + // No guarantee on the packing of CSSPixelSpan. We could #pragma it, but it's + // also trivial to just copy them into a buffer of doubles. + auto doubles = MakeUnique<double[]>(spansLength * 2); + for (int i = 0; i < spansLength; ++i) { + doubles[i * 2] = spans[i].start; + doubles[i * 2 + 1] = spans[i].end; + } + + LSTATUS result = + ::RegSetValueExW(regKey, valueName.c_str(), 0, REG_BINARY, + reinterpret_cast<const BYTE*>(doubles.get()), + spansLength * sizeof(double) * 2); + if (result != ERROR_SUCCESS) { + return Err(PreXULSkeletonUIError::RegistryError); + } + return Ok(); +} + +static Result<Ok, PreXULSkeletonUIError> WriteRegDouble( + HKEY regKey, const std::wstring& valueName, double value) { + LSTATUS result = + ::RegSetValueExW(regKey, valueName.c_str(), 0, REG_BINARY, + reinterpret_cast<const BYTE*>(&value), sizeof(value)); + if (result != ERROR_SUCCESS) { + return Err(PreXULSkeletonUIError::RegistryError); + } + + return Ok(); +} + +static Result<Ok, PreXULSkeletonUIError> WriteRegUint( + HKEY regKey, const std::wstring& valueName, uint32_t value) { + LSTATUS result = + ::RegSetValueExW(regKey, valueName.c_str(), 0, REG_DWORD, + reinterpret_cast<PBYTE>(&value), sizeof(value)); + if (result != ERROR_SUCCESS) { + return Err(PreXULSkeletonUIError::RegistryError); + } + + return Ok(); +} + +static Result<Ok, PreXULSkeletonUIError> WriteRegBool( + HKEY regKey, const std::wstring& valueName, bool value) { + return WriteRegUint(regKey, valueName, value ? 1 : 0); +} + +static Result<Ok, PreXULSkeletonUIError> CreateAndStorePreXULSkeletonUIImpl( + HINSTANCE hInstance, int argc, char** argv) { + // Initializing COM below may load modules via SetWindowHookEx, some of + // which may modify the executable's IAT for ntdll.dll. If that happens, + // this browser process fails to launch sandbox processes because we cannot + // copy a modified IAT into a remote process (See SandboxBroker::LaunchApp). + // To prevent that, we cache the intact IAT before COM initialization. + // If EAF+ is enabled, CacheNtDllThunk() causes a crash, but EAF+ will + // also prevent an injected module from parsing the PE headers and modifying + // the IAT. Therefore, we can skip CacheNtDllThunk(). + if (!mozilla::IsEafPlusEnabled()) { + CacheNtDllThunk(); + } + + // NOTE: it's important that we initialize sProcessRuntime before showing a + // window. Historically we ran into issues where showing the window would + // cause an accessibility win event to fire, which could cause in-process + // system or third party components to initialize COM and prevent us from + // initializing it with important settings we need. + + // Some COM settings are global to the process and must be set before any non- + // trivial COM is run in the application. Since these settings may affect + // stability, we should instantiate COM ASAP so that we can ensure that these + // global settings are configured before anything can interfere. + sProcessRuntime = new mscom::ProcessRuntime( + mscom::ProcessRuntime::ProcessCategory::GeckoBrowserParent); + + const TimeStamp skeletonStart = TimeStamp::Now(); + + HKEY regKey; + MOZ_TRY_VAR(regKey, OpenPreXULSkeletonUIRegKey()); + AutoCloseRegKey closeKey(regKey); + + UniquePtr<wchar_t[]> binPath; + MOZ_TRY_VAR(binPath, GetBinaryPath()); + + std::wstring regProgressName = + GetRegValueName(binPath.get(), sProgressSuffix); + auto progressResult = ReadRegUint(regKey, regProgressName); + if (!progressResult.isErr() && + progressResult.unwrap() != + static_cast<uint32_t>(PreXULSkeletonUIProgress::Completed)) { + return Err(PreXULSkeletonUIError::CrashedOnce); + } + + MOZ_TRY( + WriteRegUint(regKey, regProgressName, + static_cast<uint32_t>(PreXULSkeletonUIProgress::Started))); + auto writeCompletion = MakeScopeExit([&] { + Unused << WriteRegUint( + regKey, regProgressName, + static_cast<uint32_t>(PreXULSkeletonUIProgress::Completed)); + }); + + MOZ_TRY(GetSkeletonUILock()); + + bool explicitProfile = false; + MOZ_TRY(ValidateCmdlineArguments(argc, argv, &explicitProfile)); + MOZ_TRY(ValidateEnvVars()); + + auto enabledResult = + ReadRegBool(regKey, GetRegValueName(binPath.get(), sEnabledRegSuffix)); + if (enabledResult.isErr()) { + return Err(PreXULSkeletonUIError::EnabledKeyDoesNotExist); + } + if (!enabledResult.unwrap()) { + return Err(PreXULSkeletonUIError::Disabled); + } + sPreXULSkeletonUIEnabled = true; + + MOZ_ASSERT(!sAnimatedRects); + sAnimatedRects = new Vector<ColorRect>(); + + MOZ_TRY(LoadGdi32AndUser32Procedures()); + + if (!explicitProfile) { + MOZ_TRY(CheckForStartWithLastProfile()); + } + + WNDCLASSW wc; + wc.style = CS_DBLCLKS; + wc.lpfnWndProc = PreXULSkeletonUIProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = hInstance; + wc.hIcon = sLoadIconW(::GetModuleHandleW(nullptr), gStockApplicationIcon); + wc.hCursor = sLoadCursorW(hInstance, gIDCWait); + wc.hbrBackground = nullptr; + wc.lpszMenuName = nullptr; + + // TODO: just ensure we disable this if we've overridden the window class + wc.lpszClassName = L"MozillaWindowClass"; + + if (!sRegisterClassW(&wc)) { + return Err(PreXULSkeletonUIError::FailedRegisteringWindowClass); + } + + uint32_t screenX; + MOZ_TRY_VAR(screenX, ReadRegUint(regKey, GetRegValueName(binPath.get(), + sScreenXRegSuffix))); + uint32_t screenY; + MOZ_TRY_VAR(screenY, ReadRegUint(regKey, GetRegValueName(binPath.get(), + sScreenYRegSuffix))); + uint32_t windowWidth; + MOZ_TRY_VAR( + windowWidth, + ReadRegUint(regKey, GetRegValueName(binPath.get(), sWidthRegSuffix))); + uint32_t windowHeight; + MOZ_TRY_VAR( + windowHeight, + ReadRegUint(regKey, GetRegValueName(binPath.get(), sHeightRegSuffix))); + MOZ_TRY_VAR( + sMaximized, + ReadRegBool(regKey, GetRegValueName(binPath.get(), sMaximizedRegSuffix))); + MOZ_TRY_VAR( + sCSSToDevPixelScaling, + ReadRegDouble(regKey, GetRegValueName(binPath.get(), + sCssToDevPixelScalingRegSuffix))); + Vector<CSSPixelSpan> urlbar; + MOZ_TRY_VAR(urlbar, + ReadRegCSSPixelSpans( + regKey, GetRegValueName(binPath.get(), sUrlbarCSSRegSuffix))); + Vector<CSSPixelSpan> searchbar; + MOZ_TRY_VAR(searchbar, + ReadRegCSSPixelSpans( + regKey, GetRegValueName(binPath.get(), sSearchbarRegSuffix))); + Vector<CSSPixelSpan> springs; + MOZ_TRY_VAR(springs, ReadRegCSSPixelSpans( + regKey, GetRegValueName(binPath.get(), + sSpringsCSSRegSuffix))); + + if (urlbar.empty() || searchbar.empty()) { + return Err(PreXULSkeletonUIError::CorruptData); + } + + EnumSet<SkeletonUIFlag, uint32_t> flags; + uint32_t flagsUint; + MOZ_TRY_VAR(flagsUint, ReadRegUint(regKey, GetRegValueName(binPath.get(), + sFlagsRegSuffix))); + flags.deserialize(flagsUint); + + if (flags.contains(SkeletonUIFlag::TouchDensity) || + flags.contains(SkeletonUIFlag::CompactDensity)) { + return Err(PreXULSkeletonUIError::BadUIDensity); + } + + uint32_t theme; + MOZ_TRY_VAR(theme, ReadRegUint(regKey, GetRegValueName(binPath.get(), + sThemeRegSuffix))); + ThemeMode themeMode = static_cast<ThemeMode>(theme); + if (themeMode == ThemeMode::Default) { + if (IsSystemDarkThemeEnabled() == true) { + themeMode = ThemeMode::Dark; + } + } + ThemeColors currentTheme = GetTheme(themeMode); + + if (!VerifyWindowDimensions(windowWidth, windowHeight)) { + return Err(PreXULSkeletonUIError::BadWindowDimensions); + } + + int showCmd = SW_SHOWNORMAL; + DWORD windowStyle = kPreXULSkeletonUIWindowStyle; + if (sMaximized) { + showCmd = SW_SHOWMAXIMIZED; + windowStyle |= WS_MAXIMIZE; + } + + sPreXULSkeletonUIWindow = + sCreateWindowExW(kPreXULSkeletonUIWindowStyleEx, L"MozillaWindowClass", + L"", windowStyle, screenX, screenY, windowWidth, + windowHeight, nullptr, nullptr, hInstance, nullptr); + if (!sPreXULSkeletonUIWindow) { + return Err(PreXULSkeletonUIError::CreateWindowFailed); + } + + sShowWindow(sPreXULSkeletonUIWindow, showCmd); + + sDpi = sGetDpiForWindow(sPreXULSkeletonUIWindow); + sNonClientHorizontalMargins = + sGetSystemMetricsForDpi(SM_CXFRAME, sDpi) + + sGetSystemMetricsForDpi(SM_CXPADDEDBORDER, sDpi); + sNonClientVerticalMargins = sGetSystemMetricsForDpi(SM_CYFRAME, sDpi) + + sGetSystemMetricsForDpi(SM_CXPADDEDBORDER, sDpi); + + if (sMaximized) { + HMONITOR monitor = + sMonitorFromWindow(sPreXULSkeletonUIWindow, MONITOR_DEFAULTTONULL); + if (!monitor) { + // NOTE: we specifically don't clean up the window here. If we're unable + // to finish setting up the window how we want it, we still need to keep + // it around and consume it with the first real toplevel window we + // create, to avoid flickering. + return Err(PreXULSkeletonUIError::FailedGettingMonitorInfo); + } + MONITORINFO mi = {sizeof(MONITORINFO)}; + if (!sGetMonitorInfoW(monitor, &mi)) { + return Err(PreXULSkeletonUIError::FailedGettingMonitorInfo); + } + + sWindowWidth = + mi.rcWork.right - mi.rcWork.left + sNonClientHorizontalMargins * 2; + sWindowHeight = + mi.rcWork.bottom - mi.rcWork.top + sNonClientVerticalMargins * 2; + } else { + sWindowWidth = static_cast<int>(windowWidth); + sWindowHeight = static_cast<int>(windowHeight); + } + + sSetWindowPos(sPreXULSkeletonUIWindow, 0, 0, 0, 0, 0, + SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE | + SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER); + MOZ_TRY(DrawSkeletonUI(sPreXULSkeletonUIWindow, urlbar[0], searchbar[0], + springs, currentTheme, flags)); + if (sAnimatedRects) { + sPreXULSKeletonUIAnimationThread = ::CreateThread( + nullptr, 256 * 1024, AnimateSkeletonUI, nullptr, 0, nullptr); + } + + BASE_PROFILER_MARKER_UNTYPED( + "CreatePreXULSkeletonUI", OTHER, + MarkerTiming::IntervalUntilNowFrom(skeletonStart)); + + return Ok(); +} + +void CreateAndStorePreXULSkeletonUI(HINSTANCE hInstance, int argc, + char** argv) { + auto result = CreateAndStorePreXULSkeletonUIImpl(hInstance, argc, argv); + + if (result.isErr()) { + sErrorReason.emplace(result.unwrapErr()); + } +} + +void CleanupProcessRuntime() { + delete sProcessRuntime; + sProcessRuntime = nullptr; +} + +bool WasPreXULSkeletonUIMaximized() { return sMaximized; } + +bool GetPreXULSkeletonUIWasShown() { + return sPreXULSkeletonUIShown || !!sPreXULSkeletonUIWindow; +} + +HWND ConsumePreXULSkeletonUIHandle() { + // NOTE: we need to make sure that everything that runs here is a no-op if + // it failed to be set, which is a possibility. If anything fails to be set + // we don't want to clean everything up right away, because if we have a + // blank window up, we want that to stick around and get consumed by nsWindow + // as normal, otherwise the window will flicker in and out, which we imagine + // is unpleasant. + + // If we don't get 1 here, it means the thread is actually just sleeping, so + // we don't need to worry about giving out ownership of the window, because + // the thread will simply exit after its sleep. However, if it is 1, we need + // to wait for the thread to exit to be safe, as it could be doing anything. + if (InterlockedIncrement(&sAnimationControlFlag) == 1) { + ::WaitForSingleObject(sPreXULSKeletonUIAnimationThread, INFINITE); + } + ::CloseHandle(sPreXULSKeletonUIAnimationThread); + sPreXULSKeletonUIAnimationThread = nullptr; + HWND result = sPreXULSkeletonUIWindow; + sPreXULSkeletonUIWindow = nullptr; + free(sPixelBuffer); + sPixelBuffer = nullptr; + delete sAnimatedRects; + sAnimatedRects = nullptr; + + return result; +} + +Maybe<PreXULSkeletonUIError> GetPreXULSkeletonUIErrorReason() { + return sErrorReason; +} + +Result<Ok, PreXULSkeletonUIError> PersistPreXULSkeletonUIValues( + const SkeletonUISettings& settings) { + if (!sPreXULSkeletonUIEnabled) { + return Err(PreXULSkeletonUIError::Disabled); + } + + HKEY regKey; + MOZ_TRY_VAR(regKey, OpenPreXULSkeletonUIRegKey()); + AutoCloseRegKey closeKey(regKey); + + UniquePtr<wchar_t[]> binPath; + MOZ_TRY_VAR(binPath, GetBinaryPath()); + + MOZ_TRY(WriteRegUint(regKey, + GetRegValueName(binPath.get(), sScreenXRegSuffix), + settings.screenX)); + MOZ_TRY(WriteRegUint(regKey, + GetRegValueName(binPath.get(), sScreenYRegSuffix), + settings.screenY)); + MOZ_TRY(WriteRegUint(regKey, GetRegValueName(binPath.get(), sWidthRegSuffix), + settings.width)); + MOZ_TRY(WriteRegUint(regKey, GetRegValueName(binPath.get(), sHeightRegSuffix), + settings.height)); + + MOZ_TRY(WriteRegBool(regKey, + GetRegValueName(binPath.get(), sMaximizedRegSuffix), + settings.maximized)); + + EnumSet<SkeletonUIFlag, uint32_t> flags; + if (settings.menubarShown) { + flags += SkeletonUIFlag::MenubarShown; + } + if (settings.bookmarksToolbarShown) { + flags += SkeletonUIFlag::BookmarksToolbarShown; + } + if (settings.rtlEnabled) { + flags += SkeletonUIFlag::RtlEnabled; + } + if (settings.uiDensity == SkeletonUIDensity::Touch) { + flags += SkeletonUIFlag::TouchDensity; + } + if (settings.uiDensity == SkeletonUIDensity::Compact) { + flags += SkeletonUIFlag::CompactDensity; + } + + uint32_t flagsUint = flags.serialize(); + MOZ_TRY(WriteRegUint(regKey, GetRegValueName(binPath.get(), sFlagsRegSuffix), + flagsUint)); + + MOZ_TRY(WriteRegDouble( + regKey, GetRegValueName(binPath.get(), sCssToDevPixelScalingRegSuffix), + settings.cssToDevPixelScaling)); + MOZ_TRY(WriteRegCSSPixelSpans( + regKey, GetRegValueName(binPath.get(), sUrlbarCSSRegSuffix), + &settings.urlbarSpan, 1)); + MOZ_TRY(WriteRegCSSPixelSpans( + regKey, GetRegValueName(binPath.get(), sSearchbarRegSuffix), + &settings.searchbarSpan, 1)); + MOZ_TRY(WriteRegCSSPixelSpans( + regKey, GetRegValueName(binPath.get(), sSpringsCSSRegSuffix), + settings.springs.begin(), settings.springs.length())); + + return Ok(); +} + +MFBT_API bool GetPreXULSkeletonUIEnabled() { return sPreXULSkeletonUIEnabled; } + +MFBT_API Result<Ok, PreXULSkeletonUIError> SetPreXULSkeletonUIEnabledIfAllowed( + bool value) { + // If the pre-XUL skeleton UI was disallowed for some reason, we just want to + // ignore changes to the registry. An example of how things could be bad if + // we didn't: someone running firefox with the -profile argument could + // turn the skeleton UI on or off for the default profile. Turning it off + // maybe isn't so bad (though it's likely still incorrect), but turning it + // on could be bad if the user had specifically disabled it for a profile for + // some reason. Ultimately there's no correct decision here, and the + // messiness of this is just a consequence of sharing the registry values + // across profiles. However, whatever ill effects we observe should be + // correct themselves after one session. + if (PreXULSkeletonUIDisallowed()) { + return Err(PreXULSkeletonUIError::Disabled); + } + + HKEY regKey; + MOZ_TRY_VAR(regKey, OpenPreXULSkeletonUIRegKey()); + AutoCloseRegKey closeKey(regKey); + + UniquePtr<wchar_t[]> binPath; + MOZ_TRY_VAR(binPath, GetBinaryPath()); + MOZ_TRY(WriteRegBool( + regKey, GetRegValueName(binPath.get(), sEnabledRegSuffix), value)); + + if (!sPreXULSkeletonUIEnabled && value) { + // We specifically don't care if we fail to get this lock. We just want to + // do our best effort to lock it so that future instances don't create + // skeleton UIs while we're still running, since they will immediately exit + // and tell us to open a new window. + Unused << GetSkeletonUILock(); + } + + sPreXULSkeletonUIEnabled = value; + + return Ok(); +} + +MFBT_API Result<Ok, PreXULSkeletonUIError> SetPreXULSkeletonUIThemeId( + ThemeMode theme) { + if (theme == sTheme) { + return Ok(); + } + sTheme = theme; + + // If we fail below, invalidate sTheme + auto invalidateTheme = MakeScopeExit([] { sTheme = ThemeMode::Invalid; }); + + HKEY regKey; + MOZ_TRY_VAR(regKey, OpenPreXULSkeletonUIRegKey()); + AutoCloseRegKey closeKey(regKey); + + UniquePtr<wchar_t[]> binPath; + MOZ_TRY_VAR(binPath, GetBinaryPath()); + MOZ_TRY(WriteRegUint(regKey, GetRegValueName(binPath.get(), sThemeRegSuffix), + static_cast<uint32_t>(theme))); + + invalidateTheme.release(); + return Ok(); +} + +MFBT_API void PollPreXULSkeletonUIEvents() { + if (sPreXULSkeletonUIEnabled && sPreXULSkeletonUIWindow) { + MSG outMsg = {}; + PeekMessageW(&outMsg, sPreXULSkeletonUIWindow, 0, 0, 0); + } +} + +Result<Ok, PreXULSkeletonUIError> NotePreXULSkeletonUIRestarting() { + if (!sPreXULSkeletonUIEnabled) { + return Err(PreXULSkeletonUIError::Disabled); + } + + ::SetEnvironmentVariableW(L"MOZ_SKELETON_UI_RESTARTING", L"1"); + + // We assume that we are going to exit the application very shortly after + // this. It should thus be fine to release this lock, and we'll need to, + // since during a restart we launch the new instance before closing this + // one. + if (sPreXULSKeletonUILockFile != INVALID_HANDLE_VALUE) { + ::CloseHandle(sPreXULSKeletonUILockFile); + } + return Ok(); +} + +} // namespace mozilla diff --git a/mozglue/misc/PreXULSkeletonUI.h b/mozglue/misc/PreXULSkeletonUI.h new file mode 100644 index 0000000000..c0549cd4e6 --- /dev/null +++ b/mozglue/misc/PreXULSkeletonUI.h @@ -0,0 +1,183 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef PreXULSkeletonUI_h_ +#define PreXULSkeletonUI_h_ + +#include <windows.h> +#include "mozilla/EnumSet.h" +#include "mozilla/Maybe.h" +#include "mozilla/Result.h" +#include "mozilla/Types.h" +#include "mozilla/Vector.h" + +namespace mozilla { + +// These unfortunately need to be kept in sync with the window style and +// extended window style computations in nsWindow. Luckily those styles seem +// to not vary based off of any user settings for the initial toplevel window, +// so we're safe here for now. +static const DWORD kPreXULSkeletonUIWindowStyle = + WS_CLIPCHILDREN | WS_DLGFRAME | WS_BORDER | WS_MAXIMIZEBOX | + WS_MINIMIZEBOX | WS_SIZEBOX | WS_SYSMENU; +static const DWORD kPreXULSkeletonUIWindowStyleEx = WS_EX_WINDOWEDGE; + +struct CSSPixelSpan { + double start; + double end; +}; + +struct DevPixelSpan { + int start; + int end; +}; + +enum class SkeletonUIDensity { Default, Touch, Compact }; + +struct SkeletonUISettings { + uint32_t screenX; + uint32_t screenY; + uint32_t width; + uint32_t height; + CSSPixelSpan urlbarSpan; + CSSPixelSpan searchbarSpan; + double cssToDevPixelScaling; + Vector<CSSPixelSpan> springs; + bool maximized; + bool menubarShown; + bool bookmarksToolbarShown; + bool rtlEnabled; + SkeletonUIDensity uiDensity; +}; + +enum class ThemeMode : uint32_t { Invalid, Default, Dark, Light }; + +enum class SkeletonUIFlag : uint8_t { + MenubarShown, + BookmarksToolbarShown, + RtlEnabled, + TouchDensity, + CompactDensity, +}; + +struct ThemeColors { + uint32_t backgroundColor; + uint32_t toolbarForegroundColor; + uint32_t tabBarColor; + uint32_t tabColor; + uint32_t tabOutlineColor; + uint32_t chromeContentDividerColor; + uint32_t urlbarColor; + uint32_t urlbarBorderColor; + uint32_t animationColor; +}; + +enum class PreXULSkeletonUIError : uint32_t { + None, + Disabled, + EnabledKeyDoesNotExist, + OOM, + Cmdline, + EnvVars, + FailedToOpenRegistryKey, + RegistryError, + FailedLoadingDynamicProcs, + FailedGettingLock, + FilesystemFailure, + NoStartWithLastProfile, + FailedRegisteringWindowClass, + CorruptData, + BadWindowDimensions, + FailedGettingMonitorInfo, + CreateWindowFailed, + FailedGettingDC, + FailedBlitting, + FailedFillingBottomRect, + CrashedOnce, + BadUIDensity, + Unknown, +}; + +inline const wchar_t* GetPreXULSkeletonUIErrorString( + PreXULSkeletonUIError error) { + switch (error) { + case PreXULSkeletonUIError::None: + return L"None"; + case PreXULSkeletonUIError::Disabled: + return L"Disabled"; + case PreXULSkeletonUIError::OOM: + return L"OOM"; + case PreXULSkeletonUIError::Cmdline: + return L"Cmdline"; + case PreXULSkeletonUIError::EnvVars: + return L"EnvVars"; + case PreXULSkeletonUIError::FailedToOpenRegistryKey: + return L"FailedToOpenRegistryKey"; + case PreXULSkeletonUIError::RegistryError: + return L"RegistryError"; + case PreXULSkeletonUIError::FailedLoadingDynamicProcs: + return L"FailedLoadingDynamicProcs"; + case PreXULSkeletonUIError::FailedGettingLock: + return L"FailedGettingLock"; + case PreXULSkeletonUIError::FilesystemFailure: + return L"FilesystemFailure"; + case PreXULSkeletonUIError::NoStartWithLastProfile: + return L"NoStartWithLastProfile"; + case PreXULSkeletonUIError::FailedRegisteringWindowClass: + return L"FailedRegisteringWindowClass"; + case PreXULSkeletonUIError::CorruptData: + return L"CorruptData"; + case PreXULSkeletonUIError::BadWindowDimensions: + return L"BadWindowDimensions"; + case PreXULSkeletonUIError::FailedGettingMonitorInfo: + return L"FailedGettingMonitorInfo"; + case PreXULSkeletonUIError::EnabledKeyDoesNotExist: + return L"EnabledKeyDoesNotExist"; + case PreXULSkeletonUIError::CreateWindowFailed: + return L"CreateWindowFailed"; + case PreXULSkeletonUIError::FailedGettingDC: + return L"FailedGettingDC"; + case PreXULSkeletonUIError::FailedBlitting: + return L"FailedBlitting"; + case PreXULSkeletonUIError::FailedFillingBottomRect: + return L"FailedFillingBottomRect"; + case PreXULSkeletonUIError::CrashedOnce: + return L"CrashedOnce"; + case PreXULSkeletonUIError::BadUIDensity: + return L"BadUIDensity"; + case PreXULSkeletonUIError::Unknown: + return L"Unknown"; + } + + MOZ_ASSERT_UNREACHABLE(); + return L"Unknown"; +} + +enum class PreXULSkeletonUIProgress : uint32_t { + Started, + Completed, +}; + +MFBT_API void CreateAndStorePreXULSkeletonUI(HINSTANCE hInstance, int argc, + char** argv); +MFBT_API void CleanupProcessRuntime(); +MFBT_API bool GetPreXULSkeletonUIWasShown(); +MFBT_API HWND ConsumePreXULSkeletonUIHandle(); +MFBT_API Maybe<PreXULSkeletonUIError> GetPreXULSkeletonUIErrorReason(); +MFBT_API bool WasPreXULSkeletonUIMaximized(); +MFBT_API Result<Ok, PreXULSkeletonUIError> PersistPreXULSkeletonUIValues( + const SkeletonUISettings& settings); +MFBT_API bool GetPreXULSkeletonUIEnabled(); +MFBT_API Result<Ok, PreXULSkeletonUIError> SetPreXULSkeletonUIEnabledIfAllowed( + bool value); +MFBT_API void PollPreXULSkeletonUIEvents(); +MFBT_API Result<Ok, PreXULSkeletonUIError> SetPreXULSkeletonUIThemeId( + ThemeMode theme); +MFBT_API Result<Ok, PreXULSkeletonUIError> NotePreXULSkeletonUIRestarting(); + +} // namespace mozilla + +#endif diff --git a/mozglue/misc/Printf.cpp b/mozglue/misc/Printf.cpp new file mode 100644 index 0000000000..7313495612 --- /dev/null +++ b/mozglue/misc/Printf.cpp @@ -0,0 +1,1101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Portable safe sprintf code. + * + * Author: Kipp E.B. Hickman + */ + +#include "double-conversion/double-to-string.h" +#include "mozilla/AllocPolicy.h" +#include "mozilla/Printf.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/Vector.h" + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#if defined(XP_WIN) +# include <windows.h> +#endif + +using double_conversion::DoubleToStringConverter; +using DTSC = DoubleToStringConverter; + +/* + * Numbered Argument State + */ +struct NumArgState { + int type; // type of the current ap + va_list ap; // point to the corresponding position on ap +}; + +using NumArgStateVector = + mozilla::Vector<NumArgState, 20, mozilla::MallocAllocPolicy>; + +// For values up to and including TYPE_DOUBLE, the lowest bit indicates +// whether the type is signed (0) or unsigned (1). +#define TYPE_SHORT 0 +#define TYPE_USHORT 1 +#define TYPE_INTN 2 +#define TYPE_UINTN 3 +#define TYPE_LONG 4 +#define TYPE_ULONG 5 +#define TYPE_LONGLONG 6 +#define TYPE_ULONGLONG 7 +#define TYPE_DOUBLE 8 +#define TYPE_STRING 9 +#define TYPE_INTSTR 10 +#define TYPE_POINTER 11 +#if defined(XP_WIN) +# define TYPE_WSTRING 12 +#endif +#define TYPE_SCHAR 14 +#define TYPE_UCHAR 15 +#define TYPE_UNKNOWN 20 + +#define FLAG_LEFT 0x1 +#define FLAG_SIGNED 0x2 +#define FLAG_SPACED 0x4 +#define FLAG_ZEROS 0x8 +#define FLAG_NEG 0x10 + +static const char hex[] = "0123456789abcdef"; +static const char HEX[] = "0123456789ABCDEF"; + +// Fill into the buffer using the data in src +bool mozilla::PrintfTarget::fill2(const char* src, int srclen, int width, + int flags) { + char space = ' '; + + width -= srclen; + if (width > 0 && (flags & FLAG_LEFT) == 0) { // Right adjusting + if (flags & FLAG_ZEROS) { + space = '0'; + } + while (--width >= 0) { + if (!emit(&space, 1)) { + return false; + } + } + } + + // Copy out the source data + if (!emit(src, srclen)) { + return false; + } + + if (width > 0 && (flags & FLAG_LEFT) != 0) { // Left adjusting + while (--width >= 0) { + if (!emit(&space, 1)) { + return false; + } + } + } + return true; +} + +/* + * Fill a number. The order is: optional-sign zero-filling conversion-digits + */ +bool mozilla::PrintfTarget::fill_n(const char* src, int srclen, int width, + int prec, int type, int flags) { + int zerowidth = 0; + int precwidth = 0; + int leftspaces = 0; + int rightspaces = 0; + int cvtwidth; + char sign = 0; + + if ((type & 1) == 0) { + if (flags & FLAG_NEG) { + sign = '-'; + } else if (flags & FLAG_SIGNED) { + sign = '+'; + } else if (flags & FLAG_SPACED) { + sign = ' '; + } + } + cvtwidth = (sign ? 1 : 0) + srclen; + + if (prec > 0 && (type != TYPE_DOUBLE)) { + if (prec > srclen) { + precwidth = prec - srclen; // Need zero filling + cvtwidth += precwidth; + } + } + + if ((flags & FLAG_ZEROS) && ((type == TYPE_DOUBLE) || (prec < 0))) { + if (width > cvtwidth) { + zerowidth = width - cvtwidth; // Zero filling + cvtwidth += zerowidth; + } + } + + if (flags & FLAG_LEFT) { + if (width > cvtwidth) { + // Space filling on the right (i.e. left adjusting) + rightspaces = width - cvtwidth; + } + } else { + if (width > cvtwidth) { + // Space filling on the left (i.e. right adjusting) + leftspaces = width - cvtwidth; + } + } + while (--leftspaces >= 0) { + if (!emit(" ", 1)) { + return false; + } + } + if (sign) { + if (!emit(&sign, 1)) { + return false; + } + } + while (--precwidth >= 0) { + if (!emit("0", 1)) { + return false; + } + } + while (--zerowidth >= 0) { + if (!emit("0", 1)) { + return false; + } + } + if (!emit(src, uint32_t(srclen))) { + return false; + } + while (--rightspaces >= 0) { + if (!emit(" ", 1)) { + return false; + } + } + return true; +} + +// All that the cvt_* functions care about as far as the TYPE_* constants is +// that the low bit is set to indicate unsigned, or unset to indicate signed. +// So we don't try to hard to ensure that the passed TYPE_* constant lines +// up with the actual size of the number being printed here. The main printf +// code, below, does have to care so that the correct bits are extracted from +// the varargs list. +bool mozilla::PrintfTarget::appendIntDec(int32_t num) { + int flags = 0; + long n = num; + if (n < 0) { + n = -n; + flags |= FLAG_NEG; + } + return cvt_l(n, -1, -1, 10, TYPE_INTN, flags, hex); +} + +bool mozilla::PrintfTarget::appendIntDec(uint32_t num) { + return cvt_l(num, -1, -1, 10, TYPE_UINTN, 0, hex); +} + +bool mozilla::PrintfTarget::appendIntOct(uint32_t num) { + return cvt_l(num, -1, -1, 8, TYPE_UINTN, 0, hex); +} + +bool mozilla::PrintfTarget::appendIntHex(uint32_t num) { + return cvt_l(num, -1, -1, 16, TYPE_UINTN, 0, hex); +} + +bool mozilla::PrintfTarget::appendIntDec(int64_t num) { + int flags = 0; + if (num < 0) { + num = -num; + flags |= FLAG_NEG; + } + return cvt_ll(num, -1, -1, 10, TYPE_INTN, flags, hex); +} + +bool mozilla::PrintfTarget::appendIntDec(uint64_t num) { + return cvt_ll(num, -1, -1, 10, TYPE_UINTN, 0, hex); +} + +bool mozilla::PrintfTarget::appendIntOct(uint64_t num) { + return cvt_ll(num, -1, -1, 8, TYPE_UINTN, 0, hex); +} + +bool mozilla::PrintfTarget::appendIntHex(uint64_t num) { + return cvt_ll(num, -1, -1, 16, TYPE_UINTN, 0, hex); +} + +/* Convert a long into its printable form. */ +bool mozilla::PrintfTarget::cvt_l(long num, int width, int prec, int radix, + int type, int flags, const char* hexp) { + char cvtbuf[100]; + char* cvt; + int digits; + + // according to the man page this needs to happen + if ((prec == 0) && (num == 0)) { + return fill_n("", 0, width, prec, type, flags); + } + + // Converting decimal is a little tricky. In the unsigned case we + // need to stop when we hit 10 digits. In the signed case, we can + // stop when the number is zero. + cvt = cvtbuf + sizeof(cvtbuf); + digits = 0; + while (num) { + int digit = (((unsigned long)num) % radix) & 0xF; + *--cvt = hexp[digit]; + digits++; + num = (long)(((unsigned long)num) / radix); + } + if (digits == 0) { + *--cvt = '0'; + digits++; + } + + // Now that we have the number converted without its sign, deal with + // the sign and zero padding. + return fill_n(cvt, digits, width, prec, type, flags); +} + +/* Convert a 64-bit integer into its printable form. */ +bool mozilla::PrintfTarget::cvt_ll(int64_t num, int width, int prec, int radix, + int type, int flags, const char* hexp) { + // According to the man page, this needs to happen. + if (prec == 0 && num == 0) { + return fill_n("", 0, width, prec, type, flags); + } + + // Converting decimal is a little tricky. In the unsigned case we + // need to stop when we hit 10 digits. In the signed case, we can + // stop when the number is zero. + int64_t rad = int64_t(radix); + char cvtbuf[100]; + char* cvt = cvtbuf + sizeof(cvtbuf); + int digits = 0; + while (num != 0) { + int64_t quot = uint64_t(num) / rad; + int64_t rem = uint64_t(num) % rad; + int32_t digit = int32_t(rem); + *--cvt = hexp[digit & 0xf]; + digits++; + num = quot; + } + if (digits == 0) { + *--cvt = '0'; + digits++; + } + + // Now that we have the number converted without its sign, deal with + // the sign and zero padding. + return fill_n(cvt, digits, width, prec, type, flags); +} + +template <size_t N> +constexpr static size_t lengthof(const char (&)[N]) { + return N - 1; +} + +// Longest possible output from ToFixed for positive numbers: +// [0-9]{kMaxFixedDigitsBeforePoint}\.[0-9]{kMaxFixedDigitsAfterPoint}? +constexpr int FIXED_MAX_CHARS = + DTSC::kMaxFixedDigitsBeforePoint + 1 + DTSC::kMaxFixedDigitsAfterPoint; + +// Longest possible output from ToExponential: +// [1-9]\.[0-9]{kMaxExponentialDigits}e[+-][0-9]{1,3} +// (std::numeric_limits<double>::max() has exponent 308). +constexpr int EXPONENTIAL_MAX_CHARS = + lengthof("1.") + DTSC::kMaxExponentialDigits + lengthof("e+999"); + +// Longest possible output from ToPrecise: +// [0-9\.]{kMaxPrecisionDigits+1} or +// [1-9]\.[0-9]{kMaxPrecisionDigits-1}e[+-][0-9]{1,3} +constexpr int PRECISE_MAX_CHARS = + lengthof("1.") + DTSC::kMaxPrecisionDigits - 1 + lengthof("e+999"); + +constexpr int DTSC_MAX_CHARS = + std::max({FIXED_MAX_CHARS, EXPONENTIAL_MAX_CHARS, PRECISE_MAX_CHARS}); + +/* + * Convert a double precision floating point number into its printable + * form. + */ +bool mozilla::PrintfTarget::cvt_f(double d, char c, int width, int prec, + int flags) { + bool lower = islower(c); + const char* inf = lower ? "inf" : "INF"; + const char* nan = lower ? "nan" : "NAN"; + char e = lower ? 'e' : 'E'; + DoubleToStringConverter converter(DTSC::UNIQUE_ZERO | DTSC::NO_TRAILING_ZERO | + DTSC::EMIT_POSITIVE_EXPONENT_SIGN, + inf, nan, e, 0, 0, 4, 0, 2); + // Longest of the above cases, plus space for a terminal nul character. + char buf[DTSC_MAX_CHARS + 1]; + double_conversion::StringBuilder builder(buf, sizeof(buf)); + bool success = false; + if (std::signbit(d)) { + d = std::abs(d); + flags |= FLAG_NEG; + } + if (!std::isfinite(d)) { + flags &= ~FLAG_ZEROS; + } + // "If the precision is missing, it shall be taken as 6." + if (prec < 0) { + prec = 6; + } + switch (c) { + case 'e': + case 'E': + success = converter.ToExponential(d, prec, &builder); + break; + case 'f': + case 'F': + success = converter.ToFixed(d, prec, &builder); + break; + case 'g': + case 'G': + // "If an explicit precision is zero, it shall be taken as 1." + success = converter.ToPrecision(d, prec ? prec : 1, &builder); + break; + } + if (!success) { + return false; + } + int len = builder.position(); + char* cvt = builder.Finalize(); + return fill_n(cvt, len, width, prec, TYPE_DOUBLE, flags); +} + +/* + * Convert a string into its printable form. "width" is the output + * width. "prec" is the maximum number of characters of "s" to output, + * where -1 means until NUL. + */ +bool mozilla::PrintfTarget::cvt_s(const char* s, int width, int prec, + int flags) { + if (prec == 0) { + return true; + } + if (!s) { + s = "(null)"; + } + + // Limit string length by precision value + int slen = int(strlen(s)); + if (0 < prec && prec < slen) { + slen = prec; + } + + // and away we go + return fill2(s, slen, width, flags); +} + +/* + * BuildArgArray stands for Numbered Argument list Sprintf + * for example, + * fmp = "%4$i, %2$d, %3s, %1d"; + * the number must start from 1, and no gap among them + */ +static bool BuildArgArray(const char* fmt, va_list ap, NumArgStateVector& nas) { + size_t number = 0, cn = 0, i; + const char* p; + char c; + + // First pass: + // Detemine how many legal % I have got, then allocate space. + + p = fmt; + i = 0; + while ((c = *p++) != 0) { + if (c != '%') { + continue; + } + if ((c = *p++) == '%') { // skip %% case + continue; + } + + while (c != 0) { + if (c > '9' || c < '0') { + if (c == '$') { // numbered argument case + if (i > 0) { + MOZ_CRASH("Bad format string"); + } + number++; + } else { // non-numbered argument case + if (number > 0) { + MOZ_CRASH("Bad format string"); + } + i = 1; + } + break; + } + + c = *p++; + } + } + + if (number == 0) { + return true; + } + + // Only allow a limited number of arguments. + MOZ_RELEASE_ASSERT(number <= 20); + + if (!nas.growByUninitialized(number)) { + return false; + } + + for (i = 0; i < number; i++) { + nas[i].type = TYPE_UNKNOWN; + } + + // Second pass: + // Set nas[].type. + + p = fmt; + while ((c = *p++) != 0) { + if (c != '%') { + continue; + } + c = *p++; + if (c == '%') { + continue; + } + + cn = 0; + while (c && c != '$') { // should improve error check later + cn = cn * 10 + c - '0'; + c = *p++; + } + + if (!c || cn < 1 || cn > number) { + MOZ_CRASH("Bad format string"); + } + + // nas[cn] starts from 0, and make sure nas[cn].type is not assigned. + cn--; + if (nas[cn].type != TYPE_UNKNOWN) { + continue; + } + + c = *p++; + + // flags + while ((c == '-') || (c == '+') || (c == ' ') || (c == '0')) { + c = *p++; + } + + // width + if (c == '*') { + // not supported feature, for the argument is not numbered + MOZ_CRASH("Bad format string"); + } + + while ((c >= '0') && (c <= '9')) { + c = *p++; + } + + // precision + if (c == '.') { + c = *p++; + if (c == '*') { + // not supported feature, for the argument is not numbered + MOZ_CRASH("Bad format string"); + } + + while ((c >= '0') && (c <= '9')) { + c = *p++; + } + } + + // size + nas[cn].type = TYPE_INTN; + if (c == 'h') { + nas[cn].type = TYPE_SHORT; + c = *p++; + if (c == 'h') { + nas[cn].type = TYPE_SCHAR; + c = *p++; + } + } else if (c == 'L') { + nas[cn].type = TYPE_LONGLONG; + c = *p++; + } else if (c == 'l') { + nas[cn].type = TYPE_LONG; + c = *p++; + if (c == 'l') { + nas[cn].type = TYPE_LONGLONG; + c = *p++; + } + } else if (c == 'z' || c == 'I') { + static_assert(sizeof(size_t) == sizeof(int) || + sizeof(size_t) == sizeof(long) || + sizeof(size_t) == sizeof(long long), + "size_t is not one of the expected sizes"); + nas[cn].type = sizeof(size_t) == sizeof(int) ? TYPE_INTN + : sizeof(size_t) == sizeof(long) ? TYPE_LONG + : TYPE_LONGLONG; + c = *p++; + } else if (c == 't') { + static_assert(sizeof(ptrdiff_t) == sizeof(int) || + sizeof(ptrdiff_t) == sizeof(long) || + sizeof(ptrdiff_t) == sizeof(long long), + "ptrdiff_t is not one of the expected sizes"); + nas[cn].type = sizeof(ptrdiff_t) == sizeof(int) ? TYPE_INTN + : sizeof(ptrdiff_t) == sizeof(long) ? TYPE_LONG + : TYPE_LONGLONG; + c = *p++; + } else if (c == 'j') { + static_assert(sizeof(intmax_t) == sizeof(int) || + sizeof(intmax_t) == sizeof(long) || + sizeof(intmax_t) == sizeof(long long), + "intmax_t is not one of the expected sizes"); + nas[cn].type = sizeof(intmax_t) == sizeof(int) ? TYPE_INTN + : sizeof(intmax_t) == sizeof(long) ? TYPE_LONG + : TYPE_LONGLONG; + c = *p++; + } + + // format + switch (c) { + case 'd': + case 'c': + case 'i': + break; + + case 'o': + case 'u': + case 'x': + case 'X': + // Mark as unsigned type. + nas[cn].type |= 1; + break; + + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': + nas[cn].type = TYPE_DOUBLE; + break; + + case 'p': + nas[cn].type = TYPE_POINTER; + break; + + case 'S': +#if defined(XP_WIN) + nas[cn].type = TYPE_WSTRING; +#else + MOZ_ASSERT(0); + nas[cn].type = TYPE_UNKNOWN; +#endif + break; + + case 's': +#if defined(XP_WIN) + if (nas[cn].type == TYPE_LONG) { + nas[cn].type = TYPE_WSTRING; + break; + } +#endif + // Other type sizes are not supported here. + MOZ_ASSERT(nas[cn].type == TYPE_INTN); + nas[cn].type = TYPE_STRING; + break; + + case 'n': + nas[cn].type = TYPE_INTSTR; + break; + + default: + MOZ_ASSERT(0); + nas[cn].type = TYPE_UNKNOWN; + break; + } + + // get a legal para. + if (nas[cn].type == TYPE_UNKNOWN) { + MOZ_CRASH("Bad format string"); + } + } + + // Third pass: + // Fill nas[].ap. + + cn = 0; + while (cn < number) { + // A TYPE_UNKNOWN here means that the format asked for a + // positional argument without specifying the meaning of some + // earlier argument. + MOZ_ASSERT(nas[cn].type != TYPE_UNKNOWN); + + va_copy(nas[cn].ap, ap); + + switch (nas[cn].type) { + case TYPE_SCHAR: + case TYPE_UCHAR: + case TYPE_SHORT: + case TYPE_USHORT: + case TYPE_INTN: + case TYPE_UINTN: + (void)va_arg(ap, int); + break; + case TYPE_LONG: + (void)va_arg(ap, long); + break; + case TYPE_ULONG: + (void)va_arg(ap, unsigned long); + break; + case TYPE_LONGLONG: + (void)va_arg(ap, long long); + break; + case TYPE_ULONGLONG: + (void)va_arg(ap, unsigned long long); + break; + case TYPE_STRING: + (void)va_arg(ap, char*); + break; + case TYPE_INTSTR: + (void)va_arg(ap, int*); + break; + case TYPE_DOUBLE: + (void)va_arg(ap, double); + break; + case TYPE_POINTER: + (void)va_arg(ap, void*); + break; +#if defined(XP_WIN) + case TYPE_WSTRING: + (void)va_arg(ap, wchar_t*); + break; +#endif + + default: + MOZ_CRASH(); + } + + cn++; + } + + return true; +} + +mozilla::PrintfTarget::PrintfTarget() : mEmitted(0) {} + +bool mozilla::PrintfTarget::vprint(const char* fmt, va_list ap) { + char c; + int flags, width, prec, radix, type; + union { + char ch; + int i; + long l; + long long ll; + double d; + const char* s; + int* ip; + void* p; +#if defined(XP_WIN) + const wchar_t* ws; +#endif + } u{}; + const char* hexp; + int i; + + // Build an argument array, IF the fmt is numbered argument + // list style, to contain the Numbered Argument list pointers. + + NumArgStateVector nas; + if (!BuildArgArray(fmt, ap, nas)) { + // the fmt contains error Numbered Argument format, jliu@netscape.com + MOZ_CRASH("Bad format string"); + } + + while ((c = *fmt++) != 0) { + if (c != '%') { + if (!emit(fmt - 1, 1)) { + return false; + } + + continue; + } + + // Gobble up the % format string. Hopefully we have handled all + // of the strange cases! + flags = 0; + c = *fmt++; + if (c == '%') { + // quoting a % with %% + if (!emit(fmt - 1, 1)) { + return false; + } + + continue; + } + + if (!nas.empty()) { + // the fmt contains the Numbered Arguments feature + i = 0; + while (c && c != '$') { // should improve error check later + i = (i * 10) + (c - '0'); + c = *fmt++; + } + + if (nas[i - 1].type == TYPE_UNKNOWN) { + MOZ_CRASH("Bad format string"); + } + + ap = nas[i - 1].ap; + c = *fmt++; + } + + // Examine optional flags. Note that we do not implement the + // '#' flag of sprintf(). The ANSI C spec. of the '#' flag is + // somewhat ambiguous and not ideal, which is perhaps why + // the various sprintf() implementations are inconsistent + // on this feature. + while ((c == '-') || (c == '+') || (c == ' ') || (c == '0')) { + if (c == '-') { + flags |= FLAG_LEFT; + } + if (c == '+') { + flags |= FLAG_SIGNED; + } + if (c == ' ') { + flags |= FLAG_SPACED; + } + if (c == '0') { + flags |= FLAG_ZEROS; + } + c = *fmt++; + } + if (flags & FLAG_SIGNED) { + flags &= ~FLAG_SPACED; + } + if (flags & FLAG_LEFT) { + flags &= ~FLAG_ZEROS; + } + + // width + if (c == '*') { + c = *fmt++; + width = va_arg(ap, int); + if (width < 0) { + width = -width; + flags |= FLAG_LEFT; + flags &= ~FLAG_ZEROS; + } + } else { + width = 0; + while ((c >= '0') && (c <= '9')) { + width = (width * 10) + (c - '0'); + c = *fmt++; + } + } + + // precision + prec = -1; + if (c == '.') { + c = *fmt++; + if (c == '*') { + c = *fmt++; + prec = va_arg(ap, int); + } else { + prec = 0; + while ((c >= '0') && (c <= '9')) { + prec = (prec * 10) + (c - '0'); + c = *fmt++; + } + } + } + + // size + type = TYPE_INTN; + if (c == 'h') { + type = TYPE_SHORT; + c = *fmt++; + if (c == 'h') { + type = TYPE_SCHAR; + c = *fmt++; + } + } else if (c == 'L') { + type = TYPE_LONGLONG; + c = *fmt++; + } else if (c == 'l') { + type = TYPE_LONG; + c = *fmt++; + if (c == 'l') { + type = TYPE_LONGLONG; + c = *fmt++; + } + } else if (c == 'z' || c == 'I') { + static_assert(sizeof(size_t) == sizeof(int) || + sizeof(size_t) == sizeof(long) || + sizeof(size_t) == sizeof(long long), + "size_t is not one of the expected sizes"); + type = sizeof(size_t) == sizeof(int) ? TYPE_INTN + : sizeof(size_t) == sizeof(long) ? TYPE_LONG + : TYPE_LONGLONG; + c = *fmt++; + } else if (c == 't') { + static_assert(sizeof(ptrdiff_t) == sizeof(int) || + sizeof(ptrdiff_t) == sizeof(long) || + sizeof(ptrdiff_t) == sizeof(long long), + "ptrdiff_t is not one of the expected sizes"); + type = sizeof(ptrdiff_t) == sizeof(int) ? TYPE_INTN + : sizeof(ptrdiff_t) == sizeof(long) ? TYPE_LONG + : TYPE_LONGLONG; + c = *fmt++; + } else if (c == 'j') { + static_assert(sizeof(intmax_t) == sizeof(int) || + sizeof(intmax_t) == sizeof(long) || + sizeof(intmax_t) == sizeof(long long), + "intmax_t is not one of the expected sizes"); + type = sizeof(intmax_t) == sizeof(int) ? TYPE_INTN + : sizeof(intmax_t) == sizeof(long) ? TYPE_LONG + : TYPE_LONGLONG; + c = *fmt++; + } + + // format + hexp = hex; + switch (c) { + case 'd': + case 'i': // decimal/integer + radix = 10; + goto fetch_and_convert; + + case 'o': // octal + radix = 8; + type |= 1; + goto fetch_and_convert; + + case 'u': // unsigned decimal + radix = 10; + type |= 1; + goto fetch_and_convert; + + case 'x': // unsigned hex + radix = 16; + type |= 1; + goto fetch_and_convert; + + case 'X': // unsigned HEX + radix = 16; + hexp = HEX; + type |= 1; + goto fetch_and_convert; + + fetch_and_convert: + switch (type) { + case TYPE_SCHAR: + u.l = (signed char)va_arg(ap, int); + if (u.l < 0) { + u.l = -u.l; + flags |= FLAG_NEG; + } + goto do_long; + case TYPE_UCHAR: + u.l = (unsigned char)va_arg(ap, unsigned int); + goto do_long; + case TYPE_SHORT: + u.l = (short)va_arg(ap, int); + if (u.l < 0) { + u.l = -u.l; + flags |= FLAG_NEG; + } + goto do_long; + case TYPE_USHORT: + u.l = (unsigned short)va_arg(ap, unsigned int); + goto do_long; + case TYPE_INTN: + u.l = va_arg(ap, int); + if (u.l < 0) { + u.l = -u.l; + flags |= FLAG_NEG; + } + goto do_long; + case TYPE_UINTN: + u.l = (long)va_arg(ap, unsigned int); + goto do_long; + + case TYPE_LONG: + u.l = va_arg(ap, long); + if (u.l < 0) { + u.l = -u.l; + flags |= FLAG_NEG; + } + goto do_long; + case TYPE_ULONG: + u.l = (long)va_arg(ap, unsigned long); + do_long: + if (!cvt_l(u.l, width, prec, radix, type, flags, hexp)) { + return false; + } + + break; + + case TYPE_LONGLONG: + u.ll = va_arg(ap, long long); + if (u.ll < 0) { + u.ll = -u.ll; + flags |= FLAG_NEG; + } + goto do_longlong; + case TYPE_POINTER: + u.ll = (uintptr_t)va_arg(ap, void*); + goto do_longlong; + case TYPE_ULONGLONG: + u.ll = va_arg(ap, unsigned long long); + do_longlong: + if (!cvt_ll(u.ll, width, prec, radix, type, flags, hexp)) { + return false; + } + + break; + } + break; + + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': + u.d = va_arg(ap, double); + if (!cvt_f(u.d, c, width, prec, flags)) { + return false; + } + + break; + + case 'c': + if ((flags & FLAG_LEFT) == 0) { + while (width-- > 1) { + if (!emit(" ", 1)) { + return false; + } + } + } + switch (type) { + case TYPE_SHORT: + case TYPE_INTN: + u.ch = va_arg(ap, int); + if (!emit(&u.ch, 1)) { + return false; + } + break; + } + if (flags & FLAG_LEFT) { + while (width-- > 1) { + if (!emit(" ", 1)) { + return false; + } + } + } + break; + + case 'p': + type = TYPE_POINTER; + radix = 16; + goto fetch_and_convert; + + case 's': + if (type == TYPE_INTN) { + u.s = va_arg(ap, const char*); + if (!cvt_s(u.s, width, prec, flags)) { + return false; + } + break; + } + MOZ_ASSERT(type == TYPE_LONG); + [[fallthrough]]; + case 'S': +#if defined(XP_WIN) + { + u.ws = va_arg(ap, const wchar_t*); + + int rv = WideCharToMultiByte(CP_ACP, 0, u.ws, -1, NULL, 0, NULL, NULL); + if (rv == 0 && GetLastError() == ERROR_NO_UNICODE_TRANSLATION) { + if (!cvt_s("<unicode errors in string>", width, prec, flags)) { + return false; + } + } else { + if (rv == 0) { + rv = 1; + } + UniqueFreePtr<char[]> buf((char*)malloc(rv)); + WideCharToMultiByte(CP_ACP, 0, u.ws, -1, buf.get(), rv, NULL, NULL); + buf[rv - 1] = '\0'; + + if (!cvt_s(buf.get(), width, prec, flags)) { + return false; + } + } + } +#else + // Not supported here. + MOZ_ASSERT(0); +#endif + break; + + case 'n': + u.ip = va_arg(ap, int*); + if (u.ip) { + *u.ip = mEmitted; + } + break; + + default: + // Not a % token after all... skip it + if (!emit("%", 1)) { + return false; + } + if (!emit(fmt - 1, 1)) { + return false; + } + } + } + + return true; +} + +/************************************************************************/ + +bool mozilla::PrintfTarget::print(const char* format, ...) { + va_list ap; + + va_start(ap, format); + bool result = vprint(format, ap); + va_end(ap); + return result; +} + +#undef TYPE_SHORT +#undef TYPE_USHORT +#undef TYPE_INTN +#undef TYPE_UINTN +#undef TYPE_LONG +#undef TYPE_ULONG +#undef TYPE_LONGLONG +#undef TYPE_ULONGLONG +#undef TYPE_STRING +#undef TYPE_DOUBLE +#undef TYPE_INTSTR +#undef TYPE_POINTER +#undef TYPE_WSTRING +#undef TYPE_UNKNOWN +#undef TYPE_SCHAR +#undef TYPE_UCHAR + +#undef FLAG_LEFT +#undef FLAG_SIGNED +#undef FLAG_SPACED +#undef FLAG_ZEROS +#undef FLAG_NEG diff --git a/mozglue/misc/Printf.h b/mozglue/misc/Printf.h new file mode 100644 index 0000000000..3f4fa2c1bb --- /dev/null +++ b/mozglue/misc/Printf.h @@ -0,0 +1,266 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Printf-like functions, with canned variants that malloc their result. */ + +#ifndef mozilla_Printf_h +#define mozilla_Printf_h + +/* +** API for PR printf like routines. +** +** These exist partly for historical reasons -- initially they were in +** NSPR, then forked in tree and modified in js/ -- but now the prime +** motivation is both closer control over the exact formatting (with +** one exception, see below) and also the ability to control where +** exactly the generated results are sent. +** +** It might seem that this could all be dispensed with in favor of a +** wrapper around |vsnprintf| -- except that this implementation +** guarantees that the %s format will accept a NULL pointer, whereas +** with standard functions this is undefined. +** +** This supports the following formats. It implements a subset of the +** standard formats; due to the use of MOZ_FORMAT_PRINTF, it is not +** permissible to extend the standard, aside from relaxing undefined +** behavior. +** +** %d - decimal +** %u - unsigned decimal +** %x - unsigned hex +** %X - unsigned uppercase hex +** %o - unsigned octal +** %hd, %hu, %hx, %hX, %ho - "short" versions of above +** %ld, %lu, %lx, %lX, %lo - "long" versions of above +** %lld, %llu, %llx, %llX, %llo - "long long" versions of above +** %zd, %zo, %zu, %zx, %zX - size_t versions of above +** %Id, %Io, %Iu, %Ix, %IX - size_t versions of above (for Windows compat). +** Note that MSVC 2015 and newer supports the z length modifier so +** users should prefer using %z instead of %I. We are supporting %I in +** addition to %z in case third-party code that uses %I gets routed to +** use this printf implementation. +** %s - string +** %S, %ls - wide string, that is wchar_t* +** %c - character +** %p - pointer (deals with machine dependent pointer size) +** %f - float; note that this is actually formatted using the +** system's native printf, and so the results may vary +** %g - float; note that this is actually formatted using the +** system's native printf, and so the results may vary +*/ + +#include "mozilla/AllocPolicy.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Types.h" +#include "mozilla/UniquePtr.h" + +#include <stdarg.h> +#include <string.h> + +namespace mozilla { + +/* + * This class may be subclassed to provide a way to get the output of + * a printf-like call, as the output is generated. + */ +class PrintfTarget { + public: + /* The Printf-like interface. */ + bool MFBT_API print(const char* format, ...) MOZ_FORMAT_PRINTF(2, 3); + + /* The Vprintf-like interface. */ + bool MFBT_API vprint(const char* fmt, va_list) MOZ_FORMAT_PRINTF(2, 0); + + /* Fast paths for formatting integers as though by %d, %o, %u, or %x. + Since octal and hex formatting always treat numbers as unsigned, there + are no signed overloads for AppendInt{Oct,Hex}. */ + bool MFBT_API appendIntDec(int32_t); + bool MFBT_API appendIntDec(uint32_t); + bool MFBT_API appendIntOct(uint32_t); + bool MFBT_API appendIntHex(uint32_t); + bool MFBT_API appendIntDec(int64_t); + bool MFBT_API appendIntDec(uint64_t); + bool MFBT_API appendIntOct(uint64_t); + bool MFBT_API appendIntHex(uint64_t); + + inline size_t emitted() { return mEmitted; } + + protected: + MFBT_API PrintfTarget(); + virtual ~PrintfTarget() = default; + + /* Subclasses override this. It is called when more output is + available. It may be called with len==0. This should return + true on success, or false on failure. */ + virtual bool append(const char* sp, size_t len) = 0; + + private: + /* Number of bytes emitted so far. */ + size_t mEmitted; + + /* The implementation calls this to emit bytes and update + mEmitted. */ + bool emit(const char* sp, size_t len) { + mEmitted += len; + return append(sp, len); + } + + bool fill2(const char* src, int srclen, int width, int flags); + bool fill_n(const char* src, int srclen, int width, int prec, int type, + int flags); + bool cvt_l(long num, int width, int prec, int radix, int type, int flags, + const char* hexp); + bool cvt_ll(int64_t num, int width, int prec, int radix, int type, int flags, + const char* hexp); + bool cvt_f(double d, char c, int width, int prec, int flags); + bool cvt_s(const char* s, int width, int prec, int flags); +}; + +namespace detail { + +template <typename AllocPolicy = mozilla::MallocAllocPolicy> +struct AllocPolicyBasedFreePolicy { + void operator()(const void* ptr) { + AllocPolicy policy; + policy.free_(const_cast<void*>(ptr)); + } +}; + +} // namespace detail + +// The type returned by Smprintf and friends. +template <typename AllocPolicy> +using SmprintfPolicyPointer = + mozilla::UniquePtr<char, detail::AllocPolicyBasedFreePolicy<AllocPolicy>>; + +// The default type if no alloc policy is specified. +typedef SmprintfPolicyPointer<mozilla::MallocAllocPolicy> SmprintfPointer; + +// Used in the implementation of Smprintf et al. +template <typename AllocPolicy> +class MOZ_STACK_CLASS SprintfState final : private mozilla::PrintfTarget, + private AllocPolicy { + public: + explicit SprintfState(char* base) + : mMaxlen(base ? strlen(base) : 0), + mBase(base), + mCur(base ? base + mMaxlen : 0) {} + + ~SprintfState() { this->free_(mBase); } + + bool vprint(const char* format, va_list ap_list) MOZ_FORMAT_PRINTF(2, 0) { + // The "" here has a single \0 character, which is what we're + // trying to append. + return mozilla::PrintfTarget::vprint(format, ap_list) && append("", 1); + } + + SmprintfPolicyPointer<AllocPolicy> release() { + SmprintfPolicyPointer<AllocPolicy> result(mBase); + mBase = nullptr; + return result; + } + + protected: + bool append(const char* sp, size_t len) override { + ptrdiff_t off; + char* newbase; + size_t newlen; + + off = mCur - mBase; + if (off + len >= mMaxlen) { + /* Grow the buffer */ + newlen = mMaxlen + ((len > 32) ? len : 32); + newbase = this->template maybe_pod_malloc<char>(newlen); + if (!newbase) { + /* Ran out of memory */ + return false; + } + memcpy(newbase, mBase, mMaxlen); + this->free_(mBase); + mBase = newbase; + mMaxlen = newlen; + mCur = mBase + off; + } + + /* Copy data */ + memcpy(mCur, sp, len); + mCur += len; + MOZ_ASSERT(size_t(mCur - mBase) <= mMaxlen); + return true; + } + + private: + size_t mMaxlen; + char* mBase; + char* mCur; +}; + +/* +** sprintf into a malloc'd buffer. Return a pointer to the malloc'd +** buffer on success, nullptr on failure. Call AllocPolicy::free_ to release +** the memory returned. +*/ +template <typename AllocPolicy = mozilla::MallocAllocPolicy> +MOZ_FORMAT_PRINTF(1, 2) +SmprintfPolicyPointer<AllocPolicy> Smprintf(const char* fmt, ...) { + SprintfState<AllocPolicy> ss(nullptr); + va_list ap; + va_start(ap, fmt); + bool r = ss.vprint(fmt, ap); + va_end(ap); + if (!r) { + return nullptr; + } + return ss.release(); +} + +/* +** "append" sprintf into a malloc'd buffer. "last" is the last value of +** the malloc'd buffer. sprintf will append data to the end of last, +** growing it as necessary using realloc. If last is nullptr, SmprintfAppend +** will allocate the initial string. The return value is the new value of +** last for subsequent calls, or nullptr if there is a malloc failure. +*/ +template <typename AllocPolicy = mozilla::MallocAllocPolicy> +MOZ_FORMAT_PRINTF(2, 3) +SmprintfPolicyPointer<AllocPolicy> SmprintfAppend( + SmprintfPolicyPointer<AllocPolicy>&& last, const char* fmt, ...) { + SprintfState<AllocPolicy> ss(last.release()); + va_list ap; + va_start(ap, fmt); + bool r = ss.vprint(fmt, ap); + va_end(ap); + if (!r) { + return nullptr; + } + return ss.release(); +} + +/* +** va_list forms of the above. +*/ +template <typename AllocPolicy = mozilla::MallocAllocPolicy> +MOZ_FORMAT_PRINTF(1, 0) +SmprintfPolicyPointer<AllocPolicy> Vsmprintf(const char* fmt, va_list ap) { + SprintfState<AllocPolicy> ss(nullptr); + if (!ss.vprint(fmt, ap)) return nullptr; + return ss.release(); +} + +template <typename AllocPolicy = mozilla::MallocAllocPolicy> +MOZ_FORMAT_PRINTF(2, 0) +SmprintfPolicyPointer<AllocPolicy> VsmprintfAppend( + SmprintfPolicyPointer<AllocPolicy>&& last, const char* fmt, va_list ap) { + SprintfState<AllocPolicy> ss(last.release()); + if (!ss.vprint(fmt, ap)) return nullptr; + return ss.release(); +} + +} // namespace mozilla + +#endif /* mozilla_Printf_h */ diff --git a/mozglue/misc/ProcessType.cpp b/mozglue/misc/ProcessType.cpp new file mode 100644 index 0000000000..031d242328 --- /dev/null +++ b/mozglue/misc/ProcessType.cpp @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ProcessType.h" + +#include <cstring> + +#include "mozilla/Assertions.h" + +using namespace mozilla::startup; + +namespace mozilla { +namespace startup { +GeckoProcessType sChildProcessType = GeckoProcessType_Default; +} // namespace startup + +void SetGeckoProcessType(const char* aProcessTypeString) { +#if !defined(DEBUG) + // If not a DEBUG build then just return if sChildProcessType has already been + // set and is not fork server. In DEBUG builds we will check that process type + // matches the one already set if it is not fork server. + if (sChildProcessType != GeckoProcessType_Default && + sChildProcessType != GeckoProcessType_ForkServer) { + return; + } +#endif + +#define GECKO_PROCESS_TYPE(enum_value, enum_name, string_name, proc_typename, \ + process_bin_type, procinfo_typename, \ + webidl_typename, allcaps_name) \ + if (std::strcmp(aProcessTypeString, string_name) == 0) { \ + MOZ_ASSERT_IF( \ + sChildProcessType != GeckoProcessType_Default && \ + sChildProcessType != GeckoProcessType_ForkServer, \ + sChildProcessType == GeckoProcessType::GeckoProcessType_##enum_name); \ + sChildProcessType = GeckoProcessType::GeckoProcessType_##enum_name; \ + return; \ + } +#define SKIP_PROCESS_TYPE_DEFAULT +#if !defined(MOZ_ENABLE_FORKSERVER) +# define SKIP_PROCESS_TYPE_FORKSERVER +#endif +#if !defined(ENABLE_TESTS) +# define SKIP_PROCESS_TYPE_IPDLUNITTEST +#endif +#include "mozilla/GeckoProcessTypes.h" +#undef SKIP_PROCESS_TYPE_IPDLUNITTEST +#undef SKIP_PROCESS_TYPE_FORKSERVER +#undef SKIP_PROCESS_TYPE_DEFAULT +#undef GECKO_PROCESS_TYPE + + MOZ_CRASH("aProcessTypeString is not valid."); +} + +} // namespace mozilla diff --git a/mozglue/misc/ProcessType.h b/mozglue/misc/ProcessType.h new file mode 100644 index 0000000000..890ed4faac --- /dev/null +++ b/mozglue/misc/ProcessType.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef IPC_PROCESSTYPE_H_ +#define IPC_PROCESSTYPE_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/Types.h" + +// This enum is not dense. See GeckoProcessTypes.h for details. +enum GeckoProcessType { +#define GECKO_PROCESS_TYPE(enum_value, enum_name, string_name, proc_typename, \ + process_bin_type, procinfo_typename, \ + webidl_typename, allcaps_name) \ + GeckoProcessType_##enum_name = (enum_value), +#include "mozilla/GeckoProcessTypes.h" +#undef GECKO_PROCESS_TYPE + GeckoProcessType_End, + GeckoProcessType_Invalid = GeckoProcessType_End +}; + +namespace mozilla { +namespace startup { +extern MFBT_DATA GeckoProcessType sChildProcessType; +} // namespace startup + +/** + * @return the GeckoProcessType of the current process. + */ +MOZ_ALWAYS_INLINE GeckoProcessType GetGeckoProcessType() { + return startup::sChildProcessType; +} + +/** + * Set the gecko process type based on a null-terminated byte string. + */ +MFBT_API void SetGeckoProcessType(const char* aProcessTypeString); + +} // namespace mozilla + +#endif // IPC_PROCESSTYPE_H_ diff --git a/mozglue/misc/RWLock_posix.cpp b/mozglue/misc/RWLock_posix.cpp new file mode 100644 index 0000000000..970bddd1aa --- /dev/null +++ b/mozglue/misc/RWLock_posix.cpp @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef XP_WIN +# error This file should only be compiled on non-Windows platforms. +#endif + +#include "mozilla/PlatformRWLock.h" + +#include "mozilla/Assertions.h" + +#include <errno.h> + +// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) +mozilla::detail::RWLockImpl::RWLockImpl() { + MOZ_RELEASE_ASSERT(pthread_rwlock_init(&mRWLock, nullptr) == 0, + "pthread_rwlock_init failed"); +} + +mozilla::detail::RWLockImpl::~RWLockImpl() { + MOZ_RELEASE_ASSERT(pthread_rwlock_destroy(&mRWLock) == 0, + "pthread_rwlock_destroy failed"); +} + +bool mozilla::detail::RWLockImpl::tryReadLock() { + int rv = pthread_rwlock_tryrdlock(&mRWLock); + // We allow EDEADLK here because it has been observed returned on macos when + // the write lock is held by the current thread. + MOZ_RELEASE_ASSERT(rv == 0 || rv == EBUSY || rv == EDEADLK, + "pthread_rwlock_tryrdlock failed"); + return rv == 0; +} + +void mozilla::detail::RWLockImpl::readLock() { + MOZ_RELEASE_ASSERT(pthread_rwlock_rdlock(&mRWLock) == 0, + "pthread_rwlock_rdlock failed"); +} + +void mozilla::detail::RWLockImpl::readUnlock() { + MOZ_RELEASE_ASSERT(pthread_rwlock_unlock(&mRWLock) == 0, + "pthread_rwlock_unlock failed"); +} + +bool mozilla::detail::RWLockImpl::tryWriteLock() { + int rv = pthread_rwlock_trywrlock(&mRWLock); + // We allow EDEADLK here because it has been observed returned on macos when + // the write lock is held by the current thread. + MOZ_RELEASE_ASSERT(rv == 0 || rv == EBUSY || rv == EDEADLK, + "pthread_rwlock_trywrlock failed"); + return rv == 0; +} + +void mozilla::detail::RWLockImpl::writeLock() { + MOZ_RELEASE_ASSERT(pthread_rwlock_wrlock(&mRWLock) == 0, + "pthread_rwlock_wrlock failed"); +} + +void mozilla::detail::RWLockImpl::writeUnlock() { + MOZ_RELEASE_ASSERT(pthread_rwlock_unlock(&mRWLock) == 0, + "pthread_rwlock_unlock failed"); +} diff --git a/mozglue/misc/RWLock_windows.cpp b/mozglue/misc/RWLock_windows.cpp new file mode 100644 index 0000000000..2c347f218c --- /dev/null +++ b/mozglue/misc/RWLock_windows.cpp @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef XP_WIN +# error This file should only be compiled on Windows. +#endif + +#include "mozilla/PlatformRWLock.h" + +#include <windows.h> + +#define NativeHandle(m) (reinterpret_cast<SRWLOCK*>(&m)) + +mozilla::detail::RWLockImpl::RWLockImpl() { + static_assert(sizeof(SRWLOCK) <= sizeof(mRWLock), "SRWLOCK is too big!"); + InitializeSRWLock(NativeHandle(mRWLock)); +} + +mozilla::detail::RWLockImpl::~RWLockImpl() {} + +bool mozilla::detail::RWLockImpl::tryReadLock() { + return TryAcquireSRWLockShared(NativeHandle(mRWLock)); +} + +void mozilla::detail::RWLockImpl::readLock() { + AcquireSRWLockShared(NativeHandle(mRWLock)); +} + +void mozilla::detail::RWLockImpl::readUnlock() { + ReleaseSRWLockShared(NativeHandle(mRWLock)); +} + +bool mozilla::detail::RWLockImpl::tryWriteLock() { + return TryAcquireSRWLockExclusive(NativeHandle(mRWLock)); +} + +void mozilla::detail::RWLockImpl::writeLock() { + AcquireSRWLockExclusive(NativeHandle(mRWLock)); +} + +void mozilla::detail::RWLockImpl::writeUnlock() { + ReleaseSRWLockExclusive(NativeHandle(mRWLock)); +} + +#undef NativeHandle diff --git a/mozglue/misc/RuntimeExceptionModule.cpp b/mozglue/misc/RuntimeExceptionModule.cpp new file mode 100644 index 0000000000..b8a41cd7cf --- /dev/null +++ b/mozglue/misc/RuntimeExceptionModule.cpp @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RuntimeExceptionModule.h" + +#include <cstdint> + +#include "mozilla/ProcessType.h" + +#if defined(XP_WIN) +# include <windows.h> +# if defined(__MINGW32__) || defined(__MINGW64__) +// Add missing constants and types for mingw builds +typedef HANDLE HREPORT; +# define WerReportSubmit(a, b, c, d) \ + WerReportSubmit(a, b, c, WER_SUBMIT_RESULT* pSubmitResult) +# define WER_MAX_PREFERRED_MODULES_BUFFER 256 +# endif // defined(__MINGW32__) || defined(__MINGW64__) +# include <werapi.h> // For WerRegisterRuntimeExceptionModule() +# if defined(__MINGW32__) || defined(__MINGW64__) +# undef WerReportSubmit +# endif // defined(__MINGW32__) || defined(__MINGW64__) +# include <stdlib.h> + +# include "mozilla/Unused.h" + +using mozilla::Unused; +#endif + +namespace CrashReporter { + +#ifdef XP_WIN + +const static size_t kModulePathLength = MAX_PATH + 1; +static wchar_t sModulePath[kModulePathLength]; + +bool GetRuntimeExceptionModulePath(wchar_t* aPath, const size_t aLength) { + const wchar_t* kModuleName = L"mozwer.dll"; + DWORD res = ::GetModuleFileNameW(nullptr, aPath, aLength); + if ((res > 0) && (res != aLength)) { + wchar_t* last_backslash = wcsrchr(aPath, L'\\'); + if (last_backslash) { + *(last_backslash + 1) = L'\0'; + if (wcscat_s(aPath, aLength, kModuleName) == 0) { + return true; + } + } + } + + return false; +} + +#endif // XP_WIN + +void RegisterRuntimeExceptionModule() { +#ifdef XP_WIN +# if defined(DEBUG) + // In debug builds, disable the crash reporter by default, and allow to + // enable it with the MOZ_CRASHREPORTER environment variable. + const char* envvar = getenv("MOZ_CRASHREPORTER"); + if (!envvar || !*envvar) { + return; + } +# else + // In other builds, enable the crash reporter by default, and allow + // disabling it with the MOZ_CRASHREPORTER_DISABLE environment variable. + const char* envvar = getenv("MOZ_CRASHREPORTER_DISABLE"); + if (envvar && *envvar) { + return; + } +# endif + + // If sModulePath is set we have already registerd the module. + if (*sModulePath) { + return; + } + + // If we fail to get the path just return. + if (!GetRuntimeExceptionModulePath(sModulePath, kModulePathLength)) { + return; + } + + if (FAILED(::WerRegisterRuntimeExceptionModule( + sModulePath, + reinterpret_cast<PVOID>(mozilla::GetGeckoProcessType())))) { + // The registration failed null out sModulePath to record this. + *sModulePath = L'\0'; + return; + } +#endif // XP_WIN +} + +void UnregisterRuntimeExceptionModule() { +#ifdef XP_WIN + // If sModulePath is set then we have registered the module. + if (*sModulePath) { + Unused << ::WerUnregisterRuntimeExceptionModule( + sModulePath, reinterpret_cast<PVOID>(mozilla::GetGeckoProcessType())); + *sModulePath = L'\0'; + } +#endif // XP_WIN +} + +} // namespace CrashReporter diff --git a/mozglue/misc/RuntimeExceptionModule.h b/mozglue/misc/RuntimeExceptionModule.h new file mode 100644 index 0000000000..47468e4f36 --- /dev/null +++ b/mozglue/misc/RuntimeExceptionModule.h @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef RUNTIMEEXCEPTIONMODULE_H_ +#define RUNTIMEEXCEPTIONMODULE_H_ + +#include "mozilla/Types.h" + +namespace CrashReporter { + +MFBT_API void RegisterRuntimeExceptionModule(); + +MFBT_API void UnregisterRuntimeExceptionModule(); + +} // namespace CrashReporter + +#endif // RUNTIMEEXCEPTIONMODULE_H_ diff --git a/mozglue/misc/SIMD.cpp b/mozglue/misc/SIMD.cpp new file mode 100644 index 0000000000..3893de57b3 --- /dev/null +++ b/mozglue/misc/SIMD.cpp @@ -0,0 +1,565 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/SIMD.h" + +#include <cstring> +#include <stdint.h> +#include <type_traits> + +#include "mozilla/EndianUtils.h" +#include "mozilla/SSE.h" + +#ifdef MOZILLA_PRESUME_SSE2 + +# include <immintrin.h> + +#endif + +namespace mozilla { + +template <typename TValue> +const TValue* FindInBufferNaive(const TValue* ptr, TValue value, + size_t length) { + const TValue* end = ptr + length; + while (ptr < end) { + if (*ptr == value) { + return ptr; + } + ptr++; + } + return nullptr; +} + +#ifdef MOZILLA_PRESUME_SSE2 + +const __m128i* Cast128(uintptr_t ptr) { + return reinterpret_cast<const __m128i*>(ptr); +} + +template <typename T> +T GetAs(uintptr_t ptr) { + return *reinterpret_cast<const T*>(ptr); +} + +// Akin to ceil/floor, AlignDown/AlignUp will return the original pointer if it +// is already aligned. +uintptr_t AlignDown16(uintptr_t ptr) { return ptr & ~0xf; } + +uintptr_t AlignUp16(uintptr_t ptr) { return AlignDown16(ptr + 0xf); } + +template <typename TValue> +__m128i CmpEq128(__m128i a, __m128i b) { + static_assert(sizeof(TValue) == 1 || sizeof(TValue) == 2); + if (sizeof(TValue) == 1) { + return _mm_cmpeq_epi8(a, b); + } + return _mm_cmpeq_epi16(a, b); +} + +# ifdef __GNUC__ + +// Earlier versions of GCC are missing the _mm_loadu_si32 instruction. This +// workaround from Peter Cordes (https://stackoverflow.com/a/72837992) compiles +// down to the same instructions. We could just replace _mm_loadu_si32 +__m128i Load32BitsIntoXMM(uintptr_t ptr) { + int tmp; + memcpy(&tmp, reinterpret_cast<const void*>(ptr), + sizeof(tmp)); // unaligned aliasing-safe load + return _mm_cvtsi32_si128(tmp); // efficient on GCC/clang/MSVC +} + +# else + +__m128i Load32BitsIntoXMM(uintptr_t ptr) { + return _mm_loadu_si32(Cast128(ptr)); +} + +# endif + +const char* Check4x4Chars(__m128i needle, uintptr_t a, uintptr_t b, uintptr_t c, + uintptr_t d) { + __m128i haystackA = Load32BitsIntoXMM(a); + __m128i cmpA = CmpEq128<char>(needle, haystackA); + __m128i haystackB = Load32BitsIntoXMM(b); + __m128i cmpB = CmpEq128<char>(needle, haystackB); + __m128i haystackC = Load32BitsIntoXMM(c); + __m128i cmpC = CmpEq128<char>(needle, haystackC); + __m128i haystackD = Load32BitsIntoXMM(d); + __m128i cmpD = CmpEq128<char>(needle, haystackD); + __m128i or_ab = _mm_or_si128(cmpA, cmpB); + __m128i or_cd = _mm_or_si128(cmpC, cmpD); + __m128i or_abcd = _mm_or_si128(or_ab, or_cd); + int orMask = _mm_movemask_epi8(or_abcd); + if (orMask & 0xf) { + int cmpMask; + cmpMask = _mm_movemask_epi8(cmpA); + if (cmpMask & 0xf) { + return reinterpret_cast<const char*>(a + __builtin_ctz(cmpMask)); + } + cmpMask = _mm_movemask_epi8(cmpB); + if (cmpMask & 0xf) { + return reinterpret_cast<const char*>(b + __builtin_ctz(cmpMask)); + } + cmpMask = _mm_movemask_epi8(cmpC); + if (cmpMask & 0xf) { + return reinterpret_cast<const char*>(c + __builtin_ctz(cmpMask)); + } + cmpMask = _mm_movemask_epi8(cmpD); + if (cmpMask & 0xf) { + return reinterpret_cast<const char*>(d + __builtin_ctz(cmpMask)); + } + } + + return nullptr; +} + +template <typename TValue> +const TValue* Check4x16Bytes(__m128i needle, uintptr_t a, uintptr_t b, + uintptr_t c, uintptr_t d) { + __m128i haystackA = _mm_loadu_si128(Cast128(a)); + __m128i cmpA = CmpEq128<TValue>(needle, haystackA); + __m128i haystackB = _mm_loadu_si128(Cast128(b)); + __m128i cmpB = CmpEq128<TValue>(needle, haystackB); + __m128i haystackC = _mm_loadu_si128(Cast128(c)); + __m128i cmpC = CmpEq128<TValue>(needle, haystackC); + __m128i haystackD = _mm_loadu_si128(Cast128(d)); + __m128i cmpD = CmpEq128<TValue>(needle, haystackD); + __m128i or_ab = _mm_or_si128(cmpA, cmpB); + __m128i or_cd = _mm_or_si128(cmpC, cmpD); + __m128i or_abcd = _mm_or_si128(or_ab, or_cd); + int orMask = _mm_movemask_epi8(or_abcd); + if (orMask) { + int cmpMask; + cmpMask = _mm_movemask_epi8(cmpA); + if (cmpMask) { + return reinterpret_cast<const TValue*>(a + __builtin_ctz(cmpMask)); + } + cmpMask = _mm_movemask_epi8(cmpB); + if (cmpMask) { + return reinterpret_cast<const TValue*>(b + __builtin_ctz(cmpMask)); + } + cmpMask = _mm_movemask_epi8(cmpC); + if (cmpMask) { + return reinterpret_cast<const TValue*>(c + __builtin_ctz(cmpMask)); + } + cmpMask = _mm_movemask_epi8(cmpD); + if (cmpMask) { + return reinterpret_cast<const TValue*>(d + __builtin_ctz(cmpMask)); + } + } + + return nullptr; +} + +enum class HaystackOverlap { + Overlapping, + Sequential, +}; + +// Check two 16-byte chunks for the two-byte sequence loaded into needle1 +// followed by needle1. `carryOut` is an optional pointer which we will +// populate based on whether the last character of b matches needle1. This +// should be provided on subsequent calls via `carryIn` so we can detect cases +// where the last byte of b's 16-byte chunk is needle1 and the first byte of +// the next a's 16-byte chunk is needle2. `overlap` and whether +// `carryIn`/`carryOut` are NULL should be knowable at compile time to avoid +// branching. +template <typename TValue> +const TValue* Check2x2x16Bytes(__m128i needle1, __m128i needle2, uintptr_t a, + uintptr_t b, __m128i* carryIn, __m128i* carryOut, + HaystackOverlap overlap) { + const int shiftRightAmount = 16 - sizeof(TValue); + const int shiftLeftAmount = sizeof(TValue); + __m128i haystackA = _mm_loadu_si128(Cast128(a)); + __m128i cmpA1 = CmpEq128<TValue>(needle1, haystackA); + __m128i cmpA2 = CmpEq128<TValue>(needle2, haystackA); + __m128i cmpA; + if (carryIn) { + cmpA = _mm_and_si128( + _mm_or_si128(_mm_bslli_si128(cmpA1, shiftLeftAmount), *carryIn), cmpA2); + } else { + cmpA = _mm_and_si128(_mm_bslli_si128(cmpA1, shiftLeftAmount), cmpA2); + } + __m128i haystackB = _mm_loadu_si128(Cast128(b)); + __m128i cmpB1 = CmpEq128<TValue>(needle1, haystackB); + __m128i cmpB2 = CmpEq128<TValue>(needle2, haystackB); + __m128i cmpB; + if (overlap == HaystackOverlap::Overlapping) { + cmpB = _mm_and_si128(_mm_bslli_si128(cmpB1, shiftLeftAmount), cmpB2); + } else { + MOZ_ASSERT(overlap == HaystackOverlap::Sequential); + __m128i carryAB = _mm_bsrli_si128(cmpA1, shiftRightAmount); + cmpB = _mm_and_si128( + _mm_or_si128(_mm_bslli_si128(cmpB1, shiftLeftAmount), carryAB), cmpB2); + } + __m128i or_ab = _mm_or_si128(cmpA, cmpB); + int orMask = _mm_movemask_epi8(or_ab); + if (orMask) { + int cmpMask; + cmpMask = _mm_movemask_epi8(cmpA); + if (cmpMask) { + return reinterpret_cast<const TValue*>(a + __builtin_ctz(cmpMask) - + shiftLeftAmount); + } + cmpMask = _mm_movemask_epi8(cmpB); + if (cmpMask) { + return reinterpret_cast<const TValue*>(b + __builtin_ctz(cmpMask) - + shiftLeftAmount); + } + } + + if (carryOut) { + _mm_store_si128(carryOut, _mm_bsrli_si128(cmpB1, shiftRightAmount)); + } + + return nullptr; +} + +template <typename TValue> +const TValue* FindInBuffer(const TValue* ptr, TValue value, size_t length) { + static_assert(sizeof(TValue) == 1 || sizeof(TValue) == 2); + static_assert(std::is_unsigned<TValue>::value); + uint64_t splat64; + if (sizeof(TValue) == 1) { + splat64 = 0x0101010101010101llu; + } else { + splat64 = 0x0001000100010001llu; + } + + // Load our needle into a 16-byte register + uint64_t u64_value = static_cast<uint64_t>(value) * splat64; + int64_t i64_value = *reinterpret_cast<int64_t*>(&u64_value); + __m128i needle = _mm_set_epi64x(i64_value, i64_value); + + size_t numBytes = length * sizeof(TValue); + uintptr_t cur = reinterpret_cast<uintptr_t>(ptr); + uintptr_t end = cur + numBytes; + + if ((sizeof(TValue) > 1 && numBytes < 16) || numBytes < 4) { + while (cur < end) { + if (GetAs<TValue>(cur) == value) { + return reinterpret_cast<const TValue*>(cur); + } + cur += sizeof(TValue); + } + return nullptr; + } + + if (numBytes < 16) { + // NOTE: here and below, we have some bit fiddling which could look a + // little weird. The important thing to note though is it's just a trick + // for getting the number 4 if numBytes is greater than or equal to 8, + // and 0 otherwise. This lets us fully cover the range without any + // branching for the case where numBytes is in [4,8), and [8,16). We get + // four ranges from this - if numbytes > 8, we get: + // [0,4), [4,8], [end - 8), [end - 4) + // and if numbytes < 8, we get + // [0,4), [0,4), [end - 4), [end - 4) + uintptr_t a = cur; + uintptr_t b = cur + ((numBytes & 8) >> 1); + uintptr_t c = end - 4 - ((numBytes & 8) >> 1); + uintptr_t d = end - 4; + const char* charResult = Check4x4Chars(needle, a, b, c, d); + // Note: we ensure above that sizeof(TValue) == 1 here, so this is + // either char to char or char to something like a uint8_t. + return reinterpret_cast<const TValue*>(charResult); + } + + if (numBytes < 64) { + // NOTE: see the above explanation of the similar chunk of code, but in + // this case, replace 8 with 32 and 4 with 16. + uintptr_t a = cur; + uintptr_t b = cur + ((numBytes & 32) >> 1); + uintptr_t c = end - 16 - ((numBytes & 32) >> 1); + uintptr_t d = end - 16; + return Check4x16Bytes<TValue>(needle, a, b, c, d); + } + + // Get the initial unaligned load out of the way. This will overlap with the + // aligned stuff below, but the overlapped part should effectively be free + // (relative to a mispredict from doing a byte-by-byte loop). + __m128i haystack = _mm_loadu_si128(Cast128(cur)); + __m128i cmp = CmpEq128<TValue>(needle, haystack); + int cmpMask = _mm_movemask_epi8(cmp); + if (cmpMask) { + return reinterpret_cast<const TValue*>(cur + __builtin_ctz(cmpMask)); + } + + // Now we're working with aligned memory. Hooray! \o/ + cur = AlignUp16(cur); + + // The address of the final 48-63 bytes. We overlap this with what we check in + // our hot loop below to avoid branching. Again, the overlap should be + // negligible compared with a branch mispredict. + uintptr_t tailStartPtr = AlignDown16(end - 48); + uintptr_t tailEndPtr = end - 16; + + while (cur < tailStartPtr) { + uintptr_t a = cur; + uintptr_t b = cur + 16; + uintptr_t c = cur + 32; + uintptr_t d = cur + 48; + const TValue* result = Check4x16Bytes<TValue>(needle, a, b, c, d); + if (result) { + return result; + } + cur += 64; + } + + uintptr_t a = tailStartPtr; + uintptr_t b = tailStartPtr + 16; + uintptr_t c = tailStartPtr + 32; + uintptr_t d = tailEndPtr; + return Check4x16Bytes<TValue>(needle, a, b, c, d); +} + +template <typename TValue> +const TValue* TwoElementLoop(uintptr_t start, uintptr_t end, TValue v1, + TValue v2) { + static_assert(sizeof(TValue) == 1 || sizeof(TValue) == 2); + + const TValue* cur = reinterpret_cast<const TValue*>(start); + const TValue* preEnd = reinterpret_cast<const TValue*>(end - sizeof(TValue)); + + uint32_t expected = static_cast<uint32_t>(v1) | + (static_cast<uint32_t>(v2) << (sizeof(TValue) * 8)); + while (cur < preEnd) { + // NOTE: this should only ever be called on little endian architectures. + static_assert(MOZ_LITTLE_ENDIAN()); + // We or cur[0] and cur[1] together explicitly and compare to expected, + // in order to avoid UB from just loading them as a uint16_t/uint32_t. + // However, it will compile down the same code after optimizations on + // little endian systems which support unaligned loads. Comparing them + // value-by-value, however, will not, and seems to perform worse in local + // microbenchmarking. Even after bitwise or'ing the comparison values + // together to avoid the short circuit, the compiler doesn't seem to get + // the hint and creates two branches, the first of which might be + // frequently mispredicted. + uint32_t actual = static_cast<uint32_t>(cur[0]) | + (static_cast<uint32_t>(cur[1]) << (sizeof(TValue) * 8)); + if (actual == expected) { + return cur; + } + cur++; + } + return nullptr; +} + +template <typename TValue> +const TValue* FindTwoInBuffer(const TValue* ptr, TValue v1, TValue v2, + size_t length) { + static_assert(sizeof(TValue) == 1 || sizeof(TValue) == 2); + static_assert(std::is_unsigned<TValue>::value); + uint64_t splat64; + if (sizeof(TValue) == 1) { + splat64 = 0x0101010101010101llu; + } else { + splat64 = 0x0001000100010001llu; + } + + // Load our needle into a 16-byte register + uint64_t u64_v1 = static_cast<uint64_t>(v1) * splat64; + int64_t i64_v1 = *reinterpret_cast<int64_t*>(&u64_v1); + __m128i needle1 = _mm_set_epi64x(i64_v1, i64_v1); + uint64_t u64_v2 = static_cast<uint64_t>(v2) * splat64; + int64_t i64_v2 = *reinterpret_cast<int64_t*>(&u64_v2); + __m128i needle2 = _mm_set_epi64x(i64_v2, i64_v2); + + size_t numBytes = length * sizeof(TValue); + uintptr_t cur = reinterpret_cast<uintptr_t>(ptr); + uintptr_t end = cur + numBytes; + + if (numBytes < 16) { + return TwoElementLoop<TValue>(cur, end, v1, v2); + } + + if (numBytes < 32) { + uintptr_t a = cur; + uintptr_t b = end - 16; + return Check2x2x16Bytes<TValue>(needle1, needle2, a, b, nullptr, nullptr, + HaystackOverlap::Overlapping); + } + + // Get the initial unaligned load out of the way. This will likely overlap + // with the aligned stuff below, but the overlapped part should effectively + // be free. + __m128i haystack = _mm_loadu_si128(Cast128(cur)); + __m128i cmp1 = CmpEq128<TValue>(needle1, haystack); + __m128i cmp2 = CmpEq128<TValue>(needle2, haystack); + int cmpMask1 = _mm_movemask_epi8(cmp1); + int cmpMask2 = _mm_movemask_epi8(cmp2); + int cmpMask = (cmpMask1 << sizeof(TValue)) & cmpMask2; + if (cmpMask) { + return reinterpret_cast<const TValue*>(cur + __builtin_ctz(cmpMask) - + sizeof(TValue)); + } + + // Now we're working with aligned memory. Hooray! \o/ + cur = AlignUp16(cur); + + // The address of the final 48-63 bytes. We overlap this with what we check in + // our hot loop below to avoid branching. Again, the overlap should be + // negligible compared with a branch mispredict. + uintptr_t tailEndPtr = end - 16; + uintptr_t tailStartPtr = AlignDown16(tailEndPtr); + + __m128i cmpMaskCarry = _mm_set1_epi32(0); + while (cur < tailStartPtr) { + uintptr_t a = cur; + uintptr_t b = cur + 16; + const TValue* result = + Check2x2x16Bytes<TValue>(needle1, needle2, a, b, &cmpMaskCarry, + &cmpMaskCarry, HaystackOverlap::Sequential); + if (result) { + return result; + } + cur += 32; + } + + uint32_t carry = (cur == tailStartPtr) ? 0xffffffff : 0; + __m128i wideCarry = Load32BitsIntoXMM(reinterpret_cast<uintptr_t>(&carry)); + cmpMaskCarry = _mm_and_si128(cmpMaskCarry, wideCarry); + uintptr_t a = tailStartPtr; + uintptr_t b = tailEndPtr; + return Check2x2x16Bytes<TValue>(needle1, needle2, a, b, &cmpMaskCarry, + nullptr, HaystackOverlap::Overlapping); +} + +const char* SIMD::memchr8SSE2(const char* ptr, char value, size_t length) { + // Signed chars are just really annoying to do bit logic with. Convert to + // unsigned at the outermost scope so we don't have to worry about it. + const unsigned char* uptr = reinterpret_cast<const unsigned char*>(ptr); + unsigned char uvalue = static_cast<unsigned char>(value); + const unsigned char* uresult = + FindInBuffer<unsigned char>(uptr, uvalue, length); + return reinterpret_cast<const char*>(uresult); +} + +// So, this is a bit awkward. It generally simplifies things if we can just +// assume all the AVX2 code is 64-bit, so we have this preprocessor guard +// in SIMD_avx2 over all of its actual code, and it also defines versions +// of its endpoints that just assert false if the guard is not satisfied. +// A 32 bit processor could implement the AVX2 instruction set though, which +// would result in it passing the supports_avx2() check and landing in an +// assertion failure. Accordingly, we just don't allow that to happen. We +// are not particularly concerned about ensuring that newer 32 bit processors +// get access to the AVX2 functions exposed here. +# if defined(MOZILLA_MAY_SUPPORT_AVX2) && defined(__x86_64__) + +bool SupportsAVX2() { return supports_avx2(); } + +# else + +bool SupportsAVX2() { return false; } + +# endif + +const char* SIMD::memchr8(const char* ptr, char value, size_t length) { + if (SupportsAVX2()) { + return memchr8AVX2(ptr, value, length); + } + return memchr8SSE2(ptr, value, length); +} + +const char16_t* SIMD::memchr16SSE2(const char16_t* ptr, char16_t value, + size_t length) { + return FindInBuffer<char16_t>(ptr, value, length); +} + +const char16_t* SIMD::memchr16(const char16_t* ptr, char16_t value, + size_t length) { + if (SupportsAVX2()) { + return memchr16AVX2(ptr, value, length); + } + return memchr16SSE2(ptr, value, length); +} + +const uint64_t* SIMD::memchr64(const uint64_t* ptr, uint64_t value, + size_t length) { + if (SupportsAVX2()) { + return memchr64AVX2(ptr, value, length); + } + return FindInBufferNaive<uint64_t>(ptr, value, length); +} + +const char* SIMD::memchr2x8(const char* ptr, char v1, char v2, size_t length) { + // Signed chars are just really annoying to do bit logic with. Convert to + // unsigned at the outermost scope so we don't have to worry about it. + const unsigned char* uptr = reinterpret_cast<const unsigned char*>(ptr); + unsigned char uv1 = static_cast<unsigned char>(v1); + unsigned char uv2 = static_cast<unsigned char>(v2); + const unsigned char* uresult = + FindTwoInBuffer<unsigned char>(uptr, uv1, uv2, length); + return reinterpret_cast<const char*>(uresult); +} + +const char16_t* SIMD::memchr2x16(const char16_t* ptr, char16_t v1, char16_t v2, + size_t length) { + return FindTwoInBuffer<char16_t>(ptr, v1, v2, length); +} + +#else + +const char* SIMD::memchr8(const char* ptr, char value, size_t length) { + const void* result = ::memchr(reinterpret_cast<const void*>(ptr), + static_cast<int>(value), length); + return reinterpret_cast<const char*>(result); +} + +const char* SIMD::memchr8SSE2(const char* ptr, char value, size_t length) { + return memchr8(ptr, value, length); +} + +const char16_t* SIMD::memchr16(const char16_t* ptr, char16_t value, + size_t length) { + return FindInBufferNaive<char16_t>(ptr, value, length); +} + +const char16_t* SIMD::memchr16SSE2(const char16_t* ptr, char16_t value, + size_t length) { + return memchr16(ptr, value, length); +} + +const uint64_t* SIMD::memchr64(const uint64_t* ptr, uint64_t value, + size_t length) { + return FindInBufferNaive<uint64_t>(ptr, value, length); +} + +const char* SIMD::memchr2x8(const char* ptr, char v1, char v2, size_t length) { + const char* end = ptr + length - 1; + while (ptr < end) { + ptr = memchr8(ptr, v1, end - ptr); + if (!ptr) { + return nullptr; + } + if (ptr[1] == v2) { + return ptr; + } + ptr++; + } + return nullptr; +} + +const char16_t* SIMD::memchr2x16(const char16_t* ptr, char16_t v1, char16_t v2, + size_t length) { + const char16_t* end = ptr + length - 1; + while (ptr < end) { + ptr = memchr16(ptr, v1, end - ptr); + if (!ptr) { + return nullptr; + } + if (ptr[1] == v2) { + return ptr; + } + ptr++; + } + return nullptr; +} + +#endif + +} // namespace mozilla diff --git a/mozglue/misc/SIMD.h b/mozglue/misc/SIMD.h new file mode 100644 index 0000000000..3d17185656 --- /dev/null +++ b/mozglue/misc/SIMD.h @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_SIMD_h +#define mozilla_SIMD_h + +#include "mozilla/Types.h" + +namespace mozilla { +// A collection of SIMD-implemented algorithms. Some of these exist in the CRT. +// However, the quality of the C runtime implementation varies wildly across +// platforms, so these should at least ensure consistency. +// +// NOTE: these are currently only implemented with hand-written SIMD for x86 +// and AMD64 platforms, and fallback to the the C runtime or naive loops on +// other architectures. Please consider this before switching an already +// optimized loop to these helpers. +class SIMD { + public: + // NOTE: for memchr we have a goofy void* signature just to be an easy drop + // in replacement for the CRT version. We also give memchr8 which is just a + // typed version of memchr. + static const void* memchr(const void* ptr, int value, size_t num) { + return memchr8(reinterpret_cast<const char*>(ptr), static_cast<char>(value), + num); + } + + // Search through `ptr[0..length]` for the first occurrence of `value` and + // return the pointer to it, or nullptr if it cannot be found. + static MFBT_API const char* memchr8(const char* ptr, char value, + size_t length); + + // This function just restricts our execution to the SSE2 path + static MFBT_API const char* memchr8SSE2(const char* ptr, char value, + size_t length); + + // This function just restricts our execution to the AVX2 path + static MFBT_API const char* memchr8AVX2(const char* ptr, char value, + size_t length); + + // Search through `ptr[0..length]` for the first occurrence of `value` and + // return the pointer to it, or nullptr if it cannot be found. + static MFBT_API const char16_t* memchr16(const char16_t* ptr, char16_t value, + size_t length); + + // This function just restricts our execution to the SSE2 path + static MFBT_API const char16_t* memchr16SSE2(const char16_t* ptr, + char16_t value, size_t length); + + // This function just restricts our execution to the AVX2 path + static MFBT_API const char16_t* memchr16AVX2(const char16_t* ptr, + char16_t value, size_t length); + + // Search through `ptr[0..length]` for the first occurrence of `value` and + // return the pointer to it, or nullptr if it cannot be found. + static MFBT_API const uint64_t* memchr64(const uint64_t* ptr, uint64_t value, + size_t length); + + // This function just restricts our execution to the AVX2 path + static MFBT_API const uint64_t* memchr64AVX2(const uint64_t* ptr, + uint64_t value, size_t length); + + // Search through `ptr[0..length]` for the first occurrence of `v1` which is + // immediately followed by `v2` and return the pointer to the occurrence of + // `v1`. + static MFBT_API const char* memchr2x8(const char* ptr, char v1, char v2, + size_t length); + + // Search through `ptr[0..length]` for the first occurrence of `v1` which is + // immediately followed by `v2` and return the pointer to the occurrence of + // `v1`. + static MFBT_API const char16_t* memchr2x16(const char16_t* ptr, char16_t v1, + char16_t v2, size_t length); +}; + +} // namespace mozilla + +#endif // mozilla_SIMD_h diff --git a/mozglue/misc/SIMD_avx2.cpp b/mozglue/misc/SIMD_avx2.cpp new file mode 100644 index 0000000000..a1467c7a55 --- /dev/null +++ b/mozglue/misc/SIMD_avx2.cpp @@ -0,0 +1,294 @@ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/SIMD.h" + +#include "mozilla/SSE.h" +#include "mozilla/Assertions.h" + +// Restricting to x86_64 simplifies things, and we're not particularly +// worried about slightly degraded performance on 32 bit processors which +// support AVX2, as this should be quite a minority. +#if defined(MOZILLA_MAY_SUPPORT_AVX2) && defined(__x86_64__) + +# include <cstring> +# include <immintrin.h> +# include <stdint.h> +# include <type_traits> + +# include "mozilla/EndianUtils.h" + +namespace mozilla { + +const __m256i* Cast256(uintptr_t ptr) { + return reinterpret_cast<const __m256i*>(ptr); +} + +template <typename T> +T GetAs(uintptr_t ptr) { + return *reinterpret_cast<const T*>(ptr); +} + +uintptr_t AlignDown32(uintptr_t ptr) { return ptr & ~0x1f; } + +uintptr_t AlignUp32(uintptr_t ptr) { return AlignDown32(ptr + 0x1f); } + +template <typename TValue> +__m128i CmpEq128(__m128i a, __m128i b) { + static_assert(sizeof(TValue) == 1 || sizeof(TValue) == 2); + if (sizeof(TValue) == 1) { + return _mm_cmpeq_epi8(a, b); + } + return _mm_cmpeq_epi16(a, b); +} + +template <typename TValue> +__m256i CmpEq256(__m256i a, __m256i b) { + static_assert(sizeof(TValue) == 1 || sizeof(TValue) == 2 || + sizeof(TValue) == 8); + if (sizeof(TValue) == 1) { + return _mm256_cmpeq_epi8(a, b); + } + if (sizeof(TValue) == 2) { + return _mm256_cmpeq_epi16(a, b); + } + + return _mm256_cmpeq_epi64(a, b); +} + +# if defined(__GNUC__) && !defined(__clang__) + +// See the comment in SIMD.cpp over Load32BitsIntoXMM. This is just adapted +// from that workaround. Testing this, it also yields the correct instructions +// across all tested compilers. +__m128i Load64BitsIntoXMM(uintptr_t ptr) { + int64_t tmp; + memcpy(&tmp, reinterpret_cast<const void*>(ptr), sizeof(tmp)); + return _mm_cvtsi64_si128(tmp); +} + +# else + +__m128i Load64BitsIntoXMM(uintptr_t ptr) { + return _mm_loadu_si64(reinterpret_cast<const __m128i*>(ptr)); +} + +# endif + +template <typename TValue> +const TValue* Check4x8Bytes(__m128i needle, uintptr_t a, uintptr_t b, + uintptr_t c, uintptr_t d) { + __m128i haystackA = Load64BitsIntoXMM(a); + __m128i cmpA = CmpEq128<TValue>(needle, haystackA); + __m128i haystackB = Load64BitsIntoXMM(b); + __m128i cmpB = CmpEq128<TValue>(needle, haystackB); + __m128i haystackC = Load64BitsIntoXMM(c); + __m128i cmpC = CmpEq128<TValue>(needle, haystackC); + __m128i haystackD = Load64BitsIntoXMM(d); + __m128i cmpD = CmpEq128<TValue>(needle, haystackD); + __m128i or_ab = _mm_or_si128(cmpA, cmpB); + __m128i or_cd = _mm_or_si128(cmpC, cmpD); + __m128i or_abcd = _mm_or_si128(or_ab, or_cd); + int orMask = _mm_movemask_epi8(or_abcd); + if (orMask & 0xff) { + int cmpMask; + cmpMask = _mm_movemask_epi8(cmpA); + if (cmpMask & 0xff) { + return reinterpret_cast<const TValue*>(a + __builtin_ctz(cmpMask)); + } + cmpMask = _mm_movemask_epi8(cmpB); + if (cmpMask & 0xff) { + return reinterpret_cast<const TValue*>(b + __builtin_ctz(cmpMask)); + } + cmpMask = _mm_movemask_epi8(cmpC); + if (cmpMask & 0xff) { + return reinterpret_cast<const TValue*>(c + __builtin_ctz(cmpMask)); + } + cmpMask = _mm_movemask_epi8(cmpD); + if (cmpMask & 0xff) { + return reinterpret_cast<const TValue*>(d + __builtin_ctz(cmpMask)); + } + } + + return nullptr; +} + +template <typename TValue> +const TValue* Check4x32Bytes(__m256i needle, uintptr_t a, uintptr_t b, + uintptr_t c, uintptr_t d) { + __m256i haystackA = _mm256_loadu_si256(Cast256(a)); + __m256i cmpA = CmpEq256<TValue>(needle, haystackA); + __m256i haystackB = _mm256_loadu_si256(Cast256(b)); + __m256i cmpB = CmpEq256<TValue>(needle, haystackB); + __m256i haystackC = _mm256_loadu_si256(Cast256(c)); + __m256i cmpC = CmpEq256<TValue>(needle, haystackC); + __m256i haystackD = _mm256_loadu_si256(Cast256(d)); + __m256i cmpD = CmpEq256<TValue>(needle, haystackD); + __m256i or_ab = _mm256_or_si256(cmpA, cmpB); + __m256i or_cd = _mm256_or_si256(cmpC, cmpD); + __m256i or_abcd = _mm256_or_si256(or_ab, or_cd); + int orMask = _mm256_movemask_epi8(or_abcd); + if (orMask) { + int cmpMask; + cmpMask = _mm256_movemask_epi8(cmpA); + if (cmpMask) { + return reinterpret_cast<const TValue*>(a + __builtin_ctz(cmpMask)); + } + cmpMask = _mm256_movemask_epi8(cmpB); + if (cmpMask) { + return reinterpret_cast<const TValue*>(b + __builtin_ctz(cmpMask)); + } + cmpMask = _mm256_movemask_epi8(cmpC); + if (cmpMask) { + return reinterpret_cast<const TValue*>(c + __builtin_ctz(cmpMask)); + } + cmpMask = _mm256_movemask_epi8(cmpD); + if (cmpMask) { + return reinterpret_cast<const TValue*>(d + __builtin_ctz(cmpMask)); + } + } + + return nullptr; +} + +template <typename TValue> +const TValue* FindInBufferAVX2(const TValue* ptr, TValue value, size_t length) { + static_assert(sizeof(TValue) == 1 || sizeof(TValue) == 2 || + sizeof(TValue) == 8); + static_assert(std::is_unsigned<TValue>::value); + + // Load our needle into a 32-byte register + __m256i needle; + if (sizeof(TValue) == 1) { + needle = _mm256_set1_epi8(value); + } else if (sizeof(TValue) == 2) { + needle = _mm256_set1_epi16(value); + } else { + needle = _mm256_set1_epi64x(value); + } + + size_t numBytes = length * sizeof(TValue); + uintptr_t cur = reinterpret_cast<uintptr_t>(ptr); + uintptr_t end = cur + numBytes; + + if (numBytes < 8 || (sizeof(TValue) == 8 && numBytes < 32)) { + while (cur < end) { + if (GetAs<TValue>(cur) == value) { + return reinterpret_cast<const TValue*>(cur); + } + cur += sizeof(TValue); + } + return nullptr; + } + + if constexpr (sizeof(TValue) != 8) { + if (numBytes < 32) { + __m128i needle_narrow; + if (sizeof(TValue) == 1) { + needle_narrow = _mm_set1_epi8(value); + } else { + needle_narrow = _mm_set1_epi16(value); + } + uintptr_t a = cur; + uintptr_t b = cur + ((numBytes & 16) >> 1); + uintptr_t c = end - 8 - ((numBytes & 16) >> 1); + uintptr_t d = end - 8; + return Check4x8Bytes<TValue>(needle_narrow, a, b, c, d); + } + } + + if (numBytes < 128) { + // NOTE: here and below, we have some bit fiddling which could look a + // little weird. The important thing to note though is it's just a trick + // for getting the number 32 if numBytes is greater than or equal to 64, + // and 0 otherwise. This lets us fully cover the range without any + // branching for the case where numBytes is in [32,64), and [64,128). We get + // four ranges from this - if numbytes > 64, we get: + // [0,32), [32,64], [end - 64), [end - 32) + // and if numbytes < 64, we get + // [0,32), [0,32), [end - 32), [end - 32) + uintptr_t a = cur; + uintptr_t b = cur + ((numBytes & 64) >> 1); + uintptr_t c = end - 32 - ((numBytes & 64) >> 1); + uintptr_t d = end - 32; + return Check4x32Bytes<TValue>(needle, a, b, c, d); + } + + // Get the initial unaligned load out of the way. This will overlap with the + // aligned stuff below, but the overlapped part should effectively be free + // (relative to a mispredict from doing a byte-by-byte loop). + __m256i haystack = _mm256_loadu_si256(Cast256(cur)); + __m256i cmp = CmpEq256<TValue>(needle, haystack); + int cmpMask = _mm256_movemask_epi8(cmp); + if (cmpMask) { + return reinterpret_cast<const TValue*>(cur + __builtin_ctz(cmpMask)); + } + + // Now we're working with aligned memory. Hooray! \o/ + cur = AlignUp32(cur); + + uintptr_t tailStartPtr = AlignDown32(end - 96); + uintptr_t tailEndPtr = end - 32; + + while (cur < tailStartPtr) { + uintptr_t a = cur; + uintptr_t b = cur + 32; + uintptr_t c = cur + 64; + uintptr_t d = cur + 96; + const TValue* result = Check4x32Bytes<TValue>(needle, a, b, c, d); + if (result) { + return result; + } + cur += 128; + } + + uintptr_t a = tailStartPtr; + uintptr_t b = tailStartPtr + 32; + uintptr_t c = tailStartPtr + 64; + uintptr_t d = tailEndPtr; + return Check4x32Bytes<TValue>(needle, a, b, c, d); +} + +const char* SIMD::memchr8AVX2(const char* ptr, char value, size_t length) { + const unsigned char* uptr = reinterpret_cast<const unsigned char*>(ptr); + unsigned char uvalue = static_cast<unsigned char>(value); + const unsigned char* uresult = + FindInBufferAVX2<unsigned char>(uptr, uvalue, length); + return reinterpret_cast<const char*>(uresult); +} + +const char16_t* SIMD::memchr16AVX2(const char16_t* ptr, char16_t value, + size_t length) { + return FindInBufferAVX2<char16_t>(ptr, value, length); +} + +const uint64_t* SIMD::memchr64AVX2(const uint64_t* ptr, uint64_t value, + size_t length) { + return FindInBufferAVX2<uint64_t>(ptr, value, length); +} + +} // namespace mozilla + +#else + +namespace mozilla { + +const char* SIMD::memchr8AVX2(const char* ptr, char value, size_t length) { + MOZ_RELEASE_ASSERT(false, "AVX2 not supported in this binary."); +} + +const char16_t* SIMD::memchr16AVX2(const char16_t* ptr, char16_t value, + size_t length) { + MOZ_RELEASE_ASSERT(false, "AVX2 not supported in this binary."); +} + +const uint64_t* SIMD::memchr64AVX2(const uint64_t* ptr, uint64_t value, + size_t length) { + MOZ_RELEASE_ASSERT(false, "AVX2 not supported in this binary."); +} + +} // namespace mozilla + +#endif diff --git a/mozglue/misc/SSE.cpp b/mozglue/misc/SSE.cpp new file mode 100644 index 0000000000..74f3917788 --- /dev/null +++ b/mozglue/misc/SSE.cpp @@ -0,0 +1,259 @@ +/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* compile-time and runtime tests for whether to use SSE instructions */ + +#include "SSE.h" + +#ifdef HAVE_CPUID_H +// cpuid.h is available on gcc 4.3 and higher on i386 and x86_64 +# include <cpuid.h> +#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64)) +// MSVC 2005 or newer on x86-32 or x86-64 +# include <intrin.h> +#endif + +namespace { + +// SSE.h has parallel #ifs which declare MOZILLA_SSE_HAVE_CPUID_DETECTION. +// We can't declare these functions in the header file, however, because +// <intrin.h> conflicts with <windows.h> on MSVC 2005, and some files want to +// include both SSE.h and <windows.h>. + +#ifdef HAVE_CPUID_H + +enum CPUIDRegister { eax = 0, ebx = 1, ecx = 2, edx = 3 }; + +static bool has_cpuid_bits(unsigned int level, CPUIDRegister reg, + unsigned int bits) { + unsigned int regs[4]; + unsigned int eax, ebx, ecx, edx; + unsigned max = __get_cpuid_max(level & 0x80000000u, nullptr); + if (level > max) return false; + __cpuid_count(level, 0, eax, ebx, ecx, edx); + regs[0] = eax; + regs[1] = ebx; + regs[2] = ecx; + regs[3] = edx; + return (regs[reg] & bits) == bits; +} + +static bool has_cpuid_bits_ex(unsigned int level, CPUIDRegister reg, + unsigned int bits) { + unsigned int regs[4]; + unsigned int eax, ebx, ecx, edx; + unsigned max = __get_cpuid_max(level & 0x80000000u, nullptr); + if (level > max) return false; + __cpuid_count(level, 1, eax, ebx, ecx, edx); + regs[0] = eax; + regs[1] = ebx; + regs[2] = ecx; + regs[3] = edx; + return (regs[reg] & bits) == bits; +} + +#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64)) + +enum CPUIDRegister { eax = 0, ebx = 1, ecx = 2, edx = 3 }; + +static bool has_cpuid_bits(unsigned int level, CPUIDRegister reg, + unsigned int bits) { + // Check that the level in question is supported. + int regs[4]; + __cpuid_ex(regs, level & 0x80000000u, 1); + if (unsigned(regs[0]) < level) return false; + + // "The __cpuid intrinsic clears the ECX register before calling the cpuid + // instruction." + __cpuid_ex(regs, level, 1); + return (unsigned(regs[reg]) & bits) == bits; +} + +#elif (defined(__GNUC__) || defined(__SUNPRO_CC)) && \ + (defined(__i386) || defined(__x86_64__)) + +enum CPUIDRegister { eax = 0, ebx = 1, ecx = 2, edx = 3 }; + +# ifdef __i386 +static void moz_cpuid(int CPUInfo[4], int InfoType) { + asm("xchg %esi, %ebx\n" + "xor %ecx, %ecx\n" // ecx is the sub-leaf (we only ever need 0) + "cpuid\n" + "movl %eax, (%edi)\n" + "movl %ebx, 4(%edi)\n" + "movl %ecx, 8(%edi)\n" + "movl %edx, 12(%edi)\n" + "xchg %esi, %ebx\n" + : + : "a"(InfoType), // %eax + "D"(CPUInfo) // %edi + : "%ecx", "%edx", "%esi"); +} +static void moz_cpuid_ex(int CPUInfo[4], int InfoType) { + asm("xchg %esi, %ebx\n" + "movl 1, %ecx\n" + "cpuid\n" + "movl %eax, (%edi)\n" + "movl %ebx, 4(%edi)\n" + "movl %ecx, 8(%edi)\n" + "movl %edx, 12(%edi)\n" + "xchg %esi, %ebx\n" + : + : "a"(InfoType), // %eax + "D"(CPUInfo) // %edi + : "%ecx", "%edx", "%esi"); +} +# else +static void moz_cpuid(int CPUInfo[4], int InfoType) { + asm("xchg %rsi, %rbx\n" + "xor %ecx, %ecx\n" // ecx is the sub-leaf (we only ever need 0) + "cpuid\n" + "movl %eax, (%rdi)\n" + "movl %ebx, 4(%rdi)\n" + "movl %ecx, 8(%rdi)\n" + "movl %edx, 12(%rdi)\n" + "xchg %rsi, %rbx\n" + : + : "a"(InfoType), // %eax + "D"(CPUInfo) // %rdi + : "%ecx", "%edx", "%rsi"); +} +static void moz_cpuid_ex(int CPUInfo[4], int InfoType) { + asm("xchg %rsi, %rbx\n" + "movl 1, %ecx\n" + "cpuid\n" + "movl %eax, (%rdi)\n" + "movl %ebx, 4(%rdi)\n" + "movl %ecx, 8(%rdi)\n" + "movl %edx, 12(%rdi)\n" + "xchg %rsi, %rbx\n" + : + : "a"(InfoType), // %eax + "D"(CPUInfo) // %rdi + : "%ecx", "%edx", "%rsi"); +} +# endif + +static bool has_cpuid_bits(unsigned int level, CPUIDRegister reg, + unsigned int bits) { + // Check that the level in question is supported. + volatile int regs[4]; + moz_cpuid((int*)regs, level & 0x80000000u); + if (unsigned(regs[0]) < level) return false; + + moz_cpuid((int*)regs, level); + return (unsigned(regs[reg]) & bits) == bits; +} + +static bool has_cpuid_bits_ex(unsigned int level, CPUIDRegister reg, + unsigned int bits) { + // Check that the level in question is supported. + volatile int regs[4]; + moz_cpuid_ex((int*)regs, level & 0x80000000u); + if (unsigned(regs[0]) < level) return false; + + moz_cpuid_ex((int*)regs, level); + return (unsigned(regs[reg]) & bits) == bits; +} + +#endif // end CPUID declarations + +} // namespace + +namespace mozilla { + +namespace sse_private { + +#if defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) + +# if !defined(MOZILLA_PRESUME_MMX) +bool mmx_enabled = has_cpuid_bits(1u, edx, (1u << 23)); +# endif + +# if !defined(MOZILLA_PRESUME_SSE) +bool sse_enabled = has_cpuid_bits(1u, edx, (1u << 25)); +# endif + +# if !defined(MOZILLA_PRESUME_SSE2) +bool sse2_enabled = has_cpuid_bits(1u, edx, (1u << 26)); +# endif + +# if !defined(MOZILLA_PRESUME_SSE3) +bool sse3_enabled = has_cpuid_bits(1u, ecx, (1u << 0)); +# endif + +# if !defined(MOZILLA_PRESUME_SSSE3) +bool ssse3_enabled = has_cpuid_bits(1u, ecx, (1u << 9)); +# endif + +# if !defined(MOZILLA_PRESUME_SSE4A) +bool sse4a_enabled = has_cpuid_bits(0x80000001u, ecx, (1u << 6)); +# endif + +# if !defined(MOZILLA_PRESUME_SSE4_1) +bool sse4_1_enabled = has_cpuid_bits(1u, ecx, (1u << 19)); +# endif + +# if !defined(MOZILLA_PRESUME_SSE4_2) +bool sse4_2_enabled = has_cpuid_bits(1u, ecx, (1u << 20)); +# endif + +# if !defined(MOZILLA_PRESUME_FMA3) +bool fma3_enabled = has_cpuid_bits(1u, ecx, (1u << 12)); +# endif + +# if !defined(MOZILLA_PRESUME_AVX) || !defined(MOZILLA_PRESUME_AVX2) +static bool has_avx() { +# if defined(MOZILLA_PRESUME_AVX) + return true; +# else + const unsigned AVX = 1u << 28; + const unsigned OSXSAVE = 1u << 27; + const unsigned XSAVE = 1u << 26; + + const unsigned XMM_STATE = 1u << 1; + const unsigned YMM_STATE = 1u << 2; + const unsigned AVX_STATE = XMM_STATE | YMM_STATE; + + return has_cpuid_bits(1u, ecx, AVX | OSXSAVE | XSAVE) && + // ensure the OS supports XSAVE of YMM registers + (xgetbv(0) & AVX_STATE) == AVX_STATE; +# endif // MOZILLA_PRESUME_AVX +} +# endif // !MOZILLA_PRESUME_AVX || !MOZILLA_PRESUME_AVX2 + +# if !defined(MOZILLA_PRESUME_AVX) +bool avx_enabled = has_avx(); +# endif + +# if !defined(MOZILLA_PRESUME_AVX2) +bool avx2_enabled = has_avx() && has_cpuid_bits(7u, ebx, (1u << 5)); +# endif + +# if !defined(MOZILLA_PRESUME_AVXVNNI) +bool avxvnni_enabled = has_cpuid_bits_ex(7u, eax, (1u << 4)); +# endif + +# if !defined(MOZILLA_PRESUME_AES) +bool aes_enabled = has_cpuid_bits(1u, ecx, (1u << 25)); +# endif + +bool has_constant_tsc = has_cpuid_bits(0x80000007u, edx, (1u << 8)); + +#endif + +} // namespace sse_private + +#ifdef HAVE_CPUID_H + +uint64_t xgetbv(uint32_t xcr) { + uint32_t eax, edx; + __asm__(".byte 0x0f, 0x01, 0xd0" : "=a"(eax), "=d"(edx) : "c"(xcr)); + return (uint64_t)(edx) << 32 | eax; +} + +#endif + +} // namespace mozilla diff --git a/mozglue/misc/SSE.h b/mozglue/misc/SSE.h new file mode 100644 index 0000000000..d7c7e4ae97 --- /dev/null +++ b/mozglue/misc/SSE.h @@ -0,0 +1,388 @@ +/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* compile-time and runtime tests for whether to use SSE instructions */ + +#ifndef mozilla_SSE_h_ +#define mozilla_SSE_h_ + +// for definition of MFBT_DATA +#include "mozilla/Types.h" + +/** + * The public interface of this header consists of a set of macros and + * functions for Intel CPU features. + * + * DETECTING ISA EXTENSIONS + * ======================== + * + * This header provides the following functions for determining whether the + * current CPU supports a particular instruction set extension: + * + * mozilla::supports_mmx + * mozilla::supports_sse + * mozilla::supports_sse2 + * mozilla::supports_sse3 + * mozilla::supports_ssse3 + * mozilla::supports_sse4a + * mozilla::supports_sse4_1 + * mozilla::supports_sse4_2 + * mozilla::supports_avx + * mozilla::supports_avx2 + * mozilla::supports_aes + * mozilla::has_constant_tsc + * + * If you're writing code using inline assembly, you should guard it with a + * call to one of these functions. For instance: + * + * if (mozilla::supports_sse2()) { + * asm(" ... "); + * } + * else { + * ... + * } + * + * Note that these functions depend on cpuid intrinsics only available in gcc + * 4.3 or later and MSVC 8.0 (Visual C++ 2005) or later, so they return false + * in older compilers. (This could be fixed by replacing the code with inline + * assembly.) + * + * + * USING INTRINSICS + * ================ + * + * This header also provides support for coding using CPU intrinsics. + * + * For each mozilla::supports_abc function, we define a MOZILLA_MAY_SUPPORT_ABC + * macro which indicates that the target/compiler combination we're using is + * compatible with the ABC extension. For instance, x86_64 with MSVC 2003 is + * compatible with SSE2 but not SSE3, since although there exist x86_64 CPUs + * with SSE3 support, MSVC 2003 only supports through SSE2. + * + * Until gcc fixes #pragma target [1] [2] or our x86 builds require SSE2, + * you'll need to separate code using intrinsics into a file separate from your + * regular code. Here's the recommended pattern: + * + * #ifdef MOZILLA_MAY_SUPPORT_ABC + * namespace mozilla { + * namespace ABC { + * void foo(); + * } + * } + * #endif + * + * void foo() { + * #ifdef MOZILLA_MAY_SUPPORT_ABC + * if (mozilla::supports_abc()) { + * mozilla::ABC::foo(); // in a separate file + * return; + * } + * #endif + * + * foo_unvectorized(); + * } + * + * You'll need to define mozilla::ABC::foo() in a separate file and add the + * -mabc flag when using gcc. + * + * [1] http://gcc.gnu.org/bugzilla/show_bug.cgi?id=39787 and + * [2] http://gcc.gnu.org/bugzilla/show_bug.cgi?id=41201 being fixed. + * + */ + +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) + +# ifdef __MMX__ +// It's ok to use MMX instructions based on the -march option (or +// the default for x86_64 or for Intel Mac). +# define MOZILLA_PRESUME_MMX 1 +# endif +# ifdef __SSE__ +// It's ok to use SSE instructions based on the -march option (or +// the default for x86_64 or for Intel Mac). +# define MOZILLA_PRESUME_SSE 1 +# endif +# ifdef __SSE2__ +// It's ok to use SSE2 instructions based on the -march option (or +// the default for x86_64 or for Intel Mac). +# define MOZILLA_PRESUME_SSE2 1 +# endif +# ifdef __SSE3__ +// It's ok to use SSE3 instructions based on the -march option (or the +// default for Intel Mac). +# define MOZILLA_PRESUME_SSE3 1 +# endif +# ifdef __SSSE3__ +// It's ok to use SSSE3 instructions based on the -march option. +# define MOZILLA_PRESUME_SSSE3 1 +# endif +# ifdef __SSE4A__ +// It's ok to use SSE4A instructions based on the -march option. +# define MOZILLA_PRESUME_SSE4A 1 +# endif +# ifdef __SSE4_1__ +// It's ok to use SSE4.1 instructions based on the -march option. +# define MOZILLA_PRESUME_SSE4_1 1 +# endif +# ifdef __SSE4_2__ +// It's ok to use SSE4.2 instructions based on the -march option. +# define MOZILLA_PRESUME_SSE4_2 1 +# endif +# ifdef __AVX__ +// It's ok to use AVX instructions based on the -march option. +# define MOZILLA_PRESUME_AVX 1 +# endif +# ifdef __AVX2__ +// It's ok to use AVX instructions based on the -march option. +# define MOZILLA_PRESUME_AVX2 1 +# endif +# ifdef __AVXVNNI__ +// It's ok to use AVX instructions based on the -march option. +# define MOZILLA_PRESUME_AVXVNNI 1 +# endif +# ifdef __AES__ +// It's ok to use AES instructions based on the -march option. +# define MOZILLA_PRESUME_AES 1 +# endif + +# ifdef HAVE_CPUID_H +# define MOZILLA_SSE_HAVE_CPUID_DETECTION +# endif + +#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64)) + +# define MOZILLA_SSE_HAVE_CPUID_DETECTION + +# if defined(_M_IX86_FP) + +# if _M_IX86_FP >= 1 +// It's ok to use SSE instructions based on the /arch option +# define MOZILLA_PRESUME_SSE +# endif +# if _M_IX86_FP >= 2 +// It's ok to use SSE2 instructions based on the /arch option +# define MOZILLA_PRESUME_SSE2 +# endif + +# elif defined(_M_AMD64) +// MSVC for AMD64 doesn't support MMX, so don't presume it here. + +// SSE is always available on AMD64. +# define MOZILLA_PRESUME_SSE +// SSE2 is always available on AMD64. +# define MOZILLA_PRESUME_SSE2 +# endif + +#elif defined(__SUNPRO_CC) && (defined(__i386) || defined(__x86_64__)) +// Sun Studio on x86 or amd64 + +# define MOZILLA_SSE_HAVE_CPUID_DETECTION + +# if defined(__x86_64__) +// MMX is always available on AMD64. +# define MOZILLA_PRESUME_MMX +// SSE is always available on AMD64. +# define MOZILLA_PRESUME_SSE +// SSE2 is always available on AMD64. +# define MOZILLA_PRESUME_SSE2 +# endif + +#endif + +namespace mozilla { + +namespace sse_private { +#if defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) +# if !defined(MOZILLA_PRESUME_MMX) +extern bool MFBT_DATA mmx_enabled; +# endif +# if !defined(MOZILLA_PRESUME_SSE) +extern bool MFBT_DATA sse_enabled; +# endif +# if !defined(MOZILLA_PRESUME_SSE2) +extern bool MFBT_DATA sse2_enabled; +# endif +# if !defined(MOZILLA_PRESUME_SSE3) +extern bool MFBT_DATA sse3_enabled; +# endif +# if !defined(MOZILLA_PRESUME_SSSE3) +extern bool MFBT_DATA ssse3_enabled; +# endif +# if !defined(MOZILLA_PRESUME_SSE4A) +extern bool MFBT_DATA sse4a_enabled; +# endif +# if !defined(MOZILLA_PRESUME_SSE4_1) +extern bool MFBT_DATA sse4_1_enabled; +# endif +# if !defined(MOZILLA_PRESUME_SSE4_2) +extern bool MFBT_DATA sse4_2_enabled; +# endif +# if !defined(MOZILLA_PRESUME_FMA3) +extern bool MFBT_DATA fma3_enabled; +# endif +# if !defined(MOZILLA_PRESUME_AVX) +extern bool MFBT_DATA avx_enabled; +# endif +# if !defined(MOZILLA_PRESUME_AVX2) +extern bool MFBT_DATA avx2_enabled; +# endif +# if !defined(MOZILLA_PRESUME_AVXVNNI) +extern bool MFBT_DATA avxvnni_enabled; +# endif +# if !defined(MOZILLA_PRESUME_AES) +extern bool MFBT_DATA aes_enabled; +# endif +extern bool MFBT_DATA has_constant_tsc; + +#endif +} // namespace sse_private + +#ifdef HAVE_CPUID_H +MOZ_EXPORT uint64_t xgetbv(uint32_t xcr); +#endif + +#if defined(MOZILLA_PRESUME_MMX) +# define MOZILLA_MAY_SUPPORT_MMX 1 +inline bool supports_mmx() { return true; } +#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) +# if !(defined(_MSC_VER) && defined(_M_AMD64)) +// Define MOZILLA_MAY_SUPPORT_MMX only if we're not on MSVC for +// AMD64, since that compiler doesn't support MMX. +# define MOZILLA_MAY_SUPPORT_MMX 1 +# endif +inline bool supports_mmx() { return sse_private::mmx_enabled; } +#else +inline bool supports_mmx() { return false; } +#endif + +#if defined(MOZILLA_PRESUME_SSE) +# define MOZILLA_MAY_SUPPORT_SSE 1 +inline bool supports_sse() { return true; } +#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) +# define MOZILLA_MAY_SUPPORT_SSE 1 +inline bool supports_sse() { return sse_private::sse_enabled; } +#else +inline bool supports_sse() { return false; } +#endif + +#if defined(MOZILLA_PRESUME_SSE2) +# define MOZILLA_MAY_SUPPORT_SSE2 1 +inline bool supports_sse2() { return true; } +#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) +# define MOZILLA_MAY_SUPPORT_SSE2 1 +inline bool supports_sse2() { return sse_private::sse2_enabled; } +#else +inline bool supports_sse2() { return false; } +#endif + +#if defined(MOZILLA_PRESUME_SSE3) +# define MOZILLA_MAY_SUPPORT_SSE3 1 +inline bool supports_sse3() { return true; } +#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) +# define MOZILLA_MAY_SUPPORT_SSE3 1 +inline bool supports_sse3() { return sse_private::sse3_enabled; } +#else +inline bool supports_sse3() { return false; } +#endif + +#if defined(MOZILLA_PRESUME_SSSE3) +# define MOZILLA_MAY_SUPPORT_SSSE3 1 +inline bool supports_ssse3() { return true; } +#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) +# define MOZILLA_MAY_SUPPORT_SSSE3 1 +inline bool supports_ssse3() { return sse_private::ssse3_enabled; } +#else +inline bool supports_ssse3() { return false; } +#endif + +#if defined(MOZILLA_PRESUME_SSE4A) +# define MOZILLA_MAY_SUPPORT_SSE4A 1 +inline bool supports_sse4a() { return true; } +#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) +# define MOZILLA_MAY_SUPPORT_SSE4A 1 +inline bool supports_sse4a() { return sse_private::sse4a_enabled; } +#else +inline bool supports_sse4a() { return false; } +#endif + +#if defined(MOZILLA_PRESUME_SSE4_1) +# define MOZILLA_MAY_SUPPORT_SSE4_1 1 +inline bool supports_sse4_1() { return true; } +#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) +# define MOZILLA_MAY_SUPPORT_SSE4_1 1 +inline bool supports_sse4_1() { return sse_private::sse4_1_enabled; } +#else +inline bool supports_sse4_1() { return false; } +#endif + +#if defined(MOZILLA_PRESUME_SSE4_2) +# define MOZILLA_MAY_SUPPORT_SSE4_2 1 +inline bool supports_sse4_2() { return true; } +#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) +# define MOZILLA_MAY_SUPPORT_SSE4_2 1 +inline bool supports_sse4_2() { return sse_private::sse4_2_enabled; } +#else +inline bool supports_sse4_2() { return false; } +#endif + +#if defined(MOZILLA_PRESUME_FMA3) +# define MOZILLA_MAY_SUPPORT_FMA3 1 +inline bool supports_fma3() { return true; } +#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) +# define MOZILLA_MAY_SUPPORT_FMA3 1 +inline bool supports_fma3() { return sse_private::fma3_enabled; } +#else +inline bool supports_fma3() { return false; } +#endif + +#if defined(MOZILLA_PRESUME_AVX) +# define MOZILLA_MAY_SUPPORT_AVX 1 +inline bool supports_avx() { return true; } +#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) +# define MOZILLA_MAY_SUPPORT_AVX 1 +inline bool supports_avx() { return sse_private::avx_enabled; } +#else +inline bool supports_avx() { return false; } +#endif + +#if defined(MOZILLA_PRESUME_AVX2) +# define MOZILLA_MAY_SUPPORT_AVX2 1 +inline bool supports_avx2() { return true; } +#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) +# define MOZILLA_MAY_SUPPORT_AVX2 1 +inline bool supports_avx2() { return sse_private::avx2_enabled; } +#else +inline bool supports_avx2() { return false; } +#endif + +#if defined(MOZILLA_PRESUME_AVXVNNI) +# define MOZILLA_MAY_SUPPORT_AVXVNNI 1 +inline bool supports_avxvnni() { return true; } +#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) +# define MOZILLA_MAY_SUPPORT_AVXVNNI 1 +inline bool supports_avxvnni() { return sse_private::avxvnni_enabled; } +#else +inline bool supports_avxvnni() { return false; } +#endif + +#if defined(MOZILLA_PRESUME_AES) +# define MOZILLA_MAY_SUPPORT_AES 1 +inline bool supports_aes() { return true; } +#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) +# define MOZILLA_MAY_SUPPORT_AES 1 +inline bool supports_aes() { return sse_private::aes_enabled; } +#else +inline bool supports_aes() { return false; } +#endif + +#ifdef MOZILLA_SSE_HAVE_CPUID_DETECTION +inline bool has_constant_tsc() { return sse_private::has_constant_tsc; } +#else +inline bool has_constant_tsc() { return false; } +#endif + +} // namespace mozilla + +#endif /* !defined(mozilla_SSE_h_) */ diff --git a/mozglue/misc/Sprintf.h b/mozglue/misc/Sprintf.h new file mode 100644 index 0000000000..4b459de82d --- /dev/null +++ b/mozglue/misc/Sprintf.h @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Provides a safer sprintf for printing to fixed-size character arrays. */ + +#ifndef mozilla_Sprintf_h_ +#define mozilla_Sprintf_h_ + +#include <stdio.h> +#include <stdarg.h> +#include <algorithm> + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/Printf.h" + +#ifdef __cplusplus + +# ifndef SPRINTF_H_USES_VSNPRINTF +namespace mozilla { +namespace detail { + +struct MOZ_STACK_CLASS SprintfAppend final : public mozilla::PrintfTarget { + explicit SprintfAppend(char* aBuf, size_t aBufLen) + : mBuf(aBuf), mBufLen(aBufLen) {} + + bool append(const char* aStr, size_t aLen) override { + if (aLen == 0) { + return true; + } + // Don't copy more than what's left to use. + size_t copy = std::min(mBufLen, aLen); + if (copy > 0) { + memcpy(mBuf, aStr, copy); + mBuf += copy; + mBufLen -= copy; + } + return true; + } + + private: + char* mBuf; + size_t mBufLen; +}; + +} // namespace detail +} // namespace mozilla +# endif // SPRINTF_H_USES_VSNPRINTF + +MOZ_FORMAT_PRINTF(3, 0) +MOZ_MAYBE_UNUSED +static int VsprintfBuf(char* buffer, size_t bufsize, const char* format, + va_list args) { + MOZ_ASSERT(format != buffer); +# ifdef SPRINTF_H_USES_VSNPRINTF + int result = vsnprintf(buffer, bufsize, format, args); + buffer[bufsize - 1] = '\0'; + return result; +# else + mozilla::detail::SprintfAppend ss(buffer, bufsize); + ss.vprint(format, args); + size_t len = ss.emitted(); + buffer[std::min(len, bufsize - 1)] = '\0'; + return len; +# endif +} + +MOZ_FORMAT_PRINTF(3, 4) +MOZ_MAYBE_UNUSED +static int SprintfBuf(char* buffer, size_t bufsize, const char* format, ...) { + va_list args; + va_start(args, format); + int result = VsprintfBuf(buffer, bufsize, format, args); + va_end(args); + return result; +} + +template <size_t N> +MOZ_FORMAT_PRINTF(2, 0) +int VsprintfLiteral(char (&buffer)[N], const char* format, va_list args) { + return VsprintfBuf(buffer, N, format, args); +} + +template <size_t N> +MOZ_FORMAT_PRINTF(2, 3) +int SprintfLiteral(char (&buffer)[N], const char* format, ...) { + va_list args; + va_start(args, format); + int result = VsprintfLiteral(buffer, format, args); + va_end(args); + return result; +} + +#endif +#endif /* mozilla_Sprintf_h_ */ diff --git a/mozglue/misc/StackWalk.cpp b/mozglue/misc/StackWalk.cpp new file mode 100644 index 0000000000..2fefc5bf4d --- /dev/null +++ b/mozglue/misc/StackWalk.cpp @@ -0,0 +1,1129 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* API for getting a stack trace of the C/C++ stack on the current thread */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/StackWalk.h" +#ifdef XP_WIN +# include "mozilla/StackWalkThread.h" +# include <io.h> +#else +# include <unistd.h> +#endif +#include "mozilla/Sprintf.h" + +#include <string.h> + +#if defined(ANDROID) && defined(MOZ_LINKER) +# include "Linker.h" +# include <android/log.h> +#endif + +using namespace mozilla; + +// for _Unwind_Backtrace from libcxxrt or libunwind +// cxxabi.h from libcxxrt implicitly includes unwind.h first +#if defined(HAVE__UNWIND_BACKTRACE) && !defined(_GNU_SOURCE) +# define _GNU_SOURCE +#endif + +#if defined(HAVE_DLOPEN) || defined(XP_DARWIN) +# include <dlfcn.h> +#endif + +#if (defined(XP_DARWIN) && \ + (defined(__i386) || defined(__ppc__) || defined(HAVE__UNWIND_BACKTRACE))) +# define MOZ_STACKWALK_SUPPORTS_MACOSX 1 +#else +# define MOZ_STACKWALK_SUPPORTS_MACOSX 0 +#endif + +#if (defined(linux) && \ + ((defined(__GNUC__) && (defined(__i386) || defined(PPC))) || \ + defined(HAVE__UNWIND_BACKTRACE))) +# define MOZ_STACKWALK_SUPPORTS_LINUX 1 +#else +# define MOZ_STACKWALK_SUPPORTS_LINUX 0 +#endif + +#if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 1) +# define HAVE___LIBC_STACK_END 1 +#else +# define HAVE___LIBC_STACK_END 0 +#endif + +#if HAVE___LIBC_STACK_END +extern MOZ_EXPORT void* __libc_stack_end; // from ld-linux.so +#endif + +#ifdef ANDROID +# include <algorithm> +# include <unistd.h> +# include <pthread.h> +#endif + +class FrameSkipper { + public: + constexpr FrameSkipper() : mSkipUntilAddr(0) {} + static uintptr_t AddressFromPC(const void* aPC) { +#ifdef __arm__ + // On 32-bit ARM, mask off the thumb bit to get the instruction address. + return uintptr_t(aPC) & ~1; +#else + return uintptr_t(aPC); +#endif + } + bool ShouldSkipPC(void* aPC) { + // Skip frames until we encounter the one we were initialized with, + // and then never skip again. + uintptr_t instructionAddress = AddressFromPC(aPC); + if (mSkipUntilAddr != 0) { + if (mSkipUntilAddr != instructionAddress) { + return true; + } + mSkipUntilAddr = 0; + } + return false; + } + explicit FrameSkipper(const void* aPC) : mSkipUntilAddr(AddressFromPC(aPC)) {} + + private: + uintptr_t mSkipUntilAddr; +}; + +#ifdef XP_WIN + +# include <windows.h> +# include <process.h> +# include <stdio.h> +# include <malloc.h> +# include "mozilla/ArrayUtils.h" +# include "mozilla/Atomics.h" +# include "mozilla/StackWalk_windows.h" +# include "mozilla/WindowsVersion.h" + +# include <imagehlp.h> +// We need a way to know if we are building for WXP (or later), as if we are, we +// need to use the newer 64-bit APIs. API_VERSION_NUMBER seems to fit the bill. +// A value of 9 indicates we want to use the new APIs. +# if API_VERSION_NUMBER < 9 +# error Too old imagehlp.h +# endif + +// DbgHelp functions are not thread-safe and should therefore be protected by +// using this critical section. Only use the critical section after a +// successful call to InitializeDbgHelp(). +CRITICAL_SECTION gDbgHelpCS; + +# if defined(_M_AMD64) || defined(_M_ARM64) +// Because various Win64 APIs acquire function-table locks, we need a way of +// preventing stack walking while those APIs are being called. Otherwise, the +// stack walker may suspend a thread holding such a lock, and deadlock when the +// stack unwind code attempts to wait for that lock. +// +// We're using an atomic counter rather than a critical section because we +// don't require mutual exclusion with the stack walker. If the stack walker +// determines that it's safe to start unwinding the suspended thread (i.e. +// there are no suppressions when the unwind begins), then it's safe to +// continue unwinding that thread even if other threads request suppressions +// in the meantime, because we can't deadlock with those other threads. +// +// XXX: This global variable is a larger-than-necessary hammer. A more scoped +// solution would be to maintain a counter per thread, but then it would be +// more difficult for WalkStackMain64 to read the suspended thread's counter. +static Atomic<size_t> sStackWalkSuppressions; + +void SuppressStackWalking() { ++sStackWalkSuppressions; } + +void DesuppressStackWalking() { + auto previousValue = sStackWalkSuppressions--; + // We should never desuppress from 0. See bug 1687510 comment 10 for an + // example in which this occured. + MOZ_RELEASE_ASSERT(previousValue); +} + +MFBT_API +AutoSuppressStackWalking::AutoSuppressStackWalking() { SuppressStackWalking(); } + +MFBT_API +AutoSuppressStackWalking::~AutoSuppressStackWalking() { + DesuppressStackWalking(); +} + +static uint8_t* sJitCodeRegionStart; +static size_t sJitCodeRegionSize; +uint8_t* sMsMpegJitCodeRegionStart; +size_t sMsMpegJitCodeRegionSize; + +MFBT_API void RegisterJitCodeRegion(uint8_t* aStart, size_t aSize) { + // Currently we can only handle one JIT code region at a time + MOZ_RELEASE_ASSERT(!sJitCodeRegionStart); + + sJitCodeRegionStart = aStart; + sJitCodeRegionSize = aSize; +} + +MFBT_API void UnregisterJitCodeRegion(uint8_t* aStart, size_t aSize) { + // Currently we can only handle one JIT code region at a time + MOZ_RELEASE_ASSERT(sJitCodeRegionStart && sJitCodeRegionStart == aStart && + sJitCodeRegionSize == aSize); + + sJitCodeRegionStart = nullptr; + sJitCodeRegionSize = 0; +} + +# endif // _M_AMD64 || _M_ARM64 + +// Routine to print an error message to standard error. +static void PrintError(const char* aPrefix) { + LPSTR lpMsgBuf; + DWORD lastErr = GetLastError(); + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, lastErr, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPSTR)&lpMsgBuf, 0, nullptr); + fprintf(stderr, "### ERROR: %s: %s", aPrefix, + lpMsgBuf ? lpMsgBuf : "(null)\n"); + fflush(stderr); + LocalFree(lpMsgBuf); +} + +enum class DbgHelpInitFlags : bool { + BasicInit, + WithSymbolSupport, +}; + +// This function ensures that DbgHelp.dll is loaded in the current process, +// and initializes the gDbgHelpCS critical section that we use to protect calls +// to DbgHelp functions. If DbgHelpInitFlags::WithSymbolSupport is set, we +// additionally call the symbol initialization functions from DbgHelp so that +// symbol-related functions can be used. +// +// This function is thread-safe and reentrancy-safe. In debug and fuzzing +// builds, MOZ_ASSERT and MOZ_CRASH walk the stack to print it before actually +// crashing. Hence *any* MOZ_ASSERT or MOZ_CRASH failure reached from +// InitializeDbgHelp() leads to rentrancy (see bug 1869997 for an example). +// Such failures can occur indirectly when we load dbghelp.dll, because we +// override various Microsoft-internal functions that are called upon DLL +// loading. +[[nodiscard]] static bool InitializeDbgHelp( + DbgHelpInitFlags aInitFlags = DbgHelpInitFlags::BasicInit) { + // In the code below, it is only safe to reach MOZ_ASSERT or MOZ_CRASH while + // sInitializationThreadId is set to the current thread id. + static Atomic<DWORD> sInitializationThreadId{0}; + DWORD currentThreadId = ::GetCurrentThreadId(); + + // This code relies on Windows never giving us a current thread ID of zero. + // We make this assumption explicit, by failing if that should ever occur. + if (!currentThreadId) { + return false; + } + + if (sInitializationThreadId == currentThreadId) { + // This is a reentrant call and we must abort here. + return false; + } + + static const bool sHasInitializedDbgHelp = [currentThreadId]() { + sInitializationThreadId = currentThreadId; + + ::InitializeCriticalSection(&gDbgHelpCS); + bool dbgHelpLoaded = static_cast<bool>(::LoadLibraryW(L"dbghelp.dll")); + + MOZ_ASSERT(dbgHelpLoaded); + sInitializationThreadId = 0; + return dbgHelpLoaded; + }(); + + // If we don't need symbol initialization, we are done. If we need it, we + // can only proceed if DbgHelp initialization was successful. + if (aInitFlags == DbgHelpInitFlags::BasicInit || !sHasInitializedDbgHelp) { + return sHasInitializedDbgHelp; + } + + static const bool sHasInitializedSymbols = [currentThreadId]() { + sInitializationThreadId = currentThreadId; + + EnterCriticalSection(&gDbgHelpCS); + SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); + bool symbolsInitialized = SymInitialize(GetCurrentProcess(), nullptr, TRUE); + /* XXX At some point we need to arrange to call SymCleanup */ + LeaveCriticalSection(&gDbgHelpCS); + + if (!symbolsInitialized) { + PrintError("SymInitialize"); + } + + MOZ_ASSERT(symbolsInitialized); + sInitializationThreadId = 0; + return symbolsInitialized; + }(); + + return sHasInitializedSymbols; +} + +// Wrapper around a reference to a CONTEXT, to simplify access to main +// platform-specific execution registers. +// It also avoids using CONTEXT* nullable pointers. +class CONTEXTGenericAccessors { + public: + explicit CONTEXTGenericAccessors(CONTEXT& aCONTEXT) : mCONTEXT(aCONTEXT) {} + + CONTEXT* CONTEXTPtr() { return &mCONTEXT; } + + inline auto& PC() { +# if defined(_M_AMD64) + return mCONTEXT.Rip; +# elif defined(_M_ARM64) + return mCONTEXT.Pc; +# elif defined(_M_IX86) + return mCONTEXT.Eip; +# else +# error "unknown platform" +# endif + } + + inline auto& SP() { +# if defined(_M_AMD64) + return mCONTEXT.Rsp; +# elif defined(_M_ARM64) + return mCONTEXT.Sp; +# elif defined(_M_IX86) + return mCONTEXT.Esp; +# else +# error "unknown platform" +# endif + } + + inline auto& BP() { +# if defined(_M_AMD64) + return mCONTEXT.Rbp; +# elif defined(_M_ARM64) + return mCONTEXT.Fp; +# elif defined(_M_IX86) + return mCONTEXT.Ebp; +# else +# error "unknown platform" +# endif + } + + private: + CONTEXT& mCONTEXT; +}; + +/** + * Walk the stack, translating PC's found into strings and recording the + * chain in aBuffer. For this to work properly, the DLLs must be rebased + * so that the address in the file agrees with the address in memory. + * Otherwise StackWalk will return FALSE when it hits a frame in a DLL + * whose in memory address doesn't match its in-file address. + */ + +static void DoMozStackWalkThread(MozWalkStackCallback aCallback, + const void* aFirstFramePC, uint32_t aMaxFrames, + void* aClosure, HANDLE aThread, + CONTEXT* aContext) { +# if defined(_M_IX86) + if (!InitializeDbgHelp()) { + return; + } +# endif + + HANDLE targetThread = aThread; + bool walkCallingThread; + if (!targetThread) { + targetThread = ::GetCurrentThread(); + walkCallingThread = true; + } else { + DWORD targetThreadId = ::GetThreadId(targetThread); + DWORD currentThreadId = ::GetCurrentThreadId(); + walkCallingThread = (targetThreadId == currentThreadId); + } + + // If not already provided, get a context for the specified thread. + CONTEXT context_buf; + if (!aContext) { + memset(&context_buf, 0, sizeof(CONTEXT)); + context_buf.ContextFlags = CONTEXT_FULL; + if (walkCallingThread) { + ::RtlCaptureContext(&context_buf); + } else if (!GetThreadContext(targetThread, &context_buf)) { + return; + } + } + CONTEXTGenericAccessors context{aContext ? *aContext : context_buf}; + +# if defined(_M_IX86) + // Setup initial stack frame to walk from. + STACKFRAME64 frame64; + memset(&frame64, 0, sizeof(frame64)); + frame64.AddrPC.Offset = context.PC(); + frame64.AddrStack.Offset = context.SP(); + frame64.AddrFrame.Offset = context.BP(); + frame64.AddrPC.Mode = AddrModeFlat; + frame64.AddrStack.Mode = AddrModeFlat; + frame64.AddrFrame.Mode = AddrModeFlat; + frame64.AddrReturn.Mode = AddrModeFlat; +# endif + +# if defined(_M_AMD64) || defined(_M_ARM64) + // If there are any active suppressions, then at least one thread (we don't + // know which) is holding a lock that can deadlock RtlVirtualUnwind. Since + // that thread may be the one that we're trying to unwind, we can't proceed. + // + // But if there are no suppressions, then our target thread can't be holding + // a lock, and it's safe to proceed. By virtue of being suspended, the target + // thread can't acquire any new locks during the unwind process, so we only + // need to do this check once. After that, sStackWalkSuppressions can be + // changed by other threads while we're unwinding, and that's fine because + // we can't deadlock with those threads. + if (sStackWalkSuppressions) { + return; + } + + bool firstFrame = true; +# endif + + FrameSkipper skipper(aFirstFramePC); + + uint32_t frames = 0; + + // Now walk the stack. + while (true) { + DWORD64 addr; + DWORD64 spaddr; + +# if defined(_M_IX86) + // 32-bit frame unwinding. + // Debug routines are not threadsafe, so grab the lock. + EnterCriticalSection(&gDbgHelpCS); + BOOL ok = + StackWalk64(IMAGE_FILE_MACHINE_I386, ::GetCurrentProcess(), + targetThread, &frame64, context.CONTEXTPtr(), nullptr, + SymFunctionTableAccess64, // function table access routine + SymGetModuleBase64, // module base routine + 0); + LeaveCriticalSection(&gDbgHelpCS); + + if (ok) { + addr = frame64.AddrPC.Offset; + spaddr = frame64.AddrStack.Offset; + } else { + addr = 0; + spaddr = 0; + if (walkCallingThread) { + PrintError("WalkStack64"); + } + } + + if (!ok) { + break; + } + +# elif defined(_M_AMD64) || defined(_M_ARM64) + + auto currentInstr = context.PC(); + + // If we reach a frame in JIT code, we don't have enough information to + // unwind, so we have to give up. + if (sJitCodeRegionStart && (uint8_t*)currentInstr >= sJitCodeRegionStart && + (uint8_t*)currentInstr < sJitCodeRegionStart + sJitCodeRegionSize) { + break; + } + + // We must also avoid msmpeg2vdec.dll's JIT region: they don't generate + // unwind data, so their JIT unwind callback just throws up its hands and + // terminates the process. + if (sMsMpegJitCodeRegionStart && + (uint8_t*)currentInstr >= sMsMpegJitCodeRegionStart && + (uint8_t*)currentInstr < + sMsMpegJitCodeRegionStart + sMsMpegJitCodeRegionSize) { + break; + } + + // 64-bit frame unwinding. + // Try to look up unwind metadata for the current function. + ULONG64 imageBase; + PRUNTIME_FUNCTION runtimeFunction = + RtlLookupFunctionEntry(currentInstr, &imageBase, NULL); + + if (runtimeFunction) { + PVOID dummyHandlerData; + ULONG64 dummyEstablisherFrame; + RtlVirtualUnwind(UNW_FLAG_NHANDLER, imageBase, currentInstr, + runtimeFunction, context.CONTEXTPtr(), &dummyHandlerData, + &dummyEstablisherFrame, nullptr); + } else if (firstFrame) { + // Leaf functions can be unwound by hand. + context.PC() = *reinterpret_cast<DWORD64*>(context.SP()); + context.SP() += sizeof(void*); + } else { + // Something went wrong. + break; + } + + addr = context.PC(); + spaddr = context.SP(); + firstFrame = false; +# else +# error "unknown platform" +# endif + + if (addr == 0) { + break; + } + + if (skipper.ShouldSkipPC((void*)addr)) { + continue; + } + + aCallback(++frames, (void*)addr, (void*)spaddr, aClosure); + + if (aMaxFrames != 0 && frames == aMaxFrames) { + break; + } + +# if defined(_M_IX86) + if (frame64.AddrReturn.Offset == 0) { + break; + } +# endif + } +} + +MFBT_API void MozStackWalkThread(MozWalkStackCallback aCallback, + uint32_t aMaxFrames, void* aClosure, + HANDLE aThread, CONTEXT* aContext) { + // We don't pass a aFirstFramePC because we walk the stack for another + // thread. + DoMozStackWalkThread(aCallback, nullptr, aMaxFrames, aClosure, aThread, + aContext); +} + +MFBT_API void MozStackWalk(MozWalkStackCallback aCallback, + const void* aFirstFramePC, uint32_t aMaxFrames, + void* aClosure) { + DoMozStackWalkThread(aCallback, aFirstFramePC ? aFirstFramePC : CallerPC(), + aMaxFrames, aClosure, nullptr, nullptr); +} + +static BOOL CALLBACK callbackEspecial64(PCSTR aModuleName, DWORD64 aModuleBase, + ULONG aModuleSize, PVOID aUserContext) { + BOOL retval = TRUE; + DWORD64 addr = *(DWORD64*)aUserContext; + + /* + * You'll want to control this if we are running on an + * architecture where the addresses go the other direction. + * Not sure this is even a realistic consideration. + */ + const BOOL addressIncreases = TRUE; + + /* + * If it falls in side the known range, load the symbols. + */ + if (addressIncreases + ? (addr >= aModuleBase && addr <= (aModuleBase + aModuleSize)) + : (addr <= aModuleBase && addr >= (aModuleBase - aModuleSize))) { + retval = !!SymLoadModule64(GetCurrentProcess(), nullptr, (PSTR)aModuleName, + nullptr, aModuleBase, aModuleSize); + if (!retval) { + PrintError("SymLoadModule64"); + } + } + + return retval; +} + +/* + * SymGetModuleInfoEspecial + * + * Attempt to determine the module information. + * Bug 112196 says this DLL may not have been loaded at the time + * SymInitialize was called, and thus the module information + * and symbol information is not available. + * This code rectifies that problem. + */ + +// New members were added to IMAGEHLP_MODULE64 (that show up in the +// Platform SDK that ships with VC8, but not the Platform SDK that ships +// with VC7.1, i.e., between DbgHelp 6.0 and 6.1), but we don't need to +// use them, and it's useful to be able to function correctly with the +// older library. (Stock Windows XP SP2 seems to ship with dbghelp.dll +// version 5.1.) Since Platform SDK version need not correspond to +// compiler version, and the version number in debughlp.h was NOT bumped +// when these changes were made, ifdef based on a constant that was +// added between these versions. +# ifdef SSRVOPT_SETCONTEXT +# define NS_IMAGEHLP_MODULE64_SIZE \ + (((offsetof(IMAGEHLP_MODULE64, LoadedPdbName) + sizeof(DWORD64) - 1) / \ + sizeof(DWORD64)) * \ + sizeof(DWORD64)) +# else +# define NS_IMAGEHLP_MODULE64_SIZE sizeof(IMAGEHLP_MODULE64) +# endif + +BOOL SymGetModuleInfoEspecial64(HANDLE aProcess, DWORD64 aAddr, + PIMAGEHLP_MODULE64 aModuleInfo, + PIMAGEHLP_LINE64 aLineInfo) { + BOOL retval = FALSE; + + /* + * Init the vars if we have em. + */ + aModuleInfo->SizeOfStruct = NS_IMAGEHLP_MODULE64_SIZE; + if (aLineInfo) { + aLineInfo->SizeOfStruct = sizeof(IMAGEHLP_LINE64); + } + + /* + * Give it a go. + * It may already be loaded. + */ + retval = SymGetModuleInfo64(aProcess, aAddr, aModuleInfo); + if (retval == FALSE) { + /* + * Not loaded, here's the magic. + * Go through all the modules. + */ + // Need to cast to PENUMLOADED_MODULES_CALLBACK64 because the + // constness of the first parameter of + // PENUMLOADED_MODULES_CALLBACK64 varies over SDK versions (from + // non-const to const over time). See bug 391848 and bug + // 415426. + BOOL enumRes = EnumerateLoadedModules64( + aProcess, (PENUMLOADED_MODULES_CALLBACK64)callbackEspecial64, + (PVOID)&aAddr); + if (enumRes != FALSE) { + /* + * One final go. + * If it fails, then well, we have other problems. + */ + retval = SymGetModuleInfo64(aProcess, aAddr, aModuleInfo); + } + } + + /* + * If we got module info, we may attempt line info as well. + * We will not report failure if this does not work. + */ + if (retval != FALSE && aLineInfo) { + DWORD displacement = 0; + BOOL lineRes = FALSE; + lineRes = SymGetLineFromAddr64(aProcess, aAddr, &displacement, aLineInfo); + if (!lineRes) { + // Clear out aLineInfo to indicate that it's not valid + memset(aLineInfo, 0, sizeof(*aLineInfo)); + } + } + + return retval; +} + +MFBT_API bool MozDescribeCodeAddress(void* aPC, + MozCodeAddressDetails* aDetails) { + aDetails->library[0] = '\0'; + aDetails->loffset = 0; + aDetails->filename[0] = '\0'; + aDetails->lineno = 0; + aDetails->function[0] = '\0'; + aDetails->foffset = 0; + + if (!InitializeDbgHelp(DbgHelpInitFlags::WithSymbolSupport)) { + return false; + } + + HANDLE myProcess = ::GetCurrentProcess(); + BOOL ok; + + // debug routines are not threadsafe, so grab the lock. + EnterCriticalSection(&gDbgHelpCS); + + // + // Attempt to load module info before we attempt to resolve the symbol. + // This just makes sure we get good info if available. + // + + DWORD64 addr = (DWORD64)aPC; + IMAGEHLP_MODULE64 modInfo; + IMAGEHLP_LINE64 lineInfo; + BOOL modInfoRes; + modInfoRes = SymGetModuleInfoEspecial64(myProcess, addr, &modInfo, &lineInfo); + + if (modInfoRes) { + strncpy(aDetails->library, modInfo.LoadedImageName, + sizeof(aDetails->library)); + aDetails->library[mozilla::ArrayLength(aDetails->library) - 1] = '\0'; + aDetails->loffset = (char*)aPC - (char*)modInfo.BaseOfImage; + + if (lineInfo.FileName) { + strncpy(aDetails->filename, lineInfo.FileName, + sizeof(aDetails->filename)); + aDetails->filename[mozilla::ArrayLength(aDetails->filename) - 1] = '\0'; + aDetails->lineno = lineInfo.LineNumber; + } + } + + ULONG64 buffer[(sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR) + + sizeof(ULONG64) - 1) / + sizeof(ULONG64)]; + PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer; + pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); + pSymbol->MaxNameLen = MAX_SYM_NAME; + + DWORD64 displacement; + ok = SymFromAddr(myProcess, addr, &displacement, pSymbol); + + if (ok) { + strncpy(aDetails->function, pSymbol->Name, sizeof(aDetails->function)); + aDetails->function[mozilla::ArrayLength(aDetails->function) - 1] = '\0'; + aDetails->foffset = static_cast<ptrdiff_t>(displacement); + } + + LeaveCriticalSection(&gDbgHelpCS); // release our lock + return true; +} + +// i386 or PPC Linux stackwalking code +// +// Changes to to OS/Architecture support here should be reflected in +// build/moz.configure/memory.configure +#elif HAVE_DLADDR && \ + (HAVE__UNWIND_BACKTRACE || MOZ_STACKWALK_SUPPORTS_LINUX || \ + MOZ_STACKWALK_SUPPORTS_MACOSX) + +# include <stdlib.h> +# include <stdio.h> + +// On glibc 2.1, the Dl_info api defined in <dlfcn.h> is only exposed +// if __USE_GNU is defined. I suppose its some kind of standards +// adherence thing. +// +# if (__GLIBC_MINOR__ >= 1) && !defined(__USE_GNU) +# define __USE_GNU +# endif + +// This thing is exported by libstdc++ +// Yes, this is a gcc only hack +# if defined(MOZ_DEMANGLE_SYMBOLS) +# include <cxxabi.h> +# endif // MOZ_DEMANGLE_SYMBOLS + +namespace mozilla { + +void DemangleSymbol(const char* aSymbol, char* aBuffer, int aBufLen) { + aBuffer[0] = '\0'; + +# if defined(MOZ_DEMANGLE_SYMBOLS) + /* See demangle.h in the gcc source for the voodoo */ + char* demangled = abi::__cxa_demangle(aSymbol, 0, 0, 0); + + if (demangled) { + strncpy(aBuffer, demangled, aBufLen); + aBuffer[aBufLen - 1] = '\0'; + free(demangled); + } +# endif // MOZ_DEMANGLE_SYMBOLS +} + +} // namespace mozilla + +// {x86, ppc} x {Linux, Mac} stackwalking code. +// +// Changes to to OS/Architecture support here should be reflected in +// build/moz.configure/memory.configure +# if ((defined(__i386) || defined(PPC) || defined(__ppc__)) && \ + (MOZ_STACKWALK_SUPPORTS_MACOSX || MOZ_STACKWALK_SUPPORTS_LINUX)) + +static void DoFramePointerStackWalk(MozWalkStackCallback aCallback, + const void* aFirstFramePC, + uint32_t aMaxFrames, void* aClosure, + void** aBp, void* aStackEnd); + +MFBT_API void MozStackWalk(MozWalkStackCallback aCallback, + const void* aFirstFramePC, uint32_t aMaxFrames, + void* aClosure) { + // Get the frame pointer + void** bp = (void**)__builtin_frame_address(0); + + void* stackEnd; +# if HAVE___LIBC_STACK_END + stackEnd = __libc_stack_end; +# elif defined(XP_DARWIN) + stackEnd = pthread_get_stackaddr_np(pthread_self()); +# elif defined(ANDROID) + pthread_attr_t sattr; + pthread_attr_init(&sattr); + pthread_getattr_np(pthread_self(), &sattr); + void* stackBase = stackEnd = nullptr; + size_t stackSize = 0; + if (gettid() != getpid()) { + // bionic's pthread_attr_getstack doesn't tell the truth for the main + // thread (see bug 846670). So don't use it for the main thread. + if (!pthread_attr_getstack(&sattr, &stackBase, &stackSize)) { + stackEnd = static_cast<char*>(stackBase) + stackSize; + } else { + stackEnd = nullptr; + } + } + if (!stackEnd) { + // So consider the current frame pointer + an arbitrary size of 8MB + // (modulo overflow ; not really arbitrary as it's the default stack + // size for the main thread) if pthread_attr_getstack failed for + // some reason (or was skipped). + static const uintptr_t kMaxStackSize = 8 * 1024 * 1024; + uintptr_t maxStackStart = uintptr_t(-1) - kMaxStackSize; + uintptr_t stackStart = std::max(maxStackStart, uintptr_t(bp)); + stackEnd = reinterpret_cast<void*>(stackStart + kMaxStackSize); + } +# else +# error Unsupported configuration +# endif + DoFramePointerStackWalk(aCallback, aFirstFramePC, aMaxFrames, aClosure, bp, + stackEnd); +} + +# elif defined(HAVE__UNWIND_BACKTRACE) + +// libgcc_s.so symbols _Unwind_Backtrace@@GCC_3.3 and _Unwind_GetIP@@GCC_3.0 +# include <unwind.h> + +struct unwind_info { + MozWalkStackCallback callback; + FrameSkipper skipper; + int maxFrames; + int numFrames; + void* closure; +}; + +static _Unwind_Reason_Code unwind_callback(struct _Unwind_Context* context, + void* closure) { + unwind_info* info = static_cast<unwind_info*>(closure); + void* pc = reinterpret_cast<void*>(_Unwind_GetIP(context)); + // TODO Use something like '_Unwind_GetGR()' to get the stack pointer. + if (!info->skipper.ShouldSkipPC(pc)) { + info->numFrames++; + (*info->callback)(info->numFrames, pc, nullptr, info->closure); + if (info->maxFrames != 0 && info->numFrames == info->maxFrames) { + // Again, any error code that stops the walk will do. + return _URC_FOREIGN_EXCEPTION_CAUGHT; + } + } + return _URC_NO_REASON; +} + +MFBT_API void MozStackWalk(MozWalkStackCallback aCallback, + const void* aFirstFramePC, uint32_t aMaxFrames, + void* aClosure) { + unwind_info info; + info.callback = aCallback; + info.skipper = FrameSkipper(aFirstFramePC ? aFirstFramePC : CallerPC()); + info.maxFrames = aMaxFrames; + info.numFrames = 0; + info.closure = aClosure; + + // We ignore the return value from _Unwind_Backtrace. There are three main + // reasons for this. + // - On ARM/Android bionic's _Unwind_Backtrace usually (always?) returns + // _URC_FAILURE. See + // https://bugzilla.mozilla.org/show_bug.cgi?id=717853#c110. + // - If aMaxFrames != 0, we want to stop early, and the only way to do that + // is to make unwind_callback return something other than _URC_NO_REASON, + // which causes _Unwind_Backtrace to return a non-success code. + // - MozStackWalk doesn't have a return value anyway. + (void)_Unwind_Backtrace(unwind_callback, &info); +} + +# endif + +bool MFBT_API MozDescribeCodeAddress(void* aPC, + MozCodeAddressDetails* aDetails) { + aDetails->library[0] = '\0'; + aDetails->loffset = 0; + aDetails->filename[0] = '\0'; + aDetails->lineno = 0; + aDetails->function[0] = '\0'; + aDetails->foffset = 0; + + Dl_info info; + +# if defined(ANDROID) && defined(MOZ_LINKER) + int ok = __wrap_dladdr(aPC, &info); +# else + int ok = dladdr(aPC, &info); +# endif + + if (!ok) { + return true; + } + + strncpy(aDetails->library, info.dli_fname, sizeof(aDetails->library)); + aDetails->library[mozilla::ArrayLength(aDetails->library) - 1] = '\0'; + aDetails->loffset = (char*)aPC - (char*)info.dli_fbase; + +# if !defined(XP_FREEBSD) + // On FreeBSD, dli_sname is unusably bad, it often returns things like + // 'gtk_xtbin_new' or 'XRE_GetBootstrap' instead of long C++ symbols. Just let + // GetFunction do the lookup directly in the ELF image. + + const char* symbol = info.dli_sname; + if (!symbol || symbol[0] == '\0') { + return true; + } + + DemangleSymbol(symbol, aDetails->function, sizeof(aDetails->function)); + + if (aDetails->function[0] == '\0') { + // Just use the mangled symbol if demangling failed. + strncpy(aDetails->function, symbol, sizeof(aDetails->function)); + aDetails->function[mozilla::ArrayLength(aDetails->function) - 1] = '\0'; + } + + aDetails->foffset = (char*)aPC - (char*)info.dli_saddr; +# endif + + return true; +} + +#else // unsupported platform. + +MFBT_API void MozStackWalk(MozWalkStackCallback aCallback, + const void* aFirstFramePC, uint32_t aMaxFrames, + void* aClosure) {} + +MFBT_API bool MozDescribeCodeAddress(void* aPC, + MozCodeAddressDetails* aDetails) { + aDetails->library[0] = '\0'; + aDetails->loffset = 0; + aDetails->filename[0] = '\0'; + aDetails->lineno = 0; + aDetails->function[0] = '\0'; + aDetails->foffset = 0; + return false; +} + +#endif + +#if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_LINUX) + +# if defined(XP_MACOSX) && defined(__aarch64__) +// On macOS arm64, system libraries are arm64e binaries, and arm64e can do +// pointer authentication: The low bits of the pointer are the actual pointer +// value, and the high bits are an encrypted hash. During stackwalking, we need +// to strip off this hash. In theory, ptrauth_strip would be the right function +// to call for this. However, that function is a no-op unless it's called from +// code which also builds as arm64e - which we do not. So we cannot use it. So +// for now, we hardcode a mask that seems to work today: 40 bits for the pointer +// and 24 bits for the hash seems to do the trick. We can worry about +// dynamically computing the correct mask if this ever stops working. +const uintptr_t kPointerMask = + (uintptr_t(1) << 40) - 1; // 40 bits pointer, 24 bit PAC +# else +const uintptr_t kPointerMask = ~uintptr_t(0); +# endif + +MOZ_ASAN_IGNORE +static void DoFramePointerStackWalk(MozWalkStackCallback aCallback, + const void* aFirstFramePC, + uint32_t aMaxFrames, void* aClosure, + void** aBp, void* aStackEnd) { + // Stack walking code courtesy Kipp's "leaky". + + FrameSkipper skipper(aFirstFramePC); + uint32_t numFrames = 0; + + // Sanitize the given aBp. Assume that something reasonably close to + // but before the stack end is going be a valid frame pointer. Also + // check that it is an aligned address. This increases the chances + // that if the pointer is not valid (which might happen if the caller + // called __builtin_frame_address(1) and its frame is busted for some + // reason), we won't read it, leading to a crash. Because the calling + // code is not using frame pointers when returning, it might actually + // recover just fine. + static const uintptr_t kMaxStackSize = 8 * 1024 * 1024; + if (uintptr_t(aBp) < uintptr_t(aStackEnd) - + std::min(kMaxStackSize, uintptr_t(aStackEnd)) || + aBp >= aStackEnd || (uintptr_t(aBp) & 3)) { + return; + } + + while (aBp) { + void** next = (void**)*aBp; + // aBp may not be a frame pointer on i386 if code was compiled with + // -fomit-frame-pointer, so do some sanity checks. + // (aBp should be a frame pointer on ppc(64) but checking anyway may help + // a little if the stack has been corrupted.) + // We don't need to check against the begining of the stack because + // we can assume that aBp > sp + if (next <= aBp || next >= aStackEnd || (uintptr_t(next) & 3)) { + break; + } +# if (defined(__ppc__) && defined(XP_MACOSX)) || defined(__powerpc64__) + // ppc mac or powerpc64 linux + void* pc = *(aBp + 2); + aBp += 3; +# else // i386 or powerpc32 linux + void* pc = *(aBp + 1); + aBp += 2; +# endif + + // Strip off pointer authentication hash, if present. For now, it looks + // like only return addresses require stripping, and stack pointers do + // not. This might change in the future. + pc = (void*)((uintptr_t)pc & kPointerMask); + + if (!skipper.ShouldSkipPC(pc)) { + // Assume that the SP points to the BP of the function + // it called. We can't know the exact location of the SP + // but this should be sufficient for our use the SP + // to order elements on the stack. + numFrames++; + (*aCallback)(numFrames, pc, aBp, aClosure); + if (aMaxFrames != 0 && numFrames == aMaxFrames) { + break; + } + } + aBp = next; + } +} + +namespace mozilla { + +MFBT_API void FramePointerStackWalk(MozWalkStackCallback aCallback, + uint32_t aMaxFrames, void* aClosure, + void** aBp, void* aStackEnd) { + // We don't pass a aFirstFramePC because we start walking the stack from the + // frame at aBp. + DoFramePointerStackWalk(aCallback, nullptr, aMaxFrames, aClosure, aBp, + aStackEnd); +} + +} // namespace mozilla + +#else + +namespace mozilla { +MFBT_API void FramePointerStackWalk(MozWalkStackCallback aCallback, + uint32_t aMaxFrames, void* aClosure, + void** aBp, void* aStackEnd) {} +} // namespace mozilla + +#endif + +MFBT_API int MozFormatCodeAddressDetails( + char* aBuffer, uint32_t aBufferSize, uint32_t aFrameNumber, void* aPC, + const MozCodeAddressDetails* aDetails) { + return MozFormatCodeAddress(aBuffer, aBufferSize, aFrameNumber, aPC, + aDetails->function, aDetails->library, + aDetails->loffset, aDetails->filename, + aDetails->lineno); +} + +MFBT_API int MozFormatCodeAddress(char* aBuffer, uint32_t aBufferSize, + uint32_t aFrameNumber, const void* aPC, + const char* aFunction, const char* aLibrary, + ptrdiff_t aLOffset, const char* aFileName, + uint32_t aLineNo) { + const char* function = aFunction && aFunction[0] ? aFunction : "???"; + if (aFileName && aFileName[0]) { + // We have a filename and (presumably) a line number. Use them. + return SprintfBuf(aBuffer, aBufferSize, "#%02u: %s (%s:%u)", aFrameNumber, + function, aFileName, aLineNo); + } else if (aLibrary && aLibrary[0]) { + // We have no filename, but we do have a library name. Use it and the + // library offset, and print them in a way that `fix_stacks.py` can + // post-process. + return SprintfBuf(aBuffer, aBufferSize, "#%02u: %s[%s +0x%" PRIxPTR "]", + aFrameNumber, function, aLibrary, + static_cast<uintptr_t>(aLOffset)); + } else { + // We have nothing useful to go on. (The format string is split because + // '??)' is a trigraph and causes a warning, sigh.) + return SprintfBuf(aBuffer, aBufferSize, + "#%02u: ??? (???:???" + ")", + aFrameNumber); + } +} + +static void EnsureWrite(FILE* aStream, const char* aBuf, size_t aLen) { +#ifdef XP_WIN + int fd = _fileno(aStream); +#else + int fd = fileno(aStream); +#endif + while (aLen > 0) { +#ifdef XP_WIN + auto written = _write(fd, aBuf, aLen); +#else + auto written = write(fd, aBuf, aLen); +#endif + if (written <= 0 || size_t(written) > aLen) { + break; + } + aBuf += written; + aLen -= written; + } +} + +template <int N> +static int PrintStackFrameBuf(char (&aBuf)[N], uint32_t aFrameNumber, void* aPC, + void* aSP) { + MozCodeAddressDetails details; + MozDescribeCodeAddress(aPC, &details); + int len = + MozFormatCodeAddressDetails(aBuf, N - 1, aFrameNumber, aPC, &details); + len = std::min(len, N - 2); + aBuf[len++] = '\n'; + aBuf[len] = '\0'; + return len; +} + +static void PrintStackFrame(uint32_t aFrameNumber, void* aPC, void* aSP, + void* aClosure) { + FILE* stream = (FILE*)aClosure; + char buf[1025]; // 1024 + 1 for trailing '\n' + int len = PrintStackFrameBuf(buf, aFrameNumber, aPC, aSP); + fflush(stream); + EnsureWrite(stream, buf, len); +} + +static bool WalkTheStackEnabled() { + static bool result = [] { + char* value = getenv("MOZ_DISABLE_WALKTHESTACK"); + return !(value && value[0]); + }(); + return result; +} + +MFBT_API void MozWalkTheStack(FILE* aStream, const void* aFirstFramePC, + uint32_t aMaxFrames) { + if (WalkTheStackEnabled()) { + MozStackWalk(PrintStackFrame, aFirstFramePC ? aFirstFramePC : CallerPC(), + aMaxFrames, aStream); + } +} + +static void WriteStackFrame(uint32_t aFrameNumber, void* aPC, void* aSP, + void* aClosure) { + auto writer = (void (*)(const char*))aClosure; + char buf[1024]; + PrintStackFrameBuf(buf, aFrameNumber, aPC, aSP); + writer(buf); +} + +MFBT_API void MozWalkTheStackWithWriter(void (*aWriter)(const char*), + const void* aFirstFramePC, + uint32_t aMaxFrames) { + if (WalkTheStackEnabled()) { + MozStackWalk(WriteStackFrame, aFirstFramePC ? aFirstFramePC : CallerPC(), + aMaxFrames, (void*)aWriter); + } +} diff --git a/mozglue/misc/StackWalk.h b/mozglue/misc/StackWalk.h new file mode 100644 index 0000000000..250160934f --- /dev/null +++ b/mozglue/misc/StackWalk.h @@ -0,0 +1,215 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* APIs for getting a stack trace of the current thread */ + +#ifndef mozilla_StackWalk_h +#define mozilla_StackWalk_h + +#include "mozilla/Types.h" +#include <stdint.h> +#include <stdio.h> + +MOZ_BEGIN_EXTERN_C + +/** + * Returns the position of the Program Counter for the caller of the current + * function. This is meant to be used to feed the aFirstFramePC argument to + * MozStackWalk or MozWalkTheStack*, and should be used in the last function + * that should be skipped in the trace, and passed down to MozStackWalk or + * MozWalkTheStack*, through any intermediaries. + * + * THIS DOES NOT 100% RELIABLY GIVE THE CALLER PC, but marking functions + * calling this macro with MOZ_NEVER_INLINE gets us close. In cases it doesn't + * give the caller's PC, it may give the caller of the caller, or its caller, + * etc. depending on tail call optimization. + * + * Past versions of stackwalking relied on passing a constant number of frames + * to skip to MozStackWalk or MozWalkTheStack, which fell short in more cases + * (inlining of intermediaries, tail call optimization). + */ +#define CallerPC() __builtin_extract_return_addr(__builtin_return_address(0)) + +/** + * The callback for MozStackWalk and MozStackWalkThread. + * + * @param aFrameNumber The frame number (starts at 1, not 0). + * @param aPC The program counter value. + * @param aSP The best approximation possible of what the stack + * pointer will be pointing to when the execution returns + * to executing that at aPC. If no approximation can + * be made it will be nullptr. + * @param aClosure Extra data passed in from MozStackWalk() or + * MozStackWalkThread(). + */ +typedef void (*MozWalkStackCallback)(uint32_t aFrameNumber, void* aPC, + void* aSP, void* aClosure); + +/** + * Call aCallback for each stack frame on the current thread, from + * the caller of MozStackWalk to main (or above). + * + * @param aCallback Callback function, called once per frame. + * @param aFirstFramePC Position of the Program Counter where the trace + * starts from. All frames seen before reaching that + * address are skipped. Nullptr means that the first + * callback will be for the caller of MozStackWalk. + * @param aMaxFrames Maximum number of frames to trace. 0 means no limit. + * @param aClosure Caller-supplied data passed through to aCallback. + * + * May skip some stack frames due to compiler optimizations or code + * generation. + */ +MFBT_API void MozStackWalk(MozWalkStackCallback aCallback, + const void* aFirstFramePC, uint32_t aMaxFrames, + void* aClosure); + +typedef struct { + /* + * The name of the shared library or executable containing an + * address and the address's offset within that library, or empty + * string and zero if unknown. + */ + char library[256]; + ptrdiff_t loffset; + /* + * The name of the file name and line number of the code + * corresponding to the address, or empty string and zero if + * unknown. + */ + char filename[256]; + unsigned long lineno; + /* + * The name of the function containing an address and the address's + * offset within that function, or empty string and zero if unknown. + */ + char function[256]; + ptrdiff_t foffset; +} MozCodeAddressDetails; + +/** + * For a given pointer to code, fill in the pieces of information used + * when printing a stack trace. + * + * @param aPC The code address. + * @param aDetails A structure to be filled in with the result. + */ +MFBT_API bool MozDescribeCodeAddress(void* aPC, + MozCodeAddressDetails* aDetails); + +/** + * Format the information about a code address in a format suitable for + * stack traces on the current platform. When available, this string + * should contain the function name, source file, and line number. When + * these are not available, library and offset should be reported, if + * possible. + * + * Note that this output is parsed by several scripts including the fix*.py and + * make-tree.pl scripts in tools/rb/. It should only be change with care, and + * in conjunction with those scripts. + * + * @param aBuffer A string to be filled in with the description. + * The string will always be null-terminated. + * @param aBufferSize The size, in bytes, of aBuffer, including + * room for the terminating null. If the information + * to be printed would be larger than aBuffer, it + * will be truncated so that aBuffer[aBufferSize-1] + * is the terminating null. + * @param aFrameNumber The frame number. + * @param aPC The code address. + * @param aFunction The function name. Possibly null or the empty string. + * @param aLibrary The library name. Possibly null or the empty string. + * @param aLOffset The library offset. + * @param aFileName The filename. Possibly null or the empty string. + * @param aLineNo The line number. Possibly zero. + * @return The minimum number of characters necessary to format + * the frame information, without the terminating null. + * The buffer will have been truncated if the returned + * value is greater than aBufferSize-1. + */ +MFBT_API int MozFormatCodeAddress(char* aBuffer, uint32_t aBufferSize, + uint32_t aFrameNumber, const void* aPC, + const char* aFunction, const char* aLibrary, + ptrdiff_t aLOffset, const char* aFileName, + uint32_t aLineNo); + +/** + * Format the information about a code address in the same fashion as + * MozFormatCodeAddress. + * + * @param aBuffer A string to be filled in with the description. + * The string will always be null-terminated. + * @param aBufferSize The size, in bytes, of aBuffer, including + * room for the terminating null. If the information + * to be printed would be larger than aBuffer, it + * will be truncated so that aBuffer[aBufferSize-1] + * is the terminating null. + * @param aFrameNumber The frame number. + * @param aPC The code address. + * @param aDetails The value filled in by MozDescribeCodeAddress(aPC). + * @return The minimum number of characters necessary to format + * the frame information, without the terminating null. + * The buffer will have been truncated if the returned + * value is greater than aBufferSize-1. + */ +MFBT_API int MozFormatCodeAddressDetails(char* aBuffer, uint32_t aBufferSize, + uint32_t aFrameNumber, void* aPC, + const MozCodeAddressDetails* aDetails); + +#ifdef __cplusplus +# define FRAMES_DEFAULT = 0 +#else +# define FRAMES_DEFAULT +#endif +/** + * Walk the stack and print the stack trace to the given stream. + * + * @param aStream A stdio stream. + * @param aFirstFramePC Position of the Program Counter where the trace + * starts from. All frames seen before reaching that + * address are skipped. Nullptr means that the first + * callback will be for the caller of MozWalkTheStack. + * @param aMaxFrames Maximum number of frames to trace. 0 means no limit. + */ +MFBT_API void MozWalkTheStack(FILE* aStream, + const void* aFirstFramePC FRAMES_DEFAULT, + uint32_t aMaxFrames FRAMES_DEFAULT); + +/** + * Walk the stack and send each stack trace line to a callback writer. + * Each line string is null terminated but doesn't contain a '\n' character. + * + * @param aWriter The callback. + * @param aFirstFramePC Position of the Program Counter where the trace + * starts from. All frames seen before reaching that + * address are skipped. Nullptr means that the first + * callback will be for the caller of + * MozWalkTheStackWithWriter. + * @param aMaxFrames Maximum number of frames to trace. 0 means no limit. + */ +MFBT_API void MozWalkTheStackWithWriter( + void (*aWriter)(const char*), const void* aFirstFramePC FRAMES_DEFAULT, + uint32_t aMaxFrames FRAMES_DEFAULT); + +#undef FRAMES_DEFAULT + +MOZ_END_EXTERN_C + +#ifdef __cplusplus +namespace mozilla { + +MFBT_API void FramePointerStackWalk(MozWalkStackCallback aCallback, + uint32_t aMaxFrames, void* aClosure, + void** aBp, void* aStackEnd); + +# if defined(XP_LINUX) || defined(XP_FREEBSD) +MFBT_API void DemangleSymbol(const char* aSymbol, char* aBuffer, int aBufLen); +# endif + +} // namespace mozilla +#endif + +#endif diff --git a/mozglue/misc/StackWalkThread.h b/mozglue/misc/StackWalkThread.h new file mode 100644 index 0000000000..1db789de35 --- /dev/null +++ b/mozglue/misc/StackWalkThread.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* APIs for getting a stack trace of an arbitrary thread */ + +#ifndef mozilla_StackWalkThread_h +#define mozilla_StackWalkThread_h + +#include "mozilla/StackWalk.h" + +#include <windows.h> + +/** + * Like MozStackWalk, but walks the stack for another thread. + * Call aCallback for each stack frame on the current thread, from + * the caller of MozStackWalk to main (or above). + * + * @param aCallback Same as for MozStackWalk(). + * @param aMaxFrames Same as for MozStackWalk(). + * @param aClosure Same as for MozStackWalk(). + * @param aThread The handle of the thread whose stack is to be walked. + * If 0, walks the current thread. + * @param aContext A CONTEXT, presumably obtained with GetThreadContext() + * after suspending the thread with SuspendThread(). If + * null, the CONTEXT will be re-obtained. + */ +MFBT_API void MozStackWalkThread(MozWalkStackCallback aCallback, + uint32_t aMaxFrames, void* aClosure, + HANDLE aThread, CONTEXT* aContext); + +#endif diff --git a/mozglue/misc/StackWalk_windows.h b/mozglue/misc/StackWalk_windows.h new file mode 100644 index 0000000000..81c8125781 --- /dev/null +++ b/mozglue/misc/StackWalk_windows.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_StackWalk_windows_h +#define mozilla_StackWalk_windows_h + +#include "mozilla/Types.h" + +#if defined(_M_AMD64) || defined(_M_ARM64) +/** + * Allow stack walkers to work around the egregious win64 dynamic lookup table + * list API by locking around SuspendThread to avoid deadlock. + * + * See comment in StackWalk.cpp + */ +struct MOZ_RAII AutoSuppressStackWalking { + MFBT_API AutoSuppressStackWalking(); + MFBT_API ~AutoSuppressStackWalking(); +}; + +# if defined(IMPL_MFBT) +void SuppressStackWalking(); +void DesuppressStackWalking(); +# endif // defined(IMPL_MFBT) + +MFBT_API void RegisterJitCodeRegion(uint8_t* aStart, size_t size); + +MFBT_API void UnregisterJitCodeRegion(uint8_t* aStart, size_t size); +#endif // _M_AMD64 || _M_ARM64 + +#endif // mozilla_StackWalk_windows_h diff --git a/mozglue/misc/TimeStamp.cpp b/mozglue/misc/TimeStamp.cpp new file mode 100644 index 0000000000..f77cf63132 --- /dev/null +++ b/mozglue/misc/TimeStamp.cpp @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Implementation of the OS-independent methods of the TimeStamp class + */ + +#include "mozilla/TimeStamp.h" +#include "mozilla/Uptime.h" +#include <string.h> +#include <stdlib.h> + +namespace mozilla { + +/** + * Wrapper class used to initialize static data used by the TimeStamp class + */ +struct TimeStampInitialization { + /** + * First timestamp taken when the class static initializers are run. This + * timestamp is used to sanitize timestamps coming from different sources. + */ + TimeStamp mFirstTimeStamp; + + /** + * Timestamp representing the time when the process was created. This field + * is populated lazily the first time this information is required and is + * replaced every time the process is restarted. + */ + TimeStamp mProcessCreation; + + TimeStampInitialization() { + TimeStamp::Startup(); + mFirstTimeStamp = TimeStamp::Now(); + // On Windows < 10, initializing the uptime requires `mFirstTimeStamp` to be + // valid. + mozilla::InitializeUptime(); + } + + ~TimeStampInitialization() { TimeStamp::Shutdown(); } +}; + +static TimeStampInitialization sInitOnce; + +MFBT_API TimeStamp TimeStamp::ProcessCreation() { + if (sInitOnce.mProcessCreation.IsNull()) { + char* mozAppRestart = getenv("MOZ_APP_RESTART"); + TimeStamp ts; + + /* When calling PR_SetEnv() with an empty value the existing variable may + * be unset or set to the empty string depending on the underlying platform + * thus we have to check if the variable is present and not empty. */ + if (mozAppRestart && (strcmp(mozAppRestart, "") != 0)) { + /* Firefox was restarted, use the first time-stamp we've taken as the new + * process startup time. */ + ts = sInitOnce.mFirstTimeStamp; + } else { + TimeStamp now = Now(); + uint64_t uptime = ComputeProcessUptime(); + + ts = now - TimeDuration::FromMicroseconds(static_cast<double>(uptime)); + + if ((ts > sInitOnce.mFirstTimeStamp) || (uptime == 0)) { + ts = sInitOnce.mFirstTimeStamp; + } + } + + sInitOnce.mProcessCreation = ts; + } + + return sInitOnce.mProcessCreation; +} + +void TimeStamp::RecordProcessRestart() { + sInitOnce.mProcessCreation = TimeStamp(); +} + +MFBT_API TimeStamp TimeStamp::FirstTimeStamp() { + return sInitOnce.mFirstTimeStamp; +} + +class TimeStampTests { + // Check that nullity is set/not set correctly. + static_assert(TimeStamp{TimeStampValue{0}}.IsNull()); + static_assert(!TimeStamp{TimeStampValue{1}}.IsNull()); + + // Check that some very basic comparisons work correctly. + static constexpr uint64_t sMidTime = (uint64_t)1 << 63; + static_assert(TimeStampValue{sMidTime + 5} > TimeStampValue{sMidTime - 5}); + static_assert(TimeStampValue{sMidTime + 5} >= TimeStampValue{sMidTime - 5}); + static_assert(TimeStampValue{sMidTime - 5} < TimeStampValue{sMidTime + 5}); + static_assert(TimeStampValue{sMidTime - 5} <= TimeStampValue{sMidTime + 5}); + static_assert(TimeStampValue{sMidTime} == TimeStampValue{sMidTime}); + static_assert(TimeStampValue{sMidTime} >= TimeStampValue{sMidTime}); + static_assert(TimeStampValue{sMidTime} <= TimeStampValue{sMidTime}); + static_assert(TimeStampValue{sMidTime - 5} != TimeStampValue{sMidTime + 5}); + + // Check that comparisons involving very large and very small TimeStampValue's + // work correctly. This may seem excessive, but these asserts would have + // failed in the past due to a comparison such as "a > b" being implemented as + // "<cast to signed 64-bit value>(a - b) > 0". When a-b didn't fit into a + // signed 64-bit value, this would have given an incorrect result. + static_assert(TimeStampValue{UINT64_MAX} > TimeStampValue{1}); + static_assert(TimeStampValue{1} < TimeStampValue{UINT64_MAX}); + + // NOTE/TODO: It would be nice to add some additional tests here that involve + // arithmetic between TimeStamps and TimeDurations (and verifying some of the + // special behaviors in some cases such as not wrapping around below zero) but + // that is not possible right now because those operators are not constexpr. +}; + +} // namespace mozilla diff --git a/mozglue/misc/TimeStamp.h b/mozglue/misc/TimeStamp.h new file mode 100644 index 0000000000..b38882b250 --- /dev/null +++ b/mozglue/misc/TimeStamp.h @@ -0,0 +1,620 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_TimeStamp_h +#define mozilla_TimeStamp_h + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/Types.h" +#include <algorithm> // for std::min, std::max +#include <ostream> +#include <stdint.h> +#include <type_traits> + +namespace IPC { +template <typename T> +struct ParamTraits; +} // namespace IPC + +#ifdef XP_WIN +// defines TimeStampValue as a complex value keeping both +// GetTickCount and QueryPerformanceCounter values +# include "TimeStamp_windows.h" + +# include "mozilla/Maybe.h" // For TimeStamp::RawQueryPerformanceCounterValue +#endif + +namespace mozilla { + +#ifndef XP_WIN +typedef uint64_t TimeStampValue; +#endif + +class TimeStamp; +class TimeStampTests; + +/** + * Platform-specific implementation details of BaseTimeDuration. + */ +class BaseTimeDurationPlatformUtils { + public: + static MFBT_API double ToSeconds(int64_t aTicks); + static MFBT_API double ToSecondsSigDigits(int64_t aTicks); + static MFBT_API int64_t TicksFromMilliseconds(double aMilliseconds); + static MFBT_API int64_t ResolutionInTicks(); +}; + +/** + * Instances of this class represent the length of an interval of time. + * Negative durations are allowed, meaning the end is before the start. + * + * Internally the duration is stored as a int64_t in units of + * PR_TicksPerSecond() when building with NSPR interval timers, or a + * system-dependent unit when building with system clocks. The + * system-dependent unit must be constant, otherwise the semantics of + * this class would be broken. + * + * The ValueCalculator template parameter determines how arithmetic + * operations are performed on the integer count of ticks (mValue). + */ +template <typename ValueCalculator> +class BaseTimeDuration { + public: + // The default duration is 0. + constexpr BaseTimeDuration() : mValue(0) {} + // Allow construction using '0' as the initial value, for readability, + // but no other numbers (so we don't have any implicit unit conversions). + struct _SomethingVeryRandomHere; + MOZ_IMPLICIT BaseTimeDuration(_SomethingVeryRandomHere* aZero) : mValue(0) { + MOZ_ASSERT(!aZero, "Who's playing funny games here?"); + } + // Default copy-constructor and assignment are OK + + // Converting copy-constructor and assignment operator + template <typename E> + explicit BaseTimeDuration(const BaseTimeDuration<E>& aOther) + : mValue(aOther.mValue) {} + + template <typename E> + BaseTimeDuration& operator=(const BaseTimeDuration<E>& aOther) { + mValue = aOther.mValue; + return *this; + } + + double ToSeconds() const { + if (mValue == INT64_MAX) { + return PositiveInfinity<double>(); + } + if (mValue == INT64_MIN) { + return NegativeInfinity<double>(); + } + return BaseTimeDurationPlatformUtils::ToSeconds(mValue); + } + // Return a duration value that includes digits of time we think to + // be significant. This method should be used when displaying a + // time to humans. + double ToSecondsSigDigits() const { + if (mValue == INT64_MAX) { + return PositiveInfinity<double>(); + } + if (mValue == INT64_MIN) { + return NegativeInfinity<double>(); + } + return BaseTimeDurationPlatformUtils::ToSecondsSigDigits(mValue); + } + double ToMilliseconds() const { return ToSeconds() * 1000.0; } + double ToMicroseconds() const { return ToMilliseconds() * 1000.0; } + + // Using a double here is safe enough; with 53 bits we can represent + // durations up to over 280,000 years exactly. If the units of + // mValue do not allow us to represent durations of that length, + // long durations are clamped to the max/min representable value + // instead of overflowing. + static inline BaseTimeDuration FromSeconds(double aSeconds) { + return FromMilliseconds(aSeconds * 1000.0); + } + static BaseTimeDuration FromMilliseconds(double aMilliseconds) { + if (aMilliseconds == PositiveInfinity<double>()) { + return Forever(); + } + if (aMilliseconds == NegativeInfinity<double>()) { + return FromTicks(INT64_MIN); + } + return FromTicks( + BaseTimeDurationPlatformUtils::TicksFromMilliseconds(aMilliseconds)); + } + static inline BaseTimeDuration FromMicroseconds(double aMicroseconds) { + return FromMilliseconds(aMicroseconds / 1000.0); + } + + static constexpr BaseTimeDuration Zero() { return BaseTimeDuration(); } + static constexpr BaseTimeDuration Forever() { return FromTicks(INT64_MAX); } + + BaseTimeDuration operator+(const BaseTimeDuration& aOther) const { + return FromTicks(ValueCalculator::Add(mValue, aOther.mValue)); + } + BaseTimeDuration operator-(const BaseTimeDuration& aOther) const { + return FromTicks(ValueCalculator::Subtract(mValue, aOther.mValue)); + } + BaseTimeDuration& operator+=(const BaseTimeDuration& aOther) { + mValue = ValueCalculator::Add(mValue, aOther.mValue); + return *this; + } + BaseTimeDuration& operator-=(const BaseTimeDuration& aOther) { + mValue = ValueCalculator::Subtract(mValue, aOther.mValue); + return *this; + } + BaseTimeDuration operator-() const { + // We don't just use FromTicks(ValueCalculator::Subtract(0, mValue)) + // since that won't give the correct result for -TimeDuration::Forever(). + int64_t ticks; + if (mValue == INT64_MAX) { + ticks = INT64_MIN; + } else if (mValue == INT64_MIN) { + ticks = INT64_MAX; + } else { + ticks = -mValue; + } + + return FromTicks(ticks); + } + + static BaseTimeDuration Max(const BaseTimeDuration& aA, + const BaseTimeDuration& aB) { + return FromTicks(std::max(aA.mValue, aB.mValue)); + } + static BaseTimeDuration Min(const BaseTimeDuration& aA, + const BaseTimeDuration& aB) { + return FromTicks(std::min(aA.mValue, aB.mValue)); + } + +#if defined(DEBUG) + int64_t GetValue() const { return mValue; } +#endif + + private: + // Block double multiplier (slower, imprecise if long duration) - Bug 853398. + // If required, use MultDouble explicitly and with care. + BaseTimeDuration operator*(const double aMultiplier) const = delete; + + // Block double divisor (for the same reason, and because dividing by + // fractional values would otherwise invoke the int64_t variant, and rounding + // the passed argument can then cause divide-by-zero) - Bug 1147491. + BaseTimeDuration operator/(const double aDivisor) const = delete; + + public: + BaseTimeDuration MultDouble(double aMultiplier) const { + return FromTicks(ValueCalculator::Multiply(mValue, aMultiplier)); + } + BaseTimeDuration operator*(const int32_t aMultiplier) const { + return FromTicks(ValueCalculator::Multiply(mValue, aMultiplier)); + } + BaseTimeDuration operator*(const uint32_t aMultiplier) const { + return FromTicks(ValueCalculator::Multiply(mValue, aMultiplier)); + } + BaseTimeDuration operator*(const int64_t aMultiplier) const { + return FromTicks(ValueCalculator::Multiply(mValue, aMultiplier)); + } + BaseTimeDuration operator*(const uint64_t aMultiplier) const { + if (aMultiplier > INT64_MAX) { + return Forever(); + } + return FromTicks(ValueCalculator::Multiply(mValue, aMultiplier)); + } + BaseTimeDuration operator/(const int64_t aDivisor) const { + MOZ_ASSERT(aDivisor != 0, "Division by zero"); + return FromTicks(ValueCalculator::Divide(mValue, aDivisor)); + } + double operator/(const BaseTimeDuration& aOther) const { + MOZ_ASSERT(aOther.mValue != 0, "Division by zero"); + return ValueCalculator::DivideDouble(mValue, aOther.mValue); + } + BaseTimeDuration operator%(const BaseTimeDuration& aOther) const { + MOZ_ASSERT(aOther.mValue != 0, "Division by zero"); + return FromTicks(ValueCalculator::Modulo(mValue, aOther.mValue)); + } + + template <typename E> + bool operator<(const BaseTimeDuration<E>& aOther) const { + return mValue < aOther.mValue; + } + template <typename E> + bool operator<=(const BaseTimeDuration<E>& aOther) const { + return mValue <= aOther.mValue; + } + template <typename E> + bool operator>=(const BaseTimeDuration<E>& aOther) const { + return mValue >= aOther.mValue; + } + template <typename E> + bool operator>(const BaseTimeDuration<E>& aOther) const { + return mValue > aOther.mValue; + } + template <typename E> + bool operator==(const BaseTimeDuration<E>& aOther) const { + return mValue == aOther.mValue; + } + template <typename E> + bool operator!=(const BaseTimeDuration<E>& aOther) const { + return mValue != aOther.mValue; + } + bool IsZero() const { return mValue == 0; } + explicit operator bool() const { return mValue != 0; } + + friend std::ostream& operator<<(std::ostream& aStream, + const BaseTimeDuration& aDuration) { + return aStream << aDuration.ToMilliseconds() << " ms"; + } + + // Return a best guess at the system's current timing resolution, + // which might be variable. BaseTimeDurations below this order of + // magnitude are meaningless, and those at the same order of + // magnitude or just above are suspect. + static BaseTimeDuration Resolution() { + return FromTicks(BaseTimeDurationPlatformUtils::ResolutionInTicks()); + } + + // We could define additional operators here: + // -- convert to/from other time units + // -- scale duration by a float + // but let's do that on demand. + // Comparing durations for equality will only lead to bugs on + // platforms with high-resolution timers. + + private: + friend class TimeStamp; + friend struct IPC::ParamTraits<mozilla::BaseTimeDuration<ValueCalculator>>; + template <typename> + friend class BaseTimeDuration; + + static constexpr BaseTimeDuration FromTicks(int64_t aTicks) { + BaseTimeDuration t; + t.mValue = aTicks; + return t; + } + + static BaseTimeDuration FromTicks(double aTicks) { + // NOTE: this MUST be a >= test, because int64_t(double(INT64_MAX)) + // overflows and gives INT64_MIN. + if (aTicks >= double(INT64_MAX)) { + return FromTicks(INT64_MAX); + } + + // This MUST be a <= test. + if (aTicks <= double(INT64_MIN)) { + return FromTicks(INT64_MIN); + } + + return FromTicks(int64_t(aTicks)); + } + + // Duration, result is implementation-specific difference of two TimeStamps + int64_t mValue; +}; + +/** + * Perform arithmetic operations on the value of a BaseTimeDuration without + * doing strict checks on the range of values. + */ +class TimeDurationValueCalculator { + public: + static int64_t Add(int64_t aA, int64_t aB) { return aA + aB; } + static int64_t Subtract(int64_t aA, int64_t aB) { return aA - aB; } + + template <typename T> + static int64_t Multiply(int64_t aA, T aB) { + static_assert(std::is_integral_v<T>, + "Using integer multiplication routine with non-integer type." + " Further specialization required"); + return aA * static_cast<int64_t>(aB); + } + + static int64_t Divide(int64_t aA, int64_t aB) { return aA / aB; } + static double DivideDouble(int64_t aA, int64_t aB) { + return static_cast<double>(aA) / aB; + } + static int64_t Modulo(int64_t aA, int64_t aB) { return aA % aB; } +}; + +template <> +inline int64_t TimeDurationValueCalculator::Multiply<double>(int64_t aA, + double aB) { + return static_cast<int64_t>(aA * aB); +} + +/** + * Specialization of BaseTimeDuration that uses TimeDurationValueCalculator for + * arithmetic on the mValue member. + * + * Use this class for time durations that are *not* expected to hold values of + * Forever (or the negative equivalent) or when such time duration are *not* + * expected to be used in arithmetic operations. + */ +typedef BaseTimeDuration<TimeDurationValueCalculator> TimeDuration; + +/** + * Instances of this class represent moments in time, or a special + * "null" moment. We do not use the non-monotonic system clock or + * local time, since they can be reset, causing apparent backward + * travel in time, which can confuse algorithms. Instead we measure + * elapsed time according to the system. This time can never go + * backwards (i.e. it never wraps around, at least not in less than + * five million years of system elapsed time). It might not advance + * while the system is sleeping. If TimeStamp::SetNow() is not called + * at all for hours or days, we might not notice the passage of some + * of that time. + * + * We deliberately do not expose a way to convert TimeStamps to some + * particular unit. All you can do is compute a difference between two + * TimeStamps to get a TimeDuration. You can also add a TimeDuration + * to a TimeStamp to get a new TimeStamp. You can't do something + * meaningless like add two TimeStamps. + * + * Internally this is implemented as either a wrapper around + * - high-resolution, monotonic, system clocks if they exist on this + * platform + * - PRIntervalTime otherwise. We detect wraparounds of + * PRIntervalTime and work around them. + * + * This class is similar to C++11's time_point, however it is + * explicitly nullable and provides an IsNull() method. time_point + * is initialized to the clock's epoch and provides a + * time_since_epoch() method that functions similiarly. i.e. + * t.IsNull() is equivalent to t.time_since_epoch() == + * decltype(t)::duration::zero(); + * + * Note that, since TimeStamp objects are small, prefer to pass them by value + * unless there is a specific reason not to do so. + */ +#if defined(XP_WIN) +// If this static_assert fails then possibly the warning comment below is no +// longer valid and should be removed. +static_assert(sizeof(TimeStampValue) > 8); +#endif +/* + * WARNING: On Windows, each TimeStamp is represented internally by two + * different raw values (one from GTC and one from QPC) and which value gets + * used for a given operation depends on whether both operands have QPC values + * or not. This duality of values can lead to some surprising results when + * mixing TimeStamps with and without QPC values, such as comparisons being + * non-transitive (ie, a > b > c might not imply a > c). See bug 1829983 for + * more details/an example. + */ +class TimeStamp { + public: + /** + * Initialize to the "null" moment + */ + constexpr TimeStamp() : mValue(0) {} + // Default copy-constructor and assignment are OK + + /** + * The system timestamps are the same as the TimeStamp + * retrieved by mozilla::TimeStamp. Since we need this for + * vsync timestamps, we enable the creation of mozilla::TimeStamps + * on platforms that support vsync aligned refresh drivers / compositors + * Verified true as of Jan 31, 2015: B2G and OS X + * False on Windows 7 + * Android's event time uses CLOCK_MONOTONIC via SystemClock.uptimeMilles. + * So it is same value of TimeStamp posix implementation. + * Wayland/GTK event time also uses CLOCK_MONOTONIC on Weston/Mutter + * compositors. + * UNTESTED ON OTHER PLATFORMS + */ +#if defined(XP_DARWIN) || defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GTK) + static TimeStamp FromSystemTime(int64_t aSystemTime) { + static_assert(sizeof(aSystemTime) == sizeof(TimeStampValue), + "System timestamp should be same units as TimeStampValue"); + return TimeStamp(aSystemTime); + } +#endif + + /** + * Return true if this is the "null" moment + */ + constexpr bool IsNull() const { return mValue == 0; } + + /** + * Return true if this is not the "null" moment, may be used in tests, e.g.: + * |if (timestamp) { ... }| + */ + explicit operator bool() const { return mValue != 0; } + + /** + * Return a timestamp reflecting the current elapsed system time. This + * is monotonically increasing (i.e., does not decrease) over the + * lifetime of this process' XPCOM session. + * + * Now() is trying to ensure the best possible precision on each platform, + * at least one millisecond. + * + * NowLoRes() has been introduced to workaround performance problems of + * QueryPerformanceCounter on the Windows platform. NowLoRes() is giving + * lower precision, usually 15.6 ms, but with very good performance benefit. + * Use it for measurements of longer times, like >200ms timeouts. + */ + static TimeStamp Now() { return Now(true); } + static TimeStamp NowLoRes() { return Now(false); } + + /** + * Return a timestamp representing the time when the current process was + * created which will be comparable with other timestamps taken with this + * class. + * + * @returns A timestamp representing the time when the process was created + */ + static MFBT_API TimeStamp ProcessCreation(); + + /** + * Return the very first timestamp that was taken. This can be used instead + * of TimeStamp::ProcessCreation() by code that might not allow running the + * complex logic required to compute the real process creation. This will + * necessarily have been recorded sometimes after TimeStamp::ProcessCreation() + * or at best should be equal to it. + * + * @returns The first tiemstamp that was taken by this process + */ + static MFBT_API TimeStamp FirstTimeStamp(); + + /** + * Records a process restart. After this call ProcessCreation() will return + * the time when the browser was restarted instead of the actual time when + * the process was created. + */ + static MFBT_API void RecordProcessRestart(); + +#ifdef XP_LINUX + uint64_t RawClockMonotonicNanosecondsSinceBoot() { + return static_cast<uint64_t>(mValue); + } +#endif + +#ifdef XP_DARWIN + // Returns the number of nanoseconds since the mach_absolute_time origin. + MFBT_API uint64_t RawMachAbsoluteTimeNanoseconds() const; +#endif + +#ifdef XP_WIN + Maybe<uint64_t> RawQueryPerformanceCounterValue() const { + // mQPC is stored in `mt` i.e. QueryPerformanceCounter * 1000 + // so divide out the 1000 + return mValue.mHasQPC ? Some(mValue.mQPC / 1000ULL) : Nothing(); + } +#endif + + /** + * Compute the difference between two timestamps. Both must be non-null. + */ + TimeDuration operator-(const TimeStamp& aOther) const { + MOZ_ASSERT(!IsNull(), "Cannot compute with a null value"); + MOZ_ASSERT(!aOther.IsNull(), "Cannot compute with aOther null value"); + static_assert(-INT64_MAX > INT64_MIN, "int64_t sanity check"); + int64_t ticks = int64_t(mValue - aOther.mValue); + // Check for overflow. + if (mValue > aOther.mValue) { + if (ticks < 0) { + ticks = INT64_MAX; + } + } else { + if (ticks > 0) { + ticks = INT64_MIN; + } + } + return TimeDuration::FromTicks(ticks); + } + + TimeStamp operator+(const TimeDuration& aOther) const { + TimeStamp result = *this; + result += aOther; + return result; + } + TimeStamp operator-(const TimeDuration& aOther) const { + TimeStamp result = *this; + result -= aOther; + return result; + } + TimeStamp& operator+=(const TimeDuration& aOther) { + MOZ_ASSERT(!IsNull(), "Cannot compute with a null value"); + TimeStampValue value = mValue + aOther.mValue; + // Check for underflow. + // (We don't check for overflow because it's not obvious what the error + // behavior should be in that case.) + if (aOther.mValue < 0 && value > mValue) { + value = 0; + } + mValue = value; + return *this; + } + TimeStamp& operator-=(const TimeDuration& aOther) { + MOZ_ASSERT(!IsNull(), "Cannot compute with a null value"); + TimeStampValue value = mValue - aOther.mValue; + // Check for underflow. + // (We don't check for overflow because it's not obvious what the error + // behavior should be in that case.) + if (aOther.mValue > 0 && value > mValue) { + value = 0; + } + mValue = value; + return *this; + } + + constexpr bool operator<(const TimeStamp& aOther) const { + MOZ_ASSERT(!IsNull(), "Cannot compute with a null value"); + MOZ_ASSERT(!aOther.IsNull(), "Cannot compute with aOther null value"); + return mValue < aOther.mValue; + } + constexpr bool operator<=(const TimeStamp& aOther) const { + MOZ_ASSERT(!IsNull(), "Cannot compute with a null value"); + MOZ_ASSERT(!aOther.IsNull(), "Cannot compute with aOther null value"); + return mValue <= aOther.mValue; + } + constexpr bool operator>=(const TimeStamp& aOther) const { + MOZ_ASSERT(!IsNull(), "Cannot compute with a null value"); + MOZ_ASSERT(!aOther.IsNull(), "Cannot compute with aOther null value"); + return mValue >= aOther.mValue; + } + constexpr bool operator>(const TimeStamp& aOther) const { + MOZ_ASSERT(!IsNull(), "Cannot compute with a null value"); + MOZ_ASSERT(!aOther.IsNull(), "Cannot compute with aOther null value"); + return mValue > aOther.mValue; + } + bool operator==(const TimeStamp& aOther) const { + return IsNull() ? aOther.IsNull() + : !aOther.IsNull() && mValue == aOther.mValue; + } + bool operator!=(const TimeStamp& aOther) const { return !(*this == aOther); } + + // Comparing TimeStamps for equality should be discouraged. Adding + // two TimeStamps, or scaling TimeStamps, is nonsense and must never + // be allowed. + + static MFBT_API void Startup(); + static MFBT_API void Shutdown(); + +#if defined(DEBUG) + TimeStampValue GetValue() const { return mValue; } +#endif + + private: + friend struct IPC::ParamTraits<mozilla::TimeStamp>; + friend struct TimeStampInitialization; + friend class TimeStampTests; + + constexpr MOZ_IMPLICIT TimeStamp(TimeStampValue aValue) : mValue(aValue) {} + + static MFBT_API TimeStamp Now(bool aHighResolution); + + /** + * Computes the uptime of the current process in microseconds. The result + * is platform-dependent and needs to be checked against existing timestamps + * for consistency. + * + * @returns The number of microseconds since the calling process was started + * or 0 if an error was encountered while computing the uptime + */ + static MFBT_API uint64_t ComputeProcessUptime(); + + /** + * When built with PRIntervalTime, a value of 0 means this instance + * is "null". Otherwise, the low 32 bits represent a PRIntervalTime, + * and the high 32 bits represent a counter of the number of + * rollovers of PRIntervalTime that we've seen. This counter starts + * at 1 to avoid a real time colliding with the "null" value. + * + * PR_INTERVAL_MAX is set at 100,000 ticks per second. So the minimum + * time to wrap around is about 2^64/100000 seconds, i.e. about + * 5,849,424 years. + * + * When using a system clock, a value is system dependent. + */ + TimeStampValue mValue; +}; + +} // namespace mozilla + +#endif /* mozilla_TimeStamp_h */ diff --git a/mozglue/misc/TimeStamp_darwin.cpp b/mozglue/misc/TimeStamp_darwin.cpp new file mode 100644 index 0000000000..ec29917985 --- /dev/null +++ b/mozglue/misc/TimeStamp_darwin.cpp @@ -0,0 +1,189 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Implement TimeStamp::Now() with mach_absolute_time +// +// The "tick" unit for mach_absolute_time is defined using mach_timebase_info() +// which gives a conversion ratio to nanoseconds. For more information see +// Apple's QA1398. +// +// This code is inspired by Chromium's time_mac.cc. The biggest +// differences are that we explicitly initialize using +// TimeStamp::Initialize() instead of lazily in Now() and that +// we store the time value in ticks and convert when needed instead +// of storing the time value in nanoseconds. + +#include <mach/mach_time.h> +#include <sys/time.h> +#include <sys/sysctl.h> +#include <time.h> +#include <unistd.h> + +#include "mozilla/TimeStamp.h" +#include "mozilla/Uptime.h" + +// Estimate of the smallest duration of time we can measure. +static uint64_t sResolution; +static uint64_t sResolutionSigDigs; + +static const uint64_t kNsPerMs = 1000000; +static const uint64_t kUsPerSec = 1000000; +static const double kNsPerMsd = 1000000.0; +static const double kNsPerSecd = 1000000000.0; + +static bool gInitialized = false; +static double sNsPerTick; + +static uint64_t ClockTime() { + // mach_absolute_time is it when it comes to ticks on the Mac. Other calls + // with less precision (such as TickCount) just call through to + // mach_absolute_time. + // + // At the time of writing mach_absolute_time returns the number of nanoseconds + // since boot. This won't overflow 64bits for 500+ years so we aren't going + // to worry about that possiblity + return mach_absolute_time(); +} + +static uint64_t ClockResolutionNs() { + uint64_t start = ClockTime(); + uint64_t end = ClockTime(); + uint64_t minres = (end - start); + + // 10 total trials is arbitrary: what we're trying to avoid by + // looping is getting unlucky and being interrupted by a context + // switch or signal, or being bitten by paging/cache effects + for (int i = 0; i < 9; ++i) { + start = ClockTime(); + end = ClockTime(); + + uint64_t candidate = (start - end); + if (candidate < minres) { + minres = candidate; + } + } + + if (0 == minres) { + // measurable resolution is either incredibly low, ~1ns, or very + // high. fall back on NSPR's resolution assumption + minres = 1 * kNsPerMs; + } + + return minres; +} + +namespace mozilla { + +double BaseTimeDurationPlatformUtils::ToSeconds(int64_t aTicks) { + MOZ_ASSERT(gInitialized, "calling TimeDuration too early"); + return (aTicks * sNsPerTick) / kNsPerSecd; +} + +double BaseTimeDurationPlatformUtils::ToSecondsSigDigits(int64_t aTicks) { + MOZ_ASSERT(gInitialized, "calling TimeDuration too early"); + // don't report a value < mResolution ... + int64_t valueSigDigs = sResolution * (aTicks / sResolution); + // and chop off insignificant digits + valueSigDigs = sResolutionSigDigs * (valueSigDigs / sResolutionSigDigs); + return (valueSigDigs * sNsPerTick) / kNsPerSecd; +} + +int64_t BaseTimeDurationPlatformUtils::TicksFromMilliseconds( + double aMilliseconds) { + MOZ_ASSERT(gInitialized, "calling TimeDuration too early"); + double result = (aMilliseconds * kNsPerMsd) / sNsPerTick; + if (result > double(INT64_MAX)) { + return INT64_MAX; + } else if (result < double(INT64_MIN)) { + return INT64_MIN; + } + + return result; +} + +int64_t BaseTimeDurationPlatformUtils::ResolutionInTicks() { + MOZ_ASSERT(gInitialized, "calling TimeDuration too early"); + return static_cast<int64_t>(sResolution); +} + +void TimeStamp::Startup() { + if (gInitialized) { + return; + } + + mach_timebase_info_data_t timebaseInfo; + // Apple's QA1398 suggests that the output from mach_timebase_info + // will not change while a program is running, so it should be safe + // to cache the result. + kern_return_t kr = mach_timebase_info(&timebaseInfo); + if (kr != KERN_SUCCESS) { + MOZ_RELEASE_ASSERT(false, "mach_timebase_info failed"); + } + + sNsPerTick = double(timebaseInfo.numer) / timebaseInfo.denom; + + sResolution = ClockResolutionNs(); + + // find the number of significant digits in sResolution, for the + // sake of ToSecondsSigDigits() + for (sResolutionSigDigs = 1; !(sResolutionSigDigs == sResolution || + 10 * sResolutionSigDigs > sResolution); + sResolutionSigDigs *= 10) + ; + + gInitialized = true; +} + +void TimeStamp::Shutdown() {} + +TimeStamp TimeStamp::Now(bool aHighResolution) { + return TimeStamp(ClockTime()); +} + +uint64_t TimeStamp::RawMachAbsoluteTimeNanoseconds() const { + return static_cast<uint64_t>(double(mValue) * sNsPerTick); +} + +// Computes and returns the process uptime in microseconds. +// Returns 0 if an error was encountered. +uint64_t TimeStamp::ComputeProcessUptime() { + struct timeval tv; + int rv = gettimeofday(&tv, nullptr); + + if (rv == -1) { + return 0; + } + + int mib[] = { + CTL_KERN, + KERN_PROC, + KERN_PROC_PID, + getpid(), + }; + u_int mibLen = sizeof(mib) / sizeof(mib[0]); + + struct kinfo_proc proc; + size_t bufferSize = sizeof(proc); + rv = sysctl(mib, mibLen, &proc, &bufferSize, nullptr, 0); + + if (rv == -1) { + return 0; + } + + uint64_t startTime = + ((uint64_t)proc.kp_proc.p_un.__p_starttime.tv_sec * kUsPerSec) + + proc.kp_proc.p_un.__p_starttime.tv_usec; + uint64_t now = (tv.tv_sec * kUsPerSec) + tv.tv_usec; + + if (startTime > now) { + return 0; + } + + return now - startTime; +} + +} // namespace mozilla diff --git a/mozglue/misc/TimeStamp_posix.cpp b/mozglue/misc/TimeStamp_posix.cpp new file mode 100644 index 0000000000..ba32a230eb --- /dev/null +++ b/mozglue/misc/TimeStamp_posix.cpp @@ -0,0 +1,360 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Implement TimeStamp::Now() with POSIX clocks. +// +// The "tick" unit for POSIX clocks is simply a nanosecond, as this is +// the smallest unit of time representable by struct timespec. That +// doesn't mean that a nanosecond is the resolution of TimeDurations +// obtained with this API; see TimeDuration::Resolution; +// + +#include <sys/syscall.h> +#include <time.h> +#include <unistd.h> +#include <string.h> + +#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || \ + defined(__OpenBSD__) +# include <sys/param.h> +# include <sys/sysctl.h> +#endif + +#if defined(__DragonFly__) || defined(__FreeBSD__) +# include <sys/user.h> +#endif + +#if defined(__NetBSD__) +# undef KERN_PROC +# define KERN_PROC KERN_PROC2 +# define KINFO_PROC struct kinfo_proc2 +#else +# define KINFO_PROC struct kinfo_proc +#endif + +#if defined(__DragonFly__) +# define KP_START_SEC kp_start.tv_sec +# define KP_START_USEC kp_start.tv_usec +#elif defined(__FreeBSD__) +# define KP_START_SEC ki_start.tv_sec +# define KP_START_USEC ki_start.tv_usec +#else +# define KP_START_SEC p_ustart_sec +# define KP_START_USEC p_ustart_usec +#endif + +#include "mozilla/Sprintf.h" +#include "mozilla/TimeStamp.h" + +#if !defined(__wasi__) +# include <pthread.h> +#endif + +// Estimate of the smallest duration of time we can measure. +static uint64_t sResolution; +static uint64_t sResolutionSigDigs; + +#ifdef CLOCK_MONOTONIC_COARSE +static bool sSupportsMonotonicCoarseClock = false; +#endif + +#if !defined(__wasi__) +static const uint16_t kNsPerUs = 1000; +#endif + +static const uint64_t kNsPerMs = 1000000; +static const uint64_t kNsPerSec = 1000000000; +static const double kNsPerMsd = 1000000.0; +static const double kNsPerSecd = 1000000000.0; + +static uint64_t TimespecToNs(const struct timespec& aTs) { + uint64_t baseNs = uint64_t(aTs.tv_sec) * kNsPerSec; + return baseNs + uint64_t(aTs.tv_nsec); +} + +static uint64_t ClockTimeNs(const clockid_t aClockId = CLOCK_MONOTONIC) { + struct timespec ts; +#ifdef CLOCK_MONOTONIC_COARSE + MOZ_RELEASE_ASSERT( + aClockId == CLOCK_MONOTONIC || + (sSupportsMonotonicCoarseClock && aClockId == CLOCK_MONOTONIC_COARSE)); +#else + MOZ_RELEASE_ASSERT(aClockId == CLOCK_MONOTONIC); +#endif + // this can't fail: we know &ts is valid, and TimeStamp::Startup() + // checks that CLOCK_MONOTONIC / CLOCK_MONOTONIC_COARSE are + // supported (and aborts if the former is not). + clock_gettime(aClockId, &ts); + + // tv_sec is defined to be relative to an arbitrary point in time, + // but it would be madness for that point in time to be earlier than + // the Epoch. So we can safely assume that even if time_t is 32 + // bits, tv_sec won't overflow while the browser is open. Revisit + // this argument if we're still building with 32-bit time_t around + // the year 2037. + return TimespecToNs(ts); +} + +static uint64_t ClockResolutionNs() { + // NB: why not rely on clock_getres()? Two reasons: (i) it might + // lie, and (ii) it might return an "ideal" resolution that while + // theoretically true, could never be measured in practice. Since + // clock_gettime() likely involves a system call on your platform, + // the "actual" timing resolution shouldn't be lower than syscall + // overhead. + + uint64_t start = ClockTimeNs(); + uint64_t end = ClockTimeNs(); + uint64_t minres = (end - start); + + // 10 total trials is arbitrary: what we're trying to avoid by + // looping is getting unlucky and being interrupted by a context + // switch or signal, or being bitten by paging/cache effects + for (int i = 0; i < 9; ++i) { + start = ClockTimeNs(); + end = ClockTimeNs(); + + uint64_t candidate = (start - end); + if (candidate < minres) { + minres = candidate; + } + } + + if (0 == minres) { + // measurable resolution is either incredibly low, ~1ns, or very + // high. fall back on clock_getres() + struct timespec ts; + if (0 == clock_getres(CLOCK_MONOTONIC, &ts)) { + minres = TimespecToNs(ts); + } + } + + if (0 == minres) { + // clock_getres probably failed. fall back on NSPR's resolution + // assumption + minres = 1 * kNsPerMs; + } + + return minres; +} + +namespace mozilla { + +double BaseTimeDurationPlatformUtils::ToSeconds(int64_t aTicks) { + return double(aTicks) / kNsPerSecd; +} + +double BaseTimeDurationPlatformUtils::ToSecondsSigDigits(int64_t aTicks) { + // don't report a value < mResolution ... + int64_t valueSigDigs = sResolution * (aTicks / sResolution); + // and chop off insignificant digits + valueSigDigs = sResolutionSigDigs * (valueSigDigs / sResolutionSigDigs); + return double(valueSigDigs) / kNsPerSecd; +} + +int64_t BaseTimeDurationPlatformUtils::TicksFromMilliseconds( + double aMilliseconds) { + double result = aMilliseconds * kNsPerMsd; + if (result > double(INT64_MAX)) { + return INT64_MAX; + } + if (result < INT64_MIN) { + return INT64_MIN; + } + + return result; +} + +int64_t BaseTimeDurationPlatformUtils::ResolutionInTicks() { + return static_cast<int64_t>(sResolution); +} + +static bool gInitialized = false; + +void TimeStamp::Startup() { + if (gInitialized) { + return; + } + + struct timespec dummy; + if (clock_gettime(CLOCK_MONOTONIC, &dummy) != 0) { + MOZ_CRASH("CLOCK_MONOTONIC is absent!"); + } + +#ifdef CLOCK_MONOTONIC_COARSE + if (clock_gettime(CLOCK_MONOTONIC_COARSE, &dummy) == 0) { + sSupportsMonotonicCoarseClock = true; + } +#endif + + sResolution = ClockResolutionNs(); + + // find the number of significant digits in sResolution, for the + // sake of ToSecondsSigDigits() + for (sResolutionSigDigs = 1; !(sResolutionSigDigs == sResolution || + 10 * sResolutionSigDigs > sResolution); + sResolutionSigDigs *= 10) + ; + + gInitialized = true; +} + +void TimeStamp::Shutdown() {} + +TimeStamp TimeStamp::Now(bool aHighResolution) { +#ifdef CLOCK_MONOTONIC_COARSE + if (!aHighResolution && sSupportsMonotonicCoarseClock) { + return TimeStamp(ClockTimeNs(CLOCK_MONOTONIC_COARSE)); + } +#endif + return TimeStamp(ClockTimeNs(CLOCK_MONOTONIC)); +} + +#if defined(XP_LINUX) || defined(ANDROID) + +// Calculates the amount of jiffies that have elapsed since boot and up to the +// starttime value of a specific process as found in its /proc/*/stat file. +// Returns 0 if an error occurred. + +static uint64_t JiffiesSinceBoot(const char* aFile) { + char stat[512]; + + FILE* f = fopen(aFile, "r"); + if (!f) { + return 0; + } + + int n = fread(&stat, 1, sizeof(stat) - 1, f); + + fclose(f); + + if (n <= 0) { + return 0; + } + + stat[n] = 0; + + long long unsigned startTime = 0; // instead of uint64_t to keep GCC quiet + char* s = strrchr(stat, ')'); + + if (!s) { + return 0; + } + + int rv = sscanf(s + 2, + "%*c %*d %*d %*d %*d %*d %*u %*u %*u %*u " + "%*u %*u %*u %*d %*d %*d %*d %*d %*d %llu", + &startTime); + + if (rv != 1 || !startTime) { + return 0; + } + + return startTime; +} + +// Computes the interval that has elapsed between the thread creation and the +// process creation by comparing the starttime fields in the respective +// /proc/*/stat files. The resulting value will be a good approximation of the +// process uptime. This value will be stored at the address pointed by aTime; +// if an error occurred 0 will be stored instead. + +static void* ComputeProcessUptimeThread(void* aTime) { + uint64_t* uptime = static_cast<uint64_t*>(aTime); + long hz = sysconf(_SC_CLK_TCK); + + *uptime = 0; + + if (!hz) { + return nullptr; + } + + char threadStat[40]; + SprintfLiteral(threadStat, "/proc/self/task/%d/stat", + (pid_t)syscall(__NR_gettid)); + + uint64_t threadJiffies = JiffiesSinceBoot(threadStat); + uint64_t selfJiffies = JiffiesSinceBoot("/proc/self/stat"); + + if (!threadJiffies || !selfJiffies) { + return nullptr; + } + + *uptime = ((threadJiffies - selfJiffies) * kNsPerSec) / hz; + return nullptr; +} + +// Computes and returns the process uptime in us on Linux & its derivatives. +// Returns 0 if an error was encountered. + +uint64_t TimeStamp::ComputeProcessUptime() { + uint64_t uptime = 0; + pthread_t uptime_pthread; + + if (pthread_create(&uptime_pthread, nullptr, ComputeProcessUptimeThread, + &uptime)) { + MOZ_CRASH("Failed to create process uptime thread."); + return 0; + } + + pthread_join(uptime_pthread, NULL); + + return uptime / kNsPerUs; +} + +#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || \ + defined(__OpenBSD__) + +// Computes and returns the process uptime in us on various BSD flavors. +// Returns 0 if an error was encountered. + +uint64_t TimeStamp::ComputeProcessUptime() { + struct timespec ts; + int rv = clock_gettime(CLOCK_REALTIME, &ts); + + if (rv == -1) { + return 0; + } + + int mib[] = { + CTL_KERN, + KERN_PROC, + KERN_PROC_PID, + getpid(), +# if defined(__NetBSD__) || defined(__OpenBSD__) + sizeof(KINFO_PROC), + 1, +# endif + }; + u_int mibLen = sizeof(mib) / sizeof(mib[0]); + + KINFO_PROC proc; + size_t bufferSize = sizeof(proc); + rv = sysctl(mib, mibLen, &proc, &bufferSize, nullptr, 0); + + if (rv == -1) { + return 0; + } + + uint64_t startTime = ((uint64_t)proc.KP_START_SEC * kNsPerSec) + + (proc.KP_START_USEC * kNsPerUs); + uint64_t now = ((uint64_t)ts.tv_sec * kNsPerSec) + ts.tv_nsec; + + if (startTime > now) { + return 0; + } + + return (now - startTime) / kNsPerUs; +} + +#else + +uint64_t TimeStamp::ComputeProcessUptime() { return 0; } + +#endif + +} // namespace mozilla diff --git a/mozglue/misc/TimeStamp_windows.cpp b/mozglue/misc/TimeStamp_windows.cpp new file mode 100644 index 0000000000..81da34409d --- /dev/null +++ b/mozglue/misc/TimeStamp_windows.cpp @@ -0,0 +1,577 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Implement TimeStamp::Now() with QueryPerformanceCounter() controlled with +// values of GetTickCount64(). + +#include "mozilla/DynamicallyLinkedFunctionPtr.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Uptime.h" + +#include <stdio.h> +#include <stdlib.h> +#include <intrin.h> +#include <windows.h> + +// To enable logging define to your favorite logging API +#define LOG(x) + +class AutoCriticalSection { + public: + explicit AutoCriticalSection(LPCRITICAL_SECTION aSection) + : mSection(aSection) { + ::EnterCriticalSection(mSection); + } + ~AutoCriticalSection() { ::LeaveCriticalSection(mSection); } + + private: + LPCRITICAL_SECTION mSection; +}; + +// Estimate of the smallest duration of time we can measure. +static volatile ULONGLONG sResolution; +static volatile ULONGLONG sResolutionSigDigs; +static const double kNsPerSecd = 1000000000.0; +static const LONGLONG kNsPerMillisec = 1000000; + +// ---------------------------------------------------------------------------- +// Global constants +// ---------------------------------------------------------------------------- + +// Tolerance to failures settings. +// +// What is the interval we want to have failure free. +// in [ms] +static const uint32_t kFailureFreeInterval = 5000; +// How many failures we are willing to tolerate in the interval. +static const uint32_t kMaxFailuresPerInterval = 4; +// What is the threshold to treat fluctuations as actual failures. +// in [ms] +static const uint32_t kFailureThreshold = 50; + +// If we are not able to get the value of GTC time increment, use this value +// which is the most usual increment. +static const DWORD kDefaultTimeIncrement = 156001; + +// ---------------------------------------------------------------------------- +// Global variables, not changing at runtime +// ---------------------------------------------------------------------------- + +// Result of QueryPerformanceFrequency +// We use default of 1 for the case we can't use QueryPerformanceCounter +// to make mt/ms conversions work despite that. +static uint64_t sFrequencyPerSec = 1; + +namespace mozilla { + +MFBT_API uint64_t GetQueryPerformanceFrequencyPerSec() { + return sFrequencyPerSec; +} + +} // namespace mozilla + +// How much we are tolerant to GTC occasional loose of resoltion. +// This number says how many multiples of the minimal GTC resolution +// detected on the system are acceptable. This number is empirical. +static const LONGLONG kGTCTickLeapTolerance = 4; + +// Base tolerance (more: "inability of detection" range) threshold is calculated +// dynamically, and kept in sGTCResolutionThreshold. +// +// Schematically, QPC worked "100%" correctly if ((GTC_now - GTC_epoch) - +// (QPC_now - QPC_epoch)) was in [-sGTCResolutionThreshold, +// sGTCResolutionThreshold] interval every time we'd compared two time stamps. +// If not, then we check the overflow behind this basic threshold +// is in kFailureThreshold. If not, we condider it as a QPC failure. If too +// many failures in short time are detected, QPC is considered faulty and +// disabled. +// +// Kept in [mt] +static LONGLONG sGTCResolutionThreshold; + +// If QPC is found faulty for two stamps in this interval, we engage +// the fault detection algorithm. For duration larger then this limit +// we bypass using durations calculated from QPC when jitter is detected, +// but don't touch the sUseQPC flag. +// +// Value is in [ms]. +static const uint32_t kHardFailureLimit = 2000; +// Conversion to [mt] +static LONGLONG sHardFailureLimit; + +// Conversion of kFailureFreeInterval and kFailureThreshold to [mt] +static LONGLONG sFailureFreeInterval; +static LONGLONG sFailureThreshold; + +// ---------------------------------------------------------------------------- +// Systemm status flags +// ---------------------------------------------------------------------------- + +// Flag for stable TSC that indicates platform where QPC is stable. +static bool sHasStableTSC = false; + +// ---------------------------------------------------------------------------- +// Global state variables, changing at runtime +// ---------------------------------------------------------------------------- + +// Initially true, set to false when QPC is found unstable and never +// returns back to true since that time. +static bool volatile sUseQPC = true; + +// ---------------------------------------------------------------------------- +// Global lock +// ---------------------------------------------------------------------------- + +// Thread spin count before entering the full wait state for sTimeStampLock. +// Inspired by Rob Arnold's work on PRMJ_Now(). +static const DWORD kLockSpinCount = 4096; + +// Common mutex (thanks the relative complexity of the logic, this is better +// then using CMPXCHG8B.) +// It is protecting the globals bellow. +static CRITICAL_SECTION sTimeStampLock; + +// ---------------------------------------------------------------------------- +// Global lock protected variables +// ---------------------------------------------------------------------------- + +// Timestamp in future until QPC must behave correctly. +// Set to now + kFailureFreeInterval on first QPC failure detection. +// Set to now + E * kFailureFreeInterval on following errors, +// where E is number of errors detected during last kFailureFreeInterval +// milliseconds, calculated simply as: +// E = (sFaultIntoleranceCheckpoint - now) / kFailureFreeInterval + 1. +// When E > kMaxFailuresPerInterval -> disable QPC. +// +// Kept in [mt] +static ULONGLONG sFaultIntoleranceCheckpoint = 0; + +namespace mozilla { + +// Result is in [mt] +static inline ULONGLONG PerformanceCounter() { + LARGE_INTEGER pc; + ::QueryPerformanceCounter(&pc); + + // QueryPerformanceCounter may slightly jitter (not be 100% monotonic.) + // This is a simple go-backward protection for such a faulty hardware. + AutoCriticalSection lock(&sTimeStampLock); + + static decltype(LARGE_INTEGER::QuadPart) last; + if (last > pc.QuadPart) { + return last * 1000ULL; + } + last = pc.QuadPart; + return pc.QuadPart * 1000ULL; +} + +static void InitThresholds() { + DWORD timeAdjustment = 0, timeIncrement = 0; + BOOL timeAdjustmentDisabled; + GetSystemTimeAdjustment(&timeAdjustment, &timeIncrement, + &timeAdjustmentDisabled); + + LOG(("TimeStamp: timeIncrement=%d [100ns]", timeIncrement)); + + if (!timeIncrement) { + timeIncrement = kDefaultTimeIncrement; + } + + // Ceiling to a millisecond + // Example values: 156001, 210000 + DWORD timeIncrementCeil = timeIncrement; + // Don't want to round up if already rounded, values will be: 156000, 209999 + timeIncrementCeil -= 1; + // Convert to ms, values will be: 15, 20 + timeIncrementCeil /= 10000; + // Round up, values will be: 16, 21 + timeIncrementCeil += 1; + // Convert back to 100ns, values will be: 160000, 210000 + timeIncrementCeil *= 10000; + + // How many milli-ticks has the interval rounded up + LONGLONG ticksPerGetTickCountResolutionCeiling = + (int64_t(timeIncrementCeil) * sFrequencyPerSec) / 10000LL; + + // GTC may jump by 32 (2*16) ms in two steps, therefor use the ceiling value. + sGTCResolutionThreshold = + LONGLONG(kGTCTickLeapTolerance * ticksPerGetTickCountResolutionCeiling); + + sHardFailureLimit = ms2mt(kHardFailureLimit); + sFailureFreeInterval = ms2mt(kFailureFreeInterval); + sFailureThreshold = ms2mt(kFailureThreshold); +} + +static void InitResolution() { + // 10 total trials is arbitrary: what we're trying to avoid by + // looping is getting unlucky and being interrupted by a context + // switch or signal, or being bitten by paging/cache effects + + ULONGLONG minres = ~0ULL; + if (sUseQPC) { + int loops = 10; + do { + ULONGLONG start = PerformanceCounter(); + ULONGLONG end = PerformanceCounter(); + + ULONGLONG candidate = (end - start); + if (candidate < minres) { + minres = candidate; + } + } while (--loops && minres); + + if (0 == minres) { + minres = 1; + } + } else { + // GetTickCount has only ~16ms known resolution + minres = ms2mt(16); + } + + // Converting minres that is in [mt] to nanosecods, multiplicating + // the argument to preserve resolution. + ULONGLONG result = mt2ms(minres * kNsPerMillisec); + if (0 == result) { + result = 1; + } + + sResolution = result; + + // find the number of significant digits in mResolution, for the + // sake of ToSecondsSigDigits() + ULONGLONG sigDigs; + for (sigDigs = 1; !(sigDigs == result || 10 * sigDigs > result); + sigDigs *= 10) + ; + + sResolutionSigDigs = sigDigs; +} + +// ---------------------------------------------------------------------------- +// TimeStampValue implementation +// ---------------------------------------------------------------------------- +MFBT_API TimeStampValue& TimeStampValue::operator+=(const int64_t aOther) { + mGTC += aOther; + mQPC += aOther; + return *this; +} + +MFBT_API TimeStampValue& TimeStampValue::operator-=(const int64_t aOther) { + mGTC -= aOther; + mQPC -= aOther; + return *this; +} + +// If the duration is less then two seconds, perform check of QPC stability +// by comparing both GTC and QPC calculated durations of this and aOther. +MFBT_API uint64_t TimeStampValue::CheckQPC(const TimeStampValue& aOther) const { + uint64_t deltaGTC = mGTC - aOther.mGTC; + + if (!mHasQPC || !aOther.mHasQPC) { // Both not holding QPC + return deltaGTC; + } + + uint64_t deltaQPC = mQPC - aOther.mQPC; + + if (sHasStableTSC) { // For stable TSC there is no need to check + return deltaQPC; + } + + // Check QPC is sane before using it. + int64_t diff = DeprecatedAbs(int64_t(deltaQPC) - int64_t(deltaGTC)); + if (diff <= sGTCResolutionThreshold) { + return deltaQPC; + } + + // Treat absolutely for calibration purposes + int64_t duration = DeprecatedAbs(int64_t(deltaGTC)); + int64_t overflow = diff - sGTCResolutionThreshold; + + LOG(("TimeStamp: QPC check after %llums with overflow %1.4fms", + mt2ms(duration), mt2ms_f(overflow))); + + if (overflow <= sFailureThreshold) { // We are in the limit, let go. + return deltaQPC; + } + + // QPC deviates, don't use it, since now this method may only return deltaGTC. + + if (!sUseQPC) { // QPC already disabled, no need to run the fault tolerance + // algorithm. + return deltaGTC; + } + + LOG(("TimeStamp: QPC jittered over failure threshold")); + + if (duration < sHardFailureLimit) { + // Interval between the two time stamps is very short, consider + // QPC as unstable and record a failure. + uint64_t now = ms2mt(GetTickCount64()); + + AutoCriticalSection lock(&sTimeStampLock); + + if (sFaultIntoleranceCheckpoint && sFaultIntoleranceCheckpoint > now) { + // There's already been an error in the last fault intollerant interval. + // Time since now to the checkpoint actually holds information on how many + // failures there were in the failure free interval we have defined. + uint64_t failureCount = + (sFaultIntoleranceCheckpoint - now + sFailureFreeInterval - 1) / + sFailureFreeInterval; + if (failureCount > kMaxFailuresPerInterval) { + sUseQPC = false; + LOG(("TimeStamp: QPC disabled")); + } else { + // Move the fault intolerance checkpoint more to the future, prolong it + // to reflect the number of detected failures. + ++failureCount; + sFaultIntoleranceCheckpoint = now + failureCount * sFailureFreeInterval; + LOG(("TimeStamp: recording %dth QPC failure", failureCount)); + } + } else { + // Setup fault intolerance checkpoint in the future for first detected + // error. + sFaultIntoleranceCheckpoint = now + sFailureFreeInterval; + LOG(("TimeStamp: recording 1st QPC failure")); + } + } + + return deltaGTC; +} + +MFBT_API uint64_t +TimeStampValue::operator-(const TimeStampValue& aOther) const { + if (IsNull() && aOther.IsNull()) { + return uint64_t(0); + } + + return CheckQPC(aOther); +} + +class TimeStampValueTests { + // Check that nullity is set/not set correctly. + static_assert(TimeStampValue{0}.IsNull()); + static_assert(!TimeStampValue{1}.IsNull()); + + // Check that we ignore GTC when both TimeStampValues have QPC. (In each of + // these tests, looking at GTC would give a different result.) + static_assert(TimeStampValue{1, 2, true} < TimeStampValue{1, 3, true}); + static_assert(!(TimeStampValue{1, 2, true} == TimeStampValue{1, 3, true})); + + static_assert(TimeStampValue{2, 2, true} < TimeStampValue{1, 3, true}); + static_assert(TimeStampValue{2, 2, true} <= TimeStampValue{1, 3, true}); + static_assert(!(TimeStampValue{2, 2, true} > TimeStampValue{1, 3, true})); + + static_assert(TimeStampValue{1, 3, true} > TimeStampValue{1, 2, true}); + static_assert(!(TimeStampValue{1, 3, true} == TimeStampValue{1, 2, true})); + + static_assert(TimeStampValue{1, 3, true} > TimeStampValue{2, 2, true}); + static_assert(TimeStampValue{1, 3, true} >= TimeStampValue{2, 2, true}); + static_assert(!(TimeStampValue{1, 3, true} < TimeStampValue{2, 2, true})); + + static_assert(TimeStampValue{1, 3, true} == TimeStampValue{2, 3, true}); + static_assert(!(TimeStampValue{1, 3, true} < TimeStampValue{2, 3, true})); + + static_assert(TimeStampValue{1, 2, true} != TimeStampValue{1, 3, true}); + static_assert(!(TimeStampValue{1, 2, true} == TimeStampValue{1, 3, true})); + + // Check that, if either TimeStampValue doesn't have QPC, we only look at the + // GTC values. These are the same cases as above, except that we accept the + // opposite results because we turn off QPC on one or both of the + // TimeStampValue's. + static_assert(TimeStampValue{1, 2, false} == TimeStampValue{1, 3, true}); + static_assert(TimeStampValue{1, 2, true} == TimeStampValue{1, 3, false}); + static_assert(TimeStampValue{1, 2, false} == TimeStampValue{1, 3, false}); + + static_assert(TimeStampValue{2, 2, false} > TimeStampValue{1, 3, true}); + static_assert(TimeStampValue{2, 2, true} > TimeStampValue{1, 3, false}); + static_assert(TimeStampValue{2, 2, false} > TimeStampValue{1, 3, false}); + + static_assert(TimeStampValue{1, 3, false} == TimeStampValue{1, 2, true}); + static_assert(TimeStampValue{1, 3, true} == TimeStampValue{1, 2, false}); + static_assert(TimeStampValue{1, 3, false} == TimeStampValue{1, 2, false}); + + static_assert(TimeStampValue{1, 3, false} < TimeStampValue{2, 2, true}); + static_assert(TimeStampValue{1, 3, true} < TimeStampValue{2, 2, false}); + static_assert(TimeStampValue{1, 3, false} < TimeStampValue{2, 2, false}); + + static_assert(TimeStampValue{1, 3, false} < TimeStampValue{2, 3, true}); + static_assert(TimeStampValue{1, 3, true} < TimeStampValue{2, 3, false}); + static_assert(TimeStampValue{1, 3, false} < TimeStampValue{2, 3, false}); + + static_assert(TimeStampValue{1, 2, false} == TimeStampValue{1, 3, true}); + static_assert(TimeStampValue{1, 2, true} == TimeStampValue{1, 3, false}); + static_assert(TimeStampValue{1, 2, false} == TimeStampValue{1, 3, false}); +}; + +// ---------------------------------------------------------------------------- +// TimeDuration and TimeStamp implementation +// ---------------------------------------------------------------------------- + +MFBT_API double BaseTimeDurationPlatformUtils::ToSeconds(int64_t aTicks) { + // Converting before arithmetic avoids blocked store forward + return double(aTicks) / (double(sFrequencyPerSec) * 1000.0); +} + +MFBT_API double BaseTimeDurationPlatformUtils::ToSecondsSigDigits( + int64_t aTicks) { + // don't report a value < mResolution ... + LONGLONG resolution = sResolution; + LONGLONG resolutionSigDigs = sResolutionSigDigs; + LONGLONG valueSigDigs = resolution * (aTicks / resolution); + // and chop off insignificant digits + valueSigDigs = resolutionSigDigs * (valueSigDigs / resolutionSigDigs); + return double(valueSigDigs) / kNsPerSecd; +} + +MFBT_API int64_t +BaseTimeDurationPlatformUtils::TicksFromMilliseconds(double aMilliseconds) { + double result = ms2mt(aMilliseconds); + if (result > double(INT64_MAX)) { + return INT64_MAX; + } else if (result < double(INT64_MIN)) { + return INT64_MIN; + } + + return result; +} + +MFBT_API int64_t BaseTimeDurationPlatformUtils::ResolutionInTicks() { + return static_cast<int64_t>(sResolution); +} + +static bool HasStableTSC() { +#if defined(_M_ARM64) + // AArch64 defines that its system counter run at a constant rate + // regardless of the current clock frequency of the system. See "The + // Generic Timer", section D7, in the ARMARM for ARMv8. + return true; +#else + union { + int regs[4]; + struct { + int nIds; + char cpuString[12]; + }; + } cpuInfo; + + __cpuid(cpuInfo.regs, 0); + // Only allow Intel or AMD CPUs for now. + // The order of the registers is reg[1], reg[3], reg[2]. We just adjust the + // string so that we can compare in one go. + if (_strnicmp(cpuInfo.cpuString, "GenuntelineI", sizeof(cpuInfo.cpuString)) && + _strnicmp(cpuInfo.cpuString, "AuthcAMDenti", sizeof(cpuInfo.cpuString))) { + return false; + } + + int regs[4]; + + // detect if the Advanced Power Management feature is supported + __cpuid(regs, 0x80000000); + if ((unsigned int)regs[0] < 0x80000007) { + // XXX should we return true here? If there is no APM there may be + // no way how TSC can run out of sync among cores. + return false; + } + + __cpuid(regs, 0x80000007); + // if bit 8 is set than TSC will run at a constant rate + // in all ACPI P-states, C-states and T-states + return regs[3] & (1 << 8); +#endif +} + +static bool gInitialized = false; + +MFBT_API void TimeStamp::Startup() { + if (gInitialized) { + return; + } + + gInitialized = true; + + // Decide which implementation to use for the high-performance timer. + + InitializeCriticalSectionAndSpinCount(&sTimeStampLock, kLockSpinCount); + + bool forceGTC = false; + bool forceQPC = false; + + char* modevar = getenv("MOZ_TIMESTAMP_MODE"); + if (modevar) { + if (!strcmp(modevar, "QPC")) { + forceQPC = true; + } else if (!strcmp(modevar, "GTC")) { + forceGTC = true; + } + } + + LARGE_INTEGER freq; + sUseQPC = !forceGTC && ::QueryPerformanceFrequency(&freq); + if (!sUseQPC) { + // No Performance Counter. Fall back to use GetTickCount64. + InitResolution(); + + LOG(("TimeStamp: using GetTickCount64")); + return; + } + + sHasStableTSC = forceQPC || HasStableTSC(); + LOG(("TimeStamp: HasStableTSC=%d", sHasStableTSC)); + + sFrequencyPerSec = freq.QuadPart; + LOG(("TimeStamp: QPC frequency=%llu", sFrequencyPerSec)); + + InitThresholds(); + InitResolution(); + + return; +} + +MFBT_API void TimeStamp::Shutdown() { DeleteCriticalSection(&sTimeStampLock); } + +TimeStampValue NowInternal(bool aHighResolution) { + // sUseQPC is volatile + bool useQPC = (aHighResolution && sUseQPC); + + // Both values are in [mt] units. + ULONGLONG QPC = useQPC ? PerformanceCounter() : uint64_t(0); + ULONGLONG GTC = ms2mt(GetTickCount64()); + return TimeStampValue(GTC, QPC, useQPC); +} + +MFBT_API TimeStamp TimeStamp::Now(bool aHighResolution) { + return TimeStamp(NowInternal(aHighResolution)); +} + +// Computes and returns the process uptime in microseconds. +// Returns 0 if an error was encountered. + +MFBT_API uint64_t TimeStamp::ComputeProcessUptime() { + FILETIME start, foo, bar, baz; + bool success = GetProcessTimes(GetCurrentProcess(), &start, &foo, &bar, &baz); + if (!success) { + return 0; + } + + static const StaticDynamicallyLinkedFunctionPtr<void(WINAPI*)(LPFILETIME)> + pGetSystemTimePreciseAsFileTime(L"kernel32.dll", + "GetSystemTimePreciseAsFileTime"); + + FILETIME now; + if (pGetSystemTimePreciseAsFileTime) { + pGetSystemTimePreciseAsFileTime(&now); + } else { + GetSystemTimeAsFileTime(&now); + } + + ULARGE_INTEGER startUsec = {{start.dwLowDateTime, start.dwHighDateTime}}; + ULARGE_INTEGER nowUsec = {{now.dwLowDateTime, now.dwHighDateTime}}; + + return (nowUsec.QuadPart - startUsec.QuadPart) / 10ULL; +} + +} // namespace mozilla diff --git a/mozglue/misc/TimeStamp_windows.h b/mozglue/misc/TimeStamp_windows.h new file mode 100644 index 0000000000..97264bf769 --- /dev/null +++ b/mozglue/misc/TimeStamp_windows.h @@ -0,0 +1,113 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_TimeStamp_windows_h +#define mozilla_TimeStamp_windows_h + +#include "mozilla/Types.h" + +namespace mozilla { + +/** + * The [mt] unit: + * + * Many values are kept in ticks of the Performance Counter x 1000, + * further just referred as [mt], meaning milli-ticks. + * + * This is needed to preserve maximum precision of the performance frequency + * representation. GetTickCount64 values in milliseconds are multiplied with + * frequency per second. Therefore we need to multiply QPC value by 1000 to + * have the same units to allow simple arithmentic with both QPC and GTC. + */ +#define ms2mt(x) ((x) * mozilla::GetQueryPerformanceFrequencyPerSec()) +#define mt2ms(x) ((x) / mozilla::GetQueryPerformanceFrequencyPerSec()) +#define mt2ms_f(x) (double(x) / mozilla::GetQueryPerformanceFrequencyPerSec()) + +MFBT_API uint64_t GetQueryPerformanceFrequencyPerSec(); + +class TimeStamp; +class TimeStampValue; +class TimeStampValueTests; +class TimeStampTests; + +TimeStampValue NowInternal(bool aHighResolution); + +class TimeStampValue { + friend TimeStampValue NowInternal(bool); + friend bool IsCanonicalTimeStamp(TimeStampValue); + friend struct IPC::ParamTraits<mozilla::TimeStampValue>; + friend class TimeStamp; + friend class TimeStampValueTests; + friend class TimeStampTests; + + // Both QPC and GTC are kept in [mt] units. + uint64_t mGTC; + uint64_t mQPC; + + bool mIsNull; + bool mHasQPC; + + constexpr MFBT_API TimeStampValue(uint64_t aGTC, uint64_t aQPC, bool aHasQPC) + : mGTC(aGTC), + mQPC(aQPC), + mIsNull(aGTC == 0 && aQPC == 0), + mHasQPC(aHasQPC) {} + + // This constructor should be explicit but it is replacing a constructor that + // was MOZ_IMPLICIT and there are many locations that are using the automatic + // conversion. + constexpr MOZ_IMPLICIT MFBT_API TimeStampValue(uint64_t aGTCAndQPC) + : TimeStampValue(aGTCAndQPC, aGTCAndQPC, true) {} + + MFBT_API uint64_t CheckQPC(const TimeStampValue& aOther) const; + + public: + MFBT_API uint64_t operator-(const TimeStampValue& aOther) const; + + TimeStampValue operator+(const int64_t aOther) const { + return TimeStampValue(mGTC + aOther, mQPC + aOther, mHasQPC); + } + TimeStampValue operator-(const int64_t aOther) const { + return TimeStampValue(mGTC - aOther, mQPC - aOther, mHasQPC); + } + MFBT_API TimeStampValue& operator+=(const int64_t aOther); + MFBT_API TimeStampValue& operator-=(const int64_t aOther); + + constexpr bool operator<(const TimeStampValue& aOther) const { + return mHasQPC && aOther.mHasQPC ? mQPC < aOther.mQPC : mGTC < aOther.mGTC; + } + constexpr bool operator>(const TimeStampValue& aOther) const { + return mHasQPC && aOther.mHasQPC ? mQPC > aOther.mQPC : mGTC > aOther.mGTC; + } + constexpr bool operator<=(const TimeStampValue& aOther) const { + return mHasQPC && aOther.mHasQPC ? mQPC <= aOther.mQPC + : mGTC <= aOther.mGTC; + } + constexpr bool operator>=(const TimeStampValue& aOther) const { + return mHasQPC && aOther.mHasQPC ? mQPC >= aOther.mQPC + : mGTC >= aOther.mGTC; + } + constexpr bool operator==(const TimeStampValue& aOther) const { + return mHasQPC && aOther.mHasQPC ? mQPC == aOther.mQPC + : mGTC == aOther.mGTC; + } + constexpr bool operator!=(const TimeStampValue& aOther) const { + return mHasQPC && aOther.mHasQPC ? mQPC != aOther.mQPC + : mGTC != aOther.mGTC; + } + constexpr bool IsNull() const { return mIsNull; } + +#if defined(DEBUG) + uint64_t GTC() const { return mGTC; } + uint64_t QPC() const { return mQPC; } + + bool HasQPC() const { return mHasQPC; } +#endif +}; + +} // namespace mozilla + +#endif /* mozilla_TimeStamp_h */ diff --git a/mozglue/misc/Uptime.cpp b/mozglue/misc/Uptime.cpp new file mode 100644 index 0000000000..924b154359 --- /dev/null +++ b/mozglue/misc/Uptime.cpp @@ -0,0 +1,155 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Uptime.h" + +#ifdef XP_WIN +# include "mozilla/DynamicallyLinkedFunctionPtr.h" +#endif // XP_WIN + +#include <stdint.h> + +#include "mozilla/TimeStamp.h" +#include "mozilla/Maybe.h" +#include "mozilla/Assertions.h" + +using namespace mozilla; + +namespace { + +Maybe<uint64_t> NowIncludingSuspendMs(); +Maybe<uint64_t> NowExcludingSuspendMs(); +static Maybe<uint64_t> mStartExcludingSuspendMs; +static Maybe<uint64_t> mStartIncludingSuspendMs; + +// Apple things +#if defined(__APPLE__) && defined(__MACH__) +# include <time.h> +# include <sys/time.h> +# include <sys/types.h> +# include <mach/mach_time.h> + +const uint64_t kNSperMS = 1000000; + +Maybe<uint64_t> NowExcludingSuspendMs() { + return Some(clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / kNSperMS); +} + +Maybe<uint64_t> NowIncludingSuspendMs() { + return Some(clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW) / kNSperMS); +} + +#elif defined(XP_WIN) + +// Number of hundreds of nanoseconds in a millisecond +static constexpr uint64_t kHNSperMS = 10000; + +Maybe<uint64_t> NowExcludingSuspendMs() { + ULONGLONG interrupt_time; + if (!QueryUnbiasedInterruptTime(&interrupt_time)) { + return Nothing(); + } + return Some(interrupt_time / kHNSperMS); +} + +Maybe<uint64_t> NowIncludingSuspendMs() { + static const mozilla::StaticDynamicallyLinkedFunctionPtr<void(WINAPI*)( + PULONGLONG)> + pQueryInterruptTime(L"KernelBase.dll", "QueryInterruptTime"); + if (!pQueryInterruptTime) { + // On Windows, this does include the time the computer was suspended so it's + // an adequate fallback. + TimeStamp processCreation = TimeStamp::ProcessCreation(); + TimeStamp now = TimeStamp::Now(); + if (!processCreation.IsNull() && !now.IsNull()) { + return Some(uint64_t((now - processCreation).ToMilliseconds())); + } else { + return Nothing(); + } + } + ULONGLONG interrupt_time; + pQueryInterruptTime(&interrupt_time); + return Some(interrupt_time / kHNSperMS); +} + +#elif defined(XP_UNIX) // including BSDs and Android +# include <time.h> + +// Number of nanoseconds in a millisecond. +static constexpr uint64_t kNSperMS = 1000000; + +uint64_t TimespecToMilliseconds(struct timespec aTs) { + return aTs.tv_sec * 1000 + aTs.tv_nsec / kNSperMS; +} + +Maybe<uint64_t> NowExcludingSuspendMs() { + struct timespec ts = {0}; + +# ifdef XP_OPENBSD + if (clock_gettime(CLOCK_UPTIME, &ts)) { +# else + if (clock_gettime(CLOCK_MONOTONIC, &ts)) { +# endif + return Nothing(); + } + return Some(TimespecToMilliseconds(ts)); +} + +Maybe<uint64_t> NowIncludingSuspendMs() { +# ifndef CLOCK_BOOTTIME + return Nothing(); +# else + struct timespec ts = {0}; + + if (clock_gettime(CLOCK_BOOTTIME, &ts)) { + return Nothing(); + } + return Some(TimespecToMilliseconds(ts)); +# endif +} + +#else // catch all + +Maybe<uint64_t> NowExcludingSuspendMs() { return Nothing(); } +Maybe<uint64_t> NowIncludingSuspendMs() { return Nothing(); } + +#endif + +}; // anonymous namespace + +namespace mozilla { + +void InitializeUptime() { + MOZ_RELEASE_ASSERT(mStartIncludingSuspendMs.isNothing() && + mStartExcludingSuspendMs.isNothing(), + "Must not be called more than once"); + mStartIncludingSuspendMs = NowIncludingSuspendMs(); + mStartExcludingSuspendMs = NowExcludingSuspendMs(); +} + +Maybe<uint64_t> ProcessUptimeMs() { + if (!mStartIncludingSuspendMs) { + return Nothing(); + } + Maybe<uint64_t> maybeNow = NowIncludingSuspendMs(); + if (!maybeNow) { + return Nothing(); + } + return Some(maybeNow.value() - mStartIncludingSuspendMs.value()); +} + +Maybe<uint64_t> ProcessUptimeExcludingSuspendMs() { + if (!mStartExcludingSuspendMs) { + return Nothing(); + } + Maybe<uint64_t> maybeNow = NowExcludingSuspendMs(); + if (!maybeNow) { + return Nothing(); + } + return Some(maybeNow.value() - mStartExcludingSuspendMs.value()); +} + +}; // namespace mozilla diff --git a/mozglue/misc/Uptime.h b/mozglue/misc/Uptime.h new file mode 100644 index 0000000000..4438e0d6d1 --- /dev/null +++ b/mozglue/misc/Uptime.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_Uptime_h +#define mozilla_Uptime_h + +#include <stdint.h> + +#include "mozilla/Maybe.h" + +namespace mozilla { + +// Called at the beginning of the process from TimeStamp::Startup. +MFBT_API void InitializeUptime(); +// Returns the number of milliseconds the calling process has lived for. +MFBT_API Maybe<uint64_t> ProcessUptimeMs(); +// Returns the number of milliseconds the calling process has lived for, +// excluding the time period the system was suspended. +MFBT_API Maybe<uint64_t> ProcessUptimeExcludingSuspendMs(); + +}; // namespace mozilla + +#endif // mozilla_Uptime_h diff --git a/mozglue/misc/WinUtils.h b/mozglue/misc/WinUtils.h new file mode 100644 index 0000000000..2291a352a5 --- /dev/null +++ b/mozglue/misc/WinUtils.h @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_glue_MozglueUtils_h +#define mozilla_glue_MozglueUtils_h + +#include <windows.h> + +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" + +namespace mozilla { +namespace glue { + +#ifdef DEBUG + +class MOZ_STATIC_CLASS Win32SRWLock final { + public: + // Microsoft guarantees that '0' is never a valid thread id + // https://docs.microsoft.com/en-ca/windows/desktop/ProcThread/thread-handles-and-identifiers + static const DWORD kInvalidThreadId = 0; + + constexpr Win32SRWLock() + : mExclusiveThreadId(kInvalidThreadId), mLock(SRWLOCK_INIT) {} + + ~Win32SRWLock() { MOZ_ASSERT(mExclusiveThreadId == kInvalidThreadId); } + + void LockShared() { + MOZ_ASSERT( + mExclusiveThreadId != GetCurrentThreadId(), + "Deadlock detected - A thread attempted to acquire a shared lock on " + "a SRWLOCK when it already owns the exclusive lock on it."); + + ::AcquireSRWLockShared(&mLock); + } + + void UnlockShared() { ::ReleaseSRWLockShared(&mLock); } + + void LockExclusive() { + MOZ_ASSERT( + mExclusiveThreadId != GetCurrentThreadId(), + "Deadlock detected - A thread attempted to acquire an exclusive lock " + "on a SRWLOCK when it already owns the exclusive lock on it."); + + ::AcquireSRWLockExclusive(&mLock); + mExclusiveThreadId = GetCurrentThreadId(); + } + + void UnlockExclusive() { + MOZ_ASSERT(mExclusiveThreadId == GetCurrentThreadId()); + + mExclusiveThreadId = kInvalidThreadId; + ::ReleaseSRWLockExclusive(&mLock); + } + + Win32SRWLock(const Win32SRWLock&) = delete; + Win32SRWLock(Win32SRWLock&&) = delete; + Win32SRWLock& operator=(const Win32SRWLock&) = delete; + Win32SRWLock& operator=(Win32SRWLock&&) = delete; + + private: + // "Relaxed" memory ordering is fine. Threads will see other thread IDs + // appear here in some non-deterministic ordering (or not at all) and simply + // ignore them. + // + // But a thread will only read its own ID if it previously wrote it, and a + // single thread doesn't need a memory barrier to read its own write. + + Atomic<DWORD, Relaxed> mExclusiveThreadId; + SRWLOCK mLock; +}; + +#else // DEBUG + +class MOZ_STATIC_CLASS Win32SRWLock final { + public: + constexpr Win32SRWLock() : mLock(SRWLOCK_INIT) {} + + void LockShared() { ::AcquireSRWLockShared(&mLock); } + + void UnlockShared() { ::ReleaseSRWLockShared(&mLock); } + + void LockExclusive() { ::AcquireSRWLockExclusive(&mLock); } + + void UnlockExclusive() { ::ReleaseSRWLockExclusive(&mLock); } + + ~Win32SRWLock() = default; + + Win32SRWLock(const Win32SRWLock&) = delete; + Win32SRWLock(Win32SRWLock&&) = delete; + Win32SRWLock& operator=(const Win32SRWLock&) = delete; + Win32SRWLock& operator=(Win32SRWLock&&) = delete; + + private: + SRWLOCK mLock; +}; + +#endif + +class MOZ_RAII AutoSharedLock final { + public: + explicit AutoSharedLock(Win32SRWLock& aLock) : mLock(aLock) { + mLock.LockShared(); + } + + ~AutoSharedLock() { mLock.UnlockShared(); } + + AutoSharedLock(const AutoSharedLock&) = delete; + AutoSharedLock(AutoSharedLock&&) = delete; + AutoSharedLock& operator=(const AutoSharedLock&) = delete; + AutoSharedLock& operator=(AutoSharedLock&&) = delete; + + private: + Win32SRWLock& mLock; +}; + +class MOZ_RAII AutoExclusiveLock final { + public: + explicit AutoExclusiveLock(Win32SRWLock& aLock) : mLock(aLock) { + mLock.LockExclusive(); + } + + ~AutoExclusiveLock() { mLock.UnlockExclusive(); } + + AutoExclusiveLock(const AutoExclusiveLock&) = delete; + AutoExclusiveLock(AutoExclusiveLock&&) = delete; + AutoExclusiveLock& operator=(const AutoExclusiveLock&) = delete; + AutoExclusiveLock& operator=(AutoExclusiveLock&&) = delete; + + private: + Win32SRWLock& mLock; +}; + +} // namespace glue +} // namespace mozilla + +#endif // mozilla_glue_MozglueUtils_h diff --git a/mozglue/misc/WindowsDllMain.cpp b/mozglue/misc/WindowsDllMain.cpp new file mode 100644 index 0000000000..1baec678db --- /dev/null +++ b/mozglue/misc/WindowsDllMain.cpp @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <libloaderapi.h> + +BOOL WINAPI DllMain(HINSTANCE aInstDll, DWORD aReason, LPVOID) { + if (aReason == DLL_PROCESS_ATTACH) { + ::DisableThreadLibraryCalls(aInstDll); + + // mozglue.dll imports RtlGenRandom from advapi32.dll as SystemFunction036, + // but the actual function is implemented in cryptbase.dll. To avoid + // loading a fake cryptbase.dll from the installation directory, we preload + // cryptbase.dll from the system directory. + ::LoadLibraryExW(L"cryptbase.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + } + return TRUE; +} diff --git a/mozglue/misc/WindowsDpiAwareness.h b/mozglue/misc/WindowsDpiAwareness.h new file mode 100644 index 0000000000..589415da6d --- /dev/null +++ b/mozglue/misc/WindowsDpiAwareness.h @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef WindowsDpiAwareness_h_ +#define WindowsDpiAwareness_h_ + +#include <windows.h> + +#if !defined(DPI_AWARENESS_CONTEXT_DECLARED) && \ + !defined(DPI_AWARENESS_CONTEXT_UNAWARE) + +DECLARE_HANDLE(DPI_AWARENESS_CONTEXT); + +typedef enum DPI_AWARENESS { + DPI_AWARENESS_INVALID = -1, + DPI_AWARENESS_UNAWARE = 0, + DPI_AWARENESS_SYSTEM_AWARE = 1, + DPI_AWARENESS_PER_MONITOR_AWARE = 2 +} DPI_AWARENESS; + +# define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT)-1) +# define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT)-2) +# define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT)-3) + +# define DPI_AWARENESS_CONTEXT_DECLARED +#endif // (DPI_AWARENESS_CONTEXT_DECLARED) + +typedef DPI_AWARENESS_CONTEXT(WINAPI* SetThreadDpiAwarenessContextProc)( + DPI_AWARENESS_CONTEXT); +typedef BOOL(WINAPI* EnableNonClientDpiScalingProc)(HWND); +typedef int(WINAPI* GetSystemMetricsForDpiProc)(int, UINT); + +#endif diff --git a/mozglue/misc/WindowsDpiInitialization.cpp b/mozglue/misc/WindowsDpiInitialization.cpp new file mode 100644 index 0000000000..972d577651 --- /dev/null +++ b/mozglue/misc/WindowsDpiInitialization.cpp @@ -0,0 +1,67 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/WindowsDpiInitialization.h" + +#include "mozilla/DynamicallyLinkedFunctionPtr.h" +#include "mozilla/WindowsProcessMitigations.h" +#include "mozilla/WindowsVersion.h" + +#include <shellscalingapi.h> +#include <windows.h> + +namespace mozilla { + +typedef HRESULT(WINAPI* SetProcessDpiAwarenessType)(PROCESS_DPI_AWARENESS); +typedef BOOL(WINAPI* SetProcessDpiAwarenessContextType)(DPI_AWARENESS_CONTEXT); + +WindowsDpiInitializationResult WindowsDpiInitialization() { + // DPI Awareness can't be used in a Win32k Lockdown process, so there's + // nothing to do + if (IsWin32kLockedDown()) { + return WindowsDpiInitializationResult::Success; + } + + // From MSDN: + // SetProcessDpiAwarenessContext() was added in the Win10 Anniversary Update + // SetProcessDpiAwareness() was added in Windows 8.1 + // SetProcessDpiAware() was added in Windows Vista + // + // DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 wasn't added later until + // the Creators Update, so if it fails we just fall back to + // DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE + if (IsWin10AnniversaryUpdateOrLater()) { + DynamicallyLinkedFunctionPtr<SetProcessDpiAwarenessContextType> + setProcessDpiAwarenessContext(L"user32.dll", + "SetProcessDpiAwarenessContext"); + if (!setProcessDpiAwarenessContext) { + return WindowsDpiInitializationResult:: + FindSetProcessDpiAwarenessContextFailed; + } + + if (!setProcessDpiAwarenessContext( + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) && + !setProcessDpiAwarenessContext( + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) { + return WindowsDpiInitializationResult:: + SetProcessDpiAwarenessContextFailed; + } + + return WindowsDpiInitializationResult::Success; + } else { + DynamicallyLinkedFunctionPtr<SetProcessDpiAwarenessType> + setProcessDpiAwareness(L"Shcore.dll", "SetProcessDpiAwareness"); + if (!setProcessDpiAwareness) { + return WindowsDpiInitializationResult::FindSetProcessDpiAwarenessFailed; + } + + if (FAILED(setProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE))) { + return WindowsDpiInitializationResult::SetProcessDpiAwarenessFailed; + } + + return WindowsDpiInitializationResult::Success; + } +} + +} // namespace mozilla diff --git a/mozglue/misc/WindowsDpiInitialization.h b/mozglue/misc/WindowsDpiInitialization.h new file mode 100644 index 0000000000..7379bd53e1 --- /dev/null +++ b/mozglue/misc/WindowsDpiInitialization.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_MOZGLUE_MISC_WINDOWSDPIINITIALIZATION_H_ +#define MOZILLA_MOZGLUE_MISC_WINDOWSDPIINITIALIZATION_H_ +#include "mozilla/Types.h" + +namespace mozilla { + +// The result codes that may be returned from WindowsDpiInitialization() +enum class WindowsDpiInitializationResult : uint32_t { + Success, + FindSetProcessDpiAwarenessContextFailed, + SetProcessDpiAwarenessContextFailed, + FindSetProcessDpiAwarenessFailed, + SetProcessDpiAwarenessFailed, +}; + +// Get a string representation of any WindowsDpiInitializationResult value +inline const char* WindowsDpiInitializationResultString( + WindowsDpiInitializationResult result) { + switch (result) { + case WindowsDpiInitializationResult::Success: + return "Success"; + case WindowsDpiInitializationResult:: + FindSetProcessDpiAwarenessContextFailed: + return "Failed to find SetProcessDpiAwarenessContext"; + case WindowsDpiInitializationResult::SetProcessDpiAwarenessContextFailed: + return "SetProcessDpiAwarenessContext failed"; + case WindowsDpiInitializationResult::FindSetProcessDpiAwarenessFailed: + return "Failed to find SetProcessDpiAwareness"; + case WindowsDpiInitializationResult::SetProcessDpiAwarenessFailed: + return "SetProcessDpiAwareness failed"; + default: + return "Unknown result"; + } +} + +// Initialize DPI awareness to the best available for the current OS +// According to MSDN, this will be: +// Per-Monitor V2 for Windows 10 Creators Update (1703) and later +// Per-Monitor V1 for Windows 8.1 and later +// System DPI for Vista and later (we don't support anything older) +// https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows +MFBT_API WindowsDpiInitializationResult WindowsDpiInitialization(); + +} // namespace mozilla + +#endif // MOZILLA_MOZGLUE_MISC_WINDOWSDPIINITIALIZATION_H_ diff --git a/mozglue/misc/WindowsEnumProcessModules.h b/mozglue/misc/WindowsEnumProcessModules.h new file mode 100644 index 0000000000..573b0dbdfa --- /dev/null +++ b/mozglue/misc/WindowsEnumProcessModules.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_WindowsEnumProcessModules_h +#define mozilla_WindowsEnumProcessModules_h + +#include <windows.h> +#include <psapi.h> + +#include "mozilla/FunctionRef.h" +#include "mozilla/NativeNt.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/WinHeaderOnlyUtils.h" + +namespace mozilla { + +// Why don't we use CreateToolhelp32Snapshot instead of EnumProcessModules? +// CreateToolhelp32Snapshot gets the ANSI versions of module path strings +// via ntdll!RtlQueryProcessDebugInformation and stores them into a snapshot. +// Module32FirstW/Module32NextW re-converts ANSI into Unicode, but it cannot +// restore lost information. This means we still need GetModuleFileNameEx +// even when we use CreateToolhelp32Snapshot, but EnumProcessModules is faster. +inline bool EnumerateProcessModules( + const FunctionRef<void(const wchar_t*, HMODULE)>& aCallback) { + DWORD modulesSize; + if (!::EnumProcessModules(nt::kCurrentProcess, nullptr, 0, &modulesSize)) { + return false; + } + + DWORD modulesNum = modulesSize / sizeof(HMODULE); + UniquePtr<HMODULE[]> modules = MakeUnique<HMODULE[]>(modulesNum); + if (!::EnumProcessModules(nt::kCurrentProcess, modules.get(), + modulesNum * sizeof(HMODULE), &modulesSize)) { + return false; + } + + // The list may have shrunk between calls + if (modulesSize / sizeof(HMODULE) < modulesNum) { + modulesNum = modulesSize / sizeof(HMODULE); + } + + for (DWORD i = 0; i < modulesNum; ++i) { + UniquePtr<wchar_t[]> modulePath = GetFullModulePath(modules[i]); + if (!modulePath) { + continue; + } + + // Please note that modules[i] could be invalid if the module + // was unloaded after GetFullModulePath succeeded. + aCallback(modulePath.get(), modules[i]); + } + + return true; +} + +} // namespace mozilla + +#endif // mozilla_WindowsEnumProcessModules_h diff --git a/mozglue/misc/WindowsMapRemoteView.cpp b/mozglue/misc/WindowsMapRemoteView.cpp new file mode 100644 index 0000000000..aca9979ab6 --- /dev/null +++ b/mozglue/misc/WindowsMapRemoteView.cpp @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "mozilla/WindowsMapRemoteView.h" + +#include "mozilla/Assertions.h" +#include "mozilla/DynamicallyLinkedFunctionPtr.h" + +#include <winternl.h> + +#if (NTDDI_VERSION < NTDDI_WIN10_RS2) + +// MapViewOfFile2 is just an inline function that calls MapViewOfFileNuma2 with +// its preferred node set to NUMA_NO_PREFERRED_NODE +WINBASEAPI PVOID WINAPI MapViewOfFileNuma2(HANDLE aFileMapping, HANDLE aProcess, + ULONG64 aOffset, PVOID aBaseAddress, + SIZE_T aViewSize, + ULONG aAllocationType, + ULONG aPageProtection, + ULONG aPreferredNode); + +WINBASEAPI BOOL WINAPI UnmapViewOfFile2(HANDLE aProcess, PVOID aBaseAddress, + ULONG aUnmapFlags); + +#endif // (NTDDI_VERSION < NTDDI_WIN10_RS2) + +enum SECTION_INHERIT { ViewShare = 1, ViewUnmap = 2 }; + +NTSTATUS NTAPI NtMapViewOfSection( + HANDLE aSection, HANDLE aProcess, PVOID* aBaseAddress, ULONG_PTR aZeroBits, + SIZE_T aCommitSize, PLARGE_INTEGER aSectionOffset, PSIZE_T aViewSize, + SECTION_INHERIT aInheritDisposition, ULONG aAllocationType, + ULONG aProtectionFlags); + +NTSTATUS NTAPI NtUnmapViewOfSection(HANDLE aProcess, PVOID aBaseAddress); + +static DWORD GetWin32ErrorCode(NTSTATUS aNtStatus) { + static const mozilla::StaticDynamicallyLinkedFunctionPtr< + decltype(&RtlNtStatusToDosError)> + pRtlNtStatusToDosError(L"ntdll.dll", "RtlNtStatusToDosError"); + + MOZ_ASSERT(!!pRtlNtStatusToDosError); + if (!pRtlNtStatusToDosError) { + return ERROR_GEN_FAILURE; + } + + return pRtlNtStatusToDosError(aNtStatus); +} + +namespace mozilla { + +MFBT_API void* MapRemoteViewOfFile(HANDLE aFileMapping, HANDLE aProcess, + ULONG64 aOffset, PVOID aBaseAddress, + SIZE_T aViewSize, ULONG aAllocationType, + ULONG aProtectionFlags) { + static const StaticDynamicallyLinkedFunctionPtr<decltype(&MapViewOfFileNuma2)> + pMapViewOfFileNuma2(L"Api-ms-win-core-memory-l1-1-5.dll", + "MapViewOfFileNuma2"); + + if (!!pMapViewOfFileNuma2) { + return pMapViewOfFileNuma2(aFileMapping, aProcess, aOffset, aBaseAddress, + aViewSize, aAllocationType, aProtectionFlags, + NUMA_NO_PREFERRED_NODE); + } + + static const StaticDynamicallyLinkedFunctionPtr<decltype(&NtMapViewOfSection)> + pNtMapViewOfSection(L"ntdll.dll", "NtMapViewOfSection"); + + MOZ_ASSERT(!!pNtMapViewOfSection); + if (!pNtMapViewOfSection) { + return nullptr; + } + + // For the sake of consistency, we only permit the same flags that + // MapViewOfFileNuma2 allows + if (aAllocationType != 0 && aAllocationType != MEM_RESERVE && + aAllocationType != MEM_LARGE_PAGES) { + ::SetLastError(ERROR_INVALID_PARAMETER); + return nullptr; + } + + NTSTATUS ntStatus; + + LARGE_INTEGER offset; + offset.QuadPart = aOffset; + + ntStatus = pNtMapViewOfSection(aFileMapping, aProcess, &aBaseAddress, 0, 0, + &offset, &aViewSize, ViewUnmap, + aAllocationType, aProtectionFlags); + if (NT_SUCCESS(ntStatus)) { + ::SetLastError(ERROR_SUCCESS); + return aBaseAddress; + } + + ::SetLastError(GetWin32ErrorCode(ntStatus)); + return nullptr; +} + +MFBT_API bool UnmapRemoteViewOfFile(HANDLE aProcess, PVOID aBaseAddress) { + static const StaticDynamicallyLinkedFunctionPtr<decltype(&UnmapViewOfFile2)> + pUnmapViewOfFile2(L"kernel32.dll", "UnmapViewOfFile2"); + + if (!!pUnmapViewOfFile2) { + return !!pUnmapViewOfFile2(aProcess, aBaseAddress, 0); + } + + static const StaticDynamicallyLinkedFunctionPtr< + decltype(&NtUnmapViewOfSection)> + pNtUnmapViewOfSection(L"ntdll.dll", "NtUnmapViewOfSection"); + + MOZ_ASSERT(!!pNtUnmapViewOfSection); + if (!pNtUnmapViewOfSection) { + return false; + } + + NTSTATUS ntStatus = pNtUnmapViewOfSection(aProcess, aBaseAddress); + ::SetLastError(GetWin32ErrorCode(ntStatus)); + return NT_SUCCESS(ntStatus); +} + +} // namespace mozilla diff --git a/mozglue/misc/WindowsMapRemoteView.h b/mozglue/misc/WindowsMapRemoteView.h new file mode 100644 index 0000000000..6ab88074b5 --- /dev/null +++ b/mozglue/misc/WindowsMapRemoteView.h @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_WindowsMapRemoteView_h +#define mozilla_WindowsMapRemoteView_h + +#include "mozilla/Types.h" + +#include <windows.h> + +namespace mozilla { + +MFBT_API PVOID MapRemoteViewOfFile(HANDLE aFileMapping, HANDLE aProcess, + ULONG64 aOffset, PVOID aBaseAddress, + SIZE_T aViewSize, ULONG aAllocationType, + ULONG aProtectionFlags); + +MFBT_API bool UnmapRemoteViewOfFile(HANDLE aProcess, PVOID aBaseAddress); + +} // namespace mozilla + +#endif // mozilla_WindowsMapRemoteView_h diff --git a/mozglue/misc/WindowsProcessMitigations.cpp b/mozglue/misc/WindowsProcessMitigations.cpp new file mode 100644 index 0000000000..c439253533 --- /dev/null +++ b/mozglue/misc/WindowsProcessMitigations.cpp @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/WindowsProcessMitigations.h" + +#include <processthreadsapi.h> + +#include "mozilla/Assertions.h" +#include "mozilla/DynamicallyLinkedFunctionPtr.h" + +static_assert(sizeof(PROCESS_MITIGATION_DYNAMIC_CODE_POLICY) == 4); + +namespace mozilla { + +static decltype(&::GetProcessMitigationPolicy) +FetchGetProcessMitigationPolicyFunc() { + static const StaticDynamicallyLinkedFunctionPtr< + decltype(&::GetProcessMitigationPolicy)> + pGetProcessMitigationPolicy(L"kernel32.dll", + "GetProcessMitigationPolicy"); + return pGetProcessMitigationPolicy; +} + +static bool sWin32kLockedDownInPolicy = false; + +MFBT_API bool IsWin32kLockedDown() { + static bool sWin32kLockedDown = []() { + auto pGetProcessMitigationPolicy = FetchGetProcessMitigationPolicyFunc(); + + PROCESS_MITIGATION_SYSTEM_CALL_DISABLE_POLICY polInfo; + if (!pGetProcessMitigationPolicy || + !pGetProcessMitigationPolicy(::GetCurrentProcess(), + ProcessSystemCallDisablePolicy, &polInfo, + sizeof(polInfo))) { + // We failed to get pointer to GetProcessMitigationPolicy or the call + // to it failed, so just return what the sandbox policy says. + return sWin32kLockedDownInPolicy; + } + + return !!polInfo.DisallowWin32kSystemCalls; + }(); + + return sWin32kLockedDown; +} + +MFBT_API void SetWin32kLockedDownInPolicy() { + sWin32kLockedDownInPolicy = true; +} + +MFBT_API bool IsDynamicCodeDisabled() { + auto pGetProcessMitigationPolicy = FetchGetProcessMitigationPolicyFunc(); + if (!pGetProcessMitigationPolicy) { + return false; + } + + PROCESS_MITIGATION_DYNAMIC_CODE_POLICY polInfo; + if (!pGetProcessMitigationPolicy(::GetCurrentProcess(), + ProcessDynamicCodePolicy, &polInfo, + sizeof(polInfo))) { + return false; + } + + return polInfo.ProhibitDynamicCode; +} + +MFBT_API bool IsEafPlusEnabled() { + auto pGetProcessMitigationPolicy = FetchGetProcessMitigationPolicyFunc(); + if (!pGetProcessMitigationPolicy) { + return false; + } + + PROCESS_MITIGATION_PAYLOAD_RESTRICTION_POLICY polInfo; + if (!pGetProcessMitigationPolicy(::GetCurrentProcess(), + ProcessPayloadRestrictionPolicy, &polInfo, + sizeof(polInfo))) { + return false; + } + + return polInfo.EnableExportAddressFilterPlus; +} + +MFBT_API bool IsUserShadowStackEnabled() { + auto pGetProcessMitigationPolicy = FetchGetProcessMitigationPolicyFunc(); + if (!pGetProcessMitigationPolicy) { + return false; + } + + PROCESS_MITIGATION_USER_SHADOW_STACK_POLICY polInfo; + if (!pGetProcessMitigationPolicy(::GetCurrentProcess(), + ProcessUserShadowStackPolicy, &polInfo, + sizeof(polInfo))) { + return false; + } + + return polInfo.EnableUserShadowStack; +} + +} // namespace mozilla diff --git a/mozglue/misc/WindowsProcessMitigations.h b/mozglue/misc/WindowsProcessMitigations.h new file mode 100644 index 0000000000..95d27d2c3f --- /dev/null +++ b/mozglue/misc/WindowsProcessMitigations.h @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_WindowsProcessMitigations_h +#define mozilla_WindowsProcessMitigations_h + +#include "mozilla/Types.h" + +namespace mozilla { + +MFBT_API bool IsWin32kLockedDown(); +MFBT_API void SetWin32kLockedDownInPolicy(); +MFBT_API bool IsDynamicCodeDisabled(); +MFBT_API bool IsEafPlusEnabled(); +MFBT_API bool IsUserShadowStackEnabled(); + +} // namespace mozilla + +#endif // mozilla_WindowsProcessMitigations_h diff --git a/mozglue/misc/WindowsStackCookie.h b/mozglue/misc/WindowsStackCookie.h new file mode 100644 index 0000000000..c4196b3853 --- /dev/null +++ b/mozglue/misc/WindowsStackCookie.h @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_WindowsStackCookie_h +#define mozilla_WindowsStackCookie_h + +#if defined(DEBUG) && defined(_M_X64) && !defined(__MINGW64__) + +# include <windows.h> +# include <winnt.h> + +# include <cstdint> + +# include "mozilla/Types.h" + +namespace mozilla { + +// This function does pattern matching on the instructions generated for a +// given function, to detect whether it uses stack buffers. More specifically, +// it looks for instructions that characterize the presence of stack cookie +// checks. When this function returns true, it can be a false positive, but we +// use a rather long pattern to make false positives very unlikely. +// Note: Do not use this function inside the function that lives at +// aFunctionAddress, as that could introduce stack buffers. +// Note: The pattern we use does not work for MinGW builds. +inline bool HasStackCookieCheck(uintptr_t aFunctionAddress) { + DWORD64 imageBase{}; + auto entry = ::RtlLookupFunctionEntry( + reinterpret_cast<DWORD64>(aFunctionAddress), &imageBase, nullptr); + if (entry && entry->EndAddress > entry->BeginAddress + 14) { + auto begin = reinterpret_cast<uint8_t*>(imageBase + entry->BeginAddress); + auto end = reinterpret_cast<uint8_t*>(imageBase + entry->EndAddress - 14); + for (auto pc = begin; pc != end; ++pc) { + // 48 8b 05 XX XX XX XX: mov rax, qword ptr [rip + XXXXXXXX] + if ((pc[0] == 0x48 && pc[1] == 0x8b && pc[2] == 0x05) && + // 48 31 e0: xor rax, rsp + (pc[7] == 0x48 && pc[8] == 0x31 && pc[9] == 0xe0) && + // 48 89 (8|4)4 24 ...: mov qword ptr [rsp + ...], rax + (pc[10] == 0x48 && pc[11] == 0x89 && + (pc[12] == 0x44 || pc[12] == 0x84) && pc[13] == 0x24)) { + return true; + } + } + } + // In x64, if there is no entry, then there is no stack allocation, hence + // there is no stack cookie check: "Table-based exception handling requires a + // table entry for all functions that allocate stack space or call another + // function (for example, nonleaf functions)." + // https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64 + // Similarly, if the gap between begin and end is less than 14 bytes, then + // the function cannot contain the pattern we are looking for, therefore it + // has no cookie check either. + return false; +} + +} // namespace mozilla + +#endif // defined(DEBUG) && defined(_M_X64) && !defined(__MINGW64__) + +#endif // mozilla_WindowsStackCookie_h diff --git a/mozglue/misc/WindowsUnicode.cpp b/mozglue/misc/WindowsUnicode.cpp new file mode 100644 index 0000000000..464380b6da --- /dev/null +++ b/mozglue/misc/WindowsUnicode.cpp @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "WindowsUnicode.h" + +#include <windows.h> +// For UNICODE_STRING +#include <winternl.h> + +#include <string.h> + +namespace mozilla { +namespace glue { + +mozilla::UniquePtr<char[]> WideToUTF8(const wchar_t* aStr, + const size_t aStrLenExclNul) { + int numConv = ::WideCharToMultiByte(CP_UTF8, 0, aStr, aStrLenExclNul, nullptr, + 0, nullptr, nullptr); + if (!numConv) { + return nullptr; + } + + // Include room for the null terminator by adding one + auto buf = mozilla::MakeUnique<char[]>(numConv + 1); + + numConv = ::WideCharToMultiByte(CP_UTF8, 0, aStr, aStrLenExclNul, buf.get(), + numConv, nullptr, nullptr); + if (!numConv) { + return nullptr; + } + + // Add null termination. numConv does not include the terminator, so we don't + // subtract 1 when indexing into buf. + buf[numConv] = 0; + + return buf; +} + +mozilla::UniquePtr<char[]> WideToUTF8(const wchar_t* aStr) { + return WideToUTF8(aStr, wcslen(aStr)); +} + +mozilla::UniquePtr<char[]> WideToUTF8(const std::wstring& aStr) { + return WideToUTF8(aStr.data(), aStr.length()); +} + +mozilla::UniquePtr<char[]> WideToUTF8(PCUNICODE_STRING aStr) { + if (!aStr) { + return nullptr; + } + + return WideToUTF8(aStr->Buffer, aStr->Length / sizeof(WCHAR)); +} + +} // namespace glue +} // namespace mozilla diff --git a/mozglue/misc/WindowsUnicode.h b/mozglue/misc/WindowsUnicode.h new file mode 100644 index 0000000000..77fc376b92 --- /dev/null +++ b/mozglue/misc/WindowsUnicode.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_glue_WindowsUnicode_h +#define mozilla_glue_WindowsUnicode_h + +#include "mozilla/UniquePtr.h" + +#include <string> + +struct _UNICODE_STRING; + +namespace mozilla { +namespace glue { + +mozilla::UniquePtr<char[]> WideToUTF8(const wchar_t* aStr, + const size_t aStrLenExclNul); + +mozilla::UniquePtr<char[]> WideToUTF8(const wchar_t* aStr); +mozilla::UniquePtr<char[]> WideToUTF8(const std::wstring& aStr); +mozilla::UniquePtr<char[]> WideToUTF8(const _UNICODE_STRING* aStr); + +#if defined(bstr_t) +inline mozilla::UniquePtr<char[]> WideToUTF8(const _bstr_t& aStr) { + return WideToUTF8(static_cast<const wchar_t*>(aStr), aStr.length()); +} +#endif // defined(bstr_t) + +} // namespace glue +} // namespace mozilla + +#endif // mozilla_glue_WindowsUnicode_h diff --git a/mozglue/misc/WindowsUnwindInfo.h b/mozglue/misc/WindowsUnwindInfo.h new file mode 100644 index 0000000000..3667b5f2bc --- /dev/null +++ b/mozglue/misc/WindowsUnwindInfo.h @@ -0,0 +1,329 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_WindowsUnwindInfo_h +#define mozilla_WindowsUnwindInfo_h + +#ifdef _M_X64 + +# include <cstdint> + +# include "mozilla/Assertions.h" +# include "mozilla/UniquePtr.h" + +namespace mozilla { + +// On Windows x64, there is no standard function prologue, hence extra +// information that describes the prologue must be added for each non-leaf +// function in order to properly unwind the stack. This extra information is +// grouped into so-called function tables. +// +// A function table is a contiguous array of one or more RUNTIME_FUNCTION +// entries. Each RUNTIME_FUNCTION entry associates a start and end offset in +// code with specific unwind information. The function table is present in the +// .pdata section of binaries for static code, and added dynamically with +// RtlAddFunctionTable or RtlInstallFunctionTableCallback for dynamic code. +// RUNTIME_FUNCTION entries point to the unwind information, which can thus +// live at a different location in memory, for example it lives in the .xdata +// section for static code. +// +// Contrary to RUNTIME_FUNCTION, Microsoft provides no standard structure +// definition to map the unwind information. This file thus provides some +// helpers to read this data, originally based on breakpad code. The unwind +// information is partially documented at: +// https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64. + +// The unwind information stores a bytecode in UnwindInfo.unwind_code[] that +// describes how the instructions in the function prologue interact with the +// stack. An instruction in this bytecode is called an unwind code. +// UnwindCodeOperationCodes enumerates all opcodes used by this bytecode. +// Unwind codes are stored in contiguous slots of 16 bits, where each unwind +// code can span either 1, 2, or 3 slots depending on the opcode it uses. +enum UnwindOperationCodes : uint8_t { + // UnwindCode.operation_info == register number + UWOP_PUSH_NONVOL = 0, + // UnwindCode.operation_info == 0 or 1, + // alloc size in next slot (if 0) or next 2 slots (if 1) + UWOP_ALLOC_LARGE = 1, + // UnwindCode.operation_info == size of allocation / 8 - 1 + UWOP_ALLOC_SMALL = 2, + // no UnwindCode.operation_info; register number UnwindInfo.frame_register + // receives (rsp + UnwindInfo.frame_offset*16) + UWOP_SET_FPREG = 3, + // UnwindCode.operation_info == register number, offset in next slot + UWOP_SAVE_NONVOL = 4, + // UnwindCode.operation_info == register number, offset in next 2 slots + UWOP_SAVE_NONVOL_FAR = 5, + // Version 1; undocumented; not meant for x64 + UWOP_SAVE_XMM = 6, + // Version 2; undocumented + UWOP_EPILOG = 6, + // Version 1; undocumented; not meant for x64 + UWOP_SAVE_XMM_FAR = 7, + // Version 2; undocumented + UWOP_SPARE = 7, + // UnwindCode.operation_info == XMM reg number, offset in next slot + UWOP_SAVE_XMM128 = 8, + // UnwindCode.operation_info == XMM reg number, offset in next 2 slots + UWOP_SAVE_XMM128_FAR = 9, + // UnwindCode.operation_info == 0: no error-code, 1: error-code + UWOP_PUSH_MACHFRAME = 10 +}; + +// Strictly speaking, UnwindCode represents a slot -- not a full unwind code. +union UnwindCode { + struct { + uint8_t offset_in_prolog; + UnwindOperationCodes unwind_operation_code : 4; + uint8_t operation_info : 4; + }; + uint16_t frame_offset; +}; + +// UnwindInfo is a variable-sized struct meant for C-style direct access to the +// unwind information. Be careful: +// - prefer using the size() helper method to using sizeof; +// - don't construct objects of this type, cast pointers instead; +// - consider using the IterableUnwindInfo helpers to iterate over unwind +// codes. +struct UnwindInfo { + uint8_t version : 3; + uint8_t flags : 5; // either 0, UNW_FLAG_CHAININFO, or a combination of + // UNW_FLAG_EHANDLER and UNW_FLAG_UHANDLER + uint8_t size_of_prolog; + uint8_t count_of_codes; // contains the length of the unwind_code[] array + uint8_t frame_register : 4; + uint8_t frame_offset : 4; + UnwindCode unwind_code[1]; // variable length + // Note: There is extra data after the variable length array if using flags + // UNW_FLAG_EHANDLER, UNW_FLAG_UHANDLER, or UNW_FLAG_CHAININFO. We + // ignore the extra data at the moment. For more details, see: + // https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64. + // + // When using UNW_FLAG_EHANDLER or UNW_FLAG_UHANDLER, the extra data + // includes handler data of unspecificied size: only the handler knows + // the correct size for this data. This makes it difficult to know the + // size of the full unwind information or to copy it in this particular + // case. + + UnwindInfo(const UnwindInfo&) = delete; + UnwindInfo& operator=(const UnwindInfo&) = delete; + UnwindInfo(UnwindInfo&&) = delete; + UnwindInfo& operator=(UnwindInfo&&) = delete; + ~UnwindInfo() = delete; + + // Size of this structure, including the variable length unwind_code array + // but NOT including the extra data related to flags UNW_FLAG_EHANDLER, + // UNW_FLAG_UHANDLER, and UNW_FLAG_CHAININFO. + // + // The places where we currently use these helpers read unwind information at + // function entry points; as such we expect that they may encounter + // UNW_FLAG_EHANDLER and/or UNW_FLAG_UHANDLER but won't need to use the + // associated extra data, and it is expected that they should not encounter + // UNW_FLAG_CHAININFO. UNW_FLAG_CHAININFO is typically used for code that + // lives separately from the entry point of the function to which it belongs, + // this code then has chained unwind info pointing to the entry point. + inline size_t Size() const { + return offsetof(UnwindInfo, unwind_code) + + count_of_codes * sizeof(UnwindCode); + } + + // Note: We currently do not copy the extra data related to flags + // UNW_FLAG_EHANDLER, UNW_FLAG_UHANDLER, and UNW_FLAG_CHAININFO. + UniquePtr<uint8_t[]> Copy() const { + auto s = Size(); + auto result = MakeUnique<uint8_t[]>(s); + std::memcpy(result.get(), reinterpret_cast<const void*>(this), s); + return result; + } + + // An unwind code spans a number of slots in the unwind_code array that can + // vary from 1 to 3. This method assumes that the index parameter points to + // a slot that is the start of an unwind code. If the unwind code is + // well-formed, it returns true and sets its second parameter to the number + // of slots that the unwind code occupies. + // + // This function returns false if the unwind code is ill-formed, i.e.: + // - either the index points out of bounds; + // - or the opcode is invalid, or unexpected (e.g. UWOP_SAVE_XMM and + // UWOP_SAVE_XMM_FAR in version 1); + // - or using the correct slots count for the opcode would go out of bounds. + bool GetSlotsCountForCodeAt(uint8_t aIndex, uint8_t* aSlotsCount) const { + if (aIndex >= count_of_codes) { + MOZ_ASSERT_UNREACHABLE("The index is out of bounds"); + return false; + } + + const UnwindCode& unwindCode = unwind_code[aIndex]; + uint8_t slotsCount = 0; + + // See https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64 + switch (unwindCode.unwind_operation_code) { + // Start with fixed-size opcodes common to versions 1 and 2 + case UWOP_SAVE_NONVOL_FAR: + case UWOP_SAVE_XMM128_FAR: + slotsCount = 3; + break; + + case UWOP_SAVE_NONVOL: + case UWOP_SAVE_XMM128: + slotsCount = 2; + break; + + case UWOP_PUSH_NONVOL: + case UWOP_ALLOC_SMALL: + case UWOP_SET_FPREG: + case UWOP_PUSH_MACHFRAME: + slotsCount = 1; + break; + + // UWOP_ALLOC_LARGE is the only variable-sized opcode. It is common to + // versions 1 and 2. It is ill-formed if the info is not 0 or 1. + case UWOP_ALLOC_LARGE: + if (unwindCode.operation_info > 1) { + MOZ_ASSERT_UNREACHABLE( + "Operation UWOP_ALLOC_LARGE is used, but operation_info " + "is not 0 or 1"); + return false; + } + slotsCount = 2 + unwindCode.operation_info; + break; + + case UWOP_SPARE: + if (version != 2) { + MOZ_ASSERT_UNREACHABLE( + "Operation code UWOP_SPARE is used, but version is not 2"); + return false; + } + slotsCount = 3; + break; + + case UWOP_EPILOG: + if (version != 2) { + MOZ_ASSERT_UNREACHABLE( + "Operation code UWOP_EPILOG is used, but version is not 2"); + return false; + } + slotsCount = 2; + break; + + default: + MOZ_ASSERT_UNREACHABLE("An unknown operation code is used"); + return false; + } + + // The unwind code is ill-formed if using the correct number of slots for + // the opcode would go out of bounds. + if (count_of_codes - aIndex < slotsCount) { + MOZ_ASSERT_UNREACHABLE( + "A valid operation code is used, but it spans too many slots"); + return false; + } + + *aSlotsCount = slotsCount; + return true; + } +}; + +class IterableUnwindInfo { + class Iterator { + public: + UnwindInfo& Info() { return mInfo; } + + uint8_t Index() const { + MOZ_ASSERT(IsValid()); + return mIndex; + } + + uint8_t SlotsCount() const { + MOZ_ASSERT(IsValid()); + return mSlotsCount; + } + + // An iterator is valid if it points to a well-formed unwind code. + // The end iterator is invalid as it does not point to any unwind code. + // All invalid iterators compare equal, which allows comparison with the + // end iterator to exit loops as soon as an ill-formed unwind code is met. + bool IsValid() const { return mIsValid; } + + bool IsAtEnd() const { return mIndex >= mInfo.count_of_codes; } + + bool operator==(const Iterator& aOther) const { + if (mIsValid != aOther.mIsValid) { + return false; + } + // Comparing two invalid iterators. + if (!mIsValid) { + return true; + } + // Comparing two valid iterators. + return mIndex == aOther.mIndex; + } + + bool operator!=(const Iterator& aOther) const { return !(*this == aOther); } + + Iterator& operator++() { + MOZ_ASSERT(IsValid()); + mIndex += mSlotsCount; + if (mIndex < mInfo.count_of_codes) { + mIsValid = mInfo.GetSlotsCountForCodeAt(mIndex, &mSlotsCount); + MOZ_ASSERT(IsValid()); + } else { + mIsValid = false; + } + return *this; + } + + const UnwindCode& operator*() { + MOZ_ASSERT(IsValid()); + return mInfo.unwind_code[mIndex]; + } + + private: + friend class IterableUnwindInfo; + + Iterator(UnwindInfo& aInfo, uint8_t aIndex, uint8_t aSlotsCount, + bool aIsValid) + : mInfo(aInfo), + mIndex(aIndex), + mSlotsCount(aSlotsCount), + mIsValid(aIsValid){}; + + UnwindInfo& mInfo; + uint8_t mIndex; + uint8_t mSlotsCount; + bool mIsValid; + }; + + public: + explicit IterableUnwindInfo(UnwindInfo& aInfo) + : mBegin(aInfo, 0, 0, false), + mEnd(aInfo, aInfo.count_of_codes, 0, false) { + if (aInfo.count_of_codes) { + mBegin.mIsValid = aInfo.GetSlotsCountForCodeAt(0, &mBegin.mSlotsCount); + MOZ_ASSERT(mBegin.mIsValid); + } + } + + explicit IterableUnwindInfo(uint8_t* aInfo) + : IterableUnwindInfo(*reinterpret_cast<UnwindInfo*>(aInfo)) {} + + UnwindInfo& Info() { return mBegin.Info(); } + + const Iterator& begin() { return mBegin; } + + const Iterator& end() { return mEnd; } + + private: + Iterator mBegin; + Iterator mEnd; +}; + +} // namespace mozilla + +#endif // _M_X64 + +#endif // mozilla_WindowsUnwindInfo_h diff --git a/mozglue/misc/decimal/Decimal.cpp b/mozglue/misc/decimal/Decimal.cpp new file mode 100644 index 0000000000..7d2bcfa712 --- /dev/null +++ b/mozglue/misc/decimal/Decimal.cpp @@ -0,0 +1,1017 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "Decimal.h" +#include "moz-decimal-utils.h" +#include "DoubleConversion.h" + +using namespace moz_decimal_utils; + +#include <algorithm> +#include <float.h> + +namespace blink { + +namespace DecimalPrivate { + +// This class handles Decimal special values. +class SpecialValueHandler { + STACK_ALLOCATED(); + WTF_MAKE_NONCOPYABLE(SpecialValueHandler); +public: + enum HandleResult { + BothFinite, + BothInfinity, + EitherNaN, + LHSIsInfinity, + RHSIsInfinity, + }; + + SpecialValueHandler(const Decimal& lhs, const Decimal& rhs); + HandleResult handle(); + Decimal value() const; + +private: + enum Result { + ResultIsLHS, + ResultIsRHS, + ResultIsUnknown, + }; + + const Decimal& m_lhs; + const Decimal& m_rhs; + Result m_result; +}; + +SpecialValueHandler::SpecialValueHandler(const Decimal& lhs, const Decimal& rhs) + : m_lhs(lhs), m_rhs(rhs), m_result(ResultIsUnknown) +{ +} + +SpecialValueHandler::HandleResult SpecialValueHandler::handle() +{ + if (m_lhs.isFinite() && m_rhs.isFinite()) + return BothFinite; + + const Decimal::EncodedData::FormatClass lhsClass = m_lhs.value().formatClass(); + const Decimal::EncodedData::FormatClass rhsClass = m_rhs.value().formatClass(); + if (lhsClass == Decimal::EncodedData::ClassNaN) { + m_result = ResultIsLHS; + return EitherNaN; + } + + if (rhsClass == Decimal::EncodedData::ClassNaN) { + m_result = ResultIsRHS; + return EitherNaN; + } + + if (lhsClass == Decimal::EncodedData::ClassInfinity) + return rhsClass == Decimal::EncodedData::ClassInfinity ? BothInfinity : LHSIsInfinity; + + if (rhsClass == Decimal::EncodedData::ClassInfinity) + return RHSIsInfinity; + + ASSERT_NOT_REACHED(); + return BothFinite; +} + +Decimal SpecialValueHandler::value() const +{ + switch (m_result) { + case ResultIsLHS: + return m_lhs; + case ResultIsRHS: + return m_rhs; + case ResultIsUnknown: + default: + ASSERT_NOT_REACHED(); + return m_lhs; + } +} + +// This class is used for 128 bit unsigned integer arithmetic. +class UInt128 { +public: + UInt128(uint64_t low, uint64_t high) + : m_high(high), m_low(low) + { + } + + UInt128& operator/=(uint32_t); + + uint64_t high() const { return m_high; } + uint64_t low() const { return m_low; } + + static UInt128 multiply(uint64_t u, uint64_t v) { return UInt128(u * v, multiplyHigh(u, v)); } + +private: + static uint32_t highUInt32(uint64_t x) { return static_cast<uint32_t>(x >> 32); } + static uint32_t lowUInt32(uint64_t x) { return static_cast<uint32_t>(x & ((static_cast<uint64_t>(1) << 32) - 1)); } + static uint64_t makeUInt64(uint32_t low, uint32_t high) { return low | (static_cast<uint64_t>(high) << 32); } + + static uint64_t multiplyHigh(uint64_t, uint64_t); + + uint64_t m_high; + uint64_t m_low; +}; + +UInt128& UInt128::operator/=(const uint32_t divisor) +{ + ASSERT(divisor); + + if (!m_high) { + m_low /= divisor; + return *this; + } + + uint32_t dividend[4]; + dividend[0] = lowUInt32(m_low); + dividend[1] = highUInt32(m_low); + dividend[2] = lowUInt32(m_high); + dividend[3] = highUInt32(m_high); + + uint32_t quotient[4]; + uint32_t remainder = 0; + for (int i = 3; i >= 0; --i) { + const uint64_t work = makeUInt64(dividend[i], remainder); + remainder = static_cast<uint32_t>(work % divisor); + quotient[i] = static_cast<uint32_t>(work / divisor); + } + m_low = makeUInt64(quotient[0], quotient[1]); + m_high = makeUInt64(quotient[2], quotient[3]); + return *this; +} + +// Returns high 64bit of 128bit product. +uint64_t UInt128::multiplyHigh(uint64_t u, uint64_t v) +{ + const uint64_t uLow = lowUInt32(u); + const uint64_t uHigh = highUInt32(u); + const uint64_t vLow = lowUInt32(v); + const uint64_t vHigh = highUInt32(v); + const uint64_t partialProduct = uHigh * vLow + highUInt32(uLow * vLow); + return uHigh * vHigh + highUInt32(partialProduct) + highUInt32(uLow * vHigh + lowUInt32(partialProduct)); +} + +static int countDigits(uint64_t x) +{ + int numberOfDigits = 0; + for (uint64_t powerOfTen = 1; x >= powerOfTen; powerOfTen *= 10) { + ++numberOfDigits; + if (powerOfTen >= std::numeric_limits<uint64_t>::max() / 10) + break; + } + return numberOfDigits; +} + +static uint64_t scaleDown(uint64_t x, int n) +{ + ASSERT(n >= 0); + while (n > 0 && x) { + x /= 10; + --n; + } + return x; +} + +static uint64_t scaleUp(uint64_t x, int n) +{ + ASSERT(n >= 0); + ASSERT(n <= Precision); + + uint64_t y = 1; + uint64_t z = 10; + for (;;) { + if (n & 1) + y = y * z; + + n >>= 1; + if (!n) + return x * y; + + z = z * z; + } +} + +} // namespace DecimalPrivate + +using namespace DecimalPrivate; + +bool Decimal::EncodedData::operator==(const EncodedData& another) const +{ + return m_sign == another.m_sign + && m_formatClass == another.m_formatClass + && m_exponent == another.m_exponent + && m_coefficient == another.m_coefficient; +} + + +Decimal::Decimal(int32_t i32) + : Decimal(DecimalLiteral{i32}) {} + +Decimal::Decimal(Sign sign, int exponent, uint64_t coefficient) + : m_data(sign, coefficient ? exponent : 0, coefficient) {} + +Decimal::Decimal(const EncodedData& data) + : m_data(data) +{ +} + +Decimal::Decimal(const Decimal& other) + : m_data(other.m_data) +{ +} + +Decimal& Decimal::operator=(const Decimal& other) +{ + m_data = other.m_data; + return *this; +} + +Decimal& Decimal::operator+=(const Decimal& other) +{ + m_data = (*this + other).m_data; + return *this; +} + +Decimal& Decimal::operator-=(const Decimal& other) +{ + m_data = (*this - other).m_data; + return *this; +} + +Decimal& Decimal::operator*=(const Decimal& other) +{ + m_data = (*this * other).m_data; + return *this; +} + +Decimal& Decimal::operator/=(const Decimal& other) +{ + m_data = (*this / other).m_data; + return *this; +} + +Decimal Decimal::operator-() const +{ + if (isNaN()) + return *this; + + Decimal result(*this); + result.m_data.setSign(invertSign(m_data.sign())); + return result; +} + +Decimal Decimal::operator+(const Decimal& rhs) const +{ + const Decimal& lhs = *this; + const Sign lhsSign = lhs.sign(); + const Sign rhsSign = rhs.sign(); + + SpecialValueHandler handler(lhs, rhs); + switch (handler.handle()) { + case SpecialValueHandler::BothFinite: + break; + + case SpecialValueHandler::BothInfinity: + return lhsSign == rhsSign ? lhs : nan(); + + case SpecialValueHandler::EitherNaN: + return handler.value(); + + case SpecialValueHandler::LHSIsInfinity: + return lhs; + + case SpecialValueHandler::RHSIsInfinity: + return rhs; + } + + const AlignedOperands alignedOperands = alignOperands(lhs, rhs); + + const uint64_t result = lhsSign == rhsSign + ? alignedOperands.lhsCoefficient + alignedOperands.rhsCoefficient + : alignedOperands.lhsCoefficient - alignedOperands.rhsCoefficient; + + if (lhsSign == Negative && rhsSign == Positive && !result) + return Decimal(Positive, alignedOperands.exponent, 0); + + return static_cast<int64_t>(result) >= 0 + ? Decimal(lhsSign, alignedOperands.exponent, result) + : Decimal(invertSign(lhsSign), alignedOperands.exponent, -static_cast<int64_t>(result)); +} + +Decimal Decimal::operator-(const Decimal& rhs) const +{ + const Decimal& lhs = *this; + const Sign lhsSign = lhs.sign(); + const Sign rhsSign = rhs.sign(); + + SpecialValueHandler handler(lhs, rhs); + switch (handler.handle()) { + case SpecialValueHandler::BothFinite: + break; + + case SpecialValueHandler::BothInfinity: + return lhsSign == rhsSign ? nan() : lhs; + + case SpecialValueHandler::EitherNaN: + return handler.value(); + + case SpecialValueHandler::LHSIsInfinity: + return lhs; + + case SpecialValueHandler::RHSIsInfinity: + return infinity(invertSign(rhsSign)); + } + + const AlignedOperands alignedOperands = alignOperands(lhs, rhs); + + const uint64_t result = lhsSign == rhsSign + ? alignedOperands.lhsCoefficient - alignedOperands.rhsCoefficient + : alignedOperands.lhsCoefficient + alignedOperands.rhsCoefficient; + + if (lhsSign == Negative && rhsSign == Negative && !result) + return Decimal(Positive, alignedOperands.exponent, 0); + + return static_cast<int64_t>(result) >= 0 + ? Decimal(lhsSign, alignedOperands.exponent, result) + : Decimal(invertSign(lhsSign), alignedOperands.exponent, -static_cast<int64_t>(result)); +} + +Decimal Decimal::operator*(const Decimal& rhs) const +{ + const Decimal& lhs = *this; + const Sign lhsSign = lhs.sign(); + const Sign rhsSign = rhs.sign(); + const Sign resultSign = lhsSign == rhsSign ? Positive : Negative; + + SpecialValueHandler handler(lhs, rhs); + switch (handler.handle()) { + case SpecialValueHandler::BothFinite: { + const uint64_t lhsCoefficient = lhs.m_data.coefficient(); + const uint64_t rhsCoefficient = rhs.m_data.coefficient(); + int resultExponent = lhs.exponent() + rhs.exponent(); + UInt128 work(UInt128::multiply(lhsCoefficient, rhsCoefficient)); + while (work.high()) { + work /= 10; + ++resultExponent; + } + return Decimal(resultSign, resultExponent, work.low()); + } + + case SpecialValueHandler::BothInfinity: + return infinity(resultSign); + + case SpecialValueHandler::EitherNaN: + return handler.value(); + + case SpecialValueHandler::LHSIsInfinity: + return rhs.isZero() ? nan() : infinity(resultSign); + + case SpecialValueHandler::RHSIsInfinity: + return lhs.isZero() ? nan() : infinity(resultSign); + } + + ASSERT_NOT_REACHED(); + return nan(); +} + +Decimal Decimal::operator/(const Decimal& rhs) const +{ + const Decimal& lhs = *this; + const Sign lhsSign = lhs.sign(); + const Sign rhsSign = rhs.sign(); + const Sign resultSign = lhsSign == rhsSign ? Positive : Negative; + + SpecialValueHandler handler(lhs, rhs); + switch (handler.handle()) { + case SpecialValueHandler::BothFinite: + break; + + case SpecialValueHandler::BothInfinity: + return nan(); + + case SpecialValueHandler::EitherNaN: + return handler.value(); + + case SpecialValueHandler::LHSIsInfinity: + return infinity(resultSign); + + case SpecialValueHandler::RHSIsInfinity: + return zero(resultSign); + } + + ASSERT(lhs.isFinite()); + ASSERT(rhs.isFinite()); + + if (rhs.isZero()) + return lhs.isZero() ? nan() : infinity(resultSign); + + int resultExponent = lhs.exponent() - rhs.exponent(); + + if (lhs.isZero()) + return Decimal(resultSign, resultExponent, 0); + + uint64_t remainder = lhs.m_data.coefficient(); + const uint64_t divisor = rhs.m_data.coefficient(); + uint64_t result = 0; + for (;;) { + while (remainder < divisor && result < MaxCoefficient / 10) { + remainder *= 10; + result *= 10; + --resultExponent; + } + if (remainder < divisor) + break; + uint64_t quotient = remainder / divisor; + if (result > MaxCoefficient - quotient) + break; + result += quotient; + remainder %= divisor; + if (!remainder) + break; + } + + if (remainder > divisor / 2) + ++result; + + return Decimal(resultSign, resultExponent, result); +} + +bool Decimal::operator==(const Decimal& rhs) const +{ + if (isNaN() || rhs.isNaN()) + return false; + return m_data == rhs.m_data || compareTo(rhs).isZero(); +} + +bool Decimal::operator!=(const Decimal& rhs) const +{ + if (isNaN() || rhs.isNaN()) + return true; + if (m_data == rhs.m_data) + return false; + const Decimal result = compareTo(rhs); + if (result.isNaN()) + return false; + return !result.isZero(); +} + +bool Decimal::operator<(const Decimal& rhs) const +{ + const Decimal result = compareTo(rhs); + if (result.isNaN()) + return false; + return !result.isZero() && result.isNegative(); +} + +bool Decimal::operator<=(const Decimal& rhs) const +{ + if (isNaN() || rhs.isNaN()) + return false; + if (m_data == rhs.m_data) + return true; + const Decimal result = compareTo(rhs); + if (result.isNaN()) + return false; + return result.isZero() || result.isNegative(); +} + +bool Decimal::operator>(const Decimal& rhs) const +{ + const Decimal result = compareTo(rhs); + if (result.isNaN()) + return false; + return !result.isZero() && result.isPositive(); +} + +bool Decimal::operator>=(const Decimal& rhs) const +{ + if (isNaN() || rhs.isNaN()) + return false; + if (m_data == rhs.m_data) + return true; + const Decimal result = compareTo(rhs); + if (result.isNaN()) + return false; + return result.isZero() || !result.isNegative(); +} + +Decimal Decimal::abs() const +{ + Decimal result(*this); + result.m_data.setSign(Positive); + return result; +} + +Decimal::AlignedOperands Decimal::alignOperands(const Decimal& lhs, const Decimal& rhs) +{ + ASSERT(lhs.isFinite()); + ASSERT(rhs.isFinite()); + + const int lhsExponent = lhs.exponent(); + const int rhsExponent = rhs.exponent(); + int exponent = std::min(lhsExponent, rhsExponent); + uint64_t lhsCoefficient = lhs.m_data.coefficient(); + uint64_t rhsCoefficient = rhs.m_data.coefficient(); + + if (lhsExponent > rhsExponent) { + const int numberOfLHSDigits = countDigits(lhsCoefficient); + if (numberOfLHSDigits) { + const int lhsShiftAmount = lhsExponent - rhsExponent; + const int overflow = numberOfLHSDigits + lhsShiftAmount - Precision; + if (overflow <= 0) { + lhsCoefficient = scaleUp(lhsCoefficient, lhsShiftAmount); + } else { + lhsCoefficient = scaleUp(lhsCoefficient, lhsShiftAmount - overflow); + rhsCoefficient = scaleDown(rhsCoefficient, overflow); + exponent += overflow; + } + } + + } else if (lhsExponent < rhsExponent) { + const int numberOfRHSDigits = countDigits(rhsCoefficient); + if (numberOfRHSDigits) { + const int rhsShiftAmount = rhsExponent - lhsExponent; + const int overflow = numberOfRHSDigits + rhsShiftAmount - Precision; + if (overflow <= 0) { + rhsCoefficient = scaleUp(rhsCoefficient, rhsShiftAmount); + } else { + rhsCoefficient = scaleUp(rhsCoefficient, rhsShiftAmount - overflow); + lhsCoefficient = scaleDown(lhsCoefficient, overflow); + exponent += overflow; + } + } + } + + AlignedOperands alignedOperands; + alignedOperands.exponent = exponent; + alignedOperands.lhsCoefficient = lhsCoefficient; + alignedOperands.rhsCoefficient = rhsCoefficient; + return alignedOperands; +} + +static bool isMultiplePowersOfTen(uint64_t coefficient, int n) +{ + return !coefficient || !(coefficient % scaleUp(1, n)); +} + +// Round toward positive infinity. +Decimal Decimal::ceil() const +{ + if (isSpecial()) + return *this; + + if (exponent() >= 0) + return *this; + + uint64_t result = m_data.coefficient(); + const int numberOfDigits = countDigits(result); + const int numberOfDropDigits = -exponent(); + if (numberOfDigits <= numberOfDropDigits) + return isPositive() ? Decimal(1) : zero(Positive); + + result = scaleDown(result, numberOfDropDigits); + if (isPositive() && !isMultiplePowersOfTen(m_data.coefficient(), numberOfDropDigits)) + ++result; + return Decimal(sign(), 0, result); +} + +Decimal Decimal::compareTo(const Decimal& rhs) const +{ + const Decimal result(*this - rhs); + switch (result.m_data.formatClass()) { + case EncodedData::ClassInfinity: + return result.isNegative() ? Decimal(-1) : Decimal(1); + + case EncodedData::ClassNaN: + case EncodedData::ClassNormal: + return result; + + case EncodedData::ClassZero: + return zero(Positive); + + default: + ASSERT_NOT_REACHED(); + return nan(); + } +} + +// Round toward negative infinity. +Decimal Decimal::floor() const +{ + if (isSpecial()) + return *this; + + if (exponent() >= 0) + return *this; + + uint64_t result = m_data.coefficient(); + const int numberOfDigits = countDigits(result); + const int numberOfDropDigits = -exponent(); + if (numberOfDigits < numberOfDropDigits) + return isPositive() ? zero(Positive) : Decimal(-1); + + result = scaleDown(result, numberOfDropDigits); + if (isNegative() && !isMultiplePowersOfTen(m_data.coefficient(), numberOfDropDigits)) + ++result; + return Decimal(sign(), 0, result); +} + +Decimal Decimal::fromDouble(double doubleValue) +{ + if (std::isfinite(doubleValue)) + return fromString(mozToString(doubleValue)); + + if (std::isinf(doubleValue)) + return infinity(doubleValue < 0 ? Negative : Positive); + + return nan(); +} + +Decimal Decimal::fromString(const String& str) +{ + int exponent = 0; + Sign exponentSign = Positive; + int numberOfDigits = 0; + int numberOfDigitsAfterDot = 0; + int numberOfExtraDigits = 0; + Sign sign = Positive; + + enum { + StateDigit, + StateDot, + StateDotDigit, + StateE, + StateEDigit, + StateESign, + StateSign, + StateStart, + StateZero, + } state = StateStart; + +#define HandleCharAndBreak(expected, nextState) \ + if (ch == expected) { \ + state = nextState; \ + break; \ + } + +#define HandleTwoCharsAndBreak(expected1, expected2, nextState) \ + if (ch == expected1 || ch == expected2) { \ + state = nextState; \ + break; \ + } + + uint64_t accumulator = 0; + for (unsigned index = 0; index < str.length(); ++index) { + const int ch = str[index]; + switch (state) { + case StateDigit: + if (ch >= '0' && ch <= '9') { + if (numberOfDigits < Precision) { + ++numberOfDigits; + accumulator *= 10; + accumulator += ch - '0'; + } else { + ++numberOfExtraDigits; + } + break; + } + + HandleCharAndBreak('.', StateDot); + HandleTwoCharsAndBreak('E', 'e', StateE); + return nan(); + + case StateDot: + case StateDotDigit: + if (ch >= '0' && ch <= '9') { + if (numberOfDigits < Precision) { + ++numberOfDigits; + ++numberOfDigitsAfterDot; + accumulator *= 10; + accumulator += ch - '0'; + } + state = StateDotDigit; + break; + } + + HandleTwoCharsAndBreak('E', 'e', StateE); + return nan(); + + case StateE: + if (ch == '+') { + exponentSign = Positive; + state = StateESign; + break; + } + + if (ch == '-') { + exponentSign = Negative; + state = StateESign; + break; + } + + if (ch >= '0' && ch <= '9') { + exponent = ch - '0'; + state = StateEDigit; + break; + } + + return nan(); + + case StateEDigit: + if (ch >= '0' && ch <= '9') { + exponent *= 10; + exponent += ch - '0'; + if (exponent > ExponentMax + Precision) { + if (accumulator) + return exponentSign == Negative ? zero(Positive) : infinity(sign); + return zero(sign); + } + state = StateEDigit; + break; + } + + return nan(); + + case StateESign: + if (ch >= '0' && ch <= '9') { + exponent = ch - '0'; + state = StateEDigit; + break; + } + + return nan(); + + case StateSign: + if (ch >= '1' && ch <= '9') { + accumulator = ch - '0'; + numberOfDigits = 1; + state = StateDigit; + break; + } + + HandleCharAndBreak('0', StateZero); + return nan(); + + case StateStart: + if (ch >= '1' && ch <= '9') { + accumulator = ch - '0'; + numberOfDigits = 1; + state = StateDigit; + break; + } + + if (ch == '-') { + sign = Negative; + state = StateSign; + break; + } + + if (ch == '+') { + sign = Positive; + state = StateSign; + break; + } + + HandleCharAndBreak('0', StateZero); + HandleCharAndBreak('.', StateDot); + return nan(); + + case StateZero: + if (ch == '0') + break; + + if (ch >= '1' && ch <= '9') { + accumulator = ch - '0'; + numberOfDigits = 1; + state = StateDigit; + break; + } + + HandleCharAndBreak('.', StateDot); + HandleTwoCharsAndBreak('E', 'e', StateE); + return nan(); + + default: + ASSERT_NOT_REACHED(); + return nan(); + } + } + + if (state == StateZero) + return zero(sign); + + if (state == StateDigit || state == StateEDigit || state == StateDotDigit) { + int resultExponent = exponent * (exponentSign == Negative ? -1 : 1) - numberOfDigitsAfterDot + numberOfExtraDigits; + if (resultExponent < ExponentMin) + return zero(Positive); + + const int overflow = resultExponent - ExponentMax + 1; + if (overflow > 0) { + if (overflow + numberOfDigits - numberOfDigitsAfterDot > Precision) + return infinity(sign); + accumulator = scaleUp(accumulator, overflow); + resultExponent -= overflow; + } + + return Decimal(sign, resultExponent, accumulator); + } + + return nan(); +} + +Decimal Decimal::infinity(const Sign sign) +{ + return Decimal(EncodedData(sign, EncodedData::ClassInfinity)); +} + +Decimal Decimal::nan() +{ + return Decimal(EncodedData(Positive, EncodedData::ClassNaN)); +} + +Decimal Decimal::remainder(const Decimal& rhs) const +{ + const Decimal quotient = *this / rhs; + return quotient.isSpecial() ? quotient : *this - (quotient.isNegative() ? quotient.ceil() : quotient.floor()) * rhs; +} + +Decimal Decimal::round() const +{ + if (isSpecial()) + return *this; + + if (exponent() >= 0) + return *this; + + uint64_t result = m_data.coefficient(); + const int numberOfDigits = countDigits(result); + const int numberOfDropDigits = -exponent(); + if (numberOfDigits < numberOfDropDigits) + return zero(Positive); + + result = scaleDown(result, numberOfDropDigits - 1); + if (result % 10 >= 5) + result += 10; + result /= 10; + return Decimal(sign(), 0, result); +} + +double Decimal::toDouble() const +{ + if (isFinite()) { + bool valid; + const double doubleValue = mozToDouble(toString(), &valid); + return valid ? doubleValue : std::numeric_limits<double>::quiet_NaN(); + } + + if (isInfinity()) + return isNegative() ? -std::numeric_limits<double>::infinity() : std::numeric_limits<double>::infinity(); + + return std::numeric_limits<double>::quiet_NaN(); +} + +String Decimal::toString() const +{ + switch (m_data.formatClass()) { + case EncodedData::ClassInfinity: + return sign() ? "-Infinity" : "Infinity"; + + case EncodedData::ClassNaN: + return "NaN"; + + case EncodedData::ClassNormal: + case EncodedData::ClassZero: + break; + + default: + ASSERT_NOT_REACHED(); + return ""; + } + + StringBuilder builder; + if (sign()) + builder.append('-'); + + int originalExponent = exponent(); + uint64_t coefficient = m_data.coefficient(); + + if (originalExponent < 0) { + const int maxDigits = DBL_DIG; + uint64_t lastDigit = 0; + while (countDigits(coefficient) > maxDigits) { + lastDigit = coefficient % 10; + coefficient /= 10; + ++originalExponent; + } + + if (lastDigit >= 5) + ++coefficient; + + while (originalExponent < 0 && coefficient && !(coefficient % 10)) { + coefficient /= 10; + ++originalExponent; + } + } + + const String digits = mozToString(coefficient); + int coefficientLength = static_cast<int>(digits.length()); + const int adjustedExponent = originalExponent + coefficientLength - 1; + if (originalExponent <= 0 && adjustedExponent >= -6) { + if (!originalExponent) { + builder.append(digits); + return builder.toString(); + } + + if (adjustedExponent >= 0) { + for (int i = 0; i < coefficientLength; ++i) { + builder.append(digits[i]); + if (i == adjustedExponent) + builder.append('.'); + } + return builder.toString(); + } + + builder.appendLiteral("0."); + for (int i = adjustedExponent + 1; i < 0; ++i) + builder.append('0'); + + builder.append(digits); + + } else { + builder.append(digits[0]); + while (coefficientLength >= 2 && digits[coefficientLength - 1] == '0') + --coefficientLength; + if (coefficientLength >= 2) { + builder.append('.'); + for (int i = 1; i < coefficientLength; ++i) + builder.append(digits[i]); + } + + if (adjustedExponent) { + builder.append(adjustedExponent < 0 ? "e" : "e+"); + builder.appendNumber(adjustedExponent); + } + } + return builder.toString(); +} + +bool Decimal::toString(char* strBuf, size_t bufLength) const +{ + ASSERT(bufLength > 0); + String str = toString(); + size_t length = str.copy(strBuf, bufLength); + if (length < bufLength) { + strBuf[length] = '\0'; + return true; + } + strBuf[bufLength - 1] = '\0'; + return false; +} + +Decimal Decimal::zero(Sign sign) +{ + return Decimal(EncodedData(sign, EncodedData::ClassZero)); +} + +} // namespace blink + +// Implementation of DoubleConversion.h: + +namespace mozilla { + +Maybe<double> StringToDouble(Span<const char> aStringSpan) { + bool valid = false; + double result = mozToDouble(aStringSpan, &valid); + return valid ? Some(result) : Nothing(); +} + +} diff --git a/mozglue/misc/decimal/Decimal.h b/mozglue/misc/decimal/Decimal.h new file mode 100644 index 0000000000..4bb9a841e5 --- /dev/null +++ b/mozglue/misc/decimal/Decimal.h @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Imported from: + * https://chromium.googlesource.com/chromium/src.git/+/master/third_party/WebKit/Source/platform/Decimal.h + * Check UPSTREAM-GIT-SHA for the commit ID of the last update from Blink core. + */ + +#ifndef Decimal_h +#define Decimal_h + +#include "mozilla/Assertions.h" +#include <stdint.h> +#include "mozilla/Types.h" + +#include <string> + +#ifndef ASSERT +#define DEFINED_ASSERT_FOR_DECIMAL_H 1 +#define ASSERT MOZ_ASSERT +#endif + +#define PLATFORM_EXPORT + +// To use USING_FAST_MALLOC we'd need: +// https://chromium.googlesource.com/chromium/src.git/+/master/third_party/WebKit/Source/wtf/Allocator.h +// Since we don't allocate Decimal objects, no need. +#define USING_FAST_MALLOC(type) \ + void ignore_this_dummy_method() = delete + +#define DISALLOW_NEW() \ + private: \ + void* operator new(size_t) = delete; \ + void* operator new(size_t, void*) = delete; \ + public: + +namespace blink { + +namespace DecimalPrivate { +constexpr int ExponentMax = 1023; +constexpr int ExponentMin = -1023; +constexpr int Precision = 18; + +static const uint64_t MaxCoefficient = UINT64_C(0xDE0B6B3A763FFFF); // 999999999999999999 == 18 9's +class SpecialValueHandler; +} + +struct DecimalLiteral { + int32_t value; + friend constexpr DecimalLiteral operator*(int32_t lhs, DecimalLiteral rhs) { + return {lhs * rhs.value}; + } + constexpr DecimalLiteral operator-() { + return {-value}; + } +}; + +constexpr DecimalLiteral operator""_d(unsigned long long value) { + return {static_cast<int32_t>(value)}; +} + +// This class represents decimal base floating point number. +// +// FIXME: Once all C++ compiler support decimal type, we should replace this +// class to compiler supported one. See below URI for current status of decimal +// type for C++: // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n1977.html +class PLATFORM_EXPORT Decimal { + USING_FAST_MALLOC(Decimal); +public: + enum Sign { + Positive, + Negative, + }; + + // You should not use EncodedData other than unit testing. + class EncodedData { + DISALLOW_NEW(); + // For accessing FormatClass. + friend class Decimal; + friend class DecimalPrivate::SpecialValueHandler; + public: + constexpr EncodedData(Sign sign, int exponent, uint64_t coefficient) + : m_coefficient(0), + m_exponent(0), + m_formatClass(coefficient ? ClassNormal : ClassZero), + m_sign(sign) { + if (exponent >= DecimalPrivate::ExponentMin && + exponent <= DecimalPrivate::ExponentMax) { + while (coefficient > DecimalPrivate::MaxCoefficient) { + coefficient /= 10; + ++exponent; + } + } + + if (exponent > DecimalPrivate::ExponentMax) { + m_formatClass = ClassInfinity; + return; + } + + if (exponent < DecimalPrivate::ExponentMin) { + m_formatClass = ClassZero; + return; + } + + m_coefficient = coefficient; + m_exponent = static_cast<int16_t>(exponent); + } + + bool operator==(const EncodedData&) const; + bool operator!=(const EncodedData& another) const { return !operator==(another); } + + uint64_t coefficient() const { return m_coefficient; } + int countDigits() const; + int exponent() const { return m_exponent; } + bool isFinite() const { return !isSpecial(); } + bool isInfinity() const { return m_formatClass == ClassInfinity; } + bool isNaN() const { return m_formatClass == ClassNaN; } + bool isSpecial() const { return m_formatClass == ClassInfinity || m_formatClass == ClassNaN; } + bool isZero() const { return m_formatClass == ClassZero; } + Sign sign() const { return m_sign; } + void setSign(Sign sign) { m_sign = sign; } + + private: + enum FormatClass { + ClassInfinity, + ClassNormal, + ClassNaN, + ClassZero, + }; + + constexpr EncodedData(Sign sign, FormatClass formatClass) + : m_coefficient(0), + m_exponent(0), + m_formatClass(formatClass), + m_sign(sign) {} + + FormatClass formatClass() const { return m_formatClass; } + + uint64_t m_coefficient; + int16_t m_exponent; + FormatClass m_formatClass; + Sign m_sign; + }; + + constexpr explicit Decimal(DecimalLiteral i32) + : m_data(i32.value < 0 ? Negative : Positive, 0, + i32.value < 0 ? static_cast<uint64_t>(-static_cast<int64_t>(i32.value)) + : static_cast<uint64_t>(i32.value)) {} + + MFBT_API explicit Decimal(int32_t i32 = 0); + MFBT_API Decimal(Sign sign, int exponent, uint64_t coefficient); + MFBT_API Decimal(const Decimal&); + + MFBT_API Decimal& operator=(const Decimal&); + MFBT_API Decimal& operator+=(const Decimal&); + MFBT_API Decimal& operator-=(const Decimal&); + MFBT_API Decimal& operator*=(const Decimal&); + MFBT_API Decimal& operator/=(const Decimal&); + + MFBT_API Decimal operator-() const; + + MFBT_API bool operator==(const Decimal&) const; + MFBT_API bool operator!=(const Decimal&) const; + MFBT_API bool operator<(const Decimal&) const; + MFBT_API bool operator<=(const Decimal&) const; + MFBT_API bool operator>(const Decimal&) const; + MFBT_API bool operator>=(const Decimal&) const; + + MFBT_API Decimal operator+(const Decimal&) const; + MFBT_API Decimal operator-(const Decimal&) const; + MFBT_API Decimal operator*(const Decimal&) const; + MFBT_API Decimal operator/(const Decimal&) const; + + int exponent() const + { + ASSERT(isFinite()); + return m_data.exponent(); + } + + bool isFinite() const { return m_data.isFinite(); } + bool isInfinity() const { return m_data.isInfinity(); } + bool isNaN() const { return m_data.isNaN(); } + bool isNegative() const { return sign() == Negative; } + bool isPositive() const { return sign() == Positive; } + bool isSpecial() const { return m_data.isSpecial(); } + bool isZero() const { return m_data.isZero(); } + + MFBT_API Decimal abs() const; + MFBT_API Decimal ceil() const; + MFBT_API Decimal floor() const; + MFBT_API Decimal remainder(const Decimal&) const; + MFBT_API Decimal round() const; + + MFBT_API double toDouble() const; + // Note: toString method supports infinity and nan but fromString not. + MFBT_API std::string toString() const; + MFBT_API bool toString(char* strBuf, size_t bufLength) const; + + static MFBT_API Decimal fromDouble(double); + // fromString supports following syntax EBNF: + // number ::= sign? digit+ ('.' digit*) (exponent-marker sign? digit+)? + // | sign? '.' digit+ (exponent-marker sign? digit+)? + // sign ::= '+' | '-' + // exponent-marker ::= 'e' | 'E' + // digit ::= '0' | '1' | ... | '9' + // Note: fromString doesn't support "infinity" and "nan". + static MFBT_API Decimal fromString(const std::string& aValue); + static MFBT_API Decimal infinity(Sign); + static MFBT_API Decimal nan(); + static MFBT_API Decimal zero(Sign); + + // You should not use below methods. We expose them for unit testing. + MFBT_API explicit Decimal(const EncodedData&); + const EncodedData& value() const { return m_data; } + +private: + struct AlignedOperands { + uint64_t lhsCoefficient; + uint64_t rhsCoefficient; + int exponent; + }; + + MFBT_API explicit Decimal(double); + MFBT_API Decimal compareTo(const Decimal&) const; + + static MFBT_API AlignedOperands alignOperands(const Decimal& lhs, const Decimal& rhs); + static inline Sign invertSign(Sign sign) { return sign == Negative ? Positive : Negative; } + + Sign sign() const { return m_data.sign(); } + + EncodedData m_data; +}; + +} // namespace blink + +namespace mozilla { +typedef blink::Decimal Decimal; +using blink::operator""_d; +} // namespace mozilla + +#undef USING_FAST_MALLOC + +#ifdef DEFINED_ASSERT_FOR_DECIMAL_H +#undef DEFINED_ASSERT_FOR_DECIMAL_H +#undef ASSERT +#endif + +#endif // Decimal_h diff --git a/mozglue/misc/decimal/DoubleConversion.h b/mozglue/misc/decimal/DoubleConversion.h new file mode 100644 index 0000000000..14c19e2540 --- /dev/null +++ b/mozglue/misc/decimal/DoubleConversion.h @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* A utility function that converts a string to a double independent of OS locale. */ + +#ifndef MOZILLA_DOUBLECONVERSION_H +#define MOZILLA_DOUBLECONVERSION_H + +#include "mozilla/Maybe.h" +#include "mozilla/Span.h" + +#include <string> + +namespace mozilla { + +// Parses aStringSpan into a double floating point value. Always treats . as the +// decimal separator, regardless of OS locale. Consumes the entire string; +// trailing garbage is invalid. Returns Nothing() for invalid input. +// The implementation uses double_conversion::StringToDoubleConverter with +// NO_FLAGS, see double-conversion/string-to-double.h for more documentation. +Maybe<double> StringToDouble(Span<const char> aStringSpan); + +} + +#endif // MOZILLA_DOUBLECONVERSION_H diff --git a/mozglue/misc/decimal/UPSTREAM-GIT-SHA b/mozglue/misc/decimal/UPSTREAM-GIT-SHA new file mode 100644 index 0000000000..ed86150b28 --- /dev/null +++ b/mozglue/misc/decimal/UPSTREAM-GIT-SHA @@ -0,0 +1 @@ +cad4c9e3b3c9e80bb189059373db528272bca96f diff --git a/mozglue/misc/decimal/add-doubleconversion-impl.patch b/mozglue/misc/decimal/add-doubleconversion-impl.patch new file mode 100644 index 0000000000..1cf0fb6ff1 --- /dev/null +++ b/mozglue/misc/decimal/add-doubleconversion-impl.patch @@ -0,0 +1,42 @@ +diff --git a/mozglue/misc/decimal/Decimal.cpp b/mozglue/misc/decimal/Decimal.cpp +--- a/mozglue/misc/decimal/Decimal.cpp ++++ b/mozglue/misc/decimal/Decimal.cpp +@@ -25,16 +25,17 @@ + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + #include "Decimal.h" + #include "moz-decimal-utils.h" ++#include "DoubleConversion.h" + + using namespace moz_decimal_utils; + + #include <algorithm> + #include <float.h> + + namespace blink { + +@@ -1043,8 +1044,20 @@ bool Decimal::toString(char* strBuf, siz + } + + Decimal Decimal::zero(Sign sign) + { + return Decimal(EncodedData(sign, EncodedData::ClassZero)); + } + + } // namespace blink ++ ++// Implementation of DoubleConversion.h: ++ ++namespace mozilla { ++ ++Maybe<double> StringToDouble(Span<const char> aStringSpan) { ++ bool valid = false; ++ double result = mozToDouble(aStringSpan, &valid); ++ return valid ? Some(result) : Nothing(); ++} ++ ++} diff --git a/mozglue/misc/decimal/comparison-with-nan.patch b/mozglue/misc/decimal/comparison-with-nan.patch new file mode 100644 index 0000000000..0e274ce033 --- /dev/null +++ b/mozglue/misc/decimal/comparison-with-nan.patch @@ -0,0 +1,67 @@ +diff --git a/mozglue/misc/decimal/Decimal.cpp b/mozglue/misc/decimal/Decimal.cpp +--- a/mozglue/misc/decimal/Decimal.cpp ++++ b/mozglue/misc/decimal/Decimal.cpp +@@ -509,21 +509,25 @@ Decimal Decimal::operator/(const Decimal + if (remainder > divisor / 2) + ++result; + + return Decimal(resultSign, resultExponent, result); + } + + bool Decimal::operator==(const Decimal& rhs) const + { ++ if (isNaN() || rhs.isNaN()) ++ return false; + return m_data == rhs.m_data || compareTo(rhs).isZero(); + } + + bool Decimal::operator!=(const Decimal& rhs) const + { ++ if (isNaN() || rhs.isNaN()) ++ return true; + if (m_data == rhs.m_data) + return false; + const Decimal result = compareTo(rhs); + if (result.isNaN()) + return false; + return !result.isZero(); + } + +@@ -532,16 +536,18 @@ bool Decimal::operator<(const Decimal& r + const Decimal result = compareTo(rhs); + if (result.isNaN()) + return false; + return !result.isZero() && result.isNegative(); + } + + bool Decimal::operator<=(const Decimal& rhs) const + { ++ if (isNaN() || rhs.isNaN()) ++ return false; + if (m_data == rhs.m_data) + return true; + const Decimal result = compareTo(rhs); + if (result.isNaN()) + return false; + return result.isZero() || result.isNegative(); + } + +@@ -550,16 +556,18 @@ bool Decimal::operator>(const Decimal& r + const Decimal result = compareTo(rhs); + if (result.isNaN()) + return false; + return !result.isZero() && result.isPositive(); + } + + bool Decimal::operator>=(const Decimal& rhs) const + { ++ if (isNaN() || rhs.isNaN()) ++ return false; + if (m_data == rhs.m_data) + return true; + const Decimal result = compareTo(rhs); + if (result.isNaN()) + return false; + return result.isZero() || !result.isNegative(); + } + diff --git a/mozglue/misc/decimal/fix-wshadow-warnings.patch b/mozglue/misc/decimal/fix-wshadow-warnings.patch new file mode 100644 index 0000000000..465c61a223 --- /dev/null +++ b/mozglue/misc/decimal/fix-wshadow-warnings.patch @@ -0,0 +1,171 @@ +diff --git a/mozglue/misc/decimal/Decimal.cpp b/mozglue/misc/decimal/Decimal.cpp +--- a/mozglue/misc/decimal/Decimal.cpp ++++ b/mozglue/misc/decimal/Decimal.cpp +@@ -118,18 +118,18 @@ Decimal SpecialValueHandler::value() con + ASSERT_NOT_REACHED(); + return m_lhs; + } + } + + // This class is used for 128 bit unsigned integer arithmetic. + class UInt128 { + public: +- UInt128(uint64_t low, uint64_t high) +- : m_high(high), m_low(low) ++ UInt128(uint64_t aLow, uint64_t aHigh) ++ : m_high(aHigh), m_low(aLow) + { + } + + UInt128& operator/=(uint32_t); + + uint64_t high() const { return m_high; } + uint64_t low() const { return m_low; } + +@@ -224,68 +224,68 @@ static uint64_t scaleUp(uint64_t x, int + z = z * z; + } + } + + } // namespace DecimalPrivate + + using namespace DecimalPrivate; + +-Decimal::EncodedData::EncodedData(Sign sign, FormatClass formatClass) ++Decimal::EncodedData::EncodedData(Sign aSign, FormatClass aFormatClass) + : m_coefficient(0) + , m_exponent(0) +- , m_formatClass(formatClass) +- , m_sign(sign) ++ , m_formatClass(aFormatClass) ++ , m_sign(aSign) + { + } + +-Decimal::EncodedData::EncodedData(Sign sign, int exponent, uint64_t coefficient) +- : m_formatClass(coefficient ? ClassNormal : ClassZero) +- , m_sign(sign) ++Decimal::EncodedData::EncodedData(Sign aSign, int aExponent, uint64_t aCoefficient) ++ : m_formatClass(aCoefficient ? ClassNormal : ClassZero) ++ , m_sign(aSign) + { +- if (exponent >= ExponentMin && exponent <= ExponentMax) { +- while (coefficient > MaxCoefficient) { +- coefficient /= 10; +- ++exponent; ++ if (aExponent >= ExponentMin && aExponent <= ExponentMax) { ++ while (aCoefficient > MaxCoefficient) { ++ aCoefficient /= 10; ++ ++aExponent; + } + } + +- if (exponent > ExponentMax) { ++ if (aExponent > ExponentMax) { + m_coefficient = 0; + m_exponent = 0; + m_formatClass = ClassInfinity; + return; + } + +- if (exponent < ExponentMin) { ++ if (aExponent < ExponentMin) { + m_coefficient = 0; + m_exponent = 0; + m_formatClass = ClassZero; + return; + } + +- m_coefficient = coefficient; +- m_exponent = static_cast<int16_t>(exponent); ++ m_coefficient = aCoefficient; ++ m_exponent = static_cast<int16_t>(aExponent); + } + + bool Decimal::EncodedData::operator==(const EncodedData& another) const + { + return m_sign == another.m_sign + && m_formatClass == another.m_formatClass + && m_exponent == another.m_exponent + && m_coefficient == another.m_coefficient; + } + + Decimal::Decimal(int32_t i32) + : m_data(i32 < 0 ? Negative : Positive, 0, i32 < 0 ? static_cast<uint64_t>(-static_cast<int64_t>(i32)) : static_cast<uint64_t>(i32)) + { + } + +-Decimal::Decimal(Sign sign, int exponent, uint64_t coefficient) +- : m_data(sign, coefficient ? exponent : 0, coefficient) ++Decimal::Decimal(Sign aSign, int aExponent, uint64_t aCoefficient) ++ : m_data(aSign, aCoefficient ? aExponent : 0, aCoefficient) + { + } + + Decimal::Decimal(const EncodedData& data) + : m_data(data) + { + } + +@@ -479,32 +479,32 @@ Decimal Decimal::operator/(const Decimal + if (rhs.isZero()) + return lhs.isZero() ? nan() : infinity(resultSign); + + int resultExponent = lhs.exponent() - rhs.exponent(); + + if (lhs.isZero()) + return Decimal(resultSign, resultExponent, 0); + +- uint64_t remainder = lhs.m_data.coefficient(); ++ uint64_t lhsRemainder = lhs.m_data.coefficient(); + const uint64_t divisor = rhs.m_data.coefficient(); + uint64_t result = 0; + while (result < MaxCoefficient / 100) { +- while (remainder < divisor) { +- remainder *= 10; ++ while (lhsRemainder < divisor) { ++ lhsRemainder *= 10; + result *= 10; + --resultExponent; + } +- result += remainder / divisor; +- remainder %= divisor; +- if (!remainder) ++ result += lhsRemainder / divisor; ++ lhsRemainder %= divisor; ++ if (!lhsRemainder) + break; + } + +- if (remainder > divisor / 2) ++ if (lhsRemainder > divisor / 2) + ++result; + + return Decimal(resultSign, resultExponent, result); + } + + bool Decimal::operator==(const Decimal& rhs) const + { + if (isNaN() || rhs.isNaN()) +diff --git a/mozglue/misc/decimal/Decimal.h b/mozglue/misc/decimal/Decimal.h +--- a/mozglue/misc/decimal/Decimal.h ++++ b/mozglue/misc/decimal/Decimal.h +@@ -88,17 +88,17 @@ public: + int countDigits() const; + int exponent() const { return m_exponent; } + bool isFinite() const { return !isSpecial(); } + bool isInfinity() const { return m_formatClass == ClassInfinity; } + bool isNaN() const { return m_formatClass == ClassNaN; } + bool isSpecial() const { return m_formatClass == ClassInfinity || m_formatClass == ClassNaN; } + bool isZero() const { return m_formatClass == ClassZero; } + Sign sign() const { return m_sign; } +- void setSign(Sign sign) { m_sign = sign; } ++ void setSign(Sign aSign) { m_sign = aSign; } + + private: + enum FormatClass { + ClassInfinity, + ClassNormal, + ClassNaN, + ClassZero, + }; diff --git a/mozglue/misc/decimal/mfbt-abi-markers.patch b/mozglue/misc/decimal/mfbt-abi-markers.patch new file mode 100644 index 0000000000..1d50d3d643 --- /dev/null +++ b/mozglue/misc/decimal/mfbt-abi-markers.patch @@ -0,0 +1,150 @@ +diff --git a/mozglue/misc/decimal/Decimal.h b/mozglue/misc/decimal/Decimal.h +--- a/mozglue/misc/decimal/Decimal.h ++++ b/mozglue/misc/decimal/Decimal.h +@@ -26,16 +26,18 @@ + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + #ifndef Decimal_h + #define Decimal_h + ++#include "mozilla/Types.h" ++ + #include "platform/PlatformExport.h" + #include "wtf/Allocator.h" + #include "wtf/Assertions.h" + #include "wtf/text/WTFString.h" + #include <stdint.h> + + namespace blink { + +@@ -91,92 +93,92 @@ public: + FormatClass formatClass() const { return m_formatClass; } + + uint64_t m_coefficient; + int16_t m_exponent; + FormatClass m_formatClass; + Sign m_sign; + }; + +- Decimal(int32_t = 0); +- Decimal(Sign, int exponent, uint64_t coefficient); +- Decimal(const Decimal&); ++ MFBT_API explicit Decimal(int32_t = 0); ++ MFBT_API Decimal(Sign, int exponent, uint64_t coefficient); ++ MFBT_API Decimal(const Decimal&); + +- Decimal& operator=(const Decimal&); +- Decimal& operator+=(const Decimal&); +- Decimal& operator-=(const Decimal&); +- Decimal& operator*=(const Decimal&); +- Decimal& operator/=(const Decimal&); ++ MFBT_API Decimal& operator=(const Decimal&); ++ MFBT_API Decimal& operator+=(const Decimal&); ++ MFBT_API Decimal& operator-=(const Decimal&); ++ MFBT_API Decimal& operator*=(const Decimal&); ++ MFBT_API Decimal& operator/=(const Decimal&); + +- Decimal operator-() const; ++ MFBT_API Decimal operator-() const; + +- bool operator==(const Decimal&) const; +- bool operator!=(const Decimal&) const; +- bool operator<(const Decimal&) const; +- bool operator<=(const Decimal&) const; +- bool operator>(const Decimal&) const; +- bool operator>=(const Decimal&) const; ++ MFBT_API bool operator==(const Decimal&) const; ++ MFBT_API bool operator!=(const Decimal&) const; ++ MFBT_API bool operator<(const Decimal&) const; ++ MFBT_API bool operator<=(const Decimal&) const; ++ MFBT_API bool operator>(const Decimal&) const; ++ MFBT_API bool operator>=(const Decimal&) const; + +- Decimal operator+(const Decimal&) const; +- Decimal operator-(const Decimal&) const; +- Decimal operator*(const Decimal&) const; +- Decimal operator/(const Decimal&) const; ++ MFBT_API Decimal operator+(const Decimal&) const; ++ MFBT_API Decimal operator-(const Decimal&) const; ++ MFBT_API Decimal operator*(const Decimal&) const; ++ MFBT_API Decimal operator/(const Decimal&) const; + + int exponent() const + { + ASSERT(isFinite()); + return m_data.exponent(); + } + + bool isFinite() const { return m_data.isFinite(); } + bool isInfinity() const { return m_data.isInfinity(); } + bool isNaN() const { return m_data.isNaN(); } + bool isNegative() const { return sign() == Negative; } + bool isPositive() const { return sign() == Positive; } + bool isSpecial() const { return m_data.isSpecial(); } + bool isZero() const { return m_data.isZero(); } + +- Decimal abs() const; +- Decimal ceil() const; +- Decimal floor() const; +- Decimal remainder(const Decimal&) const; +- Decimal round() const; ++ MFBT_API Decimal abs() const; ++ MFBT_API Decimal ceil() const; ++ MFBT_API Decimal floor() const; ++ MFBT_API Decimal remainder(const Decimal&) const; ++ MFBT_API Decimal round() const; + +- double toDouble() const; ++ MFBT_API double toDouble() const; + // Note: toString method supports infinity and nan but fromString not. +- String toString() const; ++ MFBT_API String toString() const; + +- static Decimal fromDouble(double); ++ static MFBT_API Decimal fromDouble(double); + // fromString supports following syntax EBNF: + // number ::= sign? digit+ ('.' digit*) (exponent-marker sign? digit+)? + // | sign? '.' digit+ (exponent-marker sign? digit+)? + // sign ::= '+' | '-' + // exponent-marker ::= 'e' | 'E' + // digit ::= '0' | '1' | ... | '9' + // Note: fromString doesn't support "infinity" and "nan". +- static Decimal fromString(const String&); +- static Decimal infinity(Sign); +- static Decimal nan(); +- static Decimal zero(Sign); ++ static MFBT_API Decimal fromString(const String&); ++ static MFBT_API Decimal infinity(Sign); ++ static MFBT_API Decimal nan(); ++ static MFBT_API Decimal zero(Sign); + + // You should not use below methods. We expose them for unit testing. +- explicit Decimal(const EncodedData&); ++ MFBT_API explicit Decimal(const EncodedData&); + const EncodedData& value() const { return m_data; } + + private: + struct AlignedOperands { + uint64_t lhsCoefficient; + uint64_t rhsCoefficient; + int exponent; + }; + +- Decimal(double); +- Decimal compareTo(const Decimal&) const; ++ MFBT_API explicit Decimal(double); ++ MFBT_API Decimal compareTo(const Decimal&) const; + +- static AlignedOperands alignOperands(const Decimal& lhs, const Decimal& rhs); ++ static MFBT_API AlignedOperands alignOperands(const Decimal& lhs, const Decimal& rhs); + static inline Sign invertSign(Sign sign) { return sign == Negative ? Positive : Negative; } + + Sign sign() const { return m_data.sign(); } + + EncodedData m_data; + }; + + } // namespace blink diff --git a/mozglue/misc/decimal/moz-constexpr-decimal.patch b/mozglue/misc/decimal/moz-constexpr-decimal.patch new file mode 100644 index 0000000000..845352bd05 --- /dev/null +++ b/mozglue/misc/decimal/moz-constexpr-decimal.patch @@ -0,0 +1,218 @@ +diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp +index 7fdf38c5cb7c3..8bce5c3312f55 100644 +--- a/dom/html/HTMLInputElement.cpp ++++ b/dom/html/HTMLInputElement.cpp +@@ -187,16 +187,18 @@ static const nsAttrValue::EnumTable kCaptureTable[] = { + + static const nsAttrValue::EnumTable* kCaptureDefault = &kCaptureTable[2]; + +-const Decimal HTMLInputElement::kStepScaleFactorDate = Decimal(86400000); +-const Decimal HTMLInputElement::kStepScaleFactorNumberRange = Decimal(1); +-const Decimal HTMLInputElement::kStepScaleFactorTime = Decimal(1000); +-const Decimal HTMLInputElement::kStepScaleFactorMonth = Decimal(1); +-const Decimal HTMLInputElement::kStepScaleFactorWeek = Decimal(7 * 86400000); +-const Decimal HTMLInputElement::kDefaultStepBase = Decimal(0); +-const Decimal HTMLInputElement::kDefaultStepBaseWeek = Decimal(-259200000); +-const Decimal HTMLInputElement::kDefaultStep = Decimal(1); +-const Decimal HTMLInputElement::kDefaultStepTime = Decimal(60); +-const Decimal HTMLInputElement::kStepAny = Decimal(0); ++using namespace blink; ++ ++constexpr Decimal HTMLInputElement::kStepScaleFactorDate(86400000_d); ++constexpr Decimal HTMLInputElement::kStepScaleFactorNumberRange(1_d); ++constexpr Decimal HTMLInputElement::kStepScaleFactorTime(1000_d); ++constexpr Decimal HTMLInputElement::kStepScaleFactorMonth(1_d); ++constexpr Decimal HTMLInputElement::kStepScaleFactorWeek(7 * 86400000_d); ++constexpr Decimal HTMLInputElement::kDefaultStepBase(0_d); ++constexpr Decimal HTMLInputElement::kDefaultStepBaseWeek(-259200000_d); ++constexpr Decimal HTMLInputElement::kDefaultStep(1_d); ++constexpr Decimal HTMLInputElement::kDefaultStepTime(60_d); ++constexpr Decimal HTMLInputElement::kStepAny(0_d); + + const double HTMLInputElement::kMinimumYear = 1; + const double HTMLInputElement::kMaximumYear = 275760; +diff --git a/mozglue/misc/decimal/Decimal.cpp b/mozglue/misc/decimal/Decimal.cpp +index cc828e28439f5..7d2bcfa712c5d 100644 +--- a/mozglue/misc/decimal/Decimal.cpp ++++ b/mozglue/misc/decimal/Decimal.cpp +@@ -41,12 +41,6 @@ namespace blink { + + namespace DecimalPrivate { + +-static int const ExponentMax = 1023; +-static int const ExponentMin = -1023; +-static int const Precision = 18; +- +-static const uint64_t MaxCoefficient = UINT64_C(0xDE0B6B3A763FFFF); // 999999999999999999 == 18 9's +- + // This class handles Decimal special values. + class SpecialValueHandler { + STACK_ALLOCATED(); +@@ -230,43 +224,6 @@ static uint64_t scaleUp(uint64_t x, int n) + + using namespace DecimalPrivate; + +-Decimal::EncodedData::EncodedData(Sign sign, FormatClass formatClass) +- : m_coefficient(0) +- , m_exponent(0) +- , m_formatClass(formatClass) +- , m_sign(sign) +-{ +-} +- +-Decimal::EncodedData::EncodedData(Sign sign, int exponent, uint64_t coefficient) +- : m_formatClass(coefficient ? ClassNormal : ClassZero) +- , m_sign(sign) +-{ +- if (exponent >= ExponentMin && exponent <= ExponentMax) { +- while (coefficient > MaxCoefficient) { +- coefficient /= 10; +- ++exponent; +- } +- } +- +- if (exponent > ExponentMax) { +- m_coefficient = 0; +- m_exponent = 0; +- m_formatClass = ClassInfinity; +- return; +- } +- +- if (exponent < ExponentMin) { +- m_coefficient = 0; +- m_exponent = 0; +- m_formatClass = ClassZero; +- return; +- } +- +- m_coefficient = coefficient; +- m_exponent = static_cast<int16_t>(exponent); +-} +- + bool Decimal::EncodedData::operator==(const EncodedData& another) const + { + return m_sign == another.m_sign +@@ -275,15 +232,12 @@ bool Decimal::EncodedData::operator==(const EncodedData& another) const + && m_coefficient == another.m_coefficient; + } + ++ + Decimal::Decimal(int32_t i32) +- : m_data(i32 < 0 ? Negative : Positive, 0, i32 < 0 ? static_cast<uint64_t>(-static_cast<int64_t>(i32)) : static_cast<uint64_t>(i32)) +-{ +-} ++ : Decimal(DecimalLiteral{i32}) {} + + Decimal::Decimal(Sign sign, int exponent, uint64_t coefficient) +- : m_data(sign, coefficient ? exponent : 0, coefficient) +-{ +-} ++ : m_data(sign, coefficient ? exponent : 0, coefficient) {} + + Decimal::Decimal(const EncodedData& data) + : m_data(data) +diff --git a/mozglue/misc/decimal/Decimal.h b/mozglue/misc/decimal/Decimal.h +index 10d0e2c7cefa3..4bb9a841e585f 100644 +--- a/mozglue/misc/decimal/Decimal.h ++++ b/mozglue/misc/decimal/Decimal.h +@@ -65,9 +65,28 @@ + namespace blink { + + namespace DecimalPrivate { ++constexpr int ExponentMax = 1023; ++constexpr int ExponentMin = -1023; ++constexpr int Precision = 18; ++ ++static const uint64_t MaxCoefficient = UINT64_C(0xDE0B6B3A763FFFF); // 999999999999999999 == 18 9's + class SpecialValueHandler; + } + ++struct DecimalLiteral { ++ int32_t value; ++ friend constexpr DecimalLiteral operator*(int32_t lhs, DecimalLiteral rhs) { ++ return {lhs * rhs.value}; ++ } ++ constexpr DecimalLiteral operator-() { ++ return {-value}; ++ } ++}; ++ ++constexpr DecimalLiteral operator""_d(unsigned long long value) { ++ return {static_cast<int32_t>(value)}; ++} ++ + // This class represents decimal base floating point number. + // + // FIXME: Once all C++ compiler support decimal type, we should replace this +@@ -88,7 +107,32 @@ public: + friend class Decimal; + friend class DecimalPrivate::SpecialValueHandler; + public: +- EncodedData(Sign, int exponent, uint64_t coefficient); ++ constexpr EncodedData(Sign sign, int exponent, uint64_t coefficient) ++ : m_coefficient(0), ++ m_exponent(0), ++ m_formatClass(coefficient ? ClassNormal : ClassZero), ++ m_sign(sign) { ++ if (exponent >= DecimalPrivate::ExponentMin && ++ exponent <= DecimalPrivate::ExponentMax) { ++ while (coefficient > DecimalPrivate::MaxCoefficient) { ++ coefficient /= 10; ++ ++exponent; ++ } ++ } ++ ++ if (exponent > DecimalPrivate::ExponentMax) { ++ m_formatClass = ClassInfinity; ++ return; ++ } ++ ++ if (exponent < DecimalPrivate::ExponentMin) { ++ m_formatClass = ClassZero; ++ return; ++ } ++ ++ m_coefficient = coefficient; ++ m_exponent = static_cast<int16_t>(exponent); ++ } + + bool operator==(const EncodedData&) const; + bool operator!=(const EncodedData& another) const { return !operator==(another); } +@@ -112,7 +156,12 @@ public: + ClassZero, + }; + +- EncodedData(Sign, FormatClass); ++ constexpr EncodedData(Sign sign, FormatClass formatClass) ++ : m_coefficient(0), ++ m_exponent(0), ++ m_formatClass(formatClass), ++ m_sign(sign) {} ++ + FormatClass formatClass() const { return m_formatClass; } + + uint64_t m_coefficient; +@@ -121,8 +170,13 @@ public: + Sign m_sign; + }; + +- MFBT_API explicit Decimal(int32_t = 0); +- MFBT_API Decimal(Sign, int exponent, uint64_t coefficient); ++ constexpr explicit Decimal(DecimalLiteral i32) ++ : m_data(i32.value < 0 ? Negative : Positive, 0, ++ i32.value < 0 ? static_cast<uint64_t>(-static_cast<int64_t>(i32.value)) ++ : static_cast<uint64_t>(i32.value)) {} ++ ++ MFBT_API explicit Decimal(int32_t i32 = 0); ++ MFBT_API Decimal(Sign sign, int exponent, uint64_t coefficient); + MFBT_API Decimal(const Decimal&); + + MFBT_API Decimal& operator=(const Decimal&); +@@ -209,6 +263,7 @@ private: + + namespace mozilla { + typedef blink::Decimal Decimal; ++using blink::operator""_d; + } // namespace mozilla + + #undef USING_FAST_MALLOC diff --git a/mozglue/misc/decimal/moz-decimal-utils.h b/mozglue/misc/decimal/moz-decimal-utils.h new file mode 100644 index 0000000000..05ea61e4bd --- /dev/null +++ b/mozglue/misc/decimal/moz-decimal-utils.h @@ -0,0 +1,110 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZ_DECIMAL_UTILS_H +#define MOZ_DECIMAL_UTILS_H + +// This file contains extra includes, defines and typedefs to allow compilation +// of Decimal.cpp under the Mozilla source without blink core dependencies. Do +// not include it into any file other than Decimal.cpp. + +#include "double-conversion/double-conversion.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Casting.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/Span.h" + +#include <cmath> +#include <cstring> +#include <iomanip> +#include <limits> +#include <sstream> + +#ifndef UINT64_C +// For Android toolchain +#define UINT64_C(c) (c ## ULL) +#endif + +#ifdef ASSERT +#undef ASSERT +#endif +#define ASSERT MOZ_ASSERT + +#define ASSERT_NOT_REACHED() MOZ_ASSERT_UNREACHABLE("moz-decimal-utils.h") + +#define STACK_ALLOCATED() DISALLOW_NEW() + +#define WTF_MAKE_NONCOPYABLE(ClassName) \ + private: \ + ClassName(const ClassName&) = delete; \ + void operator=(const ClassName&) = delete; + +typedef std::string String; + +double mozToDouble(mozilla::Span<const char> aStr, bool *valid) { + double_conversion::StringToDoubleConverter converter( + double_conversion::StringToDoubleConverter::NO_FLAGS, + mozilla::UnspecifiedNaN<double>(), mozilla::UnspecifiedNaN<double>(), nullptr, nullptr); + const char* str = aStr.Elements(); + int length = mozilla::AssertedCast<int>(aStr.Length()); + int processed_char_count; // unused - NO_FLAGS requires the whole string to parse + double result = converter.StringToDouble(str, length, &processed_char_count); + *valid = std::isfinite(result); + return result; +} + +double mozToDouble(const String &aStr, bool *valid) { + return mozToDouble(mozilla::MakeStringSpan(aStr.c_str()), valid); +} + +String mozToString(double aNum) { + char buffer[64]; + int buffer_length = mozilla::ArrayLength(buffer); + const double_conversion::DoubleToStringConverter& converter = + double_conversion::DoubleToStringConverter::EcmaScriptConverter(); + double_conversion::StringBuilder builder(buffer, buffer_length); + converter.ToShortest(aNum, &builder); + return String(builder.Finalize()); +} + +String mozToString(int64_t aNum) { + std::ostringstream o; + o << std::setprecision(std::numeric_limits<int64_t>::digits10) << aNum; + return o.str(); +} + +String mozToString(uint64_t aNum) { + std::ostringstream o; + o << std::setprecision(std::numeric_limits<uint64_t>::digits10) << aNum; + return o.str(); +} + +namespace moz_decimal_utils { + +class StringBuilder +{ +public: + void append(char c) { + mStr += c; + } + void appendLiteral(const char *aStr) { + mStr += aStr; + } + void appendNumber(int aNum) { + mStr += mozToString(int64_t(aNum)); + } + void append(const String& aStr) { + mStr += aStr; + } + std::string toString() const { + return mStr; + } +private: + std::string mStr; +}; + +} // namespace moz_decimal_utils + +#endif diff --git a/mozglue/misc/decimal/to-moz-dependencies.patch b/mozglue/misc/decimal/to-moz-dependencies.patch new file mode 100644 index 0000000000..bf19a6da96 --- /dev/null +++ b/mozglue/misc/decimal/to-moz-dependencies.patch @@ -0,0 +1,224 @@ +diff --git a/mozglue/misc/decimal/Decimal.cpp b/mozglue/misc/decimal/Decimal.cpp +--- a/mozglue/misc/decimal/Decimal.cpp ++++ b/mozglue/misc/decimal/Decimal.cpp +@@ -23,22 +23,20 @@ + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +-#include "platform/Decimal.h" ++#include "Decimal.h" ++#include "moz-decimal-utils.h" + +-#include "wtf/Allocator.h" +-#include "wtf/MathExtras.h" +-#include "wtf/Noncopyable.h" +-#include "wtf/text/StringBuilder.h" ++using namespace moz_decimal_utils; + + #include <algorithm> + #include <float.h> + + namespace blink { + + namespace DecimalPrivate { + +@@ -690,17 +688,17 @@ Decimal Decimal::floor() const + if (isNegative() && !isMultiplePowersOfTen(m_data.coefficient(), numberOfDropDigits)) + ++result; + return Decimal(sign(), 0, result); + } + + Decimal Decimal::fromDouble(double doubleValue) + { + if (std::isfinite(doubleValue)) +- return fromString(String::numberToStringECMAScript(doubleValue)); ++ return fromString(mozToString(doubleValue)); + + if (std::isinf(doubleValue)) + return infinity(doubleValue < 0 ? Negative : Positive); + + return nan(); + } + + Decimal Decimal::fromString(const String& str) +@@ -931,17 +929,17 @@ Decimal Decimal::round() const + result /= 10; + return Decimal(sign(), 0, result); + } + + double Decimal::toDouble() const + { + if (isFinite()) { + bool valid; +- const double doubleValue = toString().toDouble(&valid); ++ const double doubleValue = mozToDouble(toString(), &valid); + return valid ? doubleValue : std::numeric_limits<double>::quiet_NaN(); + } + + if (isInfinity()) + return isNegative() ? -std::numeric_limits<double>::infinity() : std::numeric_limits<double>::infinity(); + + return std::numeric_limits<double>::quiet_NaN(); + } +@@ -984,17 +982,17 @@ String Decimal::toString() const + ++coefficient; + + while (originalExponent < 0 && coefficient && !(coefficient % 10)) { + coefficient /= 10; + ++originalExponent; + } + } + +- const String digits = String::number(coefficient); ++ const String digits = mozToString(coefficient); + int coefficientLength = static_cast<int>(digits.length()); + const int adjustedExponent = originalExponent + coefficientLength - 1; + if (originalExponent <= 0 && adjustedExponent >= -6) { + if (!originalExponent) { + builder.append(digits); + return builder.toString(); + } + +@@ -1026,14 +1024,27 @@ String Decimal::toString() const + if (adjustedExponent) { + builder.append(adjustedExponent < 0 ? "e" : "e+"); + builder.appendNumber(adjustedExponent); + } + } + return builder.toString(); + } + ++bool Decimal::toString(char* strBuf, size_t bufLength) const ++{ ++ ASSERT(bufLength > 0); ++ String str = toString(); ++ size_t length = str.copy(strBuf, bufLength); ++ if (length < bufLength) { ++ strBuf[length] = '\0'; ++ return true; ++ } ++ strBuf[bufLength - 1] = '\0'; ++ return false; ++} ++ + Decimal Decimal::zero(Sign sign) + { + return Decimal(EncodedData(sign, EncodedData::ClassZero)); + } + + } // namespace blink +diff --git a/mozglue/misc/decimal/Decimal.h b/mozglue/misc/decimal/Decimal.h +--- a/mozglue/misc/decimal/Decimal.h ++++ b/mozglue/misc/decimal/Decimal.h +@@ -23,26 +23,49 @@ + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + ++/** ++ * Imported from: ++ * https://chromium.googlesource.com/chromium/src.git/+/master/third_party/WebKit/Source/platform/Decimal.h ++ * Check UPSTREAM-GIT-SHA for the commit ID of the last update from Blink core. ++ */ ++ + #ifndef Decimal_h + #define Decimal_h + ++#include "mozilla/Assertions.h" ++#include <stdint.h> + #include "mozilla/Types.h" + +-#include "platform/PlatformExport.h" +-#include "wtf/Allocator.h" +-#include "wtf/Assertions.h" +-#include "wtf/text/WTFString.h" +-#include <stdint.h> ++#include <string> ++ ++#ifndef ASSERT ++#define DEFINED_ASSERT_FOR_DECIMAL_H 1 ++#define ASSERT MOZ_ASSERT ++#endif ++ ++#define PLATFORM_EXPORT ++ ++// To use USING_FAST_MALLOC we'd need: ++// https://chromium.googlesource.com/chromium/src.git/+/master/third_party/WebKit/Source/wtf/Allocator.h ++// Since we don't allocate Decimal objects, no need. ++#define USING_FAST_MALLOC(type) \ ++ void ignore_this_dummy_method() = delete ++ ++#define DISALLOW_NEW() \ ++ private: \ ++ void* operator new(size_t) = delete; \ ++ void* operator new(size_t, void*) = delete; \ ++ public: + + namespace blink { + + namespace DecimalPrivate { + class SpecialValueHandler; + } + + // This class represents decimal base floating point number. +@@ -139,27 +162,28 @@ public: + MFBT_API Decimal abs() const; + MFBT_API Decimal ceil() const; + MFBT_API Decimal floor() const; + MFBT_API Decimal remainder(const Decimal&) const; + MFBT_API Decimal round() const; + + MFBT_API double toDouble() const; + // Note: toString method supports infinity and nan but fromString not. +- MFBT_API String toString() const; ++ MFBT_API std::string toString() const; ++ MFBT_API bool toString(char* strBuf, size_t bufLength) const; + + static MFBT_API Decimal fromDouble(double); + // fromString supports following syntax EBNF: + // number ::= sign? digit+ ('.' digit*) (exponent-marker sign? digit+)? + // | sign? '.' digit+ (exponent-marker sign? digit+)? + // sign ::= '+' | '-' + // exponent-marker ::= 'e' | 'E' + // digit ::= '0' | '1' | ... | '9' + // Note: fromString doesn't support "infinity" and "nan". +- static MFBT_API Decimal fromString(const String&); ++ static MFBT_API Decimal fromString(const std::string& aValue); + static MFBT_API Decimal infinity(Sign); + static MFBT_API Decimal nan(); + static MFBT_API Decimal zero(Sign); + + // You should not use below methods. We expose them for unit testing. + MFBT_API explicit Decimal(const EncodedData&); + const EncodedData& value() const { return m_data; } + +@@ -178,9 +202,20 @@ private: + + Sign sign() const { return m_data.sign(); } + + EncodedData m_data; + }; + + } // namespace blink + ++namespace mozilla { ++typedef blink::Decimal Decimal; ++} // namespace mozilla ++ ++#undef USING_FAST_MALLOC ++ ++#ifdef DEFINED_ASSERT_FOR_DECIMAL_H ++#undef DEFINED_ASSERT_FOR_DECIMAL_H ++#undef ASSERT ++#endif ++ + #endif // Decimal_h diff --git a/mozglue/misc/decimal/update.sh b/mozglue/misc/decimal/update.sh new file mode 100755 index 0000000000..696685f9ba --- /dev/null +++ b/mozglue/misc/decimal/update.sh @@ -0,0 +1,61 @@ +# Usage: ./update.sh [blink-core-source-directory] +# +# Copies the needed files from a directory containing the original +# Decimal.h and Decimal.cpp source that we need. +# If [blink-core-source-directory] is not specified, this script will +# attempt to download the latest versions using git. + +set -e + +FILES=( + "Decimal.h" + "Decimal.cpp" +) + +OWN_NAME=`basename $0` + +if [ $# -gt 1 ]; then + echo "$OWN_NAME: Too many arguments">&2 + exit 1 +fi + +if [ $# -eq 1 ]; then + BLINK_CORE_DIR="$1" + for F in "${FILES[@]}" + do + P="$BLINK_CORE_DIR/$F" + if [ ! -f "$P" ]; then + echo "$OWN_NAME: Couldn't find file: $P">&2 + exit 1 + fi + done + for F in "${FILES[@]}" + do + P="$BLINK_CORE_DIR/$F" + cp "$P" . + done +else + #LATEST_SHA=$(cat UPSTREAM-GIT-SHA) + LATEST_SHA=$(git ls-remote https://chromium.googlesource.com/chromium/src.git/ | awk "/refs\/heads\/master/ {print \$1}") + REPO_PATH="https://chromium.googlesource.com/chromium/src.git/+/$LATEST_SHA/third_party/WebKit/Source/platform" + #REPO_PATH="https://github.com/WebKit/webkit/tree/master/Source/WebCore/platform" + for F in "${FILES[@]}" + do + printf "Downloading `basename $F`..." + curl "$REPO_PATH/${F}?format=TEXT" | base64 -D > "$F" + echo done. + done + echo $LATEST_SHA > UPSTREAM-GIT-SHA +fi + +# Apply patches: + +patch -p4 < zero-serialization.patch +patch -p4 < comparison-with-nan.patch +patch -p4 < mfbt-abi-markers.patch +patch -p4 < to-moz-dependencies.patch +patch -p4 < add-doubleconversion-impl.patch +patch -p4 < moz-constexpr-decimal.patch +# The following is disabled. See +# https://bugzilla.mozilla.org/show_bug.cgi?id=1208357#c7 +#patch -p4 < fix-wshadow-warnings.patch diff --git a/mozglue/misc/decimal/zero-serialization.patch b/mozglue/misc/decimal/zero-serialization.patch new file mode 100644 index 0000000000..b8de9241bd --- /dev/null +++ b/mozglue/misc/decimal/zero-serialization.patch @@ -0,0 +1,22 @@ +diff --git a/mozglue/misc/decimal/Decimal.cpp b/mozglue/misc/decimal/Decimal.cpp +--- a/mozglue/misc/decimal/Decimal.cpp ++++ b/mozglue/misc/decimal/Decimal.cpp +@@ -277,17 +277,17 @@ bool Decimal::EncodedData::operator==(co + } + + Decimal::Decimal(int32_t i32) + : m_data(i32 < 0 ? Negative : Positive, 0, i32 < 0 ? static_cast<uint64_t>(-static_cast<int64_t>(i32)) : static_cast<uint64_t>(i32)) + { + } + + Decimal::Decimal(Sign sign, int exponent, uint64_t coefficient) +- : m_data(sign, exponent, coefficient) ++ : m_data(sign, coefficient ? exponent : 0, coefficient) + { + } + + Decimal::Decimal(const EncodedData& data) + : m_data(data) + { + } + diff --git a/mozglue/misc/moz.build b/mozglue/misc/moz.build new file mode 100644 index 0000000000..016506f16a --- /dev/null +++ b/mozglue/misc/moz.build @@ -0,0 +1,157 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +FINAL_LIBRARY = "mozglue" + +EXPORTS.mozilla += [ + "AutoProfilerLabel.h", + "AwakeTimeStamp.h", + "decimal/Decimal.h", + "decimal/DoubleConversion.h", + "IntegerPrintfMacros.h", + "MmapFaultHandler.h", + "PlatformConditionVariable.h", + "PlatformMutex.h", + "PlatformRWLock.h", + "Printf.h", + "SIMD.h", + "Sprintf.h", + "SSE.h", + "StackWalk.h", + "TimeStamp.h", + "Uptime.h", +] + +EXPORTS.mozilla.glue += [ + "Debug.h", + "WinUtils.h", +] + +if CONFIG["OS_ARCH"] == "WINNT": + EXPORTS.mozilla += [ + "GetKnownFolderPath.h", + "PreXULSkeletonUI.h", + "StackWalk_windows.h", + "StackWalkThread.h", + "TimeStamp_windows.h", + "WindowsDpiAwareness.h", + ] + +SOURCES += [ + "AutoProfilerLabel.cpp", + "AwakeTimeStamp.cpp", + "Debug.cpp", + "MmapFaultHandler.cpp", + "Printf.cpp", + "SIMD.cpp", + "StackWalk.cpp", + "TimeStamp.cpp", + "Uptime.cpp", +] + +if CONFIG["TARGET_CPU"].startswith("x86"): + SOURCES += [ + "SIMD_avx2.cpp", + "SSE.cpp", + ] + SOURCES["SIMD_avx2.cpp"].flags += ["-mavx2"] + +if not CONFIG["JS_STANDALONE"]: + EXPORTS.mozilla += [ + "ProcessType.h", + "RuntimeExceptionModule.h", + ] + + SOURCES += [ + "ProcessType.cpp", + "RuntimeExceptionModule.cpp", + ] + +OS_LIBS += CONFIG["REALTIME_LIBS"] + +if CONFIG["OS_ARCH"] == "WINNT": + EXPORTS.mozilla += [ + "DynamicallyLinkedFunctionPtr.h", + "ImportDir.h", + "NativeNt.h", + "WindowsDpiInitialization.h", + "WindowsEnumProcessModules.h", + "WindowsMapRemoteView.h", + "WindowsProcessMitigations.h", + "WindowsStackCookie.h", + "WindowsUnwindInfo.h", + ] + EXPORTS.mozilla.glue += [ + "WindowsUnicode.h", + ] + SOURCES += [ + "GetKnownFolderPath.cpp", + "TimeStamp_windows.cpp", + "WindowsDllMain.cpp", + "WindowsDpiInitialization.cpp", + "WindowsMapRemoteView.cpp", + "WindowsProcessMitigations.cpp", + "WindowsUnicode.cpp", + ] + + OS_LIBS += [ + "dbghelp", + "oleaut32", + "ole32", + ] + + if not CONFIG["JS_STANDALONE"]: + SOURCES += [ + "/ipc/mscom/COMWrappers.cpp", + "/ipc/mscom/ProcessRuntime.cpp", + "PreXULSkeletonUI.cpp", + ] + +elif CONFIG["OS_ARCH"] == "Darwin": + SOURCES += [ + "TimeStamp_darwin.cpp", + ] +elif CONFIG["HAVE_CLOCK_MONOTONIC"]: + SOURCES += [ + "TimeStamp_posix.cpp", + ] +elif CONFIG["COMPILE_ENVIRONMENT"]: + error("No TimeStamp implementation on this platform. Build will not succeed") + +if CONFIG["OS_ARCH"] == "WINNT": + SOURCES += [ + "ConditionVariable_windows.cpp", + "Mutex_windows.cpp", + "RWLock_windows.cpp", + ] +# WASI hasn't supported cond vars and mutexes yet so noop implementation is used. +elif CONFIG["OS_ARCH"] == "WASI": + SOURCES += [ + "ConditionVariable_noop.cpp", + "Mutex_noop.cpp", + ] +else: + SOURCES += [ + "ConditionVariable_posix.cpp", + "Mutex_posix.cpp", + "RWLock_posix.cpp", + ] + +if CONFIG["MOZ_LINKER"] and CONFIG["MOZ_WIDGET_TOOLKIT"] == "android": + LOCAL_INCLUDES += [ + "/mozglue/linker", + ] + +SOURCES += [ + "decimal/Decimal.cpp", +] + +if CONFIG["CC_TYPE"] in ("clang", "clang-cl"): + # Suppress warnings from third-party V8 Decimal code. + SOURCES["decimal/Decimal.cpp"].flags += ["-Wno-implicit-fallthrough"] + +for var in ("MOZ_APP_BASENAME", "MOZ_APP_VENDOR"): + DEFINES[var] = '"%s"' % CONFIG[var] |