gtest-port.cc (googletest-release-1.11.0) | : | gtest-port.cc (googletest-release-1.12.0) | ||
---|---|---|---|---|
skipping to change at line 36 | skipping to change at line 36 | |||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
#include "gtest/internal/gtest-port.h" | #include "gtest/internal/gtest-port.h" | |||
#include <limits.h> | #include <limits.h> | |||
#include <stdio.h> | #include <stdio.h> | |||
#include <stdlib.h> | #include <stdlib.h> | |||
#include <string.h> | #include <string.h> | |||
#include <cstdint> | #include <cstdint> | |||
#include <fstream> | #include <fstream> | |||
#include <memory> | #include <memory> | |||
#if GTEST_OS_WINDOWS | #if GTEST_OS_WINDOWS | |||
# include <windows.h> | #include <io.h> | |||
# include <io.h> | #include <sys/stat.h> | |||
# include <sys/stat.h> | #include <windows.h> | |||
# include <map> // Used in ThreadLocal. | ||||
# ifdef _MSC_VER | #include <map> // Used in ThreadLocal. | |||
# include <crtdbg.h> | #ifdef _MSC_VER | |||
# endif // _MSC_VER | #include <crtdbg.h> | |||
#endif // _MSC_VER | ||||
#else | #else | |||
# include <unistd.h> | #include <unistd.h> | |||
#endif // GTEST_OS_WINDOWS | #endif // GTEST_OS_WINDOWS | |||
#if GTEST_OS_MAC | #if GTEST_OS_MAC | |||
# include <mach/mach_init.h> | #include <mach/mach_init.h> | |||
# include <mach/task.h> | #include <mach/task.h> | |||
# include <mach/vm_map.h> | #include <mach/vm_map.h> | |||
#endif // GTEST_OS_MAC | #endif // GTEST_OS_MAC | |||
#if GTEST_OS_DRAGONFLY || GTEST_OS_FREEBSD || GTEST_OS_GNU_KFREEBSD || \ | #if GTEST_OS_DRAGONFLY || GTEST_OS_FREEBSD || GTEST_OS_GNU_KFREEBSD || \ | |||
GTEST_OS_NETBSD || GTEST_OS_OPENBSD | GTEST_OS_NETBSD || GTEST_OS_OPENBSD | |||
# include <sys/sysctl.h> | #include <sys/sysctl.h> | |||
# if GTEST_OS_DRAGONFLY || GTEST_OS_FREEBSD || GTEST_OS_GNU_KFREEBSD | #if GTEST_OS_DRAGONFLY || GTEST_OS_FREEBSD || GTEST_OS_GNU_KFREEBSD | |||
# include <sys/user.h> | #include <sys/user.h> | |||
# endif | #endif | |||
#endif | #endif | |||
#if GTEST_OS_QNX | #if GTEST_OS_QNX | |||
# include <devctl.h> | #include <devctl.h> | |||
# include <fcntl.h> | #include <fcntl.h> | |||
# include <sys/procfs.h> | #include <sys/procfs.h> | |||
#endif // GTEST_OS_QNX | #endif // GTEST_OS_QNX | |||
#if GTEST_OS_AIX | #if GTEST_OS_AIX | |||
# include <procinfo.h> | #include <procinfo.h> | |||
# include <sys/types.h> | #include <sys/types.h> | |||
#endif // GTEST_OS_AIX | #endif // GTEST_OS_AIX | |||
#if GTEST_OS_FUCHSIA | #if GTEST_OS_FUCHSIA | |||
# include <zircon/process.h> | #include <zircon/process.h> | |||
# include <zircon/syscalls.h> | #include <zircon/syscalls.h> | |||
#endif // GTEST_OS_FUCHSIA | #endif // GTEST_OS_FUCHSIA | |||
#include "gtest/gtest-spi.h" | ||||
#include "gtest/gtest-message.h" | #include "gtest/gtest-message.h" | |||
#include "gtest/gtest-spi.h" | ||||
#include "gtest/internal/gtest-internal.h" | #include "gtest/internal/gtest-internal.h" | |||
#include "gtest/internal/gtest-string.h" | #include "gtest/internal/gtest-string.h" | |||
#include "src/gtest-internal-inl.h" | #include "src/gtest-internal-inl.h" | |||
namespace testing { | namespace testing { | |||
namespace internal { | namespace internal { | |||
#if defined(_MSC_VER) || defined(__BORLANDC__) | #if GTEST_OS_LINUX || GTEST_OS_GNU_HURD | |||
// MSVC and C++Builder do not provide a definition of STDERR_FILENO. | ||||
const int kStdOutFileno = 1; | ||||
const int kStdErrFileno = 2; | ||||
#else | ||||
const int kStdOutFileno = STDOUT_FILENO; | ||||
const int kStdErrFileno = STDERR_FILENO; | ||||
#endif // _MSC_VER | ||||
#if GTEST_OS_LINUX | ||||
namespace { | namespace { | |||
template <typename T> | template <typename T> | |||
T ReadProcFileField(const std::string& filename, int field) { | T ReadProcFileField(const std::string& filename, int field) { | |||
std::string dummy; | std::string dummy; | |||
std::ifstream file(filename.c_str()); | std::ifstream file(filename.c_str()); | |||
while (field-- > 0) { | while (field-- > 0) { | |||
file >> dummy; | file >> dummy; | |||
} | } | |||
T output = 0; | T output = 0; | |||
skipping to change at line 133 | skipping to change at line 126 | |||
#elif GTEST_OS_MAC | #elif GTEST_OS_MAC | |||
size_t GetThreadCount() { | size_t GetThreadCount() { | |||
const task_t task = mach_task_self(); | const task_t task = mach_task_self(); | |||
mach_msg_type_number_t thread_count; | mach_msg_type_number_t thread_count; | |||
thread_act_array_t thread_list; | thread_act_array_t thread_list; | |||
const kern_return_t status = task_threads(task, &thread_list, &thread_count); | const kern_return_t status = task_threads(task, &thread_list, &thread_count); | |||
if (status == KERN_SUCCESS) { | if (status == KERN_SUCCESS) { | |||
// task_threads allocates resources in thread_list and we need to free them | // task_threads allocates resources in thread_list and we need to free them | |||
// to avoid leaks. | // to avoid leaks. | |||
vm_deallocate(task, | vm_deallocate(task, reinterpret_cast<vm_address_t>(thread_list), | |||
reinterpret_cast<vm_address_t>(thread_list), | ||||
sizeof(thread_t) * thread_count); | sizeof(thread_t) * thread_count); | |||
return static_cast<size_t>(thread_count); | return static_cast<size_t>(thread_count); | |||
} else { | } else { | |||
return 0; | return 0; | |||
} | } | |||
} | } | |||
#elif GTEST_OS_DRAGONFLY || GTEST_OS_FREEBSD || GTEST_OS_GNU_KFREEBSD || \ | #elif GTEST_OS_DRAGONFLY || GTEST_OS_FREEBSD || GTEST_OS_GNU_KFREEBSD || \ | |||
GTEST_OS_NETBSD | GTEST_OS_NETBSD | |||
#if GTEST_OS_NETBSD | #if GTEST_OS_NETBSD | |||
#undef KERN_PROC | #undef KERN_PROC | |||
#define KERN_PROC KERN_PROC2 | #define KERN_PROC KERN_PROC2 | |||
#define kinfo_proc kinfo_proc2 | #define kinfo_proc kinfo_proc2 | |||
#endif | #endif | |||
#if GTEST_OS_DRAGONFLY | #if GTEST_OS_DRAGONFLY | |||
#define KP_NLWP(kp) (kp.kp_nthreads) | #define KP_NLWP(kp) (kp.kp_nthreads) | |||
#elif GTEST_OS_FREEBSD || GTEST_OS_GNU_KFREEBSD | #elif GTEST_OS_FREEBSD || GTEST_OS_GNU_KFREEBSD | |||
skipping to change at line 186 | skipping to change at line 178 | |||
return 0; | return 0; | |||
} | } | |||
return static_cast<size_t>(KP_NLWP(info)); | return static_cast<size_t>(KP_NLWP(info)); | |||
} | } | |||
#elif GTEST_OS_OPENBSD | #elif GTEST_OS_OPENBSD | |||
// Returns the number of threads running in the process, or 0 to indicate that | // Returns the number of threads running in the process, or 0 to indicate that | |||
// we cannot detect it. | // we cannot detect it. | |||
size_t GetThreadCount() { | size_t GetThreadCount() { | |||
int mib[] = { | int mib[] = { | |||
CTL_KERN, | CTL_KERN, | |||
KERN_PROC, | KERN_PROC, | |||
KERN_PROC_PID | KERN_PROC_SHOW_THREADS, | KERN_PROC_PID | KERN_PROC_SHOW_THREADS, | |||
getpid(), | getpid(), | |||
sizeof(struct kinfo_proc), | sizeof(struct kinfo_proc), | |||
0, | 0, | |||
}; | }; | |||
u_int miblen = sizeof(mib) / sizeof(mib[0]); | u_int miblen = sizeof(mib) / sizeof(mib[0]); | |||
// get number of structs | // get number of structs | |||
size_t size; | size_t size; | |||
if (sysctl(mib, miblen, NULL, &size, NULL, 0)) { | if (sysctl(mib, miblen, NULL, &size, NULL, 0)) { | |||
return 0; | return 0; | |||
} | } | |||
mib[5] = static_cast<int>(size / static_cast<size_t>(mib[4])); | mib[5] = static_cast<int>(size / static_cast<size_t>(mib[4])); | |||
// populate array of structs | // populate array of structs | |||
struct kinfo_proc info[mib[5]]; | struct kinfo_proc info[mib[5]]; | |||
if (sysctl(mib, miblen, &info, &size, NULL, 0)) { | if (sysctl(mib, miblen, &info, &size, NULL, 0)) { | |||
return 0; | return 0; | |||
} | } | |||
// exclude empty members | // exclude empty members | |||
size_t nthreads = 0; | size_t nthreads = 0; | |||
for (size_t i = 0; i < size / static_cast<size_t>(mib[4]); i++) { | for (size_t i = 0; i < size / static_cast<size_t>(mib[4]); i++) { | |||
if (info[i].p_tid != -1) | if (info[i].p_tid != -1) nthreads++; | |||
nthreads++; | ||||
} | } | |||
return nthreads; | return nthreads; | |||
} | } | |||
#elif GTEST_OS_QNX | #elif GTEST_OS_QNX | |||
// Returns the number of threads running in the process, or 0 to indicate that | // Returns the number of threads running in the process, or 0 to indicate that | |||
// we cannot detect it. | // we cannot detect it. | |||
size_t GetThreadCount() { | size_t GetThreadCount() { | |||
const int fd = open("/proc/self/as", O_RDONLY); | const int fd = open("/proc/self/as", O_RDONLY); | |||
skipping to change at line 256 | skipping to change at line 247 | |||
} else { | } else { | |||
return 0; | return 0; | |||
} | } | |||
} | } | |||
#elif GTEST_OS_FUCHSIA | #elif GTEST_OS_FUCHSIA | |||
size_t GetThreadCount() { | size_t GetThreadCount() { | |||
int dummy_buffer; | int dummy_buffer; | |||
size_t avail; | size_t avail; | |||
zx_status_t status = zx_object_get_info( | zx_status_t status = | |||
zx_process_self(), | zx_object_get_info(zx_process_self(), ZX_INFO_PROCESS_THREADS, | |||
ZX_INFO_PROCESS_THREADS, | &dummy_buffer, 0, nullptr, &avail); | |||
&dummy_buffer, | ||||
0, | ||||
nullptr, | ||||
&avail); | ||||
if (status == ZX_OK) { | if (status == ZX_OK) { | |||
return avail; | return avail; | |||
} else { | } else { | |||
return 0; | return 0; | |||
} | } | |||
} | } | |||
#else | #else | |||
size_t GetThreadCount() { | size_t GetThreadCount() { | |||
// There's no portable way to detect the number of threads, so we just | // There's no portable way to detect the number of threads, so we just | |||
// return 0 to indicate that we cannot detect it. | // return 0 to indicate that we cannot detect it. | |||
return 0; | return 0; | |||
} | } | |||
#endif // GTEST_OS_LINUX | #endif // GTEST_OS_LINUX | |||
#if GTEST_IS_THREADSAFE && GTEST_OS_WINDOWS | #if GTEST_IS_THREADSAFE && GTEST_OS_WINDOWS | |||
void SleepMilliseconds(int n) { | AutoHandle::AutoHandle() : handle_(INVALID_HANDLE_VALUE) {} | |||
::Sleep(static_cast<DWORD>(n)); | ||||
} | ||||
AutoHandle::AutoHandle() | AutoHandle::AutoHandle(Handle handle) : handle_(handle) {} | |||
: handle_(INVALID_HANDLE_VALUE) {} | ||||
AutoHandle::AutoHandle(Handle handle) | AutoHandle::~AutoHandle() { Reset(); } | |||
: handle_(handle) {} | ||||
AutoHandle::~AutoHandle() { | AutoHandle::Handle AutoHandle::Get() const { return handle_; } | |||
Reset(); | ||||
} | ||||
AutoHandle::Handle AutoHandle::Get() const { | void AutoHandle::Reset() { Reset(INVALID_HANDLE_VALUE); } | |||
return handle_; | ||||
} | ||||
void AutoHandle::Reset() { | ||||
Reset(INVALID_HANDLE_VALUE); | ||||
} | ||||
void AutoHandle::Reset(HANDLE handle) { | void AutoHandle::Reset(HANDLE handle) { | |||
// Resetting with the same handle we already own is invalid. | // Resetting with the same handle we already own is invalid. | |||
if (handle_ != handle) { | if (handle_ != handle) { | |||
if (IsCloseable()) { | if (IsCloseable()) { | |||
::CloseHandle(handle_); | ::CloseHandle(handle_); | |||
} | } | |||
handle_ = handle; | handle_ = handle; | |||
} else { | } else { | |||
GTEST_CHECK_(!IsCloseable()) | GTEST_CHECK_(!IsCloseable()) | |||
<< "Resetting a valid handle to itself is likely a programmer error " | << "Resetting a valid handle to itself is likely a programmer error " | |||
"and thus not allowed."; | "and thus not allowed."; | |||
} | } | |||
} | } | |||
bool AutoHandle::IsCloseable() const { | bool AutoHandle::IsCloseable() const { | |||
// Different Windows APIs may use either of these values to represent an | // Different Windows APIs may use either of these values to represent an | |||
// invalid handle. | // invalid handle. | |||
return handle_ != nullptr && handle_ != INVALID_HANDLE_VALUE; | return handle_ != nullptr && handle_ != INVALID_HANDLE_VALUE; | |||
} | } | |||
Notification::Notification() | ||||
: event_(::CreateEvent(nullptr, // Default security attributes. | ||||
TRUE, // Do not reset automatically. | ||||
FALSE, // Initially unset. | ||||
nullptr)) { // Anonymous event. | ||||
GTEST_CHECK_(event_.Get() != nullptr); | ||||
} | ||||
void Notification::Notify() { | ||||
GTEST_CHECK_(::SetEvent(event_.Get()) != FALSE); | ||||
} | ||||
void Notification::WaitForNotification() { | ||||
GTEST_CHECK_( | ||||
::WaitForSingleObject(event_.Get(), INFINITE) == WAIT_OBJECT_0); | ||||
} | ||||
Mutex::Mutex() | Mutex::Mutex() | |||
: owner_thread_id_(0), | : owner_thread_id_(0), | |||
type_(kDynamic), | type_(kDynamic), | |||
critical_section_init_phase_(0), | critical_section_init_phase_(0), | |||
critical_section_(new CRITICAL_SECTION) { | critical_section_(new CRITICAL_SECTION) { | |||
::InitializeCriticalSection(critical_section_); | ::InitializeCriticalSection(critical_section_); | |||
} | } | |||
Mutex::~Mutex() { | Mutex::~Mutex() { | |||
// Static mutexes are leaked intentionally. It is not thread-safe to try | // Static mutexes are leaked intentionally. It is not thread-safe to try | |||
skipping to change at line 393 | skipping to change at line 351 | |||
#ifdef _MSC_VER | #ifdef _MSC_VER | |||
// Use the RAII idiom to flag mem allocs that are intentionally never | // Use the RAII idiom to flag mem allocs that are intentionally never | |||
// deallocated. The motivation is to silence the false positive mem leaks | // deallocated. The motivation is to silence the false positive mem leaks | |||
// that are reported by the debug version of MS's CRT which can only detect | // that are reported by the debug version of MS's CRT which can only detect | |||
// if an alloc is missing a matching deallocation. | // if an alloc is missing a matching deallocation. | |||
// Example: | // Example: | |||
// MemoryIsNotDeallocated memory_is_not_deallocated; | // MemoryIsNotDeallocated memory_is_not_deallocated; | |||
// critical_section_ = new CRITICAL_SECTION; | // critical_section_ = new CRITICAL_SECTION; | |||
// | // | |||
class MemoryIsNotDeallocated | class MemoryIsNotDeallocated { | |||
{ | ||||
public: | public: | |||
MemoryIsNotDeallocated() : old_crtdbg_flag_(0) { | MemoryIsNotDeallocated() : old_crtdbg_flag_(0) { | |||
old_crtdbg_flag_ = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); | old_crtdbg_flag_ = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); | |||
// Set heap allocation block type to _IGNORE_BLOCK so that MS debug CRT | // Set heap allocation block type to _IGNORE_BLOCK so that MS debug CRT | |||
// doesn't report mem leak if there's no matching deallocation. | // doesn't report mem leak if there's no matching deallocation. | |||
_CrtSetDbgFlag(old_crtdbg_flag_ & ~_CRTDBG_ALLOC_MEM_DF); | (void)_CrtSetDbgFlag(old_crtdbg_flag_ & ~_CRTDBG_ALLOC_MEM_DF); | |||
} | } | |||
~MemoryIsNotDeallocated() { | ~MemoryIsNotDeallocated() { | |||
// Restore the original _CRTDBG_ALLOC_MEM_DF flag | // Restore the original _CRTDBG_ALLOC_MEM_DF flag | |||
_CrtSetDbgFlag(old_crtdbg_flag_); | (void)_CrtSetDbgFlag(old_crtdbg_flag_); | |||
} | } | |||
private: | private: | |||
int old_crtdbg_flag_; | int old_crtdbg_flag_; | |||
GTEST_DISALLOW_COPY_AND_ASSIGN_(MemoryIsNotDeallocated); | MemoryIsNotDeallocated(const MemoryIsNotDeallocated&) = delete; | |||
MemoryIsNotDeallocated& operator=(const MemoryIsNotDeallocated&) = delete; | ||||
}; | }; | |||
#endif // _MSC_VER | #endif // _MSC_VER | |||
} // namespace | } // namespace | |||
// Initializes owner_thread_id_ and critical_section_ in static mutexes. | // Initializes owner_thread_id_ and critical_section_ in static mutexes. | |||
void Mutex::ThreadSafeLazyInit() { | void Mutex::ThreadSafeLazyInit() { | |||
// Dynamic mutexes are initialized in the constructor. | // Dynamic mutexes are initialized in the constructor. | |||
if (type_ == kStatic) { | if (type_ == kStatic) { | |||
switch ( | switch ( | |||
skipping to change at line 437 | skipping to change at line 395 | |||
{ | { | |||
// Use RAII to flag that following mem alloc is never deallocated. | // Use RAII to flag that following mem alloc is never deallocated. | |||
#ifdef _MSC_VER | #ifdef _MSC_VER | |||
MemoryIsNotDeallocated memory_is_not_deallocated; | MemoryIsNotDeallocated memory_is_not_deallocated; | |||
#endif // _MSC_VER | #endif // _MSC_VER | |||
critical_section_ = new CRITICAL_SECTION; | critical_section_ = new CRITICAL_SECTION; | |||
} | } | |||
::InitializeCriticalSection(critical_section_); | ::InitializeCriticalSection(critical_section_); | |||
// Updates the critical_section_init_phase_ to 2 to signal | // Updates the critical_section_init_phase_ to 2 to signal | |||
// initialization complete. | // initialization complete. | |||
GTEST_CHECK_(::InterlockedCompareExchange( | GTEST_CHECK_(::InterlockedCompareExchange(&critical_section_init_phase_, | |||
&critical_section_init_phase_, 2L, 1L) == | 2L, 1L) == 1L); | |||
1L); | ||||
break; | break; | |||
case 1: | case 1: | |||
// Somebody else is already initializing the mutex; spin until they | // Somebody else is already initializing the mutex; spin until they | |||
// are done. | // are done. | |||
while (::InterlockedCompareExchange(&critical_section_init_phase_, | while (::InterlockedCompareExchange(&critical_section_init_phase_, 2L, | |||
2L, | ||||
2L) != 2L) { | 2L) != 2L) { | |||
// Possibly yields the rest of the thread's time slice to other | // Possibly yields the rest of the thread's time slice to other | |||
// threads. | // threads. | |||
::Sleep(0); | ::Sleep(0); | |||
} | } | |||
break; | break; | |||
case 2: | case 2: | |||
break; // The mutex is already initialized and ready for use. | break; // The mutex is already initialized and ready for use. | |||
skipping to change at line 490 | skipping to change at line 446 | |||
<< "CreateThread failed with error " << ::GetLastError() << "."; | << "CreateThread failed with error " << ::GetLastError() << "."; | |||
if (thread_handle == nullptr) { | if (thread_handle == nullptr) { | |||
delete param; | delete param; | |||
} | } | |||
return thread_handle; | return thread_handle; | |||
} | } | |||
private: | private: | |||
struct ThreadMainParam { | struct ThreadMainParam { | |||
ThreadMainParam(Runnable* runnable, Notification* thread_can_start) | ThreadMainParam(Runnable* runnable, Notification* thread_can_start) | |||
: runnable_(runnable), | : runnable_(runnable), thread_can_start_(thread_can_start) {} | |||
thread_can_start_(thread_can_start) { | ||||
} | ||||
std::unique_ptr<Runnable> runnable_; | std::unique_ptr<Runnable> runnable_; | |||
// Does not own. | // Does not own. | |||
Notification* thread_can_start_; | Notification* thread_can_start_; | |||
}; | }; | |||
static DWORD WINAPI ThreadMain(void* ptr) { | static DWORD WINAPI ThreadMain(void* ptr) { | |||
// Transfers ownership. | // Transfers ownership. | |||
std::unique_ptr<ThreadMainParam> param(static_cast<ThreadMainParam*>(ptr)); | std::unique_ptr<ThreadMainParam> param(static_cast<ThreadMainParam*>(ptr)); | |||
if (param->thread_can_start_ != nullptr) | if (param->thread_can_start_ != nullptr) | |||
param->thread_can_start_->WaitForNotification(); | param->thread_can_start_->WaitForNotification(); | |||
param->runnable_->Run(); | param->runnable_->Run(); | |||
return 0; | return 0; | |||
} | } | |||
// Prohibit instantiation. | // Prohibit instantiation. | |||
ThreadWithParamSupport(); | ThreadWithParamSupport(); | |||
GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadWithParamSupport); | ThreadWithParamSupport(const ThreadWithParamSupport&) = delete; | |||
ThreadWithParamSupport& operator=(const ThreadWithParamSupport&) = delete; | ||||
}; | }; | |||
} // namespace | } // namespace | |||
ThreadWithParamBase::ThreadWithParamBase(Runnable *runnable, | ThreadWithParamBase::ThreadWithParamBase(Runnable* runnable, | |||
Notification* thread_can_start) | Notification* thread_can_start) | |||
: thread_(ThreadWithParamSupport::CreateThread(runnable, | : thread_( | |||
thread_can_start)) { | ThreadWithParamSupport::CreateThread(runnable, thread_can_start)) {} | |||
} | ||||
ThreadWithParamBase::~ThreadWithParamBase() { | ThreadWithParamBase::~ThreadWithParamBase() { Join(); } | |||
Join(); | ||||
} | ||||
void ThreadWithParamBase::Join() { | void ThreadWithParamBase::Join() { | |||
GTEST_CHECK_(::WaitForSingleObject(thread_.Get(), INFINITE) == WAIT_OBJECT_0) | GTEST_CHECK_(::WaitForSingleObject(thread_.Get(), INFINITE) == WAIT_OBJECT_0) | |||
<< "Failed to join the thread with error " << ::GetLastError() << "."; | << "Failed to join the thread with error " << ::GetLastError() << "."; | |||
} | } | |||
// Maps a thread to a set of ThreadIdToThreadLocals that have values | // Maps a thread to a set of ThreadIdToThreadLocals that have values | |||
// instantiated on that thread and notifies them when the thread exits. A | // instantiated on that thread and notifies them when the thread exits. A | |||
// ThreadLocal instance is expected to persist until all threads it has | // ThreadLocal instance is expected to persist until all threads it has | |||
// values on have terminated. | // values on have terminated. | |||
skipping to change at line 550 | skipping to change at line 502 | |||
#ifdef _MSC_VER | #ifdef _MSC_VER | |||
MemoryIsNotDeallocated memory_is_not_deallocated; | MemoryIsNotDeallocated memory_is_not_deallocated; | |||
#endif // _MSC_VER | #endif // _MSC_VER | |||
DWORD current_thread = ::GetCurrentThreadId(); | DWORD current_thread = ::GetCurrentThreadId(); | |||
MutexLock lock(&mutex_); | MutexLock lock(&mutex_); | |||
ThreadIdToThreadLocals* const thread_to_thread_locals = | ThreadIdToThreadLocals* const thread_to_thread_locals = | |||
GetThreadLocalsMapLocked(); | GetThreadLocalsMapLocked(); | |||
ThreadIdToThreadLocals::iterator thread_local_pos = | ThreadIdToThreadLocals::iterator thread_local_pos = | |||
thread_to_thread_locals->find(current_thread); | thread_to_thread_locals->find(current_thread); | |||
if (thread_local_pos == thread_to_thread_locals->end()) { | if (thread_local_pos == thread_to_thread_locals->end()) { | |||
thread_local_pos = thread_to_thread_locals->insert( | thread_local_pos = | |||
std::make_pair(current_thread, ThreadLocalValues())).first; | thread_to_thread_locals | |||
->insert(std::make_pair(current_thread, ThreadLocalValues())) | ||||
.first; | ||||
StartWatcherThreadFor(current_thread); | StartWatcherThreadFor(current_thread); | |||
} | } | |||
ThreadLocalValues& thread_local_values = thread_local_pos->second; | ThreadLocalValues& thread_local_values = thread_local_pos->second; | |||
ThreadLocalValues::iterator value_pos = | ThreadLocalValues::iterator value_pos = | |||
thread_local_values.find(thread_local_instance); | thread_local_values.find(thread_local_instance); | |||
if (value_pos == thread_local_values.end()) { | if (value_pos == thread_local_values.end()) { | |||
value_pos = | value_pos = | |||
thread_local_values | thread_local_values | |||
.insert(std::make_pair( | .insert(std::make_pair( | |||
thread_local_instance, | thread_local_instance, | |||
skipping to change at line 579 | skipping to change at line 533 | |||
static void OnThreadLocalDestroyed( | static void OnThreadLocalDestroyed( | |||
const ThreadLocalBase* thread_local_instance) { | const ThreadLocalBase* thread_local_instance) { | |||
std::vector<std::shared_ptr<ThreadLocalValueHolderBase> > value_holders; | std::vector<std::shared_ptr<ThreadLocalValueHolderBase> > value_holders; | |||
// Clean up the ThreadLocalValues data structure while holding the lock, but | // Clean up the ThreadLocalValues data structure while holding the lock, but | |||
// defer the destruction of the ThreadLocalValueHolderBases. | // defer the destruction of the ThreadLocalValueHolderBases. | |||
{ | { | |||
MutexLock lock(&mutex_); | MutexLock lock(&mutex_); | |||
ThreadIdToThreadLocals* const thread_to_thread_locals = | ThreadIdToThreadLocals* const thread_to_thread_locals = | |||
GetThreadLocalsMapLocked(); | GetThreadLocalsMapLocked(); | |||
for (ThreadIdToThreadLocals::iterator it = | for (ThreadIdToThreadLocals::iterator it = | |||
thread_to_thread_locals->begin(); | thread_to_thread_locals->begin(); | |||
it != thread_to_thread_locals->end(); | it != thread_to_thread_locals->end(); ++it) { | |||
++it) { | ||||
ThreadLocalValues& thread_local_values = it->second; | ThreadLocalValues& thread_local_values = it->second; | |||
ThreadLocalValues::iterator value_pos = | ThreadLocalValues::iterator value_pos = | |||
thread_local_values.find(thread_local_instance); | thread_local_values.find(thread_local_instance); | |||
if (value_pos != thread_local_values.end()) { | if (value_pos != thread_local_values.end()) { | |||
value_holders.push_back(value_pos->second); | value_holders.push_back(value_pos->second); | |||
thread_local_values.erase(value_pos); | thread_local_values.erase(value_pos); | |||
// This 'if' can only be successful at most once, so theoretically we | // This 'if' can only be successful at most once, so theoretically we | |||
// could break out of the loop here, but we don't bother doing so. | // could break out of the loop here, but we don't bother doing so. | |||
} | } | |||
} | } | |||
skipping to change at line 611 | skipping to change at line 564 | |||
// lock, but defer the destruction of the ThreadLocalValueHolderBases. | // lock, but defer the destruction of the ThreadLocalValueHolderBases. | |||
{ | { | |||
MutexLock lock(&mutex_); | MutexLock lock(&mutex_); | |||
ThreadIdToThreadLocals* const thread_to_thread_locals = | ThreadIdToThreadLocals* const thread_to_thread_locals = | |||
GetThreadLocalsMapLocked(); | GetThreadLocalsMapLocked(); | |||
ThreadIdToThreadLocals::iterator thread_local_pos = | ThreadIdToThreadLocals::iterator thread_local_pos = | |||
thread_to_thread_locals->find(thread_id); | thread_to_thread_locals->find(thread_id); | |||
if (thread_local_pos != thread_to_thread_locals->end()) { | if (thread_local_pos != thread_to_thread_locals->end()) { | |||
ThreadLocalValues& thread_local_values = thread_local_pos->second; | ThreadLocalValues& thread_local_values = thread_local_pos->second; | |||
for (ThreadLocalValues::iterator value_pos = | for (ThreadLocalValues::iterator value_pos = | |||
thread_local_values.begin(); | thread_local_values.begin(); | |||
value_pos != thread_local_values.end(); | value_pos != thread_local_values.end(); ++value_pos) { | |||
++value_pos) { | ||||
value_holders.push_back(value_pos->second); | value_holders.push_back(value_pos->second); | |||
} | } | |||
thread_to_thread_locals->erase(thread_local_pos); | thread_to_thread_locals->erase(thread_local_pos); | |||
} | } | |||
} | } | |||
// Outside the lock, let the destructor for 'value_holders' deallocate the | // Outside the lock, let the destructor for 'value_holders' deallocate the | |||
// ThreadLocalValueHolderBases. | // ThreadLocalValueHolderBases. | |||
} | } | |||
private: | private: | |||
skipping to change at line 639 | skipping to change at line 591 | |||
// thread's ID. | // thread's ID. | |||
typedef std::map<DWORD, ThreadLocalValues> ThreadIdToThreadLocals; | typedef std::map<DWORD, ThreadLocalValues> ThreadIdToThreadLocals; | |||
// Holds the thread id and thread handle that we pass from | // Holds the thread id and thread handle that we pass from | |||
// StartWatcherThreadFor to WatcherThreadFunc. | // StartWatcherThreadFor to WatcherThreadFunc. | |||
typedef std::pair<DWORD, HANDLE> ThreadIdAndHandle; | typedef std::pair<DWORD, HANDLE> ThreadIdAndHandle; | |||
static void StartWatcherThreadFor(DWORD thread_id) { | static void StartWatcherThreadFor(DWORD thread_id) { | |||
// The returned handle will be kept in thread_map and closed by | // The returned handle will be kept in thread_map and closed by | |||
// watcher_thread in WatcherThreadFunc. | // watcher_thread in WatcherThreadFunc. | |||
HANDLE thread = ::OpenThread(SYNCHRONIZE | THREAD_QUERY_INFORMATION, | HANDLE thread = | |||
FALSE, | ::OpenThread(SYNCHRONIZE | THREAD_QUERY_INFORMATION, FALSE, thread_id); | |||
thread_id); | ||||
GTEST_CHECK_(thread != nullptr); | GTEST_CHECK_(thread != nullptr); | |||
// We need to pass a valid thread ID pointer into CreateThread for it | // We need to pass a valid thread ID pointer into CreateThread for it | |||
// to work correctly under Win98. | // to work correctly under Win98. | |||
DWORD watcher_thread_id; | DWORD watcher_thread_id; | |||
HANDLE watcher_thread = ::CreateThread( | HANDLE watcher_thread = ::CreateThread( | |||
nullptr, // Default security. | nullptr, // Default security. | |||
0, // Default stack size | 0, // Default stack size | |||
&ThreadLocalRegistryImpl::WatcherThreadFunc, | &ThreadLocalRegistryImpl::WatcherThreadFunc, | |||
reinterpret_cast<LPVOID>(new ThreadIdAndHandle(thread_id, thread)), | reinterpret_cast<LPVOID>(new ThreadIdAndHandle(thread_id, thread)), | |||
CREATE_SUSPENDED, &watcher_thread_id); | CREATE_SUSPENDED, &watcher_thread_id); | |||
GTEST_CHECK_(watcher_thread != nullptr); | GTEST_CHECK_(watcher_thread != nullptr) | |||
<< "CreateThread failed with error " << ::GetLastError() << "."; | ||||
// Give the watcher thread the same priority as ours to avoid being | // Give the watcher thread the same priority as ours to avoid being | |||
// blocked by it. | // blocked by it. | |||
::SetThreadPriority(watcher_thread, | ::SetThreadPriority(watcher_thread, | |||
::GetThreadPriority(::GetCurrentThread())); | ::GetThreadPriority(::GetCurrentThread())); | |||
::ResumeThread(watcher_thread); | ::ResumeThread(watcher_thread); | |||
::CloseHandle(watcher_thread); | ::CloseHandle(watcher_thread); | |||
} | } | |||
// Monitors exit from a given thread and notifies those | // Monitors exit from a given thread and notifies those | |||
// ThreadIdToThreadLocals about thread termination. | // ThreadIdToThreadLocals about thread termination. | |||
static DWORD WINAPI WatcherThreadFunc(LPVOID param) { | static DWORD WINAPI WatcherThreadFunc(LPVOID param) { | |||
const ThreadIdAndHandle* tah = | const ThreadIdAndHandle* tah = | |||
reinterpret_cast<const ThreadIdAndHandle*>(param); | reinterpret_cast<const ThreadIdAndHandle*>(param); | |||
GTEST_CHECK_( | GTEST_CHECK_(::WaitForSingleObject(tah->second, INFINITE) == WAIT_OBJECT_0); | |||
::WaitForSingleObject(tah->second, INFINITE) == WAIT_OBJECT_0); | ||||
OnThreadExit(tah->first); | OnThreadExit(tah->first); | |||
::CloseHandle(tah->second); | ::CloseHandle(tah->second); | |||
delete tah; | delete tah; | |||
return 0; | return 0; | |||
} | } | |||
// Returns map of thread local instances. | // Returns map of thread local instances. | |||
static ThreadIdToThreadLocals* GetThreadLocalsMapLocked() { | static ThreadIdToThreadLocals* GetThreadLocalsMapLocked() { | |||
mutex_.AssertHeld(); | mutex_.AssertHeld(); | |||
#ifdef _MSC_VER | #ifdef _MSC_VER | |||
skipping to change at line 691 | skipping to change at line 642 | |||
return map; | return map; | |||
} | } | |||
// Protects access to GetThreadLocalsMapLocked() and its return value. | // Protects access to GetThreadLocalsMapLocked() and its return value. | |||
static Mutex mutex_; | static Mutex mutex_; | |||
// Protects access to GetThreadMapLocked() and its return value. | // Protects access to GetThreadMapLocked() and its return value. | |||
static Mutex thread_map_mutex_; | static Mutex thread_map_mutex_; | |||
}; | }; | |||
Mutex ThreadLocalRegistryImpl::mutex_(Mutex::kStaticMutex); // NOLINT | Mutex ThreadLocalRegistryImpl::mutex_(Mutex::kStaticMutex); // NOLINT | |||
Mutex ThreadLocalRegistryImpl::thread_map_mutex_(Mutex::kStaticMutex); // NOLIN | Mutex ThreadLocalRegistryImpl::thread_map_mutex_( | |||
T | Mutex::kStaticMutex); // NOLINT | |||
ThreadLocalValueHolderBase* ThreadLocalRegistry::GetValueOnCurrentThread( | ThreadLocalValueHolderBase* ThreadLocalRegistry::GetValueOnCurrentThread( | |||
const ThreadLocalBase* thread_local_instance) { | const ThreadLocalBase* thread_local_instance) { | |||
return ThreadLocalRegistryImpl::GetValueOnCurrentThread( | return ThreadLocalRegistryImpl::GetValueOnCurrentThread( | |||
thread_local_instance); | thread_local_instance); | |||
} | } | |||
void ThreadLocalRegistry::OnThreadLocalDestroyed( | void ThreadLocalRegistry::OnThreadLocalDestroyed( | |||
const ThreadLocalBase* thread_local_instance) { | const ThreadLocalBase* thread_local_instance) { | |||
ThreadLocalRegistryImpl::OnThreadLocalDestroyed(thread_local_instance); | ThreadLocalRegistryImpl::OnThreadLocalDestroyed(thread_local_instance); | |||
} | } | |||
#endif // GTEST_IS_THREADSAFE && GTEST_OS_WINDOWS | #endif // GTEST_IS_THREADSAFE && GTEST_OS_WINDOWS | |||
#if GTEST_USES_POSIX_RE | #if GTEST_USES_POSIX_RE | |||
// Implements RE. Currently only needed for death tests. | // Implements RE. Currently only needed for death tests. | |||
RE::~RE() { | RE::~RE() { | |||
skipping to change at line 788 | skipping to change at line 740 | |||
// Unlike similar functions in <ctype.h>, these aren't affected by the | // Unlike similar functions in <ctype.h>, these aren't affected by the | |||
// current locale. | // current locale. | |||
bool IsAsciiDigit(char ch) { return '0' <= ch && ch <= '9'; } | bool IsAsciiDigit(char ch) { return '0' <= ch && ch <= '9'; } | |||
bool IsAsciiPunct(char ch) { | bool IsAsciiPunct(char ch) { | |||
return IsInSet(ch, "^-!\"#$%&'()*+,./:;<=>?@[\\]_`{|}~"); | return IsInSet(ch, "^-!\"#$%&'()*+,./:;<=>?@[\\]_`{|}~"); | |||
} | } | |||
bool IsRepeat(char ch) { return IsInSet(ch, "?*+"); } | bool IsRepeat(char ch) { return IsInSet(ch, "?*+"); } | |||
bool IsAsciiWhiteSpace(char ch) { return IsInSet(ch, " \f\n\r\t\v"); } | bool IsAsciiWhiteSpace(char ch) { return IsInSet(ch, " \f\n\r\t\v"); } | |||
bool IsAsciiWordChar(char ch) { | bool IsAsciiWordChar(char ch) { | |||
return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || | return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || | |||
('0' <= ch && ch <= '9') || ch == '_'; | ('0' <= ch && ch <= '9') || ch == '_'; | |||
} | } | |||
// Returns true if and only if "\\c" is a supported escape sequence. | // Returns true if and only if "\\c" is a supported escape sequence. | |||
bool IsValidEscape(char c) { | bool IsValidEscape(char c) { | |||
return (IsAsciiPunct(c) || IsInSet(c, "dDfnrsStvwW")); | return (IsAsciiPunct(c) || IsInSet(c, "dDfnrsStvwW")); | |||
} | } | |||
// Returns true if and only if the given atom (specified by escaped and | // Returns true if and only if the given atom (specified by escaped and | |||
// pattern) matches ch. The result is undefined if the atom is invalid. | // pattern) matches ch. The result is undefined if the atom is invalid. | |||
bool AtomMatchesChar(bool escaped, char pattern_char, char ch) { | bool AtomMatchesChar(bool escaped, char pattern_char, char ch) { | |||
if (escaped) { // "\\p" where p is pattern_char. | if (escaped) { // "\\p" where p is pattern_char. | |||
switch (pattern_char) { | switch (pattern_char) { | |||
case 'd': return IsAsciiDigit(ch); | case 'd': | |||
case 'D': return !IsAsciiDigit(ch); | return IsAsciiDigit(ch); | |||
case 'f': return ch == '\f'; | case 'D': | |||
case 'n': return ch == '\n'; | return !IsAsciiDigit(ch); | |||
case 'r': return ch == '\r'; | case 'f': | |||
case 's': return IsAsciiWhiteSpace(ch); | return ch == '\f'; | |||
case 'S': return !IsAsciiWhiteSpace(ch); | case 'n': | |||
case 't': return ch == '\t'; | return ch == '\n'; | |||
case 'v': return ch == '\v'; | case 'r': | |||
case 'w': return IsAsciiWordChar(ch); | return ch == '\r'; | |||
case 'W': return !IsAsciiWordChar(ch); | case 's': | |||
return IsAsciiWhiteSpace(ch); | ||||
case 'S': | ||||
return !IsAsciiWhiteSpace(ch); | ||||
case 't': | ||||
return ch == '\t'; | ||||
case 'v': | ||||
return ch == '\v'; | ||||
case 'w': | ||||
return IsAsciiWordChar(ch); | ||||
case 'W': | ||||
return !IsAsciiWordChar(ch); | ||||
} | } | |||
return IsAsciiPunct(pattern_char) && pattern_char == ch; | return IsAsciiPunct(pattern_char) && pattern_char == ch; | |||
} | } | |||
return (pattern_char == '.' && ch != '\n') || pattern_char == ch; | return (pattern_char == '.' && ch != '\n') || pattern_char == ch; | |||
} | } | |||
// Helper function used by ValidateRegex() to format error messages. | // Helper function used by ValidateRegex() to format error messages. | |||
static std::string FormatRegexSyntaxError(const char* regex, int index) { | static std::string FormatRegexSyntaxError(const char* regex, int index) { | |||
return (Message() << "Syntax error at index " << index | return (Message() << "Syntax error at index " << index | |||
<< " in simple regular expression \"" << regex << "\": ").GetString(); | << " in simple regular expression \"" << regex << "\": ") | |||
.GetString(); | ||||
} | } | |||
// Generates non-fatal failures and returns false if regex is invalid; | // Generates non-fatal failures and returns false if regex is invalid; | |||
// otherwise returns true. | // otherwise returns true. | |||
bool ValidateRegex(const char* regex) { | bool ValidateRegex(const char* regex) { | |||
if (regex == nullptr) { | if (regex == nullptr) { | |||
ADD_FAILURE() << "NULL is not a valid simple regular expression."; | ADD_FAILURE() << "NULL is not a valid simple regular expression."; | |||
return false; | return false; | |||
} | } | |||
skipping to change at line 864 | skipping to change at line 828 | |||
if (ch == '^' && i > 0) { | if (ch == '^' && i > 0) { | |||
ADD_FAILURE() << FormatRegexSyntaxError(regex, i) | ADD_FAILURE() << FormatRegexSyntaxError(regex, i) | |||
<< "'^' can only appear at the beginning."; | << "'^' can only appear at the beginning."; | |||
is_valid = false; | is_valid = false; | |||
} else if (ch == '$' && regex[i + 1] != '\0') { | } else if (ch == '$' && regex[i + 1] != '\0') { | |||
ADD_FAILURE() << FormatRegexSyntaxError(regex, i) | ADD_FAILURE() << FormatRegexSyntaxError(regex, i) | |||
<< "'$' can only appear at the end."; | << "'$' can only appear at the end."; | |||
is_valid = false; | is_valid = false; | |||
} else if (IsInSet(ch, "()[]{}|")) { | } else if (IsInSet(ch, "()[]{}|")) { | |||
ADD_FAILURE() << FormatRegexSyntaxError(regex, i) | ADD_FAILURE() << FormatRegexSyntaxError(regex, i) << "'" << ch | |||
<< "'" << ch << "' is unsupported."; | << "' is unsupported."; | |||
is_valid = false; | is_valid = false; | |||
} else if (IsRepeat(ch) && !prev_repeatable) { | } else if (IsRepeat(ch) && !prev_repeatable) { | |||
ADD_FAILURE() << FormatRegexSyntaxError(regex, i) | ADD_FAILURE() << FormatRegexSyntaxError(regex, i) << "'" << ch | |||
<< "'" << ch << "' can only follow a repeatable token."; | << "' can only follow a repeatable token."; | |||
is_valid = false; | is_valid = false; | |||
} | } | |||
prev_repeatable = !IsInSet(ch, "^$?*+"); | prev_repeatable = !IsInSet(ch, "^$?*+"); | |||
} | } | |||
} | } | |||
return is_valid; | return is_valid; | |||
} | } | |||
// Matches a repeated regex atom followed by a valid simple regular | // Matches a repeated regex atom followed by a valid simple regular | |||
// expression. The regex atom is defined as c if escaped is false, | // expression. The regex atom is defined as c if escaped is false, | |||
// or \c otherwise. repeat is the repetition meta character (?, *, | // or \c otherwise. repeat is the repetition meta character (?, *, | |||
// or +). The behavior is undefined if str contains too many | // or +). The behavior is undefined if str contains too many | |||
// characters to be indexable by size_t, in which case the test will | // characters to be indexable by size_t, in which case the test will | |||
// probably time out anyway. We are fine with this limitation as | // probably time out anyway. We are fine with this limitation as | |||
// std::string has it too. | // std::string has it too. | |||
bool MatchRepetitionAndRegexAtHead( | bool MatchRepetitionAndRegexAtHead(bool escaped, char c, char repeat, | |||
bool escaped, char c, char repeat, const char* regex, | const char* regex, const char* str) { | |||
const char* str) { | ||||
const size_t min_count = (repeat == '+') ? 1 : 0; | const size_t min_count = (repeat == '+') ? 1 : 0; | |||
const size_t max_count = (repeat == '?') ? 1 : | const size_t max_count = (repeat == '?') ? 1 : static_cast<size_t>(-1) - 1; | |||
static_cast<size_t>(-1) - 1; | ||||
// We cannot call numeric_limits::max() as it conflicts with the | // We cannot call numeric_limits::max() as it conflicts with the | |||
// max() macro on Windows. | // max() macro on Windows. | |||
for (size_t i = 0; i <= max_count; ++i) { | for (size_t i = 0; i <= max_count; ++i) { | |||
// We know that the atom matches each of the first i characters in str. | // We know that the atom matches each of the first i characters in str. | |||
if (i >= min_count && MatchRegexAtHead(regex, str + i)) { | if (i >= min_count && MatchRegexAtHead(regex, str + i)) { | |||
// We have enough matches at the head, and the tail matches too. | // We have enough matches at the head, and the tail matches too. | |||
// Since we only care about *whether* the pattern matches str | // Since we only care about *whether* the pattern matches str | |||
// (as opposed to *how* it matches), there is no need to find a | // (as opposed to *how* it matches), there is no need to find a | |||
// greedy match. | // greedy match. | |||
return true; | return true; | |||
} | } | |||
if (str[i] == '\0' || !AtomMatchesChar(escaped, c, str[i])) | if (str[i] == '\0' || !AtomMatchesChar(escaped, c, str[i])) return false; | |||
return false; | ||||
} | } | |||
return false; | return false; | |||
} | } | |||
// Returns true if and only if regex matches a prefix of str. regex must | // Returns true if and only if regex matches a prefix of str. regex must | |||
// be a valid simple regular expression and not start with "^", or the | // be a valid simple regular expression and not start with "^", or the | |||
// result is undefined. | // result is undefined. | |||
bool MatchRegexAtHead(const char* regex, const char* str) { | bool MatchRegexAtHead(const char* regex, const char* str) { | |||
if (*regex == '\0') // An empty regex matches a prefix of anything. | if (*regex == '\0') // An empty regex matches a prefix of anything. | |||
return true; | return true; | |||
// "$" only matches the end of a string. Note that regex being | // "$" only matches the end of a string. Note that regex being | |||
// valid guarantees that there's nothing after "$" in it. | // valid guarantees that there's nothing after "$" in it. | |||
if (*regex == '$') | if (*regex == '$') return *str == '\0'; | |||
return *str == '\0'; | ||||
// Is the first thing in regex an escape sequence? | // Is the first thing in regex an escape sequence? | |||
const bool escaped = *regex == '\\'; | const bool escaped = *regex == '\\'; | |||
if (escaped) | if (escaped) ++regex; | |||
++regex; | ||||
if (IsRepeat(regex[1])) { | if (IsRepeat(regex[1])) { | |||
// MatchRepetitionAndRegexAtHead() calls MatchRegexAtHead(), so | // MatchRepetitionAndRegexAtHead() calls MatchRegexAtHead(), so | |||
// here's an indirect recursion. It terminates as the regex gets | // here's an indirect recursion. It terminates as the regex gets | |||
// shorter in each recursion. | // shorter in each recursion. | |||
return MatchRepetitionAndRegexAtHead( | return MatchRepetitionAndRegexAtHead(escaped, regex[0], regex[1], regex + 2, | |||
escaped, regex[0], regex[1], regex + 2, str); | str); | |||
} else { | } else { | |||
// regex isn't empty, isn't "$", and doesn't start with a | // regex isn't empty, isn't "$", and doesn't start with a | |||
// repetition. We match the first atom of regex with the first | // repetition. We match the first atom of regex with the first | |||
// character of str and recurse. | // character of str and recurse. | |||
return (*str != '\0') && AtomMatchesChar(escaped, *regex, *str) && | return (*str != '\0') && AtomMatchesChar(escaped, *regex, *str) && | |||
MatchRegexAtHead(regex + 1, str + 1); | MatchRegexAtHead(regex + 1, str + 1); | |||
} | } | |||
} | } | |||
// Returns true if and only if regex matches any substring of str. regex must | // Returns true if and only if regex matches any substring of str. regex must | |||
// be a valid simple regular expression, or the result is undefined. | // be a valid simple regular expression, or the result is undefined. | |||
// | // | |||
// The algorithm is recursive, but the recursion depth doesn't exceed | // The algorithm is recursive, but the recursion depth doesn't exceed | |||
// the regex length, so we won't need to worry about running out of | // the regex length, so we won't need to worry about running out of | |||
// stack space normally. In rare cases the time complexity can be | // stack space normally. In rare cases the time complexity can be | |||
// exponential with respect to the regex length + the string length, | // exponential with respect to the regex length + the string length, | |||
// but usually it's must faster (often close to linear). | // but usually it's must faster (often close to linear). | |||
bool MatchRegexAnywhere(const char* regex, const char* str) { | bool MatchRegexAnywhere(const char* regex, const char* str) { | |||
if (regex == nullptr || str == nullptr) return false; | if (regex == nullptr || str == nullptr) return false; | |||
if (*regex == '^') | if (*regex == '^') return MatchRegexAtHead(regex + 1, str); | |||
return MatchRegexAtHead(regex + 1, str); | ||||
// A successful match can be anywhere in str. | // A successful match can be anywhere in str. | |||
do { | do { | |||
if (MatchRegexAtHead(regex, str)) | if (MatchRegexAtHead(regex, str)) return true; | |||
return true; | ||||
} while (*str++ != '\0'); | } while (*str++ != '\0'); | |||
return false; | return false; | |||
} | } | |||
// Implements the RE class. | // Implements the RE class. | |||
RE::~RE() { | RE::~RE() { | |||
free(const_cast<char*>(pattern_)); | free(const_cast<char*>(pattern_)); | |||
free(const_cast<char*>(full_pattern_)); | free(const_cast<char*>(full_pattern_)); | |||
} | } | |||
skipping to change at line 1040 | skipping to change at line 997 | |||
#else | #else | |||
return file_name + ":" + StreamableToString(line) + ":"; | return file_name + ":" + StreamableToString(line) + ":"; | |||
#endif // _MSC_VER | #endif // _MSC_VER | |||
} | } | |||
// Formats a file location for compiler-independent XML output. | // Formats a file location for compiler-independent XML output. | |||
// Although this function is not platform dependent, we put it next to | // Although this function is not platform dependent, we put it next to | |||
// FormatFileLocation in order to contrast the two functions. | // FormatFileLocation in order to contrast the two functions. | |||
// Note that FormatCompilerIndependentFileLocation() does NOT append colon | // Note that FormatCompilerIndependentFileLocation() does NOT append colon | |||
// to the file location it produces, unlike FormatFileLocation(). | // to the file location it produces, unlike FormatFileLocation(). | |||
GTEST_API_ ::std::string FormatCompilerIndependentFileLocation( | GTEST_API_ ::std::string FormatCompilerIndependentFileLocation(const char* file, | |||
const char* file, int line) { | int line) { | |||
const std::string file_name(file == nullptr ? kUnknownFile : file); | const std::string file_name(file == nullptr ? kUnknownFile : file); | |||
if (line < 0) | if (line < 0) | |||
return file_name; | return file_name; | |||
else | else | |||
return file_name + ":" + StreamableToString(line); | return file_name + ":" + StreamableToString(line); | |||
} | } | |||
GTestLog::GTestLog(GTestLogSeverity severity, const char* file, int line) | GTestLog::GTestLog(GTestLogSeverity severity, const char* file, int line) | |||
: severity_(severity) { | : severity_(severity) { | |||
const char* const marker = | const char* const marker = severity == GTEST_INFO ? "[ INFO ]" | |||
severity == GTEST_INFO ? "[ INFO ]" : | : severity == GTEST_WARNING ? "[WARNING]" | |||
severity == GTEST_WARNING ? "[WARNING]" : | : severity == GTEST_ERROR ? "[ ERROR ]" | |||
severity == GTEST_ERROR ? "[ ERROR ]" : "[ FATAL ]"; | : "[ FATAL ]"; | |||
GetStream() << ::std::endl << marker << " " | GetStream() << ::std::endl | |||
<< FormatFileLocation(file, line).c_str() << ": "; | << marker << " " << FormatFileLocation(file, line).c_str() | |||
<< ": "; | ||||
} | } | |||
// Flushes the buffers and, if severity is GTEST_FATAL, aborts the program. | // Flushes the buffers and, if severity is GTEST_FATAL, aborts the program. | |||
GTestLog::~GTestLog() { | GTestLog::~GTestLog() { | |||
GetStream() << ::std::endl; | GetStream() << ::std::endl; | |||
if (severity_ == GTEST_FATAL) { | if (severity_ == GTEST_FATAL) { | |||
fflush(stderr); | fflush(stderr); | |||
posix::Abort(); | posix::Abort(); | |||
} | } | |||
} | } | |||
skipping to change at line 1080 | skipping to change at line 1038 | |||
// this class (creat, dup, dup2, and close) | // this class (creat, dup, dup2, and close) | |||
GTEST_DISABLE_MSC_DEPRECATED_PUSH_() | GTEST_DISABLE_MSC_DEPRECATED_PUSH_() | |||
#if GTEST_HAS_STREAM_REDIRECTION | #if GTEST_HAS_STREAM_REDIRECTION | |||
// Object that captures an output stream (stdout/stderr). | // Object that captures an output stream (stdout/stderr). | |||
class CapturedStream { | class CapturedStream { | |||
public: | public: | |||
// The ctor redirects the stream to a temporary file. | // The ctor redirects the stream to a temporary file. | |||
explicit CapturedStream(int fd) : fd_(fd), uncaptured_fd_(dup(fd)) { | explicit CapturedStream(int fd) : fd_(fd), uncaptured_fd_(dup(fd)) { | |||
# if GTEST_OS_WINDOWS | #if GTEST_OS_WINDOWS | |||
char temp_dir_path[MAX_PATH + 1] = { '\0' }; // NOLINT | char temp_dir_path[MAX_PATH + 1] = {'\0'}; // NOLINT | |||
char temp_file_path[MAX_PATH + 1] = { '\0' }; // NOLINT | char temp_file_path[MAX_PATH + 1] = {'\0'}; // NOLINT | |||
::GetTempPathA(sizeof(temp_dir_path), temp_dir_path); | ::GetTempPathA(sizeof(temp_dir_path), temp_dir_path); | |||
const UINT success = ::GetTempFileNameA(temp_dir_path, | const UINT success = ::GetTempFileNameA(temp_dir_path, "gtest_redir", | |||
"gtest_redir", | ||||
0, // Generate unique file name. | 0, // Generate unique file name. | |||
temp_file_path); | temp_file_path); | |||
GTEST_CHECK_(success != 0) | GTEST_CHECK_(success != 0) | |||
<< "Unable to create a temporary file in " << temp_dir_path; | << "Unable to create a temporary file in " << temp_dir_path; | |||
const int captured_fd = creat(temp_file_path, _S_IREAD | _S_IWRITE); | const int captured_fd = creat(temp_file_path, _S_IREAD | _S_IWRITE); | |||
GTEST_CHECK_(captured_fd != -1) << "Unable to open temporary file " | GTEST_CHECK_(captured_fd != -1) | |||
<< temp_file_path; | << "Unable to open temporary file " << temp_file_path; | |||
filename_ = temp_file_path; | filename_ = temp_file_path; | |||
# else | #else | |||
// There's no guarantee that a test has write access to the current | // There's no guarantee that a test has write access to the current | |||
// directory, so we create the temporary file in a temporary directory. | // directory, so we create the temporary file in a temporary directory. | |||
std::string name_template; | std::string name_template; | |||
# if GTEST_OS_LINUX_ANDROID | #if GTEST_OS_LINUX_ANDROID | |||
// Note: Android applications are expected to call the framework's | // Note: Android applications are expected to call the framework's | |||
// Context.getExternalStorageDirectory() method through JNI to get | // Context.getExternalStorageDirectory() method through JNI to get | |||
// the location of the world-writable SD Card directory. However, | // the location of the world-writable SD Card directory. However, | |||
// this requires a Context handle, which cannot be retrieved | // this requires a Context handle, which cannot be retrieved | |||
// globally from native code. Doing so also precludes running the | // globally from native code. Doing so also precludes running the | |||
// code as part of a regular standalone executable, which doesn't | // code as part of a regular standalone executable, which doesn't | |||
// run in a Dalvik process (e.g. when running it through 'adb shell'). | // run in a Dalvik process (e.g. when running it through 'adb shell'). | |||
// | // | |||
// The location /data/local/tmp is directly accessible from native code. | // The location /data/local/tmp is directly accessible from native code. | |||
// '/sdcard' and other variants cannot be relied on, as they are not | // '/sdcard' and other variants cannot be relied on, as they are not | |||
// guaranteed to be mounted, or may have a delay in mounting. | // guaranteed to be mounted, or may have a delay in mounting. | |||
name_template = "/data/local/tmp/"; | name_template = "/data/local/tmp/"; | |||
# elif GTEST_OS_IOS | #elif GTEST_OS_IOS | |||
char user_temp_dir[PATH_MAX + 1]; | char user_temp_dir[PATH_MAX + 1]; | |||
// Documented alternative to NSTemporaryDirectory() (for obtaining creating | // Documented alternative to NSTemporaryDirectory() (for obtaining creating | |||
// a temporary directory) at | // a temporary directory) at | |||
// https://developer.apple.com/library/archive/documentation/Security/Concep tual/SecureCodingGuide/Articles/RaceConditions.html#//apple_ref/doc/uid/TP400025 85-SW10 | // https://developer.apple.com/library/archive/documentation/Security/Concep tual/SecureCodingGuide/Articles/RaceConditions.html#//apple_ref/doc/uid/TP400025 85-SW10 | |||
// | // | |||
// _CS_DARWIN_USER_TEMP_DIR (as well as _CS_DARWIN_USER_CACHE_DIR) is not | // _CS_DARWIN_USER_TEMP_DIR (as well as _CS_DARWIN_USER_CACHE_DIR) is not | |||
// documented in the confstr() man page at | // documented in the confstr() man page at | |||
// https://developer.apple.com/library/archive/documentation/System/Conceptu al/ManPages_iPhoneOS/man3/confstr.3.html#//apple_ref/doc/man/3/confstr | // https://developer.apple.com/library/archive/documentation/System/Conceptu al/ManPages_iPhoneOS/man3/confstr.3.html#//apple_ref/doc/man/3/confstr | |||
// but are still available, according to the WebKit patches at | // but are still available, according to the WebKit patches at | |||
// https://trac.webkit.org/changeset/262004/webkit | // https://trac.webkit.org/changeset/262004/webkit | |||
// https://trac.webkit.org/changeset/263705/webkit | // https://trac.webkit.org/changeset/263705/webkit | |||
// | // | |||
// The confstr() implementation falls back to getenv("TMPDIR"). See | // The confstr() implementation falls back to getenv("TMPDIR"). See | |||
// https://opensource.apple.com/source/Libc/Libc-1439.100.3/gen/confstr.c.au to.html | // https://opensource.apple.com/source/Libc/Libc-1439.100.3/gen/confstr.c.au to.html | |||
::confstr(_CS_DARWIN_USER_TEMP_DIR, user_temp_dir, sizeof(user_temp_dir)); | ::confstr(_CS_DARWIN_USER_TEMP_DIR, user_temp_dir, sizeof(user_temp_dir)); | |||
name_template = user_temp_dir; | name_template = user_temp_dir; | |||
if (name_template.back() != GTEST_PATH_SEP_[0]) | if (name_template.back() != GTEST_PATH_SEP_[0]) | |||
name_template.push_back(GTEST_PATH_SEP_[0]); | name_template.push_back(GTEST_PATH_SEP_[0]); | |||
# else | #else | |||
name_template = "/tmp/"; | name_template = "/tmp/"; | |||
# endif | #endif | |||
name_template.append("gtest_captured_stream.XXXXXX"); | name_template.append("gtest_captured_stream.XXXXXX"); | |||
// mkstemp() modifies the string bytes in place, and does not go beyond the | // mkstemp() modifies the string bytes in place, and does not go beyond the | |||
// string's length. This results in well-defined behavior in C++17. | // string's length. This results in well-defined behavior in C++17. | |||
// | // | |||
// The const_cast is needed below C++17. The constraints on std::string | // The const_cast is needed below C++17. The constraints on std::string | |||
// implementations in C++11 and above make assumption behind the const_cast | // implementations in C++11 and above make assumption behind the const_cast | |||
// fairly safe. | // fairly safe. | |||
const int captured_fd = ::mkstemp(const_cast<char*>(name_template.data())); | const int captured_fd = ::mkstemp(const_cast<char*>(name_template.data())); | |||
if (captured_fd == -1) { | if (captured_fd == -1) { | |||
GTEST_LOG_(WARNING) | GTEST_LOG_(WARNING) | |||
<< "Failed to create tmp file " << name_template | << "Failed to create tmp file " << name_template | |||
<< " for test; does the test have access to the /tmp directory?"; | << " for test; does the test have access to the /tmp directory?"; | |||
} | } | |||
filename_ = std::move(name_template); | filename_ = std::move(name_template); | |||
# endif // GTEST_OS_WINDOWS | #endif // GTEST_OS_WINDOWS | |||
fflush(nullptr); | fflush(nullptr); | |||
dup2(captured_fd, fd_); | dup2(captured_fd, fd_); | |||
close(captured_fd); | close(captured_fd); | |||
} | } | |||
~CapturedStream() { | ~CapturedStream() { remove(filename_.c_str()); } | |||
remove(filename_.c_str()); | ||||
} | ||||
std::string GetCapturedString() { | std::string GetCapturedString() { | |||
if (uncaptured_fd_ != -1) { | if (uncaptured_fd_ != -1) { | |||
// Restores the original stream. | // Restores the original stream. | |||
fflush(nullptr); | fflush(nullptr); | |||
dup2(uncaptured_fd_, fd_); | dup2(uncaptured_fd_, fd_); | |||
close(uncaptured_fd_); | close(uncaptured_fd_); | |||
uncaptured_fd_ = -1; | uncaptured_fd_ = -1; | |||
} | } | |||
skipping to change at line 1187 | skipping to change at line 1142 | |||
posix::FClose(file); | posix::FClose(file); | |||
return content; | return content; | |||
} | } | |||
private: | private: | |||
const int fd_; // A stream to capture. | const int fd_; // A stream to capture. | |||
int uncaptured_fd_; | int uncaptured_fd_; | |||
// Name of the temporary file holding the stderr output. | // Name of the temporary file holding the stderr output. | |||
::std::string filename_; | ::std::string filename_; | |||
GTEST_DISALLOW_COPY_AND_ASSIGN_(CapturedStream); | CapturedStream(const CapturedStream&) = delete; | |||
CapturedStream& operator=(const CapturedStream&) = delete; | ||||
}; | }; | |||
GTEST_DISABLE_MSC_DEPRECATED_POP_() | GTEST_DISABLE_MSC_DEPRECATED_POP_() | |||
static CapturedStream* g_captured_stderr = nullptr; | static CapturedStream* g_captured_stderr = nullptr; | |||
static CapturedStream* g_captured_stdout = nullptr; | static CapturedStream* g_captured_stdout = nullptr; | |||
// Starts capturing an output stream (stdout/stderr). | // Starts capturing an output stream (stdout/stderr). | |||
static void CaptureStream(int fd, const char* stream_name, | static void CaptureStream(int fd, const char* stream_name, | |||
CapturedStream** stream) { | CapturedStream** stream) { | |||
skipping to change at line 1215 | skipping to change at line 1171 | |||
// Stops capturing the output stream and returns the captured string. | // Stops capturing the output stream and returns the captured string. | |||
static std::string GetCapturedStream(CapturedStream** captured_stream) { | static std::string GetCapturedStream(CapturedStream** captured_stream) { | |||
const std::string content = (*captured_stream)->GetCapturedString(); | const std::string content = (*captured_stream)->GetCapturedString(); | |||
delete *captured_stream; | delete *captured_stream; | |||
*captured_stream = nullptr; | *captured_stream = nullptr; | |||
return content; | return content; | |||
} | } | |||
#if defined(_MSC_VER) || defined(__BORLANDC__) | ||||
// MSVC and C++Builder do not provide a definition of STDERR_FILENO. | ||||
const int kStdOutFileno = 1; | ||||
const int kStdErrFileno = 2; | ||||
#else | ||||
const int kStdOutFileno = STDOUT_FILENO; | ||||
const int kStdErrFileno = STDERR_FILENO; | ||||
#endif // defined(_MSC_VER) || defined(__BORLANDC__) | ||||
// Starts capturing stdout. | // Starts capturing stdout. | |||
void CaptureStdout() { | void CaptureStdout() { | |||
CaptureStream(kStdOutFileno, "stdout", &g_captured_stdout); | CaptureStream(kStdOutFileno, "stdout", &g_captured_stdout); | |||
} | } | |||
// Starts capturing stderr. | // Starts capturing stderr. | |||
void CaptureStderr() { | void CaptureStderr() { | |||
CaptureStream(kStdErrFileno, "stderr", &g_captured_stderr); | CaptureStream(kStdErrFileno, "stderr", &g_captured_stderr); | |||
} | } | |||
skipping to change at line 1254 | skipping to change at line 1219 | |||
char* const buffer = new char[file_size]; | char* const buffer = new char[file_size]; | |||
size_t bytes_last_read = 0; // # of bytes read in the last fread() | size_t bytes_last_read = 0; // # of bytes read in the last fread() | |||
size_t bytes_read = 0; // # of bytes read so far | size_t bytes_read = 0; // # of bytes read so far | |||
fseek(file, 0, SEEK_SET); | fseek(file, 0, SEEK_SET); | |||
// Keeps reading the file until we cannot read further or the | // Keeps reading the file until we cannot read further or the | |||
// pre-determined file size is reached. | // pre-determined file size is reached. | |||
do { | do { | |||
bytes_last_read = fread(buffer+bytes_read, 1, file_size-bytes_read, file); | bytes_last_read = | |||
fread(buffer + bytes_read, 1, file_size - bytes_read, file); | ||||
bytes_read += bytes_last_read; | bytes_read += bytes_last_read; | |||
} while (bytes_last_read > 0 && bytes_read < file_size); | } while (bytes_last_read > 0 && bytes_read < file_size); | |||
const std::string content(buffer, bytes_read); | const std::string content(buffer, bytes_read); | |||
delete[] buffer; | delete[] buffer; | |||
return content; | return content; | |||
} | } | |||
#if GTEST_HAS_DEATH_TEST | #if GTEST_HAS_DEATH_TEST | |||
skipping to change at line 1342 | skipping to change at line 1308 | |||
return false; | return false; | |||
} | } | |||
// Is the parsed value in the range of an int32_t? | // Is the parsed value in the range of an int32_t? | |||
const auto result = static_cast<int32_t>(long_value); | const auto result = static_cast<int32_t>(long_value); | |||
if (long_value == LONG_MAX || long_value == LONG_MIN || | if (long_value == LONG_MAX || long_value == LONG_MIN || | |||
// The parsed value overflows as a long. (strtol() returns | // The parsed value overflows as a long. (strtol() returns | |||
// LONG_MAX or LONG_MIN when the input overflows.) | // LONG_MAX or LONG_MIN when the input overflows.) | |||
result != long_value | result != long_value | |||
// The parsed value overflows as an int32_t. | // The parsed value overflows as an int32_t. | |||
) { | ) { | |||
Message msg; | Message msg; | |||
msg << "WARNING: " << src_text | msg << "WARNING: " << src_text | |||
<< " is expected to be a 32-bit integer, but actually" | << " is expected to be a 32-bit integer, but actually" | |||
<< " has value " << str << ", which overflows.\n"; | << " has value " << str << ", which overflows.\n"; | |||
printf("%s", msg.GetString().c_str()); | printf("%s", msg.GetString().c_str()); | |||
fflush(stdout); | fflush(stdout); | |||
return false; | return false; | |||
} | } | |||
*value = result; | *value = result; | |||
skipping to change at line 1386 | skipping to change at line 1352 | |||
return GTEST_GET_INT32_FROM_ENV_(flag, default_value); | return GTEST_GET_INT32_FROM_ENV_(flag, default_value); | |||
#else | #else | |||
const std::string env_var = FlagToEnvVar(flag); | const std::string env_var = FlagToEnvVar(flag); | |||
const char* const string_value = posix::GetEnv(env_var.c_str()); | const char* const string_value = posix::GetEnv(env_var.c_str()); | |||
if (string_value == nullptr) { | if (string_value == nullptr) { | |||
// The environment variable is not set. | // The environment variable is not set. | |||
return default_value; | return default_value; | |||
} | } | |||
int32_t result = default_value; | int32_t result = default_value; | |||
if (!ParseInt32(Message() << "Environment variable " << env_var, | if (!ParseInt32(Message() << "Environment variable " << env_var, string_value, | |||
string_value, &result)) { | &result)) { | |||
printf("The default value %s is used.\n", | printf("The default value %s is used.\n", | |||
(Message() << default_value).GetString().c_str()); | (Message() << default_value).GetString().c_str()); | |||
fflush(stdout); | fflush(stdout); | |||
return default_value; | return default_value; | |||
} | } | |||
return result; | return result; | |||
#endif // defined(GTEST_GET_INT32_FROM_ENV_) | #endif // defined(GTEST_GET_INT32_FROM_ENV_) | |||
} | } | |||
// As a special case for the 'output' flag, if GTEST_OUTPUT is not | // As a special case for the 'output' flag, if GTEST_OUTPUT is not | |||
// set, we look for XML_OUTPUT_FILE, which is set by the Bazel build | // set, we look for XML_OUTPUT_FILE, which is set by the Bazel build | |||
// system. The value of XML_OUTPUT_FILE is a filename without the | // system. The value of XML_OUTPUT_FILE is a filename without the | |||
// "xml:" prefix of GTEST_OUTPUT. | // "xml:" prefix of GTEST_OUTPUT. | |||
// Note that this is meant to be called at the call site so it does | // Note that this is meant to be called at the call site so it does | |||
// not check that the flag is 'output' | // not check that the flag is 'output' | |||
// In essence this checks an env variable called XML_OUTPUT_FILE | // In essence this checks an env variable called XML_OUTPUT_FILE | |||
// and if it is set we prepend "xml:" to its value, if it not set we return "" | // and if it is set we prepend "xml:" to its value, if it not set we return "" | |||
std::string OutputFlagAlsoCheckEnvVar(){ | std::string OutputFlagAlsoCheckEnvVar() { | |||
std::string default_value_for_output_flag = ""; | std::string default_value_for_output_flag = ""; | |||
const char* xml_output_file_env = posix::GetEnv("XML_OUTPUT_FILE"); | const char* xml_output_file_env = posix::GetEnv("XML_OUTPUT_FILE"); | |||
if (nullptr != xml_output_file_env) { | if (nullptr != xml_output_file_env) { | |||
default_value_for_output_flag = std::string("xml:") + xml_output_file_env; | default_value_for_output_flag = std::string("xml:") + xml_output_file_env; | |||
} | } | |||
return default_value_for_output_flag; | return default_value_for_output_flag; | |||
} | } | |||
// Reads and returns the string environment variable corresponding to | // Reads and returns the string environment variable corresponding to | |||
// the given flag; if it's not set, returns default_value. | // the given flag; if it's not set, returns default_value. | |||
End of changes. 75 change blocks. | ||||
190 lines changed or deleted | 155 lines changed or added |